From 2c5afc220de770c5897c9e40ed713ddfdfb4d567 Mon Sep 17 00:00:00 2001 From: edideaur Date: Fri, 6 Mar 2026 22:59:01 +0000 Subject: [PATCH] C --- functions/upload.js | 94 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/functions/upload.js b/functions/upload.js index 2f5c549..42fde96 100644 --- a/functions/upload.js +++ b/functions/upload.js @@ -1,6 +1,58 @@ const API_URL = 'https://catbox.moe/user/api.php'; const R2_PUBLIC_URL = 'https://cucks.qzz.io'; +const R2_ENDPOINT = 'https://faae2f5c0a232c7f3733ef597c55bd69.r2.cloudflarestorage.com'; +const R2_BUCKET = 'monochrome-image-uploads'; + +async function hmac(key, data) { + const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']); + return new Uint8Array(await crypto.subtle.sign('HMAC', cryptoKey, data)); +} + +async function sha256(data) { + return new Uint8Array(await crypto.subtle.digest('SHA-256', data)); +} + +function buf2hex(buffer) { + return Array.from(buffer) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); +} + +async function signature(key, date, region, service, stringToSign) { + const kDate = await hmac(new TextEncoder().encode('AWS4' + key), new TextEncoder().encode(date)); + const kRegion = await hmac(kDate, new TextEncoder().encode(region)); + const kService = await hmac(kRegion, new TextEncoder().encode(service)); + const kSigning = await hmac(kService, new TextEncoder().encode('aws4_request')); + const sig = await hmac(kSigning, new TextEncoder().encode(stringToSign)); + return buf2hex(sig); +} + +async function createSignature(method, path, headers, payloadHash) { + const now = new Date(); + const amzDate = now + .toISOString() + .replace(/[:-]|\.\d{3}/g, '') + .slice(0, 8); + const date = amzDate; + const region = 'auto'; + const service = 's3'; + + const signedHeaders = Object.keys(headers).sort().join(';'); + const canonicalHeaders = + Object.entries(headers) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([k, v]) => `${k.toLowerCase()}:${v}`) + .join('\n') + '\n'; + + const canonicalRequest = `${method}\n${path}\n\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`; + const credentialScope = `${date}/${region}/${service}/aws4_request`; + const stringToSign = `AWS4-HMAC-SHA256\n${amzDate}\n${credentialScope}\n${buf2hex(await sha256(new TextEncoder().encode(canonicalRequest)))}`; + + const sig = await signature(env.R2_SECRET_ACCESS_KEY, date, region, service, stringToSign); + return `AWS4-HMAC-SHA256 Credential=${env.R2_ACCESS_KEY_ID}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${sig}`; +} + export async function onRequest(context) { const { request, env } = context; @@ -12,7 +64,7 @@ export async function onRequest(context) { return jsonError('Method not allowed', 405); } - const useR2 = env.R2_BUCKET; + const useR2 = env.R2_ENABLED === 'true'; try { const contentType = request.headers.get('content-type') || ''; @@ -47,9 +99,43 @@ export async function onRequest(context) { let url; if (useR2) { - const key = `${Date.now()}-${fileName}`; - await env.R2_BUCKET.put(key, file, { httpMetadata: { contentType: fileType } }); - url = `${R2_PUBLIC_URL}/${key}`; + try { + const key = `${Date.now()}-${fileName}`; + const now = new Date(); + const amzDate = now + .toISOString() + .replace(/[:-]|\.\d{3}/g, '') + .slice(0, 8); + const payloadHash = buf2hex(await sha256(file)); + + const host = new URL(R2_ENDPOINT).host; + const headers = { + 'Content-Type': fileType, + 'Content-Length': file.byteLength, + 'x-amz-date': amzDate, + 'x-amz-content-sha256': payloadHash, + Host: host, + }; + + const auth = await createSignature('PUT', `/${R2_BUCKET}/${key}`, headers, payloadHash); + headers['Authorization'] = auth; + + const res = await fetch(`${R2_ENDPOINT}/${R2_BUCKET}/${key}`, { + method: 'PUT', + headers, + body: new Uint8Array(file), + }); + + if (!res.ok) { + const err = await res.text(); + throw new Error(`R2 error: ${res.status} - ${err}`); + } + + url = `${R2_PUBLIC_URL}/${key}`; + } catch (r2Err) { + console.error('R2 upload error:', r2Err); + return jsonError(`R2 upload failed: ${r2Err.message}`, 500); + } } else { const formData = new FormData(); formData.append('reqtype', 'fileupload');