~ chicken-core (chicken-5) 2c294f2e7c2efc4eec519e9720c83043e8a79ab1
commit 2c294f2e7c2efc4eec519e9720c83043e8a79ab1 Author: felix <felix@call-with-current-continuation.org> AuthorDate: Thu Apr 21 23:04:19 2016 +0200 Commit: felix <felix@call-with-current-continuation.org> CommitDate: Thu Apr 21 23:04:19 2016 +0200 moved http-download code into new file diff --git a/egg-download.scm b/egg-download.scm new file mode 100644 index 00000000..b61d20d9 --- /dev/null +++ b/egg-download.scm @@ -0,0 +1,218 @@ +;;;; egg download + + +(define +default-tcp-connect-timeout+ 30000) ; 30 seconds +(define +default-tcp-read/write-timeout+ 30000) ; 30 seconds +(define +url-regex+ "(http://)?([^/:]+)(:([^:/]+))?(/.*)?") + +(tcp-connect-timeout +default-tcp-connect-timeout+) +(tcp-read-timeout +default-tcp-read/write-timeout+) +(tcp-write-timeout +default-tcp-read/write-timeout+) + +(define user-agent (conc "chicken-install " (chicken-version))) +(define mode 'default) +(define quiet #f) + + +;; Simpler replacement for SRFI-13's string-suffix? +(define (string-suffix? suffix s) + (let ((len-s (string-length s)) + (len-suffix (string-length suffix))) + (and (not (< len-s len-suffix)) + (string=? suffix + (substring s (- len-s len-suffix)))))) + + +(define (d fstr . args) + (let ((port (if quiet (current-error-port) (current-output-port)))) + (apply fprintf port fstr args) + (flush-output port) ) ) + +(define (deconstruct-url url) + (let ((m (irregex-match +url-regex+ url))) + (values + (if m (irregex-match-substring m 2) url) + (if (and m (irregex-match-substring m 3)) + (let ((port (irregex-match-substring m 4))) + (or (string->number port) + (error "not a valid port" port))) + 80) + (or (and m (irregex-match-substring m 5)) + "/")))) + +(define (download-egg egg url #!key version destination tests + proxy-host proxy-port proxy-user-pass) + (receive (host port locn) (deconstruct-url url) + (let* ((locn (conc locn + "?name=" egg + "&release=" (##sys#fudge 41) + (if version (string-append "&version=" version) "") + "&mode=" mode + (if tests "&tests=yes" ""))) + (eggdir (make-pathname destination egg)) + (pre-existing-dir? (file-exists? eggdir)) ) + (unless pre-existing-dir? (create-directory eggdir)) + (handle-exceptions exn + (begin (unless pre-existing-dir? (remove-directory eggdir)) + (signal exn)) + (let ((fversion (http-fetch host port locn eggdir proxy-host + proxy-port proxy-user-pass))) + ;; If we get here then version of egg exists + (values eggdir (or fversion version "")) )) ) )) + +(define (http-fetch host port locn dest proxy-host proxy-port proxy-user-pass) + (let-values (((in out) + (http-connect host port locn proxy-host proxy-port + proxy-user-pass))) + (http-retrieve-files in out dest))) + +(define (http-connect host port locn proxy-host proxy-port proxy-user-pass) + (d "connecting to host ~s, port ~a ~a...~%" host port + (if proxy-host + (sprintf "(via ~a:~a) " proxy-host proxy-port) + "")) + (let-values (((in out) + (tcp-connect (or proxy-host host) (or proxy-port port)))) + (d "requesting ~s ...~%" locn) + (let ((req (make-HTTP-GET/1.1 locn user-agent host + port: port accept: "*/*" + proxy-host: proxy-host proxy-port: proxy-port))) + (display req out) + (flush-output out) + (d "reading response ...~%") + (let* ((chunked #f) + (h1 (read-line in)) + (response-match (match-http-response h1))) + (d "~a~%" h1) + ;;XXX handle redirects here + (if (response-match-code? response-match 407) + (let-values (((inpx outpx) (tcp-connect proxy-host proxy-port))) + (set! in inpx) (set! out outpx) + (display + (make-HTTP-GET/1.1 + locn *chicken-install-user-agent* host port: port + accept: "*/*" + proxy-host: proxy-host proxy-port: proxy-port + proxy-user-pass: proxy-user-pass) + out) + (unless (response-match-code? response-match 200) + (network-failure "invalid response from server" h1))) + (let loop () + (let ((ln (read-line in))) + (unless (equal? ln "") + (when (match-chunked-transfer-encoding ln) (set! chunked #t)) + (d "~a~%" ln) + (loop) ) ) ) ) + (when chunked + (d "reading chunks ") + (let ((data (read-chunks in))) + (close-input-port in) + (set! in (open-input-string data))) ) ) + (values in out)))) + +(define (http-retrieve-files in out dest) + (d "reading files ...~%") + (let ((version #f)) + (define (skip) + (let ((ln (read-line in))) + (cond ((or (eof-object? ln) + (irregex-match " *#!eof *" ln)) + (open-input-string "")) + ((irregex-match " *#\\|[- ]*([^- ]*) *\\|#.*" ln) => + (lambda (m) + (let ((v (irregex-match-substring m 1))) + (cond ((or (string=? "" v) (string=? "#f" v))) + ((and version (not (string=? v version))) + (warning "files versions are not identical" + ln version) + (set! version #f)) + (else + (set! version v))) + (open-input-string ln)))) + ((irregex-match "^[ ]*\\(error .*\\)[ ]*$" ln) + (open-input-string ln)) ; get-files deals with errors + ((irregex-match '(* ("\x09\x0a\x0b\x0c\x0d\x20\xa0")) ln) + (skip)) ; Blank line. + (else + (error "unrecognized file-information - possibly corrupt transmission" + ln))))) + (let get-files ((files '())) + (let* ((ins (skip)) + (name (read ins))) + (cond ((and (pair? name) (eq? 'error (car name))) + (throw-server-error (cadr name) (cddr name))) + ((or (eof-object? name) (not name)) + (close-input-port in) + (close-output-port out) + version) + ((not (string? name)) + (error "invalid file name - possibly corrupt transmission" + name) ) + ((string-suffix? "/" name) + (d " ~a~%" name) + (create-directory (make-pathname dest name)) + (get-files files) ) + (else + (d " ~a~%" name) + (let* ((size (read ins)) + (data (read-string size in)) ) + (with-output-to-file (make-pathname dest name) + (cut display data) #:binary ) ) + (get-files (cons name files)) ) ) ) ) )) + +(define (throw-server-error msg args) + (abort + (make-composite-condition + (make-property-condition + 'exn + 'message (string-append "[Server] " msg) + 'arguments args) + (make-property-condition 'setup-download-error)))) + +(define (read-chunks in) + (let get-chunks ((data '())) + (let ((size (string->number (read-line in) 16))) + (cond ((not size) + (error "invalid response from server - please try again")) + ((zero? size) + (d "~%") + (string-intersperse (reverse data) "")) + (else + (let ((chunk (read-string size in))) + (d ".") + (read-line in) + (get-chunks (cons chunk data)) ) ) ) ) )) + +(define (match-http-response rsp) + (and (string? rsp) + (irregex-match "HTTP/[0-9.]+\\s+([0-9]+)\\s+.*" rsp)) ) + +(define (response-match-code? mrsp code) + (and mrsp (string=? (number->string code) + (irregex-match-substring mrsp 1))) ) + +(define (match-chunked-transfer-encoding ln) + (irregex-match "[Tt]ransfer-[Ee]ncoding:\\s*chunked.*" ln) ) + +(define (make-HTTP-GET/1.1 location user-agent host + #!key + (port 80) + (connection "close") + (accept "*") + (content-length 0) + proxy-host proxy-port proxy-user-pass) + (conc + "GET " + (if proxy-host + (string-append "http://" host location) + location) + " HTTP/1.1" "\r\n" + "Connection: " connection "\r\n" + "User-Agent: " user-agent "\r\n" + "Accept: " accept "\r\n" + "Host: " host #\: port "\r\n" + (if proxy-user-pass + (string-append "Proxy-Authorization: Basic " proxy-user-pass "\r\n") + "") + "Content-length: " content-length "\r\n" + "\r\n") )Trap