mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	feat(server): lint for trailing slashes in sync URL and extra slashes in customRequestHandler
This commit is contained in:
		
							parent
							
								
									2c87721953
								
							
						
					
					
						commit
						0fe89115d1
					
				| @ -5,7 +5,7 @@ import cls from "../services/cls.js"; | |||||||
| import sql from "../services/sql.js"; | import sql from "../services/sql.js"; | ||||||
| import becca from "../becca/becca.js"; | import becca from "../becca/becca.js"; | ||||||
| import type { Request, Response, Router } from "express"; | import type { Request, Response, Router } from "express"; | ||||||
| import { safeExtractMessageAndStackFromError } from "../services/utils.js"; | import { safeExtractMessageAndStackFromError, normalizeCustomHandlerPattern } from "../services/utils.js"; | ||||||
| 
 | 
 | ||||||
| function handleRequest(req: Request, res: Response) { | function handleRequest(req: Request, res: Response) { | ||||||
| 
 | 
 | ||||||
| @ -38,15 +38,23 @@ function handleRequest(req: Request, res: Response) { | |||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const regex = new RegExp(`^${attr.value}$`); |         // Get normalized patterns to handle both trailing slash cases
 | ||||||
|         let match; |         const patterns = normalizeCustomHandlerPattern(attr.value); | ||||||
|  |         let match = null; | ||||||
| 
 | 
 | ||||||
|         try { |         // Try each pattern until we find a match
 | ||||||
|             match = path.match(regex); |         for (const pattern of patterns) { | ||||||
|         } catch (e: unknown) { |             try { | ||||||
|             const [errMessage, errStack] = safeExtractMessageAndStackFromError(e); |                 const regex = new RegExp(`^${pattern}$`); | ||||||
|             log.error(`Testing path for label '${attr.attributeId}', regex '${attr.value}' failed with error: ${errMessage}, stack: ${errStack}`); |                 match = path.match(regex); | ||||||
|             continue; |                 if (match) { | ||||||
|  |                     break; // Found a match, exit pattern loop
 | ||||||
|  |                 } | ||||||
|  |             } catch (e: unknown) { | ||||||
|  |                 const [errMessage, errStack] = safeExtractMessageAndStackFromError(e); | ||||||
|  |                 log.error(`Testing path for label '${attr.attributeId}', regex '${pattern}' failed with error: ${errMessage}, stack: ${errStack}`); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!match) { |         if (!match) { | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| import optionService from "./options.js"; | import optionService from "./options.js"; | ||||||
| import config from "./config.js"; | import config from "./config.js"; | ||||||
|  | import { normalizeUrl } from "./utils.js"; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  * Primary configuration for sync is in the options (document), but we allow to override |  * Primary configuration for sync is in the options (document), but we allow to override | ||||||
| @ -17,7 +18,10 @@ function get(name: keyof typeof config.Sync) { | |||||||
| export default { | export default { | ||||||
|     // env variable is the easiest way to guarantee we won't overwrite prod data during development
 |     // env variable is the easiest way to guarantee we won't overwrite prod data during development
 | ||||||
|     // after copying prod document/data directory
 |     // after copying prod document/data directory
 | ||||||
|     getSyncServerHost: () => get("syncServerHost"), |     getSyncServerHost: () => { | ||||||
|  |         const host = get("syncServerHost"); | ||||||
|  |         return host ? normalizeUrl(host) : host; | ||||||
|  |     }, | ||||||
|     isSyncSetup: () => { |     isSyncSetup: () => { | ||||||
|         const syncServerHost = get("syncServerHost"); |         const syncServerHost = get("syncServerHost"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -375,6 +375,85 @@ export function safeExtractMessageAndStackFromError(err: unknown): [errMessage: | |||||||
|     return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const; |     return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * Normalizes URL by removing trailing slashes and fixing double slashes. | ||||||
|  |  * Preserves the protocol (http://, https://) but removes trailing slashes from the rest.
 | ||||||
|  |  *  | ||||||
|  |  * @param url The URL to normalize | ||||||
|  |  * @returns The normalized URL without trailing slashes | ||||||
|  |  */ | ||||||
|  | export function normalizeUrl(url: string): string { | ||||||
|  |     if (!url || typeof url !== 'string') { | ||||||
|  |         return url; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Trim whitespace
 | ||||||
|  |     url = url.trim(); | ||||||
|  |      | ||||||
|  |     if (!url) { | ||||||
|  |         return url; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Remove trailing slash, but preserve protocol
 | ||||||
|  |     if (url.endsWith('/') && !url.match(/^https?:\/\/$/)) { | ||||||
|  |         url = url.slice(0, -1); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // Fix double slashes (except in protocol)
 | ||||||
|  |     url = url.replace(/([^:]\/)\/+/g, '$1'); | ||||||
|  |      | ||||||
|  |     return url; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Normalizes a path pattern for custom request handlers. | ||||||
|  |  * Ensures both trailing slash and non-trailing slash versions are handled. | ||||||
|  |  *  | ||||||
|  |  * @param pattern The original pattern from customRequestHandler attribute | ||||||
|  |  * @returns An array of patterns to match both with and without trailing slash | ||||||
|  |  */ | ||||||
|  | export function normalizeCustomHandlerPattern(pattern: string): string[] { | ||||||
|  |     if (!pattern || typeof pattern !== 'string') { | ||||||
|  |         return [pattern]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pattern = pattern.trim(); | ||||||
|  |      | ||||||
|  |     if (!pattern) { | ||||||
|  |         return [pattern]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // If pattern already ends with optional trailing slash, return as-is
 | ||||||
|  |     if (pattern.endsWith('/?$') || pattern.endsWith('/?)')) { | ||||||
|  |         return [pattern]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // If pattern ends with $, handle it specially
 | ||||||
|  |     if (pattern.endsWith('$')) { | ||||||
|  |         const basePattern = pattern.slice(0, -1); | ||||||
|  |          | ||||||
|  |         // If already ends with slash, create both versions
 | ||||||
|  |         if (basePattern.endsWith('/')) { | ||||||
|  |             const withoutSlash = basePattern.slice(0, -1) + '$'; | ||||||
|  |             const withSlash = pattern; | ||||||
|  |             return [withoutSlash, withSlash]; | ||||||
|  |         } else { | ||||||
|  |             // Add optional trailing slash
 | ||||||
|  |             const withSlash = basePattern + '/?$'; | ||||||
|  |             return [withSlash]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // For patterns without $, add both versions
 | ||||||
|  |     if (pattern.endsWith('/')) { | ||||||
|  |         const withoutSlash = pattern.slice(0, -1); | ||||||
|  |         return [withoutSlash, pattern]; | ||||||
|  |     } else { | ||||||
|  |         const withSlash = pattern + '/'; | ||||||
|  |         return [pattern, withSlash]; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|     compareVersions, |     compareVersions, | ||||||
| @ -400,6 +479,8 @@ export default { | |||||||
|     md5, |     md5, | ||||||
|     newEntityId, |     newEntityId, | ||||||
|     normalize, |     normalize, | ||||||
|  |     normalizeCustomHandlerPattern, | ||||||
|  |     normalizeUrl, | ||||||
|     quoteRegex, |     quoteRegex, | ||||||
|     randomSecureToken, |     randomSecureToken, | ||||||
|     randomString, |     randomString, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 perf3ct
						perf3ct