From 486a2ef2f850023593395b69897468ae89bb6023 Mon Sep 17 00:00:00 2001 From: Crash-- Date: Wed, 11 Feb 2026 15:19:34 +0100 Subject: [PATCH] fix: Sanitize index names to support CouchDB > 3.3.3 I didn't find a commit from CouchDB relative to this issue, but it's somewhere between 3.3.3 & 3.5.1. Partial filter values containing "/" (e.g. "/Settings") produced index names incompatible with new CouchDB. Percent-encode "/" as "%2F" (and "%" as "%25") to avoid naming issues while preserving uniqueness. Closes #1667 --- packages/cozy-pouch-link/src/mango.js | 15 +++++++++- packages/cozy-stack-client/src/mangoIndex.js | 15 +++++++++- .../cozy-stack-client/src/mangoIndex.spec.js | 28 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/cozy-pouch-link/src/mango.js b/packages/cozy-pouch-link/src/mango.js index f6fb351e30..360a031b0c 100644 --- a/packages/cozy-pouch-link/src/mango.js +++ b/packages/cozy-pouch-link/src/mango.js @@ -53,12 +53,25 @@ export const getIndexNameFromFields = (fields, partialFilter) => { const indexName = `by_${fields.join('_and_')}` if (partialFilter) { - return `${indexName}_filter_(${makeKeyFromPartialFilter(partialFilter)})` + const filterKey = makeKeyFromPartialFilter(partialFilter) + return sanitizeIndexName(`${indexName}_filter_(${filterKey})`) } return indexName } +/** + * Sanitize an index name to remove characters that are not compatible with + * CouchDB design document naming conventions (e.g. "/" breaks CouchDB > 3.3.3) + * https://github.com/linagora/cozy-client/issues/1667 + * + * @param {string} name - The index name to sanitize + * @returns {string} The sanitized index name + */ +const sanitizeIndexName = name => { + return name.replace(/%/g, '%25').replace(/\//g, '%2F') +} + /** * @function * @description Compute fields that should be indexed for a mango diff --git a/packages/cozy-stack-client/src/mangoIndex.js b/packages/cozy-stack-client/src/mangoIndex.js index c75be3e64b..661dbd72de 100644 --- a/packages/cozy-stack-client/src/mangoIndex.js +++ b/packages/cozy-stack-client/src/mangoIndex.js @@ -98,12 +98,25 @@ export const getIndexNameFromFields = (fields, partialFilter) => { const indexName = `by_${fields.join('_and_')}` if (partialFilter) { - return `${indexName}_filter_(${makeKeyFromPartialFilter(partialFilter)})` + const filterKey = makeKeyFromPartialFilter(partialFilter) + return sanitizeIndexName(`${indexName}_filter_(${filterKey})`) } return indexName } +/** + * Sanitize an index name to remove characters that are not compatible with + * CouchDB design document naming conventions (e.g. "/" breaks CouchDB > 3.3.3) + * https://github.com/linagora/cozy-client/issues/1667 + * + * @param {string} name - The index name to sanitize + * @returns {string} The sanitized index name + */ +const sanitizeIndexName = name => { + return name.replace(/%/g, '%25').replace(/\//g, '%2F') +} + /** * Transform sort into Array * diff --git a/packages/cozy-stack-client/src/mangoIndex.spec.js b/packages/cozy-stack-client/src/mangoIndex.spec.js index 0ded7cd07f..d523a15aa4 100644 --- a/packages/cozy-stack-client/src/mangoIndex.spec.js +++ b/packages/cozy-stack-client/src/mangoIndex.spec.js @@ -255,6 +255,34 @@ describe('getIndexNameFromFields', () => { ) }) + it('should sanitize slash characters from index name', () => { + const partialFilter = { + _id: { + $nin: ['io.cozy.files.trash-dir', 'io.cozy.files.shared-drives-dir'] + }, + path: { + $or: [{ $exists: false }, { $nin: ['/Settings'] }] + } + } + + const indexName = getIndexNameFromFields(fields, partialFilter) + expect(indexName).not.toContain('/') + expect(indexName).toContain('%2FSettings') + }) + + it('should produce different index names for values with and without slash', () => { + const withSlash = { + path: { $nin: ['/Settings'] } + } + const withoutSlash = { + path: { $nin: ['Settings'] } + } + + const nameWithSlash = getIndexNameFromFields(fields, withSlash) + const nameWithoutSlash = getIndexNameFromFields(fields, withoutSlash) + expect(nameWithSlash).not.toEqual(nameWithoutSlash) + }) + it('should return index fields with partial filter with $nor inside sub condition', () => { const partialFilter = { $nor: [