forked from useflyent/fhttp
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed imports and types on std lib and added header order
- Loading branch information
0 parents
commit b267984
Showing
113 changed files
with
65,725 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# fhttp | ||
The f stands for flex. fhttp is a fork of net/http that provides an array of features pertaining to the fingerprint of the golang http client. Through these changes, the http client becomes much more flexible, and when combined with transports such as [uTLS](https://github.com/refraction-networking/utls) can mitigate servers from fingerprinting requests and see that it is made by golang. It will look like it's from a regular chrome browser. | ||
|
||
Documentation can be contributed, otherwise, look at tests and examples. Main one should be [example_client_test.go](example_client_test.go) | ||
|
||
# Features | ||
## Ordered Headers | ||
Allows for pseduo header order and normal header order. Most of the code is taken from [this pr](https://go-review.googlesource.com/c/go/+/105755/). | ||
|
||
## Connection settings (TODO) | ||
Has Chrome like connection settings: | ||
``` | ||
SETTINGS_HEADER_TABLE_SIZE | ||
``` | ||
(will add rest later, notion broken on computer) | ||
|
||
## Backwards compatible with net/http | ||
Although this library is an extension of `net/http`, it is also meant to be backwards compatible. Replacing | ||
|
||
```go | ||
import ( | ||
"net/http" | ||
) | ||
``` | ||
|
||
with | ||
|
||
```go | ||
import ( | ||
http "github.com/zMrKrabz/fhttp" | ||
) | ||
``` | ||
|
||
SHOULD not break anything. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// Copyright 2013 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_test | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"fmt" | ||
"io" | ||
"strings" | ||
"testing" | ||
|
||
. "github.com/zMrKrabz/fhttp" | ||
"github.com/zMrKrabz/fhttp/httptest" | ||
) | ||
|
||
func TestNextProtoUpgrade(t *testing.T) { | ||
setParallel(t) | ||
defer afterTest(t) | ||
ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) { | ||
fmt.Fprintf(w, "path=%s,proto=", r.URL.Path) | ||
if r.TLS != nil { | ||
w.Write([]byte(r.TLS.NegotiatedProtocol)) | ||
} | ||
if r.RemoteAddr == "" { | ||
t.Error("request with no RemoteAddr") | ||
} | ||
if r.Body == nil { | ||
t.Errorf("request with nil Body") | ||
} | ||
})) | ||
ts.TLS = &tls.Config{ | ||
NextProtos: []string{"unhandled-proto", "tls-0.9"}, | ||
} | ||
ts.Config.TLSNextProto = map[string]func(*Server, *tls.Conn, Handler){ | ||
"tls-0.9": handleTLSProtocol09, | ||
} | ||
ts.StartTLS() | ||
defer ts.Close() | ||
|
||
// Normal request, without NPN. | ||
{ | ||
c := ts.Client() | ||
res, err := c.Get(ts.URL) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
body, err := io.ReadAll(res.Body) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if want := "path=/,proto="; string(body) != want { | ||
t.Errorf("plain request = %q; want %q", body, want) | ||
} | ||
} | ||
|
||
// Request to an advertised but unhandled NPN protocol. | ||
// Server will hang up. | ||
{ | ||
certPool := x509.NewCertPool() | ||
certPool.AddCert(ts.Certificate()) | ||
tr := &Transport{ | ||
TLSClientConfig: &tls.Config{ | ||
RootCAs: certPool, | ||
NextProtos: []string{"unhandled-proto"}, | ||
}, | ||
} | ||
defer tr.CloseIdleConnections() | ||
c := &Client{ | ||
Transport: tr, | ||
} | ||
res, err := c.Get(ts.URL) | ||
if err == nil { | ||
defer res.Body.Close() | ||
var buf bytes.Buffer | ||
res.Write(&buf) | ||
t.Errorf("expected error on unhandled-proto request; got: %s", buf.Bytes()) | ||
} | ||
} | ||
|
||
// Request using the "tls-0.9" protocol, which we register here. | ||
// It is HTTP/0.9 over TLS. | ||
{ | ||
c := ts.Client() | ||
tlsConfig := c.Transport.(*Transport).TLSClientConfig | ||
tlsConfig.NextProtos = []string{"tls-0.9"} | ||
conn, err := tls.Dial("tcp", ts.Listener.Addr().String(), tlsConfig) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
conn.Write([]byte("GET /foo\n")) | ||
body, err := io.ReadAll(conn) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if want := "path=/foo,proto=tls-0.9"; string(body) != want { | ||
t.Errorf("plain request = %q; want %q", body, want) | ||
} | ||
} | ||
} | ||
|
||
// handleTLSProtocol09 implements the HTTP/0.9 protocol over TLS, for the | ||
// TestNextProtoUpgrade test. | ||
func handleTLSProtocol09(srv *Server, conn *tls.Conn, h Handler) { | ||
br := bufio.NewReader(conn) | ||
line, err := br.ReadString('\n') | ||
if err != nil { | ||
return | ||
} | ||
line = strings.TrimSpace(line) | ||
path := strings.TrimPrefix(line, "GET ") | ||
if path == line { | ||
return | ||
} | ||
req, _ := NewRequest("GET", path, nil) | ||
req.Proto = "HTTP/0.9" | ||
req.ProtoMajor = 0 | ||
req.ProtoMinor = 9 | ||
rw := &http09Writer{conn, make(Header)} | ||
h.ServeHTTP(rw, req) | ||
} | ||
|
||
type http09Writer struct { | ||
io.Writer | ||
h Header | ||
} | ||
|
||
func (w http09Writer) Header() Header { return w.h } | ||
func (w http09Writer) WriteHeader(int) {} // no headers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
// Copyright 2011 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. | ||
|
||
// This file implements CGI from the perspective of a child | ||
// process. | ||
|
||
package cgi | ||
|
||
import ( | ||
"bufio" | ||
"crypto/tls" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// Request returns the HTTP request as represented in the current | ||
// environment. This assumes the current program is being run | ||
// by a web server in a CGI environment. | ||
// The returned Request's Body is populated, if applicable. | ||
func Request() (*http.Request, error) { | ||
r, err := RequestFromMap(envMap(os.Environ())) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if r.ContentLength > 0 { | ||
r.Body = io.NopCloser(io.LimitReader(os.Stdin, r.ContentLength)) | ||
} | ||
return r, nil | ||
} | ||
|
||
func envMap(env []string) map[string]string { | ||
m := make(map[string]string) | ||
for _, kv := range env { | ||
if idx := strings.Index(kv, "="); idx != -1 { | ||
m[kv[:idx]] = kv[idx+1:] | ||
} | ||
} | ||
return m | ||
} | ||
|
||
// RequestFromMap creates an http.Request from CGI variables. | ||
// The returned Request's Body field is not populated. | ||
func RequestFromMap(params map[string]string) (*http.Request, error) { | ||
r := new(http.Request) | ||
r.Method = params["REQUEST_METHOD"] | ||
if r.Method == "" { | ||
return nil, errors.New("cgi: no REQUEST_METHOD in environment") | ||
} | ||
|
||
r.Proto = params["SERVER_PROTOCOL"] | ||
var ok bool | ||
r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto) | ||
if !ok { | ||
return nil, errors.New("cgi: invalid SERVER_PROTOCOL version") | ||
} | ||
|
||
r.Close = true | ||
r.Trailer = http.Header{} | ||
r.Header = http.Header{} | ||
|
||
r.Host = params["HTTP_HOST"] | ||
|
||
if lenstr := params["CONTENT_LENGTH"]; lenstr != "" { | ||
clen, err := strconv.ParseInt(lenstr, 10, 64) | ||
if err != nil { | ||
return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr) | ||
} | ||
r.ContentLength = clen | ||
} | ||
|
||
if ct := params["CONTENT_TYPE"]; ct != "" { | ||
r.Header.Set("Content-Type", ct) | ||
} | ||
|
||
// Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers | ||
for k, v := range params { | ||
if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" { | ||
continue | ||
} | ||
r.Header.Add(strings.ReplaceAll(k[5:], "_", "-"), v) | ||
} | ||
|
||
uriStr := params["REQUEST_URI"] | ||
if uriStr == "" { | ||
// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING. | ||
uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"] | ||
s := params["QUERY_STRING"] | ||
if s != "" { | ||
uriStr += "?" + s | ||
} | ||
} | ||
|
||
// There's apparently a de-facto standard for this. | ||
// https://web.archive.org/web/20170105004655/http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636 | ||
if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" { | ||
r.TLS = &tls.ConnectionState{HandshakeComplete: true} | ||
} | ||
|
||
if r.Host != "" { | ||
// Hostname is provided, so we can reasonably construct a URL. | ||
rawurl := r.Host + uriStr | ||
if r.TLS == nil { | ||
rawurl = "http://" + rawurl | ||
} else { | ||
rawurl = "https://" + rawurl | ||
} | ||
url, err := url.Parse(rawurl) | ||
if err != nil { | ||
return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl) | ||
} | ||
r.URL = url | ||
} | ||
// Fallback logic if we don't have a Host header or the URL | ||
// failed to parse | ||
if r.URL == nil { | ||
url, err := url.Parse(uriStr) | ||
if err != nil { | ||
return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr) | ||
} | ||
r.URL = url | ||
} | ||
|
||
// Request.RemoteAddr has its port set by Go's standard http | ||
// server, so we do here too. | ||
remotePort, _ := strconv.Atoi(params["REMOTE_PORT"]) // zero if unset or invalid | ||
r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], strconv.Itoa(remotePort)) | ||
|
||
return r, nil | ||
} | ||
|
||
// Serve executes the provided Handler on the currently active CGI | ||
// request, if any. If there's no current CGI environment | ||
// an error is returned. The provided handler may be nil to use | ||
// http.DefaultServeMux. | ||
func Serve(handler http.Handler) error { | ||
req, err := Request() | ||
if err != nil { | ||
return err | ||
} | ||
if req.Body == nil { | ||
req.Body = http.NoBody | ||
} | ||
if handler == nil { | ||
handler = http.DefaultServeMux | ||
} | ||
rw := &response{ | ||
req: req, | ||
header: make(http.Header), | ||
bufw: bufio.NewWriter(os.Stdout), | ||
} | ||
handler.ServeHTTP(rw, req) | ||
rw.Write(nil) // make sure a response is sent | ||
if err = rw.bufw.Flush(); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
type response struct { | ||
req *http.Request | ||
header http.Header | ||
code int | ||
wroteHeader bool | ||
wroteCGIHeader bool | ||
bufw *bufio.Writer | ||
} | ||
|
||
func (r *response) Flush() { | ||
r.bufw.Flush() | ||
} | ||
|
||
func (r *response) Header() http.Header { | ||
return r.header | ||
} | ||
|
||
func (r *response) Write(p []byte) (n int, err error) { | ||
if !r.wroteHeader { | ||
r.WriteHeader(http.StatusOK) | ||
} | ||
if !r.wroteCGIHeader { | ||
r.writeCGIHeader(p) | ||
} | ||
return r.bufw.Write(p) | ||
} | ||
|
||
func (r *response) WriteHeader(code int) { | ||
if r.wroteHeader { | ||
// Note: explicitly using Stderr, as Stdout is our HTTP output. | ||
fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL) | ||
return | ||
} | ||
r.wroteHeader = true | ||
r.code = code | ||
} | ||
|
||
// writeCGIHeader finalizes the header sent to the client and writes it to the output. | ||
// p is not written by writeHeader, but is the first chunk of the body | ||
// that will be written. It is sniffed for a Content-Type if none is | ||
// set explicitly. | ||
func (r *response) writeCGIHeader(p []byte) { | ||
if r.wroteCGIHeader { | ||
return | ||
} | ||
r.wroteCGIHeader = true | ||
fmt.Fprintf(r.bufw, "Status: %d %s\r\n", r.code, http.StatusText(r.code)) | ||
if _, hasType := r.header["Content-Type"]; !hasType { | ||
r.header.Set("Content-Type", http.DetectContentType(p)) | ||
} | ||
r.header.Write(r.bufw) | ||
r.bufw.WriteString("\r\n") | ||
r.bufw.Flush() | ||
} |
Oops, something went wrong.