netramesh
1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package http6
7import (8"bufio"9"bytes"10"fmt"11"io"12"io/ioutil"13"net/url"14"reflect"15"strings"16"testing"17)
18
19type reqTest struct {20Raw string21Req *Request22Body string23Trailer Header
24Error string25}
26
27var noError = ""28var noBodyStr = ""29var noTrailer Header = nil30
31var reqTests = []reqTest{32// Baseline test; All Request fields included for template use33{34"GET http://www.techcrunch.com/ HTTP/1.1\r\n" +35"Host: www.techcrunch.com\r\n" +36"User-Agent: Fake\r\n" +37"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +38"Accept-Language: en-us,en;q=0.5\r\n" +39"Accept-Encoding: gzip,deflate\r\n" +40"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +41"Keep-Alive: 300\r\n" +42"Content-Length: 7\r\n" +43"Proxy-Connection: keep-alive\r\n\r\n" +44"abcdef\n???",45
46&Request{47Method: "GET",48URL: &url.URL{49Scheme: "http",50Host: "www.techcrunch.com",51Path: "/",52},53Proto: "HTTP/1.1",54ProtoMajor: 1,55ProtoMinor: 1,56Header: Header{57"Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},58"Accept-Language": {"en-us,en;q=0.5"},59"Accept-Encoding": {"gzip,deflate"},60"Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},61"Keep-Alive": {"300"},62"Proxy-Connection": {"keep-alive"},63"Content-Length": {"7"},64"User-Agent": {"Fake"},65},66Close: false,67ContentLength: 7,68Host: "www.techcrunch.com",69RequestURI: "http://www.techcrunch.com/",70},71
72"abcdef\n",73
74noTrailer,75noError,76},77
78// GET request with no body (the normal case)79{80"GET / HTTP/1.1\r\n" +81"Host: foo.com\r\n\r\n",82
83&Request{84Method: "GET",85URL: &url.URL{86Path: "/",87},88Proto: "HTTP/1.1",89ProtoMajor: 1,90ProtoMinor: 1,91Header: Header{},92Close: false,93ContentLength: 0,94Host: "foo.com",95RequestURI: "/",96},97
98noBodyStr,99noTrailer,100noError,101},102
103// Tests that we don't parse a path that looks like a104// scheme-relative URI as a scheme-relative URI.105{106"GET //user@host/is/actually/a/path/ HTTP/1.1\r\n" +107"Host: test\r\n\r\n",108
109&Request{110Method: "GET",111URL: &url.URL{112Path: "//user@host/is/actually/a/path/",113},114Proto: "HTTP/1.1",115ProtoMajor: 1,116ProtoMinor: 1,117Header: Header{},118Close: false,119ContentLength: 0,120Host: "test",121RequestURI: "//user@host/is/actually/a/path/",122},123
124noBodyStr,125noTrailer,126noError,127},128
129// Tests a bogus absolute-path on the Request-Line (RFC 7230 section 5.3.1)130{131"GET ../../../../etc/passwd HTTP/1.1\r\n" +132"Host: test\r\n\r\n",133nil,134noBodyStr,135noTrailer,136"parse ../../../../etc/passwd: invalid URI for request",137},138
139// Tests missing URL:140{141"GET HTTP/1.1\r\n" +142"Host: test\r\n\r\n",143nil,144noBodyStr,145noTrailer,146"parse : empty url",147},148
149// Tests chunked body with trailer:150{151"POST / HTTP/1.1\r\n" +152"Host: foo.com\r\n" +153"Transfer-Encoding: chunked\r\n\r\n" +154"3\r\nfoo\r\n" +155"3\r\nbar\r\n" +156"0\r\n" +157"Trailer-Key: Trailer-Value\r\n" +158"\r\n",159&Request{160Method: "POST",161URL: &url.URL{162Path: "/",163},164TransferEncoding: []string{"chunked"},165Proto: "HTTP/1.1",166ProtoMajor: 1,167ProtoMinor: 1,168Header: Header{},169ContentLength: -1,170Host: "foo.com",171RequestURI: "/",172},173
174"foobar",175Header{176"Trailer-Key": {"Trailer-Value"},177},178noError,179},180
181// Tests chunked body and a bogus Content-Length which should be deleted.182{183"POST / HTTP/1.1\r\n" +184"Host: foo.com\r\n" +185"Transfer-Encoding: chunked\r\n" +186"Content-Length: 9999\r\n\r\n" + // to be removed.187"3\r\nfoo\r\n" +188"3\r\nbar\r\n" +189"0\r\n" +190"\r\n",191&Request{192Method: "POST",193URL: &url.URL{194Path: "/",195},196TransferEncoding: []string{"chunked"},197Proto: "HTTP/1.1",198ProtoMajor: 1,199ProtoMinor: 1,200Header: Header{},201ContentLength: -1,202Host: "foo.com",203RequestURI: "/",204},205
206"foobar",207noTrailer,208noError,209},210
211// CONNECT request with domain name:212{213"CONNECT www.google.com:443 HTTP/1.1\r\n\r\n",214
215&Request{216Method: "CONNECT",217URL: &url.URL{218Host: "www.google.com:443",219},220Proto: "HTTP/1.1",221ProtoMajor: 1,222ProtoMinor: 1,223Header: Header{},224Close: false,225ContentLength: 0,226Host: "www.google.com:443",227RequestURI: "www.google.com:443",228},229
230noBodyStr,231noTrailer,232noError,233},234
235// CONNECT request with IP address:236{237"CONNECT 127.0.0.1:6060 HTTP/1.1\r\n\r\n",238
239&Request{240Method: "CONNECT",241URL: &url.URL{242Host: "127.0.0.1:6060",243},244Proto: "HTTP/1.1",245ProtoMajor: 1,246ProtoMinor: 1,247Header: Header{},248Close: false,249ContentLength: 0,250Host: "127.0.0.1:6060",251RequestURI: "127.0.0.1:6060",252},253
254noBodyStr,255noTrailer,256noError,257},258
259// CONNECT request for RPC:260{261"CONNECT /_goRPC_ HTTP/1.1\r\n\r\n",262
263&Request{264Method: "CONNECT",265URL: &url.URL{266Path: "/_goRPC_",267},268Proto: "HTTP/1.1",269ProtoMajor: 1,270ProtoMinor: 1,271Header: Header{},272Close: false,273ContentLength: 0,274Host: "",275RequestURI: "/_goRPC_",276},277
278noBodyStr,279noTrailer,280noError,281},282
283// SSDP Notify request. golang.org/issue/3692284{285"NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n",286&Request{287Method: "NOTIFY",288URL: &url.URL{289Path: "*",290},291Proto: "HTTP/1.1",292ProtoMajor: 1,293ProtoMinor: 1,294Header: Header{295"Server": []string{"foo"},296},297Close: false,298ContentLength: 0,299RequestURI: "*",300},301
302noBodyStr,303noTrailer,304noError,305},306
307// OPTIONS request. Similar to golang.org/issue/3692308{309"OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n",310&Request{311Method: "OPTIONS",312URL: &url.URL{313Path: "*",314},315Proto: "HTTP/1.1",316ProtoMajor: 1,317ProtoMinor: 1,318Header: Header{319"Server": []string{"foo"},320},321Close: false,322ContentLength: 0,323RequestURI: "*",324},325
326noBodyStr,327noTrailer,328noError,329},330
331// Connection: close. golang.org/issue/8261332{333"GET / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\n\r\n",334&Request{335Method: "GET",336URL: &url.URL{337Path: "/",338},339Header: Header{340// This wasn't removed from Go 1.0 to341// Go 1.3, so locking it in that we342// keep this:343"Connection": []string{"close"},344},345Host: "issue8261.com",346Proto: "HTTP/1.1",347ProtoMajor: 1,348ProtoMinor: 1,349Close: true,350RequestURI: "/",351},352
353noBodyStr,354noTrailer,355noError,356},357
358// HEAD with Content-Length 0. Make sure this is permitted,359// since I think we used to send it.360{361"HEAD / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",362&Request{363Method: "HEAD",364URL: &url.URL{365Path: "/",366},367Header: Header{368"Connection": []string{"close"},369"Content-Length": []string{"0"},370},371Host: "issue8261.com",372Proto: "HTTP/1.1",373ProtoMajor: 1,374ProtoMinor: 1,375Close: true,376RequestURI: "/",377},378
379noBodyStr,380noTrailer,381noError,382},383
384// http2 client preface:385{386"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",387&Request{388Method: "PRI",389URL: &url.URL{390Path: "*",391},392Header: Header{},393Proto: "HTTP/2.0",394ProtoMajor: 2,395ProtoMinor: 0,396RequestURI: "*",397ContentLength: -1,398Close: true,399},400noBodyStr,401noTrailer,402noError,403},404}
405
406func TestReadRequest(t *testing.T) {407for i := range reqTests {408tt := &reqTests[i]409req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.Raw)))410if err != nil {411if err.Error() != tt.Error {412t.Errorf("#%d: error %q, want error %q", i, err.Error(), tt.Error)413}414continue415}416rbody := req.Body417req.Body = nil418testName := fmt.Sprintf("Test %d (%q)", i, tt.Raw)419diff(t, testName, req, tt.Req)420var bout bytes.Buffer421if rbody != nil {422_, err := io.Copy(&bout, rbody)423if err != nil {424t.Fatalf("%s: copying body: %v", testName, err)425}426rbody.Close()427}428body := bout.String()429if body != tt.Body {430t.Errorf("%s: Body = %q want %q", testName, body, tt.Body)431}432if !reflect.DeepEqual(tt.Trailer, req.Trailer) {433t.Errorf("%s: Trailers differ.\n got: %v\nwant: %v", testName, req.Trailer, tt.Trailer)434}435}436}
437
438// reqBytes treats req as a request (with \n delimiters) and returns it with \r\n delimiters,
439// ending in \r\n\r\n
440func reqBytes(req string) []byte {441return []byte(strings.Replace(strings.TrimSpace(req), "\n", "\r\n", -1) + "\r\n\r\n")442}
443
444var badRequestTests = []struct {445name string446req []byte447}{448{"bad_connect_host", reqBytes("CONNECT []%20%48%54%54%50%2f%31%2e%31%0a%4d%79%48%65%61%64%65%72%3a%20%31%32%33%0a%0a HTTP/1.0")},449{"smuggle_two_contentlen", reqBytes(`POST / HTTP/1.1450Content-Length: 3
451Content-Length: 4
452
453abc`)},454{"smuggle_content_len_head", reqBytes(`HEAD / HTTP/1.1455Host: foo
456Content-Length: 5`)},457
458// golang.org/issue/22464459{"leading_space_in_header", reqBytes(`HEAD / HTTP/1.1460Host: foo
461Content-Length: 5`)},462{"leading_tab_in_header", reqBytes(`HEAD / HTTP/1.1463\tHost: foo
464Content-Length: 5`)},465}
466
467func TestReadRequest_Bad(t *testing.T) {468for _, tt := range badRequestTests {469got, err := ReadRequest(bufio.NewReader(bytes.NewReader(tt.req)))470if err == nil {471all, err := ioutil.ReadAll(got.Body)472t.Errorf("%s: got unexpected request = %#v\n Body = %q, %v", tt.name, got, all, err)473}474}475}
476