; author: Thomas Hintz ; email: t@thintz.com ; license: bsd (module amazon-s3 (;; params access-key secret-key https *last-sig* ;; procs list-objects list-buckets bucket-exists? create-bucket! delete-bucket! get-object put-object! delete-object!) (import scheme chicken srfi-1 extras srfi-13 data-structures ports posix) (use base64 sha1 http-client uri-common intarweb srfi-19 hmac ssax sxpath) ; needed to make intarweb work with Amazon's screwy authorization header (define (aws-param-subunparser params) (sprintf "~A:~A" (alist-ref 'access-key params) (alist-ref 'signed-secret params))) (authorization-param-subunparsers `((aws . ,aws-param-subunparser) . ,(authorization-param-subunparsers))) ;;; params (define (intarweb-date date) (string->time (date->string date "~a ~b ~d ~T ~Y GMT"))) (define (sig-date date) (date->string date "~a, ~d ~b ~Y ~T GMT")) (define access-key (make-parameter "")) (define secret-key (make-parameter "")) (define https (make-parameter #f)) ;;; helper methods (define (assert-404 exn) (if (string=? ((condition-property-accessor 'exn 'message) exn) "Client error: 404 Not Found") #f (abort exn))) (define (make-aws-authorization verb resource #!key (date #f) (amz-headers '()) (content-md5 #f) (content-type #f)) (let* ((can-amz-headers (sort (map (lambda (header) `(,(string-downcase (car header)) . ,(cdr header))) amz-headers) (lambda (v1 v2) (stringsymbol content-type)) (content-length ,content-length))))) (define (aws-request bucket path verb #!key no-auth (content-type "") (content-length 0)) (make-request method: (string->symbol verb) uri: (uri-reference (string-append "http" (if (https) "s" "") "://" (if bucket (string-append bucket ".") "") "s3.amazonaws.com" (if path (string-append "/" path) ""))) headers: (if no-auth (headers '()) (aws-headers bucket path verb content-type content-length)))) (define (aws-xml-parser path ns) (lambda () ((sxpath path) (ssax:xml->sxml (current-input-port) ns)))) (define (perform-aws-request bucket path #!key (sxpath '()) (body '()) (verb "GET") (ns '((x . "http://s3.amazonaws.com/doc/2006-03-01/"))) (no-xml #f) (no-auth #f) (content-type "application/x-www-form-urlencoded") (content-length 0)) ;(handle-exceptions ; exn ; ((condition-property-accessor 'client-error 'body) exn) (with-input-from-request (aws-request bucket path verb no-auth: no-auth content-type: content-type content-length: content-length) body (if no-xml read-string (aws-xml-parser sxpath ns)))) ;;; api (define (list-objects bucket) (perform-aws-request bucket #f sxpath: '(x:ListBucketResult x:Contents x:Key *text*))) (define (list-buckets) (perform-aws-request #f #f sxpath: '(x:ListAllMyBucketsResult x:Buckets x:Bucket x:Name *text*))) (define (bucket-exists? bucket) (handle-exceptions exn (assert-404 exn) (perform-aws-request #f bucket verb: "HEAD" no-xml: #t) #t)) (define (create-bucket! bucket) (perform-aws-request bucket #f verb: "PUT" no-xml: #t)) (define (delete-bucket! bucket) (perform-aws-request bucket #f verb: "DELETE" no-xml: #t)) (define (put-object! bucket key object) (perform-aws-request bucket key verb: "PUT" content-type: "text/plain" body: (lambda () (print object)) content-length: (string-length object) no-xml: #t)) (define (get-object bucket key) (perform-aws-request bucket key no-xml: #t)) (define (delete-object! bucket key) (perform-aws-request bucket key no-xml: #t verb: "DELETE")) )