external.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package external
  2. import (
  3. "bytes"
  4. "context"
  5. "net"
  6. "io"
  7. "net/http"
  8. "net/url"
  9. "os"
  10. "strings"
  11. "time"
  12. "github.com/xtls/xray-core/common/buf"
  13. "github.com/xtls/xray-core/common/errors"
  14. "github.com/xtls/xray-core/main/confloader"
  15. )
  16. func ConfigLoader(arg string) (out io.Reader, err error) {
  17. var data []byte
  18. switch {
  19. case strings.HasPrefix(arg, "http+unix://"):
  20. data, err = FetchUnixSocketHTTPContent(arg)
  21. case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"):
  22. data, err = FetchHTTPContent(arg)
  23. case arg == "stdin:":
  24. data, err = io.ReadAll(os.Stdin)
  25. default:
  26. data, err = os.ReadFile(arg)
  27. }
  28. if err != nil {
  29. return
  30. }
  31. out = bytes.NewBuffer(data)
  32. return
  33. }
  34. func FetchHTTPContent(target string) ([]byte, error) {
  35. parsedTarget, err := url.Parse(target)
  36. if err != nil {
  37. return nil, errors.New("invalid URL: ", target).Base(err)
  38. }
  39. if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
  40. return nil, errors.New("invalid scheme: ", parsedTarget.Scheme)
  41. }
  42. client := &http.Client{
  43. Timeout: 30 * time.Second,
  44. }
  45. resp, err := client.Do(&http.Request{
  46. Method: "GET",
  47. URL: parsedTarget,
  48. Close: true,
  49. })
  50. if err != nil {
  51. return nil, errors.New("failed to dial to ", target).Base(err)
  52. }
  53. defer resp.Body.Close()
  54. if resp.StatusCode != 200 {
  55. return nil, errors.New("unexpected HTTP status code: ", resp.StatusCode)
  56. }
  57. content, err := buf.ReadAllToBytes(resp.Body)
  58. if err != nil {
  59. return nil, errors.New("failed to read HTTP response").Base(err)
  60. }
  61. return content, nil
  62. }
  63. // Format: http+unix:///path/to/socket.sock/api/endpoint
  64. func FetchUnixSocketHTTPContent(target string) ([]byte, error) {
  65. path := strings.TrimPrefix(target, "http+unix://")
  66. if !strings.HasPrefix(path, "/") {
  67. return nil, errors.New("unix socket path must be absolute")
  68. }
  69. var socketPath, httpPath string
  70. sockIdx := strings.Index(path, ".sock")
  71. if sockIdx != -1 {
  72. socketPath = path[:sockIdx+5]
  73. httpPath = path[sockIdx+5:]
  74. if httpPath == "" {
  75. httpPath = "/"
  76. }
  77. } else {
  78. return nil, errors.New("cannot determine socket path, socket file should have .sock extension")
  79. }
  80. if _, err := os.Stat(socketPath); err != nil {
  81. return nil, errors.New("socket file not found: ", socketPath).Base(err)
  82. }
  83. client := &http.Client{
  84. Timeout: 30 * time.Second,
  85. Transport: &http.Transport{
  86. DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
  87. var d net.Dialer
  88. return d.DialContext(ctx, "unix", socketPath)
  89. },
  90. },
  91. }
  92. defer client.CloseIdleConnections()
  93. resp, err := client.Get("http://localhost" + httpPath)
  94. if err != nil {
  95. return nil, errors.New("failed to fetch from unix socket: ", socketPath).Base(err)
  96. }
  97. defer resp.Body.Close()
  98. if resp.StatusCode != 200 {
  99. return nil, errors.New("unexpected HTTP status code: ", resp.StatusCode)
  100. }
  101. content, err := buf.ReadAllToBytes(resp.Body)
  102. if err != nil {
  103. return nil, errors.New("failed to read response").Base(err)
  104. }
  105. return content, nil
  106. }
  107. func init() {
  108. confloader.EffectiveConfigFileLoader = ConfigLoader
  109. }