mirror of
https://github.com/rikkaneko/paste.git
synced 2025-08-05 13:40:09 +01:00
Add description text and file size check to paste.html
Add x-pass header to authentication method Add size to paste info Prefix the custom header key with x- Signed-off-by: Joe Ma <rikkaneko23@gmail.com>
This commit is contained in:
parent
78806e331d
commit
d1c43280d5
3 changed files with 67 additions and 15 deletions
18
paste.html
18
paste.html
|
@ -25,6 +25,17 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h2>Paste Service</h2>
|
<h2>Paste Service</h2>
|
||||||
|
<p>
|
||||||
|
<a href="https://pb.nekoul.com">pb.nekoul.com</a> is a pastebin-like service hosted on Cloudflare Worker.<br>
|
||||||
|
This service is primaryly designed for own usage and interest only.<br>
|
||||||
|
All data may be deleted or expired without any notification and guarantee. Please <b>DO NOT</b> abuse this service.<br>
|
||||||
|
The limit for file upload is <b>10 MB</b> and the paste will be kept for <b>180 days</b> only by default.<br>
|
||||||
|
The source code is available in my GitHub repository <a href="https://github.com/rikkaneko/paste">[here]</a>.<br>
|
||||||
|
This webpage is designed for upload files only.
|
||||||
|
For other operations like changing paste settings and deleting paste, please make use of the
|
||||||
|
<a href="https://pb.nekoul.com/api">API call</a> with <a href="https://wiki.archlinux.org/title/CURL">curl</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
<form id="upload_file_form" action="https://pb.nekoul.com" method="POST" enctype=multipart/form-data>
|
<form id="upload_file_form" action="https://pb.nekoul.com" method="POST" enctype=multipart/form-data>
|
||||||
<div>
|
<div>
|
||||||
<h4>Upload file</h4>
|
<h4>Upload file</h4>
|
||||||
|
@ -92,6 +103,13 @@
|
||||||
let select_file = elements.namedItem("upload_file");
|
let select_file = elements.namedItem("upload_file");
|
||||||
let text = elements.namedItem("text_input");
|
let text = elements.namedItem("text_input");
|
||||||
if (!!select_file.value.length ^ !!text.value.length) {
|
if (!!select_file.value.length ^ !!text.value.length) {
|
||||||
|
// Check file size
|
||||||
|
const size = select_file.files[0]?.size ?? 0;
|
||||||
|
if (size <= 0 || size > 10485760) {
|
||||||
|
alert("Upload file size must not excess 10 MB.");
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < elements.length; i++) {
|
||||||
elements[i].disabled = elements[i].value.length === 0;
|
elements[i].disabled = elements[i].value.length === 0;
|
||||||
}
|
}
|
||||||
|
|
63
src/index.ts
63
src/index.ts
|
@ -39,25 +39,40 @@ const API_SPEC_TEXT =
|
||||||
[API Specification]
|
[API Specification]
|
||||||
GET / Fetch the Web frontpage for uploading text/file [x]
|
GET / Fetch the Web frontpage for uploading text/file [x]
|
||||||
GET /api Fetch API specification
|
GET /api Fetch API specification
|
||||||
|
|
||||||
|
# Authentication support HTTP Basic access authentication (RFC 7617) or the x-pass header
|
||||||
GET /<uuid> Fetch the paste by uuid [x]
|
GET /<uuid> Fetch the paste by uuid [x]
|
||||||
|
|
||||||
GET /<uuid>/<lang> Fetch the paste (code) in rendered HTML with syntax highlighting [ ]
|
GET /<uuid>/<lang> Fetch the paste (code) in rendered HTML with syntax highlighting [ ]
|
||||||
GET /<uuid>/settings Fetch the paste information [x]
|
GET /<uuid>/settings Fetch the paste information [x]
|
||||||
GET /<uuid>/download Download the paste [x]
|
GET /<uuid>/download Download the paste [x]
|
||||||
POST / Create new paste [x] # Only support multipart/form-data and raw data
|
|
||||||
|
# Only support multipart/form-data and raw request
|
||||||
|
# For form-data, u=<upload-data>, both title and content-type is deduced from the u
|
||||||
|
# The following key is supported for both HTTP form request and headers
|
||||||
|
# x-title: File title, i.e.,
|
||||||
|
# content-type: The media type (MIME) of the data and encoding, i.e., text/plain; charset=UTF-8;
|
||||||
|
# x-pass: Paste password
|
||||||
|
# x-read-limit: Limit access times to paste to <read-limit>
|
||||||
|
POST / Create new paste [x]
|
||||||
|
|
||||||
DELETE /<uuid> Delete paste by uuid [x]
|
DELETE /<uuid> Delete paste by uuid [x]
|
||||||
POST /<uuid>/settings Update paste setting, i.e., passcode and valid time [ ]
|
POST /<uuid>/settings Update paste setting, i.e., passcode and valid time [ ]
|
||||||
|
|
||||||
|
# For paste with password protected, all API call related to the pastes requires additional x-pass header
|
||||||
|
|
||||||
* uuid: [A-z0-9]{${UUID_LENGTH}}
|
* uuid: [A-z0-9]{${UUID_LENGTH}}
|
||||||
* option: Render language
|
* option: Render language
|
||||||
|
|
||||||
Features
|
Features
|
||||||
* Password protection [ ]
|
* Password protection [x]
|
||||||
* Expiring paste [ ]
|
* Expiring paste [ ]
|
||||||
|
|
||||||
[ ] indicated not implemented
|
[ ] indicated not implemented
|
||||||
|
|
||||||
Limitation
|
Limitation
|
||||||
* Max. 10MB file size upload (Max. 100MB body size for free Cloudflare plan)
|
* Max. 10MB file size upload
|
||||||
|
* Paste will be kept for 180 days only
|
||||||
|
|
||||||
Last update on 2 June.
|
Last update on 2 June.
|
||||||
`;
|
`;
|
||||||
|
@ -165,14 +180,14 @@ export default {
|
||||||
|
|
||||||
// Raw body
|
// Raw body
|
||||||
} else {
|
} else {
|
||||||
if (headers.has("title")) {
|
if (headers.has("x-title")) {
|
||||||
title = headers.get("title") || "";
|
title = headers.get("x-title") || "";
|
||||||
mime_type = contentType(title) || undefined;
|
mime_type = contentType(title) || undefined;
|
||||||
}
|
}
|
||||||
mime_type = headers.get("content-type") || mime_type;
|
mime_type = headers.get("content-type") || mime_type;
|
||||||
password = headers.get("pass") || undefined;
|
password = headers.get("x-pass") || undefined;
|
||||||
// Handle read-limit:read_count_remain
|
// Handle read-limit:read_count_remain
|
||||||
const count = headers.get("read-limit") || undefined;
|
const count = headers.get("x-read-limit") || undefined;
|
||||||
if (count !== undefined && !isNaN(+count)) {
|
if (count !== undefined && !isNaN(+count)) {
|
||||||
read_limit = Number(count) || undefined;
|
read_limit = Number(count) || undefined;
|
||||||
}
|
}
|
||||||
|
@ -188,7 +203,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check request.body size <= 10MB
|
// Check request.body size <= 10MB
|
||||||
if (buffer.byteLength > 10485760) {
|
const size = buffer.byteLength;
|
||||||
|
if (size > 10485760) {
|
||||||
return new Response("Paste size must be under 10MB.\n", {
|
return new Response("Paste size must be under 10MB.\n", {
|
||||||
status: 422
|
status: 422
|
||||||
});
|
});
|
||||||
|
@ -212,6 +228,7 @@ export default {
|
||||||
title: title ?? undefined,
|
title: title ?? undefined,
|
||||||
last_modified: Date.now(),
|
last_modified: Date.now(),
|
||||||
password: password? sha256(password).slice(0, 16): undefined,
|
password: password? sha256(password).slice(0, 16): undefined,
|
||||||
|
size,
|
||||||
read_count_remain: read_limit,
|
read_count_remain: read_limit,
|
||||||
mime_type
|
mime_type
|
||||||
};
|
};
|
||||||
|
@ -277,24 +294,29 @@ export default {
|
||||||
if (cert === null) {
|
if (cert === null) {
|
||||||
return new Response("Invalid Authorization header.", {
|
return new Response("Invalid Authorization header.", {
|
||||||
status: 400
|
status: 400
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
// Check password and username should be empty
|
// Check password and username should be empty
|
||||||
if (cert[0].length != 0 || descriptor.password !== sha256(cert[1]).slice(0, 16)) {
|
if (cert[0].length != 0 || descriptor.password !== sha256(cert[1]).slice(0, 16)) {
|
||||||
return new Response(null, {
|
return new Response("Incorrect password.\n", {
|
||||||
status: 401,
|
status: 401,
|
||||||
headers: {
|
headers: {
|
||||||
"WWW-Authenticate": "Basic realm=\"Requires password\""
|
"WWW-Authenticate": "Basic realm=\"Requires password\""
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
// x-pass header
|
||||||
|
} else if (headers.has("x-pass")) {
|
||||||
|
if (descriptor.password !== sha256(headers.get("x-pass")!).slice(0, 16)) {
|
||||||
|
return new Response("Incorrect password.\n");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return new Response(null, {
|
return new Response("This paste requires password.\n", {
|
||||||
status: 401,
|
status: 401,
|
||||||
headers: {
|
headers: {
|
||||||
"WWW-Authenticate": "Basic realm=\"Requires password\""
|
"WWW-Authenticate": "Basic realm=\"Requires password\""
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +325,7 @@ export default {
|
||||||
if (descriptor.read_count_remain <= 0) {
|
if (descriptor.read_count_remain <= 0) {
|
||||||
return new Response("Paste expired.\n", {
|
return new Response("Paste expired.\n", {
|
||||||
status: 410
|
status: 410
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
descriptor.read_count_remain--;
|
descriptor.read_count_remain--;
|
||||||
ctx.waitUntil(env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor)));
|
ctx.waitUntil(env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor)));
|
||||||
|
@ -411,6 +433,7 @@ function get_paste_info(uuid: string, descriptor: PasteIndexEntry): string {
|
||||||
id: ${uuid}
|
id: ${uuid}
|
||||||
title: ${descriptor.title || "<empty>"}
|
title: ${descriptor.title || "<empty>"}
|
||||||
mime-type: ${descriptor.mime_type ?? "application/octet-stream"}
|
mime-type: ${descriptor.mime_type ?? "application/octet-stream"}
|
||||||
|
size: ${descriptor.size} bytes (${to_human_readable_size(descriptor.size)})
|
||||||
password: ${(!!descriptor.password)}
|
password: ${(!!descriptor.password)}
|
||||||
editable: ${descriptor.editable? descriptor.editable: true}
|
editable: ${descriptor.editable? descriptor.editable: true}
|
||||||
remaining read count: ${descriptor.read_count_remain !== undefined?
|
remaining read count: ${descriptor.read_count_remain !== undefined?
|
||||||
|
@ -449,11 +472,21 @@ function get_basic_auth(headers: Headers): [string, string] | null {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function to_human_readable_size(bytes: number): string {
|
||||||
|
let size = bytes + " bytes";
|
||||||
|
const units = ["KiB", "MiB", "GiB", "TiB"];
|
||||||
|
for (let i = 0, approx = bytes / 1024; approx > 1; approx /= 1024, i++) {
|
||||||
|
size = approx.toFixed(3) + " " + units[i];
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
interface PasteIndexEntry {
|
interface PasteIndexEntry {
|
||||||
title?: string,
|
title?: string,
|
||||||
mime_type?: string,
|
mime_type?: string,
|
||||||
last_modified: number,
|
last_modified: number,
|
||||||
password?: string
|
size: number,
|
||||||
|
password?: string,
|
||||||
editable?: boolean, // Default: True
|
editable?: boolean, // Default: True
|
||||||
read_count_remain?: number
|
read_count_remain?: number
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ name = "paste"
|
||||||
main = "src/index.ts"
|
main = "src/index.ts"
|
||||||
compatibility_date = "2022-05-30"
|
compatibility_date = "2022-05-30"
|
||||||
node_compat = true
|
node_compat = true
|
||||||
|
workers_dev = false
|
||||||
kv_namespaces = [
|
kv_namespaces = [
|
||||||
{ binding = "PASTE_INDEX", id = "a578863da0564cd7beadd9ce4a2d53e8", preview_id = "66d9440e13124099a5e508fe1ff0a489" }
|
{ binding = "PASTE_INDEX", id = "a578863da0564cd7beadd9ce4a2d53e8", preview_id = "66d9440e13124099a5e508fe1ff0a489" }
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue