forked from prebid/Prebid.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add referer detection module (prebid#3067)
* add referere detection module * dont log all errors on console * Update message * Add jsdoc
- Loading branch information
1 parent
f272031
commit 079e27f
Showing
5 changed files
with
275 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { logWarn } from './utils'; | ||
|
||
export function detectReferer(win) { | ||
function getLevels() { | ||
let levels = walkUpWindows(); | ||
let ancestors = getAncestorOrigins(); | ||
|
||
if (ancestors) { | ||
for (let i = 0, l = ancestors.length; i < l; i++) { | ||
levels[i].ancestor = ancestors[i]; | ||
} | ||
} | ||
return levels; | ||
} | ||
|
||
function getAncestorOrigins() { | ||
try { | ||
if (!win.location.ancestorOrigins) { | ||
return; | ||
} | ||
return win.location.ancestorOrigins; | ||
} catch (e) { | ||
// Ignore error | ||
} | ||
} | ||
|
||
function getPubUrlStack(levels) { | ||
let stack = []; | ||
let defUrl = null; | ||
let encodedUrl = null; | ||
let frameLocation = null; | ||
let prevFrame = null; | ||
let prevRef = null; | ||
let ancestor = null; | ||
let detectedRefererUrl = null; | ||
|
||
let i; | ||
for (i = levels.length - 1; i >= 0; i--) { | ||
try { | ||
frameLocation = levels[i].location; | ||
} catch (e) { | ||
// Ignore error | ||
} | ||
|
||
if (frameLocation) { | ||
encodedUrl = encodeURIComponent(frameLocation); | ||
stack.push(encodedUrl); | ||
if (!detectedRefererUrl) { | ||
detectedRefererUrl = encodedUrl; | ||
} | ||
} else if (i !== 0) { | ||
prevFrame = levels[i - 1]; | ||
try { | ||
prevRef = prevFrame.referrer; | ||
ancestor = prevFrame.ancestor; | ||
} catch (e) { | ||
// Ignore error | ||
} | ||
|
||
if (prevRef) { | ||
encodedUrl = encodeURIComponent(prevRef); | ||
stack.push(encodedUrl); | ||
if (!detectedRefererUrl) { | ||
detectedRefererUrl = encodedUrl; | ||
} | ||
} else if (ancestor) { | ||
encodedUrl = encodeURIComponent(ancestor); | ||
stack.push(encodedUrl); | ||
if (!detectedRefererUrl) { | ||
detectedRefererUrl = encodedUrl; | ||
} | ||
} else { | ||
stack.push(defUrl); | ||
} | ||
} else { | ||
stack.push(defUrl); | ||
} | ||
} | ||
return { | ||
stack, | ||
detectedRefererUrl | ||
}; | ||
} | ||
|
||
function walkUpWindows() { | ||
let acc = []; | ||
let currentWindow; | ||
do { | ||
try { | ||
currentWindow = currentWindow ? currentWindow.parent : win; | ||
try { | ||
acc.push({ | ||
referrer: currentWindow.document.referrer || null, | ||
location: currentWindow.location.href || null, | ||
isTop: (currentWindow == win.top) | ||
}); | ||
} catch (e) { | ||
acc.push({ | ||
referrer: null, | ||
location: null, | ||
isTop: (currentWindow == win.top) | ||
}); | ||
logWarn('Trying to access cross domain iframe. Continuing without referrer and location'); | ||
} | ||
} catch (e) { | ||
acc.push({ | ||
referrer: null, | ||
location: null, | ||
isTop: false | ||
}); | ||
return acc; | ||
} | ||
} while (currentWindow != win.top); | ||
return acc; | ||
} | ||
|
||
/** | ||
* Referer info | ||
* @typedef {Object} refererInfo | ||
* @property {string} referer detected top url | ||
* @property {boolean} reachedTop whether prebid was able to walk upto top window or not | ||
* @property {number} numIframes number of iframes | ||
* @property {string} stack comma separated urls of all origins | ||
*/ | ||
|
||
/** | ||
* Get referer info | ||
* @returns {refererInfo} | ||
*/ | ||
function refererInfo() { | ||
try { | ||
let levels = getLevels(); | ||
let numIframes = levels.length - 1; | ||
let reachedTop = (levels[numIframes].location !== null || | ||
(numIframes > 0 && levels[numIframes - 1].referrer !== null)); | ||
let stackInfo = getPubUrlStack(levels); | ||
|
||
return { | ||
referer: stackInfo.detectedRefererUrl, | ||
reachedTop, | ||
numIframes, | ||
stack: stackInfo.stack, | ||
}; | ||
} catch (e) { | ||
// Ignore error | ||
} | ||
} | ||
|
||
return refererInfo; | ||
} | ||
|
||
export const getRefererInfo = detectReferer(window); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { detectReferer } from 'src/refererDetection'; | ||
import { expect } from 'chai'; | ||
|
||
var mocks = { | ||
createFakeWindow: function (referrer, href) { | ||
return { | ||
document: { | ||
referrer: referrer | ||
}, | ||
location: { | ||
href: href, | ||
// TODO: add ancestorOrigins to increase test coverage | ||
}, | ||
parent: null, | ||
top: null | ||
}; | ||
} | ||
} | ||
|
||
describe('referer detection', () => { | ||
it('should return referer details in nested friendly iframes', function() { | ||
// Fake window object to test friendly iframes | ||
// - Main page http://example.com/page.html | ||
// - - Iframe1 http://example.com/iframe1.html | ||
// - - - Iframe2 http://example.com/iframe2.html | ||
let mockIframe2WinObject = mocks.createFakeWindow('http://example.com/iframe1.html', 'http://example.com/iframe2.html'); | ||
let mockIframe1WinObject = mocks.createFakeWindow('http://example.com/page.html', 'http://example.com/iframe1.html'); | ||
let mainWinObject = mocks.createFakeWindow('http://example.com/page.html', 'http://example.com/page.html'); | ||
mockIframe2WinObject.parent = mockIframe1WinObject; | ||
mockIframe2WinObject.top = mainWinObject; | ||
mockIframe1WinObject.parent = mainWinObject; | ||
mockIframe1WinObject.top = mainWinObject; | ||
mainWinObject.top = mainWinObject; | ||
|
||
const getRefererInfo = detectReferer(mockIframe2WinObject); | ||
let result = getRefererInfo(); | ||
let expectedResult = { | ||
referer: 'http%3A%2F%2Fexample.com%2Fpage.html', | ||
reachedTop: true, | ||
numIframes: 2, | ||
stack: [ | ||
'http%3A%2F%2Fexample.com%2Fpage.html', | ||
'http%3A%2F%2Fexample.com%2Fiframe1.html', | ||
'http%3A%2F%2Fexample.com%2Fiframe2.html' | ||
] | ||
}; | ||
expect(result).to.deep.equal(expectedResult); | ||
}); | ||
|
||
it('should return referer details in nested cross domain iframes', function() { | ||
// Fake window object to test cross domain iframes. | ||
// - Main page http://example.com/page.html | ||
// - - Iframe1 http://aaa.com/iframe1.html | ||
// - - - Iframe2 http://bbb.com/iframe2.html | ||
let mockIframe2WinObject = mocks.createFakeWindow('http://aaa.com/iframe1.html', 'http://bbb.com/iframe2.html'); | ||
// Sinon cannot throw exception when accessing a propery so passing null to create cross domain | ||
// environment for refererDetection module | ||
let mockIframe1WinObject = mocks.createFakeWindow(null, null); | ||
let mainWinObject = mocks.createFakeWindow(null, null); | ||
mockIframe2WinObject.parent = mockIframe1WinObject; | ||
mockIframe2WinObject.top = mainWinObject; | ||
mockIframe1WinObject.parent = mainWinObject; | ||
mockIframe1WinObject.top = mainWinObject; | ||
mainWinObject.top = mainWinObject; | ||
|
||
const getRefererInfo = detectReferer(mockIframe2WinObject); | ||
let result = getRefererInfo(); | ||
let expectedResult = { | ||
referer: 'http%3A%2F%2Faaa.com%2Fiframe1.html', | ||
reachedTop: false, | ||
numIframes: 2, | ||
stack: [ | ||
null, | ||
'http%3A%2F%2Faaa.com%2Fiframe1.html', | ||
'http%3A%2F%2Fbbb.com%2Fiframe2.html' | ||
] | ||
}; | ||
expect(result).to.deep.equal(expectedResult); | ||
}); | ||
}); |