fix: Document title with slashes produces folders in exported zip file

closes #2036
This commit is contained in:
Tom Moor
2021-04-17 19:30:31 -07:00
parent 03d90b3f15
commit e9f083feb8
6 changed files with 68 additions and 3 deletions

View File

@ -11,6 +11,7 @@ import parseTitle from "../../shared/utils/parseTitle";
import { FileImportError, InvalidRequestError } from "../errors"; import { FileImportError, InvalidRequestError } from "../errors";
import { User } from "../models"; import { User } from "../models";
import dataURItoBuffer from "../utils/dataURItoBuffer"; import dataURItoBuffer from "../utils/dataURItoBuffer";
import { deserializeFilename } from "../utils/fs";
import parseImages from "../utils/parseImages"; import parseImages from "../utils/parseImages";
import attachmentCreator from "./attachmentCreator"; import attachmentCreator from "./attachmentCreator";
@ -152,7 +153,7 @@ export default async function documentImporter({
if (!fileInfo) { if (!fileInfo) {
throw new InvalidRequestError(`File type ${file.type} not supported`); throw new InvalidRequestError(`File type ${file.type} not supported`);
} }
let title = file.name.replace(/\.[^/.]+$/, ""); let title = deserializeFilename(file.name.replace(/\.[^/.]+$/, ""));
let text = await fileInfo.getMarkdown(file); let text = await fileInfo.getMarkdown(file);
// If the first line of the imported text looks like a markdown heading // If the first line of the imported text looks like a markdown heading

View File

@ -94,6 +94,25 @@ describe("documentImporter", () => {
expect(response.title).toEqual("Heading 1"); expect(response.title).toEqual("Heading 1");
}); });
it("should handle encoded slashes", async () => {
const user = await buildUser();
const name = "this %2F and %2F this.md";
const file = new File({
name,
type: "text/plain",
path: path.resolve(__dirname, "..", "test", "fixtures", "empty.md"),
});
const response = await documentImporter({
user,
file,
ip,
});
expect(response.text).toContain("");
expect(response.title).toEqual("this / and / this");
});
it("should fallback to extension if mimetype unknown", async () => { it("should fallback to extension if mimetype unknown", async () => {
const user = await buildUser(); const user = await buildUser();
const name = "markdown.md"; const name = "markdown.md";

0
server/test/fixtures/empty.md vendored Normal file
View File

View File

@ -2,6 +2,14 @@
import path from "path"; import path from "path";
import fs from "fs-extra"; import fs from "fs-extra";
export function serializeFilename(text: string): string {
return text.replace(/\//g, "%2F").replace(/\\/g, "%5C");
}
export function deserializeFilename(text: string): string {
return text.replace(/%2F/g, "/").replace(/%5C/g, "\\");
}
export function requireDirectory<T>(dirName: string): [T, string][] { export function requireDirectory<T>(dirName: string): [T, string][] {
return fs return fs
.readdirSync(dirName) .readdirSync(dirName)

34
server/utils/fs.test.js Normal file
View File

@ -0,0 +1,34 @@
// @flow
import { serializeFilename, deserializeFilename } from "./fs";
describe("serializeFilename", () => {
it("should serialize forward slashes", () => {
expect(serializeFilename(`/`)).toBe("%2F");
expect(serializeFilename(`this / and / this`)).toBe(
"this %2F and %2F this"
);
});
it("should serialize back slashes", () => {
expect(serializeFilename(`\\`)).toBe("%5C");
expect(serializeFilename(`this \\ and \\ this`)).toBe(
"this %5C and %5C this"
);
});
});
describe("deserializeFilename", () => {
it("should deserialize forward slashes", () => {
expect(deserializeFilename("%2F")).toBe("/");
expect(deserializeFilename("this %2F and %2F this")).toBe(
`this / and / this`
);
});
it("should deserialize back slashes", () => {
expect(deserializeFilename("%5C")).toBe(`\\`);
expect(deserializeFilename("this %5C and %5C this")).toBe(
`this \\ and \\ this`
);
});
});

View File

@ -4,6 +4,7 @@ import * as Sentry from "@sentry/node";
import JSZip from "jszip"; import JSZip from "jszip";
import tmp from "tmp"; import tmp from "tmp";
import { Attachment, Collection, Document } from "../models"; import { Attachment, Collection, Document } from "../models";
import { serializeFilename } from "./fs";
import { getFileByKey } from "./s3"; import { getFileByKey } from "./s3";
async function addToArchive(zip, documents) { async function addToArchive(zip, documents) {
@ -23,7 +24,9 @@ async function addToArchive(zip, documents) {
text = text.replace(attachment.redirectUrl, encodeURI(attachment.key)); text = text.replace(attachment.redirectUrl, encodeURI(attachment.key));
} }
zip.file(`${document.title || "Untitled"}.md`, text, { const title = serializeFilename(document.title) || "Untitled";
zip.file(`${title}.md`, text, {
date: document.updatedAt, date: document.updatedAt,
comment: JSON.stringify({ comment: JSON.stringify({
pinned: document.pinned, pinned: document.pinned,
@ -33,7 +36,7 @@ async function addToArchive(zip, documents) {
}); });
if (doc.children && doc.children.length) { if (doc.children && doc.children.length) {
const folder = zip.folder(document.title); const folder = zip.folder(title);
await addToArchive(folder, doc.children); await addToArchive(folder, doc.children);
} }
} }