WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 4e44ab4

Browse files
authored
Harden HTTP Response Body Handling (#4489)
1 parent 52afcad commit 4e44ab4

File tree

20 files changed

+156
-72
lines changed

20 files changed

+156
-72
lines changed

adapters/audienceNetwork/facebook.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
433433
return bidder, nil
434434
}
435435

436-
func (a *adapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) {
436+
func (a *adapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, error) {
437437
var (
438438
rID string
439439
pubID string
@@ -445,15 +445,13 @@ func (a *adapter) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.
445445
// corresponding imp's ID
446446
rID, err = jsonparser.GetString(req.Body, "id")
447447
if err != nil {
448-
return &adapters.RequestData{}, []error{err}
448+
return &adapters.RequestData{}, err
449449
}
450450

451451
// The publisher ID is expected in the app object
452452
pubID, err = jsonparser.GetString(req.Body, "app", "publisher", "id")
453453
if err != nil {
454-
return &adapters.RequestData{}, []error{
455-
errors.New("path app.publisher.id not found in the request"),
456-
}
454+
return &adapters.RequestData{}, errors.New("path app.publisher.id not found in the request")
457455
}
458456

459457
uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", a.platformID, pubID, rID)

adapters/bidder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ type TimeoutBidder interface {
5151
//
5252
// Do note that if MakeRequests returns multiple requests, and more than one of these times out, MakeTimeoutNotice will be called
5353
// once for each timed out request.
54-
MakeTimeoutNotification(req *RequestData) (*RequestData, []error)
54+
MakeTimeoutNotification(req *RequestData) (*RequestData, error)
5555
}
5656

5757
// BidderResponse wraps the server's response with the list of bids and the currency used by the bidder.

analytics/agma/sender.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"compress/gzip"
66
"context"
77
"fmt"
8+
"io"
89
"net/http"
910
"net/url"
1011
"time"
@@ -74,6 +75,12 @@ func createHttpSender(httpClient *http.Client, endpoint config.AgmaAnalyticsHttp
7475
glog.Errorf("[agmaAnalytics] Sending request failed %v", err)
7576
return err
7677
}
78+
defer func() {
79+
if _, err := io.Copy(io.Discard, resp.Body); err != nil {
80+
glog.Errorf("[agmaAnalytics] Draining response body failed: %v", err)
81+
}
82+
resp.Body.Close()
83+
}()
7784

7885
if resp.StatusCode != http.StatusOK {
7986
glog.Errorf("[agmaAnalytics] Wrong code received %d instead of %d", resp.StatusCode, http.StatusOK)

analytics/pubstack/config.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,38 @@ package pubstack
22

33
import (
44
"encoding/json"
5+
"fmt"
6+
"io"
57
"net/http"
68
"net/url"
79
"time"
810

911
"github.com/docker/go-units"
12+
"github.com/golang/glog"
1013
)
1114

1215
func fetchConfig(client *http.Client, endpoint *url.URL) (*Configuration, error) {
1316
res, err := client.Get(endpoint.String())
1417
if err != nil {
1518
return nil, err
1619
}
20+
defer func() {
21+
// read the entire response body to ensure full connection reuse if there's an
22+
// error while decoding the json
23+
if _, err := io.Copy(io.Discard, res.Body); err != nil {
24+
glog.Errorf("[pubstack] Draining config response body failed: %v", err)
25+
}
26+
res.Body.Close()
27+
}()
28+
29+
if res.StatusCode != http.StatusOK {
30+
return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
31+
}
1732

18-
defer res.Body.Close()
1933
c := Configuration{}
2034
err = json.NewDecoder(res.Body).Decode(&c)
2135
if err != nil {
22-
return nil, err
36+
return nil, fmt.Errorf("failed to decode config: %w", err)
2337
}
2438
return &c, nil
2539
}

analytics/pubstack/eventchannel/sender.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package eventchannel
33
import (
44
"bytes"
55
"fmt"
6+
"io"
67
"net/http"
78
"net/url"
89
"path"
@@ -27,7 +28,12 @@ func NewHttpSender(client *http.Client, endpoint string) Sender {
2728
if err != nil {
2829
return err
2930
}
30-
resp.Body.Close()
31+
defer func() {
32+
if _, err := io.Copy(io.Discard, resp.Body); err != nil {
33+
glog.Errorf("[pubstack] Draining sender response body failed: %v", err)
34+
}
35+
resp.Body.Close()
36+
}()
3137

3238
if resp.StatusCode != http.StatusOK {
3339
glog.Errorf("[pubstack] Wrong code received %d instead of %d", resp.StatusCode, http.StatusOK)

config/config.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,15 +472,19 @@ type Analytics struct {
472472
}
473473

474474
type CurrencyConverter struct {
475-
FetchURL string `mapstructure:"fetch_url"`
476-
FetchIntervalSeconds int `mapstructure:"fetch_interval_seconds"`
477-
StaleRatesSeconds int `mapstructure:"stale_rates_seconds"`
475+
FetchURL string `mapstructure:"fetch_url"`
476+
FetchTimeoutMilliseconds int `mapstructure:"fetch_timeout_ms"`
477+
FetchIntervalSeconds int `mapstructure:"fetch_interval_seconds"`
478+
StaleRatesSeconds int `mapstructure:"stale_rates_seconds"`
478479
}
479480

480481
func (cfg *CurrencyConverter) validate(errs []error) []error {
481482
if cfg.FetchIntervalSeconds < 0 {
482483
errs = append(errs, fmt.Errorf("currency_converter.fetch_interval_seconds must be in the range [0, %d]. Got %d", 0xffff, cfg.FetchIntervalSeconds))
483484
}
485+
if cfg.FetchTimeoutMilliseconds < 0 {
486+
errs = append(errs, fmt.Errorf("currency_converter.fetch_timeout_ms must be 0 or greater. Got %d", cfg.FetchTimeoutMilliseconds))
487+
}
484488
return errs
485489
}
486490

@@ -1173,6 +1177,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) {
11731177
v.SetDefault("ccpa.enforce", false)
11741178
v.SetDefault("lmt.enforce", true)
11751179
v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json")
1180+
v.SetDefault("currency_converter.fetch_timeout_ms", 60000) // 60 seconds
11761181
v.SetDefault("currency_converter.fetch_interval_seconds", 1800) // fetch currency rates every 30 minutes
11771182
v.SetDefault("currency_converter.stale_rates_seconds", 0)
11781183
v.SetDefault("default_request.type", "")

currency/currency_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func TestGetAuctionCurrencyRates(t *testing.T) {
5353

5454
return NewRateConverter(
5555
mockCurrencyClient,
56+
60*time.Second,
5657
"currency.fake.com",
5758
24*time.Hour,
5859
)

currency/rate_converter.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package currency
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"net/http"
@@ -16,6 +17,7 @@ import (
1617
// RateConverter holds the currencies conversion rates dictionary
1718
type RateConverter struct {
1819
httpClient httpClient
20+
httpTimeout time.Duration
1921
staleRatesThreshold time.Duration
2022
syncSourceURL string
2123
rates atomic.Value // Should only hold Rates struct
@@ -27,11 +29,13 @@ type RateConverter struct {
2729
// NewRateConverter returns a new RateConverter
2830
func NewRateConverter(
2931
httpClient httpClient,
32+
httpTimeout time.Duration,
3033
syncSourceURL string,
3134
staleRatesThreshold time.Duration,
3235
) *RateConverter {
3336
return &RateConverter{
3437
httpClient: httpClient,
38+
httpTimeout: httpTimeout,
3539
staleRatesThreshold: staleRatesThreshold,
3640
syncSourceURL: syncSourceURL,
3741
rates: atomic.Value{},
@@ -43,7 +47,10 @@ func NewRateConverter(
4347

4448
// fetch allows to retrieve the currencies rates from the syncSourceURL provided
4549
func (rc *RateConverter) fetch() (*Rates, error) {
46-
request, err := http.NewRequest("GET", rc.syncSourceURL, nil)
50+
ctx, cancel := context.WithTimeout(context.Background(), rc.httpTimeout)
51+
defer cancel()
52+
53+
request, err := http.NewRequestWithContext(ctx, "GET", rc.syncSourceURL, nil)
4754
if err != nil {
4855
return nil, err
4956
}
@@ -52,23 +59,29 @@ func (rc *RateConverter) fetch() (*Rates, error) {
5259
if err != nil {
5360
return nil, err
5461
}
62+
defer func() {
63+
// read the entire response body to ensure full connection reuse if there's an
64+
// invalid status code
65+
if _, err := io.Copy(io.Discard, response.Body); err != nil {
66+
glog.Errorf("error draining conversion rates response body: %v", err)
67+
}
68+
response.Body.Close()
69+
}()
5570

5671
if response.StatusCode >= 400 {
57-
message := fmt.Sprintf("The currency rates request failed with status code %d", response.StatusCode)
72+
message := fmt.Sprintf("the currency rates request failed with status code %d", response.StatusCode)
5873
return nil, &errortypes.BadServerResponse{Message: message}
5974
}
6075

61-
defer response.Body.Close()
62-
6376
bytesJSON, err := io.ReadAll(response.Body)
6477
if err != nil {
65-
return nil, err
78+
return nil, fmt.Errorf("the currency rates request failed: %v", err)
6679
}
6780

6881
updatedRates := &Rates{}
6982
err = jsonutil.UnmarshalValid(bytesJSON, updatedRates)
7083
if err != nil {
71-
return nil, err
84+
return nil, fmt.Errorf("the currency rates request failed to parse json: %v", err)
7285
}
7386

7487
return updatedRates, err

currency/rate_converter_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func TestReadWriteRates(t *testing.T) {
123123
}
124124
currencyConverter := NewRateConverter(
125125
&http.Client{},
126+
60*time.Second,
126127
url,
127128
24*time.Hour,
128129
)
@@ -181,6 +182,7 @@ func TestRateStaleness(t *testing.T) {
181182
// Execute:
182183
currencyConverter := NewRateConverter(
183184
&http.Client{},
185+
60*time.Second,
184186
mockedHttpServer.URL,
185187
30*time.Second, // stale rates threshold
186188
)
@@ -268,6 +270,7 @@ func TestRatesAreNeverConsideredStale(t *testing.T) {
268270
// Execute:
269271
currencyConverter := NewRateConverter(
270272
&http.Client{},
273+
60*time.Second,
271274
mockedHttpServer.URL,
272275
0*time.Millisecond, // stale rates threshold
273276
)
@@ -321,6 +324,7 @@ func TestRace(t *testing.T) {
321324
interval := 1 * time.Millisecond
322325
currencyConverter := NewRateConverter(
323326
mockedHttpClient,
327+
60*time.Second,
324328
"currency.fake.com",
325329
24*time.Hour,
326330
)

endpoints/openrtb2/auction_benchmark_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
9494
nilMetrics,
9595
infos,
9696
gdprPermsBuilder,
97-
currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
97+
currency.NewRateConverter(&http.Client{}, 60*time.Second, "", time.Duration(0)),
9898
empty_fetcher.EmptyFetcher{},
9999
&adscert.NilSigner{},
100100
macros.NewStringIndexBasedReplacer(),

0 commit comments

Comments
 (0)