Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions cypress/integration/oddname.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <[email protected]>
*
* @author John Molakvoæ <[email protected]>
* @author Robbert Gurdeep Singh <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { randHash } from "../utils/";

/**
* Make a name aimed to break the viewer in case of escaping errors
*
* @param {String} realName
* @returns {String} a name for the file to be uploaded as
*/
function naughtyFileName(realName) {
const ext = realName.split(".").pop();
return (
"~⛰️ shot of a ${big} mountain`, " +
"realy #1's " +
'" #_+="%2520%27%22%60%25%21%23 was this called ' +
realName +
"in the" +
"☁️" +
"👩‍💻" +
"? :* ." +
ext.toUpperCase()
);
}

for (let [file, type] of [
["audio.mp3", "audio/mpeg"],
["audio.ogg", "audio/mpeg"],
["audio.ogg", "audio/ogg"],
["image1.jpg", "image/jpeg"],
["image.gif", "image/gif"],
["image.heic", "image/heic"],
["image.png", "image/png"],
["image-small.png", "image/png"],
["image.svg", "image/svg"],
["image.webp", "image/webp"],
["video1.mp4", "video/mp4"],
["video.mkv", "video/mkv"],
["video.ogv", "video/ogv"],
["video.webm", "video/webm"],
]) {
const placedName = naughtyFileName(file);

// We'll escape all the characters in the name to match it with css
const placedNameCss = CSS.escape(placedName);

// fresh user for each file
const randUser = randHash() + "@-" + randHash(); // @ is allowed, so use it

const folderName =
'Nextcloud "%27%22%60%25%21%23" >`⛰️<' + file + "><` e*'rocks!#?#%~";

describe(`Open ${file} in viewer with a naughty name`, function () {
before(function () {
// Init user
cy.nextcloudCreateUser(randUser, "password");
cy.login(randUser, "password");

// Upload test files
cy.createFolder(folderName);
cy.uploadFile(file, type, "/" + folderName, placedName);
cy.visit("/apps/files");

// wait a bit for things to be settled
cy.wait(1000);
cy.openFile(folderName);
cy.wait(1000);
});
after(function () {
// no need to log out we do this in the test to check the public link
});

it(`See ${file} as ${placedName} in the list`, function () {
cy.get(`#fileList tr[data-file="${placedNameCss}"]`, {
timeout: 10000,
}).should("contain", placedName);
});

it("Open the viewer on file click", function () {
cy.openFile(placedName);
cy.get("body > .viewer").should("be.visible");
});

it("Does not see a loading animation", function () {
cy.get("body > .viewer", { timeout: 10000 })
.should("be.visible")
.and("have.class", "modal-mask")
.and("not.have.class", "icon-loading");
});

it("See the menu icon and title on the viewer header", function () {
cy.get("body > .viewer .modal-title").should("contain", placedName);
cy.get(
"body > .viewer .modal-header button.action-item__menutoggle"
).should("be.visible");
cy.get("body > .viewer .modal-header button.icon-close").should(
"be.visible"
);
});

it("Does not see navigation arrows", function () {
cy.get("body > .viewer a.prev").should("not.be.visible");
cy.get("body > .viewer a.next").should("not.be.visible");
});

it("Share the folder with a share link and access the share link", function () {
cy.createLinkShare(folderName).then((token) => {
cy.logout();
cy.visit(`/s/${token}`);
});
});

it("Open the viewer on file click (public)", function () {
cy.openFile(placedName);
cy.get("body > .viewer").should("be.visible");
});

it("Does not see a loading animation (public)", function () {
cy.get("body > .viewer", { timeout: 10000 })
.should("be.visible")
.and("have.class", "modal-mask")
.and("not.have.class", "icon-loading");
});

it("See the menu icon and title on the viewer header (public)", function () {
cy.get("body > .viewer .modal-title").should("contain", placedName);
cy.get("body > .viewer .modal-header button.icon-close").should(
"be.visible"
);
});

it("Does not see navigation arrows (public)", function () {
cy.get("body > .viewer a.prev").should("not.be.visible");
cy.get("body > .viewer a.next").should("not.be.visible");
});
});
}
35 changes: 25 additions & 10 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,40 @@ Cypress.Commands.add('nextcloudCreateUser', (user, password) => {
Authorization: `Basic ${btoa('admin:admin')}`,
},
}).then(response => {
cy.log(`Created user ${user}`, response.status)
if(response.body.ocs.meta.status.toLowerCase() == "ok"){
cy.log(`Created user ${user}`, response.status)
} else {
throw new Error(`Unable to create user`)
}
})
})

