Skip to content

Commit 53f51a6

Browse files
GPC: Set extension based on header (prebid#3895)
1 parent c42fe53 commit 53f51a6

File tree

4 files changed

+289
-1
lines changed

4 files changed

+289
-1
lines changed

endpoints/openrtb2/auction.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const observeBrowsingTopicsValue = "?1"
6464

6565
var (
6666
dntKey string = http.CanonicalHeaderKey("DNT")
67+
secGPCKey string = http.CanonicalHeaderKey("Sec-GPC")
6768
dntDisabled int8 = 0
6869
dntEnabled int8 = 1
6970
notAmp int8 = 0
@@ -1497,6 +1498,11 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_
14971498

14981499
setAuctionTypeImplicitly(r)
14991500

1501+
err := setGPCImplicitly(httpReq, r)
1502+
if err != nil {
1503+
return []error{err}
1504+
}
1505+
15001506
errs := setSecBrowsingTopicsImplicitly(httpReq, r, account)
15011507
return errs
15021508
}
@@ -1516,6 +1522,28 @@ func setAuctionTypeImplicitly(r *openrtb_ext.RequestWrapper) {
15161522
}
15171523
}
15181524

1525+
func setGPCImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) error {
1526+
secGPC := httpReq.Header.Get(secGPCKey)
1527+
1528+
if secGPC != "1" {
1529+
return nil
1530+
}
1531+
1532+
regExt, err := r.GetRegExt()
1533+
if err != nil {
1534+
return err
1535+
}
1536+
1537+
if regExt.GetGPC() != nil {
1538+
return nil
1539+
}
1540+
1541+
gpc := "1"
1542+
regExt.SetGPC(&gpc)
1543+
1544+
return nil
1545+
}
1546+
15191547
// setSecBrowsingTopicsImplicitly updates user.data with data from request header 'Sec-Browsing-Topics'
15201548
func setSecBrowsingTopicsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, account *config.Account) []error {
15211549
secBrowsingTopics := httpReq.Header.Get(secBrowsingTopics)

endpoints/openrtb2/auction_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5618,6 +5618,175 @@ func TestValidateOrFillCookieDeprecation(t *testing.T) {
56185618
}
56195619
}
56205620

