Code coverage report for esecurity/lib/middleware/csp.js

Statements: 96.08% (98 / 102)      Branches: 95.51% (85 / 89)      Functions: 100% (8 / 8)      Lines: 96.63% (86 / 89)      Ignored: none     

All files » esecurity/lib/middleware/ » csp.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141  1       1   45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45 45   45 1   45 47     45 2 2 2 2 2 2 2 2 2 2     1 2   2     2     2         2 2         45     45 2   43   43 2 2     41 42   42 2   42 66 66   4 4   12 12   2 2   4 4   44       42     41   41 41 41 41 41 41 41 41 41 41 41         41   41 39   41 4   41 47 47     41      
 
var util = require("util"),
    path = require("path"),
    fs = require("fs");
 
module.exports = function CspConstructor(opts) {
 
    opts = opts || {};
    opts.headers = opts.headers || ["standard"];
    opts.reportOnly = opts.reportOnly || false;
    opts.rules_secure = opts.rules_secure || false;
    opts.defaultSrc = opts.defaultSrc || false;
    opts.scriptSrc = opts.scriptSrc || false;
    opts.objectSrc = opts.objectSrc || false;
    opts.styleSrc = opts.styleSrc || false;
    opts.imgSrc = opts.imgSrc || false;
    opts.mediaSrc = opts.mediaSrc || false;
    opts.frameSrc = opts.frameSrc || false;
    opts.fontSrc = opts.fontSrc || false;
    opts.connectSrc = opts.connectSrc || false;
    opts.sandbox = opts.sandbox || false;
    opts.reportUri = opts.reportUri || false;
    opts.reportFilename = opts.reportFilename || "/tmp/esecurity_cspreport";
    opts.onReport = opts.onReport || onReport;
    
    if (!util.isArray(opts.headers))
        opts.headers = [opts.headers];
    
    opts.headers.forEach(function (header, key) {
        opts.headers[key] = String(header).toLowerCase();
    });
    
    if (opts.rules_secure) {
        opts.defaultSrc = ["none"];
        opts.scriptSrc = ["self", "www.google-analytics.com", "ajax.googleapis.com"];
        opts.objectSrc = false;
        opts.styleSrc = ["self"];
        opts.imgSrc = ["self"];
        opts.mediaSrc = ["self"];
        opts.frameSrc = false;
        opts.fontSrc = false;
        opts.connectSrc = ["self"];
        opts.sandbox = false;
    }
    
    function onReport(data) {
        data = JSON.stringify(data || {});
        
        Iif ("" === data)
            return;
        
        var msg = "[" + new Date() + "] " + data + "\n";
        
        // backward compatibility (nodejs <= 0.6)
        Iif (!fs.appendFile) {
            var report = fs.createWriteStream(opts.reportFilename, {'flags': 'a'});
            report.end(msg);
        }
        else {
            fs.appendFile(opts.reportFilename, msg, function (err) {
              Iif (err) throw err;
            });
        }
    }
    
    return function csp(req, res, next) {
        
        // self-awareness
        if (req._esecurity_csp)
            return next();
 
        req._esecurity_csp = true;
        
        if (opts.reportUri && opts.onReport && "POST" === req.method && opts.reportUri === req.path) {
            opts.onReport(req.body);
            return res.status(200).end();
        }
        
        var sanitize = function (src) {
            var srcSanitized = [];
            
            if (!util.isArray(src))
                src = [src];
            
            src.forEach(function (elem) {
                elem = String(elem).trim();
                switch (elem.toLowerCase()) {
                    case "none":
                        srcSanitized.push("'none'");
                        break;
                    case "self":
                        srcSanitized.push("'self'");
                        break;
                    case "unsafe-inline":
                        srcSanitized.push("'unsafe-inline'");
                        break;
                    case "unsafe-eval":
                        srcSanitized.push("'unsafe-eval'");
                        break;
                    default:
                        srcSanitized.push(elem);
                }
            });
            
            return srcSanitized.join(" ");
        };
        
        var cspHeader = [];
        
        if (opts.defaultSrc) cspHeader.push('default-src ' + sanitize(opts.defaultSrc));
        if (opts.scriptSrc) cspHeader.push('script-src ' + sanitize(opts.scriptSrc));
        if (opts.objectSrc) cspHeader.push('object-src ' + sanitize(opts.objectSrc));
        if (opts.styleSrc) cspHeader.push('style-src ' + sanitize(opts.styleSrc));
        if (opts.imgSrc) cspHeader.push('img-src ' + sanitize(opts.imgSrc));
        if (opts.mediaSrc) cspHeader.push('media-src ' + sanitize(opts.mediaSrc));
        if (opts.frameSrc) cspHeader.push('frame-src ' + sanitize(opts.frameSrc));
        if (opts.fontSrc) cspHeader.push('font-src ' + sanitize(opts.fontSrc));
        if (opts.connectSrc) cspHeader.push('connect-src ' + sanitize(opts.connectSrc));
        if (opts.sandbox) cspHeader.push('sandbox ' + sanitize(opts.sandbox));
        if (opts.reportUri) cspHeader.push('report-uri ' + sanitize(opts.reportUri));
        
        /*if (!cspHeader.length)
            return next();*/
            
        var headerNames = [];
        
        if (~opts.headers.indexOf("standard"))
            headerNames.push("Content-Security-Policy");
        
        if (~opts.headers.indexOf("experimental"))
            headerNames = headerNames.concat(["X-Content-Security-Policy", "X-Webkit-CSP"]);
        
        headerNames.forEach(function (header) {
            if (opts.reportOnly) header += "-Report-Only";
            res.set(header, cspHeader.join(";"));
        });
        
        return next();
    };
};