Code coverage report for nock/lib/common.js

Statements: 93.97% (109 / 116)      Branches: 80.3% (53 / 66)      Functions: 100% (21 / 21)      Lines: 94.74% (108 / 114)      Ignored: none     

All files » nock/lib/ » common.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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271    1 1             1 543 543 543 522 522 211     211       543 543 543     543 1086 1086       543                 1   287 2       285 285 285 285 4     281 54605763         281         285                   1   283 215       68   68           68         1                         1 58   58 116   116             116         116         116   293     116               1 91     91 114   114 114 114 114     91     1 15   15       15 15   15   14     15   15     1 478 223     255 255     1 625 193         432 432 569 569 1   568     431     1 232 7                     1   27       27     27 53 53 12               1 33 2       1 6049 76   5973       1 1 1 1 1 1 1 1 1 1 1 1  
'use strict';
 
var _ = require('lodash');
var debug = require('debug')('nock.common');
 
/**
 * Normalizes the request options so that it always has `host` property.
 *
 * @param  {Object} options - a parsed options object of the request
 */
var normalizeRequestOptions = function(options) {
  options.proto = options.proto || (options._https_ ? 'https': 'http');
  options.port = options.port || ((options.proto === 'http') ? 80 : 443);
  if (options.host) {
    debug('options.host:', options.host);
    if (! options.hostname) {
      Iif (options.host.split(':').length == 2) {
        options.hostname = options.host.split(':')[0];
      } else {
        options.hostname = options.host;
      }
    }
  }
  debug('options.hostname in the end: %j', options.hostname);
  options.host = (options.hostname || 'localhost') + ':' + options.port;
  debug('options.host in the end: %j', options.host);
 
  /// lowercase host names
  ['hostname', 'host'].forEach(function(attr) {
    Eif (options[attr]) {
      options[attr] = options[attr].toLowerCase();
    }
  })
 
  return options;
};
 
/**
 * Returns true if the data contained in buffer is binary which in this case means
 * that it cannot be reconstructed from its utf8 representation.
 *
 * @param  {Object} buffer - a Buffer object
 */
var isBinaryBuffer = function(buffer) {
 
  if(!Buffer.isBuffer(buffer)) {
    return false;
  }
 
  //  Test if the buffer can be reconstructed verbatim from its utf8 encoding.
  var utfEncodedBuffer = buffer.toString('utf8');
  var reconstructedBuffer = new Buffer(utfEncodedBuffer, 'utf8');
  var compareBuffers = function(lhs, rhs) {
    if(lhs.length !== rhs.length) {
      return false;
    }
 
    for(var i = 0; i < lhs.length; ++i) {
      Iif(lhs[i] !== rhs[i]) {
        return false;
      }
    }
 
    return true;
  };
 
  //  If the buffers are *not* equal then this is a "binary buffer"
  //  meaning that it cannot be faitfully represented in utf8.
  return !compareBuffers(buffer, reconstructedBuffer);
 
};
 
/**
 * If the chunks are Buffer objects then it returns a single Buffer object with the data from all the chunks.
 * If the chunks are strings then it returns a single string value with data from all the chunks.
 *
 * @param  {Array} chunks - an array of Buffer objects or strings
 */
var mergeChunks = function(chunks) {
 
  if(_.isEmpty(chunks)) {
    return new Buffer(0);
  }
 
  //  We assume that all chunks are Buffer objects if the first is buffer object.
  var areBuffers = Buffer.isBuffer(_.first(chunks));
 
  Iif(!areBuffers) {
    //  When the chunks are not buffers we assume that they are strings.
    return chunks.join('');
  }
 
  //  Merge all the buffers into a single Buffer object.
  return Buffer.concat(chunks);
 
};
 
//  Array where all information about all the overridden requests are held.
var requestOverride = [];
 
/**
 * Overrides the current `request` function of `http` and `https` modules with
 * our own version which intercepts issues HTTP/HTTPS requests and forwards them
 * to the given `newRequest` function.
 *
 * @param  {Function} newRequest - a function handling requests; it accepts four arguments:
 *   - proto - a string with the overridden module's protocol name (either `http` or `https`)
 *   - overriddenRequest - the overridden module's request function already bound to module's object
 *   - options - the options of the issued request
 *   - callback - the callback of the issued request
 */
