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