mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-11-02 16:36:58 +00:00
XHTTP server: Accept OPTIONS as uplinkHTTPMethod (#7290)
https://github.com/XTLS/Xray-core/pull/7290#issuecomment-6505845186
This commit is contained in:
@@ -126,7 +126,9 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
|
||||
h.config.ApplyXPaddingToResponse(writer, config)
|
||||
|
||||
if request.Method == "OPTIONS" {
|
||||
// A real CORS preflight always carries Access-Control-Request-Method. An OPTIONS request without it is an
|
||||
// uplink request (uplinkHTTPMethod: "OPTIONS") and must go through the normal handling below.
|
||||
if request.Method == "OPTIONS" && request.Header.Get("Access-Control-Request-Method") != "" {
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -465,3 +465,137 @@ func Test_maxUpload(t *testing.T) {
|
||||
|
||||
common.Must(listen.Close())
|
||||
}
|
||||
|
||||
func newPacketUpEchoServer(t *testing.T, serverCfg *Config) net.Port {
|
||||
t.Helper()
|
||||
port := tcp.PickPort()
|
||||
listen, err := ListenXH(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: serverCfg,
|
||||
}, func(conn stat.Connection) {
|
||||
go func(c stat.Connection) {
|
||||
defer c.Close()
|
||||
var b [5]byte
|
||||
c.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
if _, err := io.ReadFull(c, b[:]); err != nil {
|
||||
return
|
||||
}
|
||||
common.Must2(c.Write(append([]byte("echo:"), b[:]...)))
|
||||
}(conn)
|
||||
})
|
||||
common.Must(err)
|
||||
t.Cleanup(func() { listen.Close() })
|
||||
return port
|
||||
}
|
||||
|
||||
// uplinkEcho fails instead of hanging when the uplink never reaches the server.
|
||||
func uplinkEcho(t *testing.T, mode, method, transport string) error {
|
||||
t.Helper()
|
||||
res := make(chan error, 1)
|
||||
go func() { res <- uplinkEchoOnce(mode, method, transport) }()
|
||||
select {
|
||||
case err := <-res:
|
||||
return err
|
||||
case <-time.After(10 * time.Second):
|
||||
return fmt.Errorf("timed out")
|
||||
}
|
||||
}
|
||||
|
||||
func uplinkEchoOnce(mode, method, transport string) error {
|
||||
port := tcp.PickPort()
|
||||
serverCfg := &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: &Config{Path: "/sh", Mode: mode},
|
||||
}
|
||||
clientCfg := &internet.MemoryStreamConfig{
|
||||
ProtocolName: "splithttp",
|
||||
ProtocolSettings: &Config{Path: "/sh", Mode: mode, UplinkHTTPMethod: method},
|
||||
}
|
||||
if transport == "tls" {
|
||||
ct, ctHash := cert.MustGenerate(nil, cert.CommonName("localhost"))
|
||||
sec := &tls.Config{Certificate: []*tls.Certificate{tls.ParseCertificate(ct)}, PinnedPeerCertSha256: [][]byte{ctHash[:]}}
|
||||
serverCfg.SecurityType, serverCfg.SecuritySettings = "tls", sec
|
||||
clientCfg.SecurityType, clientCfg.SecuritySettings = "tls", sec
|
||||
}
|
||||
listen, err := ListenXH(context.Background(), net.LocalHostIP, port, serverCfg, func(conn stat.Connection) {
|
||||
go func(c stat.Connection) {
|
||||
defer c.Close()
|
||||
var b [5]byte
|
||||
c.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
if _, err := io.ReadFull(c, b[:]); err != nil {
|
||||
return
|
||||
}
|
||||
common.Must2(c.Write(append([]byte("echo:"), b[:]...)))
|
||||
}(conn)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer listen.Close()
|
||||
|
||||
conn, err := Dial(context.Background(), net.TCPDestination(net.DomainAddress("localhost"), port), clientCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
if _, err = conn.Write([]byte("hello")); err != nil {
|
||||
return err
|
||||
}
|
||||
var b [1024]byte
|
||||
conn.SetReadDeadline(time.Now().Add(4 * time.Second))
|
||||
n, err := io.ReadFull(conn, b[:len("echo:hello")])
|
||||
if err != nil || string(b[:n]) != "echo:hello" {
|
||||
return fmt.Errorf("got %q: %v", b[:n], err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OPTIONS must carry uplink data exactly like POST does, in every mode and over every transport.
|
||||
func Test_UplinkHTTPMethodOptions(t *testing.T) {
|
||||
for _, transport := range []string{"plain", "tls"} {
|
||||
for _, mode := range []string{"packet-up", "stream-up", "stream-one", "auto"} {
|
||||
t.Run(transport+"/"+mode, func(t *testing.T) {
|
||||
if transport == "tls" && runtime.GOARCH == "arm64" {
|
||||
t.Skip("TLS tests are skipped on arm64, like the other TLS tests in this package")
|
||||
}
|
||||
if err := uplinkEcho(t, mode, "POST", transport); err != nil {
|
||||
t.Skipf("POST baseline does not work in this setup: %v", err)
|
||||
}
|
||||
for _, method := range []string{"PUT", "OPTIONS"} {
|
||||
if err := uplinkEcho(t, mode, method, transport); err != nil {
|
||||
t.Errorf("%s: %v", method, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CORSPreflightStillAnswered(t *testing.T) {
|
||||
port := newPacketUpEchoServer(t, &Config{Path: "/sh", Mode: "packet-up"})
|
||||
req, _ := http.NewRequest("OPTIONS", fmt.Sprintf("http://127.0.0.1:%d/sh/", port), nil)
|
||||
req.Header.Set("Origin", "https://example.com")
|
||||
req.Header.Set("Access-Control-Request-Method", "POST")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
common.Must(err)
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("preflight status = %d", resp.StatusCode)
|
||||
}
|
||||
if got := resp.Header.Get("Access-Control-Allow-Methods"); got != "POST" {
|
||||
t.Errorf("Allow-Methods = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ServerMaxHeaderBytes(t *testing.T) {
|
||||
port := newPacketUpEchoServer(t, &Config{Path: "/sh", Mode: "packet-up", ServerMaxHeaderBytes: 2048})
|
||||
big := bytes.Repeat([]byte("a"), 8192)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://127.0.0.1:%d/sh/", port), nil)
|
||||
req.Header.Set("X-Big", string(big))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
common.Must(err)
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusRequestHeaderFieldsTooLarge {
|
||||
t.Fatalf("oversized header status = %d, want 431", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user