付智勇

加包

要显示太多修改。

为保证性能只显示 31 of 31+ 个文件。

  1 +{
  2 + "node": true,
  3 + "laxbreak": true
  4 +}
  1 +lib-cov
  2 +*.seed
  3 +*.log
  4 +*.csv
  5 +*.dat
  6 +*.out
  7 +*.pid
  8 +*.gz
  9 +
  10 +pids
  11 +logs
  12 +results
  13 +
  14 +npm-debug.log
  15 +node_modules
  16 +app.js
  17 +test.js
  1 +The MIT License (MIT)
  2 +
  3 +Copyright (c) 2013 Sam
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining a copy of
  6 +this software and associated documentation files (the "Software"), to deal in
  7 +the Software without restriction, including without limitation the rights to
  8 +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  9 +the Software, and to permit persons to whom the Software is furnished to do so,
  10 +subject to the following conditions:
  11 +
  12 +The above copyright notice and this permission notice shall be included in all
  13 +copies or substantial portions of the Software.
  14 +
  15 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  17 +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  18 +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  19 +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  20 +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  1 +# httpntlm
  2 +
  3 +__httpntlm__ is a Node.js library to do HTTP NTLM authentication
  4 +
  5 +It's a port from the Python libary [python-ntml](https://code.google.com/p/python-ntlm/)
  6 +
  7 +## Install
  8 +
  9 +You can install __httpntlm__ using the Node Package Manager (npm):
  10 +
  11 + npm install httpntlm
  12 +
  13 +## How to use
  14 +
  15 +```js
  16 +var httpntlm = require('httpntlm');
  17 +
  18 +httpntlm.get({
  19 + url: "https://someurl.com",
  20 + username: 'm$',
  21 + password: 'stinks',
  22 + workstation: 'choose.something',
  23 + domain: ''
  24 +}, function (err, res){
  25 + if(err) return err;
  26 +
  27 + console.log(res.headers);
  28 + console.log(res.body);
  29 +});
  30 +```
  31 +
  32 +It supports __http__ and __https__.
  33 +
  34 +## Options
  35 +
  36 +- `url:` _{String}_ URL to connect. (Required)
  37 +- `username:` _{String}_ Username. (Required)
  38 +- `password:` _{String}_ Password. (Required)
  39 +- `workstation:` _{String}_ Name of workstation or `''`.
  40 +- `domain:` _{String}_ Name of domain or `''`.
  41 +
  42 +You can also pass along all other options of [httpreq](https://github.com/SamDecrock/node-httpreq), including custom headers, cookies, body data, ... and use POST, PUT or DELETE instead of GET.
  43 +
  44 +## Advanced
  45 +
  46 +If you want to use the NTLM-functions yourself, you can access the ntlm-library like this (https example):
  47 +
  48 +```js
  49 +var ntlm = require('httpntlm').ntlm;
  50 +var async = require('async');
  51 +var httpreq = require('httpreq');
  52 +var HttpsAgent = require('agentkeepalive').HttpsAgent;
  53 +var keepaliveAgent = new HttpsAgent();
  54 +
  55 +var options = {
  56 + url: "https://someurl.com",
  57 + username: 'm$',
  58 + password: 'stinks',
  59 + workstation: 'choose.something',
  60 + domain: ''
  61 +};
  62 +
  63 +async.waterfall([
  64 + function (callback){
  65 + var type1msg = ntlm.createType1Message(options);
  66 +
  67 + httpreq.get(options.url, {
  68 + headers:{
  69 + 'Connection' : 'keep-alive',
  70 + 'Authorization': type1msg
  71 + },
  72 + agent: keepaliveAgent
  73 + }, callback);
  74 + },
  75 +
  76 + function (res, callback){
  77 + if(!res.headers['www-authenticate'])
  78 + return callback(new Error('www-authenticate not found on response of second request'));
  79 +
  80 + var type2msg = ntlm.parseType2Message(res.headers['www-authenticate']);
  81 + var type3msg = ntlm.createType3Message(type2msg, options);
  82 +
  83 + setImmediate(function() {
  84 + httpreq.get(options.url, {
  85 + headers:{
  86 + 'Connection' : 'Close',
  87 + 'Authorization': type3msg
  88 + },
  89 + allowRedirects: false,
  90 + agent: keepaliveAgent
  91 + }, callback);
  92 + });
  93 + }
  94 +], function (err, res) {
  95 + if(err) return console.log(err);
  96 +
  97 + console.log(res.headers);
  98 + console.log(res.body);
  99 +});
  100 +```
  101 +
  102 +## Download binary files
  103 +
  104 +```javascript
  105 +httpntlm.get({
  106 + url: "https://someurl.com/file.xls",
  107 + username: 'm$',
  108 + password: 'stinks',
  109 + workstation: 'choose.something',
  110 + domain: '',
  111 + binary: true
  112 +}, function (err, response) {
  113 + if(err) return console.log(err);
  114 + fs.writeFile("file.xls", response.body, function (err) {
  115 + if(err) return console.log("error writing file");
  116 + console.log("file.xls saved!");
  117 + });
  118 +});
  119 +```
  120 +
  121 +## More information
  122 +
  123 +* [python-ntlm](https://code.google.com/p/python-ntlm/)
  124 +* [NTLM Authentication Scheme for HTTP](http://www.innovation.ch/personal/ronald/ntlm.html)
  125 +* [LM hash on Wikipedia](http://en.wikipedia.org/wiki/LM_hash)
  126 +
  127 +
  128 +## License (MIT)
  129 +
  130 +Copyright (c) Sam Decrock <https://github.com/SamDecrock/>
  131 +
  132 +Permission is hereby granted, free of charge, to any person obtaining a copy
  133 +of this software and associated documentation files (the "Software"), to deal
  134 +in the Software without restriction, including without limitation the rights
  135 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  136 +copies of the Software, and to permit persons to whom the Software is
  137 +furnished to do so, subject to the following conditions:
  138 +
  139 +The above copyright notice and this permission notice shall be included in
  140 +all copies or substantial portions of the Software.
  141 +
  142 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  143 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  144 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  145 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  146 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  147 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  148 +THE SOFTWARE.
  1 +'use strict';
  2 +
  3 +var url = require('url');
  4 +var httpreq = require('httpreq');
  5 +var ntlm = require('./ntlm');
  6 +var _ = require('underscore');
  7 +var http = require('http');
  8 +var https = require('https');
  9 +
  10 +exports.method = function(method, options, finalCallback){
  11 + if(!options.workstation) options.workstation = '';
  12 + if(!options.domain) options.domain = '';
  13 +
  14 + // extract non-ntlm-options:
  15 + var httpreqOptions = _.omit(options, 'url', 'username', 'password', 'workstation', 'domain');
  16 +
  17 + // is https?
  18 + var isHttps = false;
  19 + var reqUrl = url.parse(options.url);
  20 + if(reqUrl.protocol == 'https:') isHttps = true;
  21 +
  22 + // set keepaliveAgent (http or https):
  23 + var keepaliveAgent;
  24 +
  25 + if(isHttps){
  26 + keepaliveAgent = new https.Agent({keepAlive: true});
  27 + }else{
  28 + keepaliveAgent = new http.Agent({keepAlive: true});
  29 + }
  30 +
  31 + // build type1 request:
  32 +
  33 + function sendType1Message (callback) {
  34 + var type1msg = ntlm.createType1Message(options);
  35 +
  36 + var type1options = {
  37 + headers:{
  38 + 'Connection' : 'keep-alive',
  39 + 'Authorization': type1msg
  40 + },
  41 + timeout: options.timeout || 0,
  42 + agent: keepaliveAgent,
  43 + allowRedirects: false // don't redirect in httpreq, because http could change to https which means we need to change the keepaliveAgent
  44 + };
  45 +
  46 + // pass along timeout and ca:
  47 + if(httpreqOptions.timeout) type1options.timeout = httpreqOptions.timeout;
  48 + if(httpreqOptions.ca) type1options.ca = httpreqOptions.ca;
  49 +
  50 + // send type1 message to server:
  51 + httpreq.get(options.url, type1options, callback);
  52 + }
  53 +
  54 + function sendType3Message (res, callback) {
  55 + // catch redirect here:
  56 + if(res.headers.location) {
  57 + options.url = res.headers.location;
  58 + return exports[method](options, finalCallback);
  59 + }
  60 +
  61 +
  62 + if(!res.headers['www-authenticate'])
  63 + return callback(new Error('www-authenticate not found on response of second request'));
  64 +
  65 + // parse type2 message from server:
  66 + var type2msg = ntlm.parseType2Message(res.headers['www-authenticate']);
  67 +
  68 + // create type3 message:
  69 + var type3msg = ntlm.createType3Message(type2msg, options);
  70 +
  71 + // build type3 request:
  72 + var type3options = {
  73 + headers: {
  74 + 'Connection': 'Close',
  75 + 'Authorization': type3msg
  76 + },
  77 + allowRedirects: false,
  78 + agent: keepaliveAgent
  79 + };
  80 +
  81 + // pass along other options:
  82 + type3options.headers = _.extend(type3options.headers, httpreqOptions.headers);
  83 + type3options = _.extend(type3options, _.omit(httpreqOptions, 'headers'));
  84 +
  85 + // send type3 message to server:
  86 + httpreq[method](options.url, type3options, callback);
  87 + }
  88 +
  89 +
  90 + sendType1Message(function (err, res) {
  91 + if(err) return finalCallback(err);
  92 + setImmediate(function () { // doesn't work without setImmediate()
  93 + sendType3Message(res, finalCallback);
  94 + });
  95 + });
  96 +
  97 +};
  98 +
  99 +['get', 'put', 'patch', 'post', 'delete', 'options'].forEach(function(method){
  100 + exports[method] = exports.method.bind(exports, method);
  101 +});
  102 +
  103 +exports.ntlm = ntlm; //if you want to use the NTML functions yourself
  104 +
  1 +Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative
  2 +Reporters & Editors
  3 +
  4 +Permission is hereby granted, free of charge, to any person
  5 +obtaining a copy of this software and associated documentation
  6 +files (the "Software"), to deal in the Software without
  7 +restriction, including without limitation the rights to use,
  8 +copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 +copies of the Software, and to permit persons to whom the
  10 +Software is furnished to do so, subject to the following
  11 +conditions:
  12 +
  13 +The above copyright notice and this permission notice shall be
  14 +included in all copies or substantial portions of the Software.
  15 +
  16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  18 +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  20 +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  21 +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22 +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  23 +OTHER DEALINGS IN THE SOFTWARE.
  1 + __
  2 + /\ \ __
  3 + __ __ ___ \_\ \ __ _ __ ____ ___ ___ _ __ __ /\_\ ____
  4 + /\ \/\ \ /' _ `\ /'_ \ /'__`\/\ __\/ ,__\ / ___\ / __`\/\ __\/'__`\ \/\ \ /',__\
  5 + \ \ \_\ \/\ \/\ \/\ \ \ \/\ __/\ \ \//\__, `\/\ \__//\ \ \ \ \ \//\ __/ __ \ \ \/\__, `\
  6 + \ \____/\ \_\ \_\ \___,_\ \____\\ \_\\/\____/\ \____\ \____/\ \_\\ \____\/\_\ _\ \ \/\____/
  7 + \/___/ \/_/\/_/\/__,_ /\/____/ \/_/ \/___/ \/____/\/___/ \/_/ \/____/\/_//\ \_\ \/___/
  8 + \ \____/
  9 + \/___/
  10 +
  11 +Underscore.js is a utility-belt library for JavaScript that provides
  12 +support for the usual functional suspects (each, map, reduce, filter...)
  13 +without extending any core JavaScript objects.
  14 +
  15 +For Docs, License, Tests, and pre-packed downloads, see:
  16 +http://underscorejs.org
  17 +
  18 +Underscore is an open-sourced component of DocumentCloud:
  19 +https://github.com/documentcloud
  20 +
  21 +Many thanks to our contributors:
  22 +https://github.com/jashkenas/underscore/contributors
  1 +{
  2 + "_args": [
  3 + [
  4 + {
  5 + "raw": "underscore@~1.7.0",
  6 + "scope": null,
  7 + "escapedName": "underscore",
  8 + "name": "underscore",
  9 + "rawSpec": "~1.7.0",
  10 + "spec": ">=1.7.0 <1.8.0",
  11 + "type": "range"
  12 + },
  13 + "/Users/fzy/project/koa2_Sequelize_project/node_modules/httpntlm"
  14 + ]
  15 + ],
  16 + "_from": "underscore@>=1.7.0 <1.8.0",
  17 + "_id": "underscore@1.7.0",
  18 + "_inCache": true,
  19 + "_location": "/httpntlm/underscore",
  20 + "_npmUser": {
  21 + "name": "jashkenas",
  22 + "email": "jashkenas@gmail.com"
  23 + },
  24 + "_npmVersion": "1.4.24",
  25 + "_phantomChildren": {},
  26 + "_requested": {
  27 + "raw": "underscore@~1.7.0",
  28 + "scope": null,
  29 + "escapedName": "underscore",
  30 + "name": "underscore",
  31 + "rawSpec": "~1.7.0",
  32 + "spec": ">=1.7.0 <1.8.0",
  33 + "type": "range"
  34 + },
  35 + "_requiredBy": [
  36 + "/httpntlm"
  37 + ],
  38 + "_resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
  39 + "_shasum": "6bbaf0877500d36be34ecaa584e0db9fef035209",
  40 + "_shrinkwrap": null,
  41 + "_spec": "underscore@~1.7.0",
  42 + "_where": "/Users/fzy/project/koa2_Sequelize_project/node_modules/httpntlm",
  43 + "author": {
  44 + "name": "Jeremy Ashkenas",
  45 + "email": "jeremy@documentcloud.org"
  46 + },
  47 + "bugs": {
  48 + "url": "https://github.com/jashkenas/underscore/issues"
  49 + },
  50 + "dependencies": {},
  51 + "description": "JavaScript's functional programming helper library.",
  52 + "devDependencies": {
  53 + "docco": "0.6.x",
  54 + "eslint": "0.6.x",
  55 + "phantomjs": "1.9.7-1",
  56 + "uglify-js": "2.4.x"
  57 + },
  58 + "directories": {},
  59 + "dist": {
  60 + "shasum": "6bbaf0877500d36be34ecaa584e0db9fef035209",
  61 + "tarball": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz"
  62 + },
  63 + "files": [
  64 + "underscore.js",
  65 + "underscore-min.js",
  66 + "LICENSE"
  67 + ],
  68 + "gitHead": "da996e665deb0b69b257e80e3e257c04fde4191c",
  69 + "homepage": "http://underscorejs.org",
  70 + "keywords": [
  71 + "util",
  72 + "functional",
  73 + "server",
  74 + "client",
  75 + "browser"
  76 + ],
  77 + "licenses": [
  78 + {
  79 + "type": "MIT",
  80 + "url": "https://raw.github.com/jashkenas/underscore/master/LICENSE"
  81 + }
  82 + ],
  83 + "main": "underscore.js",
  84 + "maintainers": [
  85 + {
  86 + "name": "jashkenas",
  87 + "email": "jashkenas@gmail.com"
  88 + }
  89 + ],
  90 + "name": "underscore",
  91 + "optionalDependencies": {},
  92 + "readme": " __\n /\\ \\ __\n __ __ ___ \\_\\ \\ __ _ __ ____ ___ ___ _ __ __ /\\_\\ ____\n /\\ \\/\\ \\ /' _ `\\ /'_ \\ /'__`\\/\\ __\\/ ,__\\ / ___\\ / __`\\/\\ __\\/'__`\\ \\/\\ \\ /',__\\\n \\ \\ \\_\\ \\/\\ \\/\\ \\/\\ \\ \\ \\/\\ __/\\ \\ \\//\\__, `\\/\\ \\__//\\ \\ \\ \\ \\ \\//\\ __/ __ \\ \\ \\/\\__, `\\\n \\ \\____/\\ \\_\\ \\_\\ \\___,_\\ \\____\\\\ \\_\\\\/\\____/\\ \\____\\ \\____/\\ \\_\\\\ \\____\\/\\_\\ _\\ \\ \\/\\____/\n \\/___/ \\/_/\\/_/\\/__,_ /\\/____/ \\/_/ \\/___/ \\/____/\\/___/ \\/_/ \\/____/\\/_//\\ \\_\\ \\/___/\n \\ \\____/\n \\/___/\n\nUnderscore.js is a utility-belt library for JavaScript that provides\nsupport for the usual functional suspects (each, map, reduce, filter...)\nwithout extending any core JavaScript objects.\n\nFor Docs, License, Tests, and pre-packed downloads, see:\nhttp://underscorejs.org\n\nUnderscore is an open-sourced component of DocumentCloud:\nhttps://github.com/documentcloud\n\nMany thanks to our contributors:\nhttps://github.com/jashkenas/underscore/contributors\n",
  93 + "readmeFilename": "README.md",
  94 + "repository": {
  95 + "type": "git",
  96 + "url": "git://github.com/jashkenas/underscore.git"
  97 + },
  98 + "scripts": {
  99 + "build": "uglifyjs underscore.js -c \"evaluate=false\" --comments \"/ .*/\" -m --source-map underscore-min.map -o underscore-min.js",
  100 + "doc": "docco underscore.js",
  101 + "test": "phantomjs test/vendor/runner.js test/index.html?noglobals=true && eslint underscore.js test/*.js test/vendor/runner.js"
  102 + },
  103 + "version": "1.7.0"
  104 +}
  1 +// Underscore.js 1.7.0
  2 +// http://underscorejs.org
  3 +// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  4 +// Underscore may be freely distributed under the MIT license.
  5 +(function(){var n=this,t=n._,r=Array.prototype,e=Object.prototype,u=Function.prototype,i=r.push,a=r.slice,o=r.concat,l=e.toString,c=e.hasOwnProperty,f=Array.isArray,s=Object.keys,p=u.bind,h=function(n){return n instanceof h?n:this instanceof h?void(this._wrapped=n):new h(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=h),exports._=h):n._=h,h.VERSION="1.7.0";var g=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}};h.iteratee=function(n,t,r){return null==n?h.identity:h.isFunction(n)?g(n,t,r):h.isObject(n)?h.matches(n):h.property(n)},h.each=h.forEach=function(n,t,r){if(null==n)return n;t=g(t,r);var e,u=n.length;if(u===+u)for(e=0;u>e;e++)t(n[e],e,n);else{var i=h.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},h.map=h.collect=function(n,t,r){if(null==n)return[];t=h.iteratee(t,r);for(var e,u=n.length!==+n.length&&h.keys(n),i=(u||n).length,a=Array(i),o=0;i>o;o++)e=u?u[o]:o,a[o]=t(n[e],e,n);return a};var v="Reduce of empty array with no initial value";h.reduce=h.foldl=h.inject=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length,o=0;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[o++]:o++]}for(;a>o;o++)u=i?i[o]:o,r=t(r,n[u],u,n);return r},h.reduceRight=h.foldr=function(n,t,r,e){null==n&&(n=[]),t=g(t,e,4);var u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;if(arguments.length<3){if(!a)throw new TypeError(v);r=n[i?i[--a]:--a]}for(;a--;)u=i?i[a]:a,r=t(r,n[u],u,n);return r},h.find=h.detect=function(n,t,r){var e;return t=h.iteratee(t,r),h.some(n,function(n,r,u){return t(n,r,u)?(e=n,!0):void 0}),e},h.filter=h.select=function(n,t,r){var e=[];return null==n?e:(t=h.iteratee(t,r),h.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e)},h.reject=function(n,t,r){return h.filter(n,h.negate(h.iteratee(t)),r)},h.every=h.all=function(n,t,r){if(null==n)return!0;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,!t(n[u],u,n))return!1;return!0},h.some=h.any=function(n,t,r){if(null==n)return!1;t=h.iteratee(t,r);var e,u,i=n.length!==+n.length&&h.keys(n),a=(i||n).length;for(e=0;a>e;e++)if(u=i?i[e]:e,t(n[u],u,n))return!0;return!1},h.contains=h.include=function(n,t){return null==n?!1:(n.length!==+n.length&&(n=h.values(n)),h.indexOf(n,t)>=0)},h.invoke=function(n,t){var r=a.call(arguments,2),e=h.isFunction(t);return h.map(n,function(n){return(e?t:n[t]).apply(n,r)})},h.pluck=function(n,t){return h.map(n,h.property(t))},h.where=function(n,t){return h.filter(n,h.matches(t))},h.findWhere=function(n,t){return h.find(n,h.matches(t))},h.max=function(n,t,r){var e,u,i=-1/0,a=-1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],e>i&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(u>a||u===-1/0&&i===-1/0)&&(i=n,a=u)});return i},h.min=function(n,t,r){var e,u,i=1/0,a=1/0;if(null==t&&null!=n){n=n.length===+n.length?n:h.values(n);for(var o=0,l=n.length;l>o;o++)e=n[o],i>e&&(i=e)}else t=h.iteratee(t,r),h.each(n,function(n,r,e){u=t(n,r,e),(a>u||1/0===u&&1/0===i)&&(i=n,a=u)});return i},h.shuffle=function(n){for(var t,r=n&&n.length===+n.length?n:h.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=h.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},h.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=h.values(n)),n[h.random(n.length-1)]):h.shuffle(n).slice(0,Math.max(0,t))},h.sortBy=function(n,t,r){return t=h.iteratee(t,r),h.pluck(h.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var m=function(n){return function(t,r,e){var u={};return r=h.iteratee(r,e),h.each(t,function(e,i){var a=r(e,i,t);n(u,e,a)}),u}};h.groupBy=m(function(n,t,r){h.has(n,r)?n[r].push(t):n[r]=[t]}),h.indexBy=m(function(n,t,r){n[r]=t}),h.countBy=m(function(n,t,r){h.has(n,r)?n[r]++:n[r]=1}),h.sortedIndex=function(n,t,r,e){r=h.iteratee(r,e,1);for(var u=r(t),i=0,a=n.length;a>i;){var o=i+a>>>1;r(n[o])<u?i=o+1:a=o}return i},h.toArray=function(n){return n?h.isArray(n)?a.call(n):n.length===+n.length?h.map(n,h.identity):h.values(n):[]},h.size=function(n){return null==n?0:n.length===+n.length?n.length:h.keys(n).length},h.partition=function(n,t,r){t=h.iteratee(t,r);var e=[],u=[];return h.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},h.first=h.head=h.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:0>t?[]:a.call(n,0,t)},h.initial=function(n,t,r){return a.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},h.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:a.call(n,Math.max(n.length-t,0))},h.rest=h.tail=h.drop=function(n,t,r){return a.call(n,null==t||r?1:t)},h.compact=function(n){return h.filter(n,h.identity)};var y=function(n,t,r,e){if(t&&h.every(n,h.isArray))return o.apply(e,n);for(var u=0,a=n.length;a>u;u++){var l=n[u];h.isArray(l)||h.isArguments(l)?t?i.apply(e,l):y(l,t,r,e):r||e.push(l)}return e};h.flatten=function(n,t){return y(n,t,!1,[])},h.without=function(n){return h.difference(n,a.call(arguments,1))},h.uniq=h.unique=function(n,t,r,e){if(null==n)return[];h.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=h.iteratee(r,e));for(var u=[],i=[],a=0,o=n.length;o>a;a++){var l=n[a];if(t)a&&i===l||u.push(l),i=l;else if(r){var c=r(l,a,n);h.indexOf(i,c)<0&&(i.push(c),u.push(l))}else h.indexOf(u,l)<0&&u.push(l)}return u},h.union=function(){return h.uniq(y(arguments,!0,!0,[]))},h.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!h.contains(t,i)){for(var a=1;r>a&&h.contains(arguments[a],i);a++);a===r&&t.push(i)}}return t},h.difference=function(n){var t=y(a.call(arguments,1),!0,!0,[]);return h.filter(n,function(n){return!h.contains(t,n)})},h.zip=function(n){if(null==n)return[];for(var t=h.max(arguments,"length").length,r=Array(t),e=0;t>e;e++)r[e]=h.pluck(arguments,e);return r},h.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},h.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=h.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}for(;u>e;e++)if(n[e]===t)return e;return-1},h.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=n.length;for("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1));--e>=0;)if(n[e]===t)return e;return-1},h.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var d=function(){};h.bind=function(n,t){var r,e;if(p&&n.bind===p)return p.apply(n,a.call(arguments,1));if(!h.isFunction(n))throw new TypeError("Bind must be called on a function");return r=a.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(a.call(arguments)));d.prototype=n.prototype;var u=new d;d.prototype=null;var i=n.apply(u,r.concat(a.call(arguments)));return h.isObject(i)?i:u}},h.partial=function(n){var t=a.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===h&&(e[u]=arguments[r++]);for(;r<arguments.length;)e.push(arguments[r++]);return n.apply(this,e)}},h.bindAll=function(n){var t,r,e=arguments.length;if(1>=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=h.bind(n[r],n);return n},h.memoize=function(n,t){var r=function(e){var u=r.cache,i=t?t.apply(this,arguments):e;return h.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},h.delay=function(n,t){var r=a.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},h.defer=function(n){return h.delay.apply(h,[n,1].concat(a.call(arguments,1)))},h.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var l=function(){o=r.leading===!1?0:h.now(),a=null,i=n.apply(e,u),a||(e=u=null)};return function(){var c=h.now();o||r.leading!==!1||(o=c);var f=t-(c-o);return e=this,u=arguments,0>=f||f>t?(clearTimeout(a),a=null,o=c,i=n.apply(e,u),a||(e=u=null)):a||r.trailing===!1||(a=setTimeout(l,f)),i}},h.debounce=function(n,t,r){var e,u,i,a,o,l=function(){var c=h.now()-a;t>c&&c>0?e=setTimeout(l,t-c):(e=null,r||(o=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,a=h.now();var c=r&&!e;return e||(e=setTimeout(l,t)),c&&(o=n.apply(i,u),i=u=null),o}},h.wrap=function(n,t){return h.partial(t,n)},h.negate=function(n){return function(){return!n.apply(this,arguments)}},h.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},h.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},h.before=function(n,t){var r;return function(){return--n>0?r=t.apply(this,arguments):t=null,r}},h.once=h.partial(h.before,2),h.keys=function(n){if(!h.isObject(n))return[];if(s)return s(n);var t=[];for(var r in n)h.has(n,r)&&t.push(r);return t},h.values=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},h.pairs=function(n){for(var t=h.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},h.invert=function(n){for(var t={},r=h.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},h.functions=h.methods=function(n){var t=[];for(var r in n)h.isFunction(n[r])&&t.push(r);return t.sort()},h.extend=function(n){if(!h.isObject(n))return n;for(var t,r,e=1,u=arguments.length;u>e;e++){t=arguments[e];for(r in t)c.call(t,r)&&(n[r]=t[r])}return n},h.pick=function(n,t,r){var e,u={};if(null==n)return u;if(h.isFunction(t)){t=g(t,r);for(e in n){var i=n[e];t(i,e,n)&&(u[e]=i)}}else{var l=o.apply([],a.call(arguments,1));n=new Object(n);for(var c=0,f=l.length;f>c;c++)e=l[c],e in n&&(u[e]=n[e])}return u},h.omit=function(n,t,r){if(h.isFunction(t))t=h.negate(t);else{var e=h.map(o.apply([],a.call(arguments,1)),String);t=function(n,t){return!h.contains(e,t)}}return h.pick(n,t,r)},h.defaults=function(n){if(!h.isObject(n))return n;for(var t=1,r=arguments.length;r>t;t++){var e=arguments[t];for(var u in e)n[u]===void 0&&(n[u]=e[u])}return n},h.clone=function(n){return h.isObject(n)?h.isArray(n)?n.slice():h.extend({},n):n},h.tap=function(n,t){return t(n),n};var b=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof h&&(n=n._wrapped),t instanceof h&&(t=t._wrapped);var u=l.call(n);if(u!==l.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]===n)return e[i]===t;var a=n.constructor,o=t.constructor;if(a!==o&&"constructor"in n&&"constructor"in t&&!(h.isFunction(a)&&a instanceof a&&h.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c,f;if("[object Array]"===u){if(c=n.length,f=c===t.length)for(;c--&&(f=b(n[c],t[c],r,e)););}else{var s,p=h.keys(n);if(c=p.length,f=h.keys(t).length===c)for(;c--&&(s=p[c],f=h.has(t,s)&&b(n[s],t[s],r,e)););}return r.pop(),e.pop(),f};h.isEqual=function(n,t){return b(n,t,[],[])},h.isEmpty=function(n){if(null==n)return!0;if(h.isArray(n)||h.isString(n)||h.isArguments(n))return 0===n.length;for(var t in n)if(h.has(n,t))return!1;return!0},h.isElement=function(n){return!(!n||1!==n.nodeType)},h.isArray=f||function(n){return"[object Array]"===l.call(n)},h.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},h.each(["Arguments","Function","String","Number","Date","RegExp"],function(n){h["is"+n]=function(t){return l.call(t)==="[object "+n+"]"}}),h.isArguments(arguments)||(h.isArguments=function(n){return h.has(n,"callee")}),"function"!=typeof/./&&(h.isFunction=function(n){return"function"==typeof n||!1}),h.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},h.isNaN=function(n){return h.isNumber(n)&&n!==+n},h.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===l.call(n)},h.isNull=function(n){return null===n},h.isUndefined=function(n){return n===void 0},h.has=function(n,t){return null!=n&&c.call(n,t)},h.noConflict=function(){return n._=t,this},h.identity=function(n){return n},h.constant=function(n){return function(){return n}},h.noop=function(){},h.property=function(n){return function(t){return t[n]}},h.matches=function(n){var t=h.pairs(n),r=t.length;return function(n){if(null==n)return!r;n=new Object(n);for(var e=0;r>e;e++){var u=t[e],i=u[0];if(u[1]!==n[i]||!(i in n))return!1}return!0}},h.times=function(n,t,r){var e=Array(Math.max(0,n));t=g(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},h.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},h.now=Date.now||function(){return(new Date).getTime()};var _={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},w=h.invert(_),j=function(n){var t=function(t){return n[t]},r="(?:"+h.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};h.escape=j(_),h.unescape=j(w),h.result=function(n,t){if(null==n)return void 0;var r=n[t];return h.isFunction(r)?n[t]():r};var x=0;h.uniqueId=function(n){var t=++x+"";return n?n+t:t},h.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var A=/(.)^/,k={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},O=/\\|'|\r|\n|\u2028|\u2029/g,F=function(n){return"\\"+k[n]};h.template=function(n,t,r){!t&&r&&(t=r),t=h.defaults({},t,h.templateSettings);var e=RegExp([(t.escape||A).source,(t.interpolate||A).source,(t.evaluate||A).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(O,F),u=o+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":a&&(i+="';\n"+a+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=new Function(t.variable||"obj","_",i)}catch(o){throw o.source=i,o}var l=function(n){return a.call(this,n,h)},c=t.variable||"obj";return l.source="function("+c+"){\n"+i+"}",l},h.chain=function(n){var t=h(n);return t._chain=!0,t};var E=function(n){return this._chain?h(n).chain():n};h.mixin=function(n){h.each(h.functions(n),function(t){var r=h[t]=n[t];h.prototype[t]=function(){var n=[this._wrapped];return i.apply(n,arguments),E.call(this,r.apply(h,n))}})},h.mixin(h),h.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=r[n];h.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],E.call(this,r)}}),h.each(["concat","join","slice"],function(n){var t=r[n];h.prototype[n]=function(){return E.call(this,t.apply(this._wrapped,arguments))}}),h.prototype.value=function(){return this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return h})}).call(this);
  6 +//# sourceMappingURL=underscore-min.map
  1 +// Underscore.js 1.7.0
  2 +// http://underscorejs.org
  3 +// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  4 +// Underscore may be freely distributed under the MIT license.
  5 +
  6 +(function() {
  7 +
  8 + // Baseline setup
  9 + // --------------
  10 +
  11 + // Establish the root object, `window` in the browser, or `exports` on the server.
  12 + var root = this;
  13 +
  14 + // Save the previous value of the `_` variable.
  15 + var previousUnderscore = root._;
  16 +
  17 + // Save bytes in the minified (but not gzipped) version:
  18 + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
  19 +
  20 + // Create quick reference variables for speed access to core prototypes.
  21 + var
  22 + push = ArrayProto.push,
  23 + slice = ArrayProto.slice,
  24 + concat = ArrayProto.concat,
  25 + toString = ObjProto.toString,
  26 + hasOwnProperty = ObjProto.hasOwnProperty;
  27 +
  28 + // All **ECMAScript 5** native function implementations that we hope to use
  29 + // are declared here.
  30 + var
  31 + nativeIsArray = Array.isArray,
  32 + nativeKeys = Object.keys,
  33 + nativeBind = FuncProto.bind;
  34 +
  35 + // Create a safe reference to the Underscore object for use below.
  36 + var _ = function(obj) {
  37 + if (obj instanceof _) return obj;
  38 + if (!(this instanceof _)) return new _(obj);
  39 + this._wrapped = obj;
  40 + };
  41 +
  42 + // Export the Underscore object for **Node.js**, with
  43 + // backwards-compatibility for the old `require()` API. If we're in
  44 + // the browser, add `_` as a global object.
  45 + if (typeof exports !== 'undefined') {
  46 + if (typeof module !== 'undefined' && module.exports) {
  47 + exports = module.exports = _;
  48 + }
  49 + exports._ = _;
  50 + } else {
  51 + root._ = _;
  52 + }
  53 +
  54 + // Current version.
  55 + _.VERSION = '1.7.0';
  56 +
  57 + // Internal function that returns an efficient (for current engines) version
  58 + // of the passed-in callback, to be repeatedly applied in other Underscore
  59 + // functions.
  60 + var createCallback = function(func, context, argCount) {
  61 + if (context === void 0) return func;
  62 + switch (argCount == null ? 3 : argCount) {
  63 + case 1: return function(value) {
  64 + return func.call(context, value);
  65 + };
  66 + case 2: return function(value, other) {
  67 + return func.call(context, value, other);
  68 + };
  69 + case 3: return function(value, index, collection) {
  70 + return func.call(context, value, index, collection);
  71 + };
  72 + case 4: return function(accumulator, value, index, collection) {
  73 + return func.call(context, accumulator, value, index, collection);
  74 + };
  75 + }
  76 + return function() {
  77 + return func.apply(context, arguments);
  78 + };
  79 + };
  80 +
  81 + // A mostly-internal function to generate callbacks that can be applied
  82 + // to each element in a collection, returning the desired result — either
  83 + // identity, an arbitrary callback, a property matcher, or a property accessor.
  84 + _.iteratee = function(value, context, argCount) {
  85 + if (value == null) return _.identity;
  86 + if (_.isFunction(value)) return createCallback(value, context, argCount);
  87 + if (_.isObject(value)) return _.matches(value);
  88 + return _.property(value);
  89 + };
  90 +
  91 + // Collection Functions
  92 + // --------------------
  93 +
  94 + // The cornerstone, an `each` implementation, aka `forEach`.
  95 + // Handles raw objects in addition to array-likes. Treats all
  96 + // sparse array-likes as if they were dense.
  97 + _.each = _.forEach = function(obj, iteratee, context) {
  98 + if (obj == null) return obj;
  99 + iteratee = createCallback(iteratee, context);
  100 + var i, length = obj.length;
  101 + if (length === +length) {
  102 + for (i = 0; i < length; i++) {
  103 + iteratee(obj[i], i, obj);
  104 + }
  105 + } else {
  106 + var keys = _.keys(obj);
  107 + for (i = 0, length = keys.length; i < length; i++) {
  108 + iteratee(obj[keys[i]], keys[i], obj);
  109 + }
  110 + }
  111 + return obj;
  112 + };
  113 +
  114 + // Return the results of applying the iteratee to each element.
  115 + _.map = _.collect = function(obj, iteratee, context) {
  116 + if (obj == null) return [];
  117 + iteratee = _.iteratee(iteratee, context);
  118 + var keys = obj.length !== +obj.length && _.keys(obj),
  119 + length = (keys || obj).length,
  120 + results = Array(length),
  121 + currentKey;
  122 + for (var index = 0; index < length; index++) {
  123 + currentKey = keys ? keys[index] : index;
  124 + results[index] = iteratee(obj[currentKey], currentKey, obj);
  125 + }
  126 + return results;
  127 + };
  128 +
  129 + var reduceError = 'Reduce of empty array with no initial value';
  130 +
  131 + // **Reduce** builds up a single result from a list of values, aka `inject`,
  132 + // or `foldl`.
  133 + _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) {
  134 + if (obj == null) obj = [];
  135 + iteratee = createCallback(iteratee, context, 4);
  136 + var keys = obj.length !== +obj.length && _.keys(obj),
  137 + length = (keys || obj).length,
  138 + index = 0, currentKey;
  139 + if (arguments.length < 3) {
  140 + if (!length) throw new TypeError(reduceError);
  141 + memo = obj[keys ? keys[index++] : index++];
  142 + }
  143 + for (; index < length; index++) {
  144 + currentKey = keys ? keys[index] : index;
  145 + memo = iteratee(memo, obj[currentKey], currentKey, obj);
  146 + }
  147 + return memo;
  148 + };
  149 +
  150 + // The right-associative version of reduce, also known as `foldr`.
  151 + _.reduceRight = _.foldr = function(obj, iteratee, memo, context) {
  152 + if (obj == null) obj = [];
  153 + iteratee = createCallback(iteratee, context, 4);
  154 + var keys = obj.length !== + obj.length && _.keys(obj),
  155 + index = (keys || obj).length,
  156 + currentKey;
  157 + if (arguments.length < 3) {
  158 + if (!index) throw new TypeError(reduceError);
  159 + memo = obj[keys ? keys[--index] : --index];
  160 + }
  161 + while (index--) {
  162 + currentKey = keys ? keys[index] : index;
  163 + memo = iteratee(memo, obj[currentKey], currentKey, obj);
  164 + }
  165 + return memo;
  166 + };
  167 +
  168 + // Return the first value which passes a truth test. Aliased as `detect`.
  169 + _.find = _.detect = function(obj, predicate, context) {
  170 + var result;
  171 + predicate = _.iteratee(predicate, context);
  172 + _.some(obj, function(value, index, list) {
  173 + if (predicate(value, index, list)) {
  174 + result = value;
  175 + return true;
  176 + }
  177 + });
  178 + return result;
  179 + };
  180 +
  181 + // Return all the elements that pass a truth test.
  182 + // Aliased as `select`.
  183 + _.filter = _.select = function(obj, predicate, context) {
  184 + var results = [];
  185 + if (obj == null) return results;
  186 + predicate = _.iteratee(predicate, context);
  187 + _.each(obj, function(value, index, list) {
  188 + if (predicate(value, index, list)) results.push(value);
  189 + });
  190 + return results;
  191 + };
  192 +
  193 + // Return all the elements for which a truth test fails.
  194 + _.reject = function(obj, predicate, context) {
  195 + return _.filter(obj, _.negate(_.iteratee(predicate)), context);
  196 + };
  197 +
  198 + // Determine whether all of the elements match a truth test.
  199 + // Aliased as `all`.
  200 + _.every = _.all = function(obj, predicate, context) {
  201 + if (obj == null) return true;
  202 + predicate = _.iteratee(predicate, context);
  203 + var keys = obj.length !== +obj.length && _.keys(obj),
  204 + length = (keys || obj).length,
  205 + index, currentKey;
  206 + for (index = 0; index < length; index++) {
  207 + currentKey = keys ? keys[index] : index;
  208 + if (!predicate(obj[currentKey], currentKey, obj)) return false;
  209 + }
  210 + return true;
  211 + };
  212 +
  213 + // Determine if at least one element in the object matches a truth test.
  214 + // Aliased as `any`.
  215 + _.some = _.any = function(obj, predicate, context) {
  216 + if (obj == null) return false;
  217 + predicate = _.iteratee(predicate, context);
  218 + var keys = obj.length !== +obj.length && _.keys(obj),
  219 + length = (keys || obj).length,
  220 + index, currentKey;
  221 + for (index = 0; index < length; index++) {
  222 + currentKey = keys ? keys[index] : index;
  223 + if (predicate(obj[currentKey], currentKey, obj)) return true;
  224 + }
  225 + return false;
  226 + };
  227 +
  228 + // Determine if the array or object contains a given value (using `===`).
  229 + // Aliased as `include`.
  230 + _.contains = _.include = function(obj, target) {
  231 + if (obj == null) return false;
  232 + if (obj.length !== +obj.length) obj = _.values(obj);
  233 + return _.indexOf(obj, target) >= 0;
  234 + };
  235 +
  236 + // Invoke a method (with arguments) on every item in a collection.
  237 + _.invoke = function(obj, method) {
  238 + var args = slice.call(arguments, 2);
  239 + var isFunc = _.isFunction(method);
  240 + return _.map(obj, function(value) {
  241 + return (isFunc ? method : value[method]).apply(value, args);
  242 + });
  243 + };
  244 +
  245 + // Convenience version of a common use case of `map`: fetching a property.
  246 + _.pluck = function(obj, key) {
  247 + return _.map(obj, _.property(key));
  248 + };
  249 +
  250 + // Convenience version of a common use case of `filter`: selecting only objects
  251 + // containing specific `key:value` pairs.
  252 + _.where = function(obj, attrs) {
  253 + return _.filter(obj, _.matches(attrs));
  254 + };
  255 +
  256 + // Convenience version of a common use case of `find`: getting the first object
  257 + // containing specific `key:value` pairs.
  258 + _.findWhere = function(obj, attrs) {
  259 + return _.find(obj, _.matches(attrs));
  260 + };
  261 +
  262 + // Return the maximum element (or element-based computation).
  263 + _.max = function(obj, iteratee, context) {
  264 + var result = -Infinity, lastComputed = -Infinity,
  265 + value, computed;
  266 + if (iteratee == null && obj != null) {
  267 + obj = obj.length === +obj.length ? obj : _.values(obj);
  268 + for (var i = 0, length = obj.length; i < length; i++) {
  269 + value = obj[i];
  270 + if (value > result) {
  271 + result = value;
  272 + }
  273 + }
  274 + } else {
  275 + iteratee = _.iteratee(iteratee, context);
  276 + _.each(obj, function(value, index, list) {
  277 + computed = iteratee(value, index, list);
  278 + if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
  279 + result = value;
  280 + lastComputed = computed;
  281 + }
  282 + });
  283 + }
  284 + return result;
  285 + };
  286 +
  287 + // Return the minimum element (or element-based computation).
  288 + _.min = function(obj, iteratee, context) {
  289 + var result = Infinity, lastComputed = Infinity,
  290 + value, computed;
  291 + if (iteratee == null && obj != null) {
  292 + obj = obj.length === +obj.length ? obj : _.values(obj);
  293 + for (var i = 0, length = obj.length; i < length; i++) {
  294 + value = obj[i];
  295 + if (value < result) {
  296 + result = value;
  297 + }
  298 + }
  299 + } else {
  300 + iteratee = _.iteratee(iteratee, context);
  301 + _.each(obj, function(value, index, list) {
  302 + computed = iteratee(value, index, list);
  303 + if (computed < lastComputed || computed === Infinity && result === Infinity) {
  304 + result = value;
  305 + lastComputed = computed;
  306 + }
  307 + });
  308 + }
  309 + return result;
  310 + };
  311 +
  312 + // Shuffle a collection, using the modern version of the
  313 + // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
  314 + _.shuffle = function(obj) {
  315 + var set = obj && obj.length === +obj.length ? obj : _.values(obj);
  316 + var length = set.length;
  317 + var shuffled = Array(length);
  318 + for (var index = 0, rand; index < length; index++) {
  319 + rand = _.random(0, index);
  320 + if (rand !== index) shuffled[index] = shuffled[rand];
  321 + shuffled[rand] = set[index];
  322 + }
  323 + return shuffled;
  324 + };
  325 +
  326 + // Sample **n** random values from a collection.
  327 + // If **n** is not specified, returns a single random element.
  328 + // The internal `guard` argument allows it to work with `map`.
  329 + _.sample = function(obj, n, guard) {
  330 + if (n == null || guard) {
  331 + if (obj.length !== +obj.length) obj = _.values(obj);
  332 + return obj[_.random(obj.length - 1)];
  333 + }
  334 + return _.shuffle(obj).slice(0, Math.max(0, n));
  335 + };
  336 +
  337 + // Sort the object's values by a criterion produced by an iteratee.
  338 + _.sortBy = function(obj, iteratee, context) {
  339 + iteratee = _.iteratee(iteratee, context);
  340 + return _.pluck(_.map(obj, function(value, index, list) {
  341 + return {
  342 + value: value,
  343 + index: index,
  344 + criteria: iteratee(value, index, list)
  345 + };
  346 + }).sort(function(left, right) {
  347 + var a = left.criteria;
  348 + var b = right.criteria;
  349 + if (a !== b) {
  350 + if (a > b || a === void 0) return 1;
  351 + if (a < b || b === void 0) return -1;
  352 + }
  353 + return left.index - right.index;
  354 + }), 'value');
  355 + };
  356 +
  357 + // An internal function used for aggregate "group by" operations.
  358 + var group = function(behavior) {
  359 + return function(obj, iteratee, context) {
  360 + var result = {};
  361 + iteratee = _.iteratee(iteratee, context);
  362 + _.each(obj, function(value, index) {
  363 + var key = iteratee(value, index, obj);
  364 + behavior(result, value, key);
  365 + });
  366 + return result;
  367 + };
  368 + };
  369 +
  370 + // Groups the object's values by a criterion. Pass either a string attribute
  371 + // to group by, or a function that returns the criterion.
  372 + _.groupBy = group(function(result, value, key) {
  373 + if (_.has(result, key)) result[key].push(value); else result[key] = [value];
  374 + });
  375 +
  376 + // Indexes the object's values by a criterion, similar to `groupBy`, but for
  377 + // when you know that your index values will be unique.
  378 + _.indexBy = group(function(result, value, key) {
  379 + result[key] = value;
  380 + });
  381 +
  382 + // Counts instances of an object that group by a certain criterion. Pass
  383 + // either a string attribute to count by, or a function that returns the
  384 + // criterion.
  385 + _.countBy = group(function(result, value, key) {
  386 + if (_.has(result, key)) result[key]++; else result[key] = 1;
  387 + });
  388 +
  389 + // Use a comparator function to figure out the smallest index at which
  390 + // an object should be inserted so as to maintain order. Uses binary search.
  391 + _.sortedIndex = function(array, obj, iteratee, context) {
  392 + iteratee = _.iteratee(iteratee, context, 1);
  393 + var value = iteratee(obj);
  394 + var low = 0, high = array.length;
  395 + while (low < high) {
  396 + var mid = low + high >>> 1;
  397 + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
  398 + }
  399 + return low;
  400 + };
  401 +
  402 + // Safely create a real, live array from anything iterable.
  403 + _.toArray = function(obj) {
  404 + if (!obj) return [];
  405 + if (_.isArray(obj)) return slice.call(obj);
  406 + if (obj.length === +obj.length) return _.map(obj, _.identity);
  407 + return _.values(obj);
  408 + };
  409 +
  410 + // Return the number of elements in an object.
  411 + _.size = function(obj) {
  412 + if (obj == null) return 0;
  413 + return obj.length === +obj.length ? obj.length : _.keys(obj).length;
  414 + };
  415 +
  416 + // Split a collection into two arrays: one whose elements all satisfy the given
  417 + // predicate, and one whose elements all do not satisfy the predicate.
  418 + _.partition = function(obj, predicate, context) {
  419 + predicate = _.iteratee(predicate, context);
  420 + var pass = [], fail = [];
  421 + _.each(obj, function(value, key, obj) {
  422 + (predicate(value, key, obj) ? pass : fail).push(value);
  423 + });
  424 + return [pass, fail];
  425 + };
  426 +
  427 + // Array Functions
  428 + // ---------------
  429 +
  430 + // Get the first element of an array. Passing **n** will return the first N
  431 + // values in the array. Aliased as `head` and `take`. The **guard** check
  432 + // allows it to work with `_.map`.
  433 + _.first = _.head = _.take = function(array, n, guard) {
  434 + if (array == null) return void 0;
  435 + if (n == null || guard) return array[0];
  436 + if (n < 0) return [];
  437 + return slice.call(array, 0, n);
  438 + };
  439 +
  440 + // Returns everything but the last entry of the array. Especially useful on
  441 + // the arguments object. Passing **n** will return all the values in
  442 + // the array, excluding the last N. The **guard** check allows it to work with
  443 + // `_.map`.
  444 + _.initial = function(array, n, guard) {
  445 + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
  446 + };
  447 +
  448 + // Get the last element of an array. Passing **n** will return the last N
  449 + // values in the array. The **guard** check allows it to work with `_.map`.
  450 + _.last = function(array, n, guard) {
  451 + if (array == null) return void 0;
  452 + if (n == null || guard) return array[array.length - 1];
  453 + return slice.call(array, Math.max(array.length - n, 0));
  454 + };
  455 +
  456 + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
  457 + // Especially useful on the arguments object. Passing an **n** will return
  458 + // the rest N values in the array. The **guard**
  459 + // check allows it to work with `_.map`.
  460 + _.rest = _.tail = _.drop = function(array, n, guard) {
  461 + return slice.call(array, n == null || guard ? 1 : n);
  462 + };
  463 +
  464 + // Trim out all falsy values from an array.
  465 + _.compact = function(array) {
  466 + return _.filter(array, _.identity);
  467 + };
  468 +
  469 + // Internal implementation of a recursive `flatten` function.
  470 + var flatten = function(input, shallow, strict, output) {
  471 + if (shallow && _.every(input, _.isArray)) {
  472 + return concat.apply(output, input);
  473 + }
  474 + for (var i = 0, length = input.length; i < length; i++) {
  475 + var value = input[i];
  476 + if (!_.isArray(value) && !_.isArguments(value)) {
  477 + if (!strict) output.push(value);
  478 + } else if (shallow) {
  479 + push.apply(output, value);
  480 + } else {
  481 + flatten(value, shallow, strict, output);
  482 + }
  483 + }
  484 + return output;
  485 + };
  486 +
  487 + // Flatten out an array, either recursively (by default), or just one level.
  488 + _.flatten = function(array, shallow) {
  489 + return flatten(array, shallow, false, []);
  490 + };
  491 +
  492 + // Return a version of the array that does not contain the specified value(s).
  493 + _.without = function(array) {
  494 + return _.difference(array, slice.call(arguments, 1));
  495 + };
  496 +
  497 + // Produce a duplicate-free version of the array. If the array has already
  498 + // been sorted, you have the option of using a faster algorithm.
  499 + // Aliased as `unique`.
  500 + _.uniq = _.unique = function(array, isSorted, iteratee, context) {
  501 + if (array == null) return [];
  502 + if (!_.isBoolean(isSorted)) {
  503 + context = iteratee;
  504 + iteratee = isSorted;
  505 + isSorted = false;
  506 + }
  507 + if (iteratee != null) iteratee = _.iteratee(iteratee, context);
  508 + var result = [];
  509 + var seen = [];
  510 + for (var i = 0, length = array.length; i < length; i++) {
  511 + var value = array[i];
  512 + if (isSorted) {
  513 + if (!i || seen !== value) result.push(value);
  514 + seen = value;
  515 + } else if (iteratee) {
  516 + var computed = iteratee(value, i, array);
  517 + if (_.indexOf(seen, computed) < 0) {
  518 + seen.push(computed);
  519 + result.push(value);
  520 + }
  521 + } else if (_.indexOf(result, value) < 0) {
  522 + result.push(value);
  523 + }
  524 + }
  525 + return result;
  526 + };
  527 +
  528 + // Produce an array that contains the union: each distinct element from all of
  529 + // the passed-in arrays.
  530 + _.union = function() {
  531 + return _.uniq(flatten(arguments, true, true, []));
  532 + };
  533 +
  534 + // Produce an array that contains every item shared between all the
  535 + // passed-in arrays.
  536 + _.intersection = function(array) {
  537 + if (array == null) return [];
  538 + var result = [];
  539 + var argsLength = arguments.length;
  540 + for (var i = 0, length = array.length; i < length; i++) {
  541 + var item = array[i];
  542 + if (_.contains(result, item)) continue;
  543 + for (var j = 1; j < argsLength; j++) {
  544 + if (!_.contains(arguments[j], item)) break;
  545 + }
  546 + if (j === argsLength) result.push(item);
  547 + }
  548 + return result;
  549 + };
  550 +
  551 + // Take the difference between one array and a number of other arrays.
  552 + // Only the elements present in just the first array will remain.
  553 + _.difference = function(array) {
  554 + var rest = flatten(slice.call(arguments, 1), true, true, []);
  555 + return _.filter(array, function(value){
  556 + return !_.contains(rest, value);
  557 + });
  558 + };
  559 +
  560 + // Zip together multiple lists into a single array -- elements that share
  561 + // an index go together.
  562 + _.zip = function(array) {
  563 + if (array == null) return [];
  564 + var length = _.max(arguments, 'length').length;
  565 + var results = Array(length);
  566 + for (var i = 0; i < length; i++) {
  567 + results[i] = _.pluck(arguments, i);
  568 + }
  569 + return results;
  570 + };
  571 +
  572 + // Converts lists into objects. Pass either a single array of `[key, value]`
  573 + // pairs, or two parallel arrays of the same length -- one of keys, and one of
  574 + // the corresponding values.
  575 + _.object = function(list, values) {
  576 + if (list == null) return {};
  577 + var result = {};
  578 + for (var i = 0, length = list.length; i < length; i++) {
  579 + if (values) {
  580 + result[list[i]] = values[i];
  581 + } else {
  582 + result[list[i][0]] = list[i][1];
  583 + }
  584 + }
  585 + return result;
  586 + };
  587 +
  588 + // Return the position of the first occurrence of an item in an array,
  589 + // or -1 if the item is not included in the array.
  590 + // If the array is large and already in sort order, pass `true`
  591 + // for **isSorted** to use binary search.
  592 + _.indexOf = function(array, item, isSorted) {
  593 + if (array == null) return -1;
  594 + var i = 0, length = array.length;
  595 + if (isSorted) {
  596 + if (typeof isSorted == 'number') {
  597 + i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
  598 + } else {
  599 + i = _.sortedIndex(array, item);
  600 + return array[i] === item ? i : -1;
  601 + }
  602 + }
  603 + for (; i < length; i++) if (array[i] === item) return i;
  604 + return -1;
  605 + };
  606 +
  607 + _.lastIndexOf = function(array, item, from) {
  608 + if (array == null) return -1;
  609 + var idx = array.length;
  610 + if (typeof from == 'number') {
  611 + idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
  612 + }
  613 + while (--idx >= 0) if (array[idx] === item) return idx;
  614 + return -1;
  615 + };
  616 +
  617 + // Generate an integer Array containing an arithmetic progression. A port of
  618 + // the native Python `range()` function. See
  619 + // [the Python documentation](http://docs.python.org/library/functions.html#range).
  620 + _.range = function(start, stop, step) {
  621 + if (arguments.length <= 1) {
  622 + stop = start || 0;
  623 + start = 0;
  624 + }
  625 + step = step || 1;
  626 +
  627 + var length = Math.max(Math.ceil((stop - start) / step), 0);
  628 + var range = Array(length);
  629 +
  630 + for (var idx = 0; idx < length; idx++, start += step) {
  631 + range[idx] = start;
  632 + }
  633 +
  634 + return range;
  635 + };
  636 +
  637 + // Function (ahem) Functions
  638 + // ------------------
  639 +
  640 + // Reusable constructor function for prototype setting.
  641 + var Ctor = function(){};
  642 +
  643 + // Create a function bound to a given object (assigning `this`, and arguments,
  644 + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
  645 + // available.
  646 + _.bind = function(func, context) {
  647 + var args, bound;
  648 + if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
  649 + if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
  650 + args = slice.call(arguments, 2);
  651 + bound = function() {
  652 + if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
  653 + Ctor.prototype = func.prototype;
  654 + var self = new Ctor;
  655 + Ctor.prototype = null;
  656 + var result = func.apply(self, args.concat(slice.call(arguments)));
  657 + if (_.isObject(result)) return result;
  658 + return self;
  659 + };
  660 + return bound;
  661 + };
  662 +
  663 + // Partially apply a function by creating a version that has had some of its
  664 + // arguments pre-filled, without changing its dynamic `this` context. _ acts
  665 + // as a placeholder, allowing any combination of arguments to be pre-filled.
  666 + _.partial = function(func) {
  667 + var boundArgs = slice.call(arguments, 1);
  668 + return function() {
  669 + var position = 0;
  670 + var args = boundArgs.slice();
  671 + for (var i = 0, length = args.length; i < length; i++) {
  672 + if (args[i] === _) args[i] = arguments[position++];
  673 + }
  674 + while (position < arguments.length) args.push(arguments[position++]);
  675 + return func.apply(this, args);
  676 + };
  677 + };
  678 +
  679 + // Bind a number of an object's methods to that object. Remaining arguments
  680 + // are the method names to be bound. Useful for ensuring that all callbacks
  681 + // defined on an object belong to it.
  682 + _.bindAll = function(obj) {
  683 + var i, length = arguments.length, key;
  684 + if (length <= 1) throw new Error('bindAll must be passed function names');
  685 + for (i = 1; i < length; i++) {
  686 + key = arguments[i];
  687 + obj[key] = _.bind(obj[key], obj);
  688 + }
  689 + return obj;
  690 + };
  691 +
  692 + // Memoize an expensive function by storing its results.
  693 + _.memoize = function(func, hasher) {
  694 + var memoize = function(key) {
  695 + var cache = memoize.cache;
  696 + var address = hasher ? hasher.apply(this, arguments) : key;
  697 + if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
  698 + return cache[address];
  699 + };
  700 + memoize.cache = {};
  701 + return memoize;
  702 + };
  703 +
  704 + // Delays a function for the given number of milliseconds, and then calls
  705 + // it with the arguments supplied.
  706 + _.delay = function(func, wait) {
  707 + var args = slice.call(arguments, 2);
  708 + return setTimeout(function(){
  709 + return func.apply(null, args);
  710 + }, wait);
  711 + };
  712 +
  713 + // Defers a function, scheduling it to run after the current call stack has
  714 + // cleared.
  715 + _.defer = function(func) {
  716 + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
  717 + };
  718 +
  719 + // Returns a function, that, when invoked, will only be triggered at most once
  720 + // during a given window of time. Normally, the throttled function will run
  721 + // as much as it can, without ever going more than once per `wait` duration;
  722 + // but if you'd like to disable the execution on the leading edge, pass
  723 + // `{leading: false}`. To disable execution on the trailing edge, ditto.
  724 + _.throttle = function(func, wait, options) {
  725 + var context, args, result;
  726 + var timeout = null;
  727 + var previous = 0;
  728 + if (!options) options = {};
  729 + var later = function() {
  730 + previous = options.leading === false ? 0 : _.now();
  731 + timeout = null;
  732 + result = func.apply(context, args);
  733 + if (!timeout) context = args = null;
  734 + };
  735 + return function() {
  736 + var now = _.now();
  737 + if (!previous && options.leading === false) previous = now;
  738 + var remaining = wait - (now - previous);
  739 + context = this;
  740 + args = arguments;
  741 + if (remaining <= 0 || remaining > wait) {
  742 + clearTimeout(timeout);
  743 + timeout = null;
  744 + previous = now;
  745 + result = func.apply(context, args);
  746 + if (!timeout) context = args = null;
  747 + } else if (!timeout && options.trailing !== false) {
  748 + timeout = setTimeout(later, remaining);
  749 + }
  750 + return result;
  751 + };
  752 + };
  753 +
  754 + // Returns a function, that, as long as it continues to be invoked, will not
  755 + // be triggered. The function will be called after it stops being called for
  756 + // N milliseconds. If `immediate` is passed, trigger the function on the
  757 + // leading edge, instead of the trailing.
  758 + _.debounce = function(func, wait, immediate) {
  759 + var timeout, args, context, timestamp, result;
  760 +
  761 + var later = function() {
  762 + var last = _.now() - timestamp;
  763 +
  764 + if (last < wait && last > 0) {
  765 + timeout = setTimeout(later, wait - last);
  766 + } else {
  767 + timeout = null;
  768 + if (!immediate) {
  769 + result = func.apply(context, args);
  770 + if (!timeout) context = args = null;
  771 + }
  772 + }
  773 + };
  774 +
  775 + return function() {
  776 + context = this;
  777 + args = arguments;
  778 + timestamp = _.now();
  779 + var callNow = immediate && !timeout;
  780 + if (!timeout) timeout = setTimeout(later, wait);
  781 + if (callNow) {
  782 + result = func.apply(context, args);
  783 + context = args = null;
  784 + }
  785 +
  786 + return result;
  787 + };
  788 + };
  789 +
  790 + // Returns the first function passed as an argument to the second,
  791 + // allowing you to adjust arguments, run code before and after, and
  792 + // conditionally execute the original function.
  793 + _.wrap = function(func, wrapper) {
  794 + return _.partial(wrapper, func);
  795 + };
  796 +
  797 + // Returns a negated version of the passed-in predicate.
  798 + _.negate = function(predicate) {
  799 + return function() {
  800 + return !predicate.apply(this, arguments);
  801 + };
  802 + };
  803 +
  804 + // Returns a function that is the composition of a list of functions, each
  805 + // consuming the return value of the function that follows.
  806 + _.compose = function() {
  807 + var args = arguments;
  808 + var start = args.length - 1;
  809 + return function() {
  810 + var i = start;
  811 + var result = args[start].apply(this, arguments);
  812 + while (i--) result = args[i].call(this, result);
  813 + return result;
  814 + };
  815 + };
  816 +
  817 + // Returns a function that will only be executed after being called N times.
  818 + _.after = function(times, func) {
  819 + return function() {
  820 + if (--times < 1) {
  821 + return func.apply(this, arguments);
  822 + }
  823 + };
  824 + };
  825 +
  826 + // Returns a function that will only be executed before being called N times.
  827 + _.before = function(times, func) {
  828 + var memo;
  829 + return function() {
  830 + if (--times > 0) {
  831 + memo = func.apply(this, arguments);
  832 + } else {
  833 + func = null;
  834 + }
  835 + return memo;
  836 + };
  837 + };
  838 +
  839 + // Returns a function that will be executed at most one time, no matter how
  840 + // often you call it. Useful for lazy initialization.
  841 + _.once = _.partial(_.before, 2);
  842 +
  843 + // Object Functions
  844 + // ----------------
  845 +
  846 + // Retrieve the names of an object's properties.
  847 + // Delegates to **ECMAScript 5**'s native `Object.keys`
  848 + _.keys = function(obj) {
  849 + if (!_.isObject(obj)) return [];
  850 + if (nativeKeys) return nativeKeys(obj);
  851 + var keys = [];
  852 + for (var key in obj) if (_.has(obj, key)) keys.push(key);
  853 + return keys;
  854 + };
  855 +
  856 + // Retrieve the values of an object's properties.
  857 + _.values = function(obj) {
  858 + var keys = _.keys(obj);
  859 + var length = keys.length;
  860 + var values = Array(length);
  861 + for (var i = 0; i < length; i++) {
  862 + values[i] = obj[keys[i]];
  863 + }
  864 + return values;
  865 + };
  866 +
  867 + // Convert an object into a list of `[key, value]` pairs.
  868 + _.pairs = function(obj) {
  869 + var keys = _.keys(obj);
  870 + var length = keys.length;
  871 + var pairs = Array(length);
  872 + for (var i = 0; i < length; i++) {
  873 + pairs[i] = [keys[i], obj[keys[i]]];
  874 + }
  875 + return pairs;
  876 + };
  877 +
  878 + // Invert the keys and values of an object. The values must be serializable.
  879 + _.invert = function(obj) {
  880 + var result = {};
  881 + var keys = _.keys(obj);
  882 + for (var i = 0, length = keys.length; i < length; i++) {
  883 + result[obj[keys[i]]] = keys[i];
  884 + }
  885 + return result;
  886 + };
  887 +
  888 + // Return a sorted list of the function names available on the object.
  889 + // Aliased as `methods`
  890 + _.functions = _.methods = function(obj) {
  891 + var names = [];
  892 + for (var key in obj) {
  893 + if (_.isFunction(obj[key])) names.push(key);
  894 + }
  895 + return names.sort();
  896 + };
  897 +
  898 + // Extend a given object with all the properties in passed-in object(s).
  899 + _.extend = function(obj) {
  900 + if (!_.isObject(obj)) return obj;
  901 + var source, prop;
  902 + for (var i = 1, length = arguments.length; i < length; i++) {
  903 + source = arguments[i];
  904 + for (prop in source) {
  905 + if (hasOwnProperty.call(source, prop)) {
  906 + obj[prop] = source[prop];
  907 + }
  908 + }
  909 + }
  910 + return obj;
  911 + };
  912 +
  913 + // Return a copy of the object only containing the whitelisted properties.
  914 + _.pick = function(obj, iteratee, context) {
  915 + var result = {}, key;
  916 + if (obj == null) return result;
  917 + if (_.isFunction(iteratee)) {
  918 + iteratee = createCallback(iteratee, context);
  919 + for (key in obj) {
  920 + var value = obj[key];
  921 + if (iteratee(value, key, obj)) result[key] = value;
  922 + }
  923 + } else {
  924 + var keys = concat.apply([], slice.call(arguments, 1));
  925 + obj = new Object(obj);
  926 + for (var i = 0, length = keys.length; i < length; i++) {
  927 + key = keys[i];
  928 + if (key in obj) result[key] = obj[key];
  929 + }
  930 + }
  931 + return result;
  932 + };
  933 +
  934 + // Return a copy of the object without the blacklisted properties.
  935 + _.omit = function(obj, iteratee, context) {
  936 + if (_.isFunction(iteratee)) {
  937 + iteratee = _.negate(iteratee);
  938 + } else {
  939 + var keys = _.map(concat.apply([], slice.call(arguments, 1)), String);
  940 + iteratee = function(value, key) {
  941 + return !_.contains(keys, key);
  942 + };
  943 + }
  944 + return _.pick(obj, iteratee, context);
  945 + };
  946 +
  947 + // Fill in a given object with default properties.
  948 + _.defaults = function(obj) {
  949 + if (!_.isObject(obj)) return obj;
  950 + for (var i = 1, length = arguments.length; i < length; i++) {
  951 + var source = arguments[i];
  952 + for (var prop in source) {
  953 + if (obj[prop] === void 0) obj[prop] = source[prop];
  954 + }
  955 + }
  956 + return obj;
  957 + };
  958 +
  959 + // Create a (shallow-cloned) duplicate of an object.
  960 + _.clone = function(obj) {
  961 + if (!_.isObject(obj)) return obj;
  962 + return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  963 + };
  964 +
  965 + // Invokes interceptor with the obj, and then returns obj.
  966 + // The primary purpose of this method is to "tap into" a method chain, in
  967 + // order to perform operations on intermediate results within the chain.
  968 + _.tap = function(obj, interceptor) {
  969 + interceptor(obj);
  970 + return obj;
  971 + };
  972 +
  973 + // Internal recursive comparison function for `isEqual`.
  974 + var eq = function(a, b, aStack, bStack) {
  975 + // Identical objects are equal. `0 === -0`, but they aren't identical.
  976 + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
  977 + if (a === b) return a !== 0 || 1 / a === 1 / b;
  978 + // A strict comparison is necessary because `null == undefined`.
  979 + if (a == null || b == null) return a === b;
  980 + // Unwrap any wrapped objects.
  981 + if (a instanceof _) a = a._wrapped;
  982 + if (b instanceof _) b = b._wrapped;
  983 + // Compare `[[Class]]` names.
  984 + var className = toString.call(a);
  985 + if (className !== toString.call(b)) return false;
  986 + switch (className) {
  987 + // Strings, numbers, regular expressions, dates, and booleans are compared by value.
  988 + case '[object RegExp]':
  989 + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
  990 + case '[object String]':
  991 + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
  992 + // equivalent to `new String("5")`.
  993 + return '' + a === '' + b;
  994 + case '[object Number]':
  995 + // `NaN`s are equivalent, but non-reflexive.
  996 + // Object(NaN) is equivalent to NaN
  997 + if (+a !== +a) return +b !== +b;
  998 + // An `egal` comparison is performed for other numeric values.
  999 + return +a === 0 ? 1 / +a === 1 / b : +a === +b;
  1000 + case '[object Date]':
  1001 + case '[object Boolean]':
  1002 + // Coerce dates and booleans to numeric primitive values. Dates are compared by their
  1003 + // millisecond representations. Note that invalid dates with millisecond representations
  1004 + // of `NaN` are not equivalent.
  1005 + return +a === +b;
  1006 + }
  1007 + if (typeof a != 'object' || typeof b != 'object') return false;
  1008 + // Assume equality for cyclic structures. The algorithm for detecting cyclic
  1009 + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
  1010 + var length = aStack.length;
  1011 + while (length--) {
  1012 + // Linear search. Performance is inversely proportional to the number of
  1013 + // unique nested structures.
  1014 + if (aStack[length] === a) return bStack[length] === b;
  1015 + }
  1016 + // Objects with different constructors are not equivalent, but `Object`s
  1017 + // from different frames are.
  1018 + var aCtor = a.constructor, bCtor = b.constructor;
  1019 + if (
  1020 + aCtor !== bCtor &&
  1021 + // Handle Object.create(x) cases
  1022 + 'constructor' in a && 'constructor' in b &&
  1023 + !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
  1024 + _.isFunction(bCtor) && bCtor instanceof bCtor)
  1025 + ) {
  1026 + return false;
  1027 + }
  1028 + // Add the first object to the stack of traversed objects.
  1029 + aStack.push(a);
  1030 + bStack.push(b);
  1031 + var size, result;
  1032 + // Recursively compare objects and arrays.
  1033 + if (className === '[object Array]') {
  1034 + // Compare array lengths to determine if a deep comparison is necessary.
  1035 + size = a.length;
  1036 + result = size === b.length;
  1037 + if (result) {
  1038 + // Deep compare the contents, ignoring non-numeric properties.
  1039 + while (size--) {
  1040 + if (!(result = eq(a[size], b[size], aStack, bStack))) break;
  1041 + }
  1042 + }
  1043 + } else {
  1044 + // Deep compare objects.
  1045 + var keys = _.keys(a), key;
  1046 + size = keys.length;
  1047 + // Ensure that both objects contain the same number of properties before comparing deep equality.
  1048 + result = _.keys(b).length === size;
  1049 + if (result) {
  1050 + while (size--) {
  1051 + // Deep compare each member
  1052 + key = keys[size];
  1053 + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
  1054 + }
  1055 + }
  1056 + }
  1057 + // Remove the first object from the stack of traversed objects.
  1058 + aStack.pop();
  1059 + bStack.pop();
  1060 + return result;
  1061 + };
  1062 +
  1063 + // Perform a deep comparison to check if two objects are equal.
  1064 + _.isEqual = function(a, b) {
  1065 + return eq(a, b, [], []);
  1066 + };
  1067 +
  1068 + // Is a given array, string, or object empty?
  1069 + // An "empty" object has no enumerable own-properties.
  1070 + _.isEmpty = function(obj) {
  1071 + if (obj == null) return true;
  1072 + if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0;
  1073 + for (var key in obj) if (_.has(obj, key)) return false;
  1074 + return true;
  1075 + };
  1076 +
  1077 + // Is a given value a DOM element?
  1078 + _.isElement = function(obj) {
  1079 + return !!(obj && obj.nodeType === 1);
  1080 + };
  1081 +
  1082 + // Is a given value an array?
  1083 + // Delegates to ECMA5's native Array.isArray
  1084 + _.isArray = nativeIsArray || function(obj) {
  1085 + return toString.call(obj) === '[object Array]';
  1086 + };
  1087 +
  1088 + // Is a given variable an object?
  1089 + _.isObject = function(obj) {
  1090 + var type = typeof obj;
  1091 + return type === 'function' || type === 'object' && !!obj;
  1092 + };
  1093 +
  1094 + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
  1095 + _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
  1096 + _['is' + name] = function(obj) {
  1097 + return toString.call(obj) === '[object ' + name + ']';
  1098 + };
  1099 + });
  1100 +
  1101 + // Define a fallback version of the method in browsers (ahem, IE), where
  1102 + // there isn't any inspectable "Arguments" type.
  1103 + if (!_.isArguments(arguments)) {
  1104 + _.isArguments = function(obj) {
  1105 + return _.has(obj, 'callee');
  1106 + };
  1107 + }
  1108 +
  1109 + // Optimize `isFunction` if appropriate. Work around an IE 11 bug.
  1110 + if (typeof /./ !== 'function') {
  1111 + _.isFunction = function(obj) {
  1112 + return typeof obj == 'function' || false;
  1113 + };
  1114 + }
  1115 +
  1116 + // Is a given object a finite number?
  1117 + _.isFinite = function(obj) {
  1118 + return isFinite(obj) && !isNaN(parseFloat(obj));
  1119 + };
  1120 +
  1121 + // Is the given value `NaN`? (NaN is the only number which does not equal itself).
  1122 + _.isNaN = function(obj) {
  1123 + return _.isNumber(obj) && obj !== +obj;
  1124 + };
  1125 +
  1126 + // Is a given value a boolean?
  1127 + _.isBoolean = function(obj) {
  1128 + return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
  1129 + };
  1130 +
  1131 + // Is a given value equal to null?
  1132 + _.isNull = function(obj) {
  1133 + return obj === null;
  1134 + };
  1135 +
  1136 + // Is a given variable undefined?
  1137 + _.isUndefined = function(obj) {
  1138 + return obj === void 0;
  1139 + };
  1140 +
  1141 + // Shortcut function for checking if an object has a given property directly
  1142 + // on itself (in other words, not on a prototype).
  1143 + _.has = function(obj, key) {
  1144 + return obj != null && hasOwnProperty.call(obj, key);
  1145 + };
  1146 +
  1147 + // Utility Functions
  1148 + // -----------------
  1149 +
  1150 + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  1151 + // previous owner. Returns a reference to the Underscore object.
  1152 + _.noConflict = function() {
  1153 + root._ = previousUnderscore;
  1154 + return this;
  1155 + };
  1156 +
  1157 + // Keep the identity function around for default iteratees.
  1158 + _.identity = function(value) {
  1159 + return value;
  1160 + };
  1161 +
  1162 + _.constant = function(value) {
  1163 + return function() {
  1164 + return value;
  1165 + };
  1166 + };
  1167 +
  1168 + _.noop = function(){};
  1169 +
  1170 + _.property = function(key) {
  1171 + return function(obj) {
  1172 + return obj[key];
  1173 + };
  1174 + };
  1175 +
  1176 + // Returns a predicate for checking whether an object has a given set of `key:value` pairs.
  1177 + _.matches = function(attrs) {
  1178 + var pairs = _.pairs(attrs), length = pairs.length;
  1179 + return function(obj) {
  1180 + if (obj == null) return !length;
  1181 + obj = new Object(obj);
  1182 + for (var i = 0; i < length; i++) {
  1183 + var pair = pairs[i], key = pair[0];
  1184 + if (pair[1] !== obj[key] || !(key in obj)) return false;
  1185 + }
  1186 + return true;
  1187 + };
  1188 + };
  1189 +
  1190 + // Run a function **n** times.
  1191 + _.times = function(n, iteratee, context) {
  1192 + var accum = Array(Math.max(0, n));
  1193 + iteratee = createCallback(iteratee, context, 1);
  1194 + for (var i = 0; i < n; i++) accum[i] = iteratee(i);
  1195 + return accum;
  1196 + };
  1197 +
  1198 + // Return a random integer between min and max (inclusive).
  1199 + _.random = function(min, max) {
  1200 + if (max == null) {
  1201 + max = min;
  1202 + min = 0;
  1203 + }
  1204 + return min + Math.floor(Math.random() * (max - min + 1));
  1205 + };
  1206 +
  1207 + // A (possibly faster) way to get the current timestamp as an integer.
  1208 + _.now = Date.now || function() {
  1209 + return new Date().getTime();
  1210 + };
  1211 +
  1212 + // List of HTML entities for escaping.
  1213 + var escapeMap = {
  1214 + '&': '&amp;',
  1215 + '<': '&lt;',
  1216 + '>': '&gt;',
  1217 + '"': '&quot;',
  1218 + "'": '&#x27;',
  1219 + '`': '&#x60;'
  1220 + };
  1221 + var unescapeMap = _.invert(escapeMap);
  1222 +
  1223 + // Functions for escaping and unescaping strings to/from HTML interpolation.
  1224 + var createEscaper = function(map) {
  1225 + var escaper = function(match) {
  1226 + return map[match];
  1227 + };
  1228 + // Regexes for identifying a key that needs to be escaped
  1229 + var source = '(?:' + _.keys(map).join('|') + ')';
  1230 + var testRegexp = RegExp(source);
  1231 + var replaceRegexp = RegExp(source, 'g');
  1232 + return function(string) {
  1233 + string = string == null ? '' : '' + string;
  1234 + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
  1235 + };
  1236 + };
  1237 + _.escape = createEscaper(escapeMap);
  1238 + _.unescape = createEscaper(unescapeMap);
  1239 +
  1240 + // If the value of the named `property` is a function then invoke it with the
  1241 + // `object` as context; otherwise, return it.
  1242 + _.result = function(object, property) {
  1243 + if (object == null) return void 0;
  1244 + var value = object[property];
  1245 + return _.isFunction(value) ? object[property]() : value;
  1246 + };
  1247 +
  1248 + // Generate a unique integer id (unique within the entire client session).
  1249 + // Useful for temporary DOM ids.
  1250 + var idCounter = 0;
  1251 + _.uniqueId = function(prefix) {
  1252 + var id = ++idCounter + '';
  1253 + return prefix ? prefix + id : id;
  1254 + };
  1255 +
  1256 + // By default, Underscore uses ERB-style template delimiters, change the
  1257 + // following template settings to use alternative delimiters.
  1258 + _.templateSettings = {
  1259 + evaluate : /<%([\s\S]+?)%>/g,
  1260 + interpolate : /<%=([\s\S]+?)%>/g,
  1261 + escape : /<%-([\s\S]+?)%>/g
  1262 + };
  1263 +
  1264 + // When customizing `templateSettings`, if you don't want to define an
  1265 + // interpolation, evaluation or escaping regex, we need one that is
  1266 + // guaranteed not to match.
  1267 + var noMatch = /(.)^/;
  1268 +
  1269 + // Certain characters need to be escaped so that they can be put into a
  1270 + // string literal.
  1271 + var escapes = {
  1272 + "'": "'",
  1273 + '\\': '\\',
  1274 + '\r': 'r',
  1275 + '\n': 'n',
  1276 + '\u2028': 'u2028',
  1277 + '\u2029': 'u2029'
  1278 + };
  1279 +
  1280 + var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
  1281 +
  1282 + var escapeChar = function(match) {
  1283 + return '\\' + escapes[match];
  1284 + };
  1285 +
  1286 + // JavaScript micro-templating, similar to John Resig's implementation.
  1287 + // Underscore templating handles arbitrary delimiters, preserves whitespace,
  1288 + // and correctly escapes quotes within interpolated code.
  1289 + // NB: `oldSettings` only exists for backwards compatibility.
  1290 + _.template = function(text, settings, oldSettings) {
  1291 + if (!settings && oldSettings) settings = oldSettings;
  1292 + settings = _.defaults({}, settings, _.templateSettings);
  1293 +
  1294 + // Combine delimiters into one regular expression via alternation.
  1295 + var matcher = RegExp([
  1296 + (settings.escape || noMatch).source,
  1297 + (settings.interpolate || noMatch).source,
  1298 + (settings.evaluate || noMatch).source
  1299 + ].join('|') + '|$', 'g');
  1300 +
  1301 + // Compile the template source, escaping string literals appropriately.
  1302 + var index = 0;
  1303 + var source = "__p+='";
  1304 + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
  1305 + source += text.slice(index, offset).replace(escaper, escapeChar);
  1306 + index = offset + match.length;
  1307 +
  1308 + if (escape) {
  1309 + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
  1310 + } else if (interpolate) {
  1311 + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
  1312 + } else if (evaluate) {
  1313 + source += "';\n" + evaluate + "\n__p+='";
  1314 + }
  1315 +
  1316 + // Adobe VMs need the match returned to produce the correct offest.
  1317 + return match;
  1318 + });
  1319 + source += "';\n";
  1320 +
  1321 + // If a variable is not specified, place data values in local scope.
  1322 + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
  1323 +
  1324 + source = "var __t,__p='',__j=Array.prototype.join," +
  1325 + "print=function(){__p+=__j.call(arguments,'');};\n" +
  1326 + source + 'return __p;\n';
  1327 +
  1328 + try {
  1329 + var render = new Function(settings.variable || 'obj', '_', source);
  1330 + } catch (e) {
  1331 + e.source = source;
  1332 + throw e;
  1333 + }
  1334 +
  1335 + var template = function(data) {
  1336 + return render.call(this, data, _);
  1337 + };
  1338 +
  1339 + // Provide the compiled source as a convenience for precompilation.
  1340 + var argument = settings.variable || 'obj';
  1341 + template.source = 'function(' + argument + '){\n' + source + '}';
  1342 +
  1343 + return template;
  1344 + };
  1345 +
  1346 + // Add a "chain" function. Start chaining a wrapped Underscore object.
  1347 + _.chain = function(obj) {
  1348 + var instance = _(obj);
  1349 + instance._chain = true;
  1350 + return instance;
  1351 + };
  1352 +
  1353 + // OOP
  1354 + // ---------------
  1355 + // If Underscore is called as a function, it returns a wrapped object that
  1356 + // can be used OO-style. This wrapper holds altered versions of all the
  1357 + // underscore functions. Wrapped objects may be chained.
  1358 +
  1359 + // Helper function to continue chaining intermediate results.
  1360 + var result = function(obj) {
  1361 + return this._chain ? _(obj).chain() : obj;
  1362 + };
  1363 +
  1364 + // Add your own custom functions to the Underscore object.
  1365 + _.mixin = function(obj) {
  1366 + _.each(_.functions(obj), function(name) {
  1367 + var func = _[name] = obj[name];
  1368 + _.prototype[name] = function() {
  1369 + var args = [this._wrapped];
  1370 + push.apply(args, arguments);
  1371 + return result.call(this, func.apply(_, args));
  1372 + };
  1373 + });
  1374 + };
  1375 +
  1376 + // Add all of the Underscore functions to the wrapper object.
  1377 + _.mixin(_);
  1378 +
  1379 + // Add all mutator Array functions to the wrapper.
  1380 + _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
  1381 + var method = ArrayProto[name];
  1382 + _.prototype[name] = function() {
  1383 + var obj = this._wrapped;
  1384 + method.apply(obj, arguments);
  1385 + if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
  1386 + return result.call(this, obj);
  1387 + };
  1388 + });
  1389 +
  1390 + // Add all accessor Array functions to the wrapper.
  1391 + _.each(['concat', 'join', 'slice'], function(name) {
  1392 + var method = ArrayProto[name];
  1393 + _.prototype[name] = function() {
  1394 + return result.call(this, method.apply(this._wrapped, arguments));
  1395 + };
  1396 + });
  1397 +
  1398 + // Extracts the result from a wrapped and chained object.
  1399 + _.prototype.value = function() {
  1400 + return this._wrapped;
  1401 + };
  1402 +
  1403 + // AMD registration happens at the end for compatibility with AMD loaders
  1404 + // that may not enforce next-turn semantics on modules. Even though general
  1405 + // practice for AMD registration is to be anonymous, underscore registers
  1406 + // as a named module because, like jQuery, it is a base library that is
  1407 + // popular enough to be bundled in a third party lib, but not be part of
  1408 + // an AMD load request. Those cases could generate an error when an
  1409 + // anonymous define() is called outside of a loader request.
  1410 + if (typeof define === 'function' && define.amd) {
  1411 + define('underscore', [], function() {
  1412 + return _;
  1413 + });
  1414 + }
  1415 +}.call(this));
  1 +var crypto = require('crypto');
  2 +
  3 +var flags = {
  4 + NTLM_NegotiateUnicode : 0x00000001,
  5 + NTLM_NegotiateOEM : 0x00000002,
  6 + NTLM_RequestTarget : 0x00000004,
  7 + NTLM_Unknown9 : 0x00000008,
  8 + NTLM_NegotiateSign : 0x00000010,
  9 + NTLM_NegotiateSeal : 0x00000020,
  10 + NTLM_NegotiateDatagram : 0x00000040,
  11 + NTLM_NegotiateLanManagerKey : 0x00000080,
  12 + NTLM_Unknown8 : 0x00000100,
  13 + NTLM_NegotiateNTLM : 0x00000200,
  14 + NTLM_NegotiateNTOnly : 0x00000400,
  15 + NTLM_Anonymous : 0x00000800,
  16 + NTLM_NegotiateOemDomainSupplied : 0x00001000,
  17 + NTLM_NegotiateOemWorkstationSupplied : 0x00002000,
  18 + NTLM_Unknown6 : 0x00004000,
  19 + NTLM_NegotiateAlwaysSign : 0x00008000,
  20 + NTLM_TargetTypeDomain : 0x00010000,
  21 + NTLM_TargetTypeServer : 0x00020000,
  22 + NTLM_TargetTypeShare : 0x00040000,
  23 + NTLM_NegotiateExtendedSecurity : 0x00080000,
  24 + NTLM_NegotiateIdentify : 0x00100000,
  25 + NTLM_Unknown5 : 0x00200000,
  26 + NTLM_RequestNonNTSessionKey : 0x00400000,
  27 + NTLM_NegotiateTargetInfo : 0x00800000,
  28 + NTLM_Unknown4 : 0x01000000,
  29 + NTLM_NegotiateVersion : 0x02000000,
  30 + NTLM_Unknown3 : 0x04000000,
  31 + NTLM_Unknown2 : 0x08000000,
  32 + NTLM_Unknown1 : 0x10000000,
  33 + NTLM_Negotiate128 : 0x20000000,
  34 + NTLM_NegotiateKeyExchange : 0x40000000,
  35 + NTLM_Negotiate56 : 0x80000000
  36 +};
  37 +var typeflags = {
  38 + NTLM_TYPE1_FLAGS : flags.NTLM_NegotiateUnicode
  39 + + flags.NTLM_NegotiateOEM
  40 + + flags.NTLM_RequestTarget
  41 + + flags.NTLM_NegotiateNTLM
  42 + + flags.NTLM_NegotiateOemDomainSupplied
  43 + + flags.NTLM_NegotiateOemWorkstationSupplied
  44 + + flags.NTLM_NegotiateAlwaysSign
  45 + + flags.NTLM_NegotiateExtendedSecurity
  46 + + flags.NTLM_NegotiateVersion
  47 + + flags.NTLM_Negotiate128
  48 + + flags.NTLM_Negotiate56,
  49 +
  50 + NTLM_TYPE2_FLAGS : flags.NTLM_NegotiateUnicode
  51 + + flags.NTLM_RequestTarget
  52 + + flags.NTLM_NegotiateNTLM
  53 + + flags.NTLM_NegotiateAlwaysSign
  54 + + flags.NTLM_NegotiateExtendedSecurity
  55 + + flags.NTLM_NegotiateTargetInfo
  56 + + flags.NTLM_NegotiateVersion
  57 + + flags.NTLM_Negotiate128
  58 + + flags.NTLM_Negotiate56
  59 +};
  60 +
  61 +function createType1Message(options){
  62 + var domain = escape(options.domain.toUpperCase());
  63 + var workstation = escape(options.workstation.toUpperCase());
  64 + var protocol = 'NTLMSSP\0';
  65 +
  66 + var BODY_LENGTH = 40;
  67 +
  68 + var type1flags = typeflags.NTLM_TYPE1_FLAGS;
  69 + if(!domain || domain === '')
  70 + type1flags = type1flags - flags.NTLM_NegotiateOemDomainSupplied;
  71 +
  72 + var pos = 0;
  73 + var buf = new Buffer(BODY_LENGTH + domain.length + workstation.length);
  74 +
  75 +
  76 + buf.write(protocol, pos, protocol.length); pos += protocol.length; // protocol
  77 + buf.writeUInt32LE(1, pos); pos += 4; // type 1
  78 + buf.writeUInt32LE(type1flags, pos); pos += 4; // TYPE1 flag
  79 +
  80 + buf.writeUInt16LE(domain.length, pos); pos += 2; // domain length
  81 + buf.writeUInt16LE(domain.length, pos); pos += 2; // domain max length
  82 + buf.writeUInt32LE(BODY_LENGTH + workstation.length, pos); pos += 4; // domain buffer offset
  83 +
  84 + buf.writeUInt16LE(workstation.length, pos); pos += 2; // workstation length
  85 + buf.writeUInt16LE(workstation.length, pos); pos += 2; // workstation max length
  86 + buf.writeUInt32LE(BODY_LENGTH, pos); pos += 4; // workstation buffer offset
  87 +
  88 + buf.writeUInt8(5, pos); pos += 1; //ProductMajorVersion
  89 + buf.writeUInt8(1, pos); pos += 1; //ProductMinorVersion
  90 + buf.writeUInt16LE(2600, pos); pos += 2; //ProductBuild
  91 +
  92 + buf.writeUInt8(0 , pos); pos += 1; //VersionReserved1
  93 + buf.writeUInt8(0 , pos); pos += 1; //VersionReserved2
  94 + buf.writeUInt8(0 , pos); pos += 1; //VersionReserved3
  95 + buf.writeUInt8(15, pos); pos += 1; //NTLMRevisionCurrent
  96 +
  97 + buf.write(workstation, pos, workstation.length, 'ascii'); pos += workstation.length; // workstation string
  98 + buf.write(domain , pos, domain.length , 'ascii'); pos += domain.length;
  99 +
  100 + return 'NTLM ' + buf.toString('base64');
  101 +}
  102 +
  103 +function parseType2Message(rawmsg, callback){
  104 + var match = rawmsg.match(/NTLM (.+)?/);
  105 + if(!match || !match[1])
  106 + return callback(new Error("Couldn't find NTLM in the message type2 comming from the server"));
  107 +
  108 + var buf = new Buffer(match[1], 'base64');
  109 +
  110 + var msg = {};
  111 +
  112 + msg.signature = buf.slice(0, 8);
  113 + msg.type = buf.readInt16LE(8);
  114 +
  115 + if(msg.type != 2)
  116 + return callback(new Error("Server didn't return a type 2 message"));
  117 +
  118 + msg.targetNameLen = buf.readInt16LE(12);
  119 + msg.targetNameMaxLen = buf.readInt16LE(14);
  120 + msg.targetNameOffset = buf.readInt32LE(16);
  121 + msg.targetName = buf.slice(msg.targetNameOffset, msg.targetNameOffset + msg.targetNameMaxLen);
  122 +
  123 + msg.negotiateFlags = buf.readInt32LE(20);
  124 + msg.serverChallenge = buf.slice(24, 32);
  125 + msg.reserved = buf.slice(32, 40);
  126 +
  127 + if(msg.negotiateFlags & flags.NTLM_NegotiateTargetInfo){
  128 + msg.targetInfoLen = buf.readInt16LE(40);
  129 + msg.targetInfoMaxLen = buf.readInt16LE(42);
  130 + msg.targetInfoOffset = buf.readInt32LE(44);
  131 + msg.targetInfo = buf.slice(msg.targetInfoOffset, msg.targetInfoOffset + msg.targetInfoLen);
  132 + }
  133 + return msg;
  134 +}
  135 +
  136 +function createType3Message(msg2, options){
  137 + var nonce = msg2.serverChallenge;
  138 + var username = options.username;
  139 + var password = options.password;
  140 + var negotiateFlags = msg2.negotiateFlags;
  141 +
  142 + var isUnicode = negotiateFlags & flags.NTLM_NegotiateUnicode;
  143 + var isNegotiateExtendedSecurity = negotiateFlags & flags.NTLM_NegotiateExtendedSecurity;
  144 +
  145 + var BODY_LENGTH = 72;
  146 +
  147 + var domainName = escape(options.domain.toUpperCase());
  148 + var workstation = escape(options.workstation.toUpperCase());
  149 +
  150 + var workstationBytes, domainNameBytes, usernameBytes, encryptedRandomSessionKeyBytes;
  151 +
  152 + var encryptedRandomSessionKey = "";
  153 + if(isUnicode){
  154 + workstationBytes = new Buffer(workstation, 'utf16le');
  155 + domainNameBytes = new Buffer(domainName, 'utf16le');
  156 + usernameBytes = new Buffer(username, 'utf16le');
  157 + encryptedRandomSessionKeyBytes = new Buffer(encryptedRandomSessionKey, 'utf16le');
  158 + }else{
  159 + workstationBytes = new Buffer(workstation, 'ascii');
  160 + domainNameBytes = new Buffer(domainName, 'ascii');
  161 + usernameBytes = new Buffer(username, 'ascii');
  162 + encryptedRandomSessionKeyBytes = new Buffer(encryptedRandomSessionKey, 'ascii');
  163 + }
  164 +
  165 + var lmChallengeResponse = calc_resp(create_LM_hashed_password_v1(password), nonce);
  166 + var ntChallengeResponse = calc_resp(create_NT_hashed_password_v1(password), nonce);
  167 +
  168 + if(isNegotiateExtendedSecurity){
  169 + var pwhash = create_NT_hashed_password_v1(password);
  170 + var clientChallenge = "";
  171 + for(var i=0; i < 8; i++){
  172 + clientChallenge += String.fromCharCode( Math.floor(Math.random()*256) );
  173 + }
  174 + var clientChallengeBytes = new Buffer(clientChallenge, 'ascii');
  175 + var challenges = ntlm2sr_calc_resp(pwhash, nonce, clientChallengeBytes);
  176 + lmChallengeResponse = challenges.lmChallengeResponse;
  177 + ntChallengeResponse = challenges.ntChallengeResponse;
  178 + }
  179 +
  180 + var signature = 'NTLMSSP\0';
  181 +
  182 + var pos = 0;
  183 + var buf = new Buffer(BODY_LENGTH + domainNameBytes.length + usernameBytes.length + workstationBytes.length + lmChallengeResponse.length + ntChallengeResponse.length + encryptedRandomSessionKeyBytes.length);
  184 +
  185 + buf.write(signature, pos, signature.length); pos += signature.length;
  186 + buf.writeUInt32LE(3, pos); pos += 4; // type 1
  187 +
  188 + buf.writeUInt16LE(lmChallengeResponse.length, pos); pos += 2; // LmChallengeResponseLen
  189 + buf.writeUInt16LE(lmChallengeResponse.length, pos); pos += 2; // LmChallengeResponseMaxLen
  190 + buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length + usernameBytes.length + workstationBytes.length, pos); pos += 4; // LmChallengeResponseOffset
  191 +
  192 + buf.writeUInt16LE(ntChallengeResponse.length, pos); pos += 2; // NtChallengeResponseLen
  193 + buf.writeUInt16LE(ntChallengeResponse.length, pos); pos += 2; // NtChallengeResponseMaxLen
  194 + buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length + usernameBytes.length + workstationBytes.length + lmChallengeResponse.length, pos); pos += 4; // NtChallengeResponseOffset
  195 +
  196 + buf.writeUInt16LE(domainNameBytes.length, pos); pos += 2; // DomainNameLen
  197 + buf.writeUInt16LE(domainNameBytes.length, pos); pos += 2; // DomainNameMaxLen
  198 + buf.writeUInt32LE(BODY_LENGTH, pos); pos += 4; // DomainNameOffset
  199 +
  200 + buf.writeUInt16LE(usernameBytes.length, pos); pos += 2; // UserNameLen
  201 + buf.writeUInt16LE(usernameBytes.length, pos); pos += 2; // UserNameMaxLen
  202 + buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length, pos); pos += 4; // UserNameOffset
  203 +
  204 + buf.writeUInt16LE(workstationBytes.length, pos); pos += 2; // WorkstationLen
  205 + buf.writeUInt16LE(workstationBytes.length, pos); pos += 2; // WorkstationMaxLen
  206 + buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length + usernameBytes.length, pos); pos += 4; // WorkstationOffset
  207 +
  208 + buf.writeUInt16LE(encryptedRandomSessionKeyBytes.length, pos); pos += 2; // EncryptedRandomSessionKeyLen
  209 + buf.writeUInt16LE(encryptedRandomSessionKeyBytes.length, pos); pos += 2; // EncryptedRandomSessionKeyMaxLen
  210 + buf.writeUInt32LE(BODY_LENGTH + domainNameBytes.length + usernameBytes.length + workstationBytes.length + lmChallengeResponse.length + ntChallengeResponse.length, pos); pos += 4; // EncryptedRandomSessionKeyOffset
  211 +
  212 + buf.writeUInt32LE(typeflags.NTLM_TYPE2_FLAGS, pos); pos += 4; // NegotiateFlags
  213 +
  214 + buf.writeUInt8(5, pos); pos++; // ProductMajorVersion
  215 + buf.writeUInt8(1, pos); pos++; // ProductMinorVersion
  216 + buf.writeUInt16LE(2600, pos); pos += 2; // ProductBuild
  217 + buf.writeUInt8(0, pos); pos++; // VersionReserved1
  218 + buf.writeUInt8(0, pos); pos++; // VersionReserved2
  219 + buf.writeUInt8(0, pos); pos++; // VersionReserved3
  220 + buf.writeUInt8(15, pos); pos++; // NTLMRevisionCurrent
  221 +
  222 + domainNameBytes.copy(buf, pos); pos += domainNameBytes.length;
  223 + usernameBytes.copy(buf, pos); pos += usernameBytes.length;
  224 + workstationBytes.copy(buf, pos); pos += workstationBytes.length;
  225 + lmChallengeResponse.copy(buf, pos); pos += lmChallengeResponse.length;
  226 + ntChallengeResponse.copy(buf, pos); pos += ntChallengeResponse.length;
  227 + encryptedRandomSessionKeyBytes.copy(buf, pos); pos += encryptedRandomSessionKeyBytes.length;
  228 +
  229 + return 'NTLM ' + buf.toString('base64');
  230 +}
  231 +
  232 +function create_LM_hashed_password_v1(password){
  233 + // fix the password length to 14 bytes
  234 + password = password.toUpperCase();
  235 + var passwordBytes = new Buffer(password, 'ascii');
  236 +
  237 + var passwordBytesPadded = new Buffer(14);
  238 + passwordBytesPadded.fill("\0");
  239 + var sourceEnd = 14;
  240 + if(passwordBytes.length < 14) sourceEnd = passwordBytes.length;
  241 + passwordBytes.copy(passwordBytesPadded, 0, 0, sourceEnd);
  242 +
  243 + // split into 2 parts of 7 bytes:
  244 + var firstPart = passwordBytesPadded.slice(0,7);
  245 + var secondPart = passwordBytesPadded.slice(7);
  246 +
  247 + function encrypt(buf){
  248 + var key = insertZerosEvery7Bits(buf);
  249 + var des = crypto.createCipheriv('DES-ECB', key, '');
  250 + return des.update("KGS!@#$%"); // page 57 in [MS-NLMP]);
  251 + }
  252 +
  253 + var firstPartEncrypted = encrypt(firstPart);
  254 + var secondPartEncrypted = encrypt(secondPart);
  255 +
  256 + return Buffer.concat([firstPartEncrypted, secondPartEncrypted]);
  257 +}
  258 +
  259 +function insertZerosEvery7Bits(buf){
  260 + var binaryArray = bytes2binaryArray(buf);
  261 + var newBinaryArray = [];
  262 + for(var i=0; i<binaryArray.length; i++){
  263 + newBinaryArray.push(binaryArray[i]);
  264 +
  265 + if((i+1)%7 === 0){
  266 + newBinaryArray.push(0);
  267 + }
  268 + }
  269 + return binaryArray2bytes(newBinaryArray);
  270 +}
  271 +
  272 +function bytes2binaryArray(buf){
  273 + var hex2binary = {
  274 + 0: [0,0,0,0],
  275 + 1: [0,0,0,1],
  276 + 2: [0,0,1,0],
  277 + 3: [0,0,1,1],
  278 + 4: [0,1,0,0],
  279 + 5: [0,1,0,1],
  280 + 6: [0,1,1,0],
  281 + 7: [0,1,1,1],
  282 + 8: [1,0,0,0],
  283 + 9: [1,0,0,1],
  284 + A: [1,0,1,0],
  285 + B: [1,0,1,1],
  286 + C: [1,1,0,0],
  287 + D: [1,1,0,1],
  288 + E: [1,1,1,0],
  289 + F: [1,1,1,1]
  290 + };
  291 +
  292 + var hexString = buf.toString('hex').toUpperCase();
  293 + var array = [];
  294 + for(var i=0; i<hexString.length; i++){
  295 + var hexchar = hexString.charAt(i);
  296 + array = array.concat(hex2binary[hexchar]);
  297 + }
  298 + return array;
  299 +}
  300 +
  301 +function binaryArray2bytes(array){
  302 + var binary2hex = {
  303 + '0000': 0,
  304 + '0001': 1,
  305 + '0010': 2,
  306 + '0011': 3,
  307 + '0100': 4,
  308 + '0101': 5,
  309 + '0110': 6,
  310 + '0111': 7,
  311 + '1000': 8,
  312 + '1001': 9,
  313 + '1010': 'A',
  314 + '1011': 'B',
  315 + '1100': 'C',
  316 + '1101': 'D',
  317 + '1110': 'E',
  318 + '1111': 'F'
  319 + };
  320 +
  321 + var bufArray = [];
  322 +
  323 + for(var i=0; i<array.length; i +=8 ){
  324 + if((i+7) > array.length)
  325 + break;
  326 +
  327 + var binString1 = '' + array[i] + '' + array[i+1] + '' + array[i+2] + '' + array[i+3];
  328 + var binString2 = '' + array[i+4] + '' + array[i+5] + '' + array[i+6] + '' + array[i+7];
  329 + var hexchar1 = binary2hex[binString1];
  330 + var hexchar2 = binary2hex[binString2];
  331 +
  332 + var buf = new Buffer(hexchar1 + '' + hexchar2, 'hex');
  333 + bufArray.push(buf);
  334 + }
  335 +
  336 + return Buffer.concat(bufArray);
  337 +}
  338 +
  339 +function create_NT_hashed_password_v1(password){
  340 + var buf = new Buffer(password, 'utf16le');
  341 + var md4 = crypto.createHash('md4');
  342 + md4.update(buf);
  343 + return new Buffer(md4.digest());
  344 +}
  345 +
  346 +function calc_resp(password_hash, server_challenge){
  347 + // padding with zeros to make the hash 21 bytes long
  348 + var passHashPadded = new Buffer(21);
  349 + passHashPadded.fill("\0");
  350 + password_hash.copy(passHashPadded, 0, 0, password_hash.length);
  351 +
  352 + var resArray = [];
  353 +
  354 + var des = crypto.createCipheriv('DES-ECB', insertZerosEvery7Bits(passHashPadded.slice(0,7)), '');
  355 + resArray.push( des.update(server_challenge.slice(0,8)) );
  356 +
  357 + des = crypto.createCipheriv('DES-ECB', insertZerosEvery7Bits(passHashPadded.slice(7,14)), '');
  358 + resArray.push( des.update(server_challenge.slice(0,8)) );
  359 +
  360 + des = crypto.createCipheriv('DES-ECB', insertZerosEvery7Bits(passHashPadded.slice(14,21)), '');
  361 + resArray.push( des.update(server_challenge.slice(0,8)) );
  362 +
  363 + return Buffer.concat(resArray);
  364 +}
  365 +
  366 +function ntlm2sr_calc_resp(responseKeyNT, serverChallenge, clientChallenge){
  367 + // padding with zeros to make the hash 16 bytes longer
  368 + var lmChallengeResponse = new Buffer(clientChallenge.length + 16);
  369 + lmChallengeResponse.fill("\0");
  370 + clientChallenge.copy(lmChallengeResponse, 0, 0, clientChallenge.length);
  371 +
  372 + var buf = Buffer.concat([serverChallenge, clientChallenge]);
  373 + var md5 = crypto.createHash('md5');
  374 + md5.update(buf);
  375 + var sess = md5.digest();
  376 + var ntChallengeResponse = calc_resp(responseKeyNT, sess.slice(0,8));
  377 +
  378 + return {
  379 + lmChallengeResponse: lmChallengeResponse,
  380 + ntChallengeResponse: ntChallengeResponse
  381 + };
  382 +}
  383 +
  384 +exports.createType1Message = createType1Message;
  385 +exports.parseType2Message = parseType2Message;
  386 +exports.createType3Message = createType3Message;
  387 +
  388 +
  389 +
  390 +
  1 +{
  2 + "_args": [
  3 + [
  4 + {
  5 + "raw": "httpntlm@1.6.1",
  6 + "scope": null,
  7 + "escapedName": "httpntlm",
  8 + "name": "httpntlm",
  9 + "rawSpec": "1.6.1",
  10 + "spec": "1.6.1",
  11 + "type": "version"
  12 + },
  13 + "/Users/fzy/project/koa2_Sequelize_project/node_modules/smtp-connection"
  14 + ]
  15 + ],
  16 + "_from": "httpntlm@1.6.1",
  17 + "_id": "httpntlm@1.6.1",
  18 + "_inCache": true,
  19 + "_location": "/httpntlm",
  20 + "_nodeVersion": "0.12.2",
  21 + "_npmOperationalInternal": {
  22 + "host": "packages-16-east.internal.npmjs.com",
  23 + "tmp": "tmp/httpntlm-1.6.1.tgz_1462189866942_0.9128970899619162"
  24 + },
  25 + "_npmUser": {
  26 + "name": "samdecrock",
  27 + "email": "sam.decrock@gmail.com"
  28 + },
  29 + "_npmVersion": "2.7.4",
  30 + "_phantomChildren": {},
  31 + "_requested": {
  32 + "raw": "httpntlm@1.6.1",
  33 + "scope": null,
  34 + "escapedName": "httpntlm",
  35 + "name": "httpntlm",
  36 + "rawSpec": "1.6.1",
  37 + "spec": "1.6.1",
  38 + "type": "version"
  39 + },
  40 + "_requiredBy": [
  41 + "/smtp-connection"
  42 + ],
  43 + "_resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz",
  44 + "_shasum": "ad01527143a2e8773cfae6a96f58656bb52a34b2",
  45 + "_shrinkwrap": null,
  46 + "_spec": "httpntlm@1.6.1",
  47 + "_where": "/Users/fzy/project/koa2_Sequelize_project/node_modules/smtp-connection",
  48 + "author": {
  49 + "name": "Sam Decrock",
  50 + "url": "https://github.com/SamDecrock/"
  51 + },
  52 + "bugs": {
  53 + "url": "https://github.com/SamDecrock/node-http-ntlm/issues"
  54 + },
  55 + "dependencies": {
  56 + "httpreq": ">=0.4.22",
  57 + "underscore": "~1.7.0"
  58 + },
  59 + "description": "httpntlm is a Node.js library to do HTTP NTLM authentication",
  60 + "devDependencies": {},
  61 + "directories": {},
  62 + "dist": {
  63 + "shasum": "ad01527143a2e8773cfae6a96f58656bb52a34b2",
  64 + "tarball": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz"
  65 + },
  66 + "engines": {
  67 + "node": ">=0.8.0"
  68 + },
  69 + "gitHead": "c5cecb5a94ef1fd33d5234aec3fad8c59b920eb2",
  70 + "homepage": "https://github.com/SamDecrock/node-http-ntlm#readme",
  71 + "licenses": [
  72 + {
  73 + "type": "MIT",
  74 + "url": "http://www.opensource.org/licenses/mit-license.php"
  75 + }
  76 + ],
  77 + "main": "./httpntlm",
  78 + "maintainers": [
  79 + {
  80 + "name": "samdecrock",
  81 + "email": "sam.decrock@gmail.com"
  82 + }
  83 + ],
  84 + "name": "httpntlm",
  85 + "optionalDependencies": {},
  86 + "readme": "# httpntlm\n\n__httpntlm__ is a Node.js library to do HTTP NTLM authentication\n\nIt's a port from the Python libary [python-ntml](https://code.google.com/p/python-ntlm/)\n\n## Install\n\nYou can install __httpntlm__ using the Node Package Manager (npm):\n\n npm install httpntlm\n\n## How to use\n\n```js\nvar httpntlm = require('httpntlm');\n\nhttpntlm.get({\n url: \"https://someurl.com\",\n username: 'm$',\n password: 'stinks',\n workstation: 'choose.something',\n domain: ''\n}, function (err, res){\n if(err) return err;\n\n console.log(res.headers);\n console.log(res.body);\n});\n```\n\nIt supports __http__ and __https__.\n\n## Options\n\n- `url:` _{String}_ URL to connect. (Required)\n- `username:` _{String}_ Username. (Required)\n- `password:` _{String}_ Password. (Required)\n- `workstation:` _{String}_ Name of workstation or `''`.\n- `domain:` _{String}_ Name of domain or `''`.\n\nYou can also pass along all other options of [httpreq](https://github.com/SamDecrock/node-httpreq), including custom headers, cookies, body data, ... and use POST, PUT or DELETE instead of GET.\n\n## Advanced\n\nIf you want to use the NTLM-functions yourself, you can access the ntlm-library like this (https example):\n\n```js\nvar ntlm = require('httpntlm').ntlm;\nvar async = require('async');\nvar httpreq = require('httpreq');\nvar HttpsAgent = require('agentkeepalive').HttpsAgent;\nvar keepaliveAgent = new HttpsAgent();\n\nvar options = {\n url: \"https://someurl.com\",\n username: 'm$',\n password: 'stinks',\n workstation: 'choose.something',\n domain: ''\n};\n\nasync.waterfall([\n function (callback){\n var type1msg = ntlm.createType1Message(options);\n\n httpreq.get(options.url, {\n headers:{\n 'Connection' : 'keep-alive',\n 'Authorization': type1msg\n },\n agent: keepaliveAgent\n }, callback);\n },\n\n function (res, callback){\n if(!res.headers['www-authenticate'])\n return callback(new Error('www-authenticate not found on response of second request'));\n\n var type2msg = ntlm.parseType2Message(res.headers['www-authenticate']);\n var type3msg = ntlm.createType3Message(type2msg, options);\n\n setImmediate(function() {\n httpreq.get(options.url, {\n headers:{\n 'Connection' : 'Close',\n 'Authorization': type3msg\n },\n allowRedirects: false,\n agent: keepaliveAgent\n }, callback);\n });\n }\n], function (err, res) {\n if(err) return console.log(err);\n\n console.log(res.headers);\n console.log(res.body);\n});\n```\n\n## Download binary files\n\n```javascript\nhttpntlm.get({\n url: \"https://someurl.com/file.xls\",\n username: 'm$',\n password: 'stinks',\n workstation: 'choose.something',\n domain: '',\n binary: true\n}, function (err, response) {\n if(err) return console.log(err);\n fs.writeFile(\"file.xls\", response.body, function (err) {\n if(err) return console.log(\"error writing file\");\n console.log(\"file.xls saved!\");\n });\n});\n```\n\n## More information\n\n* [python-ntlm](https://code.google.com/p/python-ntlm/)\n* [NTLM Authentication Scheme for HTTP](http://www.innovation.ch/personal/ronald/ntlm.html)\n* [LM hash on Wikipedia](http://en.wikipedia.org/wiki/LM_hash)\n\n\n## License (MIT)\n\nCopyright (c) Sam Decrock <https://github.com/SamDecrock/>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n",
  87 + "readmeFilename": "README.md",
  88 + "repository": {
  89 + "type": "git",
  90 + "url": "git://github.com/SamDecrock/node-http-ntlm.git"
  91 + },
  92 + "scripts": {
  93 + "jshint": "jshint *.js"
  94 + },
  95 + "version": "1.6.1"
  96 +}
  1 +{
  2 + "ecmaFeatures": {
  3 + "modules": true
  4 + },
  5 +
  6 + "env": {
  7 + "node": true
  8 + },
  9 +
  10 + "rules": {
  11 + "array-bracket-spacing": [2, "never"],
  12 + "brace-style": [2, "1tbs", {
  13 + "allowSingleLine": true
  14 + }],
  15 + "camelcase": [2, {
  16 + "properties": "never"
  17 + }],
  18 + "comma-spacing": [2, {
  19 + "before": false,
  20 + "after": true
  21 + }],
  22 + "comma-style": [2, "last"],
  23 + "comma-dangle": [2, "never"],
  24 + "complexity": [1, 8],
  25 + "computed-property-spacing": [2, "never"],
  26 + "consistent-return": 1,
  27 + "curly": [2, "all"],
  28 + "default-case": 2,
  29 + "dot-notation": [1, {
  30 + "allowKeywords": true
  31 + }],
  32 + "dot-location": [2, "property"],
  33 + "eol-last": 2,
  34 + "eqeqeq": 2,
  35 + "func-style": 0,
  36 + "guard-for-in": 0,
  37 + "handle-callback-err": [2, "^(e|er|err|error)[0-9]{1,2}?$"],
  38 + "indent": [2, 2, {
  39 + "SwitchCase": 1
  40 + }],
  41 + "keyword-spacing": 2,
  42 + "key-spacing": [2, {
  43 + "beforeColon": false,
  44 + "afterColon": true
  45 + }],
  46 + "lines-around-comment": [2, {
  47 + "beforeBlockComment": true,
  48 + "afterBlockComment": true,
  49 + "beforeLineComment": true,
  50 + "afterLineComment": false,
  51 + "allowBlockStart": true,
  52 + "allowBlockEnd": false
  53 + }],
  54 + "linebreak-style": [2, "unix"],
  55 + "max-nested-callbacks": [1, 3],
  56 + "new-cap": 0,
  57 + "newline-after-var": [2, "always"],
  58 + "no-alert": 2,
  59 + "no-caller": 2,
  60 + "no-catch-shadow": 2,
  61 + "no-delete-var": 2,
  62 + "no-div-regex": 2,
  63 + "no-duplicate-case": 2,
  64 + "no-else-return": 2,
  65 + "no-empty": 2,
  66 + "no-empty-character-class": 2,
  67 + "no-eval": 2,
  68 + "no-extend-native": 2,
  69 + "no-extra-semi": 2,
  70 + "no-fallthrough": 2,
  71 + "no-floating-decimal": 2,
  72 + "no-func-assign": 2,
  73 + "no-implied-eval": 2,
  74 + "no-inline-comments": 1,
  75 + "no-invalid-regexp": 2,
  76 + "no-label-var": 2,
  77 + "no-labels": 2,
  78 + "no-lone-blocks": 2,
  79 + "no-lonely-if": 2,
  80 + "no-mixed-requires": 0,
  81 + "no-mixed-spaces-and-tabs": 2,
  82 + "no-multi-spaces": 2,
  83 + "no-multi-str": 2,
  84 + "no-multiple-empty-lines": [2, {
  85 + "max": 2
  86 + }],
  87 + "no-native-reassign": 2,
  88 + "no-nested-ternary": 2,
  89 + "no-new-func": 2,
  90 + "no-new-object": 2,
  91 + "no-new-wrappers": 2,
  92 + "no-octal-escape": 2,
  93 + "no-octal": 2,
  94 + "no-path-concat": 2,
  95 + "no-param-reassign": 0,
  96 + "no-process-env": 0,
  97 + "no-proto": 2,
  98 + "no-redeclare": 2,
  99 + "no-reserved-keys": 0,
  100 + "no-return-assign": [2, "always"],
  101 + "no-self-compare": 2,
  102 + "no-sequences": 2,
  103 + "no-shadow": 2,
  104 + "no-shadow-restricted-names": 2,
  105 + "no-spaced-func": 0,
  106 + "no-sparse-arrays": 1,
  107 + "no-sync": 1,
  108 + "no-ternary": 0,
  109 + "no-throw-literal": 2,
  110 + "no-trailing-spaces": 2,
  111 + "no-undef": 2,
  112 + "no-undef-init": 2,
  113 + "no-undefined": 1,
  114 + "no-underscore-dangle": 2,
  115 + "no-unexpected-multiline": 2,
  116 + "no-unneeded-ternary": 2,
  117 + "no-unreachable": 2,
  118 + "no-unused-vars": 1,
  119 + "no-use-before-define": 2,
  120 + "no-useless-concat": 2,
  121 + "no-warning-comments": 1,
  122 + "no-with": 2,
  123 + "no-wrap-func": 0,
  124 + "object-curly-spacing": [2, "always", {
  125 + "objectsInObjects": false,
  126 + "arraysInObjects": false
  127 + }],
  128 + "one-var": [2, "never"],
  129 + "operator-assignment": [2, "always"],
  130 + "operator-linebreak": [2, "before"],
  131 + "padded-blocks": [2, "never"],
  132 + "quote-props": [2, "consistent"],
  133 + "quotes": [2, "single", "avoid-escape"],
  134 + "radix": 2,
  135 + "semi": 2,
  136 + "semi-spacing": [2, {
  137 + "before": false,
  138 + "after": true
  139 + }],
  140 + "space-before-blocks": [2, "always"],
  141 + "space-before-function-paren": [2, "always"],
  142 + "space-in-parens": [2, "never"],
  143 + "space-infix-ops": 2,
  144 + "space-unary-ops": [2, {
  145 + "words": true,
  146 + "nonwords": false
  147 + }],
  148 + "spaced-comment": [2, "always"],
  149 + "use-isnan": 2,
  150 + "valid-typeof": 2,
  151 + "vars-on-top": 2,
  152 + "wrap-regex": 0,
  153 + "yoda": [2, "never"]
  154 + }
  155 +}
  1 +lib-cov
  2 +*.seed
  3 +*.log
  4 +*.csv
  5 +*.dat
  6 +*.out
  7 +*.pid
  8 +*.gz
  9 +
  10 +pids
  11 +logs
  12 +results
  13 +
  14 +node_modules
  15 +
  16 +npm-debug.log
  17 +.DS_Store
  1 +Copyright (c) 2017 Sam Decrock <sam.decrock@gmail.com>
  2 +
  3 +MIT License
  4 +
  5 +Permission is hereby granted, free of charge, to any person obtaining a copy
  6 +of this software and associated documentation files (the "Software"), to deal
  7 +in the Software without restriction, including without limitation the rights
  8 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 +copies of the Software, and to permit persons to whom the Software is
  10 +furnished to do so, subject to the following conditions:
  11 +The above copyright notice and this permission notice shall be included in all
  12 +copies or substantial portions of the Software.
  13 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19 +SOFTWARE.
  1 +node-httpreq
  2 +============
  3 +
  4 +node-httpreq is a node.js library to do HTTP(S) requests the easy way
  5 +
  6 +Do GET, POST, PUT, PATCH, DELETE, OPTIONS, upload files, use cookies, change headers, ...
  7 +
  8 +## Install
  9 +
  10 +You can install __httpreq__ using the Node Package Manager (npm):
  11 +
  12 + npm install httpreq
  13 +
  14 +## Simple example
  15 +```js
  16 +var httpreq = require('httpreq');
  17 +
  18 +httpreq.get('http://www.google.com', function (err, res){
  19 + if (err) return console.log(err);
  20 +
  21 + console.log(res.statusCode);
  22 + console.log(res.headers);
  23 + console.log(res.body);
  24 + console.log(res.cookies);
  25 +});
  26 +```
  27 +
  28 +## How to use
  29 +
  30 +* [httpreq.get(url, [options], callback)](#get)
  31 +* [httpreq.post(url, [options], callback)](#post)
  32 +* [httpreq.put(url, [options], callback)](#put)
  33 +* [httpreq.delete(url, [options], callback)](#delete)
  34 +* [httpreq.options(url, [options], callback)](#options)
  35 +* [Uploading files](#upload)
  36 +* [Downloading a binary file](#binary)
  37 +* [Downloading a file directly to disk](#download)
  38 +* [Sending a custom body](#custombody)
  39 +* [Using a http(s) proxy](#proxy)
  40 +* [httpreq.doRequest(options, callback)](#dorequest)
  41 +
  42 +---------------------------------------
  43 +### httpreq.get(url, [options], callback)
  44 +<a name="get"></a>
  45 +
  46 +__Arguments__
  47 + - url: The url to connect to. Can be http or https.
  48 + - options: (all are optional) The following options can be passed:
  49 + - parameters: an object of query parameters
  50 + - headers: an object of headers
  51 + - cookies: an array of cookies
  52 + - auth: a string for basic authentication. For example `username:password`
  53 + - binary: true/false (default: false), if true, res.body will a buffer containing the binary data
  54 + - allowRedirects: (default: __true__ , only with httpreq.get() ), if true, redirects will be followed
  55 + - maxRedirects: (default: __10__ ). For example 1 redirect will allow for one normal request and 1 extra redirected request.
  56 + - timeout: (default: __none__ ). Adds a timeout to the http(s) request. Should be in milliseconds.
  57 + - proxy, if you want to pass your request through a http(s) proxy server:
  58 + - host: eg: "192.168.0.1"
  59 + - port: eg: 8888
  60 + - protocol: (default: __'http'__ ) can be 'http' or 'https'
  61 + - rejectUnauthorized: validate certificate for request with HTTPS. [More here](http://nodejs.org/api/https.html#https_https_request_options_callback)
  62 + - callback(err, res): A callback function which is called when the request is complete. __res__ contains the headers ( __res.headers__ ), the http status code ( __res.statusCode__ ) and the body ( __res.body__ )
  63 +
  64 +__Example without options__
  65 +
  66 +```js
  67 +var httpreq = require('httpreq');
  68 +
  69 +httpreq.get('http://www.google.com', function (err, res){
  70 + if (err) return console.log(err);
  71 +
  72 + console.log(res.statusCode);
  73 + console.log(res.headers);
  74 + console.log(res.body);
  75 +});
  76 +```
  77 +
  78 +__Example with options__
  79 +
  80 +```js
  81 +var httpreq = require('httpreq');
  82 +
  83 +httpreq.get('http://posttestserver.com/post.php', {
  84 + parameters: {
  85 + name: 'John',
  86 + lastname: 'Doe'
  87 + },
  88 + headers:{
  89 + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/20100101 Firefox/18.0'
  90 + },
  91 + cookies: [
  92 + 'token=DGcGUmplWQSjfqEvmu%2BZA%2Fc',
  93 + 'id=2'
  94 + ]
  95 +}, function (err, res){
  96 + if (err){
  97 + console.log(err);
  98 + }else{
  99 + console.log(res.body);
  100 + }
  101 +});
  102 +```
  103 +---------------------------------------
  104 +### httpreq.post(url, [options], callback)
  105 +<a name="post"></a>
  106 +
  107 +__Arguments__
  108 + - url: The url to connect to. Can be http or https.
  109 + - options: (all are optional) The following options can be passed:
  110 + - parameters: an object of post parameters (content-type is set to *application/x-www-form-urlencoded; charset=UTF-8*)
  111 + - json: if you want to send json directly (content-type is set to *application/json*)
  112 + - files: an object of files to upload (content-type is set to *multipart/form-data; boundary=xxx*)
  113 + - body: custom body content you want to send. If used, previous options will be ignored and your custom body will be sent. (content-type will not be set)
  114 + - headers: an object of headers
  115 + - cookies: an array of cookies
  116 + - auth: a string for basic authentication. For example `username:password`
  117 + - binary: true/false (default: __false__ ), if true, res.body will be a buffer containing the binary data
  118 + - allowRedirects: (default: __false__ ), if true, redirects will be followed
  119 + - maxRedirects: (default: __10__ ). For example 1 redirect will allow for one normal request and 1 extra redirected request.
  120 + - encodePostParameters: (default: __true__ ), if true, POST/PUT parameters names will be URL encoded.
  121 + - timeout: (default: none). Adds a timeout to the http(s) request. Should be in milliseconds.
  122 + - proxy, if you want to pass your request through a http(s) proxy server:
  123 + - host: eg: "192.168.0.1"
  124 + - port: eg: 8888
  125 + - protocol: (default: __'http'__ ) can be 'http' or 'https'
  126 + - rejectUnauthorized: validate certificate for request with HTTPS. [More here](http://nodejs.org/api/https.html#https_https_request_options_callback)
  127 + - callback(err, res): A callback function which is called when the request is complete. __res__ contains the headers ( __res.headers__ ), the http status code ( __res.statusCode__ ) and the body ( __res.body__ )
  128 +
  129 +__Example without extra options__
  130 +
  131 +```js
  132 +var httpreq = require('httpreq');
  133 +
  134 +httpreq.post('http://posttestserver.com/post.php', {
  135 + parameters: {
  136 + name: 'John',
  137 + lastname: 'Doe'
  138 + }
  139 +}, function (err, res){
  140 + if (err){
  141 + console.log(err);
  142 + }else{
  143 + console.log(res.body);
  144 + }
  145 +});
  146 +```
  147 +
  148 +__Example with options__
  149 +
  150 +```js
  151 +var httpreq = require('httpreq');
  152 +
  153 +httpreq.post('http://posttestserver.com/post.php', {
  154 + parameters: {
  155 + name: 'John',
  156 + lastname: 'Doe'
  157 + },
  158 + headers:{
  159 + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/20100101 Firefox/18.0'
  160 + },
  161 + cookies: [
  162 + 'token=DGcGUmplWQSjfqEvmu%2BZA%2Fc',
  163 + 'id=2'
  164 + ]
  165 +}, function (err, res){
  166 + if (err){
  167 + console.log(err);
  168 + }else{
  169 + console.log(res.body);
  170 + }
  171 +});
  172 +```
  173 +
  174 +---------------------------------------
  175 +### httpreq.put(url, [options], callback)
  176 +<a name="put"></a>
  177 +
  178 +Same options as [httpreq.post(url, [options], callback)](#post)
  179 +
  180 +---------------------------------------
  181 +<a name="delete" />
  182 +### httpreq.delete(url, [options], callback)
  183 +
  184 +Same options as [httpreq.post(url, [options], callback)](#post)
  185 +
  186 +---------------------------------------
  187 +<a name="options" />
  188 +### httpreq.options(url, [options], callback)
  189 +
  190 +Same options as [httpreq.get(url, [options], callback)](#get) except for the ability to follow redirects.
  191 +
  192 +---------------------------------------
  193 +<a name="upload" />
  194 +### Uploading files
  195 +
  196 +You can still use ```httpreq.uploadFiles({url: 'url', files: {}}, callback)```, but it's easier to just use POST (or PUT):
  197 +
  198 +__Example__
  199 +
  200 +```js
  201 +var httpreq = require('httpreq');
  202 +
  203 +httpreq.post('http://posttestserver.com/upload.php', {
  204 + parameters: {
  205 + name: 'John',
  206 + lastname: 'Doe'
  207 + },
  208 + files:{
  209 + myfile: __dirname + "/testupload.jpg",
  210 + myotherfile: __dirname + "/testupload.jpg"
  211 + }
  212 +}, function (err, res){
  213 + if (err) throw err;
  214 +});
  215 +```
  216 +
  217 +---------------------------------------
  218 +<a name="binary"></a>
  219 +### Downloading a binary file
  220 +To download a binary file, just add __binary: true__ to the options when doing a get or a post.
  221 +
  222 +__Example__
  223 +
  224 +```js
  225 +var httpreq = require('httpreq');
  226 +
  227 +httpreq.get('https://ssl.gstatic.com/gb/images/k1_a31af7ac.png', {binary: true}, function (err, res){
  228 + if (err){
  229 + console.log(err);
  230 + }else{
  231 + fs.writeFile(__dirname + '/test.png', res.body, function (err) {
  232 + if(err)
  233 + console.log("error writing file");
  234 + });
  235 + }
  236 +});
  237 +```
  238 +
  239 +---------------------------------------
  240 +<a name="download"></a>
  241 +### Downloading a file directly to disk
  242 +To download a file directly to disk, use the download method provided.
  243 +
  244 +Downloading is done using a stream, so the data is not stored in memory and directly saved to file.
  245 +
  246 +__Example__
  247 +
  248 +```js
  249 +var httpreq = require('httpreq');
  250 +
  251 +httpreq.download(
  252 + 'https://ssl.gstatic.com/gb/images/k1_a31af7ac.png',
  253 + __dirname + '/test.png'
  254 +, function (err, progress){
  255 + if (err) return console.log(err);
  256 + console.log(progress);
  257 +}, function (err, res){
  258 + if (err) return console.log(err);
  259 + console.log(res);
  260 +});
  261 +
  262 +```
  263 +---------------------------------------
  264 +<a name="custombody"></a>
  265 +### Sending a custom body
  266 +Use the body option to send a custom body (eg. an xml post)
  267 +
  268 +__Example__
  269 +
  270 +```js
  271 +var httpreq = require('httpreq');
  272 +
  273 +httpreq.post('http://posttestserver.com/post.php',{
  274 + body: '<?xml version="1.0" encoding="UTF-8"?>',
  275 + headers:{
  276 + 'Content-Type': 'text/xml',
  277 + }},
  278 + function (err, res) {
  279 + if (err){
  280 + console.log(err);
  281 + }else{
  282 + console.log(res.body);
  283 + }
  284 + }
  285 +);
  286 +```
  287 +
  288 +---------------------------------------
  289 +<a name="proxy"></a>
  290 +### Using a http(s) proxy
  291 +
  292 +__Example__
  293 +
  294 +```js
  295 +var httpreq = require('httpreq');
  296 +
  297 +httpreq.post('http://posttestserver.com/post.php', {
  298 + proxy: {
  299 + host: '10.100.0.126',
  300 + port: 8888
  301 + }
  302 +}, function (err, res){
  303 + if (err){
  304 + console.log(err);
  305 + }else{
  306 + console.log(res.body);
  307 + }
  308 +});
  309 +```
  310 +
  311 +---------------------------------------
  312 +### httpreq.doRequest(options, callback)
  313 +<a name="dorequest"></a>
  314 +
  315 +httpreq.doRequest is internally used by httpreq.get() and httpreq.post(). You can use this directly. Everything is stays the same as httpreq.get() or httpreq.post() except that the following options MUST be passed:
  316 +- url: the url to post the files to
  317 +- method: 'GET', 'POST', 'PUT' or 'DELETE'
  318 +
  319 +## Donate
  320 +
  321 +If you like this module or you want me to update it faster, feel free to donate. It helps increasing my dedication to fixing bugs :-)
  322 +
  323 +[![](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AB3R2SUL53K7S)
  324 +
  325 +
  1 +###### Contributors
  2 +[Sam](https://github.com/SamDecrock)
  3 +<font color="#999">63 Commits</font> / <font color="#6cc644">2309++</font> / <font color="#bd3c00"> 1040--</font>
  4 +<font color="#dedede">81.82%&nbsp;<font color="#dedede">||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</font><font color="#f4f4f4">||||||||||||||||||||||||||||||||</font><br><br>
  5 +[Franklin van de Meent](https://github.com/fvdm)
  6 +<font color="#999">8 Commits</font> / <font color="#6cc644">51++</font> / <font color="#bd3c00"> 16--</font>
  7 +<font color="#dedede">10.39%&nbsp;<font color="#dedede">||||||||||||||||||</font><font color="#f4f4f4">||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</font><br><br>
  8 +[Russell Beattie](https://github.com/russellbeattie)
  9 +<font color="#999">1 Commits</font> / <font color="#6cc644">55++</font> / <font color="#bd3c00"> 3--</font>
  10 +<font color="#dedede">01.30%&nbsp;<font color="#dedede">||</font><font color="#f4f4f4">||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</font><br><br>
  11 +[Jason Prickett MSFT](https://github.com/jpricketMSFT)
  12 +<font color="#999">1 Commits</font> / <font color="#6cc644">5++</font> / <font color="#bd3c00"> 0--</font>
  13 +<font color="#dedede">01.30%&nbsp;<font color="#dedede">||</font><font color="#f4f4f4">||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</font><br><br>
  14 +[null](https://github.com/jjharriso)
  15 +<font color="#999">1 Commits</font> / <font color="#6cc644">12++</font> / <font color="#bd3c00"> 0--</font>
  16 +<font color="#dedede">01.30%&nbsp;<font color="#dedede">||</font><font color="#f4f4f4">||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</font><br><br>
  17 +[MJJ](https://github.com/mjj2000)
  18 +<font color="#999">1 Commits</font> / <font color="#6cc644">11++</font> / <font color="#bd3c00"> 1--</font>
  19 +<font color="#dedede">01.30%&nbsp;<font color="#dedede">||</font><font color="#f4f4f4">||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</font><br><br>
  20 +[Jeff Young](https://github.com/jeffyoung)
  21 +<font color="#999">1 Commits</font> / <font color="#6cc644">19++</font> / <font color="#bd3c00"> 1--</font>
  22 +<font color="#dedede">01.30%&nbsp;<font color="#dedede">||</font><font color="#f4f4f4">||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</font><br><br>
  23 +[Dave Preston](https://github.com/davepreston)
  24 +<font color="#999">1 Commits</font> / <font color="#6cc644">5++</font> / <font color="#bd3c00"> 0--</font>
  25 +<font color="#dedede">01.30%&nbsp;<font color="#dedede">||</font><font color="#f4f4f4">||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||</font><br><br>
  26 +###### [Generated](https://github.com/jakeleboeuf/contributor) on Mon May 02 2016 11:08:45 GMT+0200 (CEST)
  1 +var httpreq = require('./lib/httpreq');
  2 +fs = require('fs')
  3 +
  4 +
  5 +// example1(); // get www.google.com
  6 +// example2(); // do some post
  7 +// example3(); // same as above + extra headers + cookies
  8 +// example4(); // https also works:
  9 +// example5(); // uploading some file:
  10 +// example6(); // u can use doRequest instead of .get or .post
  11 +// example7(); // download a binary file:
  12 +// example8(); // send json
  13 +// example9(); // send your own body content (eg. xml)
  14 +// example10(); // set max redirects:
  15 +// example11(); // set timeout
  16 +// example12(); // // download file directly to disk
  17 +
  18 +
  19 +// get www.google.com
  20 +function example1(){
  21 + httpreq.get('http://www.google.com', function (err, res){
  22 + if (err){
  23 + console.log(err);
  24 + }else{
  25 + console.log(res.headers); //headers are stored in res.headers
  26 + console.log(res.body); //content of the body is stored in res.body
  27 + }
  28 + });
  29 +}
  30 +
  31 +// do some post
  32 +function example2(){
  33 + httpreq.post('http://posttestserver.com/post.php', {
  34 + parameters: {
  35 + name: 'John',
  36 + lastname: 'Doe'
  37 + }
  38 + }, function (err, res){
  39 + if (err){
  40 + console.log(err);
  41 + }else{
  42 + console.log(res.body);
  43 + }
  44 + });
  45 +}
  46 +
  47 +// same as above + extra headers + cookies
  48 +function example3(){
  49 + httpreq.post('http://posttestserver.com/post.php', {
  50 + parameters: {
  51 + name: 'John',
  52 + lastname: 'Doe'
  53 + },
  54 + headers:{
  55 + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/20100101 Firefox/18.0'
  56 + },
  57 + cookies: [
  58 + 'token=DGcGUmplWQSjfqEvmu%2BZA%2Fc',
  59 + 'id=2'
  60 + ]
  61 + }, function (err, res){
  62 + if (err){
  63 + console.log(err);
  64 + }else{
  65 + console.log(res.body);
  66 + }
  67 + });
  68 +}
  69 +
  70 +// https also works:
  71 +function example4(){
  72 + httpreq.get('https://graph.facebook.com/19292868552', function (err, res){
  73 + if (err){
  74 + console.log(err);
  75 + }else{
  76 + console.log(JSON.parse(res.body));
  77 + }
  78 + });
  79 +}
  80 +
  81 +// uploading some file:
  82 +function example5(){
  83 + httpreq.uploadFiles({
  84 + url: "http://rekognition.com/demo/do_upload/",
  85 + parameters:{
  86 + name_space : 'something',
  87 + },
  88 + files:{
  89 + fileToUpload: __dirname + "/test/testupload.jpg"
  90 + }},
  91 + function (err, res){
  92 + if (err) return console.log(err);
  93 + console.log(res.body);
  94 + });
  95 +}
  96 +
  97 +// u can use doRequest instead of .get or .post
  98 +function example6(){
  99 + httpreq.doRequest({
  100 + url: 'https://graph.facebook.com/19292868552',
  101 + method: 'GET',
  102 + parameters: {
  103 + name: 'test'
  104 + }
  105 + },
  106 + function (err, res){
  107 + if (err){
  108 + console.log(err);
  109 + }else{
  110 + console.log(JSON.parse(res.body));
  111 + }
  112 + });
  113 +}
  114 +
  115 +// download a binary file:
  116 +function example7(){
  117 + httpreq.get('https://ssl.gstatic.com/gb/images/k1_a31af7ac.png', {
  118 + binary: true,
  119 + progressCallback: function (err, progress) {
  120 + console.log(progress);
  121 + }
  122 + },
  123 + function (err, res){
  124 + if (err){
  125 + console.log(err);
  126 + }else{
  127 + fs.writeFile(__dirname + '/test.png', res.body, function (err) {
  128 + if(err) return console.log("error writing file");
  129 + });
  130 + }
  131 + });
  132 +}
  133 +
  134 +// send json
  135 +function example8(){
  136 + httpreq.post('http://posttestserver.com/post.php',{
  137 + json: {name: 'John', lastname: 'Do'},
  138 + headers:{
  139 + 'Content-Type': 'text/xml',
  140 + }},
  141 + function (err, res) {
  142 + if (err){
  143 + console.log(err);
  144 + }else{
  145 + console.log(res.body);
  146 + }
  147 + }
  148 + );
  149 +}
  150 +
  151 +// send your own body content (eg. xml):
  152 +function example9(){
  153 + httpreq.post('http://posttestserver.com/post.php',{
  154 + body: '<?xml version="1.0" encoding="UTF-8"?>',
  155 + headers:{
  156 + 'Content-Type': 'text/xml',
  157 + }},
  158 + function (err, res) {
  159 + if (err){
  160 + console.log(err);
  161 + }else{
  162 + console.log(res.body);
  163 + }
  164 + }
  165 + );
  166 +}
  167 +
  168 +// set max redirects:
  169 +function example10(){
  170 + httpreq.get('http://scobleizer.com/feed/',{
  171 + maxRedirects: 2, // default is 10
  172 + headers:{
  173 + 'User-Agent': 'Magnet', //for some reason causes endless redirects on this site...
  174 + }},
  175 + function (err, res) {
  176 + if (err){
  177 + console.log(err);
  178 + }else{
  179 + console.log(res.body);
  180 + }
  181 + }
  182 + );
  183 +}
  184 +
  185 +// set timeout
  186 +function example11(){
  187 + httpreq.get('http://localhost:3000/',{
  188 + timeout: (5 * 1000) // timeout in milliseconds
  189 + },
  190 + function (err, res) {
  191 + if (err){
  192 + console.log(err);
  193 + }else{
  194 + console.log(res.body);
  195 + }
  196 + }
  197 + );
  198 +}
  199 +
  200 +// download file directly to disk:
  201 +function example12 () {
  202 + httpreq.download(
  203 + 'https://ssl.gstatic.com/gb/images/k1_a31af7ac.png',
  204 + __dirname + '/test.png'
  205 + , function (err, progress){
  206 + if (err) return console.log(err);
  207 + console.log(progress);
  208 + }, function (err, res){
  209 + if (err) return console.log(err);
  210 + console.log(res);
  211 + });
  212 +}
  213 +
  214 +
  1 +/*
  2 +Copyright (c) 2013 Sam Decrock <sam.decrock@gmail.com>
  3 +
  4 +MIT License
  5 +
  6 +Permission is hereby granted, free of charge, to any person obtaining a copy
  7 +of this software and associated documentation files (the "Software"), to deal
  8 +in the Software without restriction, including without limitation the rights
  9 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10 +copies of the Software, and to permit persons to whom the Software is
  11 +furnished to do so, subject to the following conditions:
  12 +
  13 +The above copyright notice and this permission notice shall be included in all
  14 +copies or substantial portions of the Software.
  15 +
  16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22 +SOFTWARE.
  23 +*/
  24 +
  25 +var querystring = require ('querystring');
  26 +var https = require ('https');
  27 +var http = require ('http');
  28 +var url = require ('url');
  29 +var fs = require ('fs');
  30 +
  31 +
  32 +/**
  33 + * Generate multipart boundary
  34 + *
  35 + * @returns {string}
  36 + */
  37 +
  38 +function generateBoundary () {
  39 + var boundary = '---------------------------';
  40 + var charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  41 +
  42 + for (var i = 0; i < 29; i++) {
  43 + boundary += charset.charAt (Math.floor (Math.random () * charset.length));
  44 + }
  45 +
  46 + return boundary;
  47 +}
  48 +
  49 +
  50 +/**
  51 + * Extract cookies from headers
  52 + *
  53 + * @param headers {object} - Response headers
  54 + * @returns {array} - Extracted cookie strings
  55 + */
  56 +
  57 +function extractCookies (headers) {
  58 + var rawcookies = headers['set-cookie'];
  59 +
  60 + if (!rawcookies) {
  61 + return [];
  62 + }
  63 +
  64 + if (rawcookies == []) {
  65 + return [];
  66 + }
  67 +
  68 + var cookies = [];
  69 + for (var i = 0; i < rawcookies.length; i++) {
  70 + var rawcookie = rawcookies[i].split (';');
  71 + if (rawcookie[0]) {
  72 + cookies.push (rawcookie[0]);
  73 + }
  74 + }
  75 + return cookies;
  76 +}
  77 +
  78 +
  79 +/**
  80 + * Custom HTTP request
  81 + *
  82 + * @callback callback
  83 + * @param o {object} - Request options
  84 + * @param callback [function] - Process response
  85 + * @returns {void}
  86 + */
  87 +
  88 +function doRequest (o, callback) {
  89 + if (!callback) {
  90 + callback = function (err) {}; // dummy function
  91 + }
  92 +
  93 + // prevent multiple callbacks
  94 + var finalCallbackDone = false;
  95 + function finalCallback (err, res) {
  96 + if (!finalCallbackDone) {
  97 + finalCallbackDone = true;
  98 + callback (err, res);
  99 + }
  100 + }
  101 +
  102 + if (o.maxRedirects === undefined) {
  103 + o.maxRedirects = 10;
  104 + }
  105 +
  106 + if (o.encodePostParameters === undefined) {
  107 + o.encodePostParameters = true;
  108 + }
  109 +
  110 + var chunks = [];
  111 + var body; // Buffer
  112 + var contentType;
  113 +
  114 + var port;
  115 + var host;
  116 + var path;
  117 + var isHttps = false;
  118 +
  119 + if (o.proxy) {
  120 + port = o.proxy.port;
  121 + host = o.proxy.host;
  122 + path = o.url; // complete url
  123 +
  124 + if (o.proxy.protocol && o.proxy.protocol.match (/https/)) {
  125 + isHttps = true;
  126 + }
  127 + } else {
  128 + var reqUrl = url.parse (o.url);
  129 + host = reqUrl.hostname;
  130 + path = reqUrl.path;
  131 +
  132 + if (reqUrl.protocol === 'https:') {
  133 + isHttps = true;
  134 + }
  135 +
  136 + if (reqUrl.port) {
  137 + port = reqUrl.port;
  138 + } else if (isHttps) {
  139 + port = 443;
  140 + } else {
  141 + port = 80;
  142 + }
  143 + }
  144 +
  145 + if (o.files && o.files.length > 0 && o.method === 'GET') {
  146 + var err = new Error ('Can\'t send files using GET');
  147 + err.code = 'CANT_SEND_FILES_USING_GET';
  148 + return finalCallback (err);
  149 + }
  150 +
  151 + if (o.parameters) {
  152 + if (o.method === 'GET') {
  153 + path += '?' + querystring.stringify (o.parameters);
  154 + } else {
  155 + body = new Buffer (querystring.stringify (o.parameters), 'utf8');
  156 + contentType = 'application/x-www-form-urlencoded; charset=UTF-8';
  157 + }
  158 + }
  159 +
  160 + if (o.json) {
  161 + body = new Buffer (JSON.stringify (o.json), 'utf8');
  162 + contentType = 'application/json';
  163 + }
  164 +
  165 + if (o.files) {
  166 + var crlf = '\r\n';
  167 + var boundary = generateBoundary ();
  168 + var separator = '--' + boundary;
  169 + var bodyArray = new Array (); // temporary body array
  170 +
  171 + // if the user wants to POST/PUT files, other parameters need to be encoded using 'Content-Disposition'
  172 + for (var key in o.parameters) {
  173 + // According to RFC 2388 (https://www.ietf.org/rfc/rfc2388.txt)
  174 + // "Field names originally in non-ASCII character sets MAY be encoded
  175 + // within the value of the "name" parameter using the standard method
  176 + // described in RFC 2047."
  177 + // -- encodePostParameters -- true by default and MAY be changed by the user
  178 + var headerKey = o.encodePostParameters ? encodeURIComponent (key) : key;
  179 + var encodedParameter = separator + crlf
  180 + + 'Content-Disposition: form-data; name="' + headerKey + '"' + crlf
  181 + + crlf
  182 + + o.parameters[key] + crlf;
  183 + bodyArray.push (new Buffer (encodedParameter));
  184 + }
  185 +
  186 + // now for the files:
  187 + var haveAlreadyAddedAFile = false;
  188 +
  189 + for (var file in o.files) {
  190 + var filepath = o.files[file];
  191 + var filename = filepath.replace (/\\/g, '/').replace (/.*\//, '');
  192 +
  193 + var encodedFile = separator + crlf
  194 + + 'Content-Disposition: form-data; name="' + file + '"; filename="' + filename + '"' + crlf
  195 + + 'Content-Type: application/octet-stream' + crlf
  196 + + crlf;
  197 +
  198 + // add crlf before separator if we have already added a file
  199 + if (haveAlreadyAddedAFile) {
  200 + encodedFile = crlf + encodedFile;
  201 + }
  202 +
  203 + bodyArray.push (new Buffer (encodedFile));
  204 +
  205 + // add binary file:
  206 + bodyArray.push (require ('fs').readFileSync (filepath));
  207 +
  208 + haveAlreadyAddedAFile = true;
  209 + }
  210 +
  211 + var footer = crlf + separator + '--' + crlf;
  212 + bodyArray.push (new Buffer (footer));
  213 +
  214 + // set body and contentType:
  215 + body = Buffer.concat (bodyArray);
  216 + contentType = 'multipart/form-data; boundary=' + boundary;
  217 + }
  218 +
  219 + // overwrites the body if the user passes a body:
  220 + // clears the content-type
  221 + if (o.body) {
  222 + body = new Buffer (o.body, 'utf8');
  223 + contentType = null;
  224 + }
  225 +
  226 +
  227 + var requestoptions = {
  228 + host: host,
  229 + port: port,
  230 + path: path,
  231 + method: o.method,
  232 + headers: {}
  233 + };
  234 +
  235 + if (!o.redirectCount) {
  236 + o.redirectCount = 0;
  237 + }
  238 +
  239 + if (body) {
  240 + requestoptions.headers['Content-Length'] = body.length;
  241 + }
  242 +
  243 + if (contentType) {
  244 + requestoptions.headers['Content-Type'] = contentType;
  245 + }
  246 +
  247 + if (o.cookies) {
  248 + requestoptions.headers.Cookie = o.cookies.join ('; ');
  249 + }
  250 +
  251 + if (o.rejectUnauthorized !== undefined && isHttps) {
  252 + requestoptions.rejectUnauthorized = o.rejectUnauthorized;
  253 + }
  254 +
  255 + if (isHttps && o.key) {
  256 + requestoptions.key = o.key;
  257 + }
  258 +
  259 + if (isHttps && o.cert) {
  260 + requestoptions.cert = o.cert;
  261 + }
  262 +
  263 + if (isHttps && o.secureProtocol) {
  264 + requestoptions.secureProtocol = o.secureProtocol;
  265 + }
  266 +
  267 + if (isHttps && o.ciphers) {
  268 + requestoptions.ciphers = o.ciphers;
  269 + }
  270 +
  271 + if (isHttps && o.passphrase) {
  272 + requestoptions.passphrase = o.passphrase;
  273 + }
  274 +
  275 + if (isHttps && o.pfx) {
  276 + requestoptions.pfx = o.pfx;
  277 + }
  278 +
  279 + if (isHttps && o.ca) {
  280 + requestoptions.ca = o.ca;
  281 + }
  282 +
  283 + // add custom headers:
  284 + if (o.headers) {
  285 + for (var headerkey in o.headers) {
  286 + requestoptions.headers[headerkey] = o.headers[headerkey];
  287 + }
  288 + }
  289 +
  290 + if (o.agent) {
  291 + requestoptions.agent = o.agent;
  292 + }
  293 +
  294 + if (o.auth) {
  295 + requestoptions.auth = o.auth;
  296 + }
  297 +
  298 + if (o.localAddress) {
  299 + requestoptions.localAddress = o.localAddress;
  300 + }
  301 +
  302 + if (o.secureOptions) {
  303 + requestoptions.secureOptions = o.secureOptions;
  304 + }
  305 +
  306 +
  307 + /**
  308 + * Process request response
  309 + *
  310 + * @param res {object} - Response details
  311 + * @returns {void}
  312 + */
  313 +
  314 + function requestResponse (res) {
  315 + var ended = false;
  316 + var currentsize = 0;
  317 +
  318 + var downloadstream = null;
  319 + if (o.downloadlocation) {
  320 + downloadstream = fs.createWriteStream (o.downloadlocation);
  321 + }
  322 +
  323 + res.on ('data', function (chunk) {
  324 + if (o.downloadlocation) {
  325 + downloadstream.write (chunk); //write it to disk, not to memory
  326 + } else {
  327 + chunks.push (chunk);
  328 + }
  329 +
  330 + if (o.progressCallback) {
  331 + var totalsize = res.headers['content-length'];
  332 + if (totalsize) {
  333 + currentsize += chunk.length;
  334 +
  335 + o.progressCallback (null, {
  336 + url: o.url,
  337 + totalsize: totalsize,
  338 + currentsize: currentsize,
  339 + percentage: currentsize * 100 / totalsize
  340 + });
  341 + } else {
  342 + o.progressCallback (new Error ('no content-length specified for file, so no progress monitoring possible'));
  343 + }
  344 + }
  345 + });
  346 +
  347 + res.on ('end', function (err) {
  348 + ended = true;
  349 +
  350 + // check for redirects
  351 + if (res.headers.location && o.allowRedirects) {
  352 + // Close any open file
  353 + if (o.downloadlocation) {
  354 + downloadstream.end ();
  355 + }
  356 +
  357 + if (o.redirectCount < o.maxRedirects) {
  358 + o.redirectCount++;
  359 + o.url = res.headers.location;
  360 + o.cookies = extractCookies (res.headers);
  361 + return doRequest (o, finalCallback);
  362 + } else {
  363 + var err = new Error ('Too many redirects (> ' + o.maxRedirects + ')');
  364 + err.code = 'TOOMANYREDIRECTS';
  365 + err.redirects = o.maxRedirects;
  366 + return finalCallback (err);
  367 + }
  368 + }
  369 +
  370 + if (!o.downloadlocation) {
  371 + var responsebody = Buffer.concat (chunks);
  372 + if (!o.binary) {
  373 + responsebody = responsebody.toString ('utf8');
  374 + }
  375 +
  376 + finalCallback (null, {
  377 + headers: res.headers,
  378 + statusCode: res.statusCode,
  379 + body: responsebody,
  380 + cookies: extractCookies (res.headers)
  381 + });
  382 + } else {
  383 + downloadstream.end (null, null, function () {
  384 + finalCallback (null, {
  385 + headers: res.headers,
  386 + statusCode: res.statusCode,
  387 + downloadlocation: o.downloadlocation,
  388 + cookies: extractCookies (res.headers)
  389 + });
  390 + });
  391 + }
  392 + });
  393 +
  394 + res.on ('close', function () {
  395 + if (!ended) {
  396 + finalCallback (new Error ('Request aborted'));
  397 + }
  398 + });
  399 + }
  400 +
  401 + var request;
  402 +
  403 + // remove headers with undefined keys or values
  404 + // else we get an error in Node 0.12.0 about "setHeader ()"
  405 + for (var headerName in requestoptions.headers) {
  406 + var headerValue = requestoptions.headers[headerName];
  407 + if (!headerName || !headerValue) {
  408 + delete requestoptions.headers[headerName];
  409 + }
  410 + }
  411 +
  412 + if (isHttps) {
  413 + request = https.request (requestoptions, requestResponse);
  414 + } else {
  415 + request = http.request (requestoptions, requestResponse);
  416 + }
  417 +
  418 + if (o.timeout) {
  419 + request.setTimeout (parseInt (o.timeout, 10), function () {
  420 + var err = new Error ('request timed out');
  421 + err.code = 'TIMEOUT';
  422 + finalCallback (err);
  423 + request.abort ();
  424 + });
  425 + }
  426 +
  427 + request.on ('error', function (err) {
  428 + finalCallback (err);
  429 + });
  430 +
  431 + if (body) {
  432 + request.write (body);
  433 + }
  434 +
  435 + request.end ();
  436 +}
  437 +
  438 +exports.doRequest = doRequest;
  439 +
  440 +
  441 +/**
  442 + * HTTP GET method
  443 + *
  444 + * @callback callback
  445 + * @param url {string} - Request URL
  446 + * @param [options] {object} - Request options
  447 + * @param callback [function] - Process response
  448 + * @returns {void}
  449 + */
  450 +
  451 +exports.get = function (url, options, callback) {
  452 + if (callback === undefined && options && typeof options === 'function') {
  453 + callback = options;
  454 + }
  455 +
  456 + var moreOptions = options;
  457 + moreOptions.url = url;
  458 + moreOptions.method = 'GET';
  459 +
  460 + if (moreOptions.allowRedirects === undefined) {
  461 + moreOptions.allowRedirects = true;
  462 + }
  463 +
  464 + doRequest (moreOptions, callback);
  465 +};
  466 +
  467 +
  468 +/**
  469 + * HTTP OPTIONS method
  470 + *
  471 + * @callback callback
  472 + * @param url {string} - Request URL
  473 + * @param [options] {object} - Request options
  474 + * @param callback [function] - Process response
  475 + * @returns {void}
  476 + */
  477 +
  478 +exports.options = function (url, options, callback) {
  479 + if (callback === undefined && options && typeof options === 'function') {
  480 + callback = options;
  481 + }
  482 +
  483 + var moreOptions = options;
  484 + moreOptions.url = url;
  485 + moreOptions.method = 'OPTIONS';
  486 + doRequest (moreOptions, callback);
  487 +};
  488 +
  489 +
  490 +/**
  491 + * HTTP POST method
  492 + *
  493 + * @callback callback
  494 + * @param url {string} - Request URL
  495 + * @param [options] {object} - Request options
  496 + * @param callback [function] - Process response
  497 + * @returns {void}
  498 + */
  499 +
  500 +exports.post = function (url, options, callback) {
  501 + if (callback === undefined && options && typeof options === 'function') {
  502 + callback = options;
  503 + }
  504 +
  505 + var moreOptions = options;
  506 + moreOptions.url = url;
  507 + moreOptions.method = 'POST';
  508 + doRequest (moreOptions, callback);
  509 +};
  510 +
  511 +
  512 +/**
  513 + * HTTP PUT method
  514 + *
  515 + * @callback callback
  516 + * @param url {string} - Request URL
  517 + * @param [options] {object} - Request options
  518 + * @param callback [function] - Process response
  519 + * @returns {void}
  520 + */
  521 +
  522 +exports.put = function (url, options, callback) {
  523 + if (callback === undefined && options && typeof options === 'function') {
  524 + callback = options;
  525 + }
  526 +
  527 + var moreOptions = options;
  528 + moreOptions.url = url;
  529 + moreOptions.method = 'PUT';
  530 + doRequest (moreOptions, callback);
  531 +};
  532 +
  533 +
  534 +/**
  535 + * HTTP PATCH method
  536 + *
  537 + * @callback callback
  538 + * @param url {string} - Request URL
  539 + * @param [options] {object} - Request options
  540 + * @param callback [function] - Process response
  541 + * @returns {void}
  542 + */
  543 +
  544 +exports.patch = function (url, options, callback) {
  545 + if (callback === undefined && options && typeof options === 'function') {
  546 + callback = options;
  547 + }
  548 +
  549 + var moreOptions = options;
  550 + moreOptions.url = url;
  551 + moreOptions.method = 'PATCH';
  552 + doRequest (moreOptions, callback);
  553 +};
  554 +
  555 +
  556 +/**
  557 + * HTTP DELETE method
  558 + *
  559 + * @callback callback
  560 + * @param url {string} - Request URL
  561 + * @param [options] {object} - Request options
  562 + * @param callback [function] - Process response
  563 + * @returns {void}
  564 + */
  565 +
  566 +exports.delete = function (url, options, callback) {
  567 + if (callback === undefined && options && typeof options === 'function') {
  568 + callback = options;
  569 + }
  570 +
  571 + var moreOptions = options;
  572 + moreOptions.url = url;
  573 + moreOptions.method = 'DELETE';
  574 + doRequest (moreOptions, callback);
  575 +};
  576 +
  577 +
  578 +/**
  579 + * Download a file
  580 + *
  581 + * @callback callback
  582 + * @param url {string} - Request URL
  583 + * @param downloadlocation {string} - Path where to store file
  584 + * @param [progressCallback] {function} - Called multiple times during download
  585 + * @param callback {function} - Called once when download ends
  586 + * @returns {void}
  587 + */
  588 +
  589 +exports.download = function (url, downloadlocation, progressCallback, callback) {
  590 + var options = {};
  591 + options.url = url;
  592 + options.method = 'GET';
  593 + options.downloadlocation = downloadlocation;
  594 + options.allowRedirects = true;
  595 +
  596 + // if only 3 args are provided, so no progressCallback
  597 + if (callback === undefined && progressCallback && typeof progressCallback === 'function') {
  598 + callback = progressCallback;
  599 + } else {
  600 + options.progressCallback = progressCallback;
  601 + }
  602 +
  603 + doRequest (options, callback);
  604 +};
  605 +
  606 +
  607 +/**
  608 + * Upload files
  609 + * old function, can still be used
  610 + *
  611 + * @callback callback
  612 + * @param options {object} - Request options
  613 + * @param callback [function] - Process response
  614 + * @returns {void}
  615 + */
  616 +
  617 +exports.uploadFiles = function (options, callback) {
  618 + var moreOptions = options;
  619 + moreOptions.method = 'POST';
  620 + doRequest (moreOptions, callback);
  621 +};
  1 +{
  2 + "_args": [
  3 + [
  4 + {
  5 + "raw": "httpreq@>=0.4.22",
  6 + "scope": null,
  7 + "escapedName": "httpreq",
  8 + "name": "httpreq",
  9 + "rawSpec": ">=0.4.22",
  10 + "spec": ">=0.4.22",
  11 + "type": "range"
  12 + },
  13 + "/Users/fzy/project/koa2_Sequelize_project/node_modules/httpntlm"
  14 + ]
  15 + ],
  16 + "_from": "httpreq@>=0.4.22",
  17 + "_id": "httpreq@0.4.24",
  18 + "_inCache": true,
  19 + "_location": "/httpreq",
  20 + "_nodeVersion": "6.9.1",
  21 + "_npmOperationalInternal": {
  22 + "host": "s3://npm-registry-packages",
  23 + "tmp": "tmp/httpreq-0.4.24.tgz_1498854530181_0.7929337220266461"
  24 + },
  25 + "_npmUser": {
  26 + "name": "samdecrock",
  27 + "email": "sam.decrock@gmail.com"
  28 + },
  29 + "_npmVersion": "3.10.8",
  30 + "_phantomChildren": {},
  31 + "_requested": {
  32 + "raw": "httpreq@>=0.4.22",
  33 + "scope": null,
  34 + "escapedName": "httpreq",
  35 + "name": "httpreq",
  36 + "rawSpec": ">=0.4.22",
  37 + "spec": ">=0.4.22",
  38 + "type": "range"
  39 + },
  40 + "_requiredBy": [
  41 + "/httpntlm"
  42 + ],
  43 + "_resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz",
  44 + "_shasum": "4335ffd82cd969668a39465c929ac61d6393627f",
  45 + "_shrinkwrap": null,
  46 + "_spec": "httpreq@>=0.4.22",
  47 + "_where": "/Users/fzy/project/koa2_Sequelize_project/node_modules/httpntlm",
  48 + "author": {
  49 + "name": "Sam Decrock",
  50 + "url": "https://github.com/SamDecrock/"
  51 + },
  52 + "bugs": {
  53 + "url": "https://github.com/SamDecrock/node-httpreq/issues"
  54 + },
  55 + "contributors": [
  56 + {
  57 + "name": "Russell Beattie",
  58 + "email": "russ@russellbeattie.com",
  59 + "url": "https://github.com/russellbeattie"
  60 + },
  61 + {
  62 + "name": "Jason Prickett MSFT",
  63 + "url": "https://github.com/jpricketMSFT"
  64 + },
  65 + {
  66 + "url": "https://github.com/jjharriso"
  67 + },
  68 + {
  69 + "name": "Sam",
  70 + "url": "https://github.com/SamDecrock"
  71 + },
  72 + {
  73 + "name": "MJJ",
  74 + "url": "https://github.com/mjj2000"
  75 + },
  76 + {
  77 + "name": "Jeff Young",
  78 + "url": "https://github.com/jeffyoung"
  79 + },
  80 + {
  81 + "name": "Dave Preston",
  82 + "url": "https://github.com/davepreston"
  83 + },
  84 + {
  85 + "name": "Franklin van de Meent",
  86 + "email": "fr@nkl.in",
  87 + "url": "https://github.com/fvdm"
  88 + }
  89 + ],
  90 + "dependencies": {},
  91 + "description": "node-httpreq is a node.js library to do HTTP(S) requests the easy way",
  92 + "devDependencies": {
  93 + "chai": "~1.9.1",
  94 + "express": "3.0.3",
  95 + "mocha": "~1.20.1"
  96 + },
  97 + "directories": {},
  98 + "dist": {
  99 + "shasum": "4335ffd82cd969668a39465c929ac61d6393627f",
  100 + "tarball": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz"
  101 + },
  102 + "engines": {
  103 + "node": ">= 0.8.0"
  104 + },
  105 + "gitHead": "a48045e87f378079f4e83ed18d6032292cb4a854",
  106 + "homepage": "https://github.com/SamDecrock/node-httpreq#readme",
  107 + "license": "MIT",
  108 + "main": "./lib/httpreq",
  109 + "maintainers": [
  110 + {
  111 + "name": "samdecrock",
  112 + "email": "sam.decrock@gmail.com"
  113 + }
  114 + ],
  115 + "name": "httpreq",
  116 + "optionalDependencies": {},
  117 + "readme": "node-httpreq\n============\n\nnode-httpreq is a node.js library to do HTTP(S) requests the easy way\n\nDo GET, POST, PUT, PATCH, DELETE, OPTIONS, upload files, use cookies, change headers, ...\n\n## Install\n\nYou can install __httpreq__ using the Node Package Manager (npm):\n\n npm install httpreq\n\n## Simple example\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.get('http://www.google.com', function (err, res){\n if (err) return console.log(err);\n\n console.log(res.statusCode);\n console.log(res.headers);\n console.log(res.body);\n console.log(res.cookies);\n});\n```\n\n## How to use\n\n* [httpreq.get(url, [options], callback)](#get)\n* [httpreq.post(url, [options], callback)](#post)\n* [httpreq.put(url, [options], callback)](#put)\n* [httpreq.delete(url, [options], callback)](#delete)\n* [httpreq.options(url, [options], callback)](#options)\n* [Uploading files](#upload)\n* [Downloading a binary file](#binary)\n* [Downloading a file directly to disk](#download)\n* [Sending a custom body](#custombody)\n* [Using a http(s) proxy](#proxy)\n* [httpreq.doRequest(options, callback)](#dorequest)\n\n---------------------------------------\n### httpreq.get(url, [options], callback)\n<a name=\"get\"></a>\n\n__Arguments__\n - url: The url to connect to. Can be http or https.\n - options: (all are optional) The following options can be passed:\n - parameters: an object of query parameters\n - headers: an object of headers\n - cookies: an array of cookies\n - auth: a string for basic authentication. For example `username:password`\n - binary: true/false (default: false), if true, res.body will a buffer containing the binary data\n - allowRedirects: (default: __true__ , only with httpreq.get() ), if true, redirects will be followed\n - maxRedirects: (default: __10__ ). For example 1 redirect will allow for one normal request and 1 extra redirected request.\n - timeout: (default: __none__ ). Adds a timeout to the http(s) request. Should be in milliseconds.\n - proxy, if you want to pass your request through a http(s) proxy server:\n - host: eg: \"192.168.0.1\"\n - port: eg: 8888\n - protocol: (default: __'http'__ ) can be 'http' or 'https'\n - rejectUnauthorized: validate certificate for request with HTTPS. [More here](http://nodejs.org/api/https.html#https_https_request_options_callback)\n - callback(err, res): A callback function which is called when the request is complete. __res__ contains the headers ( __res.headers__ ), the http status code ( __res.statusCode__ ) and the body ( __res.body__ )\n\n__Example without options__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.get('http://www.google.com', function (err, res){\n if (err) return console.log(err);\n\n console.log(res.statusCode);\n console.log(res.headers);\n console.log(res.body);\n});\n```\n\n__Example with options__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.get('http://posttestserver.com/post.php', {\n parameters: {\n name: 'John',\n lastname: 'Doe'\n },\n headers:{\n 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/20100101 Firefox/18.0'\n },\n cookies: [\n 'token=DGcGUmplWQSjfqEvmu%2BZA%2Fc',\n 'id=2'\n ]\n}, function (err, res){\n if (err){\n console.log(err);\n }else{\n console.log(res.body);\n }\n});\n```\n---------------------------------------\n### httpreq.post(url, [options], callback)\n<a name=\"post\"></a>\n\n__Arguments__\n - url: The url to connect to. Can be http or https.\n - options: (all are optional) The following options can be passed:\n - parameters: an object of post parameters (content-type is set to *application/x-www-form-urlencoded; charset=UTF-8*)\n - json: if you want to send json directly (content-type is set to *application/json*)\n - files: an object of files to upload (content-type is set to *multipart/form-data; boundary=xxx*)\n - body: custom body content you want to send. If used, previous options will be ignored and your custom body will be sent. (content-type will not be set)\n - headers: an object of headers\n - cookies: an array of cookies\n - auth: a string for basic authentication. For example `username:password`\n - binary: true/false (default: __false__ ), if true, res.body will be a buffer containing the binary data\n - allowRedirects: (default: __false__ ), if true, redirects will be followed\n - maxRedirects: (default: __10__ ). For example 1 redirect will allow for one normal request and 1 extra redirected request.\n - encodePostParameters: (default: __true__ ), if true, POST/PUT parameters names will be URL encoded.\n - timeout: (default: none). Adds a timeout to the http(s) request. Should be in milliseconds.\n - proxy, if you want to pass your request through a http(s) proxy server:\n - host: eg: \"192.168.0.1\"\n - port: eg: 8888\n - protocol: (default: __'http'__ ) can be 'http' or 'https'\n - rejectUnauthorized: validate certificate for request with HTTPS. [More here](http://nodejs.org/api/https.html#https_https_request_options_callback)\n - callback(err, res): A callback function which is called when the request is complete. __res__ contains the headers ( __res.headers__ ), the http status code ( __res.statusCode__ ) and the body ( __res.body__ )\n\n__Example without extra options__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.post('http://posttestserver.com/post.php', {\n parameters: {\n name: 'John',\n lastname: 'Doe'\n }\n}, function (err, res){\n if (err){\n console.log(err);\n }else{\n console.log(res.body);\n }\n});\n```\n\n__Example with options__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.post('http://posttestserver.com/post.php', {\n parameters: {\n name: 'John',\n lastname: 'Doe'\n },\n headers:{\n 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/20100101 Firefox/18.0'\n },\n cookies: [\n 'token=DGcGUmplWQSjfqEvmu%2BZA%2Fc',\n 'id=2'\n ]\n}, function (err, res){\n if (err){\n console.log(err);\n }else{\n console.log(res.body);\n }\n});\n```\n\n---------------------------------------\n### httpreq.put(url, [options], callback)\n<a name=\"put\"></a>\n\nSame options as [httpreq.post(url, [options], callback)](#post)\n\n---------------------------------------\n<a name=\"delete\" />\n### httpreq.delete(url, [options], callback)\n\nSame options as [httpreq.post(url, [options], callback)](#post)\n\n---------------------------------------\n<a name=\"options\" />\n### httpreq.options(url, [options], callback)\n\nSame options as [httpreq.get(url, [options], callback)](#get) except for the ability to follow redirects.\n\n---------------------------------------\n<a name=\"upload\" />\n### Uploading files\n\nYou can still use ```httpreq.uploadFiles({url: 'url', files: {}}, callback)```, but it's easier to just use POST (or PUT):\n\n__Example__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.post('http://posttestserver.com/upload.php', {\n parameters: {\n name: 'John',\n lastname: 'Doe'\n },\n files:{\n myfile: __dirname + \"/testupload.jpg\",\n myotherfile: __dirname + \"/testupload.jpg\"\n }\n}, function (err, res){\n if (err) throw err;\n});\n```\n\n---------------------------------------\n<a name=\"binary\"></a>\n### Downloading a binary file\nTo download a binary file, just add __binary: true__ to the options when doing a get or a post.\n\n__Example__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.get('https://ssl.gstatic.com/gb/images/k1_a31af7ac.png', {binary: true}, function (err, res){\n if (err){\n console.log(err);\n }else{\n fs.writeFile(__dirname + '/test.png', res.body, function (err) {\n if(err)\n console.log(\"error writing file\");\n });\n }\n});\n```\n\n---------------------------------------\n<a name=\"download\"></a>\n### Downloading a file directly to disk\nTo download a file directly to disk, use the download method provided.\n\nDownloading is done using a stream, so the data is not stored in memory and directly saved to file.\n\n__Example__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.download(\n 'https://ssl.gstatic.com/gb/images/k1_a31af7ac.png',\n __dirname + '/test.png'\n, function (err, progress){\n if (err) return console.log(err);\n console.log(progress);\n}, function (err, res){\n if (err) return console.log(err);\n console.log(res);\n});\n\n```\n---------------------------------------\n<a name=\"custombody\"></a>\n### Sending a custom body\nUse the body option to send a custom body (eg. an xml post)\n\n__Example__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.post('http://posttestserver.com/post.php',{\n body: '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n headers:{\n 'Content-Type': 'text/xml',\n }},\n function (err, res) {\n if (err){\n console.log(err);\n }else{\n console.log(res.body);\n }\n }\n);\n```\n\n---------------------------------------\n<a name=\"proxy\"></a>\n### Using a http(s) proxy\n\n__Example__\n\n```js\nvar httpreq = require('httpreq');\n\nhttpreq.post('http://posttestserver.com/post.php', {\n proxy: {\n host: '10.100.0.126',\n port: 8888\n }\n}, function (err, res){\n if (err){\n console.log(err);\n }else{\n console.log(res.body);\n }\n});\n```\n\n---------------------------------------\n### httpreq.doRequest(options, callback)\n<a name=\"dorequest\"></a>\n\nhttpreq.doRequest is internally used by httpreq.get() and httpreq.post(). You can use this directly. Everything is stays the same as httpreq.get() or httpreq.post() except that the following options MUST be passed:\n- url: the url to post the files to\n- method: 'GET', 'POST', 'PUT' or 'DELETE'\n\n## Donate\n\nIf you like this module or you want me to update it faster, feel free to donate. It helps increasing my dedication to fixing bugs :-)\n\n[![](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AB3R2SUL53K7S)\n\n\n",
  118 + "readmeFilename": "README.md",
  119 + "repository": {
  120 + "type": "git",
  121 + "url": "git://github.com/SamDecrock/node-httpreq.git"
  122 + },
  123 + "scripts": {},
  124 + "version": "0.4.24"
  125 +}
  1 +var httpreq = require('../lib/httpreq');
  2 +
  3 +var assert = require("assert");
  4 +var expect = require("chai").expect;
  5 +var express = require('express');
  6 +var http = require('http');
  7 +var fs = require('fs');
  8 +
  9 +
  10 +
  11 +describe("httpreq", function(){
  12 +
  13 + var port, app, webserver, endpointroot;
  14 +
  15 + before(function (done) {
  16 + port = Math.floor( Math.random() * (65535 - 1025) + 1025 );
  17 +
  18 + endpointroot = 'http://localhost:'+port;
  19 +
  20 + app = express();
  21 +
  22 + app.configure(function(){
  23 + app.use(express.logger('dev'));
  24 + app.use(express.errorHandler());
  25 + app.use(express.bodyParser());
  26 + app.use(express.methodOverride());
  27 + app.use(app.router);
  28 + });
  29 +
  30 +
  31 + webserver = http.createServer(app).listen(port, function(){
  32 + console.log("web server listening on port " + port);
  33 + done();
  34 + });
  35 +
  36 +
  37 + });
  38 +
  39 + after(function () {
  40 + webserver.close();
  41 + });
  42 +
  43 +
  44 + describe("get", function(){
  45 +
  46 + it("should do a simple GET request", function (done){
  47 +
  48 + var path = '/get'; // make sure this is unique when writing tests
  49 +
  50 + app.get(path, function (req, res) {
  51 + res.send('ok');
  52 + done();
  53 + });
  54 +
  55 + httpreq.get(endpointroot + path, function (err, res) {
  56 + if (err) throw err;
  57 + });
  58 +
  59 + });
  60 +
  61 + });
  62 +
  63 + describe("post", function(){
  64 +
  65 + it("should do a simple POST request with parameters", function (done){
  66 +
  67 + var parameters = {
  68 + name: 'John',
  69 + lastname: 'Doe'
  70 + };
  71 +
  72 + var path = '/post';
  73 +
  74 + // set up webserver endpoint:
  75 + app.post(path, function (req, res) {
  76 + res.send('ok');
  77 +
  78 + expect(req.body).to.deep.equal(parameters);
  79 +
  80 + done();
  81 + });
  82 +
  83 + // post parameters to webserver endpoint:
  84 + httpreq.post(endpointroot + path, {
  85 + parameters: parameters
  86 + }, function (err, res){
  87 + if (err) throw err;
  88 + });
  89 +
  90 + });
  91 +
  92 + it("should do a simple POST request with parameters and cookies", function (done){
  93 +
  94 + var parameters = {
  95 + name: 'John',
  96 + lastname: 'Doe'
  97 + };
  98 +
  99 + var cookies = [
  100 + 'token=DGcGUmplWQSjfqEvmu%2BZA%2Fc',
  101 + 'id=2'
  102 + ];
  103 +
  104 + var path = '/postcookies';
  105 +
  106 + // set up webserver endpoint:
  107 + app.post(path, function (req, res) {
  108 + res.send('ok');
  109 +
  110 + expect(req.body).to.deep.equal(parameters);
  111 + expect(req.headers.cookie).to.equal(cookies.join('; '));
  112 +
  113 + done();
  114 + });
  115 +
  116 + // post testdata to webserver endpoint:
  117 + httpreq.post(endpointroot + path, {
  118 + parameters: parameters,
  119 + cookies: cookies
  120 + }, function (err, res){
  121 + if (err) throw err;
  122 + });
  123 +
  124 + });
  125 +
  126 + it("should do a simple POST request with parameters and custom headers", function (done){
  127 +
  128 + var parameters = {
  129 + name: 'John',
  130 + lastname: 'Doe'
  131 + };
  132 +
  133 + var headers = {
  134 + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) Gecko/20100101 Firefox/18.0'
  135 + };
  136 +
  137 + var path = '/postheaders';
  138 +
  139 + // set up webserver endpoint:
  140 + app.post(path, function (req, res) {
  141 + res.send('ok');
  142 +
  143 + expect(req.body).to.deep.equal(parameters);
  144 + expect(req.headers).to.have.a.property('user-agent', headers['User-Agent']);
  145 +
  146 + done();
  147 + });
  148 +
  149 + // post testdata to webserver endpoint:
  150 + httpreq.post(endpointroot + path, {
  151 + parameters: parameters,
  152 + headers: headers
  153 + }, function (err, res){
  154 + if (err) throw err;
  155 + });
  156 +
  157 + });
  158 +
  159 + });
  160 +
  161 +
  162 + describe("POST json", function () {
  163 + it('should POST some json', function (done) {
  164 + var somejson = {
  165 + name: 'John',
  166 + lastname: 'Doe'
  167 + };
  168 +
  169 + var path = '/postjson';
  170 +
  171 + // set up webserver endpoint:
  172 + app.post(path, function (req, res) {
  173 + res.send('ok');
  174 +
  175 + expect(req.body).to.deep.equal(somejson);
  176 +
  177 + done();
  178 + });
  179 +
  180 + httpreq.post(endpointroot + path, {
  181 + json: somejson
  182 + }, function (err, res){
  183 + if (err) throw err;
  184 + });
  185 + });
  186 + });
  187 +
  188 +
  189 + describe("File upload", function () {
  190 + it('should upload 1 file (old way)', function (done) {
  191 +
  192 + var testparams = {
  193 + name: 'John',
  194 + lastname: 'Doe'
  195 + };
  196 +
  197 + var testfile = __dirname + "/testupload.jpg";
  198 +
  199 + var path = '/uploadfile_old';
  200 +
  201 + // set up webserver endpoint:
  202 + app.post(path, function (req, res) {
  203 + res.send('ok');
  204 +
  205 + expect(req.body).to.deep.equal(testparams);
  206 +
  207 + comparefiles(req.files['myfile'].path, testfile, done);
  208 + });
  209 +
  210 + httpreq.uploadFiles({
  211 + url: endpointroot + path,
  212 + parameters: testparams,
  213 + files:{
  214 + myfile: testfile
  215 + }
  216 + }, function (err, res){
  217 + if (err) throw err;
  218 + });
  219 + });
  220 +
  221 + it('should upload 2 files (new way, using POST)', function (done) {
  222 +
  223 + var testparams = {
  224 + name: 'John',
  225 + lastname: 'Doe'
  226 + };
  227 +
  228 + var testfile = __dirname + "/testupload.jpg";
  229 +
  230 + var path = '/uploadfiles';
  231 +
  232 + // set up webserver endpoint:
  233 + app.post(path, function (req, res) {
  234 + res.send('ok');
  235 +
  236 + expect(req.body).to.deep.equal(testparams);
  237 +
  238 + comparefiles(req.files['myfile'].path, testfile, function () {
  239 + comparefiles(req.files['myotherfile'].path, testfile, function () {
  240 + done();
  241 + });
  242 + });
  243 + });
  244 +
  245 + httpreq.post(endpointroot + path, {
  246 + parameters: testparams,
  247 + files:{
  248 + myfile: testfile,
  249 + myotherfile: testfile
  250 + }
  251 + }, function (err, res){
  252 + if (err) throw err;
  253 + });
  254 + });
  255 +
  256 + it('should upload 2 files (new way, using PUT)', function (done) {
  257 +
  258 + var testparams = {
  259 + name: 'John',
  260 + lastname: 'Doe'
  261 + };
  262 +
  263 + var testfile = __dirname + "/testupload.jpg";
  264 +
  265 + var path = '/uploadfiles_put';
  266 +
  267 + // set up webserver endpoint:
  268 + app.put(path, function (req, res) {
  269 + res.send('ok');
  270 +
  271 + expect(req.body).to.deep.equal(testparams);
  272 +
  273 + comparefiles(req.files['myfile'].path, testfile, function () {
  274 + comparefiles(req.files['myotherfile'].path, testfile, function () {
  275 + done();
  276 + });
  277 + });
  278 + });
  279 +
  280 + httpreq.put(endpointroot + path, {
  281 + parameters: testparams,
  282 + files:{
  283 + myfile: testfile,
  284 + myotherfile: testfile
  285 + }
  286 + }, function (err, res){
  287 + if (err) throw err;
  288 + });
  289 + });
  290 + });
  291 +
  292 +});
  293 +
  294 +
  295 +function comparefiles (file1, file2, callback) {
  296 + fs.readFile(file1, function (err, file1data) {
  297 + if(err) throw err;
  298 +
  299 + fs.readFile(file2, function (err, file2data) {
  300 + if(err) throw err;
  301 +
  302 + expect(file1data).to.deep.equal(file2data);
  303 +
  304 + callback();
  305 + });
  306 + });
  307 +}
  1 +'use strict';
  2 +
  3 +module.exports = {
  4 + rules: {
  5 + indent: [2, 4, {
  6 + SwitchCase: 1
  7 + }],
  8 + quotes: [2, 'single'],
  9 + 'linebreak-style': [2, 'unix'],
  10 + semi: [2, 'always'],
  11 + strict: [2, 'global'],
  12 + eqeqeq: 2,
  13 + 'dot-notation': 2,
  14 + curly: 2,
  15 + 'no-fallthrough': 2,
  16 + 'quote-props': [2, 'as-needed'],
  17 + 'no-unused-expressions': [2, {
  18 + allowShortCircuit: true
  19 + }],
  20 + 'no-unused-vars': 2,
  21 + 'no-undef': 2,
  22 + 'handle-callback-err': 2,
  23 + 'no-new': 2,
  24 + 'new-cap': 2,
  25 + 'no-eval': 2,
  26 + 'no-invalid-this': 2,
  27 + radix: [2, 'always'],
  28 + 'no-use-before-define': [2, 'nofunc'],
  29 + 'callback-return': [2, ['callback', 'cb', 'done']],
  30 + 'comma-dangle': [2, 'never'],
  31 + 'comma-style': [2, 'last'],
  32 + 'no-regex-spaces': 2,
  33 + 'no-empty': 2,
  34 + 'no-duplicate-case': 2,
  35 + 'no-empty-character-class': 2,
  36 + 'no-redeclare': [2, {
  37 + builtinGlobals: true
  38 + }],
  39 + 'block-scoped-var': 2,
  40 + 'no-sequences': 2,
  41 + 'no-throw-literal': 2,
  42 + 'no-useless-concat': 2,
  43 + 'no-void': 2,
  44 + yoda: 2,
  45 + 'no-bitwise': 2,
  46 + 'no-lonely-if': 2,
  47 + 'no-mixed-spaces-and-tabs': 2,
  48 + 'no-console': 0
  49 + },
  50 + env: {
  51 + es6: false,
  52 + node: true
  53 + },
  54 + extends: 'eslint:recommended',
  55 + fix: true
  56 +};
  1 +node_modules
  2 +npm-debug.log
  3 +.DS_Store
  1 +language: node_js
  2 +sudo: false
  3 +node_js:
  4 + - "0.10"
  5 + - 0.12
  6 + - iojs
  7 + - '4'
  8 + - '5'
  9 +before_install:
  10 + - npm install -g grunt-cli
  11 +notifications:
  12 + email:
  13 + - andris@kreata.ee
  14 + webhooks:
  15 + urls:
  16 + - https://webhooks.gitter.im/e/0ed18fd9b3e529b3c2cc
  17 + on_success: change # options: [always|never|change] default: always
  18 + on_failure: always # options: [always|never|change] default: always
  19 + on_start: false # default: false
  1 +# Changelog
  2 +
  3 +## v1.6.0 2016-08-18
  4 +
  5 + * Added new option `headers`
  6 +
  7 +## v1.5.0 2016-08-18
  8 +
  9 + * Allow streams as POST body
  10 +
  11 +## v1.3.0 2016-02-11
  12 +
  13 + * Added new option `timeout`
  14 +
  15 +## v1.2.1 2016-01-18
  16 +
  17 + * Enclose http.request into try..catch to get url parse errors
  18 +
  19 +## v1.2.0 2016-01-18
  20 +
  21 + * Export `Cookies` constructor
  22 +
  23 +## v1.1.0 2016-01-18
  24 +
  25 + * Exposed `options` object
  26 + * Added new options `maxRedirects`, `userAgent` and `cookie`
  27 +
  28 +## v1.0.0 2015-12-30
  29 +
  30 + * Initial version
  1 +'use strict';
  2 +
  3 +module.exports = function (grunt) {
  4 +
  5 + // Project configuration.
  6 + grunt.initConfig({
  7 + eslint: {
  8 + all: ['lib/*.js', 'test/*.js', 'Gruntfile.js', '.eslintrc.js']
  9 + },
  10 +
  11 + mochaTest: {
  12 + all: {
  13 + options: {
  14 + reporter: 'spec'
  15 + },
  16 + src: ['test/*-test.js']
  17 + }
  18 + }
  19 + });
  20 +
  21 + // Load the plugin(s)
  22 + grunt.loadNpmTasks('grunt-eslint');
  23 + grunt.loadNpmTasks('grunt-mocha-test');
  24 +
  25 + // Tasks
  26 + grunt.registerTask('default', ['eslint', 'mochaTest']);
  27 +};
  1 +Copyright (c) 2015-2016 Andris Reinman
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +of this software and associated documentation files (the "Software"), to deal
  5 +in the Software without restriction, including without limitation the rights
  6 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +copies of the Software, and to permit persons to whom the Software is
  8 +furnished to do so, subject to the following conditions:
  9 +
  10 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  11 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  12 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  13 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  14 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  15 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  16 +SOFTWARE.
  1 +# nodemailer-fetch
  2 +
  3 +Fetches HTTP URL contents for [nodemailer](https://github.com/nodemailer/nodemailer).
  4 +
  5 +[![Build Status](https://secure.travis-ci.org/nodemailer/nodemailer-fetch.svg)](http://travis-ci.org/nodemailer/nodemailer-fetch)
  6 +<a href="http://badge.fury.io/js/nodemailer-fetch"><img src="https://badge.fury.io/js/nodemailer-fetch.svg" alt="NPM version" height="18"></a>
  7 +
  8 +## Usage
  9 +
  10 +```javascript
  11 +var fetch = require('nodemailer-fetch');
  12 +fetch('http://www.google.com/').pipe(process.stdout);
  13 +```
  14 +
  15 +The method takes the destination URL as the first and optional options object as the second argument.
  16 +
  17 +The defaults are the following:
  18 +
  19 + * Default method is GET
  20 + * Basic auth is supported
  21 + * Up to 5 redirects are followed (Basic auth gets lost after first redirect)
  22 + * gzip is handled if present
  23 + * Cookies are supported
  24 + * No shared HTTP Agent
  25 + * Invalid SSL certs are allowed. Can be overwritten with the `tls` option
  26 +
  27 +### options
  28 +
  29 +Possible options are the following:
  30 +
  31 + * **userAgent** a string defining the User Agent of the request (by default not set)
  32 + * **cookie** a cookie string or an array of cookie strings where a cookie is the value used by 'Set-Cookie' header
  33 + * **maxRedirects** how many redirects to allow (defaults to 5, set to 0 to disable redirects entirely)
  34 + * **method** HTTP method to use, defaults to GET (if `body` is set defaults to POST)
  35 + * **body** HTTP payload to send. If the value is an object it is converted to an *x-www-form-urlencoded* payload, other values are passed as is. Unlike authentication data payload and method is preserved between redirects
  36 + * **contentType** optional content type for the HTTP payload. Defaults to *x-www-form-urlencoded*. If the value is `false` then Content-Type header is not set
  37 + * **tls** optional object of TLS options
  38 + * **timeout** (milliseconds) sets timeout for the connection. Returns an error if timeout occurs
  39 + * **headers** custom headers as an object where key is the header key and value is either a string or an array of strings for multiple values
  40 +
  41 + ```javascript
  42 + var fetch = require('nodemailer-fetch');
  43 + fetch('http://www.google.com/', {
  44 + cookie: [
  45 + 'cookie_name1=cookie_value1',
  46 + 'cookie_name2=cookie_value2; expires=Sun, 16 Jul 3567 06:23:41 GMT',
  47 + ],
  48 + userAgent: 'MyFetcher/1.0'
  49 + }).pipe(process.stdout);
  50 + ```
  51 +
  52 +> Cookies are domain specific like normal browser cookies, so if a redirect happens to another domain, then cookies are not passed to it, HTTPS-only cookies are not passed to HTTP etc.
  53 +
  54 +## License
  55 +**MIT**
  1 +'use strict';
  2 +
  3 +// module to handle cookies
  4 +
  5 +var urllib = require('url');
  6 +
  7 +var SESSION_TIMEOUT = 1800; // 30 min
  8 +
  9 +module.exports = Cookies;
  10 +
  11 +/**
  12 + * Creates a biskviit cookie jar for managing cookie values in memory
  13 + *
  14 + * @constructor
  15 + * @param {Object} [options] Optional options object
  16 + */
  17 +function Cookies(options) {
  18 + this.options = options || {};
  19 + this.cookies = [];
  20 +}
  21 +
  22 +/**
  23 + * Stores a cookie string to the cookie storage
  24 + *
  25 + * @param {String} cookieStr Value from the 'Set-Cookie:' header
  26 + * @param {String} url Current URL
  27 + */
  28 +Cookies.prototype.set = function (cookieStr, url) {
  29 + var urlparts = urllib.parse(url || '');
  30 + var cookie = this.parse(cookieStr);
  31 + var domain;
  32 +
  33 + if (cookie.domain) {
  34 + domain = cookie.domain.replace(/^\./, '');
  35 +
  36 + // do not allow cross origin cookies
  37 + if (
  38 + // can't be valid if the requested domain is shorter than current hostname
  39 + urlparts.hostname.length < domain.length ||
  40 +
  41 + // prefix domains with dot to be sure that partial matches are not used
  42 + ('.' + urlparts.hostname).substr(-domain.length + 1) !== ('.' + domain)) {
  43 + cookie.domain = urlparts.hostname;
  44 + }
  45 + } else {
  46 + cookie.domain = urlparts.hostname;
  47 + }
  48 +
  49 + if (!cookie.path) {
  50 + cookie.path = this.getPath(urlparts.pathname);
  51 + }
  52 +
  53 + // if no expire date, then use sessionTimeout value
  54 + if (!cookie.expires) {
  55 + cookie.expires = new Date(Date.now() + (Number(this.options.sessionTimeout || SESSION_TIMEOUT) || SESSION_TIMEOUT) * 1000);
  56 + }
  57 +
  58 + return this.add(cookie);
  59 +};
  60 +
  61 +/**
  62 + * Returns cookie string for the 'Cookie:' header.
  63 + *
  64 + * @param {String} url URL to check for
  65 + * @returns {String} Cookie header or empty string if no matches were found
  66 + */
  67 +Cookies.prototype.get = function (url) {
  68 + return this.list(url).map(function (cookie) {
  69 + return cookie.name + '=' + cookie.value;
  70 + }).join('; ');
  71 +};
  72 +
  73 +/**
  74 + * Lists all valied cookie objects for the specified URL
  75 + *
  76 + * @param {String} url URL to check for
  77 + * @returns {Array} An array of cookie objects
  78 + */
  79 +Cookies.prototype.list = function (url) {
  80 + var result = [];
  81 + var i;
  82 + var cookie;
  83 +
  84 + for (i = this.cookies.length - 1; i >= 0; i--) {
  85 + cookie = this.cookies[i];
  86 +
  87 + if (this.isExpired(cookie)) {
  88 + this.cookies.splice(i, i);
  89 + continue;
  90 + }
  91 +
  92 + if (this.match(cookie, url)) {
  93 + result.unshift(cookie);
  94 + }
  95 + }
  96 +
  97 + return result;
  98 +};
  99 +
  100 +/**
  101 + * Parses cookie string from the 'Set-Cookie:' header
  102 + *
  103 + * @param {String} cookieStr String from the 'Set-Cookie:' header
  104 + * @returns {Object} Cookie object
  105 + */
  106 +Cookies.prototype.parse = function (cookieStr) {
  107 + var cookie = {};
  108 +
  109 + (cookieStr || '').toString().split(';').forEach(function (cookiePart) {
  110 + var valueParts = cookiePart.split('=');
  111 + var key = valueParts.shift().trim().toLowerCase();
  112 + var value = valueParts.join('=').trim();
  113 + var domain;
  114 +
  115 + if (!key) {
  116 + // skip empty parts
  117 + return;
  118 + }
  119 +
  120 + switch (key) {
  121 +
  122 + case 'expires':
  123 + value = new Date(value);
  124 + // ignore date if can not parse it
  125 + if (value.toString() !== 'Invalid Date') {
  126 + cookie.expires = value;
  127 + }
  128 + break;
  129 +
  130 + case 'path':
  131 + cookie.path = value;
  132 + break;
  133 +
  134 + case 'domain':
  135 + domain = value.toLowerCase();
  136 + if (domain.length && domain.charAt(0) !== '.') {
  137 + domain = '.' + domain; // ensure preceeding dot for user set domains
  138 + }
  139 + cookie.domain = domain;
  140 + break;
  141 +
  142 + case 'max-age':
  143 + cookie.expires = new Date(Date.now() + (Number(value) || 0) * 1000);
  144 + break;
  145 +
  146 + case 'secure':
  147 + cookie.secure = true;
  148 + break;
  149 +
  150 + case 'httponly':
  151 + cookie.httponly = true;
  152 + break;
  153 +
  154 + default:
  155 + if (!cookie.name) {
  156 + cookie.name = key;
  157 + cookie.value = value;
  158 + }
  159 + }
  160 + });
  161 +
  162 + return cookie;
  163 +};
  164 +
  165 +/**
  166 + * Checks if a cookie object is valid for a specified URL
  167 + *
  168 + * @param {Object} cookie Cookie object
  169 + * @param {String} url URL to check for
  170 + * @returns {Boolean} true if cookie is valid for specifiec URL
  171 + */
  172 +Cookies.prototype.match = function (cookie, url) {
  173 + var urlparts = urllib.parse(url || '');
  174 +
  175 + // check if hostname matches
  176 + // .foo.com also matches subdomains, foo.com does not
  177 + if (urlparts.hostname !== cookie.domain && (cookie.domain.charAt(0) !== '.' || ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)) {
  178 + return false;
  179 + }
  180 +
  181 + // check if path matches
  182 + var path = this.getPath(urlparts.pathname);
  183 + if (path.substr(0, cookie.path.length) !== cookie.path) {
  184 + return false;
  185 + }
  186 +
  187 + // check secure argument
  188 + if (cookie.secure && urlparts.protocol !== 'https:') {
  189 + return false;
  190 + }
  191 +
  192 + return true;
  193 +};
  194 +
  195 +/**
  196 + * Adds (or updates/removes if needed) a cookie object to the cookie storage
  197 + *
  198 + * @param {Object} cookie Cookie value to be stored
  199 + */
  200 +Cookies.prototype.add = function (cookie) {
  201 + var i;
  202 + var len;
  203 +
  204 + // nothing to do here
  205 + if (!cookie || !cookie.name) {
  206 + return false;
  207 + }
  208 +
  209 + // overwrite if has same params
  210 + for (i = 0, len = this.cookies.length; i < len; i++) {
  211 + if (this.compare(this.cookies[i], cookie)) {
  212 +
  213 + // check if the cookie needs to be removed instead
  214 + if (this.isExpired(cookie)) {
  215 + this.cookies.splice(i, 1); // remove expired/unset cookie
  216 + return false;
  217 + }
  218 +
  219 + this.cookies[i] = cookie;
  220 + return true;
  221 + }
  222 + }
  223 +
  224 + // add as new if not already expired
  225 + if (!this.isExpired(cookie)) {
  226 + this.cookies.push(cookie);
  227 + }
  228 +
  229 + return true;
  230 +};
  231 +
  232 +/**
  233 + * Checks if two cookie objects are the same
  234 + *
  235 + * @param {Object} a Cookie to check against
  236 + * @param {Object} b Cookie to check against
  237 + * @returns {Boolean} True, if the cookies are the same
  238 + */
  239 +Cookies.prototype.compare = function (a, b) {
  240 + return a.name === b.name && a.path === b.path && a.domain === b.domain && a.secure === b.secure && a.httponly === a.httponly;
  241 +};
  242 +
  243 +/**
  244 + * Checks if a cookie is expired
  245 + *
  246 + * @param {Object} cookie Cookie object to check against
  247 + * @returns {Boolean} True, if the cookie is expired
  248 + */
  249 +Cookies.prototype.isExpired = function (cookie) {
  250 + return (cookie.expires && cookie.expires < new Date()) || !cookie.value;
  251 +};
  252 +
  253 +/**
  254 + * Returns normalized cookie path for an URL path argument
  255 + *
  256 + * @param {String} pathname
  257 + * @returns {String} Normalized path
  258 + */
  259 +Cookies.prototype.getPath = function (pathname) {
  260 + var path = (pathname || '/').split('/');
  261 + path.pop(); // remove filename part
  262 + path = path.join('/').trim();
  263 +
  264 + // ensure path prefix /
  265 + if (path.charAt(0) !== '/') {
  266 + path = '/' + path;
  267 + }
  268 +
  269 + // ensure path suffix /
  270 + if (path.substr(-1) !== '/') {
  271 + path += '/';
  272 + }
  273 +
  274 + return path;
  275 +};
  1 +'use strict';
  2 +
  3 +var http = require('http');
  4 +var https = require('https');
  5 +var urllib = require('url');
  6 +var zlib = require('zlib');
  7 +var PassThrough = require('stream').PassThrough;
  8 +var Cookies = require('./cookies');
  9 +
  10 +var MAX_REDIRECTS = 5;
  11 +
  12 +module.exports = function (url, options) {
  13 + return fetch(url, options);
  14 +};
  15 +
  16 +module.exports.Cookies = Cookies;
  17 +
  18 +function fetch(url, options) {
  19 + options = options || {};
  20 +
  21 + options.fetchRes = options.fetchRes || new PassThrough();
  22 + options.cookies = options.cookies || new Cookies();
  23 + options.redirects = options.redirects || 0;
  24 + options.maxRedirects = isNaN(options.maxRedirects) ? MAX_REDIRECTS : options.maxRedirects;
  25 +
  26 + if (options.cookie) {
  27 + [].concat(options.cookie || []).forEach(function (cookie) {
  28 + options.cookies.set(cookie, url);
  29 + });
  30 + options.cookie = false;
  31 + }
  32 +
  33 + var fetchRes = options.fetchRes;
  34 + var parsed = urllib.parse(url);
  35 + var method = (options.method || '').toString().trim().toUpperCase() || 'GET';
  36 + var finished = false;
  37 + var cookies;
  38 + var body;
  39 +
  40 + var handler = parsed.protocol === 'https:' ? https : http;
  41 +
  42 + var headers = {
  43 + 'accept-encoding': 'gzip,deflate'
  44 + };
  45 +
  46 + Object.keys(options.headers || {}).forEach(function (key) {
  47 + headers[key.toLowerCase().trim()] = options.headers[key];
  48 + });
  49 +
  50 + if (options.userAgent) {
  51 + headers['User-Agent'] = options.userAgent;
  52 + }
  53 +
  54 + if (parsed.auth) {
  55 + headers.Authorization = 'Basic ' + new Buffer(parsed.auth).toString('base64');
  56 + }
  57 +
  58 + if ((cookies = options.cookies.get(url))) {
  59 + headers.cookie = cookies;
  60 + }
  61 +
  62 + if (options.body) {
  63 + if (options.contentType !== false) {
  64 + headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
  65 + }
  66 +
  67 + if (typeof options.body.pipe === 'function') {
  68 + // it's a stream
  69 + headers['Transfer-Encoding'] = 'chunked';
  70 + body = options.body;
  71 + body.on('error', function (err) {
  72 + if (finished) {
  73 + return;
  74 + }
  75 + finished = true;
  76 + fetchRes.emit('error', err);
  77 + });
  78 + } else {
  79 + if (options.body instanceof Buffer) {
  80 + body = options.body;
  81 + } else if (typeof options.body === 'object') {
  82 + body = new Buffer(Object.keys(options.body).map(function (key) {
  83 + var value = options.body[key].toString().trim();
  84 + return encodeURIComponent(key) + '=' + encodeURIComponent(value);
  85 + }).join('&'));
  86 + } else {
  87 + body = new Buffer(options.body.toString().trim());
  88 + }
  89 +
  90 + headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
  91 + headers['Content-Length'] = body.length;
  92 + }
  93 + // if method is not provided, use POST instead of GET
  94 + method = (options.method || '').toString().trim().toUpperCase() || 'POST';
  95 + }
  96 +
  97 + var req;
  98 + var reqOptions = {
  99 + method: method,
  100 + host: parsed.hostname,
  101 + path: parsed.path,
  102 + port: parsed.port ? parsed.port : (parsed.protocol === 'https:' ? 443 : 80),
  103 + headers: headers,
  104 + rejectUnauthorized: false,
  105 + agent: false
  106 + };
  107 +
  108 + if (options.tls) {
  109 + Object.keys(options.tls).forEach(function (key) {
  110 + reqOptions[key] = options.tls[key];
  111 + });
  112 + }
  113 +
  114 + try {
  115 + req = handler.request(reqOptions);
  116 + } catch (E) {
  117 + finished = true;
  118 + setImmediate(function () {
  119 + fetchRes.emit('error', E);
  120 + });
  121 + return fetchRes;
  122 + }
  123 +
  124 + if (options.timeout) {
  125 + req.setTimeout(options.timeout, function () {
  126 + if (finished) {
  127 + return;
  128 + }
  129 + finished = true;
  130 + req.abort();
  131 + fetchRes.emit('error', new Error('Request Tiemout'));
  132 + });
  133 + }
  134 +
  135 + req.on('error', function (err) {
  136 + if (finished) {
  137 + return;
  138 + }
  139 + finished = true;
  140 + fetchRes.emit('error', err);
  141 + });
  142 +
  143 + req.on('response', function (res) {
  144 + var inflate;
  145 +
  146 + if (finished) {
  147 + return;
  148 + }
  149 +
  150 + switch (res.headers['content-encoding']) {
  151 + case 'gzip':
  152 + case 'deflate':
  153 + inflate = zlib.createUnzip();
  154 + break;
  155 + }
  156 +
  157 + if (res.headers['set-cookie']) {
  158 + [].concat(res.headers['set-cookie'] || []).forEach(function (cookie) {
  159 + options.cookies.set(cookie, url);
  160 + });
  161 + }
  162 +
  163 + if ([301, 302, 303, 307, 308].indexOf(res.statusCode) >= 0 && res.headers.location) {
  164 + // redirect
  165 + options.redirects++;
  166 + if (options.redirects > options.maxRedirects) {
  167 + finished = true;
  168 + fetchRes.emit('error', new Error('Maximum redirect count exceeded'));
  169 + req.abort();
  170 + return;
  171 + }
  172 + return fetch(urllib.resolve(url, res.headers.location), options);
  173 + }
  174 +
  175 + if (res.statusCode >= 300) {
  176 + finished = true;
  177 + fetchRes.emit('error', new Error('Invalid status code ' + res.statusCode));
  178 + req.abort();
  179 + return;
  180 + }
  181 +
  182 + res.on('error', function (err) {
  183 + if (finished) {
  184 + return;
  185 + }
  186 + finished = true;
  187 + fetchRes.emit('error', err);
  188 + req.abort();
  189 + });
  190 +
  191 + if (inflate) {
  192 + res.pipe(inflate).pipe(fetchRes);
  193 + inflate.on('error', function (err) {
  194 + if (finished) {
  195 + return;
  196 + }
  197 + finished = true;
  198 + fetchRes.emit('error', err);
  199 + req.abort();
  200 + });
  201 + } else {
  202 + res.pipe(fetchRes);
  203 + }
  204 + });
  205 +
  206 + setImmediate(function () {
  207 + if (body) {
  208 + try {
  209 + if (typeof body.pipe === 'function') {
  210 + return body.pipe(req);
  211 + } else {
  212 + req.write(body);
  213 + }
  214 + } catch (err) {
  215 + finished = true;
  216 + fetchRes.emit('error', err);
  217 + return;
  218 + }
  219 + }
  220 + req.end();
  221 + });
  222 +
  223 + return fetchRes;
  224 +}