diff --git a/libraries/ortbConverter/processors/banner.js b/libraries/ortbConverter/processors/banner.js
index 51c93b652ef..2d0136c84b2 100644
--- a/libraries/ortbConverter/processors/banner.js
+++ b/libraries/ortbConverter/processors/banner.js
@@ -1,4 +1,4 @@
-import {createTrackPixelHtml, deepAccess, inIframe, mergeDeep} from '../../../src/utils.js';
+import {createTrackPixelHtml, deepAccess, encodeMacroURI, inIframe, mergeDeep} from '../../../src/utils.js';
import {BANNER} from '../../../src/mediaTypes.js';
import {sizesToFormat} from '../lib/sizes.js';
@@ -24,7 +24,7 @@ export function fillBannerImp(imp, bidRequest, context) {
}
}
-export function bannerResponseProcessor({createPixel = (url) => createTrackPixelHtml(decodeURIComponent(url))} = {}) {
+export function bannerResponseProcessor({createPixel = (url) => createTrackPixelHtml(decodeURIComponent(url), encodeMacroURI)} = {}) {
return function fillBannerResponse(bidResponse, bid) {
if (bidResponse.mediaType === BANNER) {
if (bid.adm && bid.nurl) {
diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js
index a66e825e5df..2d2fadfe4ca 100644
--- a/modules/engageyaBidAdapter.js
+++ b/modules/engageyaBidAdapter.js
@@ -81,7 +81,7 @@ function parseBannerResponse(rec, response) {
const title = rec.title && rec.title.trim() ? `
${rec.title}
` : '';
const displayName = rec.displayName && title ? `${rec.displayName}
` : '';
const trackers = getImpressionTrackers(rec, response)
- .map(createTrackPixelHtml)
+ .map((url) => createTrackPixelHtml(url))
.join('');
return `${style}`;
}
diff --git a/src/utils.js b/src/utils.js
index 71cc78090a6..abb69209020 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -489,19 +489,32 @@ export function insertUserSyncIframe(url, done, timeout) {
/**
* Creates a snippet of HTML that retrieves the specified `url`
* @param {string} url URL to be requested
+ * @param encode
* @return {string} HTML snippet that contains the img src = set to `url`
*/
-export function createTrackPixelHtml(url) {
+export function createTrackPixelHtml(url, encode = encodeURI) {
if (!url) {
return '';
}
- let escapedUrl = encodeURI(url);
+ let escapedUrl = encode(url);
let img = '';
img += '
';
return img;
};
+/**
+ * encodeURI, but preserves macros of the form '${MACRO}' (e.g. '${AUCTION_PRICE}')
+ * @param url
+ * @return {string}
+ */
+export function encodeMacroURI(url) {
+ const macros = Array.from(url.matchAll(/\$({[^}]+})/g)).map(match => match[1]);
+ return macros.reduce((str, macro) => {
+ return str.replace('$' + encodeURIComponent(macro), '$' + macro)
+ }, encodeURI(url))
+}
+
/**
* Creates a snippet of Iframe HTML that retrieves the specified `url`
* @param {string} url plain URL to be requested
diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js
index e0a114d8cf6..8ea68aadecc 100644
--- a/test/spec/utils_spec.js
+++ b/test/spec/utils_spec.js
@@ -3,7 +3,7 @@ import {expect} from 'chai';
import { TARGETING_KEYS } from 'src/constants.js';
import * as utils from 'src/utils.js';
import {getHighestCpm, getLatestHighestCpmBid, getOldestHighestCpmBid} from '../../src/utils/reducers.js';
-import {binarySearch, deepEqual, memoize, waitForElementToLoad} from 'src/utils.js';
+import {binarySearch, deepEqual, encodeMacroURI, memoize, waitForElementToLoad} from 'src/utils.js';
import {convertCamelToUnderscore} from '../../libraries/appnexusUtils/anUtils.js';
var assert = require('assert');
@@ -778,6 +778,22 @@ describe('Utils', function () {
expect(parsed.search).to.equal('?search=test&foo=bar&bar=foo&foo=xxx');
});
});
+
+ describe('encodeMacroURI', () => {
+ [
+ ['https://www.example.com', 'https://www.example.com'],
+ ['https://www.example/${MACRO}', 'https://www.example/${MACRO}'],
+ ['http://www.example/è', `http://www.example/${encodeURIComponent('è')}`],
+ ['https://www.${MACRO_1}/${MACRO_1}/${MACRO_2}è', 'https://www.${MACRO_1}/${MACRO_1}/${MACRO_2}' + encodeURIComponent('è')],
+ ['http://${MACRO}${MACRO}/${MACRO}', 'http://${MACRO}${MACRO}/${MACRO}'],
+ ['{MACRO}${MACRO}', `${encodeURIComponent('{MACRO}')}\${MACRO}`],
+ ['https://www.example.com?p=${AUCTION_PRICE}', 'https://www.example.com?p=${AUCTION_PRICE}']
+ ].forEach(([input, expected]) => {
+ it(`can encode ${input} -> ${expected}`, () => {
+ expect(encodeMacroURI(input)).to.eql(expected);
+ })
+ })
+ })
});
describe('insertElement', function () {