-
Notifications
You must be signed in to change notification settings - Fork 147
fix: %2F not handled correctly in file backend #1184 #1265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
RubenVerborgh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work!
A couple of nitpicks (given how complicated this particular escaping is).
src/util/PathUtil.ts
Outdated
| import { BadRequestHttpError } from './errors/BadRequestHttpError'; | ||
|
|
||
| // Characters (mapped to their encoded representation) to ignore when URL decoding the URI path to a file path. | ||
| const codecExceptions = new Map([ '/', '\\' ].map((item): [string, string] => [ item, encodeURIComponent(item) ])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's name this pathComponentDelimiters (as per solid/specification#381 (comment)) and use a regular JavaScript object, which are the more straightforward option when keys are strings (or symbols). We can possibly use object.fromEntries.
src/util/PathUtil.ts
Outdated
| * @param path - The path to apply the escaping on. | ||
| * @returns A copy of the input path that is safe to apply URL decoding on. | ||
| */ | ||
| function escapeCodecExceptionsForDecoding(path: string): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| function escapeCodecExceptionsForDecoding(path: string): string { | |
| function preventDelimiterDecoding(pathComponent: string): string { |
src/util/PathUtil.ts
Outdated
| codecExceptions.forEach((value, key): void => { | ||
| path = path.replace(codecExceptionsRegexCache.get(key)!, `%25${value.slice(1)}`); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A (double) loop is expensive here; I think we should just go for
| codecExceptions.forEach((value, key): void => { | |
| path = path.replace(codecExceptionsRegexCache.get(key)!, `%25${value.slice(1)}`); | |
| }); | |
| return pathComponent.replace(/\/|\\/gu, encodeComponentDelimiter); |
with
const encodedDelimiters = { '/': '%25%2f', '\\': '%25%5c' };
function encodeComponentDelimiter(delimiter: string) {
return encodedDelimiters[delimiter];
}
src/util/PathUtil.ts
Outdated
| * @param path - The path to apply the escaping on. | ||
| * @returns A copy of the input path that is safe to apply URL encoding on. | ||
| */ | ||
| function escapeCodecExceptionsForEncoding(path: string): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| function escapeCodecExceptionsForEncoding(path: string): string { | |
| function preventDelimiterEncoding(pathComponent: string): string { |
joachimvh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not much to add besides Ruben's comment except I would extend the integration tests more to really make sure there are no potential issues here.
src/util/PathUtil.ts
Outdated
| @@ -115,14 +149,16 @@ export function toCanonicalUriPath(path: string): string { | |||
| * Decodes all components of a URI path. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should have some more comments here (and in encodeUriPathComponents) to specify exactly that we use these functions to convert between URIs and file paths.
| @@ -0,0 +1,54 @@ | |||
| { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the same config as server-memory.json but with a file backend right? Would just call it server-file.json then.
| await app.stop(); | ||
| }); | ||
|
|
||
| it('can put a document for which the URI path contains url encoded separator characters.', async(): Promise<void> => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a check here that the resource is not accessible through ${baseUrl}/c1/c2/t1/. Also check that the new resource is not a container by doing a GET and checking the headers.
| expect(res.headers.get('location')).toBe(`${baseUrl}${slug}`); | ||
| }); | ||
|
|
||
| it('prevents accessing a document via a different identifier that results in the same path after url decoding.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would also add the reverse check just to be sure.
|
|
||
| const rootFilePath = getTestFolder('file-backend-encoded-slash-handling'); | ||
|
|
||
| describe('A server with a file backend storage', (): void => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For each test I would add an additional check that looks at the file name on disk since we know what it should be and where it should be located.
ea4fbb2 to
c3daf12
Compare
|
I've updated my changes based on your feedback! (Sorry for the delay) |
src/util/PathUtil.ts
Outdated
| const pathComponentDelimiters = [ '/', '\\' ]; | ||
| // Regex to find all instances of encoded path component delimiters. | ||
| const encodedDelimiterRegex = new RegExp( | ||
| `${pathComponentDelimiters.map((item): string => encodeURIComponent(item)).join('|')}`, 'giu', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd do
| `${pathComponentDelimiters.map((item): string => encodeURIComponent(item)).join('|')}`, 'giu', | |
| `${pathComponentDelimiters.map((delimiter): string => encodeURIComponent(item)).join('|')}`, 'giu', |
throughout (especially with the arrays below at 17 and 22) but that's a crazy nit
c3daf12 to
d0c7f01
Compare
|
Renamed |
|
Super-clean now. Love it ❤️ |
d0c7f01 to
08a7227
Compare
08a7227 to
18d9f2c
Compare
|
If I understand well, the above code only avoid %conversion of Trying the same concept on NSS. I stumble on the following issue : I run tests with the 2 different %encoded slash The except encode/decode function should consider both cases. |
Clever—lemme fix that indeed! |
|
Here is my PR nodeSolidServer/node-solid-server#1690 can you have a look |
|
@bourgeoa That's quite cool; I learnt a clever trick there! |
📁 Related issues
#1184
✍️ Description
Modified
encodeUriPathComponentsanddecodeUriPathComponentsinPathUtil.tsso that url encoded path separator characters (/,\) no longer erroneously impact the file path that is targeted when using a file backend. The implementation uses two new helper functionsescapeCodecExceptionsForEncodingandescapeCodecExceptionsForDecodingthat prepare a path part for encoding and decoding respectively, by replacing all instances of the matching encoded file separator characters with a character or character sequence that results in the follow-up encode/decode to have no effect. The code can be extended with additional path separator characters (or other exceptional characters) to ignore during encoding/decoding, by adding the respective characters to thecodecExceptionsconstant initializer.As requested, I've extended the integration tests with
FileBackendEncodedSlashHandling.test.ts, which checks the operations that could not be executed successfully before this fix.✅ PR check list
Before this pull request can be merged, a core maintainer will check whether