@@ -53,6 +53,9 @@ async function handleRequest(request) {
5353 case '/upload/3001' :
5454 response = await handle3001Request ( request ) ;
5555 break ;
56+ case '/upload/s3ipfs' :
57+ response = await handles3filebaseRequest ( request ) ;
58+ break ;
5659 default :
5760 response = new Response ( 'Not Found' , { status : 404 } ) ;
5861 break ;
@@ -658,4 +661,137 @@ async function handleRequest(request) {
658661 } ,
659662 } )
660663 }
661-
664+
665+ async function handles3filebaseRequest ( request ) {
666+ if ( request . method !== 'POST' || ! request . headers . get ( 'Content-Type' ) . includes ( 'multipart/form-data' ) ) {
667+ return new Response ( 'Invalid request' , { status : 400 } ) ;
668+ }
669+
670+ try {
671+ // 从 KV 获取配置
672+ const config = await WORKER_IMGBED . get ( 's3filebase_config' , 'json' ) ;
673+ if ( ! config || ! config . accessKey || ! config . secretKey || ! config . bucket ) {
674+ throw new Error ( 'Invalid S3 configuration' ) ;
675+ }
676+
677+ const formData = await request . formData ( ) ;
678+ const file = formData . get ( 'image' ) ;
679+ if ( ! file ) {
680+ return new Response ( 'No file found' , { status : 400 } ) ;
681+ }
682+
683+ const now = new Date ( ) ;
684+ const timestamp = now . toISOString ( )
685+ . replace ( / [ - : ] / g, '' )
686+ . split ( '.' ) [ 0 ]
687+ . replace ( 'T' , '_' ) ;
688+
689+ const fileName = file . name . split ( '.' ) [ 0 ] ;
690+ const extension = file . name . split ( '.' ) . pop ( ) || '' ;
691+ const s3Key = `${ fileName } _${ timestamp } .${ extension } ` ;
692+ const content = await file . arrayBuffer ( ) ;
693+
694+ const amzdate = now . toISOString ( ) . replace ( / [: -] | \. \d { 3 } / g, '' ) ;
695+ const dateStamp = amzdate . slice ( 0 , 8 ) ;
696+ const contentHash = await crypto . subtle . digest ( 'SHA-256' , content )
697+ . then ( buf => Array . from ( new Uint8Array ( buf ) )
698+ . map ( b => b . toString ( 16 ) . padStart ( 2 , '0' ) )
699+ . join ( '' ) ) ;
700+
701+ const canonicalUri = `/${ config . bucket } /${ s3Key } ` ;
702+ const uploadHeaders = {
703+ 'Host' : 's3.filebase.com' ,
704+ 'Content-Type' : file . type || 'application/octet-stream' ,
705+ 'X-Amz-Content-SHA256' : contentHash ,
706+ 'X-Amz-Date' : amzdate
707+ } ;
708+
709+ const algorithm = 'AWS4-HMAC-SHA256' ;
710+ const region = 'us-east-1' ;
711+ const service = 's3' ;
712+ const scope = `${ dateStamp } /${ region } /${ service } /aws4_request` ;
713+
714+ const canonicalHeaders = Object . entries ( uploadHeaders )
715+ . map ( ( [ k , v ] ) => `${ k . toLowerCase ( ) } :${ v } \n` )
716+ . sort ( )
717+ . join ( '' ) ;
718+ const signedHeaders = Object . keys ( uploadHeaders )
719+ . map ( k => k . toLowerCase ( ) )
720+ . sort ( )
721+ . join ( ';' ) ;
722+
723+ const canonicalRequest = [
724+ 'PUT' ,
725+ canonicalUri ,
726+ '' ,
727+ canonicalHeaders ,
728+ signedHeaders ,
729+ contentHash
730+ ] . join ( '\n' ) ;
731+
732+ const stringToSign = [
733+ algorithm ,
734+ amzdate ,
735+ scope ,
736+ await crypto . subtle . digest ( 'SHA-256' , new TextEncoder ( ) . encode ( canonicalRequest ) )
737+ . then ( buf => Array . from ( new Uint8Array ( buf ) )
738+ . map ( b => b . toString ( 16 ) . padStart ( 2 , '0' ) )
739+ . join ( '' ) )
740+ ] . join ( '\n' ) ;
741+
742+ let key = await crypto . subtle . importKey (
743+ 'raw' ,
744+ new TextEncoder ( ) . encode ( `AWS4${ config . secretKey } ` ) ,
745+ { name : 'HMAC' , hash : 'SHA-256' } ,
746+ false ,
747+ [ 'sign' ]
748+ ) ;
749+
750+ for ( const msg of [ dateStamp , region , service , 'aws4_request' ] ) {
751+ key = await crypto . subtle . importKey (
752+ 'raw' ,
753+ await crypto . subtle . sign ( 'HMAC' , key , new TextEncoder ( ) . encode ( msg ) ) ,
754+ { name : 'HMAC' , hash : 'SHA-256' } ,
755+ false ,
756+ [ 'sign' ]
757+ ) ;
758+ }
759+
760+ const signature = await crypto . subtle . sign (
761+ 'HMAC' ,
762+ key ,
763+ new TextEncoder ( ) . encode ( stringToSign )
764+ ) ;
765+
766+ const authorization =
767+ `${ algorithm } ` +
768+ `Credential=${ config . accessKey } /${ scope } , ` +
769+ `SignedHeaders=${ signedHeaders } , ` +
770+ `Signature=${ Array . from ( new Uint8Array ( signature ) )
771+ . map ( b => b . toString ( 16 ) . padStart ( 2 , '0' ) )
772+ . join ( '' ) } `;
773+
774+ const uploadResponse = await fetch ( `https://s3.filebase.com${ canonicalUri } ` , {
775+ method : 'PUT' ,
776+ headers : {
777+ ...uploadHeaders ,
778+ 'Authorization' : authorization
779+ } ,
780+ body : content
781+ } ) ;
782+
783+ if ( ! uploadResponse . ok ) {
784+ throw new Error ( `Upload failed with status ${ uploadResponse . status } ` ) ;
785+ }
786+
787+ const cid = uploadResponse . headers . get ( 'x-amz-meta-cid' ) ;
788+ if ( ! cid ) {
789+ throw new Error ( 'CID not found in response' ) ;
790+ }
791+
792+ return new Response ( `https://i0.wp.com/i0.img2ipfs.com/ipfs/${ cid } ` ) ;
793+
794+ } catch ( error ) {
795+ return new Response ( `Upload failed: ${ error . message } ` , { status : 500 } ) ;
796+ }
797+ }
0 commit comments