Skip to content
Snippets Groups Projects
request-analyzer.js 5.33 KiB
Newer Older
Thomas Rientjes's avatar
Thomas Rientjes committed
/**
 * Request Analyzer
 * Belongs to Decentraleyes.
 *
 * @author      Thomas Rientjes
 * @since       2014-05-30
 * @license     MPL 2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

/**
 * Imports
 */

/**
 * Resource version mappings.
 * @var {object} mappings
 */
Thomas Rientjes's avatar
Thomas Rientjes committed
var mappings = require('./mappings');

/**
 * Resource version shorthands.
 * @var {object} shorthands
 */
var shorthands = require('./shorthands');

Thomas Rientjes's avatar
Thomas Rientjes committed
/**
 * Gets and sets add-on specific preferences.
 * @var {object} simplePreferences
 */
var simplePreferences = require('sdk/simple-prefs');

/**
 * Constants
 */

const MAPPING_FILE_EXPRESSION = new RegExp('.map$', 'i');
const VERSION_EXPRESSION = /(?:\d{1,2}\.){1,3}\d{1,2}/;
const VERSION_PLACEHOLDER = '{version}';
const WEB_PREFIX_VALUE = 'www.';
const WEB_PREFIX_LENGTH = WEB_PREFIX_VALUE.length;
const VALUE_SEPARATOR = ';';

/**
 * Variables
 */

Thomas Rientjes's avatar
Thomas Rientjes committed
var preferences = simplePreferences.prefs;
var whitelistedDomains = {};

/**
 * Initializations
 */

Thomas Rientjes's avatar
Thomas Rientjes committed
_applyWhitelistPreference();

/**
 * Event Handlers
 */

Thomas Rientjes's avatar
Thomas Rientjes committed
simplePreferences.on('domainWhitelist', _applyWhitelistPreference);
Thomas Rientjes's avatar
Thomas Rientjes committed
/**
 * Public Methods
 */