5621+
func TestSetGPCImplicitly(t *testing.T) {
5622+
testCases := []struct {
5623+
description string
5624+
header string
5625+
regs *openrtb2.Regs
5626+
expectError bool
5627+
expectedRegs *openrtb2.Regs
5628+
}{
5629+
{
5630+
description: "regs_ext_gpc_not_set_and_header_is_1",
5631+
header: "1",
5632+
regs: &openrtb2.Regs{
5633+
Ext: []byte(`{}`),
5634+
},
5635+
expectError: false,
5636+
expectedRegs: &openrtb2.Regs{
5637+
Ext: []byte(`{"gpc":"1"}`),
5638+
},
5639+
},
5640+
{
5641+
description: "sec_gpc_header_not_set_gpc_should_not_be_modified",
5642+
header: "",
5643+
regs: &openrtb2.Regs{
5644+
Ext: []byte(`{}`),
5645+
},
5646+
expectError: false,
5647+
expectedRegs: &openrtb2.Regs{
5648+
Ext: []byte(`{}`),
5649+
},
5650+
},
5651+
{
5652+
description: "sec_gpc_header_set_to_2_gpc_should_not_be_modified",
5653+
header: "2",
5654+
regs: &openrtb2.Regs{
5655+
Ext: []byte(`{}`),
5656+
},
5657+
expectError: false,
5658+
expectedRegs: &openrtb2.Regs{
5659+
Ext: []byte(`{}`),
5660+
},
5661+
},
5662+
{
5663+
description: "sec_gpc_header_set_to_1_and_regs_ext_contains_other_data",
5664+
header: "1",
5665+
regs: &openrtb2.Regs{
5666+
Ext: []byte(`{"some_other_field":"some_value"}`),
5667+
},
5668+
expectError: false,
5669+
expectedRegs: &openrtb2.Regs{
5670+
Ext: []byte(`{"some_other_field":"some_value","gpc":"1"}`),
5671+
},
5672+
},
5673+
{
5674+
description: "regs_ext_gpc_not_set_and_header_not_set",
5675+
header: "",
5676+
regs: &openrtb2.Regs{
5677+
Ext: []byte(`{}`),
5678+
},
5679+
expectError: false,
5680+
expectedRegs: &openrtb2.Regs{
5681+
Ext: []byte(`{}`),
5682+
},
5683+
},
5684+
{
5685+
description: "regs_ext_gpc_not_set_and_header_not_1",
5686+
header: "0",
5687+
regs: &openrtb2.Regs{
5688+
Ext: []byte(`{}`),
5689+
},
5690+
expectError: false,
5691+
expectedRegs: &openrtb2.Regs{
5692+
Ext: []byte(`{}`),
5693+
},
5694+
},
5695+
{
5696+
description: "regs_ext_gpc_is_1_and_header_is_1",
5697+
header: "1",
5698+
regs: &openrtb2.Regs{
5699+
Ext: []byte(`{"gpc":"1"}`),
5700+
},
5701+
expectError: false,
5702+
expectedRegs: &openrtb2.Regs{
5703+
Ext: []byte(`{"gpc":"1"}`),
5704+
},
5705+
},
5706+
{
5707+
description: "regs_ext_gpc_is_1_and_header_not_1",
5708+
header: "0",
5709+
regs: &openrtb2.Regs{
5710+
Ext: []byte(`{"gpc":"1"}`),
5711+
},
5712+
expectError: false,
5713+
expectedRegs: &openrtb2.Regs{
5714+
Ext: []byte(`{"gpc":"1"}`),
5715+
},
5716+
},
5717+
{
5718+
description: "regs_ext_other_data_and_header_is_1",
5719+
header: "1",
5720+
regs: &openrtb2.Regs{
5721+
Ext: []byte(`{"other":"value"}`),
5722+
},
5723+
expectError: false,
5724+
expectedRegs: &openrtb2.Regs{
5725+
Ext: []byte(`{"other":"value","gpc":"1"}`),
5726+
},
5727+
},
5728+
{
5729+
description: "regs_nil_and_header_is_1",
5730+
header: "1",
5731+
regs: nil,
5732+
expectError: false,
5733+
expectedRegs: &openrtb2.Regs{
5734+
Ext: []byte(`{"gpc":"1"}`),
5735+
},
5736+
},
5737+
{
5738+
description: "regs_nil_and_header_not_set",
5739+
header: "",
5740+
regs: nil,
5741+
expectError: false,
5742+
expectedRegs: nil,
5743+
},
5744+
{
5745+
description: "regs_ext_is_nil_and_header_not_set",
5746+
header: "",
5747+
regs: &openrtb2.Regs{
5748+
Ext: nil,
5749+
},
5750+
expectError: false,
5751+
expectedRegs: &openrtb2.Regs{
5752+
Ext: nil,
5753+
},
5754+
},
5755+
}
5756+
5757+
for _, test := range testCases {
5758+
t.Run(test.description, func(t *testing.T) {
5759+
httpReq := &http.Request{
5760+
Header: http.Header{
5761+
http.CanonicalHeaderKey("Sec-GPC"): []string{test.header},
5762+
},
5763+
}
5764+
5765+
r := &openrtb_ext.RequestWrapper{
5766+
BidRequest: &openrtb2.BidRequest{
5767+
Regs: test.regs,
5768+
},
5769+
}
5770+
5771+
err := setGPCImplicitly(httpReq, r)
5772+
5773+
if test.expectError {
5774+
assert.Error(t, err)
5775+
} else {
5776+
assert.NoError(t, err)
5777+
}
5778+
assert.NoError(t, r.RebuildRequest())
5779+
if test.expectedRegs == nil {
5780+
assert.Nil(t, r.BidRequest.Regs)
5781+
} else if test.expectedRegs.Ext == nil {
5782+
assert.Nil(t, r.BidRequest.Regs.Ext)
5783+
} else {
5784+
assert.JSONEq(t, string(test.expectedRegs.Ext), string(r.BidRequest.Regs.Ext))
5785+
}
5786+
})
5787+
}
5788+
}
5789+
56215790
func TestValidateRequestCookieDeprecation(t *testing.T) {
56225791
testCases :=
56235792
[]struct {

openrtb_ext/request_wrapper.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const (
6161
schainKey = "schain"
6262
us_privacyKey = "us_privacy"
6363
cdepKey = "cdep"
64+
gpcKey = "gpc"
6465
)
6566

6667
// LenImp returns the number of impressions without causing the creation of ImpWrapper objects.
@@ -1201,6 +1202,8 @@ type RegExt struct {
12011202
dsaDirty bool
12021203
gdpr *int8
12031204
gdprDirty bool
1205+
gpc *string
1206+
gpcDirty bool
12041207
usPrivacy string
12051208
usPrivacyDirty bool
12061209
}
@@ -1244,6 +1247,13 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error {
12441247
}
12451248
}
12461249

1250+
gpcJson, hasGPC := re.ext[gpcKey]
1251+
if hasGPC && gpcJson != nil {
1252+
if err := jsonutil.Unmarshal(gpcJson, &re.gpc); err != nil {
1253+
return err
1254+
}
1255+
}
1256+
12471257
return nil
12481258
}
12491259

@@ -1287,6 +1297,19 @@ func (re *RegExt) marshal() (json.RawMessage, error) {
12871297
re.usPrivacyDirty = false
12881298
}
12891299

