fix: Document title with slashes produces folders in exported zip file
closes #2036
This commit is contained in:
@ -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
|
||||||
|
@ -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
0
server/test/fixtures/empty.md
vendored
Normal 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
34
server/utils/fs.test.js
Normal 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`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user