Skip to content

Commit

Permalink
0.13.2
Browse files Browse the repository at this point in the history
fix(s3-filebase): 修复S3认证失败和文件名编码问题
  • Loading branch information
BlueSkyXN committed Jan 24, 2025
1 parent 71e271f commit 862601d
Show file tree
Hide file tree
Showing 2 changed files with 380 additions and 216 deletions.
268 changes: 175 additions & 93 deletions cloudflare-worker-js-api/API_IMG_IPFS-S3filebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,135 +8,217 @@
*/

async function handles3filebaseRequest(request) {
if (request.method !== 'POST' || !request.headers.get('Content-Type').includes('multipart/form-data')) {
return new Response('Invalid request', { status: 400 });
}
console.log('[S3-Filebase] Starting request handling');

try {
// 从 KV 获取配置
if (request.method !== 'POST' || !request.headers.get('Content-Type').includes('multipart/form-data')) {
console.error('[S3-Filebase] Invalid request method or content type:', {
method: request.method,
contentType: request.headers.get('Content-Type')
});
return new Response('Invalid request', { status: 400 });
}

try {
console.log('[S3-Filebase] Fetching configuration from KV store');
const config = await WORKER_IMGBED.get('s3filebase_config', 'json');
if (!config || !config.accessKey || !config.secretKey || !config.bucket) {
throw new Error('Invalid S3 configuration');
console.error('[S3-Filebase] Invalid configuration:', {
hasConfig: !!config,
hasAccessKey: !!config?.accessKey,
hasSecretKey: !!config?.secretKey,
hasBucket: !!config?.bucket
});
throw new Error('Invalid S3 configuration');
}

console.log('[S3-Filebase] Configuration loaded successfully');

const formData = await request.formData();
const file = formData.get('image');
if (!file) {
return new Response('No file found', { status: 400 });
console.error('[S3-Filebase] No file found in form data');
return new Response('No file found', { status: 400 });
}

const now = new Date();
const timestamp = now.toISOString()
.replace(/[-:]/g, '')
.split('.')[0]
.replace('T', '_');

// 获取安全的文件名
const originalName = file.name;
const extension = originalName.split('.').pop() || '';
const timestamp = new Date().toISOString()
.replace(/[-:]/g, '')
.split('.')[0]
.replace('T', '_');

const fileName = file.name.split('.')[0];
const extension = file.name.split('.').pop() || '';
const s3Key = `${fileName}_${timestamp}.${extension}`;
// 生成安全的文件名:使用时间戳和随机字符串,避免中文和特殊字符
const safeFileName = `${timestamp}_${Math.random().toString(36).substring(2, 15)}.${extension}`;
console.log('[S3-Filebase] File details:', {
originalName,
safeFileName,
type: file.type,
size: file.size
});

const content = await file.arrayBuffer();

console.log('[S3-Filebase] File content loaded:', {
contentSize: content.byteLength
});

// AWS 签名所需的时间戳
const now = new Date();
const amzdate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
const dateStamp = amzdate.slice(0, 8);

// 计算内容哈希
const contentHash = await crypto.subtle.digest('SHA-256', content)
.then(buf => Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join(''));

const canonicalUri = `/${config.bucket}/${s3Key}`;
.then(buf => Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join(''));

// URI 编码的路径
const encodedKey = encodeURIComponent(safeFileName).replace(/%20/g, '+');
const canonicalUri = `/${config.bucket}/${encodedKey}`;

// 准备签名所需的头部
const uploadHeaders = {
'Host': 's3.filebase.com',
'Content-Type': file.type || 'application/octet-stream',
'X-Amz-Content-SHA256': contentHash,
'X-Amz-Date': amzdate
'Host': 's3.filebase.com',
'Content-Type': file.type || 'application/octet-stream',
'X-Amz-Content-SHA256': contentHash,
'X-Amz-Date': amzdate
};


console.log('[S3-Filebase] Request preparation:', {
canonicalUri,
amzdate,
contentHashPrefix: contentHash.substring(0, 16) + '...'
});

const algorithm = 'AWS4-HMAC-SHA256';
const region = 'us-east-1';
const service = 's3';
const scope = `${dateStamp}/${region}/${service}/aws4_request`;


// 准备规范请求
const canonicalHeaders = Object.entries(uploadHeaders)
.map(([k, v]) => `${k.toLowerCase()}:${v}\n`)
.sort()
.join('');
.map(([k, v]) => `${k.toLowerCase()}:${v}\n`)
.sort()
.join('');
const signedHeaders = Object.keys(uploadHeaders)
.map(k => k.toLowerCase())
.sort()
.join(';');
.map(k => k.toLowerCase())
.sort()
.join(';');

const canonicalRequest = [
'PUT',
canonicalUri,
'',
canonicalHeaders,
signedHeaders,
contentHash
'PUT',
canonicalUri,
'',
canonicalHeaders,
signedHeaders,
contentHash
].join('\n');


console.log('[S3-Filebase] Canonical request prepared:', {
method: 'PUT',
uri: canonicalUri,
signedHeaders
});

const stringToSign = [
algorithm,
amzdate,
scope,
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonicalRequest))
.then(buf => Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join(''))
algorithm,
amzdate,
scope,
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonicalRequest))
.then(buf => Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join(''))
].join('\n');


// 生成签名密钥
let key = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(`AWS4${config.secretKey}`),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);

for (const msg of [dateStamp, region, service, 'aws4_request']) {
key = await crypto.subtle.importKey(
'raw',
await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(msg)),
new TextEncoder().encode(`AWS4${config.secretKey}`),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
);

for (const msg of [dateStamp, region, service, 'aws4_request']) {
key = await crypto.subtle.importKey(
'raw',
await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(msg)),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
}


// 计算最终签名
const signature = await crypto.subtle.sign(
'HMAC',
key,
new TextEncoder().encode(stringToSign)
'HMAC',
key,
new TextEncoder().encode(stringToSign)
);

const authorization =
`${algorithm} ` +
`Credential=${config.accessKey}/${scope}, ` +
`SignedHeaders=${signedHeaders}, ` +
`Signature=${Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('')}`;

const uploadResponse = await fetch(`https://s3.filebase.com${canonicalUri}`, {
method: 'PUT',
headers: {
...uploadHeaders,
'Authorization': authorization
},
body: content

// 构建授权头
const credential = `${config.accessKey}/${dateStamp}/${region}/${service}/aws4_request`;
const authorization = [
`${algorithm} Credential=${credential}`,
`SignedHeaders=${signedHeaders}`,
`Signature=${Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('')}`
].join(', ');

console.log('[S3-Filebase] Authorization prepared:', {
credentialPrefix: credential.split('/')[0] + '/...',
signedHeadersCount: signedHeaders.split(';').length
});


// 发送上传请求
const uploadUrl = `https://s3.filebase.com${canonicalUri}`;
console.log('[S3-Filebase] Sending upload request to:', uploadUrl);

const uploadResponse = await fetch(uploadUrl, {
method: 'PUT',
headers: {
...uploadHeaders,
'Authorization': authorization
},
body: content
});

if (!uploadResponse.ok) {
throw new Error(`Upload failed with status ${uploadResponse.status}`);
const errorBody = await uploadResponse.text();
console.error('[S3-Filebase] Upload failed:', {
status: uploadResponse.status,
statusText: uploadResponse.statusText,
responseBody: errorBody,
headers: Object.fromEntries([...uploadResponse.headers])
});
throw new Error(`Upload failed with status ${uploadResponse.status}: ${errorBody}`);
}


console.log('[S3-Filebase] Upload successful');
const cid = uploadResponse.headers.get('x-amz-meta-cid');
if (!cid) {
throw new Error('CID not found in response');
console.error('[S3-Filebase] CID not found in response headers:',
Object.fromEntries([...uploadResponse.headers]));
throw new Error('CID not found in response');
}

return new Response(`https://i0.wp.com/i0.img2ipfs.com/ipfs/${cid}`);

} catch (error) {
return new Response(`Upload failed: ${error.message}`, { status: 500 });
}
}

const finalUrl = `https://i0.wp.com/i0.img2ipfs.com/ipfs/${cid}`;
console.log('[S3-Filebase] Generated final URL:', finalUrl);
return new Response(finalUrl);

} catch (error) {
console.error('[S3-Filebase] Error in request handling:', {
name: error.name,
message: error.message,
stack: error.stack
});
return new Response(`Upload failed: ${error.message}`, {
status: 500,
headers: {
'Content-Type': 'text/plain',
'X-Error-Details': error.message
}
});
}
}
Loading

0 comments on commit 862601d

Please sign in to comment.