fix: %2F not handled correctly in file backend #1184

Fix
This commit is contained in:
Wannes Kerckhove
2022-04-14 10:19:13 +02:00
committed by Joachim Van Herwegen
parent 3d6e3d2e39
commit dbdb9b424e
5 changed files with 274 additions and 4 deletions

View File

@@ -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> => {
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.',
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);
});
});

View File

@@ -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"
}
]
}

View File

@@ -112,6 +112,11 @@ describe('PathUtil', (): void => {
it('leaves the query string untouched.', (): void => {
expect(decodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toBe('/a path&/name?abc=def&xyz');
});
it('ignores url encoded path separator characters.', (): void => {
expect(decodeUriPathComponents('/a%20path&/c1/c2/t1%2F')).toBe('/a path&/c1/c2/t1%2F');
expect(decodeUriPathComponents('/a%20path&/c1/c2/t1%5C')).toBe('/a path&/c1/c2/t1%5C');
});
});
describe('#encodeUriPathComponents', (): void => {
@@ -122,6 +127,11 @@ describe('PathUtil', (): void => {
it('leaves the query string untouched.', (): void => {
expect(encodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toBe('/a%2520path%26/name?abc=def&xyz');
});
it('does not double-encode url encoded path separator characters.', (): void => {
expect(encodeUriPathComponents('/a%20path&/c1/c2/t1%2F')).toBe('/a%2520path%26/c1/c2/t1%2F');
expect(encodeUriPathComponents('/a%20path&/c1/c2/t1%5C')).toBe('/a%2520path%26/c1/c2/t1%5C');
});
});
describe('#isContainerPath', (): void => {

View File

@@ -8,6 +8,7 @@ const portNames = [
'ContentNegotiation',
'DynamicPods',
'ExpiringDataCleanup',
'FileBackendEncodedSlashHandling',
'GlobalQuota',
'Identity',
'LpdHandlerWithAuth',