This commit is contained in:
2018-11-04 15:58:15 +01:00
commit f956bcee28
1178 changed files with 584552 additions and 0 deletions

View File

@ -0,0 +1,42 @@
package httptoo
import (
"fmt"
"strconv"
"strings"
"github.com/anacrolix/missinggo/mime"
)
func ParseAccept(line string) (parsed AcceptDirectives, err error) {
dirs := strings.Split(line, ",")
for _, d := range dirs {
p := AcceptDirective{
Q: 1,
}
ss := strings.Split(d, ";")
switch len(ss) {
case 2:
p.Q, err = strconv.ParseFloat(ss[1], 32)
if err != nil {
return
}
fallthrough
case 1:
p.MimeType.FromString(ss[0])
default:
err = fmt.Errorf("error parsing %q", d)
return
}
parsed = append(parsed, p)
}
return
}
type (
AcceptDirectives []AcceptDirective
AcceptDirective struct {
MimeType mime.Type
Q float64
}
)

View File

@ -0,0 +1,106 @@
package httptoo
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
)
type BytesContentRange struct {
First, Last, Length int64
}
type BytesRange struct {
First, Last int64
}
func (me BytesRange) String() string {
if me.Last == math.MaxInt64 {
return fmt.Sprintf("bytes=%d-", me.First)
}
return fmt.Sprintf("bytes=%d-%d", me.First, me.Last)
}
var (
httpBytesRangeRegexp = regexp.MustCompile(`bytes[ =](\d+)-(\d*)`)
)
func ParseBytesRange(s string) (ret BytesRange, ok bool) {
ss := httpBytesRangeRegexp.FindStringSubmatch(s)
if ss == nil {
return
}
var err error
ret.First, err = strconv.ParseInt(ss[1], 10, 64)
if err != nil {
return
}
if ss[2] == "" {
ret.Last = math.MaxInt64
} else {
ret.Last, err = strconv.ParseInt(ss[2], 10, 64)
if err != nil {
return
}
}
ok = true
return
}
func parseUnitRanges(s string) (unit, ranges string) {
s = strings.TrimSpace(s)
i := strings.IndexAny(s, " =")
if i == -1 {
return
}
unit = s[:i]
ranges = s[i+1:]
return
}
func parseFirstLast(s string) (first, last int64) {
ss := strings.SplitN(s, "-", 2)
first, err := strconv.ParseInt(ss[0], 10, 64)
if err != nil {
panic(err)
}
last, err = strconv.ParseInt(ss[1], 10, 64)
if err != nil {
panic(err)
}
return
}
func parseContentRange(s string) (ret BytesContentRange) {
ss := strings.SplitN(s, "/", 2)
firstLast := strings.TrimSpace(ss[0])
if firstLast == "*" {
ret.First = -1
ret.Last = -1
} else {
ret.First, ret.Last = parseFirstLast(firstLast)
}
il := strings.TrimSpace(ss[1])
if il == "*" {
ret.Length = -1
} else {
var err error
ret.Length, err = strconv.ParseInt(il, 10, 64)
if err != nil {
panic(err)
}
}
return
}
func ParseBytesContentRange(s string) (ret BytesContentRange, ok bool) {
unit, ranges := parseUnitRanges(s)
if unit != "bytes" {
return
}
ret = parseContentRange(ranges)
ok = true
return
}

View File

@ -0,0 +1,19 @@
package httptoo
import (
"crypto/tls"
"net/http"
)
// Returns the http.Client's TLS Config, traversing and generating any
// defaults along the way to get it.
func ClientTLSConfig(cl *http.Client) *tls.Config {
if cl.Transport == nil {
cl.Transport = http.DefaultTransport
}
tr := cl.Transport.(*http.Transport)
if tr.TLSClientConfig == nil {
tr.TLSClientConfig = &tls.Config{}
}
return tr.TLSClientConfig
}

