mirror of
https://github.com/rikkaneko/paste.git
synced 2025-08-09 23:45:35 +01: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
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,21 +50,12 @@ 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 */
|
||||
// Web homepage
|
||||
router.get('/', (request) => {
|
||||
|
@ -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) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue