github.com/prebid/prebid-server@v0.275.0/endpoints/openrtb2/amp_auction.go (about)

     1  package openrtb2
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/prebid/prebid-server/privacy"
    15  
    16  	"github.com/buger/jsonparser"
    17  	"github.com/golang/glog"
    18  	"github.com/julienschmidt/httprouter"
    19  	"github.com/prebid/openrtb/v19/openrtb2"
    20  	"github.com/prebid/openrtb/v19/openrtb3"
    21  	"github.com/prebid/prebid-server/hooks/hookexecution"
    22  	"github.com/prebid/prebid-server/ortb"
    23  	"github.com/prebid/prebid-server/util/uuidutil"
    24  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
    25  
    26  	accountService "github.com/prebid/prebid-server/account"
    27  	"github.com/prebid/prebid-server/amp"
    28  	"github.com/prebid/prebid-server/analytics"
    29  	"github.com/prebid/prebid-server/config"
    30  	"github.com/prebid/prebid-server/errortypes"
    31  	"github.com/prebid/prebid-server/exchange"
    32  	"github.com/prebid/prebid-server/gdpr"
    33  	"github.com/prebid/prebid-server/hooks"
    34  	"github.com/prebid/prebid-server/metrics"
    35  	"github.com/prebid/prebid-server/openrtb_ext"
    36  	"github.com/prebid/prebid-server/stored_requests"
    37  	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
    38  	"github.com/prebid/prebid-server/stored_responses"
    39  	"github.com/prebid/prebid-server/usersync"
    40  	"github.com/prebid/prebid-server/util/iputil"
    41  	"github.com/prebid/prebid-server/version"
    42  )
    43  
    44  const defaultAmpRequestTimeoutMillis = 900
    45  
    46  var nilBody []byte = nil
    47  
    48  type AmpResponse struct {
    49  	Targeting map[string]string `json:"targeting"`
    50  	ORTB2     ORTB2             `json:"ortb2"`
    51  }
    52  
    53  type ORTB2 struct {
    54  	Ext openrtb_ext.ExtBidResponse `json:"ext"`
    55  }
    56  
    57  // NewAmpEndpoint modifies the OpenRTB endpoint to handle AMP requests. This will basically modify the parsing
    58  // of the request, and the return value, using the OpenRTB machinery to handle everything in between.
    59  func NewAmpEndpoint(
    60  	uuidGenerator uuidutil.UUIDGenerator,
    61  	ex exchange.Exchange,
    62  	validator openrtb_ext.BidderParamValidator,
    63  	requestsById stored_requests.Fetcher,
    64  	accounts stored_requests.AccountFetcher,
    65  	cfg *config.Configuration,
    66  	metricsEngine metrics.MetricsEngine,
    67  	pbsAnalytics analytics.PBSAnalyticsModule,
    68  	disabledBidders map[string]string,
    69  	defReqJSON []byte,
    70  	bidderMap map[string]openrtb_ext.BidderName,
    71  	storedRespFetcher stored_requests.Fetcher,
    72  	hookExecutionPlanBuilder hooks.ExecutionPlanBuilder,
    73  	tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed,
    74  ) (httprouter.Handle, error) {
    75  
    76  	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil {
    77  		return nil, errors.New("NewAmpEndpoint requires non-nil arguments.")
    78  	}
    79  
    80  	defRequest := defReqJSON != nil && len(defReqJSON) > 0
    81  
    82  	ipValidator := iputil.PublicNetworkIPValidator{
    83  		IPv4PrivateNetworks: cfg.RequestValidation.IPv4PrivateNetworksParsed,
    84  		IPv6PrivateNetworks: cfg.RequestValidation.IPv6PrivateNetworksParsed,
    85  	}
    86  
    87  	return httprouter.Handle((&endpointDeps{
    88  		uuidGenerator,
    89  		ex,
    90  		validator,
    91  		requestsById,
    92  		empty_fetcher.EmptyFetcher{},
    93  		accounts,
    94  		cfg,
    95  		metricsEngine,
    96  		pbsAnalytics,
    97  		disabledBidders,
    98  		defRequest,
    99  		defReqJSON,
   100  		bidderMap,
   101  		nil,
   102  		nil,
   103  		ipValidator,
   104  		storedRespFetcher,
   105  		hookExecutionPlanBuilder,
   106  		tmaxAdjustments,
   107  	}).AmpAuction), nil
   108  
   109  }
   110  
   111  func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   112  	// Prebid Server interprets request.tmax to be the maximum amount of time that a caller is willing
   113  	// to wait for bids. However, tmax may be defined in the Stored Request data.
   114  	//
   115  	// If so, then the trip to the backend might use a significant amount of this time.
   116  	// We can respect timeouts more accurately if we note the *real* start time, and use it
   117  	// to compute the auction timeout.
   118  	start := time.Now()
   119  
   120  	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAmp, deps.metricsEngine)
   121  
   122  	ao := analytics.AmpObject{
   123  		Status:    http.StatusOK,
   124  		Errors:    make([]error, 0),
   125  		StartTime: start,
   126  	}
   127  
   128  	// Set this as an AMP request in Metrics.
   129  
   130  	labels := metrics.Labels{
   131  		Source:        metrics.DemandWeb,
   132  		RType:         metrics.ReqTypeAMP,
   133  		PubID:         metrics.PublisherUnknown,
   134  		CookieFlag:    metrics.CookieFlagUnknown,
   135  		RequestStatus: metrics.RequestStatusOK,
   136  	}
   137  
   138  	defer func() {
   139  		deps.metricsEngine.RecordRequest(labels)
   140  		deps.metricsEngine.RecordRequestTime(labels, time.Since(start))
   141  		deps.analytics.LogAmpObject(&ao)
   142  	}()
   143  
   144  	// Add AMP headers
   145  	origin := r.FormValue("__amp_source_origin")
   146  	if len(origin) == 0 {
   147  		// Just to be safe
   148  		origin = r.Header.Get("Origin")
   149  		ao.Origin = origin
   150  	}
   151  
   152  	// Headers "Access-Control-Allow-Origin", "Access-Control-Allow-Headers",
   153  	// and "Access-Control-Allow-Credentials" are handled in CORS middleware
   154  	w.Header().Set("AMP-Access-Control-Allow-Source-Origin", origin)
   155  	w.Header().Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin")
   156  	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
   157  
   158  	// There is no body for AMP requests, so we pass a nil body and ignore the return value.
   159  	_, rejectErr := hookExecutor.ExecuteEntrypointStage(r, nilBody)
   160  	reqWrapper, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errL := deps.parseAmpRequest(r)
   161  	ao.Errors = append(ao.Errors, errL...)
   162  	// Process reject after parsing amp request, so we can use reqWrapper.
   163  	// There is no body for AMP requests, so we pass a nil body and ignore the return value.
   164  	if rejectErr != nil {
   165  		labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, nil, labels, ao, nil)
   166  		return
   167  	}
   168  
   169  	if errortypes.ContainsFatalError(errL) {
   170  		w.WriteHeader(http.StatusBadRequest)
   171  		for _, err := range errortypes.FatalOnly(errL) {
   172  			w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error())))
   173  		}
   174  		labels.RequestStatus = metrics.RequestStatusBadInput
   175  		return
   176  	}
   177  
   178  	ao.RequestWrapper = reqWrapper
   179  
   180  	ctx := context.Background()
   181  	var cancel context.CancelFunc
   182  	if reqWrapper.TMax > 0 {
   183  		ctx, cancel = context.WithDeadline(ctx, start.Add(time.Duration(reqWrapper.TMax)*time.Millisecond))
   184  	} else {
   185  		ctx, cancel = context.WithDeadline(ctx, start.Add(time.Duration(defaultAmpRequestTimeoutMillis)*time.Millisecond))
   186  	}
   187  	defer cancel()
   188  
   189  	// Read UserSyncs/Cookie from Request
   190  	usersyncs := usersync.ReadCookie(r, usersync.Base64Decoder{}, &deps.cfg.HostCookie)
   191  	usersync.SyncHostCookie(r, usersyncs, &deps.cfg.HostCookie)
   192  	if usersyncs.HasAnyLiveSyncs() {
   193  		labels.CookieFlag = metrics.CookieFlagYes
   194  	} else {
   195  		labels.CookieFlag = metrics.CookieFlagNo
   196  	}
   197  
   198  	labels.PubID = getAccountID(reqWrapper.Site.Publisher)
   199  	// Look up account now that we have resolved the pubID value
   200  	account, acctIDErrs := accountService.GetAccount(ctx, deps.cfg, deps.accounts, labels.PubID, deps.metricsEngine)
   201  	if len(acctIDErrs) > 0 {
   202  		// best attempt to rebuild the request for analytics. we're already in an error state, so ignoring a
   203  		// potential error from this call
   204  		reqWrapper.RebuildRequest()
   205  
   206  		errL = append(errL, acctIDErrs...)
   207  		httpStatus := http.StatusBadRequest
   208  		metricsStatus := metrics.RequestStatusBadInput
   209  		for _, er := range errL {
   210  			errCode := errortypes.ReadCode(er)
   211  			if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.BlacklistedAcctErrorCode {
   212  				httpStatus = http.StatusServiceUnavailable
   213  				metricsStatus = metrics.RequestStatusBlacklisted
   214  				break
   215  			}
   216  			if errCode == errortypes.MalformedAcctErrorCode {
   217  				httpStatus = http.StatusInternalServerError
   218  				metricsStatus = metrics.RequestStatusAccountConfigErr
   219  				break
   220  			}
   221  		}
   222  		w.WriteHeader(httpStatus)
   223  		labels.RequestStatus = metricsStatus
   224  		for _, err := range errortypes.FatalOnly(errL) {
   225  			w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error())))
   226  		}
   227  		ao.Errors = append(ao.Errors, acctIDErrs...)
   228  		return
   229  	}
   230  
   231  	tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR)
   232  
   233  	activityControl := privacy.NewActivityControl(&account.Privacy)
   234  
   235  	secGPC := r.Header.Get("Sec-GPC")
   236  
   237  	auctionRequest := &exchange.AuctionRequest{
   238  		BidRequestWrapper:          reqWrapper,
   239  		Account:                    *account,
   240  		UserSyncs:                  usersyncs,
   241  		RequestType:                labels.RType,
   242  		StartTime:                  start,
   243  		LegacyLabels:               labels,
   244  		GlobalPrivacyControlHeader: secGPC,
   245  		StoredAuctionResponses:     storedAuctionResponses,
   246  		StoredBidResponses:         storedBidResponses,
   247  		BidderImpReplaceImpID:      bidderImpReplaceImp,
   248  		PubID:                      labels.PubID,
   249  		HookExecutor:               hookExecutor,
   250  		QueryParams:                r.URL.Query(),
   251  		TCF2Config:                 tcf2Config,
   252  		Activities:                 activityControl,
   253  		TmaxAdjustments:            deps.tmaxAdjustments,
   254  	}
   255  
   256  	auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
   257  	defer func() {
   258  		if !auctionRequest.BidderResponseStartTime.IsZero() {
   259  			deps.metricsEngine.RecordOverheadTime(metrics.MakeAuctionResponse, time.Since(auctionRequest.BidderResponseStartTime))
   260  		}
   261  	}()
   262  	var response *openrtb2.BidResponse
   263  	if auctionResponse != nil {
   264  		response = auctionResponse.BidResponse
   265  	}
   266  	ao.SeatNonBid = auctionResponse.GetSeatNonBid()
   267  	ao.AuctionResponse = response
   268  	rejectErr, isRejectErr := hookexecution.CastRejectErr(err)
   269  	if err != nil && !isRejectErr {
   270  		w.WriteHeader(http.StatusInternalServerError)
   271  		fmt.Fprintf(w, "Critical error while running the auction: %v", err)
   272  		glog.Errorf("/openrtb2/amp Critical error: %v", err)
   273  		ao.Status = http.StatusInternalServerError
   274  		ao.Errors = append(ao.Errors, err)
   275  		return
   276  	}
   277  
   278  	// hold auction rebuilds the request wrapper first thing, so there is likely
   279  	// no work to do here, but added a rebuild just in case this behavior changes.
   280  	if err := reqWrapper.RebuildRequest(); err != nil {
   281  		w.WriteHeader(http.StatusInternalServerError)
   282  		fmt.Fprintf(w, "Critical error while running the auction: %v", err)
   283  		glog.Errorf("/openrtb2/amp Critical error: %v", err)
   284  		ao.Status = http.StatusInternalServerError
   285  		ao.Errors = append(ao.Errors, err)
   286  		return
   287  	}
   288  
   289  	if isRejectErr {
   290  		labels, ao = rejectAmpRequest(*rejectErr, w, hookExecutor, reqWrapper, account, labels, ao, errL)
   291  		return
   292  	}
   293  
   294  	labels, ao = sendAmpResponse(w, hookExecutor, auctionResponse, reqWrapper, account, labels, ao, errL)
   295  }
   296  
   297  func rejectAmpRequest(
   298  	rejectErr hookexecution.RejectError,
   299  	w http.ResponseWriter,
   300  	hookExecutor hookexecution.HookStageExecutor,
   301  	reqWrapper *openrtb_ext.RequestWrapper,
   302  	account *config.Account,
   303  	labels metrics.Labels,
   304  	ao analytics.AmpObject,
   305  	errs []error,
   306  ) (metrics.Labels, analytics.AmpObject) {
   307  	response := &openrtb2.BidResponse{NBR: openrtb3.NoBidReason(rejectErr.NBR).Ptr()}
   308  	ao.AuctionResponse = response
   309  	ao.Errors = append(ao.Errors, rejectErr)
   310  
   311  	return sendAmpResponse(w, hookExecutor, &exchange.AuctionResponse{BidResponse: response}, reqWrapper, account, labels, ao, errs)
   312  }
   313  
   314  func sendAmpResponse(
   315  	w http.ResponseWriter,
   316  	hookExecutor hookexecution.HookStageExecutor,
   317  	auctionResponse *exchange.AuctionResponse,
   318  	reqWrapper *openrtb_ext.RequestWrapper,
   319  	account *config.Account,
   320  	labels metrics.Labels,
   321  	ao analytics.AmpObject,
   322  	errs []error,
   323  ) (metrics.Labels, analytics.AmpObject) {
   324  	var response *openrtb2.BidResponse
   325  	if auctionResponse != nil {
   326  		response = auctionResponse.BidResponse
   327  	}
   328  	hookExecutor.ExecuteAuctionResponseStage(response)
   329  	// Need to extract the targeting parameters from the response, as those are all that
   330  	// go in the AMP response
   331  	targets := map[string]string{}
   332  	byteCache := []byte("\"hb_cache_id")
   333  	if response != nil {
   334  		for _, seatBids := range response.SeatBid {
   335  			for _, bid := range seatBids.Bid {
   336  				if bytes.Contains(bid.Ext, byteCache) {
   337  					// Looking for cache_id to be set, as this should only be set on winning bids (or
   338  					// deal bids), and AMP can only deliver cached ads in any case.
   339  					// Note, this could cause issues if a targeting key value starts with "hb_cache_id",
   340  					// but this is a very unlikely corner case. Doing this so we can catch "hb_cache_id"
   341  					// and "hb_cache_id_{deal}", which allows for deal support in AMP.
   342  					bidExt := &openrtb_ext.ExtBid{}
   343  					err := json.Unmarshal(bid.Ext, bidExt)
   344  					if err != nil {
   345  						w.WriteHeader(http.StatusInternalServerError)
   346  						fmt.Fprintf(w, "Critical error while unpacking AMP targets: %v", err)
   347  						glog.Errorf("/openrtb2/amp Critical error unpacking targets: %v", err)
   348  						ao.Errors = append(ao.Errors, fmt.Errorf("Critical error while unpacking AMP targets: %v", err))
   349  						ao.Status = http.StatusInternalServerError
   350  						return labels, ao
   351  					}
   352  					for key, value := range bidExt.Prebid.Targeting {
   353  						targets[key] = value
   354  					}
   355  				}
   356  			}
   357  		}
   358  	}
   359  
   360  	// Extract global targeting
   361  	var extResponse openrtb_ext.ExtBidResponse
   362  	eRErr := json.Unmarshal(response.Ext, &extResponse)
   363  	if eRErr != nil {
   364  		ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr))
   365  	}
   366  	// Extract global targeting
   367  	extPrebid := extResponse.Prebid
   368  	if extPrebid != nil {
   369  		for key, value := range extPrebid.Targeting {
   370  			_, exists := targets[key]
   371  			if !exists {
   372  				targets[key] = value
   373  			}
   374  		}
   375  	}
   376  	// Now JSONify the targets for the AMP response.
   377  	ampResponse := AmpResponse{Targeting: targets}
   378  	ao, ampResponse.ORTB2.Ext = getExtBidResponse(hookExecutor, auctionResponse, reqWrapper, account, ao, errs)
   379  
   380  	ao.AmpTargetingValues = targets
   381  
   382  	// Fixes #231
   383  	enc := json.NewEncoder(w)
   384  	enc.SetEscapeHTML(false)
   385  
   386  	// If an error happens when encoding the response, there isn't much we can do.
   387  	// If we've sent _any_ bytes, then Go would have sent the 200 status code first.
   388  	// That status code can't be un-sent... so the best we can do is log the error.
   389  	if err := enc.Encode(ampResponse); err != nil {
   390  		labels.RequestStatus = metrics.RequestStatusNetworkErr
   391  		ao.Errors = append(ao.Errors, fmt.Errorf("/openrtb2/amp Failed to send response: %v", err))
   392  	}
   393  
   394  	return labels, ao
   395  }
   396  
   397  func getExtBidResponse(
   398  	hookExecutor hookexecution.HookStageExecutor,
   399  	auctionResponse *exchange.AuctionResponse,
   400  	reqWrapper *openrtb_ext.RequestWrapper,
   401  	account *config.Account,
   402  	ao analytics.AmpObject,
   403  	errs []error,
   404  ) (analytics.AmpObject, openrtb_ext.ExtBidResponse) {
   405  	var response *openrtb2.BidResponse
   406  	if auctionResponse != nil {
   407  		response = auctionResponse.BidResponse
   408  	}
   409  	// Extract any errors
   410  	var extResponse openrtb_ext.ExtBidResponse
   411  	eRErr := json.Unmarshal(response.Ext, &extResponse)
   412  	if eRErr != nil {
   413  		ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr))
   414  	}
   415  
   416  	warnings := extResponse.Warnings
   417  	if warnings == nil {
   418  		warnings = make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)
   419  	}
   420  	for _, v := range errortypes.WarningOnly(errs) {
   421  		bidderErr := openrtb_ext.ExtBidderMessage{
   422  			Code:    errortypes.ReadCode(v),
   423  			Message: v.Error(),
   424  		}
   425  		warnings[openrtb_ext.BidderReservedGeneral] = append(warnings[openrtb_ext.BidderReservedGeneral], bidderErr)
   426  	}
   427  
   428  	extBidResponse := openrtb_ext.ExtBidResponse{
   429  		Errors:   extResponse.Errors,
   430  		Warnings: warnings,
   431  	}
   432  
   433  	// add debug information if requested
   434  	if reqWrapper != nil {
   435  		if reqWrapper.Test == 1 && eRErr == nil {
   436  			if extResponse.Debug != nil {
   437  				extBidResponse.Debug = extResponse.Debug
   438  			} else {
   439  				glog.Errorf("Test set on request but debug not present in response.")
   440  				ao.Errors = append(ao.Errors, fmt.Errorf("test set on request but debug not present in response"))
   441  			}
   442  		}
   443  
   444  		stageOutcomes := hookExecutor.GetOutcomes()
   445  		ao.HookExecutionOutcome = stageOutcomes
   446  		modules, warns, err := hookexecution.GetModulesJSON(stageOutcomes, reqWrapper.BidRequest, account)
   447  		if err != nil {
   448  			err := fmt.Errorf("Failed to get modules outcome: %s", err)
   449  			glog.Errorf(err.Error())
   450  			ao.Errors = append(ao.Errors, err)
   451  		} else if modules != nil {
   452  			extBidResponse.Prebid = &openrtb_ext.ExtResponsePrebid{Modules: modules}
   453  		}
   454  
   455  		if len(warns) > 0 {
   456  			ao.Errors = append(ao.Errors, warns...)
   457  		}
   458  	}
   459  
   460  	setSeatNonBid(&extBidResponse, reqWrapper, auctionResponse)
   461  
   462  	return ao, extBidResponse
   463  }
   464  
   465  // parseRequest turns the HTTP request into an OpenRTB request.
   466  // If the errors list is empty, then the returned request will be valid according to the OpenRTB 2.5 spec.
   467  // In case of "strong recommendations" in the spec, it tends to be restrictive. If a better workaround is
   468  // possible, it will return errors with messages that suggest improvements.
   469  //
   470  // If the errors list has at least one element, then no guarantees are made about the returned request.
   471  func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImp stored_responses.BidderImpReplaceImpID, errs []error) {
   472  	// Load the stored request for the AMP ID.
   473  	reqNormal, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, e := deps.loadRequestJSONForAmp(httpRequest)
   474  	if errs = append(errs, e...); errortypes.ContainsFatalError(errs) {
   475  		return
   476  	}
   477  
   478  	// move to using the request wrapper
   479  	req = &openrtb_ext.RequestWrapper{BidRequest: reqNormal}
   480  
   481  	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
   482  	deps.setFieldsImplicitly(httpRequest, req)
   483  
   484  	// Need to ensure cache and targeting are turned on
   485  	e = initAmpTargetingAndCache(req)
   486  	if errs = append(errs, e...); errortypes.ContainsFatalError(errs) {
   487  		return
   488  	}
   489  
   490  	if err := ortb.SetDefaults(req); err != nil {
   491  		errs = append(errs, err)
   492  		return
   493  	}
   494  
   495  	hasStoredResponses := len(storedAuctionResponses) > 0
   496  	e = deps.validateRequest(req, true, hasStoredResponses, storedBidResponses, false)
   497  	errs = append(errs, e...)
   498  
   499  	return
   500  }
   501  
   502  // Load the stored OpenRTB request for an incoming AMP request, or return the errors found.
   503  func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb2.BidRequest, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImp stored_responses.BidderImpReplaceImpID, errs []error) {
   504  	req = &openrtb2.BidRequest{}
   505  	errs = nil
   506  
   507  	ampParams, err := amp.ParseParams(httpRequest)
   508  	if err != nil {
   509  		return nil, nil, nil, nil, []error{err}
   510  	}
   511  
   512  	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond)
   513  	defer cancel()
   514  
   515  	storedRequests, _, errs := deps.storedReqFetcher.FetchRequests(ctx, []string{ampParams.StoredRequestID}, nil)
   516  	if len(errs) > 0 {
   517  		return nil, nil, nil, nil, errs
   518  	}
   519  	if len(storedRequests) == 0 {
   520  		errs = []error{fmt.Errorf("No AMP config found for tag_id '%s'", ampParams.StoredRequestID)}
   521  		return
   522  	}
   523  
   524  	// The fetched config becomes the entire OpenRTB request
   525  	requestJSON := storedRequests[ampParams.StoredRequestID]
   526  	if err := json.Unmarshal(requestJSON, req); err != nil {
   527  		errs = []error{err}
   528  		return
   529  	}
   530  
   531  	storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, errs = stored_responses.ProcessStoredResponses(ctx, requestJSON, deps.storedRespFetcher, deps.bidderMap)
   532  	if err != nil {
   533  		errs = []error{err}
   534  		return
   535  	}
   536  
   537  	if deps.cfg.GenerateRequestID || req.ID == "{{UUID}}" {
   538  		newBidRequestId, err := deps.uuidGenerator.Generate()
   539  		if err != nil {
   540  			errs = []error{err}
   541  			return
   542  		}
   543  		req.ID = newBidRequestId
   544  	}
   545  
   546  	if ampParams.Debug {
   547  		req.Test = 1
   548  	}
   549  
   550  	// Two checks so users know which way the Imp check failed.
   551  	if len(req.Imp) == 0 {
   552  		errs = []error{fmt.Errorf("data for tag_id='%s' does not define the required imp array", ampParams.StoredRequestID)}
   553  		return
   554  	}
   555  	if len(req.Imp) > 1 {
   556  		errs = []error{fmt.Errorf("data for tag_id '%s' includes %d imp elements. Only one is allowed", ampParams.StoredRequestID, len(req.Imp))}
   557  		return
   558  	}
   559  
   560  	if req.App != nil {
   561  		errs = []error{errors.New("request.app must not exist in AMP stored requests.")}
   562  		return
   563  	}
   564  
   565  	// Force HTTPS as AMP requires it, but pubs can forget to set it.
   566  	if req.Imp[0].Secure == nil {
   567  		secure := int8(1)
   568  		req.Imp[0].Secure = &secure
   569  	} else {
   570  		*req.Imp[0].Secure = 1
   571  	}
   572  
   573  	errs = deps.overrideWithParams(ampParams, req)
   574  	return
   575  }
   576  
   577  func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2.BidRequest) []error {
   578  	if req.Site == nil {
   579  		req.Site = &openrtb2.Site{}
   580  	}
   581  
   582  	// Override the stored request sizes with AMP ones, if they exist.
   583  	if req.Imp[0].Banner != nil {
   584  		if format := makeFormatReplacement(ampParams.Size); len(format) != 0 {
   585  			req.Imp[0].Banner.Format = format
   586  		} else if ampParams.Size.Width != 0 {
   587  			setWidths(req.Imp[0].Banner.Format, ampParams.Size.Width)
   588  		} else if ampParams.Size.Height != 0 {
   589  			setHeights(req.Imp[0].Banner.Format, ampParams.Size.Height)
   590  		}
   591  	}
   592  
   593  	if ampParams.CanonicalURL != "" {
   594  		req.Site.Page = ampParams.CanonicalURL
   595  		// Fixes #683
   596  		if parsedURL, err := url.Parse(ampParams.CanonicalURL); err == nil {
   597  			domain := parsedURL.Host
   598  			if colonIndex := strings.LastIndex(domain, ":"); colonIndex != -1 {
   599  				domain = domain[:colonIndex]
   600  			}
   601  			req.Site.Domain = domain
   602  		}
   603  	}
   604  
   605  	setAmpExtDirect(req.Site, "1")
   606  
   607  	setEffectiveAmpPubID(req, ampParams.Account)
   608  
   609  	if ampParams.Slot != "" {
   610  		req.Imp[0].TagID = ampParams.Slot
   611  	}
   612  
   613  	if err := setConsentedProviders(req, ampParams); err != nil {
   614  		return []error{err}
   615  	}
   616  
   617  	policyWriter, policyWriterErr := amp.ReadPolicy(ampParams, deps.cfg.GDPR.Enabled)
   618  	if policyWriterErr != nil {
   619  		return []error{policyWriterErr}
   620  	}
   621  	if err := policyWriter.Write(req); err != nil {
   622  		return []error{err}
   623  	}
   624  
   625  	if ampParams.Timeout != nil {
   626  		req.TMax = int64(*ampParams.Timeout) - deps.cfg.AMPTimeoutAdjustment
   627  	}
   628  
   629  	var errors []error
   630  	if warn := setTargeting(req, ampParams.Targeting); warn != nil {
   631  		errors = append(errors, warn)
   632  	}
   633  
   634  	if err := setTrace(req, ampParams.Trace); err != nil {
   635  		return append(errors, err)
   636  	}
   637  
   638  	return errors
   639  }
   640  
   641  // setConsentedProviders sets the addtl_consent value to user.ext.ConsentedProvidersSettings.consented_providers
   642  // in its orginal Google Additional Consent string format and user.ext.consented_providers_settings.consented_providers
   643  // that is an array of ints that contains the elements found in addtl_consent
   644  func setConsentedProviders(req *openrtb2.BidRequest, ampParams amp.Params) error {
   645  	if len(ampParams.AdditionalConsent) > 0 {
   646  		reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req}
   647  
   648  		userExt, err := reqWrap.GetUserExt()
   649  		if err != nil {
   650  			return err
   651  		}
   652  
   653  		// Parse addtl_consent, that is supposed to come formatted as a Google Additional Consent string, into array of ints
   654  		consentedProvidersList := openrtb_ext.ParseConsentedProvidersString(ampParams.AdditionalConsent)
   655  
   656  		// Set user.ext.consented_providers_settings.consented_providers if elements where found
   657  		if len(consentedProvidersList) > 0 {
   658  			cps := userExt.GetConsentedProvidersSettingsOut()
   659  			if cps == nil {
   660  				cps = &openrtb_ext.ConsentedProvidersSettingsOut{}
   661  			}
   662  			cps.ConsentedProvidersList = append(cps.ConsentedProvidersList, consentedProvidersList...)
   663  			userExt.SetConsentedProvidersSettingsOut(cps)
   664  		}
   665  
   666  		// Copy addtl_consent into user.ext.ConsentedProvidersSettings.consented_providers as is
   667  		cps := userExt.GetConsentedProvidersSettingsIn()
   668  		if cps == nil {
   669  			cps = &openrtb_ext.ConsentedProvidersSettingsIn{}
   670  		}
   671  		cps.ConsentedProvidersString = ampParams.AdditionalConsent
   672  		userExt.SetConsentedProvidersSettingsIn(cps)
   673  
   674  		if err := reqWrap.RebuildRequest(); err != nil {
   675  			return err
   676  		}
   677  	}
   678  	return nil
   679  }
   680  
   681  // setTargeting merges "targeting" to imp[0].ext.data
   682  func setTargeting(req *openrtb2.BidRequest, targeting string) error {
   683  	if len(targeting) == 0 {
   684  		return nil
   685  	}
   686  
   687  	targetingData := exchange.WrapJSONInData([]byte(targeting))
   688  
   689  	if len(req.Imp[0].Ext) > 0 {
   690  		newImpExt, err := jsonpatch.MergePatch(req.Imp[0].Ext, targetingData)
   691  		if err != nil {
   692  			warn := errortypes.Warning{
   693  				WarningCode: errortypes.BadInputErrorCode,
   694  				Message:     fmt.Sprintf("unable to merge imp.ext with targeting data, check targeting data is correct: %s", err.Error()),
   695  			}
   696  
   697  			return &warn
   698  		}
   699  		req.Imp[0].Ext = newImpExt
   700  		return nil
   701  	}
   702  
   703  	req.Imp[0].Ext = targetingData
   704  	return nil
   705  }
   706  
   707  func makeFormatReplacement(size amp.Size) []openrtb2.Format {
   708  	var formats []openrtb2.Format
   709  	if size.OverrideWidth != 0 && size.OverrideHeight != 0 {
   710  		formats = []openrtb2.Format{{
   711  			W: size.OverrideWidth,
   712  			H: size.OverrideHeight,
   713  		}}
   714  	} else if size.OverrideWidth != 0 && size.Height != 0 {
   715  		formats = []openrtb2.Format{{
   716  			W: size.OverrideWidth,
   717  			H: size.Height,
   718  		}}
   719  	} else if size.Width != 0 && size.OverrideHeight != 0 {
   720  		formats = []openrtb2.Format{{
   721  			W: size.Width,
   722  			H: size.OverrideHeight,
   723  		}}
   724  	} else if size.Width != 0 && size.Height != 0 {
   725  		formats = []openrtb2.Format{{
   726  			W: size.Width,
   727  			H: size.Height,
   728  		}}
   729  	}
   730  
   731  	return append(formats, size.Multisize...)
   732  }
   733  
   734  func setWidths(formats []openrtb2.Format, width int64) {
   735  	for i := 0; i < len(formats); i++ {
   736  		formats[i].W = width
   737  	}
   738  }
   739  
   740  func setHeights(formats []openrtb2.Format, height int64) {
   741  	for i := 0; i < len(formats); i++ {
   742  		formats[i].H = height
   743  	}
   744  }
   745  
   746  // AMP won't function unless ext.prebid.targeting and ext.prebid.cache.bids are defined.
   747  // If the user didn't include them, default those here.
   748  func initAmpTargetingAndCache(req *openrtb_ext.RequestWrapper) []error {
   749  	extRequest, err := req.GetRequestExt()
   750  	if err != nil {
   751  		return []error{err}
   752  	}
   753  
   754  	prebid := extRequest.GetPrebid()
   755  	prebidModified := false
   756  
   757  	// create prebid object if missing
   758  	if prebid == nil {
   759  		prebid = &openrtb_ext.ExtRequestPrebid{}
   760  	}
   761  
   762  	// create targeting object if missing
   763  	if prebid.Targeting == nil {
   764  		prebid.Targeting = &openrtb_ext.ExtRequestTargeting{}
   765  		prebidModified = true
   766  	}
   767  
   768  	// create cache object if missing
   769  	if prebid.Cache == nil {
   770  		prebid.Cache = &openrtb_ext.ExtRequestPrebidCache{}
   771  		prebidModified = true
   772  	}
   773  	if prebid.Cache.Bids == nil {
   774  		prebid.Cache.Bids = &openrtb_ext.ExtRequestPrebidCacheBids{}
   775  		prebidModified = true
   776  	}
   777  
   778  	if prebidModified {
   779  		extRequest.SetPrebid(prebid)
   780  	}
   781  	return nil
   782  }
   783  
   784  func setAmpExtDirect(site *openrtb2.Site, value string) {
   785  	if len(site.Ext) > 0 {
   786  		if _, dataType, _, _ := jsonparser.Get(site.Ext, "amp"); dataType == jsonparser.NotExist {
   787  			if val, err := jsonparser.Set(site.Ext, []byte(value), "amp"); err == nil {
   788  				site.Ext = val
   789  			}
   790  		}
   791  	} else {
   792  		site.Ext = json.RawMessage(`{"amp":` + value + `}`)
   793  	}
   794  }
   795  
   796  // Sets the effective publisher ID for amp request
   797  func setEffectiveAmpPubID(req *openrtb2.BidRequest, account string) {
   798  	// ACCOUNT_ID is the unresolved macro name and should be ignored.
   799  	if account == "" || account == "ACCOUNT_ID" {
   800  		return
   801  	}
   802  
   803  	var pub *openrtb2.Publisher
   804  	if req.App != nil {
   805  		if req.App.Publisher == nil {
   806  			req.App.Publisher = &openrtb2.Publisher{}
   807  		}
   808  		pub = req.App.Publisher
   809  	} else if req.Site != nil {
   810  		if req.Site.Publisher == nil {
   811  			req.Site.Publisher = &openrtb2.Publisher{}
   812  		}
   813  		pub = req.Site.Publisher
   814  	}
   815  
   816  	if pub.ID == "" {
   817  		pub.ID = account
   818  	}
   819  }
   820  
   821  func setTrace(req *openrtb2.BidRequest, value string) error {
   822  	if value == "" {
   823  		return nil
   824  	}
   825  
   826  	ext, err := json.Marshal(map[string]map[string]string{"prebid": {"trace": value}})
   827  	if err != nil {
   828  		return err
   829  	}
   830  
   831  	if len(req.Ext) > 0 {
   832  		ext, err = jsonpatch.MergePatch(req.Ext, ext)
   833  		if err != nil {
   834  			return err
   835  		}
   836  	}
   837  	req.Ext = ext
   838  
   839  	return nil
   840  }
   841  
   842  // setSeatNonBid populates bidresponse.ext.prebid.seatnonbid if bidrequest.ext.prebid.returnallbidstatus is true
   843  func setSeatNonBid(finalExtBidResponse *openrtb_ext.ExtBidResponse, request *openrtb_ext.RequestWrapper, auctionResponse *exchange.AuctionResponse) bool {
   844  	if finalExtBidResponse == nil || auctionResponse == nil || request == nil {
   845  		return false
   846  	}
   847  	reqExt, err := request.GetRequestExt()
   848  	if err != nil {
   849  		return false
   850  	}
   851  	prebid := reqExt.GetPrebid()
   852  	if prebid == nil || !prebid.ReturnAllBidStatus {
   853  		return false
   854  	}
   855  	if finalExtBidResponse.Prebid == nil {
   856  		finalExtBidResponse.Prebid = &openrtb_ext.ExtResponsePrebid{}
   857  	}
   858  	finalExtBidResponse.Prebid.SeatNonBid = auctionResponse.GetSeatNonBid()
   859  	return true
   860  }