mirror of
https://github.com/rikkaneko/paste.git
synced 2025-08-05 13:40:09 +01:00
Update itty-router to 5.x
Fix minor bug Update package dependencies Signed-off-by: Joe Ma <rikkaneko23@gmail.com>
This commit is contained in:
parent
1fb3076115
commit
f90633bce3
8 changed files with 127 additions and 88 deletions
|
@ -381,7 +381,7 @@ $(function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`https://pb.nekoid.cc/${uuid}/settings?${new URLSearchParams({ json: '1' })}`);
|
const res = await fetch(`${ENDPOINT}/${uuid}/settings?${new URLSearchParams({ json: '1' })}`);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const paste_info = await res.json();
|
const paste_info = await res.json();
|
||||||
build_paste_modal(paste_info, show_qrcode, false, true);
|
build_paste_modal(paste_info, show_qrcode, false, true);
|
||||||
|
|
30
package.json
30
package.json
|
@ -9,27 +9,27 @@
|
||||||
"lint": "eslint . --color --cache -f friendly --max-warnings 10"
|
"lint": "eslint . --color --cache -f friendly --max-warnings 10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws4fetch": "^1.0.17",
|
"aws4fetch": "^1.0.20",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"dedent-js": "^1.0.1",
|
"dedent-js": "^1.0.1",
|
||||||
"itty-router": "^4.0.23",
|
"itty-router": "^5.0.18",
|
||||||
"js-sha256": "^0.10.1",
|
"js-sha256": "^0.11.1",
|
||||||
"nanoid": "^5.0.2",
|
"nanoid": "^5.1.5",
|
||||||
"xml-js": "^1.6.11",
|
|
||||||
"string_decoder": "^1.3.0",
|
"string_decoder": "^1.3.0",
|
||||||
"buffer": "^6.0.3"
|
"xml-js": "^1.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "^4.20231025.0",
|
"@cloudflare/workers-types": "^4.20250725.0",
|
||||||
"@types/bootstrap": "^5.2.8",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"@types/crypto-js": "4.2.2",
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/jquery": "^3.5.25",
|
"@types/jquery": "^3.5.32",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
"eslint": "^8.52.0",
|
"eslint": "^9.31.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-formatter-friendly": "^7.0.0",
|
"eslint-formatter-friendly": "^7.0.0",
|
||||||
"eslint-plugin-import": "^2.29.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.6.2",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.8.3",
|
||||||
"wrangler": "^3.15.0"
|
"wrangler": "^4.26.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,15 @@
|
||||||
export const SERVICE_URL = 'pb.nekoid.cc';
|
import { Config, Env } from './types';
|
||||||
export const PASTE_WEB_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/frontend';
|
|
||||||
export const UUID_LENGTH = 4;
|
// @ts-ignore
|
||||||
export const CORS_DOMAIN = 'nekoid.cc';
|
let CONSTANTS: Config = {
|
||||||
|
UUID_LENGTH: 4,
|
||||||
|
};
|
||||||
|
export default CONSTANTS;
|
||||||
|
|
||||||
|
// Fetch variable from Env
|
||||||
|
export const fetch_constant = (env: Env) => {
|
||||||
|
CONSTANTS = {
|
||||||
|
...env,
|
||||||
|
...CONSTANTS,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
92
src/index.ts
92
src/index.ts
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* This file is part of paste.
|
* This file is part of paste.
|
||||||
* Copyright (c) 2022-2024 Joe Ma <rikkaneko23@gmail.com>
|
* Copyright (c) 2022-2025 Joe Ma <rikkaneko23@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
@ -18,14 +18,34 @@
|
||||||
|
|
||||||
import { AwsClient } from 'aws4fetch';
|
import { AwsClient } from 'aws4fetch';
|
||||||
import { sha256 } from 'js-sha256';
|
import { sha256 } from 'js-sha256';
|
||||||
import { Router, error } from 'itty-router';
|
import { Router, error, cors } from 'itty-router';
|
||||||
import { ERequest, Env, PasteIndexEntry } from './types';
|
import { ERequest, Env, PasteIndexEntry } from './types';
|
||||||
import { serve_static } from './proxy';
|
import { serve_static } from './proxy';
|
||||||
import { check_password_rules, get_paste_info, get_basic_auth, gen_id } from './utils';
|
import { check_password_rules, get_paste_info, get_basic_auth, gen_id } from './utils';
|
||||||
import { UUID_LENGTH, PASTE_WEB_URL, SERVICE_URL, CORS_DOMAIN } from './constant';
|
import constants, { fetch_constant } from './constant';
|
||||||
import { get_presign_url, router as large_upload } from './v2/large_upload';
|
import { get_presign_url, router as large_upload } from './v2/large_upload';
|
||||||
|
|
||||||
const router = Router<ERequest, [Env, ExecutionContext]>();
|
// In favour of new cors() in itty-router v5
|
||||||
|
const { preflight, corsify } = cors({
|
||||||
|
origin: (o) => {
|
||||||
|
if (constants?.CORS_DOMAIN) {
|
||||||
|
return o?.endsWith(constants.CORS_DOMAIN) ? o : undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
credentials: true,
|
||||||
|
allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = Router<ERequest, [Env, ExecutionContext]>({
|
||||||
|
before: [
|
||||||
|
preflight,
|
||||||
|
(_, env) => {
|
||||||
|
fetch_constant(env);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
catch: error,
|
||||||
|
finally: [corsify],
|
||||||
|
});
|
||||||
|
|
||||||
// Shared common properties to all route
|
// Shared common properties to all route
|
||||||
router.all('*', (request) => {
|
router.all('*', (request) => {
|
||||||
|
@ -39,27 +59,10 @@ router.all('*', (request) => {
|
||||||
request.origin = headers.get('referer') ?? undefined;
|
request.origin = headers.get('referer') ?? undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle preflighted CORS request
|
|
||||||
router.options('*', (request) => {
|
|
||||||
if (!request.origin) return new Response(null);
|
|
||||||
const url = new URL(request.origin);
|
|
||||||
// Allow all subdomain of nekoid.cc
|
|
||||||
if (url.hostname.endsWith(CORS_DOMAIN)) {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 204,
|
|
||||||
headers: {
|
|
||||||
'Access-Control-Allow-Origin': url.origin,
|
|
||||||
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
|
||||||
Vary: 'Origin',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Static file path */
|
/* Static file path */
|
||||||
// Web homepage
|
// Web homepage
|
||||||
router.get('/', (request) => {
|
router.get('/', (request, env, ctx) => {
|
||||||
return serve_static(PASTE_WEB_URL + '/paste.html', request.headers);
|
return serve_static(env.PASTE_WEB_URL + '/paste.html', request.headers);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Favicon
|
// Favicon
|
||||||
|
@ -73,11 +76,11 @@ router.get('/favicon.ico', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Web script and style file
|
// Web script and style file
|
||||||
router.get('/static/*', (request) => {
|
router.get('/static/*', (request, env, ctx) => {
|
||||||
const { url } = request;
|
const { url } = request;
|
||||||
const { pathname } = new URL(url);
|
const { pathname } = new URL(url);
|
||||||
const path = pathname.replace(/\/+$/, '') || '/';
|
const path = pathname.replace(/\/+$/, '') || '/';
|
||||||
return serve_static(PASTE_WEB_URL + path, request.headers);
|
return serve_static(env.PASTE_WEB_URL + path, request.headers);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create new paste (10MB limit)
|
// Create new paste (10MB limit)
|
||||||
|
@ -95,7 +98,7 @@ router.post('/', async (request, env, ctx) => {
|
||||||
let need_qrcode: boolean = false;
|
let need_qrcode: boolean = false;
|
||||||
let paste_type: string | undefined;
|
let paste_type: string | undefined;
|
||||||
let reply_json: boolean = false;
|
let reply_json: boolean = false;
|
||||||
// Content-Type: multipart/form-data (deprecated)
|
// Content-Type: multipart/form-data
|
||||||
if (content_type.includes('multipart/form-data')) {
|
if (content_type.includes('multipart/form-data')) {
|
||||||
const formdata = await request.formData();
|
const formdata = await request.formData();
|
||||||
const data: File | string | any = formdata.get('u');
|
const data: File | string | any = formdata.get('u');
|
||||||
|
@ -114,6 +117,7 @@ router.post('/', async (request, env, ctx) => {
|
||||||
buffer = await data.arrayBuffer();
|
buffer = await data.arrayBuffer();
|
||||||
// Text
|
// Text
|
||||||
} else {
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
buffer = new TextEncoder().encode(data);
|
buffer = new TextEncoder().encode(data);
|
||||||
mime_type = 'text/plain; charset=UTF-8;';
|
mime_type = 'text/plain; charset=UTF-8;';
|
||||||
}
|
}
|
||||||
|
@ -158,6 +162,7 @@ router.post('/', async (request, env, ctx) => {
|
||||||
reply_json = true;
|
reply_json = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// HTTP API
|
||||||
title = headers.get('x-paste-title') || undefined;
|
title = headers.get('x-paste-title') || undefined;
|
||||||
mime_type = headers.get('x-paste-content-type') || undefined;
|
mime_type = headers.get('x-paste-content-type') || undefined;
|
||||||
password = headers.get('x-paste-pass') || undefined;
|
password = headers.get('x-paste-pass') || undefined;
|
||||||
|
@ -222,6 +227,9 @@ router.post('/', async (request, env, ctx) => {
|
||||||
body: buffer,
|
body: buffer,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Default paste type
|
||||||
|
paste_type = paste_type ? paste_type : 'paste';
|
||||||
|
|
||||||
if (paste_type === 'link') {
|
if (paste_type === 'link') {
|
||||||
mime_type = 'text/x-uri';
|
mime_type = 'text/x-uri';
|
||||||
}
|
}
|
||||||
|
@ -256,14 +264,14 @@ router.post('/', async (request, env, ctx) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle large upload (> 25MB)
|
// Handle large upload (> 25MB)
|
||||||
router.all('/v2/large_upload/*', large_upload.handle);
|
router.all('/v2/large_upload/*', large_upload.fetch);
|
||||||
|
|
||||||
// Fetch paste by uuid [4-digit UUID]
|
// Fetch paste by uuid [4-digit UUID]
|
||||||
router.get('/:uuid/:option?', async (request, env, ctx) => {
|
router.get('/:uuid/:option?', async (request, env, ctx) => {
|
||||||
const { headers } = request;
|
const { headers } = request;
|
||||||
const { uuid, option } = request.params;
|
const { uuid, option } = request.params;
|
||||||
// UUID format: [A-z0-9]{UUID_LENGTH}
|
// UUID format: [A-z0-9]{UUID_LENGTH}
|
||||||
if (uuid.length !== UUID_LENGTH) {
|
if (uuid.length !== constants.UUID_LENGTH) {
|
||||||
return new Response('Invalid UUID.\n', {
|
return new Response('Invalid UUID.\n', {
|
||||||
status: 442,
|
status: 442,
|
||||||
});
|
});
|
||||||
|
@ -343,7 +351,12 @@ router.get('/:uuid/:option?', async (request, env, ctx) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descriptor.size >= 209715200) {
|
if (descriptor.size >= 209715200) {
|
||||||
const signed_url = await get_presign_url(uuid, descriptor, env);
|
const signed_url = await get_presign_url(uuid, descriptor);
|
||||||
|
if (signed_url == null) {
|
||||||
|
return new Response('No available download endpoint.\n', {
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ctx.waitUntil(
|
ctx.waitUntil(
|
||||||
env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), {
|
env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), {
|
||||||
|
@ -366,7 +379,7 @@ router.get('/:uuid/:option?', async (request, env, ctx) => {
|
||||||
const cache = caches.default;
|
const cache = caches.default;
|
||||||
const match_etag = headers.get('If-None-Match') || undefined;
|
const match_etag = headers.get('If-None-Match') || undefined;
|
||||||
// Define the Request object as cache key
|
// Define the Request object as cache key
|
||||||
const req_key = new Request(`https://${SERVICE_URL}/${uuid}`, {
|
const req_key = new Request(`https://${env.SERVICE_URL}/${uuid}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: match_etag
|
headers: match_etag
|
||||||
? {
|
? {
|
||||||
|
@ -493,7 +506,7 @@ router.delete('/:uuid', async (request, env, ctx) => {
|
||||||
const { headers } = request;
|
const { headers } = request;
|
||||||
const { uuid } = request.params;
|
const { uuid } = request.params;
|
||||||
// UUID format: [A-z0-9]{UUID_LENGTH}
|
// UUID format: [A-z0-9]{UUID_LENGTH}
|
||||||
if (uuid.length !== UUID_LENGTH) {
|
if (uuid.length !== constants.UUID_LENGTH) {
|
||||||
return new Response('Invalid UUID.\n', {
|
return new Response('Invalid UUID.\n', {
|
||||||
status: 442,
|
status: 442,
|
||||||
});
|
});
|
||||||
|
@ -557,7 +570,7 @@ router.delete('/:uuid', async (request, env, ctx) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
ctx.waitUntil(env.PASTE_INDEX.delete(uuid));
|
ctx.waitUntil(env.PASTE_INDEX.delete(uuid));
|
||||||
// Invalidate CF cache
|
// Invalidate CF cache
|
||||||
ctx.waitUntil(cache.delete(new Request(`https://${SERVICE_URL}/${uuid}`)));
|
ctx.waitUntil(cache.delete(new Request(`https://${env.SERVICE_URL}/${uuid}`)));
|
||||||
return new Response('OK\n');
|
return new Response('OK\n');
|
||||||
} else {
|
} else {
|
||||||
return new Response('Unable to process such request.\n', {
|
return new Response('Unable to process such request.\n', {
|
||||||
|
@ -576,17 +589,6 @@ router.all('*', () => {
|
||||||
export default {
|
export default {
|
||||||
fetch: (req: ERequest, env: Env, ctx: ExecutionContext) =>
|
fetch: (req: ERequest, env: Env, ctx: ExecutionContext) =>
|
||||||
router
|
router
|
||||||
.handle(req, env, ctx)
|
// Update with itty-router 5.x
|
||||||
.catch(error)
|
.fetch(req, env, ctx),
|
||||||
// Apply CORS headers
|
|
||||||
.then((res: Response) => {
|
|
||||||
if (!req.origin) return res;
|
|
||||||
const url = new URL(req.origin);
|
|
||||||
// Allow all subdomain of nekoid.cc
|
|
||||||
if (url.hostname.endsWith(CORS_DOMAIN)) {
|
|
||||||
res.headers.set('Access-Control-Allow-Origin', url.origin);
|
|
||||||
res.headers.set('Vary', 'Origin');
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
10
src/types.d.ts
vendored
10
src/types.d.ts
vendored
|
@ -25,6 +25,12 @@ export interface PasteIndexEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Env {
|
export interface Env {
|
||||||
|
// Variable
|
||||||
|
SERVICE_URL: string;
|
||||||
|
PASTE_WEB_URL?: string;
|
||||||
|
UUID_LENGTH: string;
|
||||||
|
CORS_DOMAIN?: string;
|
||||||
|
// Secret
|
||||||
PASTE_INDEX: KVNamespace;
|
PASTE_INDEX: KVNamespace;
|
||||||
QRCODE: ServiceWorkerGlobalScope;
|
QRCODE: ServiceWorkerGlobalScope;
|
||||||
AWS_ACCESS_KEY_ID: string;
|
AWS_ACCESS_KEY_ID: string;
|
||||||
|
@ -35,3 +41,7 @@ export interface Env {
|
||||||
LARGE_ENDPOINT?: string;
|
LARGE_ENDPOINT?: string;
|
||||||
LARGE_DOWNLOAD_ENDPOINT?: string;
|
LARGE_DOWNLOAD_ENDPOINT?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Config extends Env {
|
||||||
|
UUID_LENGTH: number;
|
||||||
|
}
|
13
src/utils.ts
13
src/utils.ts
|
@ -18,15 +18,18 @@
|
||||||
|
|
||||||
import dedent from 'dedent-js';
|
import dedent from 'dedent-js';
|
||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
import { SERVICE_URL, UUID_LENGTH } from './constant';
|
import constants from './constant';
|
||||||
import { PasteIndexEntry, Env } from './types';
|
import { PasteIndexEntry, Env } from './types';
|
||||||
|
|
||||||
export const gen_id = customAlphabet('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', UUID_LENGTH);
|
export const gen_id = customAlphabet(
|
||||||
|
'1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
||||||
|
constants.UUID_LENGTH
|
||||||
|
);
|
||||||
|
|
||||||
export function get_paste_info_obj(uuid: string, descriptor: PasteIndexEntry) {
|
export function get_paste_info_obj(uuid: string, descriptor: PasteIndexEntry, env: Env) {
|
||||||
const created = new Date(descriptor.last_modified);
|
const created = new Date(descriptor.last_modified);
|
||||||
const expired = new Date(descriptor.expiration ?? descriptor.last_modified + 2419200000);
|
const expired = new Date(descriptor.expiration ?? descriptor.last_modified + 2419200000);
|
||||||
const link = `https://${SERVICE_URL}/${uuid}`;
|
const link = `https://${env.SERVICE_URL}/${uuid}`;
|
||||||
const paste_info = {
|
const paste_info = {
|
||||||
uuid,
|
uuid,
|
||||||
link,
|
link,
|
||||||
|
@ -53,7 +56,7 @@ export async function get_paste_info(
|
||||||
need_qr: boolean = false,
|
need_qr: boolean = false,
|
||||||
reply_json = false
|
reply_json = false
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const paste_info = get_paste_info_obj(uuid, descriptor);
|
const paste_info = get_paste_info_obj(uuid, descriptor, env);
|
||||||
|
|
||||||
// Reply with JSON
|
// Reply with JSON
|
||||||
if (reply_json) {
|
if (reply_json) {
|
||||||
|
|
|
@ -4,11 +4,11 @@ import { AwsClient } from 'aws4fetch';
|
||||||
import { xml2js } from 'xml-js';
|
import { xml2js } from 'xml-js';
|
||||||
import { ERequest, Env, PasteIndexEntry } from '../types';
|
import { ERequest, Env, PasteIndexEntry } from '../types';
|
||||||
import { gen_id, get_paste_info_obj } from '../utils';
|
import { gen_id, get_paste_info_obj } from '../utils';
|
||||||
import { UUID_LENGTH } from '../constant';
|
import constants from '../constant';
|
||||||
|
|
||||||
export const router = Router<ERequest, [Env, ExecutionContext]>({ base: '/v2/large_upload' });
|
export const router = Router<ERequest, [Env, ExecutionContext]>({ base: '/v2/large_upload' });
|
||||||
|
|
||||||
export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry, env: Env) {
|
export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry) {
|
||||||
// Use cached presigned url if expiration is more than 10 mins
|
// Use cached presigned url if expiration is more than 10 mins
|
||||||
if (descriptor.cached_presigned_url) {
|
if (descriptor.cached_presigned_url) {
|
||||||
const expiration = new Date(descriptor.cached_presigned_url_expiration ?? 0);
|
const expiration = new Date(descriptor.cached_presigned_url_expiration ?? 0);
|
||||||
|
@ -18,22 +18,28 @@ export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpoint_url = new URL(`${env.LARGE_DOWNLOAD_ENDPOINT}/${uuid}`);
|
const download_url = constants.LARGE_DOWNLOAD_ENDPOINT ?? constants.LARGE_ENDPOINT;
|
||||||
endpoint_url.searchParams.set('X-Amz-Expires', '14400'); // Valid for 4 hours
|
if (download_url == null) {
|
||||||
endpoint_url.searchParams.set(
|
// Not method to download
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const download_path = new URL(`${download_url}/${uuid}`);
|
||||||
|
download_path.searchParams.set('X-Amz-Expires', '14400'); // Valid for 4 hours
|
||||||
|
download_path.searchParams.set(
|
||||||
'response-content-disposition',
|
'response-content-disposition',
|
||||||
`inline; filename*=UTF-8''${encodeURIComponent(descriptor.title ?? uuid)}`
|
`inline; filename*=UTF-8''${encodeURIComponent(descriptor.title ?? uuid)}`
|
||||||
);
|
);
|
||||||
endpoint_url.searchParams.set('response-content-type', descriptor.mime_type ?? 'text/plain; charset=UTF-8;');
|
download_path.searchParams.set('response-content-type', descriptor.mime_type ?? 'text/plain; charset=UTF-8;');
|
||||||
|
|
||||||
// Generate Presigned Request
|
// Generate Presigned Request
|
||||||
const s3 = new AwsClient({
|
const s3 = new AwsClient({
|
||||||
accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!,
|
accessKeyId: constants.LARGE_AWS_ACCESS_KEY_ID!,
|
||||||
secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!,
|
secretAccessKey: constants.LARGE_AWS_SECRET_ACCESS_KEY!,
|
||||||
service: 's3', // required
|
service: 's3', // required
|
||||||
});
|
});
|
||||||
|
|
||||||
const signed = await s3.sign(endpoint_url, {
|
const signed = await s3.sign(download_path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {},
|
headers: {},
|
||||||
aws: {
|
aws: {
|
||||||
|
@ -65,6 +71,8 @@ router.post('/create', async (request, env, ctx) => {
|
||||||
let read_limit: number | undefined;
|
let read_limit: number | undefined;
|
||||||
let file_size: number | undefined;
|
let file_size: number | undefined;
|
||||||
let file_hash: string | undefined;
|
let file_hash: string | undefined;
|
||||||
|
|
||||||
|
// Content-Type: multipart/form-data
|
||||||
if (content_type?.includes('multipart/form-data')) {
|
if (content_type?.includes('multipart/form-data')) {
|
||||||
const formdata = await request.formData();
|
const formdata = await request.formData();
|
||||||
const title = formdata.get('title');
|
const title = formdata.get('title');
|
||||||
|
@ -128,15 +136,15 @@ router.post('/create', async (request, env, ctx) => {
|
||||||
|
|
||||||
const current = Date.now();
|
const current = Date.now();
|
||||||
const expiration = new Date(current + 14400 * 1000).getTime();
|
const expiration = new Date(current + 14400 * 1000).getTime();
|
||||||
const endpoint_url = new URL(`${env.LARGE_ENDPOINT}/${uuid}`);
|
const upload_path = new URL(`${env.LARGE_ENDPOINT}/${uuid}`);
|
||||||
endpoint_url.searchParams.set('X-Amz-Expires', '900'); // Valid for 15 mins
|
upload_path.searchParams.set('X-Amz-Expires', '900'); // Valid for 15 mins
|
||||||
const required_headers = {
|
const required_headers = {
|
||||||
'Content-Length': file_size.toString(),
|
'Content-Length': file_size.toString(),
|
||||||
'X-Amz-Content-Sha256': file_hash,
|
'X-Amz-Content-Sha256': file_hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate Presigned Request
|
// Generate Presigned Request
|
||||||
const signed = await s3.sign(endpoint_url, {
|
const signed = await s3.sign(upload_path, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: required_headers,
|
headers: required_headers,
|
||||||
aws: {
|
aws: {
|
||||||
|
@ -180,7 +188,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
|
||||||
const { headers } = request;
|
const { headers } = request;
|
||||||
const { uuid } = request.params;
|
const { uuid } = request.params;
|
||||||
// UUID format: [A-z0-9]{UUID_LENGTH}
|
// UUID format: [A-z0-9]{UUID_LENGTH}
|
||||||
if (uuid.length !== UUID_LENGTH) {
|
if (uuid.length !== constants.UUID_LENGTH) {
|
||||||
return new Response('Invalid UUID.\n', {
|
return new Response('Invalid UUID.\n', {
|
||||||
status: 442,
|
status: 442,
|
||||||
});
|
});
|
||||||
|
@ -208,7 +216,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get object attributes
|
// Get object attributes
|
||||||
const objectmeta = await s3.fetch(`${env.LARGE_DOWNLOAD_ENDPOINT}/${uuid}?attributes`, {
|
const objectmeta = await s3.fetch(`${env.LARGE_ENDPOINT}/${uuid}?attributes`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'X-AMZ-Object-Attributes': 'ObjectSize',
|
'X-AMZ-Object-Attributes': 'ObjectSize',
|
||||||
|
@ -224,7 +232,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
|
||||||
});
|
});
|
||||||
const file_size: number = parsed.getobjectattributesresponse.objectsize._text;
|
const file_size: number = parsed.getobjectattributesresponse.objectsize._text;
|
||||||
if (file_size !== descriptor.size) {
|
if (file_size !== descriptor.size) {
|
||||||
return new Response('This paste is not finishing the upload.\n', {
|
return new Response(`This paste is not finishing upload. (${file_size} != ${descriptor.size})\n`, {
|
||||||
status: 400,
|
status: 400,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -249,7 +257,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
|
||||||
const paste_info = {
|
const paste_info = {
|
||||||
upload_completed: true,
|
upload_completed: true,
|
||||||
expired: new Date(expriation).toISOString(),
|
expired: new Date(expriation).toISOString(),
|
||||||
paste_info: get_paste_info_obj(uuid, descriptor),
|
paste_info: get_paste_info_obj(uuid, descriptor, env),
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Response(JSON.stringify(paste_info));
|
return new Response(JSON.stringify(paste_info));
|
||||||
|
@ -258,7 +266,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
|
||||||
router.get('/:uuid', async (request, env, ctx) => {
|
router.get('/:uuid', async (request, env, ctx) => {
|
||||||
const { uuid } = request.params;
|
const { uuid } = request.params;
|
||||||
// UUID format: [A-z0-9]{UUID_LENGTH}
|
// UUID format: [A-z0-9]{UUID_LENGTH}
|
||||||
if (uuid.length !== UUID_LENGTH) {
|
if (uuid.length !== constants.UUID_LENGTH) {
|
||||||
return new Response('Invalid UUID.\n', {
|
return new Response('Invalid UUID.\n', {
|
||||||
status: 442,
|
status: 442,
|
||||||
});
|
});
|
||||||
|
@ -284,7 +292,7 @@ router.get('/:uuid', async (request, env, ctx) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const signed_url = await get_presign_url(uuid, descriptor, env);
|
const signed_url = await get_presign_url(uuid, descriptor);
|
||||||
const result = {
|
const result = {
|
||||||
uuid,
|
uuid,
|
||||||
expire: new Date(descriptor.expiration || 0).toISOString(),
|
expire: new Date(descriptor.expiration || 0).toISOString(),
|
||||||
|
|
|
@ -9,6 +9,11 @@ services = [
|
||||||
{ binding = "QRCODE", service = "qrcode-gen", environment = "production" }
|
{ binding = "QRCODE", service = "qrcode-gen", environment = "production" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[vars]
|
||||||
|
SERVICE_URL = "pb.nekoid.cc"
|
||||||
|
PASTE_WEB_URL = "https://raw.githubusercontent.com/rikkaneko/paste/main/frontend"
|
||||||
|
CORS_DOMAIN = "nekoid.cc"
|
||||||
|
|
||||||
# [secret]
|
# [secret]
|
||||||
# AWS_ACCESS_KEY_ID
|
# AWS_ACCESS_KEY_ID
|
||||||
# AWS_SECRET_ACCESS_KEY
|
# AWS_SECRET_ACCESS_KEY
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue