/**
* @module urls
*/
var url = require('url');
var util = require('util');
/**
* Function to convert a simple string to a URL macro.
*
* Simply uppercases string, and encloses in '${}'
*
* Doesn't check for spaces or anything not URL-safe, so just
* don't give it anything stupid and expect it to work.
*
* @type {Function}
* @param str any string
*/
var stringToMacro = exports.toMacro = function(str){
return util.format('${%s}', str.toUpperCase());
};
/**
* Populates macro values in any arbitrary text according to macro-to-string
* definition and macro value mapping.
*
* @type {Function}
*/
var expandURLMacros = exports.expandURLMacros = function(url_string, macro_value_mapping){
for (var macro in macro_value_mapping) {
if (macro_value_mapping.hasOwnProperty(macro)){
var macro_string = stringToMacro(macro);
url_string = url_string.replace(macro_string, macro_value_mapping[macro]);
}
}
return url_string;
};
/**
* Abstract class to contain base methods used for constructing & parsing
* adserver URL's.
*
* @param {String} hostname domain & port #, if applicable. Ex: 'localhost:5000' or 'ads.cliquesads.com'
* @param {String} secure_hostname domain & port #, if applicable. Ex: 'localhost:5000' or 'ads.cliquesads.com'
* @param {Object} query_schema object containing query parameters expected for a
* given URL that specifies the level of encoding / decoding to perform for each
* @param {String} path path for this URL
* @param {Number} port port number if applicable, otherwise 'null'
* @constructor
* @abstract
*/
var AdServerURL = function(hostname, secure_hostname, path, query_schema, port){
this.hostname = hostname;
this.secure_hostname= secure_hostname;
this.port = port;
this.path = path;
//contains query fields to expect & levels of encoding expected for each
this.query_schema = query_schema || {};
};
/**
* Parses & decodes URL query params from raw URL
*
* Doesn't actually parse the whole string since Express parses out query params for
* you into an object, so just pass request.query as query argument.
*
* @param {Object} query output of request.query
* @param {Boolean} secure
*/
AdServerURL.prototype.parse = function(query, secure){
var self = this;
self.secure = secure;
Object.keys(query).forEach(function(item){
if (query.hasOwnProperty(item)){
// decode as necessary
self[item] = query[item];
for (var d=0; d < self.query_schema[item]; d++){
self[item] = decodeURIComponent(self[item])
}
}
});
};
/**
* Lightweight wrapper around url.format to encode any query params which are
* expected to be encoded, and return fully formed URL.
*
* Any query values in `query` arg will be encoded according to this.query_schema
* and added to the formatted URL.
*
* Any query values in query_schema AND NOT in `query` object will be formatted as
* macros using standard macro delimiters.
*
* Stores resulting URL on instance as this.url
*
* @param {Object} query object containing a URL's query param keys & values
* @param {Boolean} secure
*/
AdServerURL.prototype.format = function(query, secure){
var self = this;
query = query || {};
// encode all query params appropriate number of times
for (var key in self.query_schema){
if (self.query_schema.hasOwnProperty(key)){
if (query.hasOwnProperty(key)){
// if value passed in query object for this key,
// just encode it however many times we're supposed
// to according to self.query_schema
for (var d=0; d < self.query_schema[key]; d++) {
query[key] = encodeURIComponent(query[key])
}
} else {
query[key] = stringToMacro(key);
}
}
}
// This pisses me off royally, but URL format automatically encodes
// anything encodable found in query object, so have to decode once before
// returning.
var encodedURL = url.format({
protocol: secure ? 'https' : 'http',
hostname: secure ? self.secure_hostname : self.hostname,
port: self.port,
pathname: self.path,
query: query
});
this.url = decodeURIComponent(encodedURL);
return this.url;
};
/**
* @const PUB_PATH
* @type {string}
*/
var PUB_PATH = exports.PUB_PATH = '/pub';
/**
* Publisher URL subclass
* @class
* @type {string}
*/
var PubURL = exports.PubURL = function(hostname, secure_hostname, port){
var query_schema = {
pid: 0,
cb: 0
};
AdServerURL.call(this, hostname, secure_hostname, PUB_PATH, query_schema, port);
};
util.inherits(PubURL, AdServerURL);
/**
* @const IMP_PATH
* @type {string}
*/
var IMP_PATH = exports.IMP_PATH = '/crg';
/**
* Impression URL subclass
* @class
* @type {string}
*/
var ImpURL = exports.ImpURL = function(hostname, secure_hostname, port){
var query_schema = {
crgid: 0,
pid: 0,
impid: 0
};
AdServerURL.call(this, hostname, secure_hostname, IMP_PATH, query_schema, port);
};
util.inherits(ImpURL, AdServerURL);
/**
* @const CR_PATH
* @type {string}
*/
var CR_PATH = exports.CR_PATH = '/cr';
/**
* Creative Test URL subclass
*
* @class
* @type {string}
*/
var CreativeURL = exports.CreativeURL = function(hostname, secure_hostname, port){
var query_schema = {
cid: 0
};
AdServerURL.call(this, hostname, secure_hostname, CR_PATH, query_schema, port);
};
util.inherits(CreativeURL, AdServerURL);
/**
* @const PUBCR_PATH
* @type {string}
*/
var PUBCR_PATH = exports.PUBCR_PATH = '/pubcr';
/**
* Publisher Default Creative URL subclass
* @class
* @type {string}
*/
var PubCreativeURL = exports.PubCreativeURL = function(hostname, secure_hostname, port){
var query_schema = {
pid: 0
};
AdServerURL.call(this, hostname, secure_hostname, PUBCR_PATH, query_schema, port);
};
util.inherits(PubCreativeURL, AdServerURL);
/**
* @const CLICK_PATH
* @type {string}
*/
var CLICK_PATH = exports.CLICK_PATH = '/clk';
/**
* Click URL subclass
* @class
* @type {string}
*/
var ClickURL = exports.ClickURL = function(hostname, secure_hostname, port){
var query_schema = {
cid: 0,
pid: 0,
advid: 0,
crgid: 0,
campid: 0,
redir: 1,
impid: 0
};
AdServerURL.call(this, hostname, secure_hostname, CLICK_PATH, query_schema, port);
};
util.inherits(ClickURL, AdServerURL);
/**
* @const CLICK_PATH
* @type {string}
*/
var ACTION_PATH = exports.ACTION_PATH = '/ab';
/**
* Conversion URL subclass
*
* @class
* @type {string}
*/
var ActionBeaconURL = exports.ActionBeaconURL = function(hostname, secure_hostname, port){
var query_schema = {
abid: 0,
value: 0,
advid: 0
};
AdServerURL.call(this, hostname, secure_hostname, ACTION_PATH, query_schema, port);
};
util.inherits(ActionBeaconURL, AdServerURL);