Files
Zakk 21948e1fd7 fix(serverObj): HTTP proxy nodes use the core's http outbound; SSR nodes say they are unsupported
Commit 0baefc45 (2026-05-03) removed the plugin layer under
service/pkg/plugin and the code that started a plugin per node, but
left the HTTP and ShadowsocksR node types producing what that layer
consumed: a socks outbound to 127.0.0.1:<plugin port> plus a plugin
chain nothing reads. Every http/https proxy node and every SSR node
imported since then connected to a closed local port.

HTTP and HTTPS nodes now produce the core's native http outbound with
the credentials and, for https, TLS to the server name; they no longer
ask for a plugin port. The core has no ShadowsocksR outbound, so an SSR
node's configuration returns an "unsupported" error the latency test
and the automatic selection already understand; the node still parses,
displays and exports so nothing is lost from the store.
2026-09-20 09:59:29 +10:00

157 lines
4.0 KiB
Go

package serverObj
import (
"fmt"
"net"
"net/url"
"strconv"
"github.com/v2rayA/v2rayA/kernel/coreObj"
)
func init() {
FromLinkRegister("http", NewHTTP)
FromLinkRegister("https", NewHTTP)
FromLinkRegister("http-proxy", NewHTTP)
FromLinkRegister("https-proxy", NewHTTP)
EmptyRegister("http", func() (ServerObj, error) {
return new(HTTP), nil
})
EmptyRegister("https", func() (ServerObj, error) {
return new(HTTP), nil
})
EmptyRegister("http-proxy", func() (ServerObj, error) {
return new(HTTP), nil
})
EmptyRegister("https-proxy", func() (ServerObj, error) {
return new(HTTP), nil
})
}
type HTTP struct {
Name string `json:"name"`
Server string `json:"server"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Protocol string `json:"protocol"`
}
func NewHTTP(link string) (ServerObj, error) {
return ParseHttpURL(link)
}
func ParseHttpURL(u string) (data *HTTP, err error) {
t, err := url.Parse(u)
if err != nil {
return nil, fmt.Errorf("%w: http proxy link is not a valid URL; expected http-proxy://[user:pass@]host:port", ErrInvalidParameter)
}
// A proxy link is scheme://[user:pass@]host[:port][#name]. Anything with
// a path or query is almost always a subscription URL pasted into the
// wrong box, so name that instead of returning a bare parameter error.
if (t.Path != "" && t.Path != "/") || t.RawQuery != "" {
return nil, fmt.Errorf("%w: http proxy link for %q has a path or query; expected http-proxy://[user:pass@]host:port", ErrInvalidParameter, t.Hostname())
}
// A default port only makes sense with a host to connect to; an empty
// hostname used to be rejected by the port conversion.
if t.Hostname() == "" {
return nil, fmt.Errorf("%w: http proxy link has no server address; expected http-proxy://[user:pass@]host:port", ErrInvalidParameter)
}
// An absent port falls through to the per-scheme default below.
port := 0
if p := t.Port(); p != "" {
if port, err = strconv.Atoi(p); err != nil {
return nil, fmt.Errorf("%w: http proxy link for %q has an invalid port; expected http-proxy://[user:pass@]host:port", ErrInvalidParameter, t.Hostname())
}
}
data = &HTTP{
Name: t.Fragment,
Server: t.Hostname(),
Port: port,
}
if t.User != nil && len(t.User.String()) > 0 {
data.Username = t.User.Username()
data.Password, _ = t.User.Password()
}
switch t.Scheme {
case "https-proxy", "https":
data.Protocol = "https"
if data.Port == 0 {
data.Port = 443
}
case "http-proxy", "http":
data.Protocol = "http"
if data.Port == 0 {
data.Port = 80
}
default:
data.Protocol = t.Scheme
}
return data, nil
}
func (h *HTTP) Configuration(info PriorInfo) (c Configuration, err error) {
server := coreObj.Server{Address: h.Server, Port: h.Port}
if h.Username != "" {
server.Users = []coreObj.OutboundUser{{User: h.Username, Pass: h.Password}}
}
var streamSettings *coreObj.StreamSettings
if h.Protocol == "https" {
streamSettings = &coreObj.StreamSettings{
Security: "tls",
TLSSettings: &coreObj.TLSSettings{ServerName: h.Server},
}
}
return Configuration{
CoreOutbound: coreObj.OutboundObject{
Tag: info.Tag,
Protocol: "http",
Settings: coreObj.Settings{Servers: []coreObj.Server{server}},
StreamSettings: streamSettings,
},
UDPSupport: false,
}, nil
}
func (h *HTTP) ExportToURL() string {
var user *url.Userinfo
if h.Username != "" && h.Password != "" {
user = url.UserPassword(h.Username, h.Password)
}
u := &url.URL{
Scheme: h.Protocol,
User: user,
Host: net.JoinHostPort(h.Server, strconv.Itoa(h.Port)),
Fragment: h.Name,
}
return u.String()
}
func (h *HTTP) NeedPluginPort() bool {
return false
}
func (h *HTTP) ProtoToShow() string {
return h.Protocol
}
func (h *HTTP) GetProtocol() string {
return h.Protocol
}
func (h *HTTP) GetHostname() string {
return h.Server
}
func (h *HTTP) GetPort() int {
return h.Port
}
func (h *HTTP) GetName() string {
return h.Name
}
func (h *HTTP) SetName(name string) {
h.Name = name
}