http.server.requests: if the content-length header is missing or invalid, a (controlled) error is thrown
							parent
							
								
									d30beb13ed
								
							
						
					
					
						commit
						eac41a588a
					
				| 
						 | 
					@ -1,73 +1,37 @@
 | 
				
			||||||
USING: accessors assocs continuations http http.server http.server.requests
 | 
					USING: accessors assocs continuations http http.client http.client.private
 | 
				
			||||||
io.streams.limited io.streams.string kernel multiline namespaces peg sequences
 | 
					http.server http.server.requests io.streams.limited io.streams.string kernel
 | 
				
			||||||
splitting tools.test urls ;
 | 
					multiline namespaces peg sequences splitting tools.test urls ;
 | 
				
			||||||
IN: http.server.requests.tests
 | 
					IN: http.server.requests.tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
: normalize-nl ( str -- str' )
 | 
					: normalize-nl ( str -- str' )
 | 
				
			||||||
    "\n" "\r\n" replace ;
 | 
					    "\n" "\r\n" replace ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					: request>string ( request -- string )
 | 
				
			||||||
 | 
					    [ write-request ] with-string-writer ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
: string>request ( str -- request )
 | 
					: string>request ( str -- request )
 | 
				
			||||||
    normalize-nl
 | 
					 | 
				
			||||||
    [ request-limit get limited-input read-request ] with-string-reader ;
 | 
					    [ request-limit get limited-input read-request ] with-string-reader ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
! POST requests
 | 
					! POST requests
 | 
				
			||||||
STRING: test-post-no-content-type
 | 
					 | 
				
			||||||
POST / HTTP/1.1
 | 
					 | 
				
			||||||
connection: close
 | 
					 | 
				
			||||||
host: 127.0.0.1:55532
 | 
					 | 
				
			||||||
user-agent: Factor http.client
 | 
					 | 
				
			||||||
content-length: 7
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
foo=bar
 | 
					 | 
				
			||||||
;
 | 
					 | 
				
			||||||
{ "foo=bar" "7" } [
 | 
					{ "foo=bar" "7" } [
 | 
				
			||||||
    test-post-no-content-type string>request
 | 
					    "foo=bar" "localhost" <post-request> request>string string>request
 | 
				
			||||||
    [ post-data>> data>> ] [ header>> "content-length" of ] bi
 | 
					    [ post-data>> data>> ] [ header>> "content-length" of ] bi
 | 
				
			||||||
] unit-test
 | 
					] unit-test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
STRING: test-post-0-content-length
 | 
					 | 
				
			||||||
POST / HTTP/1.1
 | 
					 | 
				
			||||||
connection: close
 | 
					 | 
				
			||||||
host: 127.0.0.1:55532
 | 
					 | 
				
			||||||
user-agent: Factor http.client
 | 
					 | 
				
			||||||
content-length: 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
;
 | 
					 | 
				
			||||||
{ f "0" } [
 | 
					{ f "0" } [
 | 
				
			||||||
    test-post-0-content-length string>request
 | 
					    "" "localhost" <post-request> request>string string>request
 | 
				
			||||||
    [ post-data>> data>> ] [ header>> "content-length" of ] bi
 | 
					    [ post-data>> data>> ] [ header>> "content-length" of ] bi
 | 
				
			||||||
] unit-test
 | 
					] unit-test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
! Should work no problem.
 | 
					! Incorrect content-length works fine
 | 
				
			||||||
STRING: test-post-wrong-content-length
 | 
					 | 
				
			||||||
POST / HTTP/1.1
 | 
					 | 
				
			||||||
connection: close
 | 
					 | 
				
			||||||
host: 127.0.0.1:55532
 | 
					 | 
				
			||||||
user-agent: Factor http.client
 | 
					 | 
				
			||||||
Content-Type: application/x-www-form-urlencoded; charset=utf-8
 | 
					 | 
				
			||||||
content-length: 190
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
foo=bar
 | 
					 | 
				
			||||||
;
 | 
					 | 
				
			||||||
{ H{ { "foo" "bar" } } } [
 | 
					{ H{ { "foo" "bar" } } } [
 | 
				
			||||||
    test-post-wrong-content-length string>request post-data>> params>>
 | 
					    { { "foo" "bar" } } "localhost" <post-request> request>string
 | 
				
			||||||
 | 
					    "7" "190" replace string>request post-data>> params>>
 | 
				
			||||||
] unit-test
 | 
					] unit-test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
STRING: test-post-urlencoded
 | 
					 | 
				
			||||||
POST / HTTP/1.1
 | 
					 | 
				
			||||||
Accept: */*
 | 
					 | 
				
			||||||
Accept-Encoding: gzip, deflate
 | 
					 | 
				
			||||||
Connection: keep-alive
 | 
					 | 
				
			||||||
Content-Length: 15
 | 
					 | 
				
			||||||
Content-Type: application/x-www-form-urlencoded; charset=utf-8
 | 
					 | 
				
			||||||
Host: news.ycombinator.com
 | 
					 | 
				
			||||||
User-Agent: HTTPie/0.9.0-dev
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
name=John+Smith
 | 
					 | 
				
			||||||
;
 | 
					 | 
				
			||||||
{ H{ { "name" "John Smith" } } } [
 | 
					{ H{ { "name" "John Smith" } } } [
 | 
				
			||||||
    test-post-urlencoded string>request post-data>> params>>
 | 
					    { { "name" "John Smith" } } "localhost" <post-request> request>string
 | 
				
			||||||
 | 
					    string>request post-data>> params>>
 | 
				
			||||||
] unit-test
 | 
					] unit-test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
! multipart/form-data
 | 
					! multipart/form-data
 | 
				
			||||||
| 
						 | 
					@ -95,8 +59,8 @@ hello
 | 
				
			||||||
          "form-data; name=\"text\"; filename=\"upload.txt\"" }
 | 
					          "form-data; name=\"text\"; filename=\"upload.txt\"" }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
} [
 | 
					} [
 | 
				
			||||||
    test-multipart/form-data string>request post-data>> params>> "text" of
 | 
					    test-multipart/form-data normalize-nl string>request
 | 
				
			||||||
    [ filename>> ] [ headers>> ] bi
 | 
					    post-data>> params>> "text" of [ filename>> ] [ headers>> ] bi
 | 
				
			||||||
] unit-test
 | 
					] unit-test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
! Error handling
 | 
					! Error handling
 | 
				
			||||||
| 
						 | 
					@ -119,21 +83,39 @@ hello
 | 
				
			||||||
--768de80194d942619886d23f1337aa15--
 | 
					--768de80194d942619886d23f1337aa15--
 | 
				
			||||||
 | 
					
 | 
				
			||||||
;
 | 
					;
 | 
				
			||||||
{ t } [
 | 
					[ test-multipart/form-data-missing-boundary string>request ]
 | 
				
			||||||
    [
 | 
					[ no-boundary? ] must-fail-with
 | 
				
			||||||
        test-multipart/form-data-missing-boundary string>request
 | 
					 | 
				
			||||||
    ] [ no-boundary? ] recover
 | 
					 | 
				
			||||||
] unit-test
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
! Relative urls are invalid.
 | 
					! Relative urls are invalid.
 | 
				
			||||||
{ "foo" } [
 | 
					[ "GET foo HTTP/1.1" string>request ] [ path>> "foo" = ] must-fail-with
 | 
				
			||||||
    [ "GET foo HTTP/1.1" string>request ] [ path>> ] recover
 | 
					 | 
				
			||||||
] unit-test
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
! Empty request lines
 | 
					! Empty request lines
 | 
				
			||||||
{ t } [
 | 
					[ "" string>request ] [ parse-error>> parse-error? ] must-fail-with
 | 
				
			||||||
    [ "" string>request ] [ parse-error>> parse-error? ] recover
 | 
					
 | 
				
			||||||
] unit-test
 | 
					! Missing content-length is probably not ok. It's plausible
 | 
				
			||||||
 | 
					! transfer-length could replace it, but we don't handle it atm anyway.
 | 
				
			||||||
 | 
					[
 | 
				
			||||||
 | 
					    { { "foo" "bar" } } "localhost" <post-request> request>string
 | 
				
			||||||
 | 
					    "content-length" "foo" replace string>request
 | 
				
			||||||
 | 
					] [ content-length-missing? ] must-fail-with
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					! Non-numeric content-length is ofc crap.
 | 
				
			||||||
 | 
					[
 | 
				
			||||||
 | 
					    { { "foo" "bar" } } "localhost" <post-request> request>string
 | 
				
			||||||
 | 
					    "7" "i am not a number!" replace string>request
 | 
				
			||||||
 | 
					] [
 | 
				
			||||||
 | 
					    [ invalid-content-length? ]
 | 
				
			||||||
 | 
					    [ content-length>> "i am not a number!" = ] bi and
 | 
				
			||||||
 | 
					] must-fail-with
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					! Negative is it too.
 | 
				
			||||||
 | 
					[
 | 
				
			||||||
 | 
					    { { "foo" "bar" } } "localhost" <post-request> request>string
 | 
				
			||||||
 | 
					    "7" "-1234" replace string>request
 | 
				
			||||||
 | 
					] [
 | 
				
			||||||
 | 
					    [ invalid-content-length? ]
 | 
				
			||||||
 | 
					    [ content-length>> -1234 = ] bi and
 | 
				
			||||||
 | 
					] must-fail-with
 | 
				
			||||||
 | 
					
 | 
				
			||||||
! RFC 2616: Section 4.1
 | 
					! RFC 2616: Section 4.1
 | 
				
			||||||
! In the interest of robustness, servers SHOULD ignore any empty
 | 
					! In the interest of robustness, servers SHOULD ignore any empty
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
USING: accessors combinators continuations http http.parsers io io.crlf
 | 
					USING: accessors combinators continuations http http.parsers io io.crlf
 | 
				
			||||||
io.encodings io.encodings.binary io.streams.limited kernel math.order
 | 
					io.encodings io.encodings.binary io.streams.limited kernel math math.order
 | 
				
			||||||
math.parser namespaces sequences splitting urls urls.encoding ;
 | 
					math.parser namespaces sequences splitting urls urls.encoding ;
 | 
				
			||||||
FROM: mime.multipart => parse-multipart ;
 | 
					FROM: mime.multipart => parse-multipart ;
 | 
				
			||||||
IN: http.server.requests
 | 
					IN: http.server.requests
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,10 @@ ERROR: no-boundary < request-error ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ERROR: invalid-path < request-error path ;
 | 
					ERROR: invalid-path < request-error path ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ERROR: invalid-content-length < request-error content-length ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ERROR: content-length-missing < request-error ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ERROR: bad-request-line < request-error parse-error ;
 | 
					ERROR: bad-request-line < request-error parse-error ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
: check-absolute ( url -- )
 | 
					: check-absolute ( url -- )
 | 
				
			||||||
| 
						 | 
					@ -34,22 +38,28 @@ upload-limit [ 200,000,000 ] initialize
 | 
				
			||||||
    ";" split1 nip
 | 
					    ";" split1 nip
 | 
				
			||||||
    "=" split1 nip [ no-boundary ] unless* ;
 | 
					    "=" split1 nip [ no-boundary ] unless* ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
: read-multipart-data ( request -- mime-parts )
 | 
					: maybe-limit-input ( content-length -- )
 | 
				
			||||||
    [ "content-type" header ]
 | 
					    unlimited-input upload-limit get [ min ] when* limited-input ;
 | 
				
			||||||
    [ "content-length" header string>number ] bi
 | 
					 | 
				
			||||||
    unlimited-input
 | 
					 | 
				
			||||||
    upload-limit get [ min ] when* limited-input
 | 
					 | 
				
			||||||
    binary decode-input
 | 
					 | 
				
			||||||
    parse-multipart-form-data parse-multipart ;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
: read-content ( request -- bytes )
 | 
					: read-multipart-data ( request content-length -- mime-parts )
 | 
				
			||||||
    "content-length" header string>number read ;
 | 
					    maybe-limit-input binary decode-input
 | 
				
			||||||
 | 
					    "content-type" header parse-multipart-form-data parse-multipart ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					: parse-content-length-safe ( request -- content-length )
 | 
				
			||||||
 | 
					    "content-length" header [
 | 
				
			||||||
 | 
					        dup string>number [
 | 
				
			||||||
 | 
					            nip dup 0 >= [ invalid-content-length ] unless
 | 
				
			||||||
 | 
					        ] [ invalid-content-length ] if*
 | 
				
			||||||
 | 
					    ] [ content-length-missing ] if* ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
: parse-content ( request content-type -- post-data )
 | 
					: parse-content ( request content-type -- post-data )
 | 
				
			||||||
    [ <post-data> swap ] keep {
 | 
					    dup <post-data> -rot over parse-content-length-safe swap
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        { "multipart/form-data" [ read-multipart-data >>params ] }
 | 
					        { "multipart/form-data" [ read-multipart-data >>params ] }
 | 
				
			||||||
        { "application/x-www-form-urlencoded" [ read-content query>assoc >>params ] }
 | 
					        { "application/x-www-form-urlencoded" [
 | 
				
			||||||
        [ drop read-content >>data ]
 | 
					            nip read query>assoc >>params
 | 
				
			||||||
 | 
					        ] }
 | 
				
			||||||
 | 
					        [ drop nip read >>data ]
 | 
				
			||||||
    } case ;
 | 
					    } case ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
: read-post-data ( request -- request )
 | 
					: read-post-data ( request -- request )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue