mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-31 18:21:47 +00:00
143 lines
3.7 KiB
Go
143 lines
3.7 KiB
Go
package external
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common/buf"
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/utils"
|
|
"github.com/xtls/xray-core/main/confloader"
|
|
)
|
|
|
|
func ConfigLoader(arg string) (out io.Reader, err error) {
|
|
var data []byte
|
|
switch {
|
|
case strings.HasPrefix(arg, "http+unix://"):
|
|
errors.PrintDeprecatedFeatureWarning(`"http+unix://" prefix`, `direct Unix socket path (e.g. /path/socket.sock:/api or @abstract:/api)`)
|
|
data, err = FetchHTTPContent(httpUnixToCanonical(arg))
|
|
|
|
case isRemoteSource(arg):
|
|
data, err = FetchHTTPContent(arg)
|
|
|
|
case arg == "stdin:":
|
|
data, err = io.ReadAll(os.Stdin)
|
|
|
|
default:
|
|
data, err = os.ReadFile(arg)
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
out = bytes.NewBuffer(data)
|
|
return
|
|
}
|
|
|
|
// FetchHTTPContent issues an HTTP GET against either a regular HTTP(S) URL
|
|
// or a Unix socket HTTP endpoint.
|
|
//
|
|
// http(s)://host/api regular HTTP(S)
|
|
// /path/to/socket.sock[:/api] filesystem socket
|
|
// @abstract[:/api] abstract socket (Linux/Android)
|
|
// @@padded[:/api] padded abstract socket (HAProxy compat)
|
|
//
|
|
// When the ":/" separator is omitted on a socket target, the request is
|
|
// made to "/".
|
|
func FetchHTTPContent(target string) ([]byte, error) {
|
|
httpURL, socketPath := utils.SplitHTTPUnixURL(target)
|
|
|
|
parsedTarget, err := url.Parse(httpURL)
|
|
if err != nil {
|
|
return nil, errors.New("invalid URL: ", target).Base(err)
|
|
}
|
|
|
|
client := &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
}
|
|
|
|
if socketPath != "" {
|
|
dialAddr := utils.ResolveSocketPath(socketPath)
|
|
client.Transport = &http.Transport{
|
|
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
var d net.Dialer
|
|
return d.DialContext(ctx, "unix", dialAddr)
|
|
},
|
|
}
|
|
}
|
|
|
|
resp, err := client.Do(&http.Request{
|
|
Method: "GET",
|
|
URL: parsedTarget,
|
|
Close: true,
|
|
})
|
|
if err != nil {
|
|
return nil, errors.New("failed to dial to ", target).Base(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
return nil, errors.New("unexpected HTTP status code: ", resp.StatusCode)
|
|
}
|
|
|
|
content, err := buf.ReadAllToBytes(resp.Body)
|
|
if err != nil {
|
|
return nil, errors.New("failed to read HTTP response").Base(err)
|
|
}
|
|
|
|
return content, nil
|
|
}
|
|
|
|
|
|
// isRemoteSource reports whether arg should be fetched via HTTP (regular
|
|
// network or Unix socket) rather than read from the local filesystem.
|
|
// Recognized forms:
|
|
//
|
|
// - http(s)://... regular HTTP(S)
|
|
// - @abstract[:/api] abstract socket (Linux/Android)
|
|
// - /abs/path:/api filesystem socket, explicit HTTP path
|
|
// - /abs/path filesystem socket detected via os.ModeSocket
|
|
func isRemoteSource(arg string) bool {
|
|
if arg == "" {
|
|
return false
|
|
}
|
|
if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") {
|
|
return true
|
|
}
|
|
if arg[0] == '@' {
|
|
return true
|
|
}
|
|
if arg[0] != '/' {
|
|
return false
|
|
}
|
|
if strings.Contains(arg, ":/") {
|
|
return true
|
|
}
|
|
info, err := os.Stat(arg)
|
|
return err == nil && info.Mode()&os.ModeSocket != 0
|
|
}
|
|
|
|
|
|
// httpUnixToCanonical converts the deprecated http+unix:///path/to/socket.sock/api
|
|
// URL into the canonical /path/to/socket.sock:/api form by inserting ":"
|
|
// between the ".sock" extension and the HTTP path. Inputs without a path
|
|
// after ".sock" are returned with just the "http+unix://" prefix stripped.
|
|
func httpUnixToCanonical(target string) string {
|
|
raw := strings.TrimPrefix(target, "http+unix://")
|
|
if i := strings.Index(raw, ".sock/"); i >= 0 {
|
|
raw = raw[:i+5] + ":" + raw[i+5:]
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func init() {
|
|
confloader.EffectiveConfigFileLoader = ConfigLoader
|
|
}
|