fix: restrict share uploads to text files
This commit is contained in:
+37
-8
@@ -33,6 +33,21 @@ import {
|
|||||||
} from "@/lib/share/limits";
|
} from "@/lib/share/limits";
|
||||||
|
|
||||||
const IMAGE_TYPES = "image/png,image/jpeg,image/webp,image/gif";
|
const IMAGE_TYPES = "image/png,image/jpeg,image/webp,image/gif";
|
||||||
|
const TEXT_FILE_TYPES = [
|
||||||
|
"application/json",
|
||||||
|
"application/xml",
|
||||||
|
"application/yaml",
|
||||||
|
"application/toml",
|
||||||
|
"application/javascript",
|
||||||
|
"application/x-javascript",
|
||||||
|
];
|
||||||
|
const TEXT_FILE_ACCEPT = `text/*,${TEXT_FILE_TYPES.join(",")}`;
|
||||||
|
|
||||||
|
function isTextFile(file: File) {
|
||||||
|
return !file.type ||
|
||||||
|
file.type.startsWith("text/") ||
|
||||||
|
TEXT_FILE_TYPES.includes(file.type);
|
||||||
|
}
|
||||||
|
|
||||||
function formatBytes(bytes: number) {
|
function formatBytes(bytes: number) {
|
||||||
if (bytes < 1024) return `${bytes} B`;
|
if (bytes < 1024) return `${bytes} B`;
|
||||||
@@ -57,20 +72,33 @@ export function ShareUploadClient() {
|
|||||||
return () => URL.revokeObjectURL(imagePreview);
|
return () => URL.revokeObjectURL(imagePreview);
|
||||||
}, [imagePreview]);
|
}, [imagePreview]);
|
||||||
|
|
||||||
|
function showError(message: string) {
|
||||||
|
setError(message);
|
||||||
|
toast.error(message);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadTextFile(file: File) {
|
async function loadTextFile(file: File) {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
if (!isTextFile(file)) {
|
||||||
|
showError("รองรับเฉพาะไฟล์ข้อความเท่านั้น");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (file.size > MAX_SHARE_TEXT_BYTES) {
|
if (file.size > MAX_SHARE_TEXT_BYTES) {
|
||||||
setError("ไฟล์ต้องมีขนาดไม่เกิน 5 MiB");
|
showError("ไฟล์ต้องมีขนาดไม่เกิน 5 MiB");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const content = new TextDecoder("utf-8", { fatal: true }).decode(
|
const content = new TextDecoder("utf-8", { fatal: true }).decode(
|
||||||
await file.arrayBuffer()
|
await file.arrayBuffer()
|
||||||
);
|
);
|
||||||
|
if (content.includes("\0")) {
|
||||||
|
showError("ไฟล์ข้อความต้องไม่มีอักขระ NUL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setText(content);
|
setText(content);
|
||||||
toast.success(`เปิด ${file.name} แล้ว`);
|
toast.success(`เปิด ${file.name} แล้ว`);
|
||||||
} catch {
|
} catch {
|
||||||
setError("ไฟล์ต้องเป็นข้อความ UTF-8 ที่ถูกต้อง");
|
showError("ไฟล์ต้องเป็นข้อความ UTF-8 ที่ถูกต้อง");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +106,11 @@ export function ShareUploadClient() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
if (!IMAGE_TYPES.split(",").includes(file.type)) {
|
if (!IMAGE_TYPES.split(",").includes(file.type)) {
|
||||||
setError("รูปภาพต้องเป็น PNG, JPEG, WebP หรือ GIF");
|
showError("รูปภาพต้องเป็น PNG, JPEG, WebP หรือ GIF");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (file.size > MAX_SHARE_IMAGE_BYTES) {
|
if (file.size > MAX_SHARE_IMAGE_BYTES) {
|
||||||
setError("รูปภาพต้องมีขนาดไม่เกิน 50 MiB");
|
showError("รูปภาพต้องมีขนาดไม่เกิน 50 MiB");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setImage(file);
|
setImage(file);
|
||||||
@@ -93,11 +121,11 @@ export function ShareUploadClient() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setError(null);
|
setError(null);
|
||||||
if (!text.trim()) {
|
if (!text.trim()) {
|
||||||
setError("วางข้อความก่อนสร้างลิงก์");
|
showError("วางข้อความก่อนสร้างลิงก์");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (byteCount > MAX_SHARE_TEXT_BYTES) {
|
if (byteCount > MAX_SHARE_TEXT_BYTES) {
|
||||||
setError("ข้อความต้องมีขนาดไม่เกิน 5 MiB");
|
showError("ข้อความต้องมีขนาดไม่เกิน 5 MiB");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +138,12 @@ export function ShareUploadClient() {
|
|||||||
try {
|
try {
|
||||||
const result = await createShareAction(formData);
|
const result = await createShareAction(formData);
|
||||||
if (!result.success || !result.id) {
|
if (!result.success || !result.id) {
|
||||||
setError(result.success ? "สร้างลิงก์ไม่สำเร็จ" : result.error);
|
showError(result.success ? "สร้างลิงก์ไม่สำเร็จ" : result.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(`/share/${result.id}`);
|
router.push(`/share/${result.id}`);
|
||||||
} catch {
|
} catch {
|
||||||
setError("เชื่อมต่อเซิร์ฟเวอร์ไม่สำเร็จ ลองใหม่อีกครั้ง");
|
showError("เชื่อมต่อเซิร์ฟเวอร์ไม่สำเร็จ ลองใหม่อีกครั้ง");
|
||||||
} finally {
|
} finally {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}
|
}
|
||||||
@@ -196,6 +224,7 @@ export function ShareUploadClient() {
|
|||||||
<Input
|
<Input
|
||||||
ref={textFileRef}
|
ref={textFileRef}
|
||||||
type="file"
|
type="file"
|
||||||
|
accept={TEXT_FILE_ACCEPT}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
|
|||||||
Reference in New Issue
Block a user