From 8297f0f273e326e64145a48266805b0b3d073c32 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Daniel=20Kuty=C5=82a?= Date: Mon, 22 Mar 2021 20:44:34 +0100 Subject: [PATCH] 17337: Added custom encode functions with tests MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła --- cypress/integration/collection.spec.js | 37 ++---------- src/common/url.test.ts | 60 +++++++++++++++++++ src/common/url.ts | 17 ++++-- src/common/webdav.ts | 9 ++- .../collection-service-files-response.ts | 6 +- .../collection-service/collection-service.ts | 3 +- 6 files changed, 88 insertions(+), 44 deletions(-) create mode 100644 src/common/url.test.ts diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js index dc26d09a..a85be9d4 100644 --- a/cypress/integration/collection.spec.js +++ b/cypress/integration/collection.spec.js @@ -170,7 +170,7 @@ describe('Collection panel tests', function () { }) }) - it('renames a file using valid names', function () { + it.only('renames a file using valid names', function () { // Creates the collection using the admin token so we can set up // a bogus manifest text without block signatures. cy.createCollection(adminUser.token, { @@ -188,10 +188,10 @@ describe('Collection panel tests', function () { ['foo', '&'], ['&', 'I ❤️ ⛵️'], ['I ❤️ ⛵️', '...'], - ['...', , '#...'], - ['#...', 'some name with whitespaces'], - ['some name with whitespaces', 'some name with whitespaces #2'], - ['some name with whitespaces #2', 'is this name legal? I hope it is'], + ['...', '#..'], + ['#..', 'some name with whitespaces'], + ['some name with whitespaces', 'some name with #2'], + ['some name with #2', 'is this name legal? I hope it is'], ['is this name legal? I hope it is', 'some_file.pdf#'], ['some_file.pdf#', 'some_file.pdf?'], ['some_file.pdf?', '?some_file.pdf'] @@ -359,33 +359,6 @@ describe('Collection panel tests', function () { }); }); - it('should display all filles within the collection even with the # sign within the file name', () => { - const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`; - - cy.createCollection(adminUser.token, { - name: colName, - owner_uuid: activeUser.user.uuid, - preserve_version: true, - manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:#foo 0:3:bar\n" - }) - .as('collection') - .then((collection) => { - cy.loginAs(activeUser) - cy.doSearch(`${collection.uuid}`); - cy.get('[data-cy=collection-files-panel]').contains('#foo').closest('[data-cy=virtual-file-tree]').find('[type=checkbox]').click(); - cy.get('[data-cy=collection-files-panel-options-btn]').click(); - cy.get('[data-cy=context-menu]').contains('Remove selected').click(); - cy.get('[data-cy=confirmation-dialog-ok-btn]').click(); - cy.get('[data-cy=collection-files-panel]').contains('#foo').should('not.exist'); - - cy.get('[data-cy=collection-files-panel]').contains('bar').rightclick(); - cy.get('[data-cy=context-menu]').contains('Rename').click(); - cy.get('input[name=path]').type('bar 123 321 bar'); - cy.get('[data-cy=form-submit-btn]').click(); - cy.get('[data-cy=collection-files-panel]').contains('barbar 123 321 bar').should('exist'); - }); - }); - it('uses the collection version browser to view a previous version', function () { const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`; diff --git a/src/common/url.test.ts b/src/common/url.test.ts new file mode 100644 index 00000000..47530966 --- /dev/null +++ b/src/common/url.test.ts @@ -0,0 +1,60 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { customDecodeURI, customEncodeURI, encodeHash } from './url'; + +describe('url', () => { + describe('encodeHash', () => { + it('should ignore path without hash', () => { + // given + const path = 'path/without/hash'; + + // when + const result = encodeHash(path); + + // then + expect(result).toEqual(path); + }); + + it('should replace all hashes within the path', () => { + // given + const path = 'path/with/hash # and one more #'; + const expectedResult = 'path/with/hash %23 and one more %23'; + + // when + const result = encodeHash(path); + + // then + expect(result).toEqual(expectedResult); + }); + }); + + describe('customEncodeURI', () => { + it('should decode', () => { + // given + const path = 'test%23test%2Ftest'; + const expectedResult = 'test#test/test'; + + // when + const result = customDecodeURI(path); + + // then + expect(result).toEqual(expectedResult); + }); + }); + + describe('customEncodeURI', () => { + it('should encode', () => { + // given + const path = 'test#test/test'; + const expectedResult = 'test%23test%2Ftest'; + + // when + const result = customEncodeURI(path); + + // then + expect(result).toEqual(expectedResult); + }); + }); +}); \ No newline at end of file diff --git a/src/common/url.ts b/src/common/url.ts index 6223485e..0d2549c1 100644 --- a/src/common/url.ts +++ b/src/common/url.ts @@ -18,9 +18,14 @@ export function normalizeURLPath(url: string) { return u.toString(); } -export const escapeHashIfRequired = (name: string, defaultTransformation?: Function) => - name.indexOf('#') > -1 ? - encodeURIComponent(name) : - defaultTransformation ? - defaultTransformation(name) : - name; \ No newline at end of file +export const customEncodeURI = (path: string) => { + return encodeURIComponent(path.replace(/%2F/g, '/')); +}; + +export const customDecodeURI = (path: string) => { + return decodeURIComponent(path.replace(/\//g, '%2F')); +}; + +export const encodeHash = (path: string) => { + return path.replace(/#/g, '%23'); +}; \ No newline at end of file diff --git a/src/common/webdav.ts b/src/common/webdav.ts index 54601f16..ca3b6d74 100644 --- a/src/common/webdav.ts +++ b/src/common/webdav.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0 -import { escapeHashIfRequired } from "./url"; +import { customEncodeURI, encodeHash } from "./url"; export class WebDAV { @@ -77,7 +77,12 @@ export class WebDAV { r.open(config.method, `${this.defaults.baseURL ? this.defaults.baseURL+'/' - : ''}${escapeHashIfRequired(config.url, encodeURI)}`); + : ''}${customEncodeURI(config.url)}`); + + if (config.headers && config.headers.Destination && config.headers.Destination.indexOf('#') > -1) { + config.headers.Destination = encodeHash(config.headers.Destination); + } + const headers = { ...this.defaults.headers, ...config.headers }; Object .keys(headers) diff --git a/src/services/collection-service/collection-service-files-response.ts b/src/services/collection-service/collection-service-files-response.ts index 6f8b3de3..0dd7260b 100644 --- a/src/services/collection-service/collection-service-files-response.ts +++ b/src/services/collection-service/collection-service-files-response.ts @@ -5,7 +5,7 @@ import { CollectionDirectory, CollectionFile, CollectionFileType, createCollectionDirectory, createCollectionFile } from "../../models/collection-file"; import { getTagValue } from "~/common/xml"; import { getNodeChildren, Tree, mapTree } from '~/models/tree'; -import { escapeHashIfRequired } from "~/common/url"; +import { customDecodeURI } from "~/common/url"; export const sortFilesTree = (tree: Tree) => { return mapTree(node => { @@ -28,8 +28,8 @@ export const extractFilesData = (document: Document) => { .map(element => { const name = getTagValue(element, 'D:displayname', ''); const size = parseInt(getTagValue(element, 'D:getcontentlength', '0'), 10); - const url = getTagValue(element, 'D:href', ''); - const nameSuffix = `/${escapeHashIfRequired(name) || ''}`; + const url = customDecodeURI(getTagValue(element, 'D:href', '')); + const nameSuffix = name; const collectionUuidMatch = collectionUrlPrefix.exec(url); const collectionUuid = collectionUuidMatch ? collectionUuidMatch.pop() : ''; const directory = url diff --git a/src/services/collection-service/collection-service.ts b/src/services/collection-service/collection-service.ts index c46c3e27..9fd8a135 100644 --- a/src/services/collection-service/collection-service.ts +++ b/src/services/collection-service/collection-service.ts @@ -10,6 +10,7 @@ import { AuthService } from "../auth-service/auth-service"; import { extractFilesData } from "./collection-service-files-response"; import { TrashableResourceService } from "~/services/common-service/trashable-resource-service"; import { ApiActions } from "~/services/api/api-actions"; +import { customEncodeURI } from "~/common/url"; export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void; @@ -78,7 +79,7 @@ export class CollectionService extends TrashableResourceService