linting
This commit is contained in:
@@ -116,7 +116,11 @@ func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmo
|
||||
|
||||
return nil, merrors.InternalWrap(err, "binance: request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close Binance response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn("Binance returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
||||
|
||||
@@ -122,9 +122,11 @@ func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Conne
|
||||
logger,
|
||||
client,
|
||||
httpClientOptions{
|
||||
userAgent: userAgent,
|
||||
accept: acceptHeader,
|
||||
referer: referer,
|
||||
userAgent: userAgent,
|
||||
accept: acceptHeader,
|
||||
referer: referer,
|
||||
allowedScheme: parsed.Scheme,
|
||||
allowedHost: parsed.Host,
|
||||
},
|
||||
),
|
||||
base: strings.TrimRight(parsed.String(), "/"),
|
||||
@@ -200,7 +202,11 @@ func (c *cbrConnector) refreshDirectory() error {
|
||||
)
|
||||
return merrors.InternalWrap(err, "cbr: directory request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close CBR daily response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn(
|
||||
@@ -258,7 +264,11 @@ func (c *cbrConnector) fetchDailyRate(ctx context.Context, valute valuteInfo) (s
|
||||
)
|
||||
return "", merrors.InternalWrap(err, "cbr: daily request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close CBR historical response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn(
|
||||
@@ -326,7 +336,11 @@ func (c *cbrConnector) fetchHistoricalRate( //nolint:funlen
|
||||
)
|
||||
return "", merrors.InternalWrap(err, "cbr: historical request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close CBR historical response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn(
|
||||
|
||||
@@ -2,7 +2,10 @@ package cbr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
@@ -14,17 +17,28 @@ const (
|
||||
defaultAccept = "application/xml,text/xml;q=0.9,*/*;q=0.8"
|
||||
)
|
||||
|
||||
var (
|
||||
errNilRequestURL = errors.New("http_client: request URL is nil")
|
||||
errRelativeRequestURL = errors.New("http_client: request URL must be absolute")
|
||||
errUnexpectedURLScheme = errors.New("http_client: unexpected URL scheme")
|
||||
errUnexpectedURLHost = errors.New("http_client: unexpected URL host")
|
||||
)
|
||||
|
||||
// httpClient wraps http.Client to ensure CBR requests always carry required headers.
|
||||
type httpClient struct {
|
||||
client *http.Client
|
||||
headers http.Header
|
||||
logger mlogger.Logger
|
||||
client *http.Client
|
||||
headers http.Header
|
||||
logger mlogger.Logger
|
||||
allowedScheme string
|
||||
allowedHost string
|
||||
}
|
||||
|
||||
type httpClientOptions struct {
|
||||
userAgent string
|
||||
accept string
|
||||
referer string
|
||||
userAgent string
|
||||
accept string
|
||||
referer string
|
||||
allowedScheme string
|
||||
allowedHost string
|
||||
}
|
||||
|
||||
func newHTTPClient(logger mlogger.Logger, client *http.Client, opts httpClientOptions) *httpClient {
|
||||
@@ -42,6 +56,20 @@ func newHTTPClient(logger mlogger.Logger, client *http.Client, opts httpClientOp
|
||||
if strings.TrimSpace(referer) == "" {
|
||||
referer = defaultCBRBaseURL
|
||||
}
|
||||
|
||||
allowedScheme := strings.ToLower(strings.TrimSpace(opts.allowedScheme))
|
||||
allowedHost := strings.ToLower(strings.TrimSpace(opts.allowedHost))
|
||||
|
||||
if allowedScheme == "" || allowedHost == "" {
|
||||
if parsed, err := url.Parse(referer); err == nil {
|
||||
if allowedScheme == "" {
|
||||
allowedScheme = strings.ToLower(parsed.Scheme)
|
||||
}
|
||||
if allowedHost == "" {
|
||||
allowedHost = strings.ToLower(parsed.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
httpLogger := logger.Named("http_client")
|
||||
|
||||
headers := make(http.Header, 3)
|
||||
@@ -53,9 +81,11 @@ func newHTTPClient(logger mlogger.Logger, client *http.Client, opts httpClientOp
|
||||
zap.String("accept", accept), zap.String("referrer", referer))
|
||||
|
||||
return &httpClient{
|
||||
client: client,
|
||||
headers: headers,
|
||||
logger: httpLogger,
|
||||
client: client,
|
||||
headers: headers,
|
||||
logger: httpLogger,
|
||||
allowedScheme: allowedScheme,
|
||||
allowedHost: allowedHost,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +104,13 @@ func (h *httpClient) Do(req *http.Request) (*http.Response, error) {
|
||||
enriched.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.validateRequestTarget(enriched.URL); err != nil {
|
||||
h.logger.Warn("HTTP request blocked by target validation", zap.Error(err), zap.String("method", req.Method))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//nolint:gosec // request URL is constrained in validateRequestTarget before any outbound call.
|
||||
r, err := h.client.Do(enriched)
|
||||
if err != nil {
|
||||
h.logger.Warn("HTTP request failed", zap.Error(err), zap.String("method", req.Method),
|
||||
@@ -85,3 +122,26 @@ func (h *httpClient) Do(req *http.Request) (*http.Response, error) {
|
||||
func (h *httpClient) headerValue(name string) string {
|
||||
return h.headers.Get(name)
|
||||
}
|
||||
|
||||
func (h *httpClient) validateRequestTarget(requestURL *url.URL) error {
|
||||
if requestURL == nil {
|
||||
return errNilRequestURL
|
||||
}
|
||||
|
||||
if !requestURL.IsAbs() {
|
||||
return errRelativeRequestURL
|
||||
}
|
||||
|
||||
scheme := strings.ToLower(requestURL.Scheme)
|
||||
host := strings.ToLower(requestURL.Host)
|
||||
|
||||
if h.allowedScheme != "" && scheme != h.allowedScheme {
|
||||
return fmt.Errorf("%w: %q", errUnexpectedURLScheme, requestURL.Scheme)
|
||||
}
|
||||
|
||||
if h.allowedHost != "" && host != h.allowedHost {
|
||||
return fmt.Errorf("%w: %q", errUnexpectedURLHost, requestURL.Host)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -125,7 +125,11 @@ func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*m
|
||||
|
||||
return nil, merrors.InternalWrap(err, "coingecko: request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close CoinGecko response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn("CoinGecko returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package common //nolint:revive // package provides shared market connector utilities
|
||||
// Package common provides shared market connector utilities.
|
||||
package common
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
Reference in New Issue
Block a user