31
vendor/github.com/anacrolix/missinggo/httptoo/fs.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
package httptoo
import (
"net/http"
"os"
)
// Wraps a http.FileSystem, disabling directory listings, per the commonly
// requested feature at https://groups.google.com/forum/#!topic/golang-
// nuts/bStLPdIVM6w .
type JustFilesFilesystem struct {
Fs http.FileSystem
}
func (fs JustFilesFilesystem) Open(name string) (http.File, error) {
f, err := fs.Fs.Open(name)
if err != nil {
return nil, err
}
d, err := f.Stat()
if err != nil {
f.Close()
return nil, err
}
if d.IsDir() {
f.Close()
// This triggers http.FileServer to show a 404.
return nil, os.ErrNotExist
}
return f, nil
}

49
vendor/github.com/anacrolix/missinggo/httptoo/gzip.go generated vendored Normal file
View File

@ -0,0 +1,49 @@
package httptoo
import (
"compress/gzip"
"io"
"net/http"
"strings"
)
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
haveWritten bool
}
var _ http.ResponseWriter = &gzipResponseWriter{}
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
if w.haveWritten {
goto write
}
w.haveWritten = true
if w.Header().Get("Content-Type") != "" {
goto write
}
if type_ := http.DetectContentType(b); type_ != "application/octet-stream" {
w.Header().Set("Content-Type", type_)
}
write:
return w.Writer.Write(b)
}
// Gzips response body if the request says it'll allow it.
func GzipHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") || w.Header().Get("Content-Encoding") != "" || w.Header().Get("Vary") != "" {
h.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Vary", "Accept-Encoding")
gz := gzip.NewWriter(w)
defer gz.Close()
h.ServeHTTP(&gzipResponseWriter{
Writer: gz,
ResponseWriter: w,
}, r)
})
}

View File

@ -0,0 +1,61 @@
package httptoo
import (
"fmt"
"strings"
"time"
)
type Visibility int
const (
Default = 0
Public = 1
Private = 2
)
type CacheControlHeader struct {
MaxAge time.Duration
Caching Visibility
NoStore bool
}
func (me *CacheControlHeader) caching() []string {
switch me.Caching {
case Public:
return []string{"public"}
case Private:
return []string{"private"}
default:
return nil
}
}
func (me *CacheControlHeader) maxAge() []string {
if me.MaxAge == 0 {
return nil
}
d := me.MaxAge
if d < 0 {
d = 0
}
return []string{fmt.Sprintf("max-age=%d", d/time.Second)}
}
func (me *CacheControlHeader) noStore() []string {
if me.NoStore {
return []string{"no-store"}
}
return nil
}
func (me *CacheControlHeader) concat(sss ...[]string) (ret []string) {
for _, ss := range sss {
ret = append(ret, ss...)
}
return
}
func (me CacheControlHeader) String() string {
return strings.Join(me.concat(me.caching(), me.maxAge()), ", ")
}

View File

@ -0,0 +1,42 @@
package httptoo
import (
"net/http"
"strconv"
"strings"
"github.com/bradfitz/iter"
"github.com/anacrolix/missinggo"
)
func OriginatingProtocol(r *http.Request) string {
if fp := r.Header.Get("X-Forwarded-Proto"); fp != "" {
return fp
} else if r.TLS != nil {
return "https"
} else {
return "http"
}
}
// Clears the named cookie for every domain that leads to the current one.
func NukeCookie(w http.ResponseWriter, r *http.Request, name, path string) {
parts := strings.Split(missinggo.SplitHostMaybePort(r.Host).Host, ".")
for i := range iter.N(len(parts) + 1) { // Include the empty domain.
http.SetCookie(w, &http.Cookie{
Name: name,
MaxAge: -1,
Path: path,
Domain: strings.Join(parts[i:], "."),
})
}
}
// Performs quoted-string from http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html
func EncodeQuotedString(s string) string {
return strconv.Quote(s)
}
// https://httpstatuses.com/499
const StatusClientCancelledRequest = 499

View File

