Skip to content

Commit

Permalink
Fixed imports and types on std lib and added header order
Browse files Browse the repository at this point in the history
  • Loading branch information
gotsteez committed May 16, 2021
0 parents commit b267984
Show file tree
Hide file tree
Showing 113 changed files with 65,725 additions and 0 deletions.
34 changes: 34 additions & 0 deletions README.md
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.
133 changes: 133 additions & 0 deletions alpn_test.go
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
220 changes: 220 additions & 0 deletions cgi/child.go
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()
}
Loading

0 comments on commit b267984

Please sign in to comment.