mirror of
https://github.com/rikkaneko/paste.git
synced 2025-06-06 16:45:41 +00:00
Use GetObjectAttributes to get object size (large_paste)
Fix CORS handling Signed-off-by: Joe Ma <rikkaneko23@gmail.com>
This commit is contained in:
parent
6be1e97122
commit
2f67469ef5
7 changed files with 91 additions and 38 deletions
|
@ -13,12 +13,15 @@
|
|||
"dedent-js": "^1.0.1",
|
||||
"itty-router": "^4.0.23",
|
||||
"js-sha256": "^0.10.1",
|
||||
"nanoid": "^5.0.2"
|
||||
"nanoid": "^5.0.2",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20231025.0",
|
||||
"@types/bootstrap": "^5.2.8",
|
||||
"@types/jquery": "^3.5.25",
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"eslint": "^8.52.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-formatter-friendly": "^7.0.0",
|
||||
|
|
35
src/index.ts
35
src/index.ts
|
@ -27,9 +27,22 @@ import { get_presign_url, router as large_upload } from './v2/large_upload';
|
|||
|
||||
const router = Router<ERequest, [Env, ExecutionContext]>();
|
||||
|
||||
// Shared common properties to all route
|
||||
router.all('*', (request) => {
|
||||
const { headers } = request;
|
||||
// Detect if request from browsers
|
||||
const agent = headers.get('user-agent') ?? '';
|
||||
request.is_browser = ['Chrome', 'Mozilla', 'AppleWebKit', 'Safari', 'Gecko', 'Chromium'].some((v) =>
|
||||
agent.includes(v)
|
||||
);
|
||||
// Append the origin/referer
|
||||
request.origin = headers.get('origin') ?? undefined;
|
||||
});
|
||||
|
||||
// Handle preflighted CORS request
|
||||
router.options('*', (request) => {
|
||||
const url = new URL(request.url);
|
||||
if (!request.origin) return new Response(null);
|
||||
const url = new URL(request.origin);
|
||||
// Allow all subdomain of nekoid.cc
|
||||
if (url.hostname.endsWith('nekoid.cc')) {
|
||||
return new Response(null, {
|
||||
|
@ -37,19 +50,10 @@ router.options('*', (request) => {
|
|||
headers: {
|
||||
'Access-Control-Allow-Origin': url.origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
||||
'Vary': 'Origin',
|
||||
Vary: 'Origin',
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// Shared common properties to all route
|
||||
router.all('*', (request) => {
|
||||
// Detect if request from browsers
|
||||
const agent = request.headers.get('user-agent') ?? '';
|
||||
request.is_browser = ['Chrome', 'Mozilla', 'AppleWebKit', 'Safari', 'Gecko', 'Chromium'].some((v) =>
|
||||
agent.includes(v)
|
||||
);
|
||||
});
|
||||
|
||||
/* Static file path */
|
||||
|
@ -546,13 +550,14 @@ router.all('*', () => {
|
|||
});
|
||||
|
||||
export default {
|
||||
fetch: (req: Request, env: Env, ctx: ExecutionContext) =>
|
||||
fetch: (req: ERequest, env: Env, ctx: ExecutionContext) =>
|
||||
router
|
||||
.handle(req, env, ctx)
|
||||
.catch(error)
|
||||
// Apply CORS headers
|
||||
.then((res: Response) => {
|
||||
const url = new URL(req.url);
|
||||
if (!req.origin) return res;
|
||||
const url = new URL(req.origin);
|
||||
// Allow all subdomain of nekoid.cc
|
||||
if (url.hostname.endsWith('nekoid.cc')) {
|
||||
res.headers.set('Access-Control-Allow-Origin', url.origin);
|
||||
|
|
3
src/types.d.ts
vendored
3
src/types.d.ts
vendored
|
@ -2,6 +2,7 @@ import { IRequest } from 'itty-router';
|
|||
|
||||
export type ERequest = {
|
||||
is_browser: boolean;
|
||||
origin?: string;
|
||||
// match_etag?: string;
|
||||
} & IRequest;
|
||||
|
||||
|
@ -20,6 +21,8 @@ export interface PasteIndexEntry {
|
|||
// Only apply when large_paste
|
||||
upload_completed?: boolean;
|
||||
sha256_hash?: string;
|
||||
cached_presigned_url?: string;
|
||||
cached_presigned_url_expiration?: string;
|
||||
}
|
||||
|
||||
export interface Env {
|
||||
|
|
27
src/utils.ts
27
src/utils.ts
|
@ -23,14 +23,7 @@ import { PasteIndexEntry, Env } from './types';
|
|||
|
||||
export const gen_id = customAlphabet('1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', UUID_LENGTH);
|
||||
|
||||
export async function get_paste_info(
|
||||
uuid: string,
|
||||
descriptor: PasteIndexEntry,
|
||||
env: Env,
|
||||
use_html: boolean = true,
|
||||
need_qr: boolean = false,
|
||||
reply_json = false
|
||||
): Promise<Response> {
|
||||
export function get_paste_info_obj(uuid: string, descriptor: PasteIndexEntry, env: Env) {
|
||||
const created = new Date(descriptor.last_modified);
|
||||
const expired = new Date(descriptor.expiration ?? descriptor.last_modified + 2419200000);
|
||||
const link = `https://${SERVICE_URL}/${uuid}`;
|
||||
|
@ -49,6 +42,18 @@ export async function get_paste_info(
|
|||
expired: expired.toISOString(),
|
||||
update_completed: descriptor.upload_completed ?? undefined, // only for large_paste
|
||||
};
|
||||
return paste_info;
|
||||
}
|
||||
|
||||
export async function get_paste_info(
|
||||
uuid: string,
|
||||
descriptor: PasteIndexEntry,
|
||||
env: Env,
|
||||
use_html: boolean = true,
|
||||
need_qr: boolean = false,
|
||||
reply_json = false
|
||||
): Promise<Response> {
|
||||
const paste_info = get_paste_info_obj(uuid, descriptor, env);
|
||||
|
||||
// Reply with JSON
|
||||
if (reply_json) {
|
||||
|
@ -63,7 +68,7 @@ export async function get_paste_info(
|
|||
// Plain text reply
|
||||
let content = dedent`
|
||||
uuid: ${uuid}
|
||||
link: ${link}
|
||||
link: ${paste_info.link}
|
||||
type: ${paste_info.type ?? 'paste'}
|
||||
title: ${paste_info.title || '-'}
|
||||
mime-type: ${paste_info.mime_type ?? '-'}
|
||||
|
@ -95,7 +100,7 @@ export async function get_paste_info(
|
|||
${
|
||||
need_qr
|
||||
? `<img src="${paste_info.link_qr}"
|
||||
alt="${link}" style="max-width: 280px">`
|
||||
alt="${paste_info.link}" style="max-width: 280px">`
|
||||
: ''
|
||||
}
|
||||
</body>
|
||||
|
@ -116,7 +121,7 @@ export async function get_paste_info(
|
|||
const res = await env.QRCODE.fetch(
|
||||
'https://qrcode.nekoid.cc?' +
|
||||
new URLSearchParams({
|
||||
q: link,
|
||||
q: paste_info.link,
|
||||
type: 'utf8',
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Router } from 'itty-router';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import { AwsClient } from 'aws4fetch';
|
||||
import { parseStringPromise } from 'xml2js';
|
||||
import { ERequest, Env, PasteIndexEntry } from '../types';
|
||||
import { gen_id } from '../utils';
|
||||
import { gen_id, get_paste_info_obj } from '../utils';
|
||||
import { UUID_LENGTH } from '../constant';
|
||||
|
||||
export const router = Router<ERequest, [Env, ExecutionContext]>({ base: '/v2/large_upload' });
|
||||
|
@ -195,24 +196,31 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
|
|||
});
|
||||
|
||||
try {
|
||||
const objectmeta = await s3.fetch(`${env.LARGE_ENDPOINT}/${uuid}`, {
|
||||
method: 'HEAD',
|
||||
// Get object attributes
|
||||
const objectmeta = await s3.fetch(`${env.LARGE_DOWNLOAD_ENDPOINT}/${uuid}?attributes`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-AMZ-Object-Attributes': 'ObjectSize',
|
||||
},
|
||||
});
|
||||
if (objectmeta.ok) {
|
||||
const { headers } = objectmeta;
|
||||
const file_size = headers.get('Content-Length') || '0';
|
||||
const xml = await objectmeta.text();
|
||||
const parsed = await parseStringPromise(xml, {
|
||||
tagNameProcessors: [(name) => name.toLowerCase()],
|
||||
});
|
||||
const file_size = parsed.getobjectattributesresponse.objectsize[0];
|
||||
if (parseInt(file_size) !== descriptor.size) {
|
||||
return new Response('This paste is not finishing the upload.\n', {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return new Response('This paste is not finishing the upload.\n', {
|
||||
return new Response('This paste is not finishing upload.\n', {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
return new Response('Unable to connect to remote.\n', {
|
||||
return new Response('Internal server error.\n', {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
|
@ -225,14 +233,12 @@ router.post('/complete/:uuid', async (request, env, ctx) => {
|
|||
ctx.waitUntil(env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), { expirationTtl: 2419200 }));
|
||||
|
||||
const paste_info = {
|
||||
uuid,
|
||||
upload_completed: true,
|
||||
expired: new Date(expriation).toISOString(),
|
||||
paste_info: get_paste_info_obj(uuid, descriptor, env),
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(paste_info), {
|
||||
status: 400,
|
||||
});
|
||||
return new Response(JSON.stringify(paste_info));
|
||||
});
|
||||
|
||||
router.get('/:uuid', async (request, env, ctx) => {
|
||||
|
|
|
@ -8,6 +8,7 @@ kv_namespaces = [
|
|||
services = [
|
||||
{ binding = "QRCODE", service = "qrcode-gen", environment = "production" }
|
||||
]
|
||||
node_compat = true
|
||||
|
||||
# [secret]
|
||||
# AWS_ACCESS_KEY_ID
|
||||
|
|
30
yarn.lock
30
yarn.lock
|
@ -302,6 +302,11 @@
|
|||
dependencies:
|
||||
"@popperjs/core" "^2.9.2"
|
||||
|
||||
"@types/crypto-js@4.2.2":
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.2.2.tgz#771c4a768d94eb5922cc202a3009558204df0cea"
|
||||
integrity sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==
|
||||
|
||||
"@types/jquery@^3.5.25":
|
||||
version "3.5.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.29.tgz#3c06a1f519cd5fc3a7a108971436c00685b5dcea"
|
||||
|
@ -333,6 +338,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.8.tgz#518609aefb797da19bf222feb199e8f653ff7627"
|
||||
integrity sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==
|
||||
|
||||
"@types/xml2js@^0.4.14":
|
||||
version "0.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.14.tgz#5d462a2a7330345e2309c6b549a183a376de8f9a"
|
||||
integrity sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@ungap/structured-clone@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||
|
@ -1729,6 +1741,11 @@ safe-regex-test@^1.0.0:
|
|||
get-intrinsic "^1.2.2"
|
||||
is-regex "^1.1.4"
|
||||
|
||||
sax@>=0.6.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
|
||||
integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
|
||||
|
||||
selfsigned@^2.0.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0"
|
||||
|
@ -2060,6 +2077,19 @@ ws@^8.11.0:
|
|||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
|
||||
integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
|
||||
|
||||
xml2js@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499"
|
||||
integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~11.0.0"
|
||||
|
||||
xmlbuilder@~11.0.0:
|
||||
version "11.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
xxhash-wasm@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz#ecc0f813219b727af4d5f3958ca6becee2f2f1ff"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue