diff --git a/app/api/webdav/[...path]/route.ts b/app/api/webdav/[...path]/route.ts index c60ca18bb..75c23ac87 100644 --- a/app/api/webdav/[...path]/route.ts +++ b/app/api/webdav/[...path]/route.ts @@ -12,17 +12,28 @@ async function handle( const requestUrl = new URL(req.url); let endpoint = requestUrl.searchParams.get("endpoint"); - if (!endpoint?.endsWith("/")) { - endpoint += "/"; + + // Validate the endpoint to prevent potential SSRF attacks + if (!endpoint || !endpoint.startsWith("/")) { + return NextResponse.json( + { + error: true, + msg: "Invalid endpoint", + }, + { + status: 400, + }, + ); } const endpointPath = params.path.join("/"); + const targetPath = `${endpoint}/${endpointPath}`; // only allow MKCOL, GET, PUT if (req.method !== "MKCOL" && req.method !== "GET" && req.method !== "PUT") { return NextResponse.json( { error: true, - msg: "you are not allowed to request " + params.path.join("/"), + msg: "you are not allowed to request " + targetPath, }, { status: 403, @@ -31,14 +42,11 @@ async function handle( } // for MKCOL request, only allow request ${folder} - if ( - req.method == "MKCOL" && - !new URL(endpointPath).pathname.endsWith(folder) - ) { + if (req.method === "MKCOL" && !targetPath.endsWith(folder)) { return NextResponse.json( { error: true, - msg: "you are not allowed to request " + params.path.join("/"), + msg: "you are not allowed to request " + targetPath, }, { status: 403, @@ -47,14 +55,11 @@ async function handle( } // for GET request, only allow request ending with fileName - if ( - req.method == "GET" && - !new URL(endpointPath).pathname.endsWith(fileName) - ) { + if (req.method === "GET" && !targetPath.endsWith(fileName)) { return NextResponse.json( { error: true, - msg: "you are not allowed to request " + params.path.join("/"), + msg: "you are not allowed to request " + targetPath, }, { status: 403, @@ -63,14 +68,11 @@ async function handle( } // for PUT request, only allow request ending with fileName - if ( - req.method == "PUT" && - !new URL(endpointPath).pathname.endsWith(fileName) - ) { + if (req.method === "PUT" && !targetPath.endsWith(fileName)) { return NextResponse.json( { error: true, - msg: "you are not allowed to request " + params.path.join("/"), + msg: "you are not allowed to request " + targetPath, }, { status: 403, @@ -78,7 +80,7 @@ async function handle( ); } - const targetUrl = `${endpoint + endpointPath}`; + const targetUrl = `${endpoint}/${endpointPath}`; const method = req.method; const shouldNotHaveBody = ["get", "head"].includes( @@ -90,6 +92,7 @@ async function handle( authorization: req.headers.get("authorization") ?? "", }, body: shouldNotHaveBody ? null : req.body, + redirect: "manual", method, // @ts-ignore duplex: "half", diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 6be3e74b1..ea26d8254 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -236,6 +236,9 @@ export class ChatGPTApi implements LLMApi { if (finished || controller.signal.aborted) { responseText += remainText; console.log("[Response Animation] finished"); + if (responseText?.length === 0) { + options.onError?.(new Error("empty response from server")); + } return; } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index ae9f5881d..6c33f6bc5 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -240,6 +240,8 @@ function useSubmitHandler() { }, []); const shouldSubmit = (e: React.KeyboardEvent) => { + // Fix Chinese input method "Enter" on Safari + if (e.keyCode == 229) return false; if (e.key !== "Enter") return false; if (e.key === "Enter" && (e.nativeEvent.isComposing || isComposing.current)) return false; @@ -399,12 +401,12 @@ function ChatAction(props: { ...props.style, } as React.CSSProperties) : props.loding - ? ({ - "--icon-width": `30px`, - "--full-width": `30px`, - ...props.style, - } as React.CSSProperties) - : props.style + ? ({ + "--icon-width": `30px`, + "--full-width": `30px`, + ...props.style, + } as React.CSSProperties) + : props.style } > {props.icon ? ( diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx index f3a916cc5..7c70fe1a5 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -116,9 +116,27 @@ function escapeDollarNumber(text: string) { return escapedText; } +function escapeBrackets(text: string) { + const pattern = + /(```[\s\S]*?```|`.*?`)|\\\[([\s\S]*?[^\\])\\\]|\\\((.*?)\\\)/g; + return text.replace( + pattern, + (match, codeBlock, squareBracket, roundBracket) => { + if (codeBlock) { + return codeBlock; + } else if (squareBracket) { + return `$$${squareBracket}$$`; + } else if (roundBracket) { + return `$${roundBracket}$`; + } + return match; + }, + ); +} + function _MarkDownContent(props: { content: string }) { const escapedContent = useMemo( - () => escapeDollarNumber(props.content), + () => escapeBrackets(escapeDollarNumber(props.content)), [props.content], ); diff --git a/app/utils.ts b/app/utils.ts index a3ba5eaf9..1fb3a1649 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -59,10 +59,7 @@ export async function downloadAs(text: string, filename: string) { if (result !== null) { try { - await window.__TAURI__.fs.writeTextFile( - result, - text - ); + await window.__TAURI__.fs.writeTextFile(result, text); showToast(Locale.Download.Success); } catch (error) { showToast(Locale.Download.Failed); @@ -294,9 +291,8 @@ export function getMessageImages(message: RequestMessage): string[] { } export function isVisionModel(model: string) { - return ( - // model.startsWith("gpt-4-vision") || - // model.startsWith("gemini-pro-vision") || - model.includes("vision") - ); + // Note: This is a better way using the TypeScript feature instead of `&&` or `||` (ts v5.5.0-dev.20240314 I've been using) + const visionKeywords = ["vision", "claude-3"]; + + return visionKeywords.some((keyword) => model.includes(keyword)); } diff --git a/package.json b/package.json index 7f400f43e..dd910bc63 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "axios": "^0.26.0", "cheerio": "^1.0.0-rc.12", "duck-duck-scrape": "^2.2.4", - "emoji-picker-react": "^4.5.15", + "emoji-picker-react": "^4.9.2", "encoding": "^0.1.13", "fuse.js": "^7.0.0", "html-entities": "^2.4.0", @@ -61,8 +61,8 @@ "devDependencies": { "@tauri-apps/cli": "1.5.7", "@types/html-to-text": "^9.0.1", - "@types/node": "^20.9.0", - "@types/react": "^18.2.14", + "@types/node": "^20.11.30", + "@types/react": "^18.2.70", "@types/react-dom": "^18.2.7", "@types/react-katex": "^3.0.0", "@types/spark-md5": "^3.0.4", @@ -70,7 +70,7 @@ "eslint": "^8.49.0", "eslint-config-next": "13.4.19", "eslint-config-prettier": "^8.8.0", - "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-prettier": "^5.1.3", "husky": "^8.0.0", "lint-staged": "^13.2.2", "prettier": "^3.0.2", diff --git a/yarn.lock b/yarn.lock index 4e3e2acf5..1db465ddd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1938,17 +1938,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@pkgr/utils@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" - integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw== - dependencies: - cross-spawn "^7.0.3" - is-glob "^4.0.3" - open "^8.4.0" - picocolors "^1.0.0" - tiny-glob "^0.2.9" - tslib "^2.4.0" +"@pkgr/core@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.0.tgz#7d8dacb7fdef0e4387caf7396cbd77f179867d06" + integrity sha512-Zwq5OCzuwJC2jwqmpEQt7Ds1DTi6BWSwoGkbb1n9pO3hzb35BoJELx7c0T23iDkBGkh2e7tvOtjF3tr3OaQHDQ== "@remix-run/router@1.8.0": version "1.8.0" @@ -2738,10 +2731,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.2.14": - version "18.2.14" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.14.tgz#fa7a6fecf1ce35ca94e74874f70c56ce88f7a127" - integrity sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g== +"@types/react@*", "@types/react@^18.2.70": + version "18.2.70" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.70.tgz#89a37f9e0a6a4931f4259c598f40fd44dd6abf71" + integrity sha512-hjlM2hho2vqklPhopNkXkdkeq6Lv8WSZTpr7956zY+3WS5cfYUewtCzsJLsbW5dEv3lfSeQ4W14ZFeKC437JRQ== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -3994,11 +3987,6 @@ deepmerge@^4.2.2, deepmerge@^4.3.1: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - define-properties@^1.1.3, define-properties@^1.1.4: version "1.2.0" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" @@ -4146,10 +4134,12 @@ elkjs@^0.8.2: resolved "https://registry.npmmirror.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e" integrity sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ== -emoji-picker-react@^4.5.15: - version "4.5.15" - resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.5.15.tgz#e12797c50584cb8af8aee7eb6c7c8fd953e41f7e" - integrity sha512-BTqo+pNUE8kqX8BKFTbD4fhlxcA69qfie5En4PerReLaaPfXVyRlDJ1uf85nKj2u5esUQ999iUf8YyqcPsM2Qw== +emoji-picker-react@^4.9.2: + version "4.9.2" + resolved "https://registry.yarnpkg.com/emoji-picker-react/-/emoji-picker-react-4.9.2.tgz#5118c5e1028ce4a96c94eb7c9bef09d30b08742c" + integrity sha512-pdvLKpto0DMrjE+/8V9QeYjrMcOkJmqBn3GyCSG2zanY32rN2cnWzBUmzArvapAjzBvgf7hNmJP8xmsdu0cmJA== + dependencies: + flairup "0.0.38" emoji-regex@^8.0.0: version "8.0.0" @@ -4403,12 +4393,13 @@ eslint-plugin-jsx-a11y@^6.5.1: object.fromentries "^2.0.6" semver "^6.3.0" -eslint-plugin-prettier@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" - integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== +eslint-plugin-prettier@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" + integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== dependencies: prettier-linter-helpers "^1.0.0" + synckit "^0.8.6" "eslint-plugin-react-hooks@^4.5.0 || 5.0.0-canary-7118f5dd7-20230705": version "4.6.0" @@ -4660,6 +4651,11 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +flairup@0.0.38: + version "0.0.38" + resolved "https://registry.yarnpkg.com/flairup/-/flairup-0.0.38.tgz#62216990a8317a1b07d1d816033624c5b2130f31" + integrity sha512-W9QA5TM7eYNlGoBYwfVn/o6v4yWBCxfq4+EJ5w774oFeyWvVWnYq6Dgt4CJltjG9y/lPwbOqz3jSSr8K66ToGg== + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -4853,11 +4849,6 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globalyzer@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" - integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== - globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -4881,11 +4872,6 @@ globby@^13.1.3: merge2 "^1.4.1" slash "^4.0.0" -globrex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" - integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== - gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -5255,11 +5241,6 @@ is-date-object@^1.0.1, is-date-object@^1.0.5: dependencies: has-tostringtag "^1.0.0" -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -5384,13 +5365,6 @@ is-weakset@^2.0.1: call-bind "^1.0.2" get-intrinsic "^1.1.1" -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -7379,13 +7353,13 @@ svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" -synckit@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" - integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== +synckit@^0.8.5, synckit@^0.8.6: + version "0.8.8" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" + integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== dependencies: - "@pkgr/utils" "^2.3.1" - tslib "^2.5.0" + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" tapable@^2.1.1, tapable@^2.2.0: version "2.2.1" @@ -7428,14 +7402,6 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -tiny-glob@^0.2.9: - version "0.2.9" - resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" - integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== - dependencies: - globalyzer "0.1.0" - globrex "^0.1.2" - tiny-invariant@^1.0.6: version "1.3.1" resolved "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"