USING: destructors http http.server http.server.requests http.client http.client.private tools.test multiline fry io.streams.string io.crlf io.encodings.utf8 io.encodings.latin1 io.encodings.binary io.encodings.string io.encodings.ascii kernel arrays splitting sequences assocs io.sockets db db.sqlite make continuations urls hashtables accessors namespaces xml.data random combinators.short-circuit literals ; IN: http.tests { "text/plain" "UTF-8" } [ "text/plain" parse-content-type ] unit-test { "text/html" "ASCII" } [ "text/html; charset=ASCII" parse-content-type ] unit-test { "text/html" "utf-8" } [ "text/html; charset=\"utf-8\"" parse-content-type ] unit-test { "application/octet-stream" f } [ "application/octet-stream" parse-content-type ] unit-test { "localhost" f } [ "localhost" parse-host ] unit-test { "localhost" 8888 } [ "localhost:8888" parse-host ] unit-test { "::1" 8888 } [ "::1:8888" parse-host ] unit-test { "127.0.0.1" 8888 } [ "127.0.0.1:8888" parse-host ] unit-test { "localhost" } [ T{ url { protocol "http" } { host "localhost" } } unparse-host ] unit-test { "localhost" } [ T{ url { protocol "http" } { host "localhost" } { port 80 } } unparse-host ] unit-test { "localhost" } [ T{ url { protocol "https" } { host "localhost" } { port 443 } } unparse-host ] unit-test { "localhost:8080" } [ T{ url { protocol "http" } { host "localhost" } { port 8080 } } unparse-host ] unit-test { "localhost:8443" } [ T{ url { protocol "https" } { host "localhost" } { port 8443 } } unparse-host ] unit-test STRING: read-request-test-1 POST /bar HTTP/1.1 Some-Header: 1 Some-Header: 2 Content-Length: 4 Content-type: application/octet-stream blah ; { T{ request { url T{ url { path "/bar" } } } { proxy-url T{ url } } { method "POST" } { version "1.1" } { header H{ { "some-header" "1; 2" } { "content-length" "4" } { "content-type" "application/octet-stream" } } } { post-data T{ post-data { data "blah" } { content-type "application/octet-stream" } } } { cookies V{ } } { redirects 10 } } } [ read-request-test-1 lf>crlf [ read-request ] with-string-reader ] unit-test STRING: read-request-test-1' POST /bar HTTP/1.1 content-length: 4 content-type: application/octet-stream some-header: 1; 2 blah ; ${ read-request-test-1' } [ read-request-test-1 lf>crlf [ read-request ] with-string-reader [ write-request ] with-string-writer ! normalize crlf split-lines join-lines ] unit-test STRING: read-request-test-2 HEAD /bar HTTP/1.1 Host: www.sex.com ; { T{ request { url T{ url { host "www.sex.com" } { path "/bar" } } } { proxy-url T{ url } } { method "HEAD" } { version "1.1" } { header H{ { "host" "www.sex.com" } } } { cookies V{ } } { redirects 10 } } } [ read-request-test-2 lf>crlf [ read-request ] with-string-reader ] unit-test STRING: read-request-test-2' HEAD /bar HTTP/1.1 Host: www.sex.com:101 ; { T{ request { url T{ url { host "www.sex.com" } { port 101 } { path "/bar" } } } { proxy-url T{ url } } { method "HEAD" } { version "1.1" } { header H{ { "host" "www.sex.com:101" } } } { cookies V{ } } { redirects 10 } } } [ read-request-test-2' lf>crlf [ read-request ] with-string-reader ] unit-test STRING: read-request-test-3 GET nested HTTP/1.0 ; STRING: read-request-test-4 GET /blah HTTP/1.0 Host: "www.amazon.com" ; { "www.amazon.com" } [ read-request-test-4 lf>crlf [ read-request ] with-string-reader "host" header ] unit-test STRING: read-response-test-1 HTTP/1.1 404 not found Content-Type: text/html; charset=UTF-8 blah ; { T{ response { version "1.1" } { code 404 } { message "not found" } { header H{ { "content-type" "text/html; charset=UTF-8" } } } { cookies { } } { content-type "text/html" } { content-charset "UTF-8" } { content-encoding utf8 } } } [ read-response-test-1 lf>crlf [ read-response ] with-string-reader ] unit-test STRING: read-response-test-1' HTTP/1.1 404 not found content-type: text/html; charset=UTF-8 ; ${ read-response-test-1' } [ URL" http://localhost/" url set read-response-test-1 lf>crlf [ read-response ] with-string-reader [ write-response ] with-string-writer ! normalize crlf split-lines join-lines ] unit-test { t } [ "rmid=732423sdfs73242; path=/; domain=.example.net; expires=Fri, 31-Dec-2010 23:59:59 GMT" dup parse-set-cookie first unparse-set-cookie = ] unit-test { { T{ cookie { name "lang" } { value "en-US" } { path "/" } { domain "example.com" } } } } [ "lang=en-US; Path=/; Domain=example.com" parse-set-cookie ] unit-test { t } [ "a=" dup parse-set-cookie first unparse-set-cookie = ] unit-test STRING: read-response-test-2 HTTP/1.1 200 Content follows Set-Cookie: oo="bar; a=b"; httponly=yes; sid=123456 ; { 2 } [ read-response-test-2 lf>crlf [ read-response ] with-string-reader cookies>> length ] unit-test STRING: read-response-test-3 HTTP/1.1 200 Content follows Set-Cookie: oo="bar; a=b"; comment="your mom"; httponly=yes ; { 1 } [ read-response-test-3 lf>crlf [ read-response ] with-string-reader cookies>> length ] unit-test ! Live-fire exercise USING: http.server.static furnace.sessions furnace.alloy furnace.actions furnace.auth furnace.auth.login furnace.db io.servers io.files io.files.temp io.directories io threads http.server.responses http.server.redirection furnace.redirection http.server.dispatchers db.tuples ; : add-quit-action ( responder -- responder ) [ stop-this-server "Goodbye" "text/html" ] >>display "quit" add-responder ; : test-db-file ( -- path ) "test.db" temp-file ; : test-db ( -- db ) test-db-file ; : add-addr ( url -- url' ) >url clone "addr" get set-url-addr ; : stop-test-httpd ( -- ) "http://localhost/quit" add-addr http-get nip "Goodbye" assert= ; { } [ test-db-file ?delete-file test-db [ init-furnace-tables ] with-db ] unit-test : test-with-dispatcher ( dispatcher quot -- ) [ main-responder ] dip '[ 0 >>insecure f >>secure [ server-addrs random "addr" set @ ] with-threaded-server ] with-variable ; inline USING: locals ; :: test-with-db-persistence ( db-persistence quot -- ) db-persistence [ quot test-with-dispatcher ] with-disposal ; inline add-quit-action "vocab:http/test" >>default "nested" add-responder [ URL" redirect-loop" ] >>display "redirect-loop" add-responder [ [ t ] [ "vocab:http/test/foo.html" ascii file-contents "http://localhost/nested/foo.html" add-addr http-get nip = ] unit-test [ "http://localhost/redirect-loop" add-addr http-get nip ] [ too-many-redirects? ] must-fail-with [ "Goodbye" ] [ "http://localhost/quit" add-addr http-get nip ] unit-test ] test-with-dispatcher ! HTTP client redirect bug add-quit-action [ "quit" ] >>display "redirect" add-responder [ [ "Goodbye" ] [ "http://localhost/redirect" add-addr http-get nip ] unit-test [ ] [ [ stop-test-httpd ] ignore-errors ] unit-test ] test-with-dispatcher ! Dispatcher bugs : 404? ( response -- ? ) { [ download-failed? ] [ response>> response? ] [ response>> code>> 404 = ] } 1&& ; "Test" "" add-responder add-quit-action "" add-responder "d" add-responder test-db [ ! This should give a 404 not an infinite redirect loop [ "http://localhost/d/blah" add-addr http-get nip ] [ 404? ] must-fail-with ! This should give a 404 not an infinite redirect loop [ "http://localhost/blah/" add-addr http-get nip ] [ 404? ] must-fail-with [ "Goodbye" ] [ "http://localhost/quit" add-addr http-get nip ] unit-test ] test-with-db-persistence [ [ "Hi" write ] "text/plain" ] >>display "Test" "" add-responder add-quit-action test-db [ [ "Hi" ] [ "http://localhost/" add-addr http-get nip ] unit-test [ "Goodbye" ] [ "http://localhost/quit" add-addr http-get nip ] unit-test ] test-with-db-persistence USING: html.components html.forms xml xml.traversal validators furnace furnace.conversations ; SYMBOL: a : test-a ( xml -- value ) string>xml body>> "input" deep-tag-named "value" attr ; [ a get-global "a" set-value ] >>init [ [ "" write "a" render "" write ] "text/html" ] >>display [ { { "a" [ v-integer ] } } validate-params ] >>validate [ "a" value a set-global URL" " ] >>submit >>default add-quit-action test-db [ 3 a set-global [ "3" ] [ "http://localhost/" add-addr http-get swap dup cookies>> "cookies" set session-id-key get-cookie value>> "session-id" set test-a ] unit-test [ "4" ] [ [ "4" "a" ,, "http://localhost" add-addr "__u" ,, "session-id" get session-id-key ,, ] H{ } make "http://localhost/" add-addr "cookies" get >>cookies http-request nip test-a ] unit-test [ 4 ] [ a get-global ] unit-test ! Test flash scope [ "xyz" ] [ [ "xyz" "a" ,, "http://localhost" add-addr "__u" ,, "session-id" get session-id-key ,, ] H{ } make "http://localhost/" add-addr "cookies" get >>cookies http-request nip test-a ] unit-test [ 4 ] [ a get-global ] unit-test [ "Goodbye" ] [ "http://localhost/quit" add-addr http-get nip ] unit-test ] test-with-db-persistence ! Test cloning { f } [ <404> dup clone "b" "a" set-header drop "a" header ] unit-test { f } [ <404> dup clone "b" "a" put-cookie drop "a" get-cookie ] unit-test ! Test basic auth { "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" } [ "Aladdin" "open sesame" set-basic-auth "Authorization" header ] unit-test ! Test a corner case with static responder add-quit-action "vocab:http/test/foo.html" >>default [ [ t ] [ "http://localhost/" add-addr http-get nip "vocab:http/test/foo.html" ascii file-contents = ] unit-test [ ] [ stop-test-httpd ] unit-test ] test-with-dispatcher ! Check behavior of 307 redirect (reported by Chris Double) add-quit-action [ "b" ] >>submit "a" add-responder [ request get post-data>> data>> "data" = [ "OK" "text/plain" ] [ "OOPS" throw ] if ] >>submit "b" add-responder [ [ "OK" ] [ "data" "http://localhost/a" add-addr http-post nip ] unit-test ! Check that download throws errors (reported by Chris Double) [ [ "http://localhost/tweet_my_twat" add-addr download ] with-temp-directory ] must-fail [ ] [ stop-test-httpd ] unit-test ] test-with-dispatcher ! Check that index.fhtml works "resource:basis/http/test/" enable-fhtml >>default add-quit-action [ [ "OK\n" ] [ "http://localhost/" add-addr http-get nip ] unit-test [ ] [ stop-test-httpd ] unit-test ] test-with-dispatcher ! Check that just closing the socket without sending anything works add-quit-action [ [ ] [ "addr" get binary [ ] with-client ] unit-test [ ] [ stop-test-httpd ] unit-test ] test-with-dispatcher