Cypress.Commands.add('uploadFile', (fileName, mimeType, path = '') => {
/**
* cy.uploadedFile - uploads a file from the fixtures folder
*
* @param {string} fixtureFileName
* @param {string} mimeType eg. image/png
* @param {string} path to the folder in which this file should be uploaded
* @param {string} uploadedFileName alternative name to give the file while uploading
*/
Cypress.Commands.add('uploadFile', (fixtureFileName, mimeType, path = '', uploadedFileName = null) => {
if(uploadedFileName === null){
uploadedFileName = fixtureFileName;
}
// get fixture
return cy.fixture(fileName, 'base64').then(file => {
return cy.fixture(fixtureFileName, 'base64').then(file => {
// convert the logo base64 string to a blob
const blob = Cypress.Blob.base64StringToBlob(file, mimeType)
try {
const file = new File([blob], fileName, { type: mimeType })
const file = new File([blob], uploadedFileName, { type: mimeType })
return cy.window().then(async window => {
await axios.put(`${Cypress.env('baseUrl')}/remote.php/webdav${path}/${fileName}`, file, {
await axios.put(`${Cypress.env('baseUrl')}/remote.php/webdav${path.split("/").map(encodeURIComponent).join("/")}/${encodeURIComponent(uploadedFileName)}`, file, {
headers: {
requesttoken: window.OC.requestToken,
'Content-Type': mimeType,
}
}).then(response => {
cy.log(`Uploaded ${fileName}`, response)
cy.log(`Uploaded ${fixtureFileName} as ${uploadedFileName}`, response)
})
})
} catch (error) {
Expand All @@ -103,18 +118,18 @@ Cypress.Commands.add('createFolder', dirName => {
})

Cypress.Commands.add('openFile', fileName => {
cy.get(`#fileList tr[data-file="${fileName}"] a.name`).click()
cy.get(`#fileList tr[data-file="${CSS.escape(fileName)}"] a.name`).click()
cy.wait(250)
})

Cypress.Commands.add('getFileId', fileName => {
return cy.get(`#fileList tr[data-file="${fileName}"]`)
return cy.get(`#fileList tr[data-file="${CSS.escape(fileName)}"]`)
.should('have.attr', 'data-id')
})

Cypress.Commands.add('deleteFile', fileName => {
cy.get(`#fileList tr[data-file="${fileName}"] a.name .action-menu`).click()
cy.get(`#fileList tr[data-file="${fileName}"] a.name + .popovermenu .action-delete`).click()
cy.get(`#fileList tr[data-file="${CSS.escape(fileName)}"] a.name .action-menu`).click()
cy.get(`#fileList tr[data-file="${CSS.escape(fileName)}"] a.name + .popovermenu .action-delete`).click()
})

/**
Expand Down
4 changes: 2 additions & 2 deletions js/viewer-main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/viewer-main.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/utils/fileUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ const getDavPath = function({ filename, basename }) {
// TODO: allow proper dav access without the need of basic auth
// https://github.com/nextcloud/server/issues/19700
if (isPublic()) {
return generateUrl(`/s/${getToken()}/download?path=${dirname(filename)}&files=${basename}`)
return generateUrl(`/s/${getToken()}/download?path=${encodeURIComponent(dirname(filename))}&files=${encodeURIComponent(basename)}`)
}
return getRootPath() + filename
return getRootPath() + filename.split('/').map((x) => encodeURIComponent(x)).join('/')
}

export { encodeFilePath, extractFilePaths, sortCompare, genFileInfo, getDavPath }