chore(core): integrate some more utils

This commit is contained in:
Elian Doran 2026-01-06 15:41:34 +02:00
parent 299c06c1a6
commit c6197e520d
No known key found for this signature in database
8 changed files with 60 additions and 43 deletions

View File

@ -55,8 +55,7 @@
"@types/express-session": "1.18.2",
"@types/fs-extra": "11.0.4",
"@types/html": "1.0.4",
"@types/ini": "4.1.1",
"@types/mime-types": "3.0.1",
"@types/ini": "4.1.1",
"@types/multer": "2.0.0",
"@types/safe-compare": "1.1.2",
"@types/sax": "1.2.7",
@ -108,14 +107,12 @@
"jimp": "1.6.0",
"lorem-ipsum": "2.0.8",
"marked": "17.0.1",
"mime-types": "3.0.2",
"multer": "2.0.2",
"normalize-strings": "1.1.1",
"ollama": "0.6.3",
"openai": "6.15.0",
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"safe-compare": "1.1.4",
"sax": "1.4.3",
"serve-favicon": "2.5.1",
"stream-throttle": "0.1.3",

View File

@ -5,11 +5,8 @@ import chardet from "chardet";
import crypto from "crypto";
import escape from "escape-html";
import { t } from "i18next";
import mimeTypes from "mime-types";
import { release as osRelease } from "os";
import path from "path";
import { generator } from "rand-token";
import sanitize from "sanitize-filename";
import stripBom from "strip-bom";
import unescape from "unescape";
@ -140,12 +137,12 @@ export async function crash(message: string) {
}
}
/** @deprecated */
export function getContentDisposition(filename: string) {
const sanitizedFilename = sanitize(filename).trim() || "file";
const uriEncodedFilename = encodeURIComponent(sanitizedFilename);
return `file; filename="${uriEncodedFilename}"; filename*=UTF-8''${uriEncodedFilename}`;
return coreUtils.getContentDisposition(filename);
}
/** @deprecated */
export function isStringNote(type: string | undefined, mime: string) {
return coreUtils.isStringNote(type, mime);
}
@ -160,30 +157,9 @@ export function replaceAll(string: string, replaceWhat: string, replaceWith: str
return coreUtils.replaceAll(string, replaceWhat, replaceWith);
}
/** @deprecated */
export function formatDownloadTitle(fileName: string, type: string | null, mime: string) {
const fileNameBase = !fileName ? "untitled" : sanitize(fileName);
const getExtension = () => {
if (type === "text") return ".html";
if (type === "relationMap" || type === "canvas" || type === "search") return ".json";
if (!mime) return "";
const mimeLc = mime.toLowerCase();
// better to just return the current name without a fake extension
// it's possible that the title still preserves the correct extension anyways
if (mimeLc === "application/octet-stream") return "";
// if fileName has an extension matching the mime already - reuse it
const mimeTypeFromFileName = mimeTypes.lookup(fileName);
if (mimeTypeFromFileName === mimeLc) return "";
// as last resort try to get extension from mimeType
const extensions = mimeTypes.extension(mime);
return extensions ? `.${extensions}` : "";
};
return `${fileNameBase}${getExtension()}`;
return coreUtils.formatDownloadTitle(fileName, type, mime);
}
export function removeTextFileExtension(filePath: string) {

View File

@ -8,8 +8,13 @@
},
"dependencies": {
"@triliumnext/commons": "workspace:*",
"sanitize-html": "2.17.0",
"sanitize-html": "2.17.0",
"@braintree/sanitize-url": "7.1.1",
"sanitize-filename": "1.6.3",
"mime-types": "3.0.2"
},
"devDependencies": {
"@types/sanitize-html": "2.16.0",
"@braintree/sanitize-url": "7.1.1"
"@types/mime-types": "3.0.1"
}
}

View File

