Repository: prebid/prebid-server
Pull request #3494 (2024-02-13 21:57:11)

Remove Extra Buffer from GZIP Compression
Through some comments left in this GZIP [commit](https://github.com/prebid/prebid-server/commit/0c2865730d948b3d14f2527f0c32820c5935aafa#comments), there was a realization that there's unnecessary extra buffer utilized by returning bytes from `getRequestBody()`, and creating a new buffer for `NewRequest()`

So this PR makes updates to remove this extra buffer.



Patch:
diff --git a/.github/workflows/helpers/pull-request-utils.js b/.github/workflows/helpers/pull-request-utils.js
index 941ae5b6..73c80396 100644
--- a/.github/workflows/helpers/pull-request-utils.js
+++ b/.github/workflows/helpers/pull-request-utils.js
@@ -1,7 +1,9 @@
 const synchronizeEvent = "synchronize",
   openedEvent = "opened",
   completedStatus = "completed",
-  resultSize = 100
+  resultSize = 100,
+  adminPermission = "admin",
+  writePermission = "write"
 
 class diffHelper {
   constructor(input) {
@@ -407,8 +409,31 @@ class coverageHelper {
   }
 }
 
+class userHelper {
+  constructor(input) {
+    this.owner = input.context.repo.owner
+    this.repo = input.context.repo.repo
+    this.github = input.github
+    this.user = input.user
+  }
+
+  /*
+    Checks if the user has write permissions for the repository
+    @returns {boolean} - returns true if the user has write permissions, otherwise false
+  */
+  async hasWritePermissions() {
+    const { data } = await this.github.rest.repos.getCollaboratorPermissionLevel({
+      owner: this.owner,
+      repo: this.repo,
+      username: this.user,
+    })
+    return data.permission === writePermission || data.permission === adminPermission
+  }
+}
+
 module.exports = {
   diffHelper: (input) => new diffHelper(input),
   semgrepHelper: (input) => new semgrepHelper(input),
   coverageHelper: (input) => new coverageHelper(input),
+  userHelper: (input) => new userHelper(input),
 }
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f1d2b10c..d3559732 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,13 +25,25 @@ jobs:
     permissions:
       contents: read
     steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+          repository: ${{ github.repository }}
+          ref: master
       - name: Check user permission
-        uses: actions-cool/check-user-permission@v2.2.0
+        uses: actions/github-script@v7
         id: check
         with:
-          require: 'write'
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          result-encoding: string
+          script: |
+            const utils = require('./.github/workflows/helpers/pull-request-utils.js')
+            const helper = utils.userHelper({github, context, user: '${{ github.actor }}'})
+            const hasPermission = await helper.hasWritePermissions()
+            return hasPermission
     outputs:
-      hasWritePermission: ${{ steps.check.outputs.require-result }}
+      hasWritePermission: ${{ steps.check.outputs.result }}
 
   build-master:
     name: Build master
@@ -40,7 +52,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@master
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0
           repository: ${{ github.repository }}
@@ -52,13 +64,12 @@ jobs:
   publish-tag:
     name: Publish tag
     needs: build-master
-    if: contains(needs.check-permission.outputs.hasWritePermission, 'true')
     permissions:
       contents: write
     runs-on: ubuntu-latest
     steps:
       - name: Checkout Prebid Server
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0
       - name: Create & publish tag
@@ -111,7 +122,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout Prebid Server
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0
       - name: Build image
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
index baa10b93..09a0fd56 100644
--- a/.github/workflows/security.yml
+++ b/.github/workflows/security.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-20.04
     steps:
       - name: Checkout Code
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           # Resolves to empty string for push events and falls back to HEAD.
           ref: ${{ github.event.pull_request.head.sha }}
@@ -29,6 +29,6 @@ jobs:
           severity: 'CRITICAL,HIGH'
           
       - name: Upload Results To GitHub Security Tab
-        uses: github/codeql-action/upload-sarif@v2
+        uses: github/codeql-action/upload-sarif@v3
         with:
           sarif_file: 'trivy-results.sarif'
\ No newline at end of file
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
index 565bb9b8..b641d566 100644
--- a/.github/workflows/semgrep.yml
+++ b/.github/workflows/semgrep.yml
@@ -9,7 +9,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repo
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0
           ref: ${{github.event.pull_request.head.ref}}
@@ -17,7 +17,7 @@ jobs:
 
       - name: Calculate diff
         id: calculate_diff
-        uses: actions/github-script@v6
+        uses: actions/github-script@v7
         with:
           result-encoding: string
           script: |
@@ -52,7 +52,7 @@ jobs:
       - name: Add pull request comment
         id: add_pull_request_comment
         if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true')
-        uses: actions/github-script@v6.4.1
+        uses: actions/github-script@v7
         with:
           github-token: ${{ secrets.GITHUB_TOKEN }}
           result-encoding: string
diff --git a/adapters/bidder.go b/adapters/bidder.go
index 2bbcb657..5f486771 100644
--- a/adapters/bidder.go
+++ b/adapters/bidder.go
@@ -45,7 +45,7 @@ type Bidder interface {
 type TimeoutBidder interface {
 	Bidder
 
-	// MakeTimeoutNotice functions much the same as MakeRequests, except it is fed the bidder request that timed out,
+	// MakeTimeoutNotification functions much the same as MakeRequests, except it is fed the bidder request that timed out,
 	// and expects that only one notification "request" will be generated. A use case for multiple timeout notifications
 	// has not been anticipated.
 	//
diff --git a/adapters/yieldlab/types.go b/adapters/yieldlab/types.go
index 90612700..9d3bc6a2 100644
--- a/adapters/yieldlab/types.go
+++ b/adapters/yieldlab/types.go
@@ -1,18 +1,60 @@
 package yieldlab
 
 import (
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
 	"strconv"
 	"time"
 )
 
 type bidResponse struct {
-	ID         uint64 `json:"id"`
-	Price      uint   `json:"price"`
-	Advertiser string `json:"advertiser"`
-	Adsize     string `json:"adsize"`
-	Pid        uint64 `json:"pid"`
-	Did        uint64 `json:"did"`
-	Pvid       string `json:"pvid"`
+	ID         uint64       `json:"id"`
+	Price      uint         `json:"price"`
+	Advertiser string       `json:"advertiser"`
+	Adsize     string       `json:"adsize"`
+	Pid        uint64       `json:"pid"`
+	Did        uint64       `json:"did"`
+	Pvid       string       `json:"pvid"`
+	DSA        *dsaResponse `json:"dsa,omitempty"`
+}
+
+// dsaResponse defines Digital Service Act (DSA) parameters from Yieldlab yieldprobe response.
+type dsaResponse struct {
+	Behalf       string            `json:"behalf,omitempty"`
+	Paid         string            `json:"paid,omitempty"`
+	Adrender     *int              `json:"adrender,omitempty"`
+	Transparency []dsaTransparency `json:"transparency,omitempty"`
+}
+
+// openRTBExtRegsWithDSA defines the contract for bidrequest.regs.ext with the missing DSA property.
+//
+// The openrtb_ext.ExtRegs needs to be extended on yieldlab adapter level until DSA has been implemented
+// by the prebid server team (https://github.com/prebid/prebid-server/issues/3424).
+type openRTBExtRegsWithDSA struct {
+	openrtb_ext.ExtRegs
+	DSA *dsaRequest `json:"dsa,omitempty"`
+}
+
+// responseExtWithDSA defines seatbid.bid.ext with the DSA object.
+type responseExtWithDSA struct {
+	DSA dsaResponse `json:"dsa"`
+}
+
+// dsaRequest defines Digital Service Act (DSA) parameter
+// as specified by the OpenRTB 2.X DSA Transparency community extension.
+//
+// Should rather come from openrtb_ext package but will be defined here until DSA has been
+// implemented by the prebid server team (https://github.com/prebid/prebid-server/issues/3424).
+type dsaRequest struct {
+	Required     *int              `json:"dsarequired"`
+	PubRender    *int              `json:"pubrender"`
+	DataToPub    *int              `json:"datatopub"`
+	Transparency []dsaTransparency `json:"transparency"`
+}
+
+// dsaTransparency Digital Service Act (DSA) transparency object
+type dsaTransparency struct {
+	Domain string `json:"domain,omitempty"`
+	Params []int  `json:"dsaparams,omitempty"`
 }
 
 type cacheBuster func() string
diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go
index 744a45ae..ed7c283e 100644
--- a/adapters/yieldlab/yieldlab.go
+++ b/adapters/yieldlab/yieldlab.go
@@ -96,11 +96,94 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *open
 		}
 	}
 
+	dsa, err := getDSA(req)
+	if err != nil {
+		return "", err
+	}
+	if dsa != nil {
+		if dsa.Required != nil {
+			q.Set("dsarequired", strconv.Itoa(*dsa.Required))
+		}
+		if dsa.PubRender != nil {
+			q.Set("dsapubrender", strconv.Itoa(*dsa.PubRender))
+		}
+		if dsa.DataToPub != nil {
+			q.Set("dsadatatopub", strconv.Itoa(*dsa.DataToPub))
+		}
+		if len(dsa.Transparency) != 0 {
+			transparencyParam := makeDSATransparencyURLParam(dsa.Transparency)
+			if len(transparencyParam) != 0 {
+				q.Set("dsatransparency", transparencyParam)
+			}
+		}
+	}
+
 	uri.RawQuery = q.Encode()
 
 	return uri.String(), nil
 }
 
+// getDSA extracts the Digital Service Act (DSA) properties from the request.
+func getDSA(req *openrtb2.BidRequest) (*dsaRequest, error) {
+	if req.Regs == nil || req.Regs.Ext == nil {
+		return nil, nil
+	}
+
+	var extRegs openRTBExtRegsWithDSA
+	err := json.Unmarshal(req.Regs.Ext, &extRegs)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse Regs.Ext object from Yieldlab response: %v", err)
+	}
+
+	return extRegs.DSA, nil
+}
+
+// makeDSATransparencyURLParam creates the transparency url parameter
+// as specified by the OpenRTB 2.X DSA Transparency community extension.
+//
+// Example result: platform1domain.com~1~~SSP2domain.com~1_2
+func makeDSATransparencyURLParam(transparencyObjects []dsaTransparency) string {
+	valueSeparator, itemSeparator, objectSeparator := "_", "~", "~~"
+
+	var b strings.Builder
+
+	concatParams := func(params []int) {
+		b.WriteString(strconv.Itoa(params[0]))
+		for _, param := range params[1:] {
+			b.WriteString(valueSeparator)
+			b.WriteString(strconv.Itoa(param))
+		}
+	}
+
+	concatTransparency := func(object dsaTransparency) {
+		if len(object.Domain) == 0 {
+			return
+		}
+
+		b.WriteString(object.Domain)
+		if len(object.Params) != 0 {
+			b.WriteString(itemSeparator)
+			concatParams(object.Params)
+		}
+	}
+
+	concatTransparencies := func(objects []dsaTransparency) {
+		if len(objects) == 0 {
+			return
+		}
+
+		concatTransparency(objects[0])
+		for _, obj := range objects[1:] {
+			b.WriteString(objectSeparator)
+			concatTransparency(obj)
+		}
+	}
+
+	concatTransparencies(transparencyObjects)
+
+	return b.String()
+}
+
 func (a *YieldlabAdapter) makeFormats(req *openrtb2.BidRequest) (bool, string) {
 	var formats []string
 	const sizesSeparator, adslotSizesSeparator = "|", ","
@@ -253,6 +336,7 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 		}
 	}
 