Thomas Rientjes's avatar
Thomas Rientjes committed
exports.isValidCandidate = function (httpChannel) {
    var initiatorDomain;

    // See if the request uses a supported URI scheme.
    if (!httpChannel.URI.schemeIs('http') &&
        !httpChannel.URI.schemeIs('https')) {
        return false;
    }

Thomas Rientjes's avatar
Thomas Rientjes committed
    // See if the request is targeted at a Content Delivery Network.
    if (mappings[httpChannel.URI.host] === undefined) {
        return false;
    }
Thomas Rientjes's avatar
Thomas Rientjes committed

Thomas Rientjes's avatar
Thomas Rientjes committed
    // Attempt to determine the domain of the request initiator.
    initiatorDomain =
Thomas Rientjes's avatar
Thomas Rientjes committed
        httpChannel.loadInfo && httpChannel.loadInfo.loadingDocument && httpChannel.loadInfo.loadingDocument.domain ||
        httpChannel.referrer && httpChannel.referrer.host;
Thomas Rientjes's avatar
Thomas Rientjes committed
    // If the request initiator could be determined and is whitelisted.
    if (initiatorDomain && whitelistedDomains[_normalizeDomain(initiatorDomain)]) {
        // Remove sensitive headers from the request.
Thomas Rientjes's avatar
Thomas Rientjes committed
        httpChannel.setRequestHeader('Referer', null, false);
        httpChannel.setRequestHeader('Origin', null, false);
        httpChannel.setRequestHeader('Cookie', null, false);

Thomas Rientjes's avatar
Thomas Rientjes committed
        return false;
Thomas Rientjes's avatar
Thomas Rientjes committed
    // Only requests of type GET can be valid candidates.
    return httpChannel.requestMethod === 'GET';
Thomas Rientjes's avatar
Thomas Rientjes committed

Thomas Rientjes's avatar
Thomas Rientjes committed
exports.getLocalTarget = function (channelHost, channelPath) {
Thomas Rientjes's avatar
Thomas Rientjes committed
    var hostMappings, basePath, resourceMappings;
Thomas Rientjes's avatar
Thomas Rientjes committed
    // Use the proper mappings for the targeted host.
    hostMappings = mappings[channelHost];

Thomas Rientjes's avatar
Thomas Rientjes committed
    // Resource mapping files are never locally available.
    if (MAPPING_FILE_EXPRESSION.test(channelPath)) {
        return false;
    }
Thomas Rientjes's avatar
Thomas Rientjes committed

Thomas Rientjes's avatar
Thomas Rientjes committed
    basePath = _matchBasePath(hostMappings, channelPath);
    resourceMappings = hostMappings[basePath];
Thomas Rientjes's avatar
Thomas Rientjes committed

Thomas Rientjes's avatar
Thomas Rientjes committed
    if (!resourceMappings) {
Thomas Rientjes's avatar
Thomas Rientjes committed
    // Return either the local target's path or false.
    return _findLocalTarget(resourceMappings, basePath, channelHost, channelPath);
Thomas Rientjes's avatar
Thomas Rientjes committed

/**
 * Private Methods
 */

function _matchBasePath (hostMappings, channelPath) {
Thomas Rientjes's avatar
Thomas Rientjes committed
    for (let basePath of Object.keys(hostMappings)) {
Thomas Rientjes's avatar
Thomas Rientjes committed
        if (channelPath.startsWith(basePath)) {
            return basePath;
Thomas Rientjes's avatar
Thomas Rientjes committed

    return false;
function _findLocalTarget (resourceMappings, basePath, channelHost, channelPath) {
Thomas Rientjes's avatar
Thomas Rientjes committed

    var resourcePath, versionNumber, resourcePattern;

    resourcePath = channelPath.replace(basePath, '');
Thomas Rientjes's avatar
Thomas Rientjes committed
    versionNumber = resourcePath.match(VERSION_EXPRESSION);
    resourcePattern = resourcePath.replace(versionNumber, VERSION_PLACEHOLDER);
Thomas Rientjes's avatar
Thomas Rientjes committed

    // Determine if the resource path has a static mapping.
    if (resourceMappings[resourcePath]) {

        // Prepare and return a local target.
        return {
            'path': resourceMappings[resourcePath].path,
            'type': resourceMappings[resourcePath].type
        };
    }

    // Determine if the resource path fits into a resource mold.
Thomas Rientjes's avatar
Thomas Rientjes committed
    for (let resourceMold of Object.keys(resourceMappings)) {
Thomas Rientjes's avatar
Thomas Rientjes committed

Thomas Rientjes's avatar
Thomas Rientjes committed
        if (resourcePattern.startsWith(resourceMold)) {
Thomas Rientjes's avatar
Thomas Rientjes committed

            let targetPath, targetType, hostShorthands;

            targetPath = resourceMappings[resourceMold].path;
            targetPath = targetPath.replace(VERSION_PLACEHOLDER, versionNumber);
            targetType = resourceMappings[resourceMold].type;

            hostShorthands = shorthands[channelHost];

            if (hostShorthands && hostShorthands[targetPath]) {

                let shorthand = hostShorthands[targetPath];

                targetPath = shorthand.path;
                targetType = shorthand.type;
            }

Thomas Rientjes's avatar
Thomas Rientjes committed
            // Prepare and return a local target.
            return {
                'path': targetPath,
                'type': targetType
Thomas Rientjes's avatar
Thomas Rientjes committed

    return false;
Thomas Rientjes's avatar
Thomas Rientjes committed
}
function _normalizeDomain (domain) {

    domain = domain.toLowerCase().trim();

Thomas Rientjes's avatar
Thomas Rientjes committed
    if (domain.startsWith(WEB_PREFIX_VALUE)) {
        domain = domain.slice(WEB_PREFIX_LENGTH);
function _applyWhitelistPreference () {
Thomas Rientjes's avatar
Thomas Rientjes committed
    whitelistedDomains = {};

    //noinspection JSUnresolvedVariable
Thomas Rientjes's avatar
Thomas Rientjes committed
    preferences.domainWhitelist.split(VALUE_SEPARATOR).forEach(function (domain) {
        whitelistedDomains[_normalizeDomain(domain)] = true;