autobench 0.0.1alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +16 -0
  3. data/README.md +123 -0
  4. data/bin/autobench +180 -0
  5. data/bin/autobench-config +162 -0
  6. data/lib/autobench.rb +28 -0
  7. data/lib/autobench/client.rb +78 -0
  8. data/lib/autobench/common.rb +49 -0
  9. data/lib/autobench/config.rb +62 -0
  10. data/lib/autobench/render.rb +102 -0
  11. data/lib/autobench/version.rb +3 -0
  12. data/lib/autobench/yslow.rb +75 -0
  13. data/lib/phantomas/README.md +296 -0
  14. data/lib/phantomas/core/formatter.js +65 -0
  15. data/lib/phantomas/core/helper.js +64 -0
  16. data/lib/phantomas/core/modules/requestsMonitor/requestsMonitor.js +214 -0
  17. data/lib/phantomas/core/pads.js +16 -0
  18. data/lib/phantomas/core/phantomas.js +418 -0
  19. data/lib/phantomas/lib/args.js +27 -0
  20. data/lib/phantomas/lib/modules/_coffee-script.js +2 -0
  21. data/lib/phantomas/lib/modules/assert.js +326 -0
  22. data/lib/phantomas/lib/modules/events.js +216 -0
  23. data/lib/phantomas/lib/modules/http.js +55 -0
  24. data/lib/phantomas/lib/modules/path.js +441 -0
  25. data/lib/phantomas/lib/modules/punycode.js +510 -0
  26. data/lib/phantomas/lib/modules/querystring.js +214 -0
  27. data/lib/phantomas/lib/modules/tty.js +7 -0
  28. data/lib/phantomas/lib/modules/url.js +625 -0
  29. data/lib/phantomas/lib/modules/util.js +520 -0
  30. data/lib/phantomas/modules/ajaxRequests/ajaxRequests.js +15 -0
  31. data/lib/phantomas/modules/assetsTypes/assetsTypes.js +21 -0
  32. data/lib/phantomas/modules/cacheHits/cacheHits.js +28 -0
  33. data/lib/phantomas/modules/caching/caching.js +66 -0
  34. data/lib/phantomas/modules/cookies/cookies.js +54 -0
  35. data/lib/phantomas/modules/domComplexity/domComplexity.js +130 -0
  36. data/lib/phantomas/modules/domQueries/domQueries.js +148 -0
  37. data/lib/phantomas/modules/domains/domains.js +49 -0
  38. data/lib/phantomas/modules/globalVariables/globalVariables.js +44 -0
  39. data/lib/phantomas/modules/headers/headers.js +48 -0
  40. data/lib/phantomas/modules/localStorage/localStorage.js +14 -0
  41. data/lib/phantomas/modules/requestsStats/requestsStats.js +71 -0
  42. data/lib/phantomas/modules/staticAssets/staticAssets.js +40 -0
  43. data/lib/phantomas/modules/waterfall/waterfall.js +62 -0
  44. data/lib/phantomas/modules/windowPerformance/windowPerformance.js +36 -0
  45. data/lib/phantomas/package.json +27 -0
  46. data/lib/phantomas/phantomas.js +35 -0
  47. data/lib/phantomas/run-multiple.js +177 -0
  48. data/lib/yslow.js +5 -0
  49. metadata +135 -0
@@ -0,0 +1,214 @@
1
+ // Copyright Joyent, Inc. and other Node contributors.
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a
4
+ // copy of this software and associated documentation files (the
5
+ // "Software"), to deal in the Software without restriction, including
6
+ // without limitation the rights to use, copy, modify, merge, publish,
7
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ // persons to whom the Software is furnished to do so, subject to the
9
+ // following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included
12
+ // in all copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ // Query String Utilities
23
+
24
+ var QueryString = exports;
25
+
26
+
27
+ // If obj.hasOwnProperty has been overridden, then calling
28
+ // obj.hasOwnProperty(prop) will break.
29
+ // See: https://github.com/joyent/node/issues/1707
30
+ function hasOwnProperty(obj, prop) {
31
+ return Object.prototype.hasOwnProperty.call(obj, prop);
32
+ }
33
+
34
+
35
+ function charCode(c) {
36
+ return c.charCodeAt(0);
37
+ }
38
+
39
+
40
+ // a safe fast alternative to decodeURIComponent
41
+ QueryString.unescapeBuffer = function(s, decodeSpaces) {
42
+ var out = new Buffer(s.length);
43
+ var state = 'CHAR'; // states: CHAR, HEX0, HEX1
44
+ var n, m, hexchar;
45
+
46
+ for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) {
47
+ var c = s.charCodeAt(inIndex);
48
+ switch (state) {
49
+ case 'CHAR':
50
+ switch (c) {
51
+ case charCode('%'):
52
+ n = 0;
53
+ m = 0;
54
+ state = 'HEX0';
55
+ break;
56
+ case charCode('+'):
57
+ if (decodeSpaces) c = charCode(' ');
58
+ // pass thru
59
+ default:
60
+ out[outIndex++] = c;
61
+ break;
62
+ }
63
+ break;
64
+
65
+ case 'HEX0':
66
+ state = 'HEX1';
67
+ hexchar = c;
68
+ if (charCode('0') <= c && c <= charCode('9')) {
69
+ n = c - charCode('0');
70
+ } else if (charCode('a') <= c && c <= charCode('f')) {
71
+ n = c - charCode('a') + 10;
72
+ } else if (charCode('A') <= c && c <= charCode('F')) {
73
+ n = c - charCode('A') + 10;
74
+ } else {
75
+ out[outIndex++] = charCode('%');
76
+ out[outIndex++] = c;
77
+ state = 'CHAR';
78
+ break;
79
+ }
80
+ break;
81
+
82
+ case 'HEX1':
83
+ state = 'CHAR';
84
+ if (charCode('0') <= c && c <= charCode('9')) {
85
+ m = c - charCode('0');
86
+ } else if (charCode('a') <= c && c <= charCode('f')) {
87
+ m = c - charCode('a') + 10;
88
+ } else if (charCode('A') <= c && c <= charCode('F')) {
89
+ m = c - charCode('A') + 10;
90
+ } else {
91
+ out[outIndex++] = charCode('%');
92
+ out[outIndex++] = hexchar;
93
+ out[outIndex++] = c;
94
+ break;
95
+ }
96
+ out[outIndex++] = 16 * n + m;
97
+ break;
98
+ }
99
+ }
100
+
101
+ // TODO support returning arbitrary buffers.
102
+
103
+ return out.slice(0, outIndex - 1);
104
+ };
105
+
106
+
107
+ QueryString.unescape = function(s, decodeSpaces) {
108
+ return QueryString.unescapeBuffer(s, decodeSpaces).toString();
109
+ };
110
+
111
+
112
+ QueryString.escape = function(str) {
113
+ return encodeURIComponent(str);
114
+ };
115
+
116
+ var stringifyPrimitive = function(v) {
117
+ switch (typeof v) {
118
+ case 'string':
119
+ return v;
120
+
121
+ case 'boolean':
122
+ return v ? 'true' : 'false';
123
+
124
+ case 'number':
125
+ return isFinite(v) ? v : '';
126
+
127
+ default:
128
+ return '';
129
+ }
130
+ };
131
+
132
+
133
+ QueryString.stringify = QueryString.encode = function(obj, sep, eq, name) {
134
+ sep = sep || '&';
135
+ eq = eq || '=';
136
+ if (obj === null) {
137
+ obj = undefined;
138
+ }
139
+
140
+ if (typeof obj === 'object') {
141
+ return Object.keys(obj).map(function(k) {
142
+ var ks = QueryString.escape(stringifyPrimitive(k)) + eq;
143
+ if (Array.isArray(obj[k])) {
144
+ return obj[k].map(function(v) {
145
+ return ks + QueryString.escape(stringifyPrimitive(v));
146
+ }).join(sep);
147
+ } else {
148
+ return ks + QueryString.escape(stringifyPrimitive(obj[k]));
149
+ }
150
+ }).join(sep);
151
+
152
+ }
153
+
154
+ if (!name) return '';
155
+ return QueryString.escape(stringifyPrimitive(name)) + eq +
156
+ QueryString.escape(stringifyPrimitive(obj));
157
+ };
158
+
159
+ // Parse a key=val string.
160
+ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
161
+ sep = sep || '&';
162
+ eq = eq || '=';
163
+ var obj = {};
164
+
165
+ if (typeof qs !== 'string' || qs.length === 0) {
166
+ return obj;
167
+ }
168
+
169
+ var regexp = /\+/g;
170
+ qs = qs.split(sep);
171
+
172
+ var maxKeys = 1000;
173
+ if (options && typeof options.maxKeys === 'number') {
174
+ maxKeys = options.maxKeys;
175
+ }
176
+
177
+ var len = qs.length;
178
+ // maxKeys <= 0 means that we should not limit keys count
179
+ if (maxKeys > 0 && len > maxKeys) {
180
+ len = maxKeys;
181
+ }
182
+
183
+ for (var i = 0; i < len; ++i) {
184
+ var x = qs[i].replace(regexp, '%20'),
185
+ idx = x.indexOf(eq),
186
+ kstr, vstr, k, v;
187
+
188
+ if (idx >= 0) {
189
+ kstr = x.substr(0, idx);
190
+ vstr = x.substr(idx + 1);
191
+ } else {
192
+ kstr = x;
193
+ vstr = '';
194
+ }
195
+
196
+ try {
197
+ k = decodeURIComponent(kstr);
198
+ v = decodeURIComponent(vstr);
199
+ } catch (e) {
200
+ k = QueryString.unescape(kstr, true);
201
+ v = QueryString.unescape(vstr, true);
202
+ }
203
+
204
+ if (!hasOwnProperty(obj, k)) {
205
+ obj[k] = v;
206
+ } else if (Array.isArray(obj[k])) {
207
+ obj[k].push(v);
208
+ } else {
209
+ obj[k] = [obj[k], v];
210
+ }
211
+ }
212
+
213
+ return obj;
214
+ };
@@ -0,0 +1,7 @@
1
+ exports.isatty = function(){
2
+ return true;
3
+ };
4
+
5
+ exports.getWindowSize = function(){
6
+ return [window.innerHeight, window.innerWidth];
7
+ };
@@ -0,0 +1,625 @@
1
+ // Copyright Joyent, Inc. and other Node contributors.
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a
4
+ // copy of this software and associated documentation files (the
5
+ // "Software"), to deal in the Software without restriction, including
6
+ // without limitation the rights to use, copy, modify, merge, publish,
7
+ // distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ // persons to whom the Software is furnished to do so, subject to the
9
+ // following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included
12
+ // in all copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
+ // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18
+ // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20
+ // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ var punycode = require('./punycode');
23
+
24
+ exports.parse = urlParse;
25
+ exports.resolve = urlResolve;
26
+ exports.resolveObject = urlResolveObject;
27
+ exports.format = urlFormat;
28
+
29
+ // Reference: RFC 3986, RFC 1808, RFC 2396
30
+
31
+ // define these here so at least they only have to be
32
+ // compiled once on the first module load.
33
+ var protocolPattern = /^([a-z0-9.+-]+:)/i,
34
+ portPattern = /:[0-9]*$/,
35
+
36
+ // RFC 2396: characters reserved for delimiting URLs.
37
+ // We actually just auto-escape these.
38
+ delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
39
+
40
+ // RFC 2396: characters not allowed for various reasons.
41
+ unwise = ['{', '}', '|', '\\', '^', '~', '`'].concat(delims),
42
+
43
+ // Allowed by RFCs, but cause of XSS attacks. Always escape these.
44
+ autoEscape = ['\''].concat(delims),
45
+ // Characters that are never ever allowed in a hostname.
46
+ // Note that any invalid chars are also handled, but these
47
+ // are the ones that are *expected* to be seen, so we fast-path
48
+ // them.
49
+ nonHostChars = ['%', '/', '?', ';', '#']
50
+ .concat(unwise).concat(autoEscape),
51
+ nonAuthChars = ['/', '@', '?', '#'].concat(delims),
52
+ hostnameMaxLen = 255,
53
+ hostnamePartPattern = /^[a-zA-Z0-9][a-z0-9A-Z_-]{0,62}$/,
54
+ hostnamePartStart = /^([a-zA-Z0-9][a-z0-9A-Z_-]{0,62})(.*)$/,
55
+ // protocols that can allow "unsafe" and "unwise" chars.
56
+ unsafeProtocol = {
57
+ 'javascript': true,
58
+ 'javascript:': true
59
+ },
60
+ // protocols that never have a hostname.
61
+ hostlessProtocol = {
62
+ 'javascript': true,
63
+ 'javascript:': true
64
+ },
65
+ // protocols that always have a path component.
66
+ pathedProtocol = {
67
+ 'http': true,
68
+ 'https': true,
69
+ 'ftp': true,
70
+ 'gopher': true,
71
+ 'file': true,
72
+ 'http:': true,
73
+ 'ftp:': true,
74
+ 'gopher:': true,
75
+ 'file:': true
76
+ },
77
+ // protocols that always contain a // bit.
78
+ slashedProtocol = {
79
+ 'http': true,
80
+ 'https': true,
81
+ 'ftp': true,
82
+ 'gopher': true,
83
+ 'file': true,
84
+ 'http:': true,
85
+ 'https:': true,
86
+ 'ftp:': true,
87
+ 'gopher:': true,
88
+ 'file:': true
89
+ },
90
+ querystring = require('./querystring');
91
+
92
+ function urlParse(url, parseQueryString, slashesDenoteHost) {
93
+ if (url && typeof(url) === 'object' && url.href) return url;
94
+
95
+ if (typeof url !== 'string') {
96
+ throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
97
+ }
98
+
99
+ var out = {},
100
+ rest = url;
101
+
102
+ // trim before proceeding.
103
+ // This is to support parse stuff like " http://foo.com \n"
104
+ rest = rest.trim();
105
+
106
+ var proto = protocolPattern.exec(rest);
107
+ if (proto) {
108
+ proto = proto[0];
109
+ var lowerProto = proto.toLowerCase();
110
+ out.protocol = lowerProto;
111
+ rest = rest.substr(proto.length);
112
+ }
113
+
114
+ // figure out if it's got a host
115
+ // user@server is *always* interpreted as a hostname, and url
116
+ // resolution will treat //foo/bar as host=foo,path=bar because that's
117
+ // how the browser resolves relative URLs.
118
+ if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
119
+ var slashes = rest.substr(0, 2) === '//';
120
+ if (slashes && !(proto && hostlessProtocol[proto])) {
121
+ rest = rest.substr(2);
122
+ out.slashes = true;
123
+ }
124
+ }
125
+
126
+ if (!hostlessProtocol[proto] &&
127
+ (slashes || (proto && !slashedProtocol[proto]))) {
128
+ // there's a hostname.
129
+ // the first instance of /, ?, ;, or # ends the host.
130
+ // don't enforce full RFC correctness, just be unstupid about it.
131
+
132
+ // If there is an @ in the hostname, then non-host chars *are* allowed
133
+ // to the left of the first @ sign, unless some non-auth character
134
+ // comes *before* the @-sign.
135
+ // URLs are obnoxious.
136
+ var atSign = rest.indexOf('@');
137
+ if (atSign !== -1) {
138
+ var auth = rest.slice(0, atSign);
139
+
140
+ // there *may be* an auth
141
+ var hasAuth = true;
142
+ for (var i = 0, l = nonAuthChars.length; i < l; i++) {
143
+ if (auth.indexOf(nonAuthChars[i]) !== -1) {
144
+ // not a valid auth. Something like http://foo.com/bar@baz/
145
+ hasAuth = false;
146
+ break;
147
+ }
148
+ }
149
+
150
+ if (hasAuth) {
151
+ // pluck off the auth portion.
152
+ out.auth = decodeURIComponent(auth);
153
+ rest = rest.substr(atSign + 1);
154
+ }
155
+ }
156
+
157
+ var firstNonHost = -1;
158
+ for (var i = 0, l = nonHostChars.length; i < l; i++) {
159
+ var index = rest.indexOf(nonHostChars[i]);
160
+ if (index !== -1 &&
161
+ (firstNonHost < 0 || index < firstNonHost)) firstNonHost = index;
162
+ }
163
+
164
+ if (firstNonHost !== -1) {
165
+ out.host = rest.substr(0, firstNonHost);
166
+ rest = rest.substr(firstNonHost);
167
+ } else {
168
+ out.host = rest;
169
+ rest = '';
170
+ }
171
+
172
+ // pull out port.
173
+ var p = parseHost(out.host);
174
+ var keys = Object.keys(p);
175
+ for (var i = 0, l = keys.length; i < l; i++) {
176
+ var key = keys[i];
177
+ out[key] = p[key];
178
+ }
179
+
180
+ // we've indicated that there is a hostname,
181
+ // so even if it's empty, it has to be present.
182
+ out.hostname = out.hostname || '';
183
+
184
+ // if hostname begins with [ and ends with ]
185
+ // assume that it's an IPv6 address.
186
+ var ipv6Hostname = out.hostname[0] === '[' &&
187
+ out.hostname[out.hostname.length - 1] === ']';
188
+
189
+ // validate a little.
190
+ if (out.hostname.length > hostnameMaxLen) {
191
+ out.hostname = '';
192
+ } else if (!ipv6Hostname) {
193
+ var hostparts = out.hostname.split(/\./);
194
+ for (var i = 0, l = hostparts.length; i < l; i++) {
195
+ var part = hostparts[i];
196
+ if (!part) continue;
197
+ if (!part.match(hostnamePartPattern)) {
198
+ var newpart = '';
199
+ for (var j = 0, k = part.length; j < k; j++) {
200
+ if (part.charCodeAt(j) > 127) {
201
+ // we replace non-ASCII char with a temporary placeholder
202
+ // we need this to make sure size of hostname is not
203
+ // broken by replacing non-ASCII by nothing
204
+ newpart += 'x';
205
+ } else {
206
+ newpart += part[j];
207
+ }
208
+ }
209
+ // we test again with ASCII char only
210
+ if (!newpart.match(hostnamePartPattern)) {
211
+ var validParts = hostparts.slice(0, i);
212
+ var notHost = hostparts.slice(i + 1);
213
+ var bit = part.match(hostnamePartStart);
214
+ if (bit) {
215
+ validParts.push(bit[1]);
216
+ notHost.unshift(bit[2]);
217
+ }
218
+ if (notHost.length) {
219
+ rest = '/' + notHost.join('.') + rest;
220
+ }
221
+ out.hostname = validParts.join('.');
222
+ break;
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ // hostnames are always lower case.
229
+ out.hostname = out.hostname.toLowerCase();
230
+
231
+ if (!ipv6Hostname) {
232
+ // IDNA Support: Returns a puny coded representation of "domain".
233
+ // It only converts the part of the domain name that
234
+ // has non ASCII characters. I.e. it dosent matter if
235
+ // you call it with a domain that already is in ASCII.
236
+ var domainArray = out.hostname.split('.');
237
+ var newOut = [];
238
+ for (var i = 0; i < domainArray.length; ++i) {
239
+ var s = domainArray[i];
240
+ newOut.push(s.match(/[^A-Za-z0-9_-]/) ?
241
+ 'xn--' + punycode.encode(s) : s);
242
+ }
243
+ out.hostname = newOut.join('.');
244
+ }
245
+
246
+ out.host = (out.hostname || '') +
247
+ ((out.port) ? ':' + out.port : '');
248
+ out.href += out.host;
249
+
250
+ // strip [ and ] from the hostname
251
+ if (ipv6Hostname) {
252
+ out.hostname = out.hostname.substr(1, out.hostname.length - 2);
253
+ if (rest[0] !== '/') {
254
+ rest = '/' + rest;
255
+ }
256
+ }
257
+ }
258
+
259
+ // now rest is set to the post-host stuff.
260
+ // chop off any delim chars.
261
+ if (!unsafeProtocol[lowerProto]) {
262
+
263
+ // First, make 100% sure that any "autoEscape" chars get
264
+ // escaped, even if encodeURIComponent doesn't think they
265
+ // need to be.
266
+ for (var i = 0, l = autoEscape.length; i < l; i++) {
267
+ var ae = autoEscape[i];
268
+ var esc = encodeURIComponent(ae);
269
+ if (esc === ae) {
270
+ esc = escape(ae);
271
+ }
272
+ rest = rest.split(ae).join(esc);
273
+ }
274
+ }
275
+
276
+
277
+ // chop off from the tail first.
278
+ var hash = rest.indexOf('#');
279
+ if (hash !== -1) {
280
+ // got a fragment string.
281
+ out.hash = rest.substr(hash);
282
+ rest = rest.slice(0, hash);
283
+ }
284
+ var qm = rest.indexOf('?');
285
+ if (qm !== -1) {
286
+ out.search = rest.substr(qm);
287
+ out.query = rest.substr(qm + 1);
288
+ if (parseQueryString) {
289
+ out.query = querystring.parse(out.query);
290
+ }
291
+ rest = rest.slice(0, qm);
292
+ } else if (parseQueryString) {
293
+ // no query string, but parseQueryString still requested
294
+ out.search = '';
295
+ out.query = {};
296
+ }
297
+ if (rest) out.pathname = rest;
298
+ if (slashedProtocol[proto] &&
299
+ out.hostname && !out.pathname) {
300
+ out.pathname = '/';
301
+ }
302
+
303
+ //to support http.request
304
+ if (out.pathname || out.search) {
305
+ out.path = (out.pathname ? out.pathname : '') +
306
+ (out.search ? out.search : '');
307
+ }
308
+
309
+ // finally, reconstruct the href based on what has been validated.
310
+ out.href = urlFormat(out);
311
+ return out;
312
+ }
313
+
314
+ // format a parsed object into a url string
315
+ function urlFormat(obj) {
316
+ // ensure it's an object, and not a string url.
317
+ // If it's an obj, this is a no-op.
318
+ // this way, you can call url_format() on strings
319
+ // to clean up potentially wonky urls.
320
+ if (typeof(obj) === 'string') obj = urlParse(obj);
321
+
322
+ var auth = obj.auth || '';
323
+ if (auth) {
324
+ auth = encodeURIComponent(auth);
325
+ auth = auth.replace(/%3A/i, ':');
326
+ auth += '@';
327
+ }
328
+
329
+ var protocol = obj.protocol || '',
330
+ pathname = obj.pathname || '',
331
+ hash = obj.hash || '',
332
+ host = false,
333
+ query = '';
334
+
335
+ if (obj.host !== undefined) {
336
+ host = auth + obj.host;
337
+ } else if (obj.hostname !== undefined) {
338
+ host = auth + (obj.hostname.indexOf(':') === -1 ?
339
+ obj.hostname :
340
+ '[' + obj.hostname + ']');
341
+ if (obj.port) {
342
+ host += ':' + obj.port;
343
+ }
344
+ }
345
+
346
+ if (obj.query && typeof obj.query === 'object' &&
347
+ Object.keys(obj.query).length) {
348
+ query = querystring.stringify(obj.query);
349
+ }
350
+
351
+ var search = obj.search || (query && ('?' + query)) || '';
352
+
353
+ if (protocol && protocol.substr(-1) !== ':') protocol += ':';
354
+
355
+ // only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
356
+ // unless they had them to begin with.
357
+ if (obj.slashes ||
358
+ (!protocol || slashedProtocol[protocol]) && host !== false) {
359
+ host = '//' + (host || '');
360
+ if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
361
+ } else if (!host) {
362
+ host = '';
363
+ }
364
+
365
+ if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
366
+ if (search && search.charAt(0) !== '?') search = '?' + search;
367
+
368
+ return protocol + host + pathname + search + hash;
369
+ }
370
+
371
+ function urlResolve(source, relative) {
372
+ return urlFormat(urlResolveObject(source, relative));
373
+ }
374
+
375
+ function urlResolveObject(source, relative) {
376
+ if (!source) return relative;
377
+
378
+ source = urlParse(urlFormat(source), false, true);
379
+ relative = urlParse(urlFormat(relative), false, true);
380
+
381
+ // hash is always overridden, no matter what.
382
+ source.hash = relative.hash;
383
+
384
+ if (relative.href === '') {
385
+ source.href = urlFormat(source);
386
+ return source;
387
+ }
388
+
389
+ // hrefs like //foo/bar always cut to the protocol.
390
+ if (relative.slashes && !relative.protocol) {
391
+ relative.protocol = source.protocol;
392
+ //urlParse appends trailing / to urls like http://www.example.com
393
+ if (slashedProtocol[relative.protocol] &&
394
+ relative.hostname && !relative.pathname) {
395
+ relative.path = relative.pathname = '/';
396
+ }
397
+ relative.href = urlFormat(relative);
398
+ return relative;
399
+ }
400
+
401
+ if (relative.protocol && relative.protocol !== source.protocol) {
402
+ // if it's a known url protocol, then changing
403
+ // the protocol does weird things
404
+ // first, if it's not file:, then we MUST have a host,
405
+ // and if there was a path
406
+ // to begin with, then we MUST have a path.
407
+ // if it is file:, then the host is dropped,
408
+ // because that's known to be hostless.
409
+ // anything else is assumed to be absolute.
410
+ if (!slashedProtocol[relative.protocol]) {
411
+ relative.href = urlFormat(relative);
412
+ return relative;
413
+ }
414
+ source.protocol = relative.protocol;
415
+ if (!relative.host && !hostlessProtocol[relative.protocol]) {
416
+ var relPath = (relative.pathname || '').split('/');
417
+ while (relPath.length && !(relative.host = relPath.shift()));
418
+ if (!relative.host) relative.host = '';
419
+ if (!relative.hostname) relative.hostname = '';
420
+ if (relPath[0] !== '') relPath.unshift('');
421
+ if (relPath.length < 2) relPath.unshift('');
422
+ relative.pathname = relPath.join('/');
423
+ }
424
+ source.pathname = relative.pathname;
425
+ source.search = relative.search;
426
+ source.query = relative.query;
427
+ source.host = relative.host || '';
428
+ source.auth = relative.auth;
429
+ source.hostname = relative.hostname || relative.host;
430
+ source.port = relative.port;
431
+ //to support http.request
432
+ if (source.pathname !== undefined || source.search !== undefined) {
433
+ source.path = (source.pathname ? source.pathname : '') +
434
+ (source.search ? source.search : '');
435
+ }
436
+ source.slashes = source.slashes || relative.slashes;
437
+ source.href = urlFormat(source);
438
+ return source;
439
+ }
440
+
441
+ var isSourceAbs = (source.pathname && source.pathname.charAt(0) === '/'),
442
+ isRelAbs = (
443
+ relative.host !== undefined ||
444
+ relative.pathname && relative.pathname.charAt(0) === '/'
445
+ ),
446
+ mustEndAbs = (isRelAbs || isSourceAbs ||
447
+ (source.host && relative.pathname)),
448
+ removeAllDots = mustEndAbs,
449
+ srcPath = source.pathname && source.pathname.split('/') || [],
450
+ relPath = relative.pathname && relative.pathname.split('/') || [],
451
+ psychotic = source.protocol &&
452
+ !slashedProtocol[source.protocol];
453
+
454
+ // if the url is a non-slashed url, then relative
455
+ // links like ../.. should be able
456
+ // to crawl up to the hostname, as well. This is strange.
457
+ // source.protocol has already been set by now.
458
+ // Later on, put the first path part into the host field.
459
+ if (psychotic) {
460
+
461
+ delete source.hostname;
462
+ delete source.port;
463
+ if (source.host) {
464
+ if (srcPath[0] === '') srcPath[0] = source.host;
465
+ else srcPath.unshift(source.host);
466
+ }
467
+ delete source.host;
468
+ if (relative.protocol) {
469
+ delete relative.hostname;
470
+ delete relative.port;
471
+ if (relative.host) {
472
+ if (relPath[0] === '') relPath[0] = relative.host;
473
+ else relPath.unshift(relative.host);
474
+ }
475
+ delete relative.host;
476
+ }
477
+ mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
478
+ }
479
+
480
+ if (isRelAbs) {
481
+ // it's absolute.
482
+ source.host = (relative.host || relative.host === '') ?
483
+ relative.host : source.host;
484
+ source.hostname = (relative.hostname || relative.hostname === '') ?
485
+ relative.hostname : source.hostname;
486
+ source.search = relative.search;
487
+ source.query = relative.query;
488
+ srcPath = relPath;
489
+ // fall through to the dot-handling below.
490
+ } else if (relPath.length) {
491
+ // it's relative
492
+ // throw away the existing file, and take the new path instead.
493
+ if (!srcPath) srcPath = [];
494
+ srcPath.pop();
495
+ srcPath = srcPath.concat(relPath);
496
+ source.search = relative.search;
497
+ source.query = relative.query;
498
+ } else if ('search' in relative) {
499
+ // just pull out the search.
500
+ // like href='?foo'.
501
+ // Put this after the other two cases because it simplifies the booleans
502
+ if (psychotic) {
503
+ source.hostname = source.host = srcPath.shift();
504
+ //occationaly the auth can get stuck only in host
505
+ //this especialy happens in cases like
506
+ //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
507
+ var authInHost = source.host && source.host.indexOf('@') > 0 ?
508
+ source.host.split('@') : false;
509
+ if (authInHost) {
510
+ source.auth = authInHost.shift();
511
+ source.host = source.hostname = authInHost.shift();
512
+ }
513
+ }
514
+ source.search = relative.search;
515
+ source.query = relative.query;
516
+ //to support http.request
517
+ if (source.pathname !== undefined || source.search !== undefined) {
518
+ source.path = (source.pathname ? source.pathname : '') +
519
+ (source.search ? source.search : '');
520
+ }
521
+ source.href = urlFormat(source);
522
+ return source;
523
+ }
524
+ if (!srcPath.length) {
525
+ // no path at all. easy.
526
+ // we've already handled the other stuff above.
527
+ delete source.pathname;
528
+ //to support http.request
529
+ if (!source.search) {
530
+ source.path = '/' + source.search;
531
+ } else {
532
+ delete source.path;
533
+ }
534
+ source.href = urlFormat(source);
535
+ return source;
536
+ }
537
+ // if a url ENDs in . or .., then it must get a trailing slash.
538
+ // however, if it ends in anything else non-slashy,
539
+ // then it must NOT get a trailing slash.
540
+ var last = srcPath.slice(-1)[0];
541
+ var hasTrailingSlash = (
542
+ (source.host || relative.host) && (last === '.' || last === '..') ||
543
+ last === '');
544
+
545
+ // strip single dots, resolve double dots to parent dir
546
+ // if the path tries to go above the root, `up` ends up > 0
547
+ var up = 0;
548
+ for (var i = srcPath.length; i >= 0; i--) {
549
+ last = srcPath[i];
550
+ if (last == '.') {
551
+ srcPath.splice(i, 1);
552
+ } else if (last === '..') {
553
+ srcPath.splice(i, 1);
554
+ up++;
555
+ } else if (up) {
556
+ srcPath.splice(i, 1);
557
+ up--;
558
+ }
559
+ }
560
+
561
+ // if the path is allowed to go above the root, restore leading ..s
562
+ if (!mustEndAbs && !removeAllDots) {
563
+ for (; up--; up) {
564
+ srcPath.unshift('..');
565
+ }
566
+ }
567
+
568
+ if (mustEndAbs && srcPath[0] !== '' &&
569
+ (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
570
+ srcPath.unshift('');
571
+ }
572
+
573
+ if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
574
+ srcPath.push('');
575
+ }
576
+
577
+ var isAbsolute = srcPath[0] === '' ||
578
+ (srcPath[0] && srcPath[0].charAt(0) === '/');
579
+
580
+ // put the host back
581
+ if (psychotic) {
582
+ source.hostname = source.host = isAbsolute ? '' :
583
+ srcPath.length ? srcPath.shift() : '';
584
+ //occationaly the auth can get stuck only in host
585
+ //this especialy happens in cases like
586
+ //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
587
+ var authInHost = source.host && source.host.indexOf('@') > 0 ?
588
+ source.host.split('@') : false;
589
+ if (authInHost) {
590
+ source.auth = authInHost.shift();
591
+ source.host = source.hostname = authInHost.shift();
592
+ }
593
+ }
594
+
595
+ mustEndAbs = mustEndAbs || (source.host && srcPath.length);
596
+
597
+ if (mustEndAbs && !isAbsolute) {
598
+ srcPath.unshift('');
599
+ }
600
+
601
+ source.pathname = srcPath.join('/');
602
+ //to support request.http
603
+ if (source.pathname !== undefined || source.search !== undefined) {
604
+ source.path = (source.pathname ? source.pathname : '') +
605
+ (source.search ? source.search : '');
606
+ }
607
+ source.auth = relative.auth || source.auth;
608
+ source.slashes = source.slashes || relative.slashes;
609
+ source.href = urlFormat(source);
610
+ return source;
611
+ }
612
+
613
+ function parseHost(host) {
614
+ var out = {};
615
+ var port = portPattern.exec(host);
616
+ if (port) {
617
+ port = port[0];
618
+ if (port !== ':') {
619
+ out.port = port.substr(1);
620
+ }
621
+ host = host.substr(0, host.length - port.length);
622
+ }
623
+ if (host) out.hostname = host;
624
+ return out;
625
+ }