+	var bidErrors []error
 	for _, bid := range bids {
 		width, height, err := splitSize(bid.Adsize)
 		if err != nil {
@@ -269,7 +353,13 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 		if imp, exists := adslotToImpMap[strconv.FormatUint(bid.ID, 10)]; !exists {
 			continue
 		} else {
-			var bidType openrtb_ext.BidType
+			extJson, err := makeResponseExt(bid)
+			if err != nil {
+				bidErrors = append(bidErrors, err)
+				// skip as bids with missing ext.dsa will be discarded anyway
+				continue
+			}
+
 			responseBid := &openrtb2.Bid{
 				ID:     strconv.FormatUint(bid.ID, 10),
 				Price:  float64(bid.Price) / 100,
@@ -278,8 +368,10 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 				DealID: strconv.FormatUint(bid.Pid, 10),
 				W:      int64(width),
 				H:      int64(height),
+				Ext:    extJson,
 			}
 
+			var bidType openrtb_ext.BidType
 			if imp.Video != nil {
 				bidType = openrtb_ext.BidTypeVideo
 				responseBid.NURL = a.makeAdSourceURL(internalRequest, req, bid)
@@ -299,7 +391,18 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externa
 		}
 	}
 
-	return bidderResponse, nil
+	return bidderResponse, bidErrors
+}
+
+func makeResponseExt(bid *bidResponse) (json.RawMessage, error) {
+	if bid.DSA != nil {
+		extJson, err := json.Marshal(responseExtWithDSA{*bid.DSA})
+		if err != nil {
+			return nil, fmt.Errorf("failed to make JSON for seatbid.bid.ext for adslotID %v. This is most likely a programming issue", bid.ID)
+		}
+		return extJson, nil
+	}
+	return nil, nil
 }
 
 func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtImpYieldlab) *openrtb_ext.ExtImpYieldlab {
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 96f8ac9b..8b54f984 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -523,8 +523,6 @@ func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.Reques
 }
 
 func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo {
-	var requestBody []byte
-
 	requestBody, err := getRequestBody(req, bidder.config.EndpointCompression)
 	if err != nil {
 		return &httpCallInfo{
@@ -532,7 +530,7 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re
 			err:     err,
 		}
 	}
-	httpReq, err := http.NewRequest(req.Method, req.Uri, bytes.NewBuffer(requestBody))
+	httpReq, err := http.NewRequest(req.Method, req.Uri, requestBody)
 	if err != nil {
 		return &httpCallInfo{
 			request: req,
@@ -746,18 +744,16 @@ func hasShorterDurationThanTmax(ctx bidderTmaxContext, tmaxAdjustments TmaxAdjus
 	return false
 }
 
-func getRequestBody(req *adapters.RequestData, endpointCompression string) ([]byte, error) {
-	var requestBody []byte
-
+func getRequestBody(req *adapters.RequestData, endpointCompression string) (*bytes.Buffer, error) {
 	switch strings.ToUpper(endpointCompression) {
 	case Gzip:
 		// Compress to GZIP
-		var b bytes.Buffer
+		b := bytes.NewBuffer(make([]byte, 0, len(req.Body)))
 
 		w := gzipWriterPool.Get().(*gzip.Writer)
 		defer gzipWriterPool.Put(w)
 
-		w.Reset(&b)
+		w.Reset(b)
 		_, err := w.Write(req.Body)
 		if err != nil {
 			return nil, err
@@ -766,14 +762,13 @@ func getRequestBody(req *adapters.RequestData, endpointCompression string) ([]by
 		if err != nil {
 			return nil, err
 		}
-		requestBody = b.Bytes()
 
 		// Set Header
 		req.Headers.Set("Content-Encoding", "gzip")
 
-		return requestBody, nil
+		return b, nil
 	default:
-		return req.Body, nil
+		return bytes.NewBuffer(req.Body), nil
 	}
 }
 
diff --git a/static/bidder-info/kargo.yaml b/static/bidder-info/kargo.yaml
index e1022032..1a7a77eb 100644
--- a/static/bidder-info/kargo.yaml
+++ b/static/bidder-info/kargo.yaml
@@ -11,7 +11,7 @@ capabilities:
       - native
 userSync:
   redirect:
-    url: "https://crb.kargo.com/api/v1/dsync/PrebidServer?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}"
+    url: "https://crb.kargo.com/api/v1/dsync/PrebidServer?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}"
     userMacro: "$UID"
 endpointCompression: "GZIP"
 openrtb:


Test patch:
diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go
index 7704c886..49dcc31e 100644
--- a/adapters/yieldlab/yieldlab_test.go
+++ b/adapters/yieldlab/yieldlab_test.go
@@ -257,6 +257,98 @@ func Test_makeSupplyChain(t *testing.T) {
 	}
 }
 
+func Test_makeDSATransparencyUrlParam(t *testing.T) {
+	tests := []struct {
+		name           string
+		transparencies []dsaTransparency
+		expected       string
+	}{
+		{
+			name:           "No transparency objects",
+			transparencies: []dsaTransparency{},
+			expected:       "",
+		},
+		{
+			name:           "Nil transparency",
+			transparencies: nil,
+			expected:       "",
+		},
+		{
+			name: "Params without a domain",
+			transparencies: []dsaTransparency{
+				{
+					Params: []int{1, 2},
+				},
+			},
+			expected: "",
+		},
+		{
+			name: "Params without a params",
+			transparencies: []dsaTransparency{
+				{
+					Domain: "domain.com",
+				},
+			},
+			expected: "domain.com",
+		},
+		{
+			name: "One object; No Params",
+			transparencies: []dsaTransparency{
+				{
+					Domain: "domain.com",
+					Params: []int{},
+				},
+			},
+			expected: "domain.com",
+		},
+		{
+			name: "One object; One Param",
+			transparencies: []dsaTransparency{
+				{
+					Domain: "domain.com",
+					Params: []int{1},
+				},
+			},
+			expected: "domain.com~1",
+		},
+		{
+			name: "Three domain objects",
+			transparencies: []dsaTransparency{
+				{
+					Domain: "domain1.com",
+					Params: []int{1, 2},
+				},
+				{
+					Domain: "domain2.com",
+					Params: []int{3, 4},
+				},
+				{
+					Domain: "domain3.com",
+					Params: []int{5, 6},
+				},
+			},
+			expected: "domain1.com~1_2~~domain2.com~3_4~~domain3.com~5_6",
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			actual := makeDSATransparencyURLParam(test.transparencies)
+			assert.Equal(t, test.expected, actual)
+		})
+	}
+}
+
+func Test_getDSA_invalidRequestExt(t *testing.T) {
+	req := &openrtb2.BidRequest{
+		Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"DSA":"wrongValueType"}`)},
+	}
+
+	dsa, err := getDSA(req)
+
+	assert.NotNil(t, err)
+	assert.Nil(t, dsa)
+}
+
 func TestYieldlabAdapter_makeEndpointURL_invalidEndpoint(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderYieldlab, config.Adapter{
 		Endpoint: "test$:/something§"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
diff --git a/adapters/yieldlab/yieldlabtest/exemplary/dsa.json b/adapters/yieldlab/yieldlabtest/exemplary/dsa.json
new file mode 100644
index 00000000..0c815835
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/exemplary/dsa.json
@@ -0,0 +1,169 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-with-DSA",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adslotId": "12345",
+            "supplyId": "123456789",
+            "targeting": {
+              "key1": "value1",
+              "key2": "value2"
+            },
+            "extId": "abc"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ifa": "hello-ads",
+      "devicetype": 4,
+      "connectiontype": 6,
+      "geo": {
+        "lat": 51.499488,
+        "lon": -0.128953
+      },
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+      "ip": "169.254.13.37",
+      "h": 1098,
+      "w": 814
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "dsa": {
+          "dsarequired": 3,
+          "pubrender": 0,
+          "datatopub": 2,
+          "transparency": [
+            {
+              "domain": "platform1domain.com",
+              "dsaparams": [
+                1
+              ]
+            },
+            {
+              "domain": "SSP2domain.com",
+              "dsaparams": [
+                1,
+                2
+              ]
+            }
+          ]
+        }
+      }
+    },
+    "site": {
+      "id": "fake-site-id",
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://localhost:9090/gdpr.html"
+    },
+    "user": {
+      "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c",
+      "ext": {
+        "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Cookie": [
+            "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+          ],
+          "Referer": [
+            "http://localhost:9090/gdpr.html"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+          ],
+          "X-Forwarded-For": [
+            "169.254.13.37"
+          ]
+        },
+        "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&dsadatatopub=2&dsapubrender=0&dsarequired=3&dsatransparency=platform1domain.com~1~~SSP2domain.com~1_2&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "id": 12345,
+            "price": 201,
+            "advertiser": "yieldlab",
+            "adsize": "728x90",
+            "pid": 1234,
+            "did": 5678,
+            "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5",
+            "dsa": {
+              "required": 3,
+              "behalf": "on behalf of yieldlab",
+              "paid": "by yieldlab",
+              "transparency": [
+                {
+                  "domain": "yieldlab.de",
+                  "dsaparams": [
+                    1,
+                    2
+                  ]
+                }
+              ],
+              "adrender": 0
+            }
+          }
+        ]
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "adm": "<script src=\"https://ad.yieldlab.net/d/12345/123456789/728x90?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&gdpr=1&id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing\"></script>",
+            "crid": "12345123433",
+            "dealid": "1234",
+            "ext": {
+              "dsa": {
+                "adrender": 0,
+                "transparency": [
+                  {
+                    "domain": "yieldlab.de",
+                    "dsaparams": [
+                      1,
+                      2
+                    ]
+                  }
+                ],
+                "behalf": "on behalf of yieldlab",
+                "paid": "by yieldlab"
+              }
+            },
+            "id": "12345",
+            "impid": "test-imp-id",
+            "price": 2.01,
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty.json b/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty.json
new file mode 100644
index 00000000..594e5bb4
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty.json
@@ -0,0 +1,127 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-with-empty-DSA",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adslotId": "12345",
+            "supplyId": "123456789",
+            "targeting": {
+              "key1": "value1",
+              "key2": "value2"
+            },
+            "extId": "abc"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ifa": "hello-ads",
+      "devicetype": 4,
+      "connectiontype": 6,
+      "geo": {
+        "lat": 51.499488,
+        "lon": -0.128953
+      },
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+      "ip": "169.254.13.37",
+      "h": 1098,
+      "w": 814
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "dsa": {
+        }
+      }
+    },
+    "site": {
+      "id": "fake-site-id",
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://localhost:9090/gdpr.html"
+    },
+    "user": {
+      "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c",
+      "ext": {
+        "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Cookie": [
+            "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+          ],
+          "Referer": [
+            "http://localhost:9090/gdpr.html"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+          ],
+          "X-Forwarded-For": [
+            "169.254.13.37"
+          ]
+        },
+        "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "id": 12345,
+            "price": 201,
+            "advertiser": "yieldlab",
+            "adsize": "728x90",
+            "pid": 1234,
+            "did": 5678,
+            "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5",
+            "dsa": {
+            }
+          }
+        ]
+
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "adm": "<script src=\"https://ad.yieldlab.net/d/12345/123456789/728x90?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&gdpr=1&id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing\"></script>",
+            "crid": "12345123433",
+            "dealid": "1234",
+            "ext": {
+              "dsa": {
+              }
+            },
+            "id": "12345",
+            "impid": "test-imp-id",
+            "price": 2.01,
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty_transparency.json b/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty_transparency.json
new file mode 100644
index 00000000..c966a716
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/supplemental/dsa_empty_transparency.json
@@ -0,0 +1,139 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-with-empty-DSA-transparency",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adslotId": "12345",
+            "supplyId": "123456789",
+            "targeting": {
+              "key1": "value1",
+              "key2": "value2"
+            },
+            "extId": "abc"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ifa": "hello-ads",
+      "devicetype": 4,
+      "connectiontype": 6,
+      "geo": {
+        "lat": 51.499488,
+        "lon": -0.128953
+      },
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+      "ip": "169.254.13.37",
+      "h": 1098,
+      "w": 814
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "dsa": {
+          "transparency": [
+            {
+            }
+          ]
+        }
+      }
+    },
+    "site": {
+      "id": "fake-site-id",
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://localhost:9090/gdpr.html"
+    },
+    "user": {
+      "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c",
+      "ext": {
+        "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02"
+      }
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Cookie": [
+            "id=34a53e82-0dc3-4815-8b7e-b725ede0361c"
+          ],
+          "Referer": [
+            "http://localhost:9090/gdpr.html"
+          ],
+          "User-Agent": [
+            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
+          ],
+          "X-Forwarded-For": [
+            "169.254.13.37"
+          ]
+        },
+        "uri": "https://ad.yieldlab.net/testing/12345?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&content=json&gdpr=1&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&lat=51.499488&lon=-0.128953&pvid=true&sizes=12345%3A728x90&t=key1%3Dvalue1%26key2%3Dvalue2&ts=testing&yl_rtb_connectiontype=6&yl_rtb_devicetype=4&yl_rtb_ifa=hello-ads"
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": [
+          {
+            "id": 12345,
+            "price": 201,
+            "advertiser": "yieldlab",
+            "adsize": "728x90",
+            "pid": 1234,
+            "did": 5678,
+            "pvid": "40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5",
+            "dsa": {
+              "transparency": [
+                {
+                }
+              ]
+            }
+          }
+        ]
+
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "EUR",
+      "bids": [
+        {
+          "bid": {
+            "adm": "<script src=\"https://ad.yieldlab.net/d/12345/123456789/728x90?consent=BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02&gdpr=1&id=abc&ids=ylid%3A34a53e82-0dc3-4815-8b7e-b725ede0361c&pvid=40cb3251-1e1e-4cfd-8edc-7d32dc1a21e5&ts=testing\"></script>",
+            "crid": "12345123433",
+            "dealid": "1234",
+            "ext": {
+              "dsa": {
+                "transparency": [
+                  {
+                  }
+                ]
+              }
+            },
+            "id": "12345",
+            "impid": "test-imp-id",
+            "price": 2.01,
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/yieldlab/yieldlabtest/supplemental/invalid_reg_ext.json b/adapters/yieldlab/yieldlabtest/supplemental/invalid_reg_ext.json
new file mode 100644
index 00000000..1723b8fb
--- /dev/null
+++ b/adapters/yieldlab/yieldlabtest/supplemental/invalid_reg_ext.json
@@ -0,0 +1,67 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-with-wrong-DSA-type",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "adslotId": "12345",
+            "supplyId": "123456789",
+            "targeting": {
+              "key1": "value1",
+              "key2": "value2"
+            },
+            "extId": "abc"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ifa": "hello-ads",
+      "devicetype": 4,
+      "connectiontype": 6,
+      "geo": {
+        "lat": 51.499488,
+        "lon": -0.128953
+      },
+      "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
+      "ip": "169.254.13.37",
+      "h": 1098,
+      "w": 814
+    },
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "DSA": ""
+      }
+    },
+    "site": {
+      "id": "fake-site-id",
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://localhost:9090/gdpr.html"
+    },
+    "user": {
+      "buyeruid": "34a53e82-0dc3-4815-8b7e-b725ede0361c",
+      "ext": {
+        "consent": "BOlOrv1OlOr2EAAABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02"
+      }
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "failed to parse Regs.Ext object from Yieldlab response: json: cannot unmarshal string into Go struct field openRTBExtRegsWithDSA.dsa of type yieldlab.dsaRequest",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index a1662ab7..9f092b7d 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -3377,11 +3377,11 @@ func TestGetRequestBody(t *testing.T) {
 			if test.endpointCompression == "GZIP" {
 				assert.Equal(t, "gzip", req.Headers.Get("Content-Encoding"))
 
-				decompressedReqBody, err := decompressGzip(requestBody)
+				decompressedReqBody, err := decompressGzip(requestBody.Bytes())
 				assert.NoError(t, err)
 				assert.Equal(t, test.givenReqBody, decompressedReqBody)
 			} else {
-				assert.Equal(t, test.givenReqBody, requestBody)
+				assert.Equal(t, test.givenReqBody, requestBody.Bytes())
 			}
 		})
 	}