@ -0,0 +1,112 @@
package httptoo
import (
"context"
"io"
"net/http"
"sync"
"github.com/anacrolix/missinggo"
)
type responseWriter struct {
mu sync.Mutex
r http.Response
headerWritten missinggo.Event
bodyWriter io.WriteCloser
bodyClosed missinggo.SynchronizedEvent
}
var _ interface {
http.ResponseWriter
// We're able to emulate this easily enough.
http.CloseNotifier
} = &responseWriter{}
// Use Request.Context.Done instead.
func (me *responseWriter) CloseNotify() <-chan bool {
ret := make(chan bool, 1)
go func() {
<-me.bodyClosed.C()
ret <- true
}()
return ret
}
func (me *responseWriter) Header() http.Header {
if me.r.Header == nil {
me.r.Header = make(http.Header)
}
return me.r.Header
}
func (me *responseWriter) Write(b []byte) (int, error) {
me.mu.Lock()
if !me.headerWritten.IsSet() {
me.writeHeader(200)
}
me.mu.Unlock()
return me.bodyWriter.Write(b)
}
func (me *responseWriter) WriteHeader(status int) {
me.mu.Lock()
me.writeHeader(status)
me.mu.Unlock()
}
func (me *responseWriter) writeHeader(status int) {
if me.headerWritten.IsSet() {
return
}
me.r.StatusCode = status
me.headerWritten.Set()
}
func (me *responseWriter) runHandler(h http.Handler, req *http.Request) {
var pr *io.PipeReader
pr, me.bodyWriter = io.Pipe()
me.r.Body = struct {
io.Reader
io.Closer
}{pr, eventCloser{pr, &me.bodyClosed}}
// Shouldn't be writing to the response after the handler returns.
defer me.bodyWriter.Close()
// Send a 200 if nothing was written yet.
defer me.WriteHeader(200)
// Wrap the context in the given Request with one that closes when either
// the handler returns, or the response body is closed.
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
go func() {
<-me.bodyClosed.C()
cancel()
}()
h.ServeHTTP(me, req.WithContext(ctx))
}
type eventCloser struct {
c io.Closer
closed *missinggo.SynchronizedEvent
}
func (me eventCloser) Close() (err error) {
err = me.c.Close()
me.closed.Set()
return
}
func RoundTripHandler(req *http.Request, h http.Handler) (*http.Response, error) {
rw := responseWriter{}
go rw.runHandler(h, req)
<-rw.headerWritten.LockedChan(&rw.mu)
return &rw.r, nil
}
type InProcRoundTripper struct {
Handler http.Handler
}
func (me *InProcRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return RoundTripHandler(req, me.Handler)
}

View File

@ -0,0 +1,23 @@
package httptoo
import (
"net"
"net/http"
"github.com/anacrolix/missinggo"
)
// Request is intended for localhost, either with a localhost name, or
// loopback IP.
func RequestIsForLocalhost(r *http.Request) bool {
hostHost := missinggo.SplitHostMaybePort(r.Host).Host
if ip := net.ParseIP(hostHost); ip != nil {
return ip.IsLoopback()
}
return hostHost == "localhost"
}
// Request originated from a loopback IP.
func RequestIsFromLocalhost(r *http.Request) bool {
return net.ParseIP(missinggo.SplitHostMaybePort(r.RemoteAddr).Host).IsLoopback()
}

View File

