From 80e8aec8570c387c3a0305ee17415b0234a7a8bb Mon Sep 17 00:00:00 2001 From: Joe Ma Date: Thu, 2 Jun 2022 01:31:56 +0800 Subject: [PATCH] Add support to create paste from HTTP form Add paste.html Add support to direct download paste Change the default UUID length to 4 Add copyright notice --- paste.html | 69 +++++++++++++++++++ paste.iml | 8 +++ src/index.ts | 191 ++++++++++++++++++++++++++++++--------------------- 3 files changed, 190 insertions(+), 78 deletions(-) create mode 100644 paste.html create mode 100644 paste.iml diff --git a/paste.html b/paste.html new file mode 100644 index 0000000..14aa1a8 --- /dev/null +++ b/paste.html @@ -0,0 +1,69 @@ + + + + + + + Paste + + + +

Paste service - paste.nekoul.com

+[Homepage][API] +

Upload file

+
+
+ +
(0 byte)
+
+
+ + +

Upload text

+
+
+ +
+
+
+ +

© 2022 rikkaneko

+ + diff --git a/paste.iml b/paste.iml new file mode 100644 index 0000000..80cc739 --- /dev/null +++ b/paste.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index f356f85..71073c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,28 @@ +/* + * This file is part of paste. + * Copyright (c) 2022 Joe Ma + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + import {AwsClient} from "aws4fetch"; import { customAlphabet } from 'nanoid' // Constants -const SERVICE_URL = "https://paste.nekoul.com" +const SERVICE_URL = "paste.nekoul.com" +const PASTE_INDEX_HTML_URL = "https://raw.githubusercontent.com/rikkaneko/paste/main/paste.html" +const UUID_LENGTH = 4 export interface Env { PASTE_INDEX: KVNamespace; @@ -11,30 +31,37 @@ export interface Env { ENDPOINT: string } -const API_DOCS = - `Paste service https://paste.nekoul.com +const API_SPEC_TEXT = + `Paste service https://${SERVICE_URL} -[API Draft] -GET / Fetch the HTML for uploading text/file [ ] -GET / Fetch the paste by uuid [x] -GET // Fetch the paste (code) in rendered HTML with syntax highlighting [ ] -GET //settings Fetch the paste information [x] -GET /status Fetch service information [x] -PUT / Create new paste [x] -POST / Update the paste by uuid [x] -DELETE / Delete paste by uuid [x] -POST //settings Update paste setting, i.e., passcode and valid time [ ] +[API Specification] +GET / Fetch the Web frontpage for uploading text/file [x] +GET /api Fetch API specification +GET / Fetch the paste by uuid [x] +GET // Fetch the paste (code) in rendered HTML with syntax highlighting [ ] +GET //settings Fetch the paste information [x] +GET //download Download the paste [x] +POST / Create new paste [x] # Only support multipart/form-data and raw data +DELETE / Delete paste by uuid [x] +POST //settings Update paste setting, i.e., passcode and valid time [ ] + +* uuid: [A-z0-9]{${UUID_LENGTH}} +* option: Render language + +Features +* Password protection [ ] +* Expiring paste [ ] [ ] indicated not implemented Limitation * Max. 10MB file size upload (Max. 100MB body size for free Cloudflare plan) -Last update on 30 May. +Last update on 2 June. `; const gen_id = customAlphabet( - "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 8); + "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", UUID_LENGTH); export default { async fetch( @@ -50,24 +77,60 @@ export default { secretAccessKey: env.AWS_SECRET_ACCESS_KEY }); - // if (hostname !== SERVICE_URL) { - // // Invalid case - // return new Response(null, { status: 403 }) - // } + // Special path + if (path === "/api" && method == "GET") { + return new Response(API_SPEC_TEXT); + } if (path === "/") { switch (method) { // Fetch the HTML for uploading text/file case "GET": - return new Response(API_DOCS); + return await fetch(PASTE_INDEX_HTML_URL); // Create new paste - case "PUT": + case "POST": let uuid = gen_id(); - let buffer = await request.arrayBuffer(); + let buffer: ArrayBuffer; + let title: string | undefined; + // Handle content-type + const content_type = headers.get("content-type") || ""; + // Content-Type: multipart/form-data + if (content_type.includes("form")) { + let formdata = await request.formData(); + let data = formdata.get("upload-content"); + if (data === null) { + return new Response("Invalid request.\n", { + status: 422 + }) + } + // File + if (data instanceof File) { + title = data.name ?? undefined; + buffer = await data.arrayBuffer(); + // Text + } else { + buffer = new TextEncoder().encode(data) + } + + // Raw body + } else { + title = headers.get("title") ?? undefined; + buffer = await request.arrayBuffer(); + } + // Check request.body size <= 10MB if (buffer.byteLength > 10485760) { - return new Response("File size must be under 10MB.\n"); + return new Response("Paste size must be under 10MB.\n", { + status: 422 + }); + } + + // Check request.body size not empty + if (buffer.byteLength == 0) { + return new Response("Paste cannot be empty.\n", { + status: 422 + }); } let res = await s3.fetch(`${env.ENDPOINT}/${uuid}`, { @@ -78,10 +141,8 @@ export default { if (res.ok) { // Upload success let descriptor: PasteIndexEntry = { - title: headers.get("title") || undefined, - last_modified: Date.now(), - password: undefined, - editable: undefined // Default: true + title: title ?? undefined, + last_modified: Date.now() }; let counter = await env.PASTE_INDEX.get("__count__") || "0"; @@ -96,7 +157,7 @@ export default { } - } else if (path.length >= 9) { + } else if (path.length >= UUID_LENGTH + 1) { // RegExpr to match //