132 lines
3.2 KiB
Go
132 lines
3.2 KiB
Go
package tgutil
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/gotd/td/telegram/dcs"
|
|
"github.com/krau/SaveAny-Bot/config"
|
|
"golang.org/x/net/proxy"
|
|
)
|
|
|
|
// httpProxyDialer implements proxy.ContextDialer for HTTP CONNECT proxies
|
|
type httpProxyDialer struct {
|
|
proxyURL *url.URL
|
|
forward proxy.Dialer
|
|
}
|
|
|
|
func (d *httpProxyDialer) Dial(network, addr string) (net.Conn, error) {
|
|
return d.DialContext(context.Background(), network, addr)
|
|
}
|
|
|
|
func (d *httpProxyDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
proxyAddr := d.proxyURL.Host
|
|
if d.proxyURL.Port() == "" {
|
|
if d.proxyURL.Scheme == "https" {
|
|
proxyAddr = net.JoinHostPort(d.proxyURL.Hostname(), "443")
|
|
} else {
|
|
proxyAddr = net.JoinHostPort(d.proxyURL.Hostname(), "80")
|
|
}
|
|
}
|
|
|
|
var conn net.Conn
|
|
var err error
|
|
if ctxDialer, ok := d.forward.(proxy.ContextDialer); ok {
|
|
conn, err = ctxDialer.DialContext(ctx, "tcp", proxyAddr)
|
|
} else {
|
|
conn, err = d.forward.Dial("tcp", proxyAddr)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to connect to proxy: %w", err)
|
|
}
|
|
|
|
// Send CONNECT request
|
|
connectReq := &http.Request{
|
|
Method: "CONNECT",
|
|
URL: &url.URL{Opaque: addr},
|
|
Host: addr,
|
|
Header: make(http.Header),
|
|
}
|
|
|
|
// Add proxy authentication if provided
|
|
if d.proxyURL.User != nil {
|
|
username := d.proxyURL.User.Username()
|
|
password, _ := d.proxyURL.User.Password()
|
|
auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
|
|
connectReq.Header.Set("Proxy-Authorization", "Basic "+auth)
|
|
}
|
|
|
|
if err := connectReq.Write(conn); err != nil {
|
|
conn.Close()
|
|
return nil, fmt.Errorf("failed to write CONNECT request: %w", err)
|
|
}
|
|
|
|
// Read response
|
|
br := bufio.NewReader(conn)
|
|
resp, err := http.ReadResponse(br, connectReq)
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, fmt.Errorf("failed to read CONNECT response: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
conn.Close()
|
|
return nil, fmt.Errorf("proxy CONNECT failed with status: %s", resp.Status)
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func newProxyDialer(proxyUrl string) (proxy.ContextDialer, error) {
|
|
parsedURL, err := url.Parse(proxyUrl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch parsedURL.Scheme {
|
|
case "http", "https":
|
|
return &httpProxyDialer{
|
|
proxyURL: parsedURL,
|
|
forward: proxy.Direct,
|
|
}, nil
|
|
case "socks5", "socks5h":
|
|
dialer, err := proxy.FromURL(parsedURL, proxy.Direct)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return dialer.(proxy.ContextDialer), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported proxy scheme: %s", parsedURL.Scheme)
|
|
}
|
|
}
|
|
|
|
func NewConfigProxyResolver() (dcs.Resolver, error) {
|
|
resolver := dcs.DefaultResolver()
|
|
if config.C().Proxy != "" {
|
|
// global proxy, which has lower priority
|
|
dialer, err := newProxyDialer(config.C().Proxy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resolver = dcs.Plain(dcs.PlainOptions{
|
|
Dial: dialer.DialContext,
|
|
})
|
|
}
|
|
if config.C().Telegram.Proxy.Enable && config.C().Telegram.Proxy.URL != "" {
|
|
dialer, err := newProxyDialer(config.C().Telegram.Proxy.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resolver = dcs.Plain(dcs.PlainOptions{
|
|
Dial: dialer.DialContext,
|
|
})
|
|
}
|
|
return resolver, nil
|
|
}
|