init
This commit is contained in:
42
vendor/github.com/anacrolix/missinggo/httptoo/accept.go
generated
vendored
Normal file
42
vendor/github.com/anacrolix/missinggo/httptoo/accept.go
generated
vendored
Normal 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
|
||||
}
|
||||
)
|
106
vendor/github.com/anacrolix/missinggo/httptoo/bytes_content_range.go
generated
vendored
Normal file
106
vendor/github.com/anacrolix/missinggo/httptoo/bytes_content_range.go
generated
vendored
Normal 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
|
||||
}
|
19
vendor/github.com/anacrolix/missinggo/httptoo/client.go
generated
vendored
Normal file
19
vendor/github.com/anacrolix/missinggo/httptoo/client.go
generated
vendored
Normal 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
31
vendor/github.com/anacrolix/missinggo/httptoo/fs.go
generated
vendored
Normal 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
49
vendor/github.com/anacrolix/missinggo/httptoo/gzip.go
generated
vendored
Normal 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)
|
||||
})
|
||||
}
|
61
vendor/github.com/anacrolix/missinggo/httptoo/headers.go
generated
vendored
Normal file
61
vendor/github.com/anacrolix/missinggo/httptoo/headers.go
generated
vendored
Normal 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()), ", ")
|
||||
}
|
42
vendor/github.com/anacrolix/missinggo/httptoo/httptoo.go
generated
vendored
Normal file
42
vendor/github.com/anacrolix/missinggo/httptoo/httptoo.go
generated
vendored
Normal 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
|
112
vendor/github.com/anacrolix/missinggo/httptoo/inproc_roundtrip.go
generated
vendored
Normal file
112
vendor/github.com/anacrolix/missinggo/httptoo/inproc_roundtrip.go
generated
vendored
Normal 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)
|
||||
}
|
23
vendor/github.com/anacrolix/missinggo/httptoo/request.go
generated
vendored
Normal file
23
vendor/github.com/anacrolix/missinggo/httptoo/request.go
generated
vendored
Normal 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()
|
||||
}
|
138
vendor/github.com/anacrolix/missinggo/httptoo/reverse_proxy.go
generated
vendored
Normal file
138
vendor/github.com/anacrolix/missinggo/httptoo/reverse_proxy.go
generated
vendored
Normal 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
64
vendor/github.com/anacrolix/missinggo/httptoo/url.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user