-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import fetch from 'cross-fetch'; | ||
| import { pathExists } from 'fs-extra'; | ||
| import type { App } from '../../src/init/App'; | ||
| import { getPort } from '../util/Util'; | ||
| import { | ||
| getDefaultVariables, | ||
| getTestConfigPath, | ||
| getTestFolder, | ||
| instantiateFromConfig, | ||
| removeFolder, | ||
| } from './Config'; | ||
|
|
||
| const port = getPort('FileBackendEncodedSlashHandling'); | ||
| const baseUrl = `http://localhost:${port}/`; | ||
|
|
||
| const rootFilePath = getTestFolder('file-backend-encoded-slash-handling'); | ||
|
|
||
| describe('A server with a file backend storage', (): void => { | ||
| let app: App; | ||
|
|
||
| beforeAll(async(): Promise<void> => { | ||
| await removeFolder(rootFilePath); | ||
| const variables = { | ||
| ...getDefaultVariables(port, baseUrl), | ||
| 'urn:solid-server:default:variable:rootFilePath': rootFilePath, | ||
| }; | ||
|
|
||
| // Create and start the server | ||
| const instances = await instantiateFromConfig( | ||
| 'urn:solid-server:test:Instances', | ||
| [ | ||
| getTestConfigPath('server-file.json'), | ||
| ], | ||
| variables, | ||
| ) as Record<string, any>; | ||
| ({ app } = instances); | ||
|
|
||
| await app.start(); | ||
| }); | ||
|
|
||
| afterAll(async(): Promise<void> => { | ||
| // Await removeFolder(rootFilePath); | ||
| await app.stop(); | ||
| }); | ||
|
|
||
| it('can put a document for which the URI path contains url encoded separator characters.', async(): Promise<void> => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a check here that the resource is not accessible through |
||
| const url = `${baseUrl}/c1/c2/t1%2F`; | ||
| const res = await fetch(url, { | ||
| method: 'PUT', | ||
| headers: { | ||
| 'content-type': 'text/plain', | ||
| }, | ||
| body: 'abc', | ||
| }); | ||
| expect(res.status).toBe(201); | ||
| expect(res.headers.get('location')).toBe(url); | ||
|
|
||
| // The resource should not be accessible through ${baseUrl}/c1/c2/t1/. | ||
| const check1 = await fetch(`${baseUrl}/c1/c2/t1/}`, { | ||
| method: 'GET', | ||
| headers: { | ||
| accept: 'text/plain', | ||
| }, | ||
| }); | ||
| expect(check1.status).toBe(404); | ||
|
|
||
| // Check that the created resource is not a container | ||
| const check2 = await fetch(url, { | ||
| method: 'GET', | ||
| headers: { | ||
| accept: 'text/plain', | ||
| }, | ||
| }); | ||
| const linkHeaderValues = check2.headers.get('link')!.split(',').map((item): string => item.trim()); | ||
| expect(linkHeaderValues).not.toContain('<http://www.w3.org/ns/ldp#Container>; rel="type"'); | ||
|
|
||
| // Check that the appropriate file path exists | ||
| const check3 = await pathExists(`${rootFilePath}/c1/c2/t1%2F$.txt`); | ||
| expect(check3).toBe(true); | ||
| }); | ||
|
|
||
| it('can post a document using a slug that contains url encoded separator characters.', async(): Promise<void> => { | ||
| const slug = 't1%2Faa'; | ||
| const res = await fetch(baseUrl, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'content-type': 'text/plain', | ||
| slug, | ||
| }, | ||
| body: 'abc', | ||
| }); | ||
| expect(res.status).toBe(201); | ||
| expect(res.headers.get('location')).toBe(`${baseUrl}${slug}`); | ||
|
|
||
| // Check that the the appropriate file path exists | ||
| const check = await pathExists(`${rootFilePath}/${slug}$.txt`); | ||
| expect(check).toBe(true); | ||
| }); | ||
|
|
||
| it('prevents accessing a document via a different identifier that results in the same path after url decoding.', | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would also add the reverse check just to be sure. |
||
| async(): Promise<void> => { | ||
| // First put a resource using a path without encoded separator characters: foo/bar | ||
| const url = `${baseUrl}/foo/bar`; | ||
| await fetch(url, { | ||
| method: 'PUT', | ||
| headers: { | ||
| 'content-type': 'text/plain', | ||
| }, | ||
| body: 'abc', | ||
| }); | ||
|
|
||
| // The resource at foo/bar should not be accessible using the url encoded variant of this path: foo%2Fbar | ||
| const check1 = await fetch(`${baseUrl}/foo%2Fbar`, { | ||
| method: 'GET', | ||
| headers: { | ||
| accept: 'text/plain', | ||
| }, | ||
| }); | ||
| // Expect foo%2Fbar to correctly refer to a different document, which does not exist. | ||
| expect(check1.status).toBe(404); | ||
|
|
||
| // Check that the the appropriate file path for foo/bar exists | ||
| const check2 = await pathExists(`${rootFilePath}/foo/bar$.txt`); | ||
| expect(check2).toBe(true); | ||
|
|
||
| // Next, put a resource using a path with an encoded separator character: bar%2Ffoo | ||
| await fetch(`${baseUrl}/bar%2Ffoo`, { | ||
| method: 'PUT', | ||
| headers: { | ||
| 'content-type': 'text/plain', | ||
| }, | ||
| body: 'abc', | ||
| }); | ||
|
|
||
| // The resource at bar%2Ffoo should not be accessible through bar/foo | ||
| const check3 = await fetch(`${baseUrl}/bar/foo`, { | ||
| method: 'GET', | ||
| headers: { | ||
| accept: 'text/plain', | ||
| }, | ||
| }); | ||
| // Expect bar/foo to correctly refer to a different document, which does not exist. | ||
| expect(check3.status).toBe(404); | ||
|
|
||
| // Check that the the appropriate file path for bar%foo exists | ||
| const check4 = await pathExists(`${rootFilePath}/bar%2Ffoo$.txt`); | ||
| expect(check4).toBe(true); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| { | ||
| "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^4.0.0/components/context.jsonld", | ||
| "import": [ | ||
| "files-scs:config/app/main/default.json", | ||
| "files-scs:config/app/init/initialize-root.json", | ||
| "files-scs:config/app/setup/disabled.json", | ||
| "files-scs:config/http/handler/default.json", | ||
| "files-scs:config/http/middleware/websockets.json", | ||
| "files-scs:config/http/server-factory/websockets.json", | ||
| "files-scs:config/http/static/default.json", | ||
| "files-scs:config/identity/access/public.json", | ||
| "files-scs:config/identity/handler/default.json", | ||
| "files-scs:config/identity/ownership/token.json", | ||
| "files-scs:config/identity/pod/static.json", | ||
| "files-scs:config/identity/registration/enabled.json", | ||
| "files-scs:config/ldp/authentication/dpop-bearer.json", | ||
| "files-scs:config/ldp/authorization/webacl.json", | ||
| "files-scs:config/ldp/handler/default.json", | ||
| "files-scs:config/ldp/metadata-parser/default.json", | ||
| "files-scs:config/ldp/metadata-writer/default.json", | ||
| "files-scs:config/ldp/modes/default.json", | ||
| "files-scs:config/storage/backend/file.json", | ||
| "files-scs:config/storage/key-value/resource-store.json", | ||
| "files-scs:config/storage/middleware/default.json", | ||
| "files-scs:config/util/auxiliary/acl.json", | ||
| "files-scs:config/util/identifiers/suffix.json", | ||
| "files-scs:config/util/index/default.json", | ||
| "files-scs:config/util/logging/winston.json", | ||
| "files-scs:config/util/representation-conversion/default.json", | ||
| "files-scs:config/util/resource-locker/memory.json", | ||
| "files-scs:config/util/variables/default.json" | ||
| ], | ||
| "@graph": [ | ||
| { | ||
| "@id": "urn:solid-server:test:Instances", | ||
| "@type": "RecordObject", | ||
| "RecordObject:_record": [ | ||
| { | ||
| "RecordObject:_record_key": "app", | ||
| "RecordObject:_record_value": { "@id": "urn:solid-server:default:App" } | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "@id": "urn:solid-server:default:EmailSender", | ||
| "@type": "BaseEmailSender", | ||
| "args_senderName": "Solid Server", | ||
| "args_emailConfig_host": "smtp.example.email", | ||
| "args_emailConfig_port": 587, | ||
| "args_emailConfig_auth_user": "alice@example.email", | ||
| "args_emailConfig_auth_pass": "NYEaCsqV7aVStRCbmC" | ||
| } | ||
| ] | ||
| } |
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.