This commit is contained in:
xteamlyer
2026-04-26 23:22:13 +03:00
committed by Alexey71
parent 1f29e12cf0
commit 677c32bba1
3 changed files with 134 additions and 161 deletions
+52 -17
View File
@@ -2,8 +2,26 @@ name: build
on: on:
push: push:
tags: workflow_dispatch:
- 'v*' inputs:
create_release:
description: 'Create release? (yes/no)'
required: true
default: 'no'
type: choice
options:
- 'yes'
- 'no'
tag_name:
description: 'Release tag name (e.g., v1.2.3)'
required: false
default: ''
type: string
release_name:
description: 'Release name (optional, defaults to tag name)'
required: false
type: string
default: ''
jobs: jobs:
build: build:
@@ -12,31 +30,48 @@ jobs:
contents: write contents: write
steps: steps:
- - name: Checkout
name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
-
name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: 'stable' go-version: "stable"
-
name: Read tag - name: Build
id: tag
run: echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
-
name: Build
run: >- run: >-
make -j $(nproc) allplus make -j $(nproc) allplus
NDK_CC_ARM64="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang" NDK_CC_ARM64="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang"
NDK_CC_ARM="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang" NDK_CC_ARM="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang"
VERSION=${{steps.tag.outputs.tag}} VERSION=v1.17.1
-
name: Release - name: Upload artifact for arm64
uses: actions/upload-artifact@v7
with:
path: bin/opera-proxy.android-arm64
archive: false
- name: Upload artifact for win amd64
uses: actions/upload-artifact@v7
with:
path: bin/opera-proxy.windows-amd64.exe
archive: false
- name: Upload all artifact
uses: actions/upload-artifact@v7
with:
path: bin/*
- name: Create Release
if: github.event_name == 'workflow_dispatch' && github.event.inputs.create_release == 'yes'
uses: softprops/action-gh-release@v3 uses: softprops/action-gh-release@v3
with: with:
files: bin/* tag_name: ${{ github.event.inputs.tag_name }}
name: ${{ github.event.inputs.release_name || github.event.inputs.tag_name }}
files: bin/**/*
fail_on_unmatched_files: true fail_on_unmatched_files: true
generate_release_notes: true generate_release_notes: true
draft: false
prerelease: false
+52 -67
View File
@@ -120,9 +120,6 @@ type CLIArgs struct {
proxy string proxy string
apiLogin string apiLogin string
apiPassword string apiPassword string
// randomAPICreds: when true, ignore --api-login/--api-password and
// generate a fresh cryptographically-random credential pair on every run.
randomAPICreds bool
apiAddress string apiAddress string
apiClientType string apiClientType string
apiClientVersion string apiClientVersion string
@@ -168,7 +165,7 @@ func parse_args() *CLIArgs {
flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity "+ flag.IntVar(&args.verbosity, "verbosity", 20, "logging verbosity "+
"(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)") "(10 - debug, 20 - info, 30 - warning, 40 - error, 50 - critical)")
flag.DurationVar(&args.timeout, "timeout", DEFAULT_TIMEOUT, flag.DurationVar(&args.timeout, "timeout", DEFAULT_TIMEOUT,
"timeout for network operations (default raised to 30s for better API reliability)") "timeout for network operations")
flag.BoolVar(&args.showVersion, "version", false, "show program version and exit") flag.BoolVar(&args.showVersion, "version", false, "show program version and exit")
flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. "+ flag.StringVar(&args.proxy, "proxy", "", "sets base proxy to use for all dial-outs. "+
"Format: <http|https|socks5|socks5h>://[login:password@]host[:port] "+ "Format: <http|https|socks5|socks5h>://[login:password@]host[:port] "+
@@ -176,13 +173,8 @@ func parse_args() *CLIArgs {
flag.StringVar(&args.apiClientVersion, "api-client-version", se.DefaultSESettings.ClientVersion, "client version reported to SurfEasy API") flag.StringVar(&args.apiClientVersion, "api-client-version", se.DefaultSESettings.ClientVersion, "client version reported to SurfEasy API")
flag.StringVar(&args.apiClientType, "api-client-type", se.DefaultSESettings.ClientType, "client type reported to SurfEasy API") flag.StringVar(&args.apiClientType, "api-client-type", se.DefaultSESettings.ClientType, "client type reported to SurfEasy API")
flag.StringVar(&args.apiUserAgent, "api-user-agent", se.DefaultSESettings.UserAgent, "user agent reported to SurfEasy API") flag.StringVar(&args.apiUserAgent, "api-user-agent", se.DefaultSESettings.UserAgent, "user agent reported to SurfEasy API")
flag.StringVar(&args.apiLogin, "api-login", "se0316", flag.StringVar(&args.apiLogin, "api-login", "se0316", "SurfEasy API login")
"SurfEasy API login (ignored when -random-api-creds is set)") flag.StringVar(&args.apiPassword, "api-password", "SILrMEPBmJuhomxWkfm3JalqHX2Eheg1YhlEZiMh8II", "SurfEasy API password")
flag.StringVar(&args.apiPassword, "api-password", "SILrMEPBmJuhomxWkfm3JalqHX2Eheg1YhlEZiMh8II",
"SurfEasy API password (ignored when -random-api-creds is set)")
flag.BoolVar(&args.randomAPICreds, "random-api-creds", false,
"generate a random API login/password pair on every run instead of using -api-login/-api-password; "+
"reduces fingerprinting and avoids credential-reuse bans")
flag.StringVar(&args.apiAddress, "api-address", "", fmt.Sprintf("override IP address of %s", API_DOMAIN)) flag.StringVar(&args.apiAddress, "api-address", "", fmt.Sprintf("override IP address of %s", API_DOMAIN))
flag.StringVar(&args.apiProxy, "api-proxy", "", "additional proxy server used to access SurfEasy API") flag.StringVar(&args.apiProxy, "api-proxy", "", "additional proxy server used to access SurfEasy API")
flag.Var(args.bootstrapDNS, "bootstrap-dns", flag.Var(args.bootstrapDNS, "bootstrap-dns",
@@ -198,7 +190,7 @@ func parse_args() *CLIArgs {
flag.StringVar(&args.overrideProxyAddress, "override-proxy-address", "", "use fixed proxy address instead of server address returned by SurfEasy API") flag.StringVar(&args.overrideProxyAddress, "override-proxy-address", "", "use fixed proxy address instead of server address returned by SurfEasy API")
flag.Var(&args.serverSelection, "server-selection", "server selection policy (first/random/fastest)") flag.Var(&args.serverSelection, "server-selection", "server selection policy (first/random/fastest)")
flag.DurationVar(&args.serverSelectionTimeout, "server-selection-timeout", DEFAULT_SERVER_SELECTION_TIMEOUT, flag.DurationVar(&args.serverSelectionTimeout, "server-selection-timeout", DEFAULT_SERVER_SELECTION_TIMEOUT,
"timeout given for server selection function to produce result (default raised to 60s)") "timeout given for server selection function to produce result")
flag.StringVar(&args.serverSelectionTestURL, "server-selection-test-url", flag.StringVar(&args.serverSelectionTestURL, "server-selection-test-url",
"https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js", "https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js",
"URL used for download benchmark by fastest server selection policy") "URL used for download benchmark by fastest server selection policy")
@@ -241,6 +233,40 @@ func buildAPITransport(
} }
} }
// buildCAPool constructs the x509 cert pool used for all TLS verification.
// When -cafile is given, only that file is loaded (useful for custom/corporate CAs).
// Otherwise the bundled Mozilla NSS root store is used, which includes all
// major roots and supports AddCertWithConstraint for name-constrained CAs —
// strictly better than a plain PEM file.
func buildCAPool(caFile string, logger *clog.CondLogger) (*x509.CertPool, int) {
pool := x509.NewCertPool()
if caFile != "" {
certs, err := os.ReadFile(caFile)
if err != nil {
logger.Error("Can't load CA file: %v", err)
return nil, 15
}
if ok := pool.AppendCertsFromPEM(certs); !ok {
logger.Error("Can't load certificates from CA file")
return nil, 15
}
return pool, 0
}
for c := range bundle.Roots() {
cert, err := x509.ParseCertificate(c.Certificate)
if err != nil {
logger.Error("Unable to parse bundled certificate: %v", err)
return nil, 15
}
if c.Constraint == nil {
pool.AddCert(cert)
} else {
pool.AddCertWithConstraint(cert, c.Constraint)
}
}
return pool, 0
}
func run() int { func run() int {
args := parse_args() args := parse_args()
if args.showVersion { if args.showVersion {
@@ -267,31 +293,9 @@ func run() int {
KeepAlive: 30 * time.Second, KeepAlive: 30 * time.Second,
} }
caPool := x509.NewCertPool() caPool, exitCode := buildCAPool(args.caFile, mainLogger)
if args.caFile != "" { if exitCode != 0 {
// os.ReadFile replaces deprecated ioutil.ReadFile return exitCode
certs, err := os.ReadFile(args.caFile)
if err != nil {
mainLogger.Error("Can't load CA file: %v", err)
return 15
}
if ok := caPool.AppendCertsFromPEM(certs); !ok {
mainLogger.Error("Can't load certificates from CA file")
return 15
}
} else {
for c := range bundle.Roots() {
cert, err := x509.ParseCertificate(c.Certificate)
if err != nil {
mainLogger.Error("Unable to parse bundled certificate: %v", err)
return 15
}
if c.Constraint == nil {
caPool.AddCert(cert)
} else {
caPool.AddCertWithConstraint(cert, c.Constraint)
}
}
} }
xproxy.RegisterDialerType("http", proxyFromURLWrapper) xproxy.RegisterDialerType("http", proxyFromURLWrapper)
@@ -314,12 +318,12 @@ func run() int {
if args.apiProxy != "" { if args.apiProxy != "" {
apiProxyURL, err := url.Parse(args.apiProxy) apiProxyURL, err := url.Parse(args.apiProxy)
if err != nil { if err != nil {
mainLogger.Critical("Unable to parse base proxy URL: %v", err) mainLogger.Critical("Unable to parse api-proxy URL: %v", err)
return 6 return 6
} }
pxDialer, err := xproxy.FromURL(apiProxyURL, seclientDialer) pxDialer, err := xproxy.FromURL(apiProxyURL, seclientDialer)
if err != nil { if err != nil {
mainLogger.Critical("Unable to instantiate base proxy dialer: %v", err) mainLogger.Critical("Unable to instantiate api-proxy dialer: %v", err)
return 7 return 7
} }
seclientDialer = pxDialer.(dialer.ContextDialer) seclientDialer = pxDialer.(dialer.ContextDialer)
@@ -336,27 +340,16 @@ func run() int {
seclientDialer = dialer.NewResolvingDialer(res, seclientDialer) seclientDialer = dialer.NewResolvingDialer(res, seclientDialer)
} }
// Dialing w/o SNI, receiving self-signed certificate, so skip verification. // TLS config for the API connection: SNI suppressed (or faked), cert
// Either way we validate the certificate of the actual proxy server. // verification is skipped at the TLS layer because the API endpoint uses
// a self-signed cert — actual peer verification happens in VerifyConnection
// inside ProxyDialer for the proxy connections.
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
ServerName: args.fakeSNI, ServerName: args.fakeSNI,
InsecureSkipVerify: true, InsecureSkipVerify: true,
} }
// Resolve which API credentials to use. seclient, err := se.NewSEClient(args.apiLogin, args.apiPassword, buildAPITransport(
apiLogin := args.apiLogin
apiPassword := args.apiPassword
if args.randomAPICreds {
var err error
apiLogin, apiPassword, err = se.GenerateRandomAPICreds()
if err != nil {
mainLogger.Critical("Failed to generate random API credentials: %v", err)
return 17
}
mainLogger.Info("Using randomly generated API credentials (login prefix: %.8s...)", apiLogin)
}
seclient, err := se.NewSEClient(apiLogin, apiPassword, buildAPITransport(
seclientDialer.DialContext, seclientDialer.DialContext,
func(ctx context.Context, network, addr string) (net.Conn, error) { func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := seclientDialer.DialContext(ctx, network, addr) conn, err := seclientDialer.DialContext(ctx, network, addr)
@@ -460,9 +453,7 @@ func run() int {
ss = dialer.NewFastestServerSelectionFunc( ss = dialer.NewFastestServerSelectionFunc(
args.serverSelectionTestURL, args.serverSelectionTestURL,
args.serverSelectionDLLimit, args.serverSelectionDLLimit,
&tls.Config{ &tls.Config{RootCAs: caPool},
RootCAs: caPool,
},
) )
default: default:
panic("unhandled server selection value got past parsing") panic("unhandled server selection value got past parsing")
@@ -497,8 +488,7 @@ func run() int {
mainLogger.Info("Refreshing login...") mainLogger.Info("Refreshing login...")
reqCtx, cl := context.WithTimeout(ctx, args.timeout) reqCtx, cl := context.WithTimeout(ctx, args.timeout)
defer cl() defer cl()
err := seclient.Login(reqCtx) if err := seclient.Login(reqCtx); err != nil {
if err != nil {
mainLogger.Error("Login refresh failed: %v", err) mainLogger.Error("Login refresh failed: %v", err)
return err return err
} }
@@ -507,8 +497,7 @@ func run() int {
mainLogger.Info("Refreshing device password...") mainLogger.Info("Refreshing device password...")
reqCtx, cl = context.WithTimeout(ctx, args.timeout) reqCtx, cl = context.WithTimeout(ctx, args.timeout)
defer cl() defer cl()
err = seclient.DeviceGeneratePassword(reqCtx) if err := seclient.DeviceGeneratePassword(reqCtx); err != nil {
if err != nil {
mainLogger.Error("Device password refresh failed: %v", err) mainLogger.Error("Device password refresh failed: %v", err)
return err return err
} }
@@ -547,7 +536,6 @@ func printCountries(try func(string, func() error) error, logger *clog.CondLogge
if err != nil { if err != nil {
return 11 return 11
} }
wr := csv.NewWriter(os.Stdout) wr := csv.NewWriter(os.Stdout)
defer wr.Flush() defer wr.Flush()
wr.Write([]string{"country code", "country name"}) wr.Write([]string{"country code", "country name"})
@@ -591,10 +579,7 @@ func dpExport(ips []se.SEIPEntry, seclient *se.SEClient, sni string) int {
u := url.URL{ u := url.URL{
Scheme: "https", Scheme: "https",
User: creds, User: creds,
Host: net.JoinHostPort( Host: net.JoinHostPort(ip.IP, strconv.Itoa(int(ip.Ports[0]))),
ip.IP,
strconv.Itoa(int(ip.Ports[0])),
),
RawQuery: url.Values{ RawQuery: url.Values{
"sni": []string{sni}, "sni": []string{sni},
"peername": []string{fmt.Sprintf("%s%d.%s", strings.ToLower(ip.Geo.CountryCode), i, PROXY_SUFFIX)}, "peername": []string{fmt.Sprintf("%s%d.%s", strings.ToLower(ip.Geo.CountryCode), i, PROXY_SUFFIX)},
+25 -72
View File
@@ -19,10 +19,6 @@ const (
ANON_PASSWORD_BYTES = 20 ANON_PASSWORD_BYTES = 20
DEVICE_ID_BYTES = 20 DEVICE_ID_BYTES = 20
READ_LIMIT int64 = 128 * 1024 READ_LIMIT int64 = 128 * 1024
// Byte length for randomly generated API credential components.
RANDOM_API_LOGIN_BYTES = 16
RANDOM_API_PASSWORD_BYTES = 24
) )
type SEEndpoints struct { type SEEndpoints struct {
@@ -76,24 +72,10 @@ type SEClient struct {
type StrKV map[string]string type StrKV map[string]string
// GenerateRandomAPICreds returns a cryptographically-random (login, password) pair // NewSEClient instantiates a SurfEasy client.
// suitable for use as SurfEasy digest-auth credentials. Call this instead of // apiUsername/apiSecret are the application-level Digest Auth credentials
// using the hardcoded defaults when -random-api-creds is set. // embedded in every Opera client — they are NOT per-user and must not be randomised.
func GenerateRandomAPICreds() (login, password string, err error) { // transport may be nil (uses http.DefaultTransport).
rng := rand.New(RandomSource)
login, err = randomCapitalHexString(rng, RANDOM_API_LOGIN_BYTES)
if err != nil {
return "", "", fmt.Errorf("generate random api login: %w", err)
}
password, err = randomCapitalHexString(rng, RANDOM_API_PASSWORD_BYTES)
if err != nil {
return "", "", fmt.Errorf("generate random api password: %w", err)
}
return login, password, nil
}
// NewSEClient instantiates a SurfEasy client with default settings and given API keys.
// Optional transport parameter allows overriding the HTTP transport used for API calls.
func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*SEClient, error) { func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*SEClient, error) {
if transport == nil { if transport == nil {
transport = http.DefaultTransport transport = http.DefaultTransport
@@ -101,7 +83,7 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S
rng := rand.New(RandomSource) rng := rand.New(RandomSource)
device_id, err := randomCapitalHexString(rng, DEVICE_ID_BYTES) deviceID, err := randomCapitalHexString(rng, DEVICE_ID_BYTES)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -111,17 +93,15 @@ func NewSEClient(apiUsername, apiSecret string, transport http.RoundTripper) (*S
return nil, err return nil, err
} }
res := &SEClient{ return &SEClient{
httpClient: &http.Client{ httpClient: &http.Client{
Jar: jar, Jar: jar,
Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport), Transport: dac.NewDigestTransport(apiUsername, apiSecret, transport),
}, },
Settings: DefaultSESettings, Settings: DefaultSESettings,
rng: rng, rng: rng,
DeviceID: device_id, DeviceID: deviceID,
} }, nil
return res, nil
} }
func (c *SEClient) ResetCookies() error { func (c *SEClient) ResetCookies() error {
@@ -142,7 +122,8 @@ func (c *SEClient) AnonRegister(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
// Each run generates a fresh random subscriber identity — this is the
// actual anonymisation layer. The API-level credentials above are fixed.
c.SubscriberEmail = fmt.Sprintf("%s@%s.best.vpn", localPart, c.Settings.ClientType) c.SubscriberEmail = fmt.Sprintf("%s@%s.best.vpn", localPart, c.Settings.ClientType)
c.SubscriberPassword = capitalHexSHA1(c.SubscriberEmail) c.SubscriberPassword = capitalHexSHA1(c.SubscriberEmail)
@@ -156,22 +137,19 @@ func (c *SEClient) Register(ctx context.Context) error {
} }
func (c *SEClient) register(ctx context.Context) error { func (c *SEClient) register(ctx context.Context) error {
err := c.resetCookies() if err := c.resetCookies(); err != nil {
if err != nil {
return err return err
} }
var regRes SERegisterSubscriberResponse var regRes SERegisterSubscriberResponse
err = c.rpcCall(ctx, c.Settings.Endpoints.RegisterSubscriber, StrKV{ err := c.rpcCall(ctx, c.Settings.Endpoints.RegisterSubscriber, StrKV{
"email": c.SubscriberEmail, "email": c.SubscriberEmail,
"password": c.SubscriberPassword, "password": c.SubscriberPassword,
}, &regRes) }, &regRes)
if err != nil { if err != nil {
return err return err
} }
if regRes.Status.Code != SE_STATUS_OK { if regRes.Status.Code != SE_STATUS_OK {
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"", return fmt.Errorf("API responded with error message: code=%d, msg=%q",
regRes.Status.Code, regRes.Status.Message) regRes.Status.Code, regRes.Status.Message)
} }
return nil return nil
@@ -190,12 +168,10 @@ func (c *SEClient) RegisterDevice(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
if regRes.Status.Code != SE_STATUS_OK { if regRes.Status.Code != SE_STATUS_OK {
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"", return fmt.Errorf("API responded with error message: code=%d, msg=%q",
regRes.Status.Code, regRes.Status.Message) regRes.Status.Code, regRes.Status.Message)
} }
c.AssignedDeviceID = regRes.Data.DeviceID c.AssignedDeviceID = regRes.Data.DeviceID
c.DevicePassword = regRes.Data.DevicePassword c.DevicePassword = regRes.Data.DevicePassword
c.AssignedDeviceIDHash = capitalHexSHA1(regRes.Data.DeviceID) c.AssignedDeviceIDHash = capitalHexSHA1(regRes.Data.DeviceID)
@@ -213,12 +189,10 @@ func (c *SEClient) GeoList(ctx context.Context) ([]SEGeoEntry, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if geoListRes.Status.Code != SE_STATUS_OK { if geoListRes.Status.Code != SE_STATUS_OK {
return nil, fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"", return nil, fmt.Errorf("API responded with error message: code=%d, msg=%q",
geoListRes.Status.Code, geoListRes.Status.Message) geoListRes.Status.Code, geoListRes.Status.Message)
} }
return geoListRes.Data.Geos, nil return geoListRes.Data.Geos, nil
} }
@@ -234,12 +208,10 @@ func (c *SEClient) Discover(ctx context.Context, requestedGeo string) ([]SEIPEnt
if err != nil { if err != nil {
return nil, err return nil, err
} }
if discoverRes.Status.Code != SE_STATUS_OK { if discoverRes.Status.Code != SE_STATUS_OK {
return nil, fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"", return nil, fmt.Errorf("API responded with error message: code=%d, msg=%q",
discoverRes.Status.Code, discoverRes.Status.Message) discoverRes.Status.Code, discoverRes.Status.Message)
} }
return discoverRes.Data.IPs, nil return discoverRes.Data.IPs, nil
} }
@@ -247,13 +219,11 @@ func (c *SEClient) Login(ctx context.Context) error {
c.Mux.Lock() c.Mux.Lock()
defer c.Mux.Unlock() defer c.Mux.Unlock()
err := c.resetCookies() if err := c.resetCookies(); err != nil {
if err != nil {
return err return err
} }
var loginRes SESubscriberLoginResponse var loginRes SESubscriberLoginResponse
err = c.rpcCall(ctx, c.Settings.Endpoints.SubscriberLogin, StrKV{ err := c.rpcCall(ctx, c.Settings.Endpoints.SubscriberLogin, StrKV{
"login": c.SubscriberEmail, "login": c.SubscriberEmail,
"password": c.SubscriberPassword, "password": c.SubscriberPassword,
"client_type": c.Settings.ClientType, "client_type": c.Settings.ClientType,
@@ -261,9 +231,8 @@ func (c *SEClient) Login(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
if loginRes.Status.Code != SE_STATUS_OK { if loginRes.Status.Code != SE_STATUS_OK {
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"", return fmt.Errorf("API responded with error message: code=%d, msg=%q",
loginRes.Status.Code, loginRes.Status.Message) loginRes.Status.Code, loginRes.Status.Message)
} }
return nil return nil
@@ -280,12 +249,10 @@ func (c *SEClient) DeviceGeneratePassword(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
if genRes.Status.Code != SE_STATUS_OK { if genRes.Status.Code != SE_STATUS_OK {
return fmt.Errorf("API responded with error message: code=%d, msg=\"%s\"", return fmt.Errorf("API responded with error message: code=%d, msg=%q",
genRes.Status.Code, genRes.Status.Message) genRes.Status.Code, genRes.Status.Message)
} }
c.DevicePassword = genRes.Data.DevicePassword c.DevicePassword = genRes.Data.DevicePassword
return nil return nil
} }
@@ -293,7 +260,6 @@ func (c *SEClient) DeviceGeneratePassword(ctx context.Context) error {
func (c *SEClient) GetProxyCredentials() (string, string) { func (c *SEClient) GetProxyCredentials() (string, string) {
c.Mux.Lock() c.Mux.Lock()
defer c.Mux.Unlock() defer c.Mux.Unlock()
return c.AssignedDeviceIDHash, c.DevicePassword return c.AssignedDeviceIDHash, c.DevicePassword
} }
@@ -306,7 +272,6 @@ func (c *SEClient) populateRequest(req *http.Request) {
func (c *SEClient) RpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error { func (c *SEClient) RpcCall(ctx context.Context, endpoint string, params map[string]string, res interface{}) error {
c.Mux.Lock() c.Mux.Lock()
defer c.Mux.Unlock() defer c.Mux.Unlock()
return c.rpcCall(ctx, endpoint, params, res) return c.rpcCall(ctx, endpoint, params, res)
} }
@@ -315,12 +280,8 @@ func (c *SEClient) rpcCall(ctx context.Context, endpoint string, params map[stri
for k, v := range params { for k, v := range params {
input[k] = []string{v} input[k] = []string{v}
} }
req, err := http.NewRequestWithContext( req, err := http.NewRequestWithContext(ctx, "POST", endpoint,
ctx, strings.NewReader(input.Encode()))
"POST",
endpoint,
strings.NewReader(input.Encode()),
)
if err != nil { if err != nil {
return err return err
} }
@@ -334,25 +295,17 @@ func (c *SEClient) rpcCall(ctx context.Context, endpoint string, params map[stri
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
cleanupBody(resp.Body)
return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header) return fmt.Errorf("bad http status: %s, headers: %#v", resp.Status, resp.Header)
} }
decoder := json.NewDecoder(resp.Body) err = json.NewDecoder(resp.Body).Decode(res)
err = decoder.Decode(res)
cleanupBody(resp.Body) cleanupBody(resp.Body)
if err != nil {
return err return err
} }
return nil
}
// cleanupBody drains and closes an HTTP response body to allow connection reuse. // cleanupBody drains and closes an HTTP response body to allow connection reuse.
func cleanupBody(body io.ReadCloser) { func cleanupBody(body io.ReadCloser) {
io.Copy(io.Discard, &io.LimitedReader{ io.Copy(io.Discard, &io.LimitedReader{R: body, N: READ_LIMIT})
R: body,
N: READ_LIMIT,
})
body.Close() body.Close()
} }