@ -10,7 +10,7 @@ import AbstractBeccaEntity from "./abstract_becca_entity.js";
import type BBranch from "./bbranch.js";
import type BNote from "./bnote.js";
import { getSql } from "../../services/sql/index.js";
import { isStringNote, replaceAll } from "../../services/utils";
import { formatDownloadTitle, isStringNote, replaceAll } from "../../services/utils";
const attachmentRoleToNoteTypeMapping = {
image: "image",
@ -201,7 +201,7 @@ class BAttachment extends AbstractBeccaEntity<BAttachment> {
getFileName() {
const type = this.role === "image" ? "image" : "file";
return utils.formatDownloadTitle(this.title, type, this.mime);
return formatDownloadTitle(this.title, type, this.mime);
}
override beforeSaving() {

View File

@ -19,7 +19,7 @@ import BAttribute from "./battribute.js";
import type BBranch from "./bbranch.js";
import BRevision from "./brevision.js";
import { getSql } from "../../services/sql/index.js";
import { isStringNote, normalize, randomString, replaceAll } from "../../services/utils";
import { formatDownloadTitle, isStringNote, normalize, randomString, replaceAll } from "../../services/utils";
const LABEL = "label";
const RELATION = "relation";
@ -1654,7 +1654,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
}
getFileName() {
return utils.formatDownloadTitle(this.title, this.type, this.mime);
return formatDownloadTitle(this.title, this.type, this.mime);
}
override beforeSaving() {

View File

@ -2,10 +2,10 @@ import type { BlobRow, EntityChange } from "@triliumnext/commons";
import becca from "../becca/becca.js";
import dateUtils from "./utils/date.js";
import { getLog } from "./log.js";
import { getLog } from "./log.js";
import { randomString } from "./utils/index.js";
import { getSql } from "./sql/index.js";
import { getComponentId } from "./context.js";
import * as cls from "./context.js";
import events from "./events.js";
import blobService from "./blob.js";
import getInstanceId from "./instance_id.js";
@ -33,7 +33,7 @@ function putEntityChange(origEntityChange: EntityChange) {
ec.changeId = randomString(12);
}
ec.componentId = ec.componentId || getComponentId() || "NA"; // NA = not available
ec.componentId = ec.componentId || cls.getComponentId() || "NA"; // NA = not available
ec.instanceId = ec.instanceId || getInstanceId();
ec.isSynced = ec.isSynced ? 1 : 0;
ec.isErased = ec.isErased ? 1 : 0;

View File

@ -3,6 +3,7 @@ import { ALLOWED_PROTOCOLS, SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/
import optionService from "./options.js";
import sanitize from "sanitize-html";
import sanitizeFileNameInternal from "sanitize-filename";
// intended mainly as protection against XSS via import
// secondarily, it (partly) protects against "CSS takeover"
@ -88,3 +89,7 @@ export function sanitizeHtmlCustom(dirtyHtml: string, config: sanitize.IOptions)
export function sanitizeUrl(url: string) {
return sanitizeUrlInternal(url).trim();
}
export function sanitizeFileName(fileName: string) {
return sanitizeFileNameInternal(fileName);
}

View File

@ -1,5 +1,7 @@
import { getCrypto } from "../encryption/crypto";
import { sanitizeFileName } from "../sanitizer";
import { encodeBase64 } from "./binary";
import mimeTypes from "mime-types";
// render and book are string note in the sense that they are expected to contain empty string
const STRING_NOTE_TYPES = new Set(["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas", "webView"]);
@ -65,3 +67,35 @@ export function sanitizeAttributeName(origName: string) {
return fixedName;
}
export function getContentDisposition(filename: string) {
const sanitizedFilename = sanitizeFileName(filename).trim() || "file";
const uriEncodedFilename = encodeURIComponent(sanitizedFilename);
return `file; filename="${uriEncodedFilename}"; filename*=UTF-8''${uriEncodedFilename}`;
}
export function formatDownloadTitle(fileName: string, type: string | null, mime: string) {
const fileNameBase = !fileName ? "untitled" : sanitizeFileName(fileName);
const getExtension = () => {
if (type === "text") return ".html";
if (type === "relationMap" || type === "canvas" || type === "search") return ".json";
if (!mime) return "";
const mimeLc = mime.toLowerCase();
// better to just return the current name without a fake extension
// it's possible that the title still preserves the correct extension anyways
if (mimeLc === "application/octet-stream") return "";
// if fileName has an extension matching the mime already - reuse it
const mimeTypeFromFileName = mimeTypes.lookup(fileName);
if (mimeTypeFromFileName === mimeLc) return "";
// as last resort try to get extension from mimeType
const extensions = mimeTypes.extension(mime);
return extensions ? `.${extensions}` : "";
};
return `${fileNameBase}${getExtension()}`;
}