var overrideRequests = function(newRequest) {
  debug('overriding requests');
 
  ['http', 'https'].forEach(function(proto) {
    debug('- overriding request for', proto);
 
    var moduleName = proto, // 1 to 1 match of protocol and module is fortunate :)
        module = {
          http: require('http'),
          https: require('https')
        }[moduleName],
        overriddenRequest = module.request;
 
    Iif(requestOverride[moduleName]) {
      throw new Error('Module\'s request already overridden for ' + moduleName + ' protocol.');
    }
 
    //  Store the properties of the overridden request so that it can be restored later on.
    requestOverride[moduleName] = {
      module: module,
      request: overriddenRequest
    };
 
    module.request = function(options, callback) {
      // debug('request options:', options);
      return newRequest(proto, overriddenRequest.bind(module), options, callback);
    };
 
    debug('- overridden request for', proto);
  });
};
 
/**
 * Restores `request` function of `http` and `https` modules to values they
 * held before they were overridden by us.
 */
var restoreOverriddenRequests = function() {
  debug('restoring requests');
 
  //  Restore any overridden requests.
  _(requestOverride).keys().each(function(proto) {
    debug('- restoring request for', proto);
 
    var override = requestOverride[proto];
    Eif(override) {
      override.module.request = override.request;
      debug('- restored request for', proto);
    }
  }).value();
  requestOverride = [];
};
 
function stringifyRequest(options, body) {
  var method = options.method || 'GET';
 
  Iif (body && typeof(body) !== 'string') {
    body = body.toString();
  }
 
  var port = options.port;
  Iif (! port) port = (options.proto == 'https' ? '443' : '80');
 
  if (options.proto == 'https' && port == '443' ||
      options.proto == 'http' && port == '80') {
    port = '';
  }
 
  if (port) port = ':' + port;
 
  return method + ' ' + options.proto + '://' + options.hostname + port + options.path + ' ' + body;
}
 
function isContentEncoded(headers) {
  if(!headers) {
    return false;
  }
 
  var contentEncoding = headers['content-encoding'];
  return _.isString(contentEncoding) && contentEncoding !== '';
}
 
var headersFieldNamesToLowerCase = function(headers) {
  if(!_.isObject(headers)) {
    return headers;
  }
 
  //  For each key in the headers, delete its value and reinsert it with lower-case key.
  //  Keys represent headers field names.
  var lowerCaseHeaders = {};
  _.each(_.keys(headers), function(fieldName) {
    var lowerCaseFieldName = fieldName.toLowerCase();
    if(!_.isUndefined(lowerCaseHeaders[lowerCaseFieldName])) {
      throw new Error('Failed to convert header keys to lower case due to field name conflict: ' + lowerCaseFieldName);
    }
    lowerCaseHeaders[lowerCaseFieldName] = headers[fieldName];
  });
 
  return lowerCaseHeaders;
};
 
var headersFieldsArrayToLowerCase = function (headers) {
  return _.uniq(_.map(headers, function (fieldName) {
    return fieldName.toLowerCase();
  }));
};
 
/**
 * Deletes the given `fieldName` property from `headers` object by performing
 * case-insensitive search through keys.
 *
 * @headers   {Object} headers - object of header field names and values
 * @fieldName {String} field name - string with the case-insensitive field name
 */
var deleteHeadersField = function(headers, fieldNameToDelete) {
 
  Iif(!_.isObject(headers) || !_.isString(fieldNameToDelete)) {
    return;
  }
 
  var lowerCaseFieldNameToDelete = fieldNameToDelete.toLowerCase();
 
  //  Search through the headers and delete all values whose field name matches the given field name.
  _(headers).keys().each(function(fieldName) {
    var lowerCaseFieldName = fieldName.toLowerCase();
    if(lowerCaseFieldName === lowerCaseFieldNameToDelete) {
      delete headers[fieldName];
      //  We don't stop here but continue in order to remove *all* matching field names
      //  (even though if seen regorously there shouldn't be any)
    }
  }).value();
 
};
 
function percentEncode(str) {
  return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
    return '%' + c.charCodeAt(0).toString(16).toUpperCase();
  });
}
 
function matchStringOrRegexp(target, pattern) {
  if (pattern instanceof RegExp) {
    return target.toString().match(pattern);
  } else {
    return target === pattern;
  }
}
 
exports.normalizeRequestOptions = normalizeRequestOptions;
exports.isBinaryBuffer = isBinaryBuffer;
exports.mergeChunks = mergeChunks;
exports.overrideRequests = overrideRequests;
exports.restoreOverriddenRequests = restoreOverriddenRequests;
exports.stringifyRequest = stringifyRequest;
exports.isContentEncoded = isContentEncoded;
exports.headersFieldNamesToLowerCase = headersFieldNamesToLowerCase;
exports.headersFieldsArrayToLowerCase = headersFieldsArrayToLowerCase;
exports.deleteHeadersField = deleteHeadersField;
exports.percentEncode = percentEncode;
exports.matchStringOrRegexp = matchStringOrRegexp;