Просмотр исходного кода

Merge pull request #9550 from weseek/fix/escape-regexp-when-finding-ancestors-children

fix: Escape page path when generating RegExp to find ancestors children
mergify[bot] 1 год назад
Родитель
Сommit
ffa0fefb16

+ 5 - 0
.changeset/little-toys-reflect.md

@@ -0,0 +1,5 @@
+---
+'@growi/core': patch
+---
+
+Fix generateChildrenRegExp method

+ 1 - 1
apps/app/src/server/service/page/index.ts

@@ -4391,7 +4391,7 @@ class PageService implements IPageService {
     const Page = mongoose.model('Page') as unknown as PageModel;
 
     const ancestorPaths = isTopPage(path) ? ['/'] : collectAncestorPaths(path); // root path is necessary for rendering
-    const regexps = ancestorPaths.map(path => new RegExp(generateChildrenRegExp(path))); // cannot use re2
+    const regexps = ancestorPaths.map(path => generateChildrenRegExp(path)); // cannot use re2
 
     // get pages at once
     const queryBuilder = new PageQueryBuilder(Page.find({ path: { $in: regexps } }), true);

+ 55 - 0
packages/core/src/utils/page-path-utils/generate-children-regexp.spec.ts

@@ -0,0 +1,55 @@
+import { describe, expect, test } from 'vitest';
+
+import { generateChildrenRegExp } from './generate-children-regexp';
+
+describe('generateChildrenRegExp', () => {
+  describe.each([
+    {
+      path: '/',
+      expected: '^\\/[^/]+$',
+      validPaths: ['/child', '/test'],
+      invalidPaths: ['/', '/child/grandchild'],
+    },
+    {
+      path: '/parent',
+      expected: '^\\/parent(\\/[^/]+)\\/?$',
+      validPaths: ['/parent/child', '/parent/test'],
+      invalidPaths: ['/parent', '/parent/child/grandchild', '/other/path'],
+    },
+    {
+      path: '/parent (with brackets)',
+      expected: '^\\/parent \\(with brackets\\)(\\/[^/]+)\\/?$',
+      validPaths: ['/parent (with brackets)/child', '/parent (with brackets)/test'],
+      invalidPaths: ['/parent (with brackets)', '/parent (with brackets)/child/grandchild'],
+    },
+    {
+      path: '/parent[with square]',
+      expected: '^\\/parent\\[with square\\](\\/[^/]+)\\/?$',
+      validPaths: ['/parent[with square]/child', '/parent[with square]/test'],
+      invalidPaths: ['/parent[with square]', '/parent[with square]/child/grandchild'],
+    },
+    {
+      path: '/parent*with+special?chars',
+      expected: '^\\/parent\\*with\\+special\\?chars(\\/[^/]+)\\/?$',
+      validPaths: ['/parent*with+special?chars/child', '/parent*with+special?chars/test'],
+      invalidPaths: ['/parent*with+special?chars', '/parent*with+special?chars/child/grandchild'],
+    },
+  ])('with path: $path', ({
+    path, expected, validPaths, invalidPaths,
+  }) => {
+    test('should generate correct regexp pattern', () => {
+      const result = generateChildrenRegExp(path);
+      expect(result.source).toBe(expected);
+    });
+
+    test.each(validPaths)('should match valid path: %s', (validPath) => {
+      const result = generateChildrenRegExp(path);
+      expect(validPath).toMatch(result);
+    });
+
+    test.each(invalidPaths)('should not match invalid path: %s', (invalidPath) => {
+      const result = generateChildrenRegExp(path);
+      expect(invalidPath).not.toMatch(result);
+    });
+  });
+});

+ 16 - 0
packages/core/src/utils/page-path-utils/generate-children-regexp.ts

@@ -0,0 +1,16 @@
+import escapeStringRegexp from 'escape-string-regexp';
+
+import { isTopPage } from './is-top-page';
+
+/**
+ * Generate RegExp instance for one level lower path
+ */
+export const generateChildrenRegExp = (path: string): RegExp => {
+  // https://regex101.com/r/laJGzj/1
+  // ex. /any_level1
+  if (isTopPage(path)) return new RegExp(/^\/[^/]+$/);
+
+  // https://regex101.com/r/mrDJrx/1
+  // ex. /parent/any_child OR /any_level1
+  return new RegExp(`^${escapeStringRegexp(path)}(\\/[^/]+)\\/?$`);
+};

+ 1 - 13
packages/core/src/utils/page-path-utils/index.ts

@@ -8,6 +8,7 @@ import { addTrailingSlash } from '../path-utils';
 import { isTopPage as _isTopPage } from './is-top-page';
 
 export const isTopPage = _isTopPage;
+export * from './generate-children-regexp';
 
 /**
  * Whether path is the top page of users
@@ -276,19 +277,6 @@ export const hasSlash = (str: string): boolean => {
   return str.includes('/');
 };
 
-/**
- * Generate RegExp instance for one level lower path
- */
-export const generateChildrenRegExp = (path: string): RegExp => {
-  // https://regex101.com/r/laJGzj/1
-  // ex. /any_level1
-  if (isTopPage(path)) return new RegExp(/^\/[^/]+$/);
-
-  // https://regex101.com/r/mrDJrx/1
-  // ex. /parent/any_child OR /any_level1
-  return new RegExp(`^${path}(\\/[^/]+)\\/?$`);
-};
-
 /**
  * Get username from user page path
  * @param path string