! Copyright (C) 2005, 2010 Slava Pestov. ! See http://factorcode.org/license.txt for BSD license. USING: accessors ascii assocs calendar combinators.short-circuit destructors fry hashtables http http.client.post-data http.parsers io io.crlf io.encodings io.encodings.ascii io.encodings.binary io.encodings.iana io.encodings.string io.files io.pathnames io.sockets io.sockets.secure io.timeouts kernel locals math math.order math.parser mime.types namespaces present sequences splitting urls vocabs.loader combinators environment ; IN: http.client ERROR: too-many-redirects ; ERROR: invalid-proxy proxy ; : success? ( code -- ? ) 200 299 between? ; ERROR: download-failed response ; : check-response ( response -- response ) dup code>> success? [ download-failed ] unless ; > ] [ port>> number>string ] bi ":" glue ; : absolute-uri ( url -- str ) clone f >>username f >>password f >>anchor present ; : abs-path-uri ( url -- str ) relative-url f >>anchor present ; : request-uri ( request -- str ) { { [ dup proxy-url>> ] [ url>> absolute-uri ] } { [ dup method>> "CONNECT" = ] [ url>> authority-uri ] } [ url>> abs-path-uri ] } cond ; : write-request-line ( request -- request ) dup [ method>> write bl ] [ request-uri write bl ] [ "HTTP/" write version>> write crlf ] tri ; : default-port? ( url -- ? ) { [ port>> not ] [ [ port>> ] [ protocol>> protocol-port ] bi = ] } 1|| ; : unparse-host ( url -- string ) dup default-port? [ host>> ] [ [ host>> ] [ port>> number>string ] bi ":" glue ] if ; : set-host-header ( request header -- request header ) over url>> unparse-host "host" pick set-at ; : set-cookie-header ( header cookies -- header ) unparse-cookie "cookie" pick set-at ; : ?set-basic-auth ( header url name -- header ) swap [ [ username>> ] [ password>> ] bi 2dup and [ basic-auth swap pick set-at ] [ 3drop ] if ] [ drop ] if* ; : write-request-header ( request -- request ) dup header>> >hashtable over url>> host>> [ set-host-header ] when over url>> "Authorization" ?set-basic-auth over proxy-url>> "Proxy-Authorization" ?set-basic-auth over post-data>> [ set-post-data-headers ] when* over cookies>> [ set-cookie-header ] unless-empty write-header ; : write-request ( request -- ) unparse-post-data write-request-line write-request-header binary encode-output write-post-data flush drop ; : read-response-line ( response -- response ) read-?crlf parse-response-line first3 [ >>version ] [ >>code ] [ >>message ] tri* ; : detect-encoding ( response -- encoding ) [ content-charset>> name>encoding ] [ content-type>> mime-type-encoding ] bi or ; : read-response-header ( response -- response ) read-header >>header dup "set-cookie" header parse-set-cookie >>cookies dup "content-type" header [ parse-content-type [ >>content-type ] [ >>content-charset ] bi* dup detect-encoding >>content-encoding ] when* ; : read-response ( -- response ) read-response-line read-response-header ; DEFER: (with-http-request) SYMBOL: redirects : redirect-url ( request url -- request ) '[ _ >url derive-url ensure-port ] change-url ; : redirect? ( response -- ? ) code>> 300 399 between? ; :: do-redirect ( quot: ( chunk -- ) response -- response ) redirects inc redirects get request get redirects>> < [ request get clone response "location" header redirect-url response code>> 307 = [ "GET" >>method f >>post-data ] unless quot (with-http-request) ] [ too-many-redirects ] if ; inline recursive : read-chunk-size ( -- n ) read-crlf ";" split1 drop [ blank? ] trim-tail hex> [ "Bad chunk size" throw ] unless* ; : read-chunked ( quot: ( chunk -- ) -- ) read-chunk-size dup zero? [ 2drop ] [ read [ swap call ] [ drop ] 2bi read-crlf B{ } assert= read-chunked ] if ; inline recursive : read-response-body ( quot: ( chunk -- ) response -- ) binary decode-input "transfer-encoding" header "chunked" = [ read-chunked ] [ each-block ] if ; inline : request-socket-endpoints ( request -- physical logical ) [ proxy-url>> ] [ url>> ] bi [ or ] keep ; : ( -- stream ) request get request-socket-endpoints [ url-addr ] bi@ remote-address set ascii local-address set 1 minutes over set-timeout ; : https-tunnel? ( request -- ? ) [ proxy-url>> ] [ url>> protocol>> "https" = ] bi and ; : ?copy-proxy-basic-auth ( dst-request src-request -- dst-request ) proxy-url>> [ username>> ] [ password>> ] bi 2dup and [ set-proxy-basic-auth ] [ 2drop ] if ; : ?https-tunnel ( -- ) request get dup https-tunnel? [ swap [ url>> >>url ] [ ?copy-proxy-basic-auth ] bi f >>proxy-url "CONNECT" >>method write-request read-response check-response drop send-secure-handshake ] [ drop ] if ; ! Note: ipv4 addresses are interpreted as subdomains but "work" : no-proxy-match? ( host-path no-proxy-path -- ? ) dup first empty? [ [ rest ] bi@ ] when [ drop f ] [ tail? ] if-empty ; : get-no-proxy-list ( -- list ) "no_proxy" get [ "no_proxy" os-env ] unless* [ "NO_PROXY" os-env ] unless* ; : no-proxy? ( request -- ? ) get-no-proxy-list [ [ url>> host>> "." split ] dip "," split [ "." split no-proxy-match? ] with any? ] [ drop f ] if* ; : (check-proxy) ( proxy -- ? ) { { [ dup URL" " = ] [ drop f ] } { [ dup host>> ] [ drop t ] } [ invalid-proxy ] } cond ; : check-proxy ( request proxy -- request' ) dup [ (check-proxy) ] [ f ] if* [ drop f ] unless [ clone ] dip >>proxy-url ; : get-default-proxy ( request -- default-proxy ) url>> protocol>> "https" = [ "https.proxy" get [ "https_proxy" os-env ] unless* [ "HTTPS_PROXY" os-env ] unless* ] [ "http.proxy" get [ "http_proxy" os-env ] unless* [ "HTTP_PROXY" os-env ] unless* ] if ; : misparsed-url? ( url -- url' ) [ protocol>> not ] [ host>> not ] [ path>> ] tri and and ; : request-url ( url -- url' ) dup >url dup misparsed-url? [ drop dup url? [ present ] when "http://" prepend >url ] [ nip ] if ensure-port ; : ?default-proxy ( request -- request' ) dup get-default-proxy over proxy-url>> dup [ request-url ] when 2dup and [ pick no-proxy? [ nip ] [ [ request-url ] dip derive-url ] if ] [ nip ] if check-proxy ; : (with-http-request) ( request quot: ( chunk -- ) -- response ) swap ?default-proxy request [ [ [ [ in>> ] [ out>> ] bi [ ?https-tunnel ] with-streams* ] [ out>> [ request get write-request ] with-output-stream* ] [ in>> [ read-response dup redirect? request get redirects>> 0 > and [ t ] [ [ nip response set ] [ read-response-body ] [ ] 2tri f ] if ] with-input-stream* ] tri ] with-disposal [ do-redirect ] [ nip ] if ] with-variable ; inline recursive : ( url method -- request ) swap >>method swap request-url >>url ; inline PRIVATE> : with-http-request* ( request quot: ( chunk -- ) -- response ) [ (with-http-request) ] with-destructors ; inline : with-http-request ( request quot: ( chunk -- ) -- response ) with-http-request* check-response ; inline : http-request* ( request -- response data ) BV{ } clone [ '[ _ push-all ] with-http-request* ] keep B{ } like over content-encoding>> decode [ >>body ] keep ; : http-request ( request -- response data ) http-request* [ check-response ] dip ; : ( url -- request ) "GET" ; : http-get ( url -- response data ) http-request ; : http-get* ( url -- response data ) http-request* ; : download-name ( url -- name ) present file-name "?" split1 drop "/" ?tail drop ; : download-to ( url file -- ) binary [ [ write ] with-http-request drop ] with-file-writer ; : ?download-to ( url file -- ) dup exists? [ 2drop ] [ download-to ] if ; : download ( url -- ) dup download-name download-to ; : ( post-data url -- request ) "POST" swap >>post-data ; : http-post ( post-data url -- response data ) http-request ; : http-post* ( post-data url -- response data ) http-request* ; : ( post-data url -- request ) "PUT" swap >>post-data ; : http-put ( post-data url -- response data ) http-request ; : http-put* ( post-data url -- response data ) http-request* ; : ( url -- request ) "DELETE" ; : http-delete ( url -- response data ) http-request ; : http-delete* ( url -- response data ) http-request* ; : ( url -- request ) "HEAD" ; : http-head ( url -- response data ) http-request ; : http-head* ( url -- response data ) http-request* ; : ( url -- request ) "OPTIONS" ; : http-options ( url -- response data ) http-request ; : http-options* ( url -- response data ) http-request* ; : ( url -- request ) "TRACE" ; : http-trace ( url -- response data ) http-request ; : http-trace* ( url -- response data ) http-request* ; { "http.client" "debugger" } "http.client.debugger" require-when