Repository navigation
Expand file tree
/
Copy pathrequest.go
More file actions
130 lines (115 loc) · 4.15 KB
/
Copy pathrequest.go
File metadata and controls
130 lines (115 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package connectip
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"reflect"
"github.com/dunglas/httpsfv"
"github.com/quic-go/quic-go/http3"
"github.com/yosida95/uritemplate/v3"
)
const requestProtocol = "connect-ip"
var capsuleProtocolHeaderValue string
func init() {
v, err := httpsfv.Marshal(httpsfv.NewItem(true))
if err != nil {
panic(fmt.Sprintf("failed to marshal capsule protocol header value: %v", err))
}
capsuleProtocolHeaderValue = v
}
// Request is a CONNECT-IP request created by NewRequest.
// The zero value is not valid.
type Request struct {
req *http.Request
}
// NewRequest creates a CONNECT-IP request.
func NewRequest(ctx context.Context, proxyTemplate *uritemplate.Template) (*Request, error) {
// connect-ip-go currently does not support IP flow forwarding,
// see https://github.com/quic-go/connect-ip-go/issues/31.
if len(proxyTemplate.Varnames()) > 0 {
return nil, errors.New("connect-ip: IP flow forwarding not supported")
}
req, err := http.NewRequestWithContext(ctx, http.MethodConnect, proxyTemplate.Raw(), nil)
if err != nil {
return nil, fmt.Errorf("connect-ip: failed to create request: %w", err)
}
req.Proto = requestProtocol
req.Host = req.URL.Host
req.Header.Set(http3.CapsuleProtocolHeader, capsuleProtocolHeaderValue)
return &Request{req: req}, nil
}
// Header returns the HTTP header fields sent with the CONNECT-IP request.
// Callers may add custom headers before dialing.
func (r *Request) Header() http.Header { return r.req.Header }
func (r *Request) httpRequest() *http.Request { return r.req }
// ProxyRequest is the parsed CONNECT-IP request returned from ParseProxyRequest.
// It currently doesn't have any fields, since connect-ip-go doesn't support IP flow forwarding.
type ProxyRequest struct{}
// ProxyRequestParseError is returned from ParseProxyRequest if parsing the CONNECT-IP request fails.
// It is recommended that the request is rejected with the corresponding HTTP status code.
type ProxyRequestParseError struct {
HTTPStatus int
Err error
}
func (e *ProxyRequestParseError) Error() string { return e.Err.Error() }
func (e *ProxyRequestParseError) Unwrap() error { return e.Err }
// ParseProxyRequest parses a CONNECT-IP request.
// The template is the URI template that clients will use to configure this proxy.
func ParseProxyRequest(r *http.Request, template *uritemplate.Template) (*ProxyRequest, error) {
if len(template.Varnames()) > 0 {
return nil, errors.New("connect-ip-go currently does not support IP flow forwarding")
}
u, err := url.Parse(template.Raw())
if err != nil {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusInternalServerError,
Err: fmt.Errorf("failed to parse template: %w", err),
}
}
if r.Method != http.MethodConnect {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusMethodNotAllowed,
Err: fmt.Errorf("expected CONNECT request, got %s", r.Method),
}
}
if r.Proto != requestProtocol {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusNotImplemented,
Err: fmt.Errorf("unexpected protocol: %s", r.Proto),
}
}
if r.Host != u.Host {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("host in :authority (%s) does not match template host (%s)", r.Host, u.Host),
}
}
capsuleHeaderValues, ok := r.Header[http3.CapsuleProtocolHeader]
if !ok {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("missing Capsule-Protocol header"),
}
}
item, err := httpsfv.UnmarshalItem(capsuleHeaderValues)
if err != nil {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("invalid capsule header value: %s", capsuleHeaderValues),
}
}
if v, ok := item.Value.(bool); !ok {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("incorrect capsule header value type: %s", reflect.TypeOf(item.Value)),
}
} else if !v {
return nil, &ProxyRequestParseError{
HTTPStatus: http.StatusBadRequest,
Err: fmt.Errorf("incorrect capsule header value: %t", item.Value),
}
}
return &ProxyRequest{}, nil
}