@ -0,0 +1,138 @@
package httptoo
import (
"bufio"
"encoding/gob"
"io"
"net"
"net/http"
"net/url"
"sync"
)
func deepCopy(dst, src interface{}) error {
r, w := io.Pipe()
e := gob.NewEncoder(w)
d := gob.NewDecoder(r)
var decErr, encErr error
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
decErr = d.Decode(dst)
r.Close()
}()
encErr = e.Encode(src)
// Always returns nil.
w.CloseWithError(encErr)
wg.Wait()
if encErr != nil {
return encErr
}
return decErr
}
// Takes a request, and alters its destination fields, for proxying.
func RedirectedRequest(r *http.Request, newUrl string) (ret *http.Request, err error) {
u, err := url.Parse(newUrl)
if err != nil {
return
}
ret = new(http.Request)
*ret = *r
ret.Header = nil
err = deepCopy(&ret.Header, r.Header)
if err != nil {
return
}
ret.URL = u
ret.RequestURI = ""
return
}
func CopyHeaders(w http.ResponseWriter, r *http.Response) {
for h, vs := range r.Header {
for _, v := range vs {
w.Header().Add(h, v)
}
}
}
func ForwardResponse(w http.ResponseWriter, r *http.Response) {
CopyHeaders(w, r)
w.WriteHeader(r.StatusCode)
// Errors frequently occur writing the body when the client hangs up.
io.Copy(w, r.Body)
r.Body.Close()
}
func SetOriginRequestForwardingHeaders(o, f *http.Request) {
xff := o.Header.Get("X-Forwarded-For")
hop, _, _ := net.SplitHostPort(f.RemoteAddr)
if xff == "" {
xff = hop
} else {
xff += "," + hop
}
o.Header.Set("X-Forwarded-For", xff)
o.Header.Set("X-Forwarded-Proto", OriginatingProtocol(f))
}
// w is for the client response. r is the request to send to the origin
// (already "forwarded"). originUrl is where to send the request.
func ReverseProxyUpgrade(w http.ResponseWriter, r *http.Request, originUrl string) (err error) {
u, err := url.Parse(originUrl)
if err != nil {
return
}
oc, err := net.Dial("tcp", u.Host)
if err != nil {
return
}
defer oc.Close()
err = r.Write(oc)
if err != nil {
return
}
originConnReadBuffer := bufio.NewReader(oc)
originResp, err := http.ReadResponse(originConnReadBuffer, r)
if err != nil {
return
}
if originResp.StatusCode != 101 {
ForwardResponse(w, originResp)
return
}
cc, _, err := w.(http.Hijacker).Hijack()
if err != nil {
return
}
defer cc.Close()
originResp.Write(cc)
go io.Copy(oc, cc)
// Let the origin connection control when this routine returns, as we
// should trust it more.
io.Copy(cc, originConnReadBuffer)
return
}
func ReverseProxy(w http.ResponseWriter, r *http.Request, originUrl string, client *http.Client) (err error) {
originRequest, err := RedirectedRequest(r, originUrl)
if err != nil {
return
}
SetOriginRequestForwardingHeaders(originRequest, r)
if r.Header.Get("Connection") == "Upgrade" {
return ReverseProxyUpgrade(w, originRequest, originUrl)
}
rt := client.Transport
if rt == nil {
rt = http.DefaultTransport
}
originResp, err := rt.RoundTrip(originRequest)
if err != nil {
return
}
ForwardResponse(w, originResp)
return
}

64
vendor/github.com/anacrolix/missinggo/httptoo/url.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
package httptoo
import (
"net/http"
"net/url"
)
// Deep copies a URL. I could call it DeepCopyURL, but what else would you be
// copying when you have a *url.URL? Of note is that the Userinfo is deep
// copied. The returned URL shares no references with the original.
func CopyURL(u *url.URL) (ret *url.URL) {
ret = new(url.URL)
*ret = *u
if u.User != nil {
ret.User = new(url.Userinfo)
*ret.User = *u.User
}
return
}
// Reconstructs the URL that would have produced the given Request.
// Request.URLs are not fully populated in http.Server handlers.
func RequestedURL(r *http.Request) (ret *url.URL) {
ret = CopyURL(r.URL)
ret.Host = r.Host
ret.Scheme = OriginatingProtocol(r)
return
}
// The official URL struct parameters, for tracking changes and reference
// here.
//
// Scheme string
// Opaque string // encoded opaque data
// User *Userinfo // username and password information
// Host string // host or host:port
// Path string
// RawPath string // encoded path hint (Go 1.5 and later only; see EscapedPath method)
// ForceQuery bool // append a query ('?') even if RawQuery is empty
// RawQuery string // encoded query values, without '?'
// Fragment string // fragment for references, without '#'
// Return the first URL extended with elements of the second, in the manner
// that occurs throughout my projects. Noteworthy difference from
// url.URL.ResolveReference is that if the reference has a scheme, the base is
// not completely ignored.
func AppendURL(u, v *url.URL) *url.URL {
u = CopyURL(u)
clobberString(&u.Scheme, v.Scheme)
clobberString(&u.Host, v.Host)
u.Path += v.Path
q := u.Query()
for k, v := range v.Query() {
q[k] = append(q[k], v...)
}
u.RawQuery = q.Encode()
return u
}
func clobberString(s *string, value string) {
if value != "" {
*s = value
}
}