mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			468 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			468 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2010 The Go Authors. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
package http
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"net/url"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
)
 | 
						|
 | 
						|
type reqTest struct {
 | 
						|
	Raw     string
 | 
						|
	Req     *Request
 | 
						|
	Body    string
 | 
						|
	Trailer Header
 | 
						|
	Error   string
 | 
						|
}
 | 
						|
 | 
						|
var noError = ""
 | 
						|
var noBody = ""
 | 
						|
var noTrailer Header = nil
 | 
						|
 | 
						|
var reqTests = []reqTest{
 | 
						|
	// Baseline test; All Request fields included for template use
 | 
						|
	{
 | 
						|
		"GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
 | 
						|
			"Host: www.techcrunch.com\r\n" +
 | 
						|
			"User-Agent: Fake\r\n" +
 | 
						|
			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
 | 
						|
			"Accept-Language: en-us,en;q=0.5\r\n" +
 | 
						|
			"Accept-Encoding: gzip,deflate\r\n" +
 | 
						|
			"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
 | 
						|
			"Keep-Alive: 300\r\n" +
 | 
						|
			"Content-Length: 7\r\n" +
 | 
						|
			"Proxy-Connection: keep-alive\r\n\r\n" +
 | 
						|
			"abcdef\n???",
 | 
						|
 | 
						|
		&Request{
 | 
						|
			Method: "GET",
 | 
						|
			URL: &url.URL{
 | 
						|
				Scheme: "http",
 | 
						|
				Host:   "www.techcrunch.com",
 | 
						|
				Path:   "/",
 | 
						|
			},
 | 
						|
			Proto:      "HTTP/1.1",
 | 
						|
			ProtoMajor: 1,
 | 
						|
			ProtoMinor: 1,
 | 
						|
			Header: Header{
 | 
						|
				"Accept":           {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
 | 
						|
				"Accept-Language":  {"en-us,en;q=0.5"},
 | 
						|
				"Accept-Encoding":  {"gzip,deflate"},
 | 
						|
				"Accept-Charset":   {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
 | 
						|
				"Keep-Alive":       {"300"},
 | 
						|
				"Proxy-Connection": {"keep-alive"},
 | 
						|
				"Content-Length":   {"7"},
 | 
						|
				"User-Agent":       {"Fake"},
 | 
						|
			},
 | 
						|
			Close:         false,
 | 
						|
			ContentLength: 7,
 | 
						|
			Host:          "www.techcrunch.com",
 | 
						|
			RequestURI:    "http://www.techcrunch.com/",
 | 
						|
		},
 | 
						|
 | 
						|
		"abcdef\n",
 | 
						|
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// GET request with no body (the normal case)
 | 
						|
	{
 | 
						|
		"GET / HTTP/1.1\r\n" +
 | 
						|
			"Host: foo.com\r\n\r\n",
 | 
						|
 | 
						|
		&Request{
 | 
						|
			Method: "GET",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "/",
 | 
						|
			},
 | 
						|
			Proto:         "HTTP/1.1",
 | 
						|
			ProtoMajor:    1,
 | 
						|
			ProtoMinor:    1,
 | 
						|
			Header:        Header{},
 | 
						|
			Close:         false,
 | 
						|
			ContentLength: 0,
 | 
						|
			Host:          "foo.com",
 | 
						|
			RequestURI:    "/",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// Tests that we don't parse a path that looks like a
 | 
						|
	// scheme-relative URI as a scheme-relative URI.
 | 
						|
	{
 | 
						|
		"GET //user@host/is/actually/a/path/ HTTP/1.1\r\n" +
 | 
						|
			"Host: test\r\n\r\n",
 | 
						|
 | 
						|
		&Request{
 | 
						|
			Method: "GET",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "//user@host/is/actually/a/path/",
 | 
						|
			},
 | 
						|
			Proto:         "HTTP/1.1",
 | 
						|
			ProtoMajor:    1,
 | 
						|
			ProtoMinor:    1,
 | 
						|
			Header:        Header{},
 | 
						|
			Close:         false,
 | 
						|
			ContentLength: 0,
 | 
						|
			Host:          "test",
 | 
						|
			RequestURI:    "//user@host/is/actually/a/path/",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// Tests a bogus abs_path on the Request-Line (RFC 2616 section 5.1.2)
 | 
						|
	{
 | 
						|
		"GET ../../../../etc/passwd HTTP/1.1\r\n" +
 | 
						|
			"Host: test\r\n\r\n",
 | 
						|
		nil,
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		"parse ../../../../etc/passwd: invalid URI for request",
 | 
						|
	},
 | 
						|
 | 
						|
	// Tests missing URL:
 | 
						|
	{
 | 
						|
		"GET  HTTP/1.1\r\n" +
 | 
						|
			"Host: test\r\n\r\n",
 | 
						|
		nil,
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		"parse : empty url",
 | 
						|
	},
 | 
						|
 | 
						|
	// Tests chunked body with trailer:
 | 
						|
	{
 | 
						|
		"POST / HTTP/1.1\r\n" +
 | 
						|
			"Host: foo.com\r\n" +
 | 
						|
			"Transfer-Encoding: chunked\r\n\r\n" +
 | 
						|
			"3\r\nfoo\r\n" +
 | 
						|
			"3\r\nbar\r\n" +
 | 
						|
			"0\r\n" +
 | 
						|
			"Trailer-Key: Trailer-Value\r\n" +
 | 
						|
			"\r\n",
 | 
						|
		&Request{
 | 
						|
			Method: "POST",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "/",
 | 
						|
			},
 | 
						|
			TransferEncoding: []string{"chunked"},
 | 
						|
			Proto:            "HTTP/1.1",
 | 
						|
			ProtoMajor:       1,
 | 
						|
			ProtoMinor:       1,
 | 
						|
			Header:           Header{},
 | 
						|
			ContentLength:    -1,
 | 
						|
			Host:             "foo.com",
 | 
						|
			RequestURI:       "/",
 | 
						|
		},
 | 
						|
 | 
						|
		"foobar",
 | 
						|
		Header{
 | 
						|
			"Trailer-Key": {"Trailer-Value"},
 | 
						|
		},
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// Tests chunked body and a bogus Content-Length which should be deleted.
 | 
						|
	{
 | 
						|
		"POST / HTTP/1.1\r\n" +
 | 
						|
			"Host: foo.com\r\n" +
 | 
						|
			"Transfer-Encoding: chunked\r\n" +
 | 
						|
			"Content-Length: 9999\r\n\r\n" + // to be removed.
 | 
						|
			"3\r\nfoo\r\n" +
 | 
						|
			"3\r\nbar\r\n" +
 | 
						|
			"0\r\n" +
 | 
						|
			"\r\n",
 | 
						|
		&Request{
 | 
						|
			Method: "POST",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "/",
 | 
						|
			},
 | 
						|
			TransferEncoding: []string{"chunked"},
 | 
						|
			Proto:            "HTTP/1.1",
 | 
						|
			ProtoMajor:       1,
 | 
						|
			ProtoMinor:       1,
 | 
						|
			Header:           Header{},
 | 
						|
			ContentLength:    -1,
 | 
						|
			Host:             "foo.com",
 | 
						|
			RequestURI:       "/",
 | 
						|
		},
 | 
						|
 | 
						|
		"foobar",
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// CONNECT request with domain name:
 | 
						|
	{
 | 
						|
		"CONNECT www.google.com:443 HTTP/1.1\r\n\r\n",
 | 
						|
 | 
						|
		&Request{
 | 
						|
			Method: "CONNECT",
 | 
						|
			URL: &url.URL{
 | 
						|
				Host: "www.google.com:443",
 | 
						|
			},
 | 
						|
			Proto:         "HTTP/1.1",
 | 
						|
			ProtoMajor:    1,
 | 
						|
			ProtoMinor:    1,
 | 
						|
			Header:        Header{},
 | 
						|
			Close:         false,
 | 
						|
			ContentLength: 0,
 | 
						|
			Host:          "www.google.com:443",
 | 
						|
			RequestURI:    "www.google.com:443",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// CONNECT request with IP address:
 | 
						|
	{
 | 
						|
		"CONNECT 127.0.0.1:6060 HTTP/1.1\r\n\r\n",
 | 
						|
 | 
						|
		&Request{
 | 
						|
			Method: "CONNECT",
 | 
						|
			URL: &url.URL{
 | 
						|
				Host: "127.0.0.1:6060",
 | 
						|
			},
 | 
						|
			Proto:         "HTTP/1.1",
 | 
						|
			ProtoMajor:    1,
 | 
						|
			ProtoMinor:    1,
 | 
						|
			Header:        Header{},
 | 
						|
			Close:         false,
 | 
						|
			ContentLength: 0,
 | 
						|
			Host:          "127.0.0.1:6060",
 | 
						|
			RequestURI:    "127.0.0.1:6060",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// CONNECT request for RPC:
 | 
						|
	{
 | 
						|
		"CONNECT /_goRPC_ HTTP/1.1\r\n\r\n",
 | 
						|
 | 
						|
		&Request{
 | 
						|
			Method: "CONNECT",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "/_goRPC_",
 | 
						|
			},
 | 
						|
			Proto:         "HTTP/1.1",
 | 
						|
			ProtoMajor:    1,
 | 
						|
			ProtoMinor:    1,
 | 
						|
			Header:        Header{},
 | 
						|
			Close:         false,
 | 
						|
			ContentLength: 0,
 | 
						|
			Host:          "",
 | 
						|
			RequestURI:    "/_goRPC_",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// SSDP Notify request. golang.org/issue/3692
 | 
						|
	{
 | 
						|
		"NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n",
 | 
						|
		&Request{
 | 
						|
			Method: "NOTIFY",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "*",
 | 
						|
			},
 | 
						|
			Proto:      "HTTP/1.1",
 | 
						|
			ProtoMajor: 1,
 | 
						|
			ProtoMinor: 1,
 | 
						|
			Header: Header{
 | 
						|
				"Server": []string{"foo"},
 | 
						|
			},
 | 
						|
			Close:         false,
 | 
						|
			ContentLength: 0,
 | 
						|
			RequestURI:    "*",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// OPTIONS request. Similar to golang.org/issue/3692
 | 
						|
	{
 | 
						|
		"OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n",
 | 
						|
		&Request{
 | 
						|
			Method: "OPTIONS",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "*",
 | 
						|
			},
 | 
						|
			Proto:      "HTTP/1.1",
 | 
						|
			ProtoMajor: 1,
 | 
						|
			ProtoMinor: 1,
 | 
						|
			Header: Header{
 | 
						|
				"Server": []string{"foo"},
 | 
						|
			},
 | 
						|
			Close:         false,
 | 
						|
			ContentLength: 0,
 | 
						|
			RequestURI:    "*",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// Connection: close. golang.org/issue/8261
 | 
						|
	{
 | 
						|
		"GET / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\n\r\n",
 | 
						|
		&Request{
 | 
						|
			Method: "GET",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "/",
 | 
						|
			},
 | 
						|
			Header: Header{
 | 
						|
				// This wasn't removed from Go 1.0 to
 | 
						|
				// Go 1.3, so locking it in that we
 | 
						|
				// keep this:
 | 
						|
				"Connection": []string{"close"},
 | 
						|
			},
 | 
						|
			Host:       "issue8261.com",
 | 
						|
			Proto:      "HTTP/1.1",
 | 
						|
			ProtoMajor: 1,
 | 
						|
			ProtoMinor: 1,
 | 
						|
			Close:      true,
 | 
						|
			RequestURI: "/",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// HEAD with Content-Length 0. Make sure this is permitted,
 | 
						|
	// since I think we used to send it.
 | 
						|
	{
 | 
						|
		"HEAD / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
 | 
						|
		&Request{
 | 
						|
			Method: "HEAD",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "/",
 | 
						|
			},
 | 
						|
			Header: Header{
 | 
						|
				"Connection":     []string{"close"},
 | 
						|
				"Content-Length": []string{"0"},
 | 
						|
			},
 | 
						|
			Host:       "issue8261.com",
 | 
						|
			Proto:      "HTTP/1.1",
 | 
						|
			ProtoMajor: 1,
 | 
						|
			ProtoMinor: 1,
 | 
						|
			Close:      true,
 | 
						|
			RequestURI: "/",
 | 
						|
		},
 | 
						|
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
 | 
						|
	// http2 client preface:
 | 
						|
	{
 | 
						|
		"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
 | 
						|
		&Request{
 | 
						|
			Method: "PRI",
 | 
						|
			URL: &url.URL{
 | 
						|
				Path: "*",
 | 
						|
			},
 | 
						|
			Header:        Header{},
 | 
						|
			Proto:         "HTTP/2.0",
 | 
						|
			ProtoMajor:    2,
 | 
						|
			ProtoMinor:    0,
 | 
						|
			RequestURI:    "*",
 | 
						|
			ContentLength: -1,
 | 
						|
			Close:         true,
 | 
						|
		},
 | 
						|
		noBody,
 | 
						|
		noTrailer,
 | 
						|
		noError,
 | 
						|
	},
 | 
						|
}
 | 
						|
 | 
						|
func TestReadRequest(t *testing.T) {
 | 
						|
	for i := range reqTests {
 | 
						|
		tt := &reqTests[i]
 | 
						|
		req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.Raw)))
 | 
						|
		if err != nil {
 | 
						|
			if err.Error() != tt.Error {
 | 
						|
				t.Errorf("#%d: error %q, want error %q", i, err.Error(), tt.Error)
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		rbody := req.Body
 | 
						|
		req.Body = nil
 | 
						|
		testName := fmt.Sprintf("Test %d (%q)", i, tt.Raw)
 | 
						|
		diff(t, testName, req, tt.Req)
 | 
						|
		var bout bytes.Buffer
 | 
						|
		if rbody != nil {
 | 
						|
			_, err := io.Copy(&bout, rbody)
 | 
						|
			if err != nil {
 | 
						|
				t.Fatalf("%s: copying body: %v", testName, err)
 | 
						|
			}
 | 
						|
			rbody.Close()
 | 
						|
		}
 | 
						|
		body := bout.String()
 | 
						|
		if body != tt.Body {
 | 
						|
			t.Errorf("%s: Body = %q want %q", testName, body, tt.Body)
 | 
						|
		}
 | 
						|
		if !reflect.DeepEqual(tt.Trailer, req.Trailer) {
 | 
						|
			t.Errorf("%s: Trailers differ.\n got: %v\nwant: %v", testName, req.Trailer, tt.Trailer)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// reqBytes treats req as a request (with \n delimiters) and returns it with \r\n delimiters,
 | 
						|
// ending in \r\n\r\n
 | 
						|
func reqBytes(req string) []byte {
 | 
						|
	return []byte(strings.Replace(strings.TrimSpace(req), "\n", "\r\n", -1) + "\r\n\r\n")
 | 
						|
}
 | 
						|
 | 
						|
var badRequestTests = []struct {
 | 
						|
	name string
 | 
						|
	req  []byte
 | 
						|
}{
 | 
						|
	{"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")},
 | 
						|
	{"smuggle_two_contentlen", reqBytes(`POST / HTTP/1.1
 | 
						|
Content-Length: 3
 | 
						|
Content-Length: 4
 | 
						|
 | 
						|
abc`)},
 | 
						|
	{"smuggle_content_len_head", reqBytes(`HEAD / HTTP/1.1
 | 
						|
Host: foo
 | 
						|
Content-Length: 5`)},
 | 
						|
}
 | 
						|
 | 
						|
func TestReadRequest_Bad(t *testing.T) {
 | 
						|
	for _, tt := range badRequestTests {
 | 
						|
		got, err := ReadRequest(bufio.NewReader(bytes.NewReader(tt.req)))
 | 
						|
		if err == nil {
 | 
						|
			all, err := ioutil.ReadAll(got.Body)
 | 
						|
			t.Errorf("%s: got unexpected request = %#v\n  Body = %q, %v", tt.name, got, all, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |