17337: Added custom encode functions with tests
authorDaniel Kutyła <daniel.kutyla@contractors.roche.com>
Mon, 22 Mar 2021 19:44:34 +0000 (20:44 +0100)
committerDaniel Kutyła <daniel.kutyla@contractors.roche.com>
Mon, 22 Mar 2021 20:18:29 +0000 (21:18 +0100)
Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla@contractors.roche.com>

cypress/integration/collection.spec.js
src/common/url.test.ts [new file with mode: 0644]
src/common/url.ts
src/common/webdav.ts
src/services/collection-service/collection-service-files-response.ts
src/services/collection-service/collection-service.ts

index dc26d09a36d95de842fb4b43235853a8a2b926e5..a85be9d4bdf89d21a2e6c6f385dd542d08c71b7d 100644 (file)
@@ -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', '&amp;'],
                     ['&amp;', '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 (file)
index 0000000..4753096
--- /dev/null
@@ -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
index 6223485eabee6908fa363ea94e7345c1c54d7822..0d2549c1b9fc55cb6b123833ab30e29d4e3aa083 100644 (file)
@@ -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
index 54601f16d2e5a380b315e8d8eb9834d2228bcb38..ca3b6d74686093ad75b3776faa29499ca9684de6 100644 (file)
@@ -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)
index 6f8b3de3a9b99bc0aac4ecb1100b95f71d6af309..0dd7260b022c63e231373504449716182387ef90 100644 (file)
@@ -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<CollectionDirectory | CollectionFile>) => {
     return mapTree<CollectionDirectory | CollectionFile>(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
index c46c3e27764470f1c214cc4ff82200d612b6a8c4..9fd8a13511ff850b26f9a40af01336616c2340e2 100644 (file)
@@ -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<CollectionResour
     async moveFile(collectionUuid: string, oldPath: string, newPath: string) {
         await this.webdavClient.move(
             `c=${collectionUuid}${oldPath}`,
-            `c=${collectionUuid}${encodeURI(newPath)}`
+            `c=${collectionUuid}/${customEncodeURI(newPath)}`
         );
         await this.update(collectionUuid, { preserveVersion: true });
     }