github.com/prebid/prebid-server@v0.275.0/endpoints/openrtb2/auction.go (about) 1 package openrtb2 2 3 import ( 4 "compress/gzip" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "net/http" 11 "net/url" 12 "regexp" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/prebid/prebid-server/privacy" 18 19 "github.com/buger/jsonparser" 20 "github.com/gofrs/uuid" 21 "github.com/golang/glog" 22 "github.com/julienschmidt/httprouter" 23 gpplib "github.com/prebid/go-gpp" 24 "github.com/prebid/go-gpp/constants" 25 "github.com/prebid/openrtb/v19/adcom1" 26 "github.com/prebid/openrtb/v19/native1" 27 nativeRequests "github.com/prebid/openrtb/v19/native1/request" 28 "github.com/prebid/openrtb/v19/openrtb2" 29 "github.com/prebid/openrtb/v19/openrtb3" 30 "github.com/prebid/prebid-server/bidadjustment" 31 "github.com/prebid/prebid-server/hooks" 32 "github.com/prebid/prebid-server/ortb" 33 "golang.org/x/net/publicsuffix" 34 jsonpatch "gopkg.in/evanphx/json-patch.v4" 35 36 accountService "github.com/prebid/prebid-server/account" 37 "github.com/prebid/prebid-server/analytics" 38 "github.com/prebid/prebid-server/config" 39 "github.com/prebid/prebid-server/currency" 40 "github.com/prebid/prebid-server/errortypes" 41 "github.com/prebid/prebid-server/exchange" 42 "github.com/prebid/prebid-server/gdpr" 43 "github.com/prebid/prebid-server/hooks/hookexecution" 44 "github.com/prebid/prebid-server/metrics" 45 "github.com/prebid/prebid-server/openrtb_ext" 46 "github.com/prebid/prebid-server/prebid_cache_client" 47 "github.com/prebid/prebid-server/privacy/ccpa" 48 "github.com/prebid/prebid-server/privacy/lmt" 49 "github.com/prebid/prebid-server/schain" 50 "github.com/prebid/prebid-server/stored_requests" 51 "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" 52 "github.com/prebid/prebid-server/stored_responses" 53 "github.com/prebid/prebid-server/usersync" 54 "github.com/prebid/prebid-server/util/httputil" 55 "github.com/prebid/prebid-server/util/iputil" 56 "github.com/prebid/prebid-server/util/uuidutil" 57 "github.com/prebid/prebid-server/version" 58 ) 59 60 const storedRequestTimeoutMillis = 50 61 const ampChannel = "amp" 62 const appChannel = "app" 63 64 var ( 65 dntKey string = http.CanonicalHeaderKey("DNT") 66 dntDisabled int8 = 0 67 dntEnabled int8 = 1 68 notAmp int8 = 0 69 ) 70 71 var accountIdSearchPath = [...]struct { 72 isApp bool 73 key []string 74 }{ 75 {true, []string{"app", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, 76 {true, []string{"app", "publisher", "id"}}, 77 {false, []string{"site", "publisher", "ext", openrtb_ext.PrebidExtKey, "parentAccount"}}, 78 {false, []string{"site", "publisher", "id"}}, 79 } 80 81 func NewEndpoint( 82 uuidGenerator uuidutil.UUIDGenerator, 83 ex exchange.Exchange, 84 validator openrtb_ext.BidderParamValidator, 85 requestsById stored_requests.Fetcher, 86 accounts stored_requests.AccountFetcher, 87 cfg *config.Configuration, 88 metricsEngine metrics.MetricsEngine, 89 pbsAnalytics analytics.PBSAnalyticsModule, 90 disabledBidders map[string]string, 91 defReqJSON []byte, 92 bidderMap map[string]openrtb_ext.BidderName, 93 storedRespFetcher stored_requests.Fetcher, 94 hookExecutionPlanBuilder hooks.ExecutionPlanBuilder, 95 tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed, 96 ) (httprouter.Handle, error) { 97 if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil { 98 return nil, errors.New("NewEndpoint requires non-nil arguments.") 99 } 100 101 defRequest := defReqJSON != nil && len(defReqJSON) > 0 102 103 ipValidator := iputil.PublicNetworkIPValidator{ 104 IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed, 105 IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed, 106 } 107 108 return httprouter.Handle((&endpointDeps{ 109 uuidGenerator, 110 ex, 111 validator, 112 requestsById, 113 empty_fetcher.EmptyFetcher{}, 114 accounts, 115 cfg, 116 metricsEngine, 117 pbsAnalytics, 118 disabledBidders, 119 defRequest, 120 defReqJSON, 121 bidderMap, 122 nil, 123 nil, 124 ipValidator, 125 storedRespFetcher, 126 hookExecutionPlanBuilder, 127 tmaxAdjustments}).Auction), nil 128 } 129 130 type endpointDeps struct { 131 uuidGenerator uuidutil.UUIDGenerator 132 ex exchange.Exchange 133 paramsValidator openrtb_ext.BidderParamValidator 134 storedReqFetcher stored_requests.Fetcher 135 videoFetcher stored_requests.Fetcher 136 accounts stored_requests.AccountFetcher 137 cfg *config.Configuration 138 metricsEngine metrics.MetricsEngine 139 analytics analytics.PBSAnalyticsModule 140 disabledBidders map[string]string 141 defaultRequest bool 142 defReqJSON []byte 143 bidderMap map[string]openrtb_ext.BidderName 144 cache prebid_cache_client.Client 145 debugLogRegexp *regexp.Regexp 146 privateNetworkIPValidator iputil.IPValidator 147 storedRespFetcher stored_requests.Fetcher 148 hookExecutionPlanBuilder hooks.ExecutionPlanBuilder 149 tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed 150 } 151 152 func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 153 // Prebid Server interprets request.tmax to be the maximum amount of time that a caller is willing 154 // to wait for bids. However, tmax may be defined in the Stored Request data. 155 // 156 // If so, then the trip to the backend might use a significant amount of this time. 157 // We can respect timeouts more accurately if we note the *real* start time, and use it 158 // to compute the auction timeout. 159 start := time.Now() 160 161 hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) 162 163 ao := analytics.AuctionObject{ 164 Status: http.StatusOK, 165 Errors: make([]error, 0), 166 StartTime: start, 167 } 168 169 labels := metrics.Labels{ 170 Source: metrics.DemandUnknown, 171 RType: metrics.ReqTypeORTB2Web, 172 PubID: metrics.PublisherUnknown, 173 CookieFlag: metrics.CookieFlagUnknown, 174 RequestStatus: metrics.RequestStatusOK, 175 } 176 defer func() { 177 deps.metricsEngine.RecordRequest(labels) 178 deps.metricsEngine.RecordRequestTime(labels, time.Since(start)) 179 deps.analytics.LogAuctionObject(&ao) 180 }() 181 182 w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver)) 183 184 req, impExtInfoMap, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, account, errL := deps.parseRequest(r, &labels, hookExecutor) 185 if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { 186 return 187 } 188 189 if rejectErr := hookexecution.FindFirstRejectOrNil(errL); rejectErr != nil { 190 ao.RequestWrapper = req 191 labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao) 192 return 193 } 194 195 tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) 196 197 activityControl := privacy.NewActivityControl(&account.Privacy) 198 199 ctx := context.Background() 200 201 timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(req.TMax) * time.Millisecond) 202 if timeout > 0 { 203 var cancel context.CancelFunc 204 ctx, cancel = context.WithDeadline(ctx, start.Add(timeout)) 205 defer cancel() 206 } 207 208 // Read Usersyncs/Cookie 209 decoder := usersync.Base64Decoder{} 210 usersyncs := usersync.ReadCookie(r, decoder, &deps.cfg.HostCookie) 211 usersync.SyncHostCookie(r, usersyncs, &deps.cfg.HostCookie) 212 213 if req.Site != nil { 214 if usersyncs.HasAnyLiveSyncs() { 215 labels.CookieFlag = metrics.CookieFlagYes 216 } else { 217 labels.CookieFlag = metrics.CookieFlagNo 218 } 219 } 220 221 // Set Integration Information 222 err := deps.setIntegrationType(req, account) 223 if err != nil { 224 errL = append(errL, err) 225 writeError(errL, w, &labels) 226 return 227 } 228 secGPC := r.Header.Get("Sec-GPC") 229 230 warnings := errortypes.WarningOnly(errL) 231 232 auctionRequest := &exchange.AuctionRequest{ 233 BidRequestWrapper: req, 234 Account: *account, 235 UserSyncs: usersyncs, 236 RequestType: labels.RType, 237 StartTime: start, 238 LegacyLabels: labels, 239 Warnings: warnings, 240 GlobalPrivacyControlHeader: secGPC, 241 ImpExtInfoMap: impExtInfoMap, 242 StoredAuctionResponses: storedAuctionResponses, 243 StoredBidResponses: storedBidResponses, 244 BidderImpReplaceImpID: bidderImpReplaceImp, 245 PubID: labels.PubID, 246 HookExecutor: hookExecutor, 247 TCF2Config: tcf2Config, 248 Activities: activityControl, 249 TmaxAdjustments: deps.tmaxAdjustments, 250 } 251 auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) 252 defer func() { 253 if !auctionRequest.BidderResponseStartTime.IsZero() { 254 deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime)) 255 } 256 }() 257 ao.RequestWrapper = req 258 ao.Account = account 259 var response *openrtb2.BidResponse 260 if auctionResponse != nil { 261 response = auctionResponse.BidResponse 262 } 263 ao.Response = response 264 ao.SeatNonBid = auctionResponse.GetSeatNonBid() 265 rejectErr, isRejectErr := hookexecution.CastRejectErr(err) 266 if err != nil && !isRejectErr { 267 if errortypes.ReadCode(err) == errortypes.BadInputErrorCode { 268 writeError([]error{err}, w, &labels) 269 return 270 } 271 labels.RequestStatus = metrics.RequestStatusErr 272 w.WriteHeader(http.StatusInternalServerError) 273 fmt.Fprintf(w, "Critical error while running the auction: %v", err) 274 glog.Errorf("/openrtb2/auction Critical error: %v", err) 275 ao.Status = http.StatusInternalServerError 276 ao.Errors = append(ao.Errors, err) 277 return 278 } else if isRejectErr { 279 labels, ao = rejectAuctionRequest(*rejectErr, w, hookExecutor, req.BidRequest, account, labels, ao) 280 return 281 } 282 283 err = setSeatNonBidRaw(req, auctionResponse) 284 if err != nil { 285 glog.Errorf("Error setting seat non-bid: %v", err) 286 } 287 labels, ao = sendAuctionResponse(w, hookExecutor, response, req.BidRequest, account, labels, ao) 288 } 289 290 // setSeatNonBidRaw is transitional function for setting SeatNonBid inside bidResponse.Ext 291 // Because, 292 // 1. today exchange.HoldAuction prepares and marshals some piece of response.Ext which is then used by auction.go, amp_auction.go and video_auction.go 293 // 2. As per discussion with Prebid Team we are planning to move away from - HoldAuction building openrtb2.BidResponse. instead respective auction modules will build this object 294 // 3. So, we will need this method to do first, unmarshalling of response.Ext 295 func setSeatNonBidRaw(request *openrtb_ext.RequestWrapper, auctionResponse *exchange.AuctionResponse) error { 296 if auctionResponse == nil || auctionResponse.BidResponse == nil { 297 return nil 298 } 299 // unmarshalling is required here, until we are moving away from bidResponse.Ext, which is populated 300 // by HoldAuction 301 response := auctionResponse.BidResponse 302 respExt := &openrtb_ext.ExtBidResponse{} 303 if err := json.Unmarshal(response.Ext, &respExt); err != nil { 304 return err 305 } 306 if setSeatNonBid(respExt, request, auctionResponse) { 307 if respExtJson, err := json.Marshal(respExt); err == nil { 308 response.Ext = respExtJson 309 return nil 310 } else { 311 return err 312 } 313 } 314 return nil 315 } 316 317 func rejectAuctionRequest( 318 rejectErr hookexecution.RejectError, 319 w http.ResponseWriter, 320 hookExecutor hookexecution.HookStageExecutor, 321 request *openrtb2.BidRequest, 322 account *config.Account, 323 labels metrics.Labels, 324 ao analytics.AuctionObject, 325 ) (metrics.Labels, analytics.AuctionObject) { 326 response := &openrtb2.BidResponse{NBR: openrtb3.NoBidReason(rejectErr.NBR).Ptr()} 327 if request != nil { 328 response.ID = request.ID 329 } 330 331 ao.Response = response 332 ao.Errors = append(ao.Errors, rejectErr) 333 334 return sendAuctionResponse(w, hookExecutor, response, request, account, labels, ao) 335 } 336 337 func sendAuctionResponse( 338 w http.ResponseWriter, 339 hookExecutor hookexecution.HookStageExecutor, 340 response *openrtb2.BidResponse, 341 request *openrtb2.BidRequest, 342 account *config.Account, 343 labels metrics.Labels, 344 ao analytics.AuctionObject, 345 ) (metrics.Labels, analytics.AuctionObject) { 346 hookExecutor.ExecuteAuctionResponseStage(response) 347 348 if response != nil { 349 stageOutcomes := hookExecutor.GetOutcomes() 350 ao.HookExecutionOutcome = stageOutcomes 351 352 ext, warns, err := hookexecution.EnrichExtBidResponse(response.Ext, stageOutcomes, request, account) 353 if err != nil { 354 err = fmt.Errorf("Failed to enrich Bid Response with hook debug information: %s", err) 355 glog.Errorf(err.Error()) 356 ao.Errors = append(ao.Errors, err) 357 } else { 358 response.Ext = ext 359 } 360 361 if len(warns) > 0 { 362 ao.Errors = append(ao.Errors, warns...) 363 } 364 } 365 366 // Fixes #231 367 enc := json.NewEncoder(w) 368 enc.SetEscapeHTML(false) 369 370 w.Header().Set("Content-Type", "application/json") 371 372 // If an error happens when encoding the response, there isn't much we can do. 373 // If we've sent _any_ bytes, then Go would have sent the 200 status code first. 374 // That status code can't be un-sent... so the best we can do is log the error. 375 if err := enc.Encode(response); err != nil { 376 labels.RequestStatus = metrics.RequestStatusNetworkErr 377 ao.Errors = append(ao.Errors, fmt.Errorf("/openrtb2/auction Failed to send response: %v", err)) 378 } 379 380 return labels, ao 381 } 382 383 // parseRequest turns the HTTP request into an OpenRTB request. This is guaranteed to return: 384 // 385 // - A context which times out appropriately, given the request. 386 // - A cancellation function which should be called if the auction finishes early. 387 // 388 // If the errors list is empty, then the returned request will be valid according to the OpenRTB 2.5 spec. 389 // In case of "strong recommendations" in the spec, it tends to be restrictive. If a better workaround is 390 // possible, it will return errors with messages that suggest improvements. 391 // 392 // If the errors list has at least one element, then no guarantees are made about the returned request. 393 func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metrics.Labels, hookExecutor hookexecution.HookStageExecutor) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImpId stored_responses.BidderImpReplaceImpID, account *config.Account, errs []error) { 394 errs = nil 395 var err error 396 var r io.ReadCloser = httpRequest.Body 397 reqContentEncoding := httputil.ContentEncoding(httpRequest.Header.Get("Content-Encoding")) 398 if reqContentEncoding != "" { 399 if !deps.cfg.Compression.Request.IsSupported(reqContentEncoding) { 400 errs = []error{fmt.Errorf("Content-Encoding of type %s is not supported", reqContentEncoding)} 401 return 402 } else { 403 r, err = getCompressionEnabledReader(httpRequest.Body, reqContentEncoding) 404 if err != nil { 405 errs = []error{err} 406 return 407 } 408 } 409 } 410 defer r.Close() 411 limitedReqReader := &io.LimitedReader{ 412 R: r, 413 N: deps.cfg.MaxRequestSize, 414 } 415 416 requestJson, err := io.ReadAll(limitedReqReader) 417 if err != nil { 418 errs = []error{err} 419 return 420 } 421 422 if limitedReqReader.N <= 0 { 423 // Limited Reader returns 0 if the request was exactly at the max size or over the limit. 424 // This is because it only reads up to N bytes. To check if the request was too large, 425 // we need to look at the next byte of its underlying reader, limitedReader.R. 426 if _, err := limitedReqReader.R.Read(make([]byte, 1)); err != io.EOF { 427 // Discard the rest of the request body so that the connection can be reused. 428 io.Copy(io.Discard, httpRequest.Body) 429 errs = []error{fmt.Errorf("request size exceeded max size of %d bytes.", deps.cfg.MaxRequestSize)} 430 return 431 } 432 } 433 434 req = &openrtb_ext.RequestWrapper{} 435 req.BidRequest = &openrtb2.BidRequest{} 436 437 requestJson, rejectErr := hookExecutor.ExecuteEntrypointStage(httpRequest, requestJson) 438 if rejectErr != nil { 439 errs = []error{rejectErr} 440 if err = json.Unmarshal(requestJson, req.BidRequest); err != nil { 441 glog.Errorf("Failed to unmarshal BidRequest during entrypoint rejection: %s", err) 442 } 443 return 444 } 445 446 timeout := parseTimeout(requestJson, time.Duration(storedRequestTimeoutMillis)*time.Millisecond) 447 ctx, cancel := context.WithTimeout(context.Background(), timeout) 448 defer cancel() 449 450 impInfo, errs := parseImpInfo(requestJson) 451 if len(errs) > 0 { 452 return nil, nil, nil, nil, nil, nil, errs 453 } 454 455 storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(ctx, requestJson, impInfo) 456 if len(errs) > 0 { 457 return 458 } 459 460 accountId, isAppReq, errs := getAccountIdFromRawRequest(hasStoredBidRequest, storedRequests[storedBidRequestId], requestJson) 461 // fill labels here in order to pass correct metrics in case of errors 462 if isAppReq { 463 labels.Source = metrics.DemandApp 464 labels.RType = metrics.ReqTypeORTB2App 465 labels.PubID = accountId 466 } else { // is Site request 467 labels.Source = metrics.DemandWeb 468 labels.PubID = accountId 469 } 470 if errs != nil { 471 return 472 } 473 474 // Look up account 475 account, errs = accountService.GetAccount(ctx, deps.cfg, deps.accounts, accountId, deps.metricsEngine) 476 if len(errs) > 0 { 477 return 478 } 479 480 hookExecutor.SetAccount(account) 481 requestJson, rejectErr = hookExecutor.ExecuteRawAuctionStage(requestJson) 482 if rejectErr != nil { 483 errs = []error{rejectErr} 484 if err = json.Unmarshal(requestJson, req.BidRequest); err != nil { 485 glog.Errorf("Failed to unmarshal BidRequest during raw auction stage rejection: %s", err) 486 } 487 return 488 } 489 490 // retrieve storedRequests and storedImps once more in case stored data was changed by the raw auction hook 491 if hasPayloadUpdatesAt(hooks.StageRawAuctionRequest.String(), hookExecutor.GetOutcomes()) { 492 impInfo, errs = parseImpInfo(requestJson) 493 if len(errs) > 0 { 494 return nil, nil, nil, nil, nil, nil, errs 495 } 496 storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs = deps.getStoredRequests(ctx, requestJson, impInfo) 497 if len(errs) > 0 { 498 return 499 } 500 } 501 502 // Fetch the Stored Request data and merge it into the HTTP request. 503 if requestJson, impExtInfoMap, errs = deps.processStoredRequests(requestJson, impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest); len(errs) > 0 { 504 return 505 } 506 507 //Stored auction responses should be processed after stored requests due to possible impression modification 508 storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, requestJson, deps.storedRespFetcher, deps.bidderMap) 509 if len(errs) > 0 { 510 return nil, nil, nil, nil, nil, nil, errs 511 } 512 513 if err := json.Unmarshal(requestJson, req.BidRequest); err != nil { 514 errs = []error{err} 515 return 516 } 517 518 if err := mergeBidderParams(req); err != nil { 519 errs = []error{err} 520 return 521 } 522 523 // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). 524 deps.setFieldsImplicitly(httpRequest, req) 525 526 if err := ortb.SetDefaults(req); err != nil { 527 errs = []error{err} 528 return 529 } 530 531 if err := processInterstitials(req); err != nil { 532 errs = []error{err} 533 return 534 } 535 536 lmt.ModifyForIOS(req.BidRequest) 537 538 hasStoredResponses := len(storedAuctionResponses) > 0 539 errL := deps.validateRequest(req, false, hasStoredResponses, storedBidResponses, hasStoredBidRequest) 540 if len(errL) > 0 { 541 errs = append(errs, errL...) 542 } 543 544 return 545 } 546 547 func getCompressionEnabledReader(body io.ReadCloser, contentEncoding httputil.ContentEncoding) (io.ReadCloser, error) { 548 switch contentEncoding { 549 case httputil.ContentEncodingGZIP: 550 return gzip.NewReader(body) 551 default: 552 return nil, fmt.Errorf("unsupported compression type '%s'", contentEncoding) 553 } 554 } 555 556 // hasPayloadUpdatesAt checks if there are any successful payload updates at given stage 557 func hasPayloadUpdatesAt(stageName string, outcomes []hookexecution.StageOutcome) bool { 558 for _, outcome := range outcomes { 559 if stageName != outcome.Stage { 560 continue 561 } 562 563 for _, group := range outcome.Groups { 564 for _, invocationResult := range group.InvocationResults { 565 if invocationResult.Status == hookexecution.StatusSuccess && 566 invocationResult.Action == hookexecution.ActionUpdate { 567 return true 568 } 569 } 570 } 571 } 572 573 return false 574 } 575 576 // parseTimeout returns parses tmax from the requestJson, or returns the default if it doesn't exist. 577 // 578 // requestJson should be the content of the POST body. 579 // 580 // If the request defines tmax explicitly, then this will return that duration in milliseconds. 581 // If not, it will return the default timeout. 582 func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duration { 583 if tmax, dataType, _, err := jsonparser.Get(requestJson, "tmax"); dataType != jsonparser.NotExist && err == nil { 584 if tmaxInt, err := strconv.Atoi(string(tmax)); err == nil && tmaxInt > 0 { 585 return time.Duration(tmaxInt) * time.Millisecond 586 } 587 } 588 return defaultTimeout 589 } 590 591 // mergeBidderParams merges bidder parameters in req.ext down to the imp[].ext level, with 592 // priority given to imp[].ext in case of a conflict. No validation of bidder parameters or 593 // of the ext json is performed. Unmarshal errors are not expected since the ext json was 594 // validated during the bid request unmarshal. 595 func mergeBidderParams(req *openrtb_ext.RequestWrapper) error { 596 reqExt, err := req.GetRequestExt() 597 if err != nil { 598 return nil 599 } 600 601 prebid := reqExt.GetPrebid() 602 if prebid == nil { 603 return nil 604 } 605 606 bidderParamsJson := prebid.BidderParams 607 if len(bidderParamsJson) == 0 { 608 return nil 609 } 610 611 bidderParams := map[string]map[string]json.RawMessage{} 612 if err := json.Unmarshal(bidderParamsJson, &bidderParams); err != nil { 613 return nil 614 } 615 616 for i, imp := range req.GetImp() { 617 impExt, err := imp.GetImpExt() 618 if err != nil { 619 continue 620 } 621 622 // merges bidder parameters passed at req.ext level with imp[].ext.BIDDER level 623 if err := mergeBidderParamsImpExt(impExt, bidderParams); err != nil { 624 return fmt.Errorf("error processing bidder parameters for imp[%d]: %s", i, err.Error()) 625 } 626 627 // merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder.BIDDER level 628 if err := mergeBidderParamsImpExtPrebid(impExt, bidderParams); err != nil { 629 return fmt.Errorf("error processing bidder parameters for imp[%d]: %s", i, err.Error()) 630 } 631 } 632 633 return nil 634 } 635 636 // mergeBidderParamsImpExt merges bidder parameters in req.ext down to the imp[].ext.BIDDER 637 // level, giving priority to imp[].ext.BIDDER in case of a conflict. Unmarshal errors are not 638 // expected since the ext json was validated during the bid request unmarshal. 639 func mergeBidderParamsImpExt(impExt *openrtb_ext.ImpExt, reqExtParams map[string]map[string]json.RawMessage) error { 640 extMap := impExt.GetExt() 641 extMapModified := false 642 643 for bidder, params := range reqExtParams { 644 if !isPossibleBidder(bidder) { 645 continue 646 } 647 648 impExtBidder, impExtBidderExists := extMap[bidder] 649 if !impExtBidderExists || impExtBidder == nil { 650 continue 651 } 652 653 impExtBidderMap := map[string]json.RawMessage{} 654 if len(impExtBidder) > 0 { 655 if err := json.Unmarshal(impExtBidder, &impExtBidderMap); err != nil { 656 continue 657 } 658 } 659 660 modified := false 661 for key, value := range params { 662 if _, present := impExtBidderMap[key]; !present { 663 impExtBidderMap[key] = value 664 modified = true 665 } 666 } 667 668 if modified { 669 impExtBidderJson, err := json.Marshal(impExtBidderMap) 670 if err != nil { 671 return fmt.Errorf("error marshalling ext.BIDDER: %s", err.Error()) 672 } 673 extMap[bidder] = impExtBidderJson 674 extMapModified = true 675 } 676 } 677 678 if extMapModified { 679 impExt.SetExt(extMap) 680 } 681 682 return nil 683 } 684 685 // mergeBidderParamsImpExtPrebid merges bidder parameters in req.ext down to the imp[].ext.prebid.bidder.BIDDER 686 // level, giving priority to imp[].ext.prebid.bidder.BIDDER in case of a conflict. 687 func mergeBidderParamsImpExtPrebid(impExt *openrtb_ext.ImpExt, reqExtParams map[string]map[string]json.RawMessage) error { 688 prebid := impExt.GetPrebid() 689 prebidModified := false 690 691 if prebid == nil || len(prebid.Bidder) == 0 { 692 return nil 693 } 694 695 for bidder, params := range reqExtParams { 696 impExtPrebidBidder, impExtPrebidBidderExists := prebid.Bidder[bidder] 697 if !impExtPrebidBidderExists || impExtPrebidBidder == nil { 698 continue 699 } 700 701 impExtPrebidBidderMap := map[string]json.RawMessage{} 702 if len(impExtPrebidBidder) > 0 { 703 if err := json.Unmarshal(impExtPrebidBidder, &impExtPrebidBidderMap); err != nil { 704 continue 705 } 706 } 707 708 modified := false 709 for key, value := range params { 710 if _, present := impExtPrebidBidderMap[key]; !present { 711 impExtPrebidBidderMap[key] = value 712 modified = true 713 } 714 } 715 716 if modified { 717 impExtPrebidBidderJson, err := json.Marshal(impExtPrebidBidderMap) 718 if err != nil { 719 return fmt.Errorf("error marshalling ext.prebid.bidder.BIDDER: %s", err.Error()) 720 } 721 prebid.Bidder[bidder] = impExtPrebidBidderJson 722 prebidModified = true 723 } 724 } 725 726 if prebidModified { 727 impExt.SetPrebid(prebid) 728 } 729 730 return nil 731 } 732 733 func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp, hasStoredBidRequest bool) []error { 734 errL := []error{} 735 if req.ID == "" { 736 return []error{errors.New("request missing required field: \"id\"")} 737 } 738 739 if req.TMax < 0 { 740 return []error{fmt.Errorf("request.tmax must be nonnegative. Got %d", req.TMax)} 741 } 742 743 if req.LenImp() < 1 { 744 return []error{errors.New("request.imp must contain at least one element.")} 745 } 746 747 if len(req.Cur) > 1 { 748 req.Cur = req.Cur[0:1] 749 errL = append(errL, &errortypes.Warning{Message: fmt.Sprintf("A prebid request can only process one currency. Taking the first currency in the list, %s, as the active currency", req.Cur[0])}) 750 } 751 752 // If automatically filling source TID is enabled then validate that 753 // source.TID exists and If it doesn't, fill it with a randomly generated UUID 754 if deps.cfg.AutoGenSourceTID { 755 if err := validateAndFillSourceTID(req, deps.cfg.GenerateRequestID, hasStoredBidRequest, isAmp); err != nil { 756 return []error{err} 757 } 758 } 759 760 var aliases map[string]string 761 reqExt, err := req.GetRequestExt() 762 if err != nil { 763 return []error{fmt.Errorf("request.ext is invalid: %v", err)} 764 } 765 766 reqPrebid := reqExt.GetPrebid() 767 if err := deps.parseBidExt(req); err != nil { 768 return []error{err} 769 } 770 771 if reqPrebid != nil { 772 aliases = reqPrebid.Aliases 773 774 if err := deps.validateAliases(aliases); err != nil { 775 return []error{err} 776 } 777 778 if err := deps.validateAliasesGVLIDs(reqPrebid.AliasGVLIDs, aliases); err != nil { 779 return []error{err} 780 } 781 782 if err := deps.validateBidAdjustmentFactors(reqPrebid.BidAdjustmentFactors, aliases); err != nil { 783 return []error{err} 784 } 785 786 if err := validateSChains(reqPrebid.SChains); err != nil { 787 return []error{err} 788 } 789 790 if err := deps.validateEidPermissions(reqPrebid.Data, aliases); err != nil { 791 return []error{err} 792 } 793 794 if err := currency.ValidateCustomRates(reqPrebid.CurrencyConversions); err != nil { 795 return []error{err} 796 } 797 } 798 799 if err := mapSChains(req); err != nil { 800 return []error{err} 801 } 802 803 if err := validateOrFillChannel(req, isAmp); err != nil { 804 return []error{err} 805 } 806 807 if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { 808 return append(errL, errors.New("request.site or request.app must be defined, but not both.")) 809 } 810 811 if errs := validateRequestExt(req); len(errs) != 0 { 812 if errortypes.ContainsFatalError(errs) { 813 return append(errL, errs...) 814 } 815 errL = append(errL, errs...) 816 } 817 818 if err := deps.validateSite(req); err != nil { 819 return append(errL, err) 820 } 821 822 if err := deps.validateApp(req); err != nil { 823 return append(errL, err) 824 } 825 826 var gpp gpplib.GppContainer 827 if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { 828 gpp, err = gpplib.Parse(req.BidRequest.Regs.GPP) 829 if err != nil { 830 errL = append(errL, &errortypes.Warning{ 831 Message: fmt.Sprintf("GPP consent string is invalid and will be ignored. (%v)", err), 832 WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) 833 } 834 } 835 836 if errs := deps.validateUser(req, aliases, gpp); errs != nil { 837 if len(errs) > 0 { 838 errL = append(errL, errs...) 839 } 840 if errortypes.ContainsFatalError(errs) { 841 return errL 842 } 843 } 844 845 if errs := validateRegs(req, gpp); errs != nil { 846 if len(errs) > 0 { 847 errL = append(errL, errs...) 848 } 849 if errortypes.ContainsFatalError(errs) { 850 return errL 851 } 852 } 853 854 if err := validateDevice(req.Device); err != nil { 855 return append(errL, err) 856 } 857 858 if ccpaPolicy, err := ccpa.ReadFromRequestWrapper(req, gpp); err != nil { 859 errL = append(errL, err) 860 if errortypes.ContainsFatalError([]error{err}) { 861 return errL 862 } 863 } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { 864 if _, invalidConsent := err.(*errortypes.Warning); invalidConsent { 865 errL = append(errL, &errortypes.Warning{ 866 Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err), 867 WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) 868 regsExt, err := req.GetRegExt() 869 if err != nil { 870 return append(errL, err) 871 } 872 regsExt.SetUSPrivacy("") 873 } else { 874 return append(errL, err) 875 } 876 } 877 878 impIDs := make(map[string]int, req.LenImp()) 879 for i, imp := range req.GetImp() { 880 // check for unique imp id 881 if firstIndex, ok := impIDs[imp.ID]; ok { 882 errL = append(errL, fmt.Errorf(`request.imp[%d].id and request.imp[%d].id are both "%s". Imp IDs must be unique.`, firstIndex, i, imp.ID)) 883 } 884 impIDs[imp.ID] = i 885 886 errs := deps.validateImp(imp, aliases, i, hasStoredResponses, storedBidResp) 887 if len(errs) > 0 { 888 errL = append(errL, errs...) 889 } 890 if errortypes.ContainsFatalError(errs) { 891 return errL 892 } 893 } 894 895 return errL 896 } 897 898 // mapSChains maps an schain defined in an ORTB 2.4 location (req.ext.schain) to the ORTB 2.5 location 899 // (req.source.ext.schain) if no ORTB 2.5 schain (req.source.ext.schain, req.ext.prebid.schains) exists. 900 // An ORTB 2.4 schain is always deleted from the 2.4 location regardless of whether an ORTB 2.5 schain exists. 901 func mapSChains(req *openrtb_ext.RequestWrapper) error { 902 reqExt, err := req.GetRequestExt() 903 if err != nil { 904 return fmt.Errorf("req.ext is invalid: %v", err) 905 } 906 sourceExt, err := req.GetSourceExt() 907 if err != nil { 908 return fmt.Errorf("source.ext is invalid: %v", err) 909 } 910 911 reqExtSChain := reqExt.GetSChain() 912 reqExt.SetSChain(nil) 913 914 if reqPrebid := reqExt.GetPrebid(); reqPrebid != nil && reqPrebid.SChains != nil { 915 return nil 916 } else if sourceExt.GetSChain() != nil { 917 return nil 918 } else if reqExtSChain != nil { 919 sourceExt.SetSChain(reqExtSChain) 920 } 921 return nil 922 } 923 924 func validateAndFillSourceTID(req *openrtb_ext.RequestWrapper, generateRequestID bool, hasStoredBidRequest bool, isAmp bool) error { 925 if req.Source == nil { 926 req.Source = &openrtb2.Source{} 927 } 928 929 if req.Source.TID == "" || req.Source.TID == "{{UUID}}" || (generateRequestID && (isAmp || hasStoredBidRequest)) { 930 rawUUID, err := uuid.NewV4() 931 if err != nil { 932 return errors.New("error creating a random UUID for source.tid") 933 } 934 req.Source.TID = rawUUID.String() 935 } 936 937 for _, impWrapper := range req.GetImp() { 938 ie, _ := impWrapper.GetImpExt() 939 if ie.GetTid() == "" || ie.GetTid() == "{{UUID}}" || (generateRequestID && (isAmp || hasStoredBidRequest)) { 940 rawUUID, err := uuid.NewV4() 941 if err != nil { 942 return errors.New("imp.ext.tid missing in the imp and error creating a random UID") 943 } 944 ie.SetTid(rawUUID.String()) 945 impWrapper.RebuildImp() 946 } 947 } 948 949 return nil 950 } 951 952 func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[string]float64, aliases map[string]string) error { 953 for bidderToAdjust, adjustmentFactor := range adjustmentFactors { 954 if adjustmentFactor <= 0 { 955 return fmt.Errorf("request.ext.prebid.bidadjustmentfactors.%s must be a positive number. Got %f", bidderToAdjust, adjustmentFactor) 956 } 957 if _, isBidder := deps.bidderMap[bidderToAdjust]; !isBidder { 958 if _, isAlias := aliases[bidderToAdjust]; !isAlias { 959 return fmt.Errorf("request.ext.prebid.bidadjustmentfactors.%s is not a known bidder or alias", bidderToAdjust) 960 } 961 } 962 } 963 return nil 964 } 965 966 func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { 967 _, err := schain.BidderToPrebidSChains(sChains) 968 return err 969 } 970 971 func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, aliases map[string]string) error { 972 if prebid == nil { 973 return nil 974 } 975 976 uniqueSources := make(map[string]struct{}, len(prebid.EidPermissions)) 977 for i, eid := range prebid.EidPermissions { 978 if len(eid.Source) == 0 { 979 return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing required field: "source"`, i) 980 } 981 982 if _, exists := uniqueSources[eid.Source]; exists { 983 return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] duplicate entry with field: "source"`, i) 984 } 985 uniqueSources[eid.Source] = struct{}{} 986 987 if len(eid.Bidders) == 0 { 988 return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing or empty required field: "bidders"`, i) 989 } 990 991 if err := validateBidders(eid.Bidders, deps.bidderMap, aliases); err != nil { 992 return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] contains %v`, i, err) 993 } 994 } 995 996 return nil 997 } 998 999 func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.BidderName, knownAliases map[string]string) error { 1000 for _, bidder := range bidders { 1001 if bidder == "*" { 1002 if len(bidders) > 1 { 1003 return errors.New(`bidder wildcard "*" mixed with specific bidders`) 1004 } 1005 } else { 1006 _, isCoreBidder := knownBidders[bidder] 1007 _, isAlias := knownAliases[bidder] 1008 if !isCoreBidder && !isAlias { 1009 return fmt.Errorf(`unrecognized bidder "%v"`, bidder) 1010 } 1011 } 1012 } 1013 return nil 1014 } 1015 1016 func (deps *endpointDeps) validateImp(imp *openrtb_ext.ImpWrapper, aliases map[string]string, index int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { 1017 if imp.ID == "" { 1018 return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)} 1019 } 1020 1021 if len(imp.Metric) != 0 { 1022 return []error{fmt.Errorf("request.imp[%d].metric is not yet supported by prebid-server. Support may be added in the future", index)} 1023 } 1024 1025 if imp.Banner == nil && imp.Video == nil && imp.Audio == nil && imp.Native == nil { 1026 return []error{fmt.Errorf("request.imp[%d] must contain at least one of \"banner\", \"video\", \"audio\", or \"native\"", index)} 1027 } 1028 1029 if err := validateBanner(imp.Banner, index, isInterstitial(imp)); err != nil { 1030 return []error{err} 1031 } 1032 1033 if err := validateVideo(imp.Video, index); err != nil { 1034 return []error{err} 1035 } 1036 1037 if err := validateAudio(imp.Audio, index); err != nil { 1038 return []error{err} 1039 } 1040 1041 if err := fillAndValidateNative(imp.Native, index); err != nil { 1042 return []error{err} 1043 } 1044 1045 if err := validatePmp(imp.PMP, index); err != nil { 1046 return []error{err} 1047 } 1048 1049 errL := deps.validateImpExt(imp, aliases, index, hasStoredResponses, storedBidResp) 1050 if len(errL) != 0 { 1051 return errL 1052 } 1053 1054 return nil 1055 } 1056 1057 func isInterstitial(imp *openrtb_ext.ImpWrapper) bool { 1058 return imp.Instl == 1 1059 } 1060 1061 func validateBanner(banner *openrtb2.Banner, impIndex int, isInterstitial bool) error { 1062 if banner == nil { 1063 return nil 1064 } 1065 1066 // The following fields were previously uints in the OpenRTB library we use, but have 1067 // since been changed to ints. We decided to maintain the non-negative check. 1068 if banner.W != nil && *banner.W < 0 { 1069 return fmt.Errorf("request.imp[%d].banner.w must be a positive number", impIndex) 1070 } 1071 if banner.H != nil && *banner.H < 0 { 1072 return fmt.Errorf("request.imp[%d].banner.h must be a positive number", impIndex) 1073 } 1074 1075 // The following fields are deprecated in the OpenRTB 2.5 spec but are still present 1076 // in the OpenRTB library we use. Enforce they are not specified. 1077 if banner.WMin != 0 { 1078 return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmin\". Use the \"format\" array instead.", impIndex) 1079 } 1080 if banner.WMax != 0 { 1081 return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmax\". Use the \"format\" array instead.", impIndex) 1082 } 1083 if banner.HMin != 0 { 1084 return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"hmin\". Use the \"format\" array instead.", impIndex) 1085 } 1086 if banner.HMax != 0 { 1087 return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"hmax\". Use the \"format\" array instead.", impIndex) 1088 } 1089 1090 hasRootSize := banner.H != nil && banner.W != nil && *banner.H > 0 && *banner.W > 0 1091 if !hasRootSize && len(banner.Format) == 0 && !isInterstitial { 1092 return fmt.Errorf("request.imp[%d].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements.", impIndex) 1093 } 1094 1095 for i, format := range banner.Format { 1096 if err := validateFormat(&format, impIndex, i); err != nil { 1097 return err 1098 } 1099 } 1100 1101 return nil 1102 } 1103 1104 func validateVideo(video *openrtb2.Video, impIndex int) error { 1105 if video == nil { 1106 return nil 1107 } 1108 1109 if len(video.MIMEs) < 1 { 1110 return fmt.Errorf("request.imp[%d].video.mimes must contain at least one supported MIME type", impIndex) 1111 } 1112 1113 // The following fields were previously uints in the OpenRTB library we use, but have 1114 // since been changed to ints. We decided to maintain the non-negative check. 1115 if video.W < 0 { 1116 return fmt.Errorf("request.imp[%d].video.w must be a positive number", impIndex) 1117 } 1118 if video.H < 0 { 1119 return fmt.Errorf("request.imp[%d].video.h must be a positive number", impIndex) 1120 } 1121 if video.MinBitRate < 0 { 1122 return fmt.Errorf("request.imp[%d].video.minbitrate must be a positive number", impIndex) 1123 } 1124 if video.MaxBitRate < 0 { 1125 return fmt.Errorf("request.imp[%d].video.maxbitrate must be a positive number", impIndex) 1126 } 1127 1128 return nil 1129 } 1130 1131 func validateAudio(audio *openrtb2.Audio, impIndex int) error { 1132 if audio == nil { 1133 return nil 1134 } 1135 1136 if len(audio.MIMEs) < 1 { 1137 return fmt.Errorf("request.imp[%d].audio.mimes must contain at least one supported MIME type", impIndex) 1138 } 1139 1140 // The following fields were previously uints in the OpenRTB library we use, but have 1141 // since been changed to ints. We decided to maintain the non-negative check. 1142 if audio.Sequence < 0 { 1143 return fmt.Errorf("request.imp[%d].audio.sequence must be a positive number", impIndex) 1144 } 1145 if audio.MaxSeq < 0 { 1146 return fmt.Errorf("request.imp[%d].audio.maxseq must be a positive number", impIndex) 1147 } 1148 if audio.MinBitrate < 0 { 1149 return fmt.Errorf("request.imp[%d].audio.minbitrate must be a positive number", impIndex) 1150 } 1151 if audio.MaxBitrate < 0 { 1152 return fmt.Errorf("request.imp[%d].audio.maxbitrate must be a positive number", impIndex) 1153 } 1154 1155 return nil 1156 } 1157 1158 // fillAndValidateNative validates the request, and assigns the Asset IDs as recommended by the Native v1.2 spec. 1159 func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { 1160 if n == nil { 1161 return nil 1162 } 1163 1164 if len(n.Request) == 0 { 1165 return fmt.Errorf("request.imp[%d].native missing required property \"request\"", impIndex) 1166 } 1167 var nativePayload nativeRequests.Request 1168 if err := json.Unmarshal(json.RawMessage(n.Request), &nativePayload); err != nil { 1169 return err 1170 } 1171 1172 if err := validateNativeContextTypes(nativePayload.Context, nativePayload.ContextSubType, impIndex); err != nil { 1173 return err 1174 } 1175 if err := validateNativePlacementType(nativePayload.PlcmtType, impIndex); err != nil { 1176 return err 1177 } 1178 if err := fillAndValidateNativeAssets(nativePayload.Assets, impIndex); err != nil { 1179 return err 1180 } 1181 if err := validateNativeEventTrackers(nativePayload.EventTrackers, impIndex); err != nil { 1182 return err 1183 } 1184 1185 serialized, err := json.Marshal(nativePayload) 1186 if err != nil { 1187 return err 1188 } 1189 n.Request = string(serialized) 1190 return nil 1191 } 1192 1193 func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.ContextSubType, impIndex int) error { 1194 if cType == 0 { 1195 // Context is only recommended, so none is a valid type. 1196 return nil 1197 } 1198 if cType < native1.ContextTypeContent || (cType > native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound) { 1199 return fmt.Errorf("request.imp[%d].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) 1200 } 1201 if cSubtype < 0 { 1202 return fmt.Errorf("request.imp[%d].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) 1203 } 1204 if cSubtype == 0 { 1205 return nil 1206 } 1207 if cSubtype >= native1.ContextSubTypeGeneral && cSubtype <= native1.ContextSubTypeUserGenerated { 1208 if cType != native1.ContextTypeContent && cType < openrtb_ext.NativeExchangeSpecificLowerBound { 1209 return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) 1210 } 1211 return nil 1212 } 1213 if cSubtype >= native1.ContextSubTypeSocial && cSubtype <= native1.ContextSubTypeChat { 1214 if cType != native1.ContextTypeSocial && cType < openrtb_ext.NativeExchangeSpecificLowerBound { 1215 return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) 1216 } 1217 return nil 1218 } 1219 if cSubtype >= native1.ContextSubTypeSelling && cSubtype <= native1.ContextSubTypeProductReview { 1220 if cType != native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound { 1221 return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) 1222 } 1223 return nil 1224 } 1225 if cSubtype >= openrtb_ext.NativeExchangeSpecificLowerBound { 1226 return nil 1227 } 1228 1229 return fmt.Errorf("request.imp[%d].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) 1230 } 1231 1232 func validateNativePlacementType(pt native1.PlacementType, impIndex int) error { 1233 if pt == 0 { 1234 // Placement Type is only recommended, not required. 1235 return nil 1236 } 1237 if pt < native1.PlacementTypeFeed || (pt > native1.PlacementTypeRecommendationWidget && pt < openrtb_ext.NativeExchangeSpecificLowerBound) { 1238 return fmt.Errorf("request.imp[%d].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex) 1239 } 1240 return nil 1241 } 1242 1243 func fillAndValidateNativeAssets(assets []nativeRequests.Asset, impIndex int) error { 1244 if len(assets) < 1 { 1245 return fmt.Errorf("request.imp[%d].native.request.assets must be an array containing at least one object", impIndex) 1246 } 1247 1248 assetIDs := make(map[int64]struct{}, len(assets)) 1249 1250 // If none of the asset IDs are defined by the caller, then prebid server should assign its own unique IDs. But 1251 // if the caller did assign its own asset IDs, then prebid server will respect those IDs 1252 assignAssetIDs := true 1253 for i := 0; i < len(assets); i++ { 1254 assignAssetIDs = assignAssetIDs && (assets[i].ID == 0) 1255 } 1256 1257 for i := 0; i < len(assets); i++ { 1258 if err := validateNativeAsset(assets[i], impIndex, i); err != nil { 1259 return err 1260 } 1261 1262 if assignAssetIDs { 1263 assets[i].ID = int64(i) 1264 continue 1265 } 1266 1267 // Each asset should have a unique ID thats assigned by the caller 1268 if _, ok := assetIDs[assets[i].ID]; ok { 1269 return fmt.Errorf("request.imp[%d].native.request.assets[%d].id is already being used by another asset. Each asset ID must be unique.", impIndex, i) 1270 } 1271 1272 assetIDs[assets[i].ID] = struct{}{} 1273 } 1274 1275 return nil 1276 } 1277 1278 func validateNativeAsset(asset nativeRequests.Asset, impIndex int, assetIndex int) error { 1279 assetErr := "request.imp[%d].native.request.assets[%d] must define exactly one of {title, img, video, data}" 1280 foundType := false 1281 1282 if asset.Title != nil { 1283 foundType = true 1284 if err := validateNativeAssetTitle(asset.Title, impIndex, assetIndex); err != nil { 1285 return err 1286 } 1287 } 1288 1289 if asset.Img != nil { 1290 if foundType { 1291 return fmt.Errorf(assetErr, impIndex, assetIndex) 1292 } 1293 foundType = true 1294 if err := validateNativeAssetImage(asset.Img, impIndex, assetIndex); err != nil { 1295 return err 1296 } 1297 } 1298 1299 if asset.Video != nil { 1300 if foundType { 1301 return fmt.Errorf(assetErr, impIndex, assetIndex) 1302 } 1303 foundType = true 1304 if err := validateNativeAssetVideo(asset.Video, impIndex, assetIndex); err != nil { 1305 return err 1306 } 1307 } 1308 1309 if asset.Data != nil { 1310 if foundType { 1311 return fmt.Errorf(assetErr, impIndex, assetIndex) 1312 } 1313 foundType = true 1314 if err := validateNativeAssetData(asset.Data, impIndex, assetIndex); err != nil { 1315 return err 1316 } 1317 } 1318 1319 if !foundType { 1320 return fmt.Errorf(assetErr, impIndex, assetIndex) 1321 } 1322 1323 return nil 1324 } 1325 1326 func validateNativeEventTrackers(trackers []nativeRequests.EventTracker, impIndex int) error { 1327 for i := 0; i < len(trackers); i++ { 1328 if err := validateNativeEventTracker(trackers[i], impIndex, i); err != nil { 1329 return err 1330 } 1331 } 1332 return nil 1333 } 1334 1335 func validateNativeAssetTitle(title *nativeRequests.Title, impIndex int, assetIndex int) error { 1336 if title.Len < 1 { 1337 return fmt.Errorf("request.imp[%d].native.request.assets[%d].title.len must be a positive number", impIndex, assetIndex) 1338 } 1339 return nil 1340 } 1341 1342 func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex int, eventIndex int) error { 1343 if tracker.Event < native1.EventTypeImpression || (tracker.Event > native1.EventTypeViewableVideo50 && tracker.Event < openrtb_ext.NativeExchangeSpecificLowerBound) { 1344 return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) 1345 } 1346 if len(tracker.Methods) < 1 { 1347 return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) 1348 } 1349 for methodIndex, method := range tracker.Methods { 1350 if method < native1.EventTrackingMethodImage || (method > native1.EventTrackingMethodJS && method < openrtb_ext.NativeExchangeSpecificLowerBound) { 1351 return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].methods[%d] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex, methodIndex) 1352 } 1353 } 1354 1355 return nil 1356 } 1357 1358 func validateNativeAssetImage(img *nativeRequests.Image, impIndex int, assetIndex int) error { 1359 if img.W < 0 { 1360 return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.w must be a positive integer", impIndex, assetIndex) 1361 } 1362 if img.H < 0 { 1363 return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.h must be a positive integer", impIndex, assetIndex) 1364 } 1365 if img.WMin < 0 { 1366 return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.wmin must be a positive integer", impIndex, assetIndex) 1367 } 1368 if img.HMin < 0 { 1369 return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.hmin must be a positive integer", impIndex, assetIndex) 1370 } 1371 return nil 1372 } 1373 1374 func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIndex int) error { 1375 if len(video.MIMEs) < 1 { 1376 return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.mimes must be an array with at least one MIME type", impIndex, assetIndex) 1377 } 1378 if video.MinDuration < 1 { 1379 return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.minduration must be a positive integer", impIndex, assetIndex) 1380 } 1381 if video.MaxDuration < 1 { 1382 return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.maxduration must be a positive integer", impIndex, assetIndex) 1383 } 1384 if err := validateNativeVideoProtocols(video.Protocols, impIndex, assetIndex); err != nil { 1385 return err 1386 } 1387 1388 return nil 1389 } 1390 1391 func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex int) error { 1392 if data.Type < native1.DataAssetTypeSponsored || (data.Type > native1.DataAssetTypeCTAText && data.Type < 500) { 1393 return fmt.Errorf("request.imp[%d].native.request.assets[%d].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex, assetIndex) 1394 } 1395 1396 return nil 1397 } 1398 1399 func validateNativeVideoProtocols(protocols []adcom1.MediaCreativeSubtype, impIndex int, assetIndex int) error { 1400 if len(protocols) < 1 { 1401 return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols must be an array with at least one element", impIndex, assetIndex) 1402 } 1403 for i := 0; i < len(protocols); i++ { 1404 if err := validateNativeVideoProtocol(protocols[i], impIndex, assetIndex, i); err != nil { 1405 return err 1406 } 1407 } 1408 return nil 1409 } 1410 1411 func validateNativeVideoProtocol(protocol adcom1.MediaCreativeSubtype, impIndex int, assetIndex int, protocolIndex int) error { 1412 if protocol < adcom1.CreativeVAST10 || protocol > adcom1.CreativeDAAST10Wrapper { 1413 return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols[%d] is invalid. See Section 5.8: https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=52", impIndex, assetIndex, protocolIndex) 1414 } 1415 return nil 1416 } 1417 1418 func validateFormat(format *openrtb2.Format, impIndex, formatIndex int) error { 1419 usesHW := format.W != 0 || format.H != 0 1420 usesRatios := format.WMin != 0 || format.WRatio != 0 || format.HRatio != 0 1421 1422 // The following fields were previously uints in the OpenRTB library we use, but have 1423 // since been changed to ints. We decided to maintain the non-negative check. 1424 if format.W < 0 { 1425 return fmt.Errorf("request.imp[%d].banner.format[%d].w must be a positive number", impIndex, formatIndex) 1426 } 1427 if format.H < 0 { 1428 return fmt.Errorf("request.imp[%d].banner.format[%d].h must be a positive number", impIndex, formatIndex) 1429 } 1430 if format.WRatio < 0 { 1431 return fmt.Errorf("request.imp[%d].banner.format[%d].wratio must be a positive number", impIndex, formatIndex) 1432 } 1433 if format.HRatio < 0 { 1434 return fmt.Errorf("request.imp[%d].banner.format[%d].hratio must be a positive number", impIndex, formatIndex) 1435 } 1436 if format.WMin < 0 { 1437 return fmt.Errorf("request.imp[%d].banner.format[%d].wmin must be a positive number", impIndex, formatIndex) 1438 } 1439 1440 if usesHW && usesRatios { 1441 return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} *or* {wmin, wratio, hratio}, but not both. If both are valid, send two \"format\" objects in the request.", impIndex, formatIndex) 1442 } 1443 if !usesHW && !usesRatios { 1444 return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} (for static size requirements) *or* {wmin, wratio, hratio} (for flexible sizes) to be non-zero.", impIndex, formatIndex) 1445 } 1446 if usesHW && (format.W == 0 || format.H == 0) { 1447 return fmt.Errorf("Request imp[%d].banner.format[%d] must define non-zero \"h\" and \"w\" properties.", impIndex, formatIndex) 1448 } 1449 if usesRatios && (format.WMin == 0 || format.WRatio == 0 || format.HRatio == 0) { 1450 return fmt.Errorf("Request imp[%d].banner.format[%d] must define non-zero \"wmin\", \"wratio\", and \"hratio\" properties.", impIndex, formatIndex) 1451 } 1452 return nil 1453 } 1454 1455 func validatePmp(pmp *openrtb2.PMP, impIndex int) error { 1456 if pmp == nil { 1457 return nil 1458 } 1459 1460 for dealIndex, deal := range pmp.Deals { 1461 if deal.ID == "" { 1462 return fmt.Errorf("request.imp[%d].pmp.deals[%d] missing required field: \"id\"", impIndex, dealIndex) 1463 } 1464 } 1465 return nil 1466 } 1467 1468 func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases map[string]string, impIndex int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error { 1469 if len(imp.Ext) == 0 { 1470 return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)} 1471 } 1472 1473 impExt, err := imp.GetImpExt() 1474 if err != nil { 1475 return []error{err} 1476 } 1477 1478 prebid := impExt.GetOrCreatePrebid() 1479 prebidModified := false 1480 1481 if prebid.Bidder == nil { 1482 prebid.Bidder = make(map[string]json.RawMessage) 1483 } 1484 1485 ext := impExt.GetExt() 1486 extModified := false 1487 1488 // promote imp[].ext.BIDDER to newer imp[].ext.prebid.bidder.BIDDER location, with the later taking precedence 1489 for k, v := range ext { 1490 if isPossibleBidder(k) { 1491 if _, exists := prebid.Bidder[k]; !exists { 1492 prebid.Bidder[k] = v 1493 prebidModified = true 1494 } 1495 delete(ext, k) 1496 extModified = true 1497 } 1498 } 1499 1500 if hasStoredResponses && prebid.StoredAuctionResponse == nil { 1501 return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)} 1502 } 1503 1504 if len(storedBidResp) > 0 { 1505 if err := validateStoredBidRespAndImpExtBidders(prebid.Bidder, storedBidResp, imp.ID); err != nil { 1506 return []error{err} 1507 } 1508 } 1509 1510 errL := []error{} 1511 1512 for bidder, ext := range prebid.Bidder { 1513 coreBidder := bidder 1514 if tmp, isAlias := aliases[bidder]; isAlias { 1515 coreBidder = tmp 1516 } 1517 1518 if coreBidderNormalized, isValid := deps.bidderMap[coreBidder]; isValid { 1519 if err := deps.paramsValidator.Validate(coreBidderNormalized, ext); err != nil { 1520 return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)} 1521 } 1522 } else { 1523 if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled { 1524 errL = append(errL, &errortypes.BidderTemporarilyDisabled{Message: msg}) 1525 delete(prebid.Bidder, bidder) 1526 prebidModified = true 1527 } else { 1528 return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impIndex, bidder)} 1529 } 1530 } 1531 } 1532 1533 if len(prebid.Bidder) == 0 { 1534 errL = append(errL, fmt.Errorf("request.imp[%d].ext.prebid.bidder must contain at least one bidder", impIndex)) 1535 return errL 1536 } 1537 1538 if prebidModified { 1539 impExt.SetPrebid(prebid) 1540 } 1541 if extModified { 1542 impExt.SetExt(ext) 1543 } 1544 1545 return errL 1546 } 1547 1548 // isPossibleBidder determines if a bidder name is a potential real bidder. 1549 func isPossibleBidder(bidder string) bool { 1550 switch openrtb_ext.BidderName(bidder) { 1551 case openrtb_ext.BidderReservedContext: 1552 return false 1553 case openrtb_ext.BidderReservedData: 1554 return false 1555 case openrtb_ext.BidderReservedGPID: 1556 return false 1557 case openrtb_ext.BidderReservedPrebid: 1558 return false 1559 case openrtb_ext.BidderReservedSKAdN: 1560 return false 1561 case openrtb_ext.BidderReservedTID: 1562 return false 1563 case openrtb_ext.BidderReservedAE: 1564 return false 1565 default: 1566 return true 1567 } 1568 } 1569 1570 func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error { 1571 if _, err := req.GetRequestExt(); err != nil { 1572 return fmt.Errorf("request.ext is invalid: %v", err) 1573 } 1574 return nil 1575 } 1576 1577 func (deps *endpointDeps) validateAliases(aliases map[string]string) error { 1578 for alias, coreBidder := range aliases { 1579 if _, isCoreBidderDisabled := deps.disabledBidders[coreBidder]; isCoreBidderDisabled { 1580 return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, coreBidder) 1581 } 1582 1583 if _, isCoreBidder := deps.bidderMap[coreBidder]; !isCoreBidder { 1584 return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, coreBidder) 1585 } 1586 1587 if alias == coreBidder { 1588 return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", alias) 1589 } 1590 } 1591 return nil 1592 } 1593 1594 func (deps *endpointDeps) validateAliasesGVLIDs(aliasesGVLIDs map[string]uint16, aliases map[string]string) error { 1595 for alias, vendorId := range aliasesGVLIDs { 1596 1597 if _, aliasExist := aliases[alias]; !aliasExist { 1598 return fmt.Errorf("request.ext.prebid.aliasgvlids. vendorId %d refers to unknown bidder alias: %s", vendorId, alias) 1599 } 1600 1601 if vendorId < 1 { 1602 return fmt.Errorf("request.ext.prebid.aliasgvlids. Invalid vendorId %d for alias: %s. Choose a different vendorId, or remove this entry.", vendorId, alias) 1603 } 1604 } 1605 return nil 1606 } 1607 1608 func validateRequestExt(req *openrtb_ext.RequestWrapper) []error { 1609 reqExt, err := req.GetRequestExt() 1610 if err != nil { 1611 return []error{err} 1612 } 1613 1614 prebid := reqExt.GetPrebid() 1615 // exit early if there is no request.ext.prebid to validate 1616 if prebid == nil { 1617 return nil 1618 } 1619 1620 if prebid.Cache != nil { 1621 if prebid.Cache.Bids == nil && prebid.Cache.VastXML == nil { 1622 return []error{errors.New(`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`)} 1623 } 1624 } 1625 1626 if err := validateTargeting(prebid.Targeting); err != nil { 1627 return []error{err} 1628 } 1629 1630 var errs []error 1631 if prebid.MultiBid != nil { 1632 validatedMultiBids, multBidErrs := openrtb_ext.ValidateAndBuildExtMultiBid(prebid) 1633 1634 for _, err := range multBidErrs { 1635 errs = append(errs, &errortypes.Warning{ 1636 WarningCode: errortypes.MultiBidWarningCode, 1637 Message: err.Error(), 1638 }) 1639 } 1640 1641 // update the downstream multibid to avoid passing unvalidated ext to bidders, etc. 1642 prebid.MultiBid = validatedMultiBids 1643 reqExt.SetPrebid(prebid) 1644 } 1645 1646 if !bidadjustment.Validate(prebid.BidAdjustments) { 1647 prebid.BidAdjustments = nil 1648 reqExt.SetPrebid(prebid) 1649 errs = append(errs, &errortypes.Warning{ 1650 WarningCode: errortypes.BidAdjustmentWarningCode, 1651 Message: "bid adjustment from request was invalid", 1652 }) 1653 } 1654 1655 return errs 1656 } 1657 1658 func validateTargeting(t *openrtb_ext.ExtRequestTargeting) error { 1659 if t == nil { 1660 return nil 1661 } 1662 1663 if (t.IncludeWinners == nil || !*t.IncludeWinners) && (t.IncludeBidderKeys == nil || !*t.IncludeBidderKeys) { 1664 return errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support") 1665 } 1666 1667 if t.PriceGranularity != nil { 1668 if err := validatePriceGranularity(t.PriceGranularity); err != nil { 1669 return err 1670 } 1671 } 1672 1673 if t.MediaTypePriceGranularity.Video != nil { 1674 if err := validatePriceGranularity(t.MediaTypePriceGranularity.Video); err != nil { 1675 return err 1676 } 1677 } 1678 if t.MediaTypePriceGranularity.Banner != nil { 1679 if err := validatePriceGranularity(t.MediaTypePriceGranularity.Banner); err != nil { 1680 return err 1681 } 1682 } 1683 if t.MediaTypePriceGranularity.Native != nil { 1684 if err := validatePriceGranularity(t.MediaTypePriceGranularity.Native); err != nil { 1685 return err 1686 } 1687 } 1688 1689 return nil 1690 } 1691 1692 func validatePriceGranularity(pg *openrtb_ext.PriceGranularity) error { 1693 if pg.Precision == nil { 1694 return errors.New("Price granularity error: precision is required") 1695 } else if *pg.Precision < 0 { 1696 return errors.New("Price granularity error: precision must be non-negative") 1697 } else if *pg.Precision > openrtb_ext.MaxDecimalFigures { 1698 return fmt.Errorf("Price granularity error: precision of more than %d significant figures is not supported", openrtb_ext.MaxDecimalFigures) 1699 } 1700 1701 var prevMax float64 = 0 1702 for _, gr := range pg.Ranges { 1703 if gr.Max <= prevMax { 1704 return errors.New(`Price granularity error: range list must be ordered with increasing "max"`) 1705 } 1706 1707 if gr.Increment <= 0.0 { 1708 return errors.New("Price granularity error: increment must be a nonzero positive number") 1709 } 1710 prevMax = gr.Max 1711 } 1712 return nil 1713 } 1714 1715 func (deps *endpointDeps) validateSite(req *openrtb_ext.RequestWrapper) error { 1716 if req.Site == nil { 1717 return nil 1718 } 1719 1720 if req.Site.ID == "" && req.Site.Page == "" { 1721 return errors.New("request.site should include at least one of request.site.id or request.site.page.") 1722 } 1723 siteExt, err := req.GetSiteExt() 1724 if err != nil { 1725 return err 1726 } 1727 siteAmp := siteExt.GetAmp() 1728 if siteAmp != nil && (*siteAmp < 0 || *siteAmp > 1) { 1729 return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) 1730 } 1731 1732 return nil 1733 } 1734 1735 func (deps *endpointDeps) validateApp(req *openrtb_ext.RequestWrapper) error { 1736 if req.App == nil { 1737 return nil 1738 } 1739 1740 if req.App.ID != "" { 1741 if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found { 1742 return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)} 1743 } 1744 } 1745 1746 _, err := req.GetAppExt() 1747 return err 1748 } 1749 1750 func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases map[string]string, gpp gpplib.GppContainer) []error { 1751 var errL []error 1752 1753 if req == nil || req.BidRequest == nil || req.BidRequest.User == nil { 1754 return nil 1755 } 1756 // The following fields were previously uints in the OpenRTB library we use, but have 1757 // since been changed to ints. We decided to maintain the non-negative check. 1758 if req.User.Geo != nil && req.User.Geo.Accuracy < 0 { 1759 return append(errL, errors.New("request.user.geo.accuracy must be a positive number")) 1760 } 1761 1762 if req.User.Consent != "" { 1763 for _, section := range gpp.Sections { 1764 if section.GetID() == constants.SectionTCFEU2 && section.GetValue() != req.User.Consent { 1765 errL = append(errL, &errortypes.Warning{ 1766 Message: "user.consent GDPR string conflicts with GPP (regs.gpp) GDPR string, using regs.gpp", 1767 WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) 1768 } 1769 } 1770 } 1771 userExt, err := req.GetUserExt() 1772 if err != nil { 1773 return append(errL, fmt.Errorf("request.user.ext object is not valid: %v", err)) 1774 } 1775 // Check if the buyeruids are valid 1776 prebid := userExt.GetPrebid() 1777 if prebid != nil { 1778 if len(prebid.BuyerUIDs) < 1 { 1779 return append(errL, errors.New(`request.user.ext.prebid requires a "buyeruids" property with at least one ID defined. If none exist, then request.user.ext.prebid should not be defined.`)) 1780 } 1781 for bidderName := range prebid.BuyerUIDs { 1782 if _, ok := deps.bidderMap[bidderName]; !ok { 1783 if _, ok := aliases[bidderName]; !ok { 1784 return append(errL, fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases", bidderName)) 1785 } 1786 } 1787 } 1788 } 1789 // Check Universal User ID 1790 eids := userExt.GetEid() 1791 if eids != nil { 1792 eidsValue := *eids 1793 uniqueSources := make(map[string]struct{}, len(eidsValue)) 1794 for eidIndex, eid := range eidsValue { 1795 if eid.Source == "" { 1796 return append(errL, fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex)) 1797 } 1798 if _, ok := uniqueSources[eid.Source]; ok { 1799 return append(errL, errors.New("request.user.ext.eids must contain unique sources")) 1800 } 1801 uniqueSources[eid.Source] = struct{}{} 1802 1803 if len(eid.UIDs) == 0 { 1804 return append(errL, fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex)) 1805 } 1806 1807 for uidIndex, uid := range eid.UIDs { 1808 if uid.ID == "" { 1809 return append(errL, fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex)) 1810 } 1811 } 1812 } 1813 } 1814 1815 return errL 1816 } 1817 1818 func validateRegs(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) []error { 1819 var errL []error 1820 1821 if req == nil || req.BidRequest == nil || req.BidRequest.Regs == nil { 1822 return nil 1823 } 1824 1825 if req.BidRequest.Regs.GDPR != nil && req.BidRequest.Regs.GPPSID != nil { 1826 gdpr := int8(0) 1827 for _, id := range req.BidRequest.Regs.GPPSID { 1828 if id == int8(constants.SectionTCFEU2) { 1829 gdpr = 1 1830 break 1831 } 1832 } 1833 if gdpr != *req.BidRequest.Regs.GDPR { 1834 errL = append(errL, &errortypes.Warning{ 1835 Message: "regs.gdpr signal conflicts with GPP (regs.gpp_sid) and will be ignored", 1836 WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) 1837 } 1838 } 1839 regsExt, err := req.GetRegExt() 1840 if err != nil { 1841 return append(errL, fmt.Errorf("request.regs.ext is invalid: %v", err)) 1842 } 1843 1844 gdpr := regsExt.GetGDPR() 1845 if gdpr != nil && *gdpr != 0 && *gdpr != 1 { 1846 return append(errL, errors.New("request.regs.ext.gdpr must be either 0 or 1")) 1847 } 1848 1849 return errL 1850 } 1851 1852 func validateDevice(device *openrtb2.Device) error { 1853 if device == nil { 1854 return nil 1855 } 1856 1857 // The following fields were previously uints in the OpenRTB library we use, but have 1858 // since been changed to ints. We decided to maintain the non-negative check. 1859 if device.W < 0 { 1860 return errors.New("request.device.w must be a positive number") 1861 } 1862 if device.H < 0 { 1863 return errors.New("request.device.h must be a positive number") 1864 } 1865 if device.PPI < 0 { 1866 return errors.New("request.device.ppi must be a positive number") 1867 } 1868 if device.Geo != nil && device.Geo.Accuracy < 0 { 1869 return errors.New("request.device.geo.accuracy must be a positive number") 1870 } 1871 1872 return nil 1873 } 1874 1875 func validateOrFillChannel(reqWrapper *openrtb_ext.RequestWrapper, isAmp bool) error { 1876 requestExt, err := reqWrapper.GetRequestExt() 1877 if err != nil { 1878 return err 1879 } 1880 requestPrebid := requestExt.GetPrebid() 1881 1882 if requestPrebid == nil || requestPrebid.Channel == nil { 1883 fillChannel(reqWrapper, isAmp) 1884 } else if requestPrebid.Channel.Name == "" { 1885 return errors.New("ext.prebid.channel.name can't be empty") 1886 } 1887 return nil 1888 } 1889 1890 func fillChannel(reqWrapper *openrtb_ext.RequestWrapper, isAmp bool) error { 1891 var channelName string 1892 requestExt, err := reqWrapper.GetRequestExt() 1893 if err != nil { 1894 return err 1895 } 1896 requestPrebid := requestExt.GetPrebid() 1897 if isAmp { 1898 channelName = ampChannel 1899 } 1900 if reqWrapper.App != nil { 1901 channelName = appChannel 1902 } 1903 if channelName != "" { 1904 if requestPrebid == nil { 1905 requestPrebid = &openrtb_ext.ExtRequestPrebid{} 1906 } 1907 requestPrebid.Channel = &openrtb_ext.ExtRequestPrebidChannel{Name: channelName} 1908 requestExt.SetPrebid(requestPrebid) 1909 reqWrapper.RebuildRequest() 1910 } 1911 return nil 1912 1913 } 1914 1915 func sanitizeRequest(r *openrtb_ext.RequestWrapper, ipValidator iputil.IPValidator) { 1916 if r.Device != nil { 1917 if ip, ver := iputil.ParseIP(r.Device.IP); ip == nil || ver != iputil.IPv4 || !ipValidator.IsValid(ip, ver) { 1918 r.Device.IP = "" 1919 } 1920 1921 if ip, ver := iputil.ParseIP(r.Device.IPv6); ip == nil || ver != iputil.IPv6 || !ipValidator.IsValid(ip, ver) { 1922 r.Device.IPv6 = "" 1923 } 1924 } 1925 } 1926 1927 // setFieldsImplicitly uses _implicit_ information from the httpReq to set values on bidReq. 1928 // This function does not consume the request body, which was set explicitly, but infers certain 1929 // OpenRTB properties from the headers and other implicit info. 1930 // 1931 // This function _should not_ override any fields which were defined explicitly by the caller in the request. 1932 func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { 1933 sanitizeRequest(r, deps.privateNetworkIPValidator) 1934 1935 setDeviceImplicitly(httpReq, r, deps.privateNetworkIPValidator) 1936 1937 // Per the OpenRTB spec: A bid request must not contain both a Site and an App object. If neither are 1938 // present, we'll assume it's a site request. 1939 if r.App == nil { 1940 setSiteImplicitly(httpReq, r) 1941 } 1942 1943 setAuctionTypeImplicitly(r) 1944 } 1945 1946 // setDeviceImplicitly uses implicit info from httpReq to populate bidReq.Device 1947 func setDeviceImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, ipValidtor iputil.IPValidator) { 1948 setIPImplicitly(httpReq, r, ipValidtor) 1949 setUAImplicitly(httpReq, r) 1950 setDoNotTrackImplicitly(httpReq, r) 1951 1952 } 1953 1954 // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request, 1955 // since header bidding is generally a first-price auction. 1956 func setAuctionTypeImplicitly(r *openrtb_ext.RequestWrapper) { 1957 if r.AT == 0 { 1958 r.AT = 1 1959 } 1960 } 1961 1962 func setSiteImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { 1963 if r.Site == nil { 1964 r.Site = &openrtb2.Site{} 1965 } 1966 1967 referrerCandidate := httpReq.Referer() 1968 if referrerCandidate == "" && r.Site.Page != "" { 1969 referrerCandidate = r.Site.Page // If http referer is disabled and thus has empty value - use site.page instead 1970 } 1971 1972 if referrerCandidate != "" { 1973 setSitePageIfEmpty(r.Site, referrerCandidate) 1974 if parsedUrl, err := url.Parse(referrerCandidate); err == nil { 1975 setSiteDomainIfEmpty(r.Site, parsedUrl.Host) 1976 if publisherDomain, err := publicsuffix.EffectiveTLDPlusOne(parsedUrl.Host); err == nil { 1977 setSitePublisherDomainIfEmpty(r.Site, publisherDomain) 1978 } 1979 } 1980 } 1981 1982 if siteExt, err := r.GetSiteExt(); err == nil && siteExt.GetAmp() == nil { 1983 siteExt.SetAmp(¬Amp) 1984 } 1985 1986 } 1987 1988 func setSitePageIfEmpty(site *openrtb2.Site, sitePage string) { 1989 if site.Page == "" { 1990 site.Page = sitePage 1991 } 1992 } 1993 1994 func setSiteDomainIfEmpty(site *openrtb2.Site, siteDomain string) { 1995 if site.Domain == "" { 1996 site.Domain = siteDomain 1997 } 1998 } 1999 2000 func setSitePublisherDomainIfEmpty(site *openrtb2.Site, publisherDomain string) { 2001 if site.Publisher == nil { 2002 site.Publisher = &openrtb2.Publisher{} 2003 } 2004 if site.Publisher.Domain == "" { 2005 site.Publisher.Domain = publisherDomain 2006 } 2007 } 2008 2009 func getJsonSyntaxError(testJSON []byte) (bool, string) { 2010 type JsonNode struct { 2011 raw *json.RawMessage 2012 doc map[string]*JsonNode 2013 ary []*JsonNode 2014 which int 2015 } 2016 type jNode map[string]*JsonNode 2017 docErrdoc := &jNode{} 2018 docErr := json.Unmarshal(testJSON, docErrdoc) 2019 if uerror, ok := docErr.(*json.SyntaxError); ok { 2020 err := fmt.Sprintf("%s at offset %v", uerror.Error(), uerror.Offset) 2021 return true, err 2022 } 2023 return false, "" 2024 } 2025 2026 func (deps *endpointDeps) getStoredRequests(ctx context.Context, requestJson []byte, impInfo []ImpExtPrebidData) (string, bool, map[string]json.RawMessage, map[string]json.RawMessage, []error) { 2027 // Parse the Stored Request IDs from the BidRequest and Imps. 2028 storedBidRequestId, hasStoredBidRequest, err := getStoredRequestId(requestJson) 2029 if err != nil { 2030 return "", false, nil, nil, []error{err} 2031 } 2032 2033 // Fetch the Stored Request data 2034 var storedReqIds []string 2035 if hasStoredBidRequest { 2036 storedReqIds = []string{storedBidRequestId} 2037 } 2038 2039 impStoredReqIds := make([]string, 0, len(impInfo)) 2040 impStoredReqIdsUniqueTracker := make(map[string]struct{}, len(impInfo)) 2041 for _, impData := range impInfo { 2042 if impData.ImpExtPrebid.StoredRequest != nil && len(impData.ImpExtPrebid.StoredRequest.ID) > 0 { 2043 storedImpId := impData.ImpExtPrebid.StoredRequest.ID 2044 if _, present := impStoredReqIdsUniqueTracker[storedImpId]; !present { 2045 impStoredReqIds = append(impStoredReqIds, storedImpId) 2046 impStoredReqIdsUniqueTracker[storedImpId] = struct{}{} 2047 } 2048 } 2049 } 2050 2051 storedRequests, storedImps, errs := deps.storedReqFetcher.FetchRequests(ctx, storedReqIds, impStoredReqIds) 2052 if len(errs) != 0 { 2053 return "", false, nil, nil, errs 2054 } 2055 2056 return storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs 2057 } 2058 2059 func (deps *endpointDeps) processStoredRequests(requestJson []byte, impInfo []ImpExtPrebidData, storedRequests map[string]json.RawMessage, storedImps map[string]json.RawMessage, storedBidRequestId string, hasStoredBidRequest bool) ([]byte, map[string]exchange.ImpExtInfo, []error) { 2060 bidRequestID, err := getBidRequestID(storedRequests[storedBidRequestId]) 2061 if err != nil { 2062 return nil, nil, []error{err} 2063 } 2064 2065 // Apply the Stored BidRequest, if it exists 2066 resolvedRequest := requestJson 2067 2068 if hasStoredBidRequest { 2069 isAppRequest, err := checkIfAppRequest(requestJson) 2070 if err != nil { 2071 return nil, nil, []error{err} 2072 } 2073 if (deps.cfg.GenerateRequestID && isAppRequest) || bidRequestID == "{{UUID}}" { 2074 uuidPatch, err := generateUuidForBidRequest(deps.uuidGenerator) 2075 if err != nil { 2076 return nil, nil, []error{err} 2077 } 2078 uuidPatch, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], uuidPatch) 2079 if err != nil { 2080 errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) 2081 return nil, nil, errL 2082 } 2083 resolvedRequest, err = jsonpatch.MergePatch(requestJson, uuidPatch) 2084 if err != nil { 2085 errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) 2086 return nil, nil, errL 2087 } 2088 } else { 2089 resolvedRequest, err = jsonpatch.MergePatch(storedRequests[storedBidRequestId], requestJson) 2090 if err != nil { 2091 errL := storedRequestErrorChecker(requestJson, storedRequests, storedBidRequestId) 2092 return nil, nil, errL 2093 } 2094 } 2095 } 2096 2097 // Apply default aliases, if they are provided 2098 if deps.defaultRequest { 2099 aliasedRequest, err := jsonpatch.MergePatch(deps.defReqJSON, resolvedRequest) 2100 if err != nil { 2101 hasErr, Err := getJsonSyntaxError(resolvedRequest) 2102 if hasErr { 2103 err = fmt.Errorf("Invalid JSON in Incoming Request: %s", Err) 2104 } else { 2105 hasErr, Err = getJsonSyntaxError(deps.defReqJSON) 2106 if hasErr { 2107 err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", Err) 2108 } 2109 } 2110 return nil, nil, []error{err} 2111 } 2112 resolvedRequest = aliasedRequest 2113 } 2114 2115 // Apply any Stored Imps, if they exist. Since the JSON Merge Patch overrides arrays, 2116 // and Prebid Server defers to the HTTP Request to resolve conflicts, it's safe to 2117 // assume that the request.imp data did not change when applying the Stored BidRequest. 2118 impExtInfoMap := make(map[string]exchange.ImpExtInfo, len(impInfo)) 2119 resolvedImps := make([]json.RawMessage, 0, len(impInfo)) 2120 for i, impData := range impInfo { 2121 if impData.ImpExtPrebid.StoredRequest != nil && len(impData.ImpExtPrebid.StoredRequest.ID) > 0 { 2122 resolvedImp, err := jsonpatch.MergePatch(storedImps[impData.ImpExtPrebid.StoredRequest.ID], impData.Imp) 2123 2124 if err != nil { 2125 hasErr, errMessage := getJsonSyntaxError(impData.Imp) 2126 if hasErr { 2127 err = fmt.Errorf("Invalid JSON in Imp[%d] of Incoming Request: %s", i, errMessage) 2128 } else { 2129 hasErr, errMessage = getJsonSyntaxError(storedImps[impData.ImpExtPrebid.StoredRequest.ID]) 2130 if hasErr { 2131 err = fmt.Errorf("imp.ext.prebid.storedrequest.id %s: Stored Imp has Invalid JSON: %s", impData.ImpExtPrebid.StoredRequest.ID, errMessage) 2132 } 2133 } 2134 return nil, nil, []error{err} 2135 } 2136 resolvedImps = append(resolvedImps, resolvedImp) 2137 impId, err := jsonparser.GetString(resolvedImp, "id") 2138 if err != nil { 2139 return nil, nil, []error{err} 2140 } 2141 2142 echoVideoAttributes := false 2143 if impData.ImpExtPrebid.Options != nil { 2144 echoVideoAttributes = impData.ImpExtPrebid.Options.EchoVideoAttrs 2145 } 2146 2147 // Extract Passthrough from Merged Imp 2148 passthrough, _, _, err := jsonparser.Get(resolvedImp, "ext", "prebid", "passthrough") 2149 if err != nil && err != jsonparser.KeyPathNotFoundError { 2150 return nil, nil, []error{err} 2151 } 2152 impExtInfoMap[impId] = exchange.ImpExtInfo{EchoVideoAttrs: echoVideoAttributes, StoredImp: storedImps[impData.ImpExtPrebid.StoredRequest.ID], Passthrough: passthrough} 2153 2154 } else { 2155 resolvedImps = append(resolvedImps, impData.Imp) 2156 impId, err := jsonparser.GetString(impData.Imp, "id") 2157 if err != nil { 2158 if err == jsonparser.KeyPathNotFoundError { 2159 err = fmt.Errorf("request.imp[%d] missing required field: \"id\"\n", i) 2160 } 2161 return nil, nil, []error{err} 2162 } 2163 impExtInfoMap[impId] = exchange.ImpExtInfo{Passthrough: impData.ImpExtPrebid.Passthrough} 2164 } 2165 } 2166 if len(resolvedImps) > 0 { 2167 newImpJson, err := json.Marshal(resolvedImps) 2168 if err != nil { 2169 return nil, nil, []error{err} 2170 } 2171 resolvedRequest, err = jsonparser.Set(resolvedRequest, newImpJson, "imp") 2172 if err != nil { 2173 return nil, nil, []error{err} 2174 } 2175 } 2176 2177 return resolvedRequest, impExtInfoMap, nil 2178 } 2179 2180 // parseImpInfo parses the request JSON and returns impression and unmarshalled imp.ext.prebid 2181 func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) { 2182 if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array { 2183 _, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) { 2184 impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid") 2185 var impExtPrebid openrtb_ext.ExtImpPrebid 2186 if impExtData != nil { 2187 if err := json.Unmarshal(impExtData, &impExtPrebid); err != nil { 2188 errs = append(errs, err) 2189 } 2190 } 2191 newImpData := ImpExtPrebidData{imp, impExtPrebid} 2192 impData = append(impData, newImpData) 2193 }) 2194 } 2195 return 2196 } 2197 2198 type ImpExtPrebidData struct { 2199 Imp json.RawMessage 2200 ImpExtPrebid openrtb_ext.ExtImpPrebid 2201 } 2202 2203 // getStoredRequestId parses a Stored Request ID from some json, without doing a full (slow) unmarshal. 2204 // It returns the ID, true/false whether a stored request key existed, and an error if anything went wrong 2205 // (e.g. malformed json, id not a string, etc). 2206 func getStoredRequestId(data []byte) (string, bool, error) { 2207 // These keys must be kept in sync with openrtb_ext.ExtStoredRequest 2208 storedRequestId, dataType, _, err := jsonparser.Get(data, "ext", openrtb_ext.PrebidExtKey, "storedrequest", "id") 2209 2210 if dataType == jsonparser.NotExist { 2211 return "", false, nil 2212 } 2213 if err != nil { 2214 return "", false, err 2215 } 2216 if dataType != jsonparser.String { 2217 return "", true, errors.New("ext.prebid.storedrequest.id must be a string") 2218 } 2219 return string(storedRequestId), true, nil 2220 } 2221 2222 func getBidRequestID(data json.RawMessage) (string, error) { 2223 bidRequestID, dataType, _, err := jsonparser.Get(data, "id") 2224 if dataType == jsonparser.NotExist { 2225 return "", nil 2226 } 2227 if err != nil { 2228 return "", err 2229 } 2230 return string(bidRequestID), nil 2231 } 2232 2233 // setIPImplicitly sets the IP address on bidReq, if it's not explicitly defined and we can figure it out. 2234 func setIPImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, ipValidator iputil.IPValidator) { 2235 if r.Device == nil || (r.Device.IP == "" && r.Device.IPv6 == "") { 2236 if ip, ver := httputil.FindIP(httpReq, ipValidator); ip != nil { 2237 switch ver { 2238 case iputil.IPv4: 2239 if r.Device == nil { 2240 r.Device = &openrtb2.Device{} 2241 } 2242 r.Device.IP = ip.String() 2243 case iputil.IPv6: 2244 if r.Device == nil { 2245 r.Device = &openrtb2.Device{} 2246 } 2247 r.Device.IPv6 = ip.String() 2248 } 2249 } 2250 } 2251 } 2252 2253 // setUAImplicitly sets the User Agent on bidReq, if it's not explicitly defined and it's defined on the request. 2254 func setUAImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { 2255 if r.Device == nil || r.Device.UA == "" { 2256 if ua := httpReq.UserAgent(); ua != "" { 2257 if r.Device == nil { 2258 r.Device = &openrtb2.Device{} 2259 } 2260 r.Device.UA = ua 2261 } 2262 } 2263 } 2264 2265 func setDoNotTrackImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) { 2266 if r.Device == nil || r.Device.DNT == nil { 2267 dnt := httpReq.Header.Get(dntKey) 2268 if dnt == "0" || dnt == "1" { 2269 if r.Device == nil { 2270 r.Device = &openrtb2.Device{} 2271 } 2272 2273 switch dnt { 2274 case "0": 2275 r.Device.DNT = &dntDisabled 2276 case "1": 2277 r.Device.DNT = &dntEnabled 2278 } 2279 } 2280 } 2281 } 2282 2283 // Write(return) errors to the client, if any. Returns true if errors were found. 2284 func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) bool { 2285 var rc bool = false 2286 if len(errs) > 0 { 2287 httpStatus := http.StatusBadRequest 2288 metricsStatus := metrics.RequestStatusBadInput 2289 for _, err := range errs { 2290 erVal := errortypes.ReadCode(err) 2291 if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.BlacklistedAcctErrorCode { 2292 httpStatus = http.StatusServiceUnavailable 2293 metricsStatus = metrics.RequestStatusBlacklisted 2294 break 2295 } else if erVal == errortypes.MalformedAcctErrorCode { 2296 httpStatus = http.StatusInternalServerError 2297 metricsStatus = metrics.RequestStatusAccountConfigErr 2298 break 2299 } 2300 } 2301 w.WriteHeader(httpStatus) 2302 labels.RequestStatus = metricsStatus 2303 for _, err := range errs { 2304 w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error()))) 2305 } 2306 rc = true 2307 } 2308 return rc 2309 } 2310 2311 // Returns the account ID for the request 2312 func getAccountID(pub *openrtb2.Publisher) string { 2313 if pub != nil { 2314 if pub.Ext != nil { 2315 var pubExt openrtb_ext.ExtPublisher 2316 err := json.Unmarshal(pub.Ext, &pubExt) 2317 if err == nil && pubExt.Prebid != nil && pubExt.Prebid.ParentAccount != nil && *pubExt.Prebid.ParentAccount != "" { 2318 return *pubExt.Prebid.ParentAccount 2319 } 2320 } 2321 if pub.ID != "" { 2322 return pub.ID 2323 } 2324 } 2325 return metrics.PublisherUnknown 2326 } 2327 2328 func getAccountIdFromRawRequest(hasStoredRequest bool, storedRequest json.RawMessage, originalRequest []byte) (string, bool, []error) { 2329 request := originalRequest 2330 if hasStoredRequest { 2331 request = storedRequest 2332 } 2333 2334 accountId, isAppReq, err := searchAccountId(request) 2335 if err != nil { 2336 return "", isAppReq, []error{err} 2337 } 2338 2339 // In case the stored request did not have account data we specifically search it in the original request 2340 if accountId == "" && hasStoredRequest { 2341 accountId, _, err = searchAccountId(originalRequest) 2342 if err != nil { 2343 return "", isAppReq, []error{err} 2344 } 2345 } 2346 2347 if accountId == "" { 2348 return metrics.PublisherUnknown, isAppReq, nil 2349 } 2350 2351 return accountId, isAppReq, nil 2352 } 2353 2354 func searchAccountId(request []byte) (string, bool, error) { 2355 for _, path := range accountIdSearchPath { 2356 accountId, exists, err := getStringValueFromRequest(request, path.key) 2357 if err != nil { 2358 return "", path.isApp, err 2359 } 2360 if exists { 2361 return accountId, path.isApp, nil 2362 } 2363 } 2364 return "", false, nil 2365 } 2366 2367 func getStringValueFromRequest(request []byte, key []string) (string, bool, error) { 2368 val, dataType, _, err := jsonparser.Get(request, key...) 2369 if dataType == jsonparser.NotExist { 2370 return "", false, nil 2371 } 2372 if err != nil { 2373 return "", false, err 2374 } 2375 if dataType != jsonparser.String { 2376 return "", true, fmt.Errorf("%s must be a string", strings.Join(key, ".")) 2377 } 2378 return string(val), true, nil 2379 } 2380 2381 func storedRequestErrorChecker(requestJson []byte, storedRequests map[string]json.RawMessage, storedBidRequestId string) []error { 2382 if hasErr, syntaxErr := getJsonSyntaxError(requestJson); hasErr { 2383 return []error{fmt.Errorf("Invalid JSON in Incoming Request: %s", syntaxErr)} 2384 } 2385 if hasErr, syntaxErr := getJsonSyntaxError(storedRequests[storedBidRequestId]); hasErr { 2386 return []error{fmt.Errorf("ext.prebid.storedrequest.id refers to Stored Request %s which contains Invalid JSON: %s", storedBidRequestId, syntaxErr)} 2387 } 2388 return nil 2389 } 2390 2391 func generateUuidForBidRequest(uuidGenerator uuidutil.UUIDGenerator) ([]byte, error) { 2392 newBidRequestID, err := uuidGenerator.Generate() 2393 if err != nil { 2394 return nil, err 2395 } 2396 return []byte(`{"id":"` + newBidRequestID + `"}`), nil 2397 } 2398 2399 func (deps *endpointDeps) setIntegrationType(req *openrtb_ext.RequestWrapper, account *config.Account) error { 2400 reqExt, err := req.GetRequestExt() 2401 if err != nil { 2402 return err 2403 } 2404 reqPrebid := reqExt.GetPrebid() 2405 2406 if account == nil || account.DefaultIntegration == "" { 2407 return nil 2408 } 2409 if reqPrebid == nil { 2410 reqPrebid = &openrtb_ext.ExtRequestPrebid{Integration: account.DefaultIntegration} 2411 reqExt.SetPrebid(reqPrebid) 2412 } else if reqPrebid.Integration == "" { 2413 reqPrebid.Integration = account.DefaultIntegration 2414 reqExt.SetPrebid(reqPrebid) 2415 } 2416 return nil 2417 } 2418 2419 func checkIfAppRequest(request []byte) (bool, error) { 2420 requestApp, dataType, _, err := jsonparser.Get(request, "app") 2421 if dataType == jsonparser.NotExist { 2422 return false, nil 2423 } 2424 if err != nil { 2425 return false, err 2426 } 2427 if requestApp != nil { 2428 return true, nil 2429 } 2430 return false, nil 2431 } 2432 2433 func validateStoredBidRespAndImpExtBidders(bidderExts map[string]json.RawMessage, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error { 2434 if bidResponses, ok := storedBidResp[impId]; ok { 2435 if len(bidResponses) != len(bidderExts) { 2436 return generateStoredBidResponseValidationError(impId) 2437 } 2438 2439 for bidderName := range bidResponses { 2440 if _, present := bidderExts[bidderName]; !present { 2441 return generateStoredBidResponseValidationError(impId) 2442 } 2443 } 2444 } 2445 return nil 2446 } 2447 2448 func generateStoredBidResponseValidationError(impID string) error { 2449 return fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impID) 2450 }