1300+
if re.gpcDirty {
1301+
if re.gpc != nil {
1302+
rawjson, err := jsonutil.Marshal(re.gpc)
1303+
if err != nil {
1304+
return nil, err
1305+
}
1306+
re.ext[gpcKey] = rawjson
1307+
} else {
1308+
delete(re.ext, gpcKey)
1309+
}
1310+
re.gpcDirty = false
1311+
}
1312+
12901313
re.extDirty = false
12911314
if len(re.ext) == 0 {
12921315
return nil, nil
@@ -1295,7 +1318,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) {
12951318
}
12961319

12971320
func (re *RegExt) Dirty() bool {
1298-
return re.extDirty || re.dsaDirty || re.gdprDirty || re.usPrivacyDirty
1321+
return re.extDirty || re.dsaDirty || re.gdprDirty || re.usPrivacyDirty || re.gpcDirty
12991322
}
13001323

13011324
func (re *RegExt) GetExt() map[string]json.RawMessage {
@@ -1337,6 +1360,19 @@ func (re *RegExt) SetGDPR(gdpr *int8) {
13371360
re.gdprDirty = true
13381361
}
13391362

1363+
func (re *RegExt) GetGPC() *string {
1364+
if re.gpc == nil {
1365+
return nil
1366+
}
1367+
gpc := *re.gpc
1368+
return &gpc
1369+
}
1370+
1371+
func (re *RegExt) SetGPC(gpc *string) {
1372+
re.gpc = gpc
1373+
re.gpcDirty = true
1374+
}
1375+
13401376
func (re *RegExt) GetUSPrivacy() string {
13411377
uSPrivacy := re.usPrivacy
13421378
return uSPrivacy

openrtb_ext/request_wrapper_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,6 +2174,30 @@ func TestRebuildRegExt(t *testing.T) {
21742174
regExt: RegExt{usPrivacy: "", usPrivacyDirty: true},
21752175
expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
21762176
},
2177+
{
2178+
name: "req_regs_gpc_populated_-_not_dirty_-_no_change",
2179+
request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
2180+
regExt: RegExt{},
2181+
expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
2182+
},
2183+
{
2184+
name: "req_regs_gpc_populated_-_dirty_and_different-_change",
2185+
request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
2186+
regExt: RegExt{gpc: &strB, gpcDirty: true},
2187+
expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"b"}`)}},
2188+
},
2189+
{
2190+
name: "req_regs_gpc_populated_-_dirty_and_same_-_no_change",
2191+
request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
2192+
regExt: RegExt{gpc: &strA, gpcDirty: true},
2193+
expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
2194+
},
2195+
{
2196+
name: "req_regs_gpc_populated_-_dirty_and_nil_-_cleared",
2197+
request: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
2198+
regExt: RegExt{gpc: nil, gpcDirty: true},
2199+
expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
2200+
},
21772201
}
21782202

21792203
for _, tt := range tests {
@@ -2194,6 +2218,7 @@ func TestRegExtUnmarshal(t *testing.T) {
21942218
extJson json.RawMessage
21952219
expectDSA *ExtRegsDSA
21962220
expectGDPR *int8
2221+
expectGPC *string
21972222
expectUSPrivacy string
21982223
expectError bool
21992224
}{
@@ -2253,6 +2278,21 @@ func TestRegExtUnmarshal(t *testing.T) {
22532278
expectGDPR: ptrutil.ToPtr[int8](0),
22542279
expectError: true,
22552280
},
2281+
// GPC
2282+
{
2283+
name: "valid_gpc_json",
2284+
regExt: &RegExt{},
2285+
extJson: json.RawMessage(`{"gpc":"some_value"}`),
2286+
expectGPC: ptrutil.ToPtr("some_value"),
2287+
expectError: false,
2288+
},
2289+
{
2290+
name: "malformed_gpc_json",
2291+
regExt: &RegExt{},
2292+
extJson: json.RawMessage(`{"gpc":nill}`),
2293+
expectGPC: nil,
2294+
expectError: true,
2295+
},
22562296
// us_privacy
22572297
{
22582298
name: "valid_usprivacy_json",
@@ -2348,3 +2388,18 @@ func TestRegExtGetGDPRSetGDPR(t *testing.T) {
23482388
assert.Equal(t, regExtGDPR, gdpr)
23492389
assert.NotSame(t, regExtGDPR, gdpr)
23502390
}
2391+
2392+
func TestRegExtGetGPCSetGPC(t *testing.T) {
2393+
regExt := &RegExt{}
2394+
regExtGPC := regExt.GetGPC()
2395+
assert.Nil(t, regExtGPC)
2396+
assert.False(t, regExt.Dirty())
2397+
2398+
gpc := ptrutil.ToPtr("Gpc")
2399+
regExt.SetGPC(gpc)
2400+
assert.True(t, regExt.Dirty())
2401+
2402+
regExtGPC = regExt.GetGPC()
2403+
assert.Equal(t, regExtGPC, gpc)
2404+
assert.NotSame(t, regExtGPC, gpc)
2405+
}

0 commit comments

Comments
 (0)