mirror of
https://github.com/docmost/docmost.git
synced 2026-05-11 00:44:07 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b16ac4151 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.80.1",
|
"version": "0.80.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
@@ -31,8 +31,8 @@
|
|||||||
"emoji-mart": "^5.6.0",
|
"emoji-mart": "^5.6.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"highlightjs-sap-abap": "^0.3.0",
|
"highlightjs-sap-abap": "^0.3.0",
|
||||||
"i18next": "25.10.1",
|
"i18next": "^25.10.1",
|
||||||
"i18next-http-backend": "3.0.6",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"jotai": "^2.18.1",
|
"jotai": "^2.18.1",
|
||||||
"jotai-optics": "^0.4.0",
|
"jotai-optics": "^0.4.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"mantine-form-zod-resolver": "^1.3.0",
|
"mantine-form-zod-resolver": "^1.3.0",
|
||||||
"mermaid": "^11.13.0",
|
"mermaid": "^11.13.0",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"posthog-js": "1.372.2",
|
"posthog-js": "1.370.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-arborist": "3.4.0",
|
"react-arborist": "3.4.0",
|
||||||
"react-clear-modal": "^2.0.18",
|
"react-clear-modal": "^2.0.18",
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
"react-drawio": "^1.0.7",
|
"react-drawio": "^1.0.7",
|
||||||
"react-error-boundary": "^6.1.1",
|
"react-error-boundary": "^6.1.1",
|
||||||
"react-helmet-async": "^3.0.0",
|
"react-helmet-async": "^3.0.0",
|
||||||
"react-i18next": "16.5.8",
|
"react-i18next": "^16.5.8",
|
||||||
"react-router-dom": "^7.13.1",
|
"react-router-dom": "^7.13.1",
|
||||||
"semver": "^7.7.4",
|
"semver": "^7.7.4",
|
||||||
"socket.io-client": "^4.8.3",
|
"socket.io-client": "^4.8.3",
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^15.13.0",
|
"globals": "^15.13.0",
|
||||||
"optics-ts": "^2.4.1",
|
"optics-ts": "^2.4.1",
|
||||||
"postcss": "^8.5.12",
|
"postcss": "^8.5.8",
|
||||||
"postcss-preset-mantine": "^1.18.0",
|
"postcss-preset-mantine": "^1.18.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.80.1",
|
"version": "0.80.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
"@ai-sdk/google": "^3.0.52",
|
"@ai-sdk/google": "^3.0.52",
|
||||||
"@ai-sdk/openai": "^3.0.47",
|
"@ai-sdk/openai": "^3.0.47",
|
||||||
"@ai-sdk/openai-compatible": "^2.0.37",
|
"@ai-sdk/openai-compatible": "^2.0.37",
|
||||||
"@aws-sdk/client-s3": "3.1037.0",
|
"@aws-sdk/client-s3": "3.1014.0",
|
||||||
"@aws-sdk/lib-storage": "3.1037.0",
|
"@aws-sdk/lib-storage": "3.1014.0",
|
||||||
"@aws-sdk/s3-request-presigner": "3.1037.0",
|
"@aws-sdk/s3-request-presigner": "3.1014.0",
|
||||||
"@clickhouse/client": "^1.18.2",
|
"@clickhouse/client": "^1.18.2",
|
||||||
"@fastify/cookie": "^11.0.2",
|
"@fastify/cookie": "^11.0.2",
|
||||||
"@fastify/multipart": "^10.0.0",
|
"@fastify/multipart": "^10.0.0",
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename-ts": "1.0.2",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"stripe": "^17.7.0",
|
"stripe": "^17.7.0",
|
||||||
"tlds": "^1.261.0",
|
"tlds": "^1.261.0",
|
||||||
@@ -166,9 +166,6 @@
|
|||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
},
|
},
|
||||||
"transformIgnorePatterns": [
|
|
||||||
"/node_modules/(?!(\\.pnpm/)?(nanoid|uuid|image-dimensions|marked)(@|/))"
|
|
||||||
],
|
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"**/*.(t|j)s"
|
"**/*.(t|j)s"
|
||||||
],
|
],
|
||||||
|
|||||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import sanitize = require('sanitize-filename');
|
import { sanitize } from 'sanitize-filename-ts';
|
||||||
import { FastifyRequest } from 'fastify';
|
import { FastifyRequest } from 'fastify';
|
||||||
import { Readable, Transform } from 'stream';
|
import { Readable, Transform } from 'stream';
|
||||||
|
|
||||||
@@ -72,33 +72,11 @@ export function extractDateFromUuid7(uuid7: string) {
|
|||||||
return new Date(timestamp);
|
return new Date(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SanitizeFileNameOptions = {
|
export function sanitizeFileName(fileName: string): string {
|
||||||
/** Keep spaces and `#` instead of replacing them with `_`. Useful for
|
const sanitizedFilename = sanitize(fileName)
|
||||||
* download filenames where readability matters. Defaults to false. */
|
.replace(/ /g, '_')
|
||||||
preserveSpaces?: boolean;
|
.replace(/#/g, '_');
|
||||||
};
|
return sanitizedFilename.slice(0, 255);
|
||||||
|
|
||||||
export function sanitizeFileName(
|
|
||||||
fileName: string,
|
|
||||||
options: SanitizeFileNameOptions = {},
|
|
||||||
): string {
|
|
||||||
// Decode percent-encoded sequences so that bypasses like "..%2F" reach
|
|
||||||
// sanitize() as literal "../" and get stripped. sanitize-filename only
|
|
||||||
// strips literal characters and won't catch encoded path separators
|
|
||||||
// on its own.
|
|
||||||
const decoded = fileName.replace(/%[0-9a-fA-F]{2}/g, (m) => {
|
|
||||||
try {
|
|
||||||
return decodeURIComponent(m);
|
|
||||||
} catch {
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const sanitized = sanitize(decoded);
|
|
||||||
if (options.preserveSpaces) {
|
|
||||||
return sanitized;
|
|
||||||
}
|
|
||||||
return sanitized.replace(/ /g, '_').replace(/#/g, '_');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAccent(str: string): string {
|
export function removeAccent(str: string): string {
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import { EnvironmentService } from '../../integrations/environment/environment.s
|
|||||||
import { TokenService } from '../auth/services/token.service';
|
import { TokenService } from '../auth/services/token.service';
|
||||||
import { JwtAttachmentPayload, JwtType } from '../auth/dto/jwt-payload';
|
import { JwtAttachmentPayload, JwtType } from '../auth/dto/jwt-payload';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import { sanitize } from 'sanitize-filename-ts';
|
||||||
import { AttachmentInfoDto, RemoveIconDto } from './dto/attachment.dto';
|
import { AttachmentInfoDto, RemoveIconDto } from './dto/attachment.dto';
|
||||||
import { PageAccessService } from '../page/page-access/page-access.service';
|
import { PageAccessService } from '../page/page-access/page-access.service';
|
||||||
import { AuditEvent, AuditResource } from '../../common/events/audit-events';
|
import { AuditEvent, AuditResource } from '../../common/events/audit-events';
|
||||||
@@ -356,19 +357,13 @@ export class AttachmentController {
|
|||||||
throw new BadRequestException('Invalid image attachment type');
|
throw new BadRequestException('Invalid image attachment type');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fileName) {
|
if (!fileName || sanitize(fileName) !== fileName) {
|
||||||
throw new BadRequestException('Invalid file name');
|
throw new BadRequestException('Invalid file name');
|
||||||
}
|
}
|
||||||
|
|
||||||
const ext = path.extname(fileName);
|
const filenameWithoutExt = path.basename(fileName, path.extname(fileName));
|
||||||
const filenameWithoutExt = path.basename(fileName, ext);
|
if (!isValidUUID(filenameWithoutExt)) {
|
||||||
|
throw new BadRequestException('Invalid file id');
|
||||||
if (
|
|
||||||
!ext ||
|
|
||||||
!isValidUUID(filenameWithoutExt) ||
|
|
||||||
`${filenameWithoutExt}${ext}` !== fileName
|
|
||||||
) {
|
|
||||||
throw new BadRequestException('Invalid file name');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`;
|
const filePath = `${getAttachmentFolderPath(attachmentType, workspace.id)}/${fileName}`;
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: 4101fc427b...e703b8bf47
@@ -23,12 +23,9 @@ import {
|
|||||||
SpaceCaslSubject,
|
SpaceCaslSubject,
|
||||||
} from '../../core/casl/interfaces/space-ability.type';
|
} from '../../core/casl/interfaces/space-ability.type';
|
||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
|
import { sanitize } from 'sanitize-filename-ts';
|
||||||
import { getExportExtension } from './utils';
|
import { getExportExtension } from './utils';
|
||||||
import {
|
import { getMimeType, getPageTitle } from '../../common/helpers';
|
||||||
getMimeType,
|
|
||||||
getPageTitle,
|
|
||||||
sanitizeFileName,
|
|
||||||
} from '../../common/helpers';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { AuditEvent, AuditResource } from '../../common/events/audit-events';
|
import { AuditEvent, AuditResource } from '../../common/events/audit-events';
|
||||||
import {
|
import {
|
||||||
@@ -88,9 +85,7 @@ export class ExportController {
|
|||||||
|
|
||||||
if (result.type === 'file') {
|
if (result.type === 'file') {
|
||||||
const ext = getExportExtension(dto.format);
|
const ext = getExportExtension(dto.format);
|
||||||
const fileName =
|
const fileName = sanitize(page.title || 'untitled') + ext;
|
||||||
sanitizeFileName(page.title || 'untitled', { preserveSpaces: true }) +
|
|
||||||
ext;
|
|
||||||
const contentType = getMimeType(path.extname(fileName));
|
const contentType = getMimeType(path.extname(fileName));
|
||||||
|
|
||||||
res.headers({
|
res.headers({
|
||||||
@@ -101,9 +96,7 @@ export class ExportController {
|
|||||||
|
|
||||||
res.send(result.content);
|
res.send(result.content);
|
||||||
} else {
|
} else {
|
||||||
const fileName =
|
const fileName = sanitize(page.title || 'untitled') + '.zip';
|
||||||
sanitizeFileName(page.title || 'untitled', { preserveSpaces: true }) +
|
|
||||||
'.zip';
|
|
||||||
|
|
||||||
res.headers({
|
res.headers({
|
||||||
'Content-Type': 'application/zip',
|
'Content-Type': 'application/zip',
|
||||||
@@ -151,9 +144,7 @@ export class ExportController {
|
|||||||
'Content-Type': 'application/zip',
|
'Content-Type': 'application/zip',
|
||||||
'Content-Disposition':
|
'Content-Disposition':
|
||||||
'attachment; filename="' +
|
'attachment; filename="' +
|
||||||
encodeURIComponent(
|
encodeURIComponent(sanitize(exportFile.fileName)) +
|
||||||
sanitizeFileName(exportFile.fileName, { preserveSpaces: true }),
|
|
||||||
) +
|
|
||||||
'"',
|
'"',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||||
import { MultipartFile } from '@fastify/multipart';
|
import { MultipartFile } from '@fastify/multipart';
|
||||||
|
import { sanitize } from 'sanitize-filename-ts';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import {
|
import {
|
||||||
htmlToJson,
|
htmlToJson,
|
||||||
@@ -52,8 +53,8 @@ export class ImportService {
|
|||||||
const file = await filePromise;
|
const file = await filePromise;
|
||||||
const fileBuffer = await file.toBuffer();
|
const fileBuffer = await file.toBuffer();
|
||||||
const fileExtension = path.extname(file.filename).toLowerCase();
|
const fileExtension = path.extname(file.filename).toLowerCase();
|
||||||
const fileName = sanitizeFileName(
|
const fileName = sanitize(
|
||||||
path.basename(file.filename, fileExtension),
|
path.basename(file.filename, fileExtension).slice(0, 255),
|
||||||
);
|
);
|
||||||
const fileContent = fileBuffer.toString();
|
const fileContent = fileBuffer.toString();
|
||||||
|
|
||||||
|
|||||||
+4
-5
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "docmost",
|
"name": "docmost",
|
||||||
"homepage": "https://docmost.com",
|
"homepage": "https://docmost.com",
|
||||||
"version": "0.80.1",
|
"version": "0.80.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nx run-many -t build",
|
"build": "nx run-many -t build",
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"ms": "3.0.0-canary.1",
|
"ms": "3.0.0-canary.1",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"rfc6902": "5.2.0",
|
"rfc6902": "5.2.0",
|
||||||
"uuid": "^14.0.0",
|
"uuid": "^13.0.0",
|
||||||
"y-indexeddb": "^9.0.12",
|
"y-indexeddb": "^9.0.12",
|
||||||
"y-prosemirror": "1.3.7",
|
"y-prosemirror": "1.3.7",
|
||||||
"yjs": "^13.6.30"
|
"yjs": "^13.6.30"
|
||||||
@@ -128,12 +128,11 @@
|
|||||||
"yaml@>=2.0.0 <2.8.3": "2.8.3",
|
"yaml@>=2.0.0 <2.8.3": "2.8.3",
|
||||||
"path-to-regexp@^8": "8.4.0",
|
"path-to-regexp@^8": "8.4.0",
|
||||||
"brace-expansion@^5": "5.0.5",
|
"brace-expansion@^5": "5.0.5",
|
||||||
"@xmldom/xmldom": "0.8.13",
|
"@xmldom/xmldom": "0.8.12",
|
||||||
"handlebars": "4.7.9",
|
"handlebars": "4.7.9",
|
||||||
"axios": "1.15.0",
|
"axios": "1.15.0",
|
||||||
"langsmith": "0.5.19",
|
"langsmith": "0.5.19",
|
||||||
"follow-redirects": "1.16.0",
|
"follow-redirects": "1.16.0"
|
||||||
"protobufjs": "7.5.5"
|
|
||||||
},
|
},
|
||||||
"neverBuiltDependencies": []
|
"neverBuiltDependencies": []
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+656
-660
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user