macgyver 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Growl +0 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Growl +0 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/Growl.h +5 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlApplicationBridge.h +551 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Headers/GrowlDefines.h +341 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/Resources/Info.plist +40 -0
- data/assets/MacGap.app/Contents/Frameworks/Growl.framework/Versions/A/_CodeSignature/CodeResources +34 -0
- data/assets/MacGap.app/Contents/Info.plist +48 -0
- data/assets/MacGap.app/Contents/MacOS/MacGap +0 -0
- data/assets/MacGap.app/Contents/PkgInfo +1 -0
- data/assets/MacGap.app/Contents/Resources/_debugger.js +1718 -0
- data/assets/MacGap.app/Contents/Resources/_http_agent.js +310 -0
- data/assets/MacGap.app/Contents/Resources/_http_client.js +533 -0
- data/assets/MacGap.app/Contents/Resources/_http_common.js +222 -0
- data/assets/MacGap.app/Contents/Resources/_http_incoming.js +194 -0
- data/assets/MacGap.app/Contents/Resources/_http_outgoing.js +597 -0
- data/assets/MacGap.app/Contents/Resources/_http_server.js +510 -0
- data/assets/MacGap.app/Contents/Resources/_linklist.js +76 -0
- data/assets/MacGap.app/Contents/Resources/_stream_duplex.js +69 -0
- data/assets/MacGap.app/Contents/Resources/_stream_passthrough.js +41 -0
- data/assets/MacGap.app/Contents/Resources/_stream_readable.js +900 -0
- data/assets/MacGap.app/Contents/Resources/_stream_transform.js +204 -0
- data/assets/MacGap.app/Contents/Resources/_stream_writable.js +456 -0
- data/assets/MacGap.app/Contents/Resources/_tls_legacy.js +887 -0
- data/assets/MacGap.app/Contents/Resources/_tls_wrap.js +831 -0
- data/assets/MacGap.app/Contents/Resources/application.icns +0 -0
- data/assets/MacGap.app/Contents/Resources/assert.js +326 -0
- data/assets/MacGap.app/Contents/Resources/buffer.js +724 -0
- data/assets/MacGap.app/Contents/Resources/child_process.js +1107 -0
- data/assets/MacGap.app/Contents/Resources/cluster.js +613 -0
- data/assets/MacGap.app/Contents/Resources/console.js +108 -0
- data/assets/MacGap.app/Contents/Resources/constants.js +22 -0
- data/assets/MacGap.app/Contents/Resources/crypto.js +691 -0
- data/assets/MacGap.app/Contents/Resources/dgram.js +459 -0
- data/assets/MacGap.app/Contents/Resources/dns.js +274 -0
- data/assets/MacGap.app/Contents/Resources/domain.js +292 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/Credits.rtf +29 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/MainMenu.nib +0 -0
- data/assets/MacGap.app/Contents/Resources/en.lproj/Window.nib +0 -0
- data/assets/MacGap.app/Contents/Resources/events.js +312 -0
- data/assets/MacGap.app/Contents/Resources/freelist.js +43 -0
- data/assets/MacGap.app/Contents/Resources/fs.js +1732 -0
- data/assets/MacGap.app/Contents/Resources/http.js +119 -0
- data/assets/MacGap.app/Contents/Resources/https.js +134 -0
- data/assets/MacGap.app/Contents/Resources/module.js +529 -0
- data/assets/MacGap.app/Contents/Resources/net.js +1378 -0
- data/assets/MacGap.app/Contents/Resources/nodelike.js +195 -0
- data/assets/MacGap.app/Contents/Resources/os.js +64 -0
- data/assets/MacGap.app/Contents/Resources/path.js +517 -0
- data/assets/MacGap.app/Contents/Resources/public/index.html +38 -0
- data/assets/MacGap.app/Contents/Resources/punycode.js +507 -0
- data/assets/MacGap.app/Contents/Resources/querystring.js +206 -0
- data/assets/MacGap.app/Contents/Resources/readline.js +1311 -0
- data/assets/MacGap.app/Contents/Resources/repl.js +945 -0
- data/assets/MacGap.app/Contents/Resources/smalloc.js +90 -0
- data/assets/MacGap.app/Contents/Resources/stream.js +127 -0
- data/assets/MacGap.app/Contents/Resources/string_decoder.js +189 -0
- data/assets/MacGap.app/Contents/Resources/sys.js +24 -0
- data/assets/MacGap.app/Contents/Resources/timers.js +568 -0
- data/assets/MacGap.app/Contents/Resources/tls.js +220 -0
- data/assets/MacGap.app/Contents/Resources/tty.js +129 -0
- data/assets/MacGap.app/Contents/Resources/url.js +693 -0
- data/assets/MacGap.app/Contents/Resources/util.js +688 -0
- data/assets/MacGap.app/Contents/Resources/vm.js +73 -0
- data/assets/MacGap.app/Contents/Resources/zlib.js +524 -0
- data/assets/index.html +38 -0
- data/bin/macgyver +104 -0
- data/macgyver.gemspec +19 -0
- data/test/public/index.html +27 -0
- metadata +121 -0
@@ -0,0 +1,220 @@
|
|
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 net = require('net');
|
23
|
+
var url = require('url');
|
24
|
+
var util = require('util');
|
25
|
+
|
26
|
+
exports.DEFAULT_CIPHERS =
|
27
|
+
'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:' + // TLS 1.2
|
28
|
+
'RC4:HIGH:!MD5:!aNULL:!EDH'; // TLS 1.0
|
29
|
+
|
30
|
+
exports.DEFAULT_ECDH_CURVE = 'prime256v1';
|
31
|
+
|
32
|
+
// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
|
33
|
+
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
|
34
|
+
// renegotations are seen. The settings are applied to all remote client
|
35
|
+
// connections.
|
36
|
+
exports.CLIENT_RENEG_LIMIT = 3;
|
37
|
+
exports.CLIENT_RENEG_WINDOW = 600;
|
38
|
+
|
39
|
+
exports.SLAB_BUFFER_SIZE = 10 * 1024 * 1024;
|
40
|
+
|
41
|
+
exports.getCiphers = function() {
|
42
|
+
var names = process.binding('crypto').getSSLCiphers();
|
43
|
+
// Drop all-caps names in favor of their lowercase aliases,
|
44
|
+
var ctx = {};
|
45
|
+
names.forEach(function(name) {
|
46
|
+
if (/^[0-9A-Z\-]+$/.test(name)) name = name.toLowerCase();
|
47
|
+
ctx[name] = true;
|
48
|
+
});
|
49
|
+
return Object.getOwnPropertyNames(ctx).sort();
|
50
|
+
};
|
51
|
+
|
52
|
+
// Convert protocols array into valid OpenSSL protocols list
|
53
|
+
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
|
54
|
+
exports.convertNPNProtocols = function convertNPNProtocols(NPNProtocols, out) {
|
55
|
+
// If NPNProtocols is Array - translate it into buffer
|
56
|
+
if (util.isArray(NPNProtocols)) {
|
57
|
+
var buff = new Buffer(NPNProtocols.reduce(function(p, c) {
|
58
|
+
return p + 1 + Buffer.byteLength(c);
|
59
|
+
}, 0));
|
60
|
+
|
61
|
+
NPNProtocols.reduce(function(offset, c) {
|
62
|
+
var clen = Buffer.byteLength(c);
|
63
|
+
buff[offset] = clen;
|
64
|
+
buff.write(c, offset + 1);
|
65
|
+
|
66
|
+
return offset + 1 + clen;
|
67
|
+
}, 0);
|
68
|
+
|
69
|
+
NPNProtocols = buff;
|
70
|
+
}
|
71
|
+
|
72
|
+
// If it's already a Buffer - store it
|
73
|
+
if (util.isBuffer(NPNProtocols)) {
|
74
|
+
out.NPNProtocols = NPNProtocols;
|
75
|
+
}
|
76
|
+
};
|
77
|
+
|
78
|
+
exports.checkServerIdentity = function checkServerIdentity(host, cert) {
|
79
|
+
// Create regexp to much hostnames
|
80
|
+
function regexpify(host, wildcards) {
|
81
|
+
// Add trailing dot (make hostnames uniform)
|
82
|
+
if (!/\.$/.test(host)) host += '.';
|
83
|
+
|
84
|
+
// The same applies to hostname with more than one wildcard,
|
85
|
+
// if hostname has wildcard when wildcards are not allowed,
|
86
|
+
// or if there are less than two dots after wildcard (i.e. *.com or *d.com)
|
87
|
+
//
|
88
|
+
// also
|
89
|
+
//
|
90
|
+
// "The client SHOULD NOT attempt to match a presented identifier in
|
91
|
+
// which the wildcard character comprises a label other than the
|
92
|
+
// left-most label (e.g., do not match bar.*.example.net)."
|
93
|
+
// RFC6125
|
94
|
+
if (!wildcards && /\*/.test(host) || /[\.\*].*\*/.test(host) ||
|
95
|
+
/\*/.test(host) && !/\*.*\..+\..+/.test(host)) {
|
96
|
+
return /$./;
|
97
|
+
}
|
98
|
+
|
99
|
+
// Replace wildcard chars with regexp's wildcard and
|
100
|
+
// escape all characters that have special meaning in regexps
|
101
|
+
// (i.e. '.', '[', '{', '*', and others)
|
102
|
+
var re = host.replace(
|
103
|
+
/\*([a-z0-9\\-_\.])|[\.,\-\\\^\$+?*\[\]\(\):!\|{}]/g,
|
104
|
+
function(all, sub) {
|
105
|
+
if (sub) return '[a-z0-9\\-_]*' + (sub === '-' ? '\\-' : sub);
|
106
|
+
return '\\' + all;
|
107
|
+
});
|
108
|
+
|
109
|
+
return new RegExp('^' + re + '$', 'i');
|
110
|
+
}
|
111
|
+
|
112
|
+
var dnsNames = [],
|
113
|
+
uriNames = [],
|
114
|
+
ips = [],
|
115
|
+
matchCN = true,
|
116
|
+
valid = false;
|
117
|
+
|
118
|
+
// There're several names to perform check against:
|
119
|
+
// CN and altnames in certificate extension
|
120
|
+
// (DNS names, IP addresses, and URIs)
|
121
|
+
//
|
122
|
+
// Walk through altnames and generate lists of those names
|
123
|
+
if (cert.subjectaltname) {
|
124
|
+
cert.subjectaltname.split(/, /g).forEach(function(altname) {
|
125
|
+
if (/^DNS:/.test(altname)) {
|
126
|
+
dnsNames.push(altname.slice(4));
|
127
|
+
} else if (/^IP Address:/.test(altname)) {
|
128
|
+
ips.push(altname.slice(11));
|
129
|
+
} else if (/^URI:/.test(altname)) {
|
130
|
+
var uri = url.parse(altname.slice(4));
|
131
|
+
if (uri) uriNames.push(uri.hostname);
|
132
|
+
}
|
133
|
+
});
|
134
|
+
}
|
135
|
+
|
136
|
+
// If hostname is an IP address, it should be present in the list of IP
|
137
|
+
// addresses.
|
138
|
+
if (net.isIP(host)) {
|
139
|
+
valid = ips.some(function(ip) {
|
140
|
+
return ip === host;
|
141
|
+
});
|
142
|
+
} else {
|
143
|
+
// Transform hostname to canonical form
|
144
|
+
if (!/\.$/.test(host)) host += '.';
|
145
|
+
|
146
|
+
// Otherwise check all DNS/URI records from certificate
|
147
|
+
// (with allowed wildcards)
|
148
|
+
dnsNames = dnsNames.map(function(name) {
|
149
|
+
return regexpify(name, true);
|
150
|
+
});
|
151
|
+
|
152
|
+
// Wildcards ain't allowed in URI names
|
153
|
+
uriNames = uriNames.map(function(name) {
|
154
|
+
return regexpify(name, false);
|
155
|
+
});
|
156
|
+
|
157
|
+
dnsNames = dnsNames.concat(uriNames);
|
158
|
+
|
159
|
+
if (dnsNames.length > 0) matchCN = false;
|
160
|
+
|
161
|
+
// Match against Common Name (CN) only if no supported identifiers are
|
162
|
+
// present.
|
163
|
+
//
|
164
|
+
// "As noted, a client MUST NOT seek a match for a reference identifier
|
165
|
+
// of CN-ID if the presented identifiers include a DNS-ID, SRV-ID,
|
166
|
+
// URI-ID, or any application-specific identifier types supported by the
|
167
|
+
// client."
|
168
|
+
// RFC6125
|
169
|
+
if (matchCN) {
|
170
|
+
var commonNames = cert.subject.CN;
|
171
|
+
if (util.isArray(commonNames)) {
|
172
|
+
for (var i = 0, k = commonNames.length; i < k; ++i) {
|
173
|
+
dnsNames.push(regexpify(commonNames[i], true));
|
174
|
+
}
|
175
|
+
} else {
|
176
|
+
dnsNames.push(regexpify(commonNames, true));
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
valid = dnsNames.some(function(re) {
|
181
|
+
return re.test(host);
|
182
|
+
});
|
183
|
+
}
|
184
|
+
|
185
|
+
return valid;
|
186
|
+
};
|
187
|
+
|
188
|
+
// Example:
|
189
|
+
// C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org
|
190
|
+
exports.parseCertString = function parseCertString(s) {
|
191
|
+
var out = {};
|
192
|
+
var parts = s.split('\n');
|
193
|
+
for (var i = 0, len = parts.length; i < len; i++) {
|
194
|
+
var sepIndex = parts[i].indexOf('=');
|
195
|
+
if (sepIndex > 0) {
|
196
|
+
var key = parts[i].slice(0, sepIndex);
|
197
|
+
var value = parts[i].slice(sepIndex + 1);
|
198
|
+
if (key in out) {
|
199
|
+
if (!util.isArray(out[key])) {
|
200
|
+
out[key] = [out[key]];
|
201
|
+
}
|
202
|
+
out[key].push(value);
|
203
|
+
} else {
|
204
|
+
out[key] = value;
|
205
|
+
}
|
206
|
+
}
|
207
|
+
}
|
208
|
+
return out;
|
209
|
+
};
|
210
|
+
|
211
|
+
// Public API
|
212
|
+
exports.TLSSocket = require('_tls_wrap').TLSSocket;
|
213
|
+
exports.Server = require('_tls_wrap').Server;
|
214
|
+
exports.createServer = require('_tls_wrap').createServer;
|
215
|
+
exports.connect = require('_tls_wrap').connect;
|
216
|
+
|
217
|
+
// Legacy API
|
218
|
+
exports.__defineGetter__('createSecurePair', util.deprecate(function() {
|
219
|
+
return require('_tls_legacy').createSecurePair;
|
220
|
+
}, 'createSecurePair() is deprecated, use TLSSocket instead'));
|
@@ -0,0 +1,129 @@
|
|
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 inherits = require('util').inherits;
|
23
|
+
var net = require('net');
|
24
|
+
var TTY = process.binding('tty_wrap').TTY;
|
25
|
+
var isTTY = process.binding('tty_wrap').isTTY;
|
26
|
+
var util = require('util');
|
27
|
+
|
28
|
+
var errnoException = util._errnoException;
|
29
|
+
|
30
|
+
|
31
|
+
exports.isatty = function(fd) {
|
32
|
+
return isTTY(fd);
|
33
|
+
};
|
34
|
+
|
35
|
+
|
36
|
+
// backwards-compat
|
37
|
+
exports.setRawMode = util.deprecate(function(flag) {
|
38
|
+
if (!process.stdin.isTTY) {
|
39
|
+
throw new Error('can\'t set raw mode on non-tty');
|
40
|
+
}
|
41
|
+
process.stdin.setRawMode(flag);
|
42
|
+
}, 'tty.setRawMode: Use `process.stdin.setRawMode()` instead.');
|
43
|
+
|
44
|
+
|
45
|
+
function ReadStream(fd, options) {
|
46
|
+
if (!(this instanceof ReadStream))
|
47
|
+
return new ReadStream(fd, options);
|
48
|
+
|
49
|
+
options = util._extend({
|
50
|
+
highWaterMark: 0,
|
51
|
+
readable: true,
|
52
|
+
writable: false,
|
53
|
+
handle: new TTY(fd, true)
|
54
|
+
}, options);
|
55
|
+
|
56
|
+
net.Socket.call(this, options);
|
57
|
+
|
58
|
+
this.isRaw = false;
|
59
|
+
this.isTTY = true;
|
60
|
+
}
|
61
|
+
inherits(ReadStream, net.Socket);
|
62
|
+
|
63
|
+
exports.ReadStream = ReadStream;
|
64
|
+
|
65
|
+
ReadStream.prototype.setRawMode = function(flag) {
|
66
|
+
flag = !!flag;
|
67
|
+
this._handle.setRawMode(flag);
|
68
|
+
this.isRaw = flag;
|
69
|
+
};
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
function WriteStream(fd) {
|
74
|
+
if (!(this instanceof WriteStream)) return new WriteStream(fd);
|
75
|
+
net.Socket.call(this, {
|
76
|
+
handle: new TTY(fd, false),
|
77
|
+
readable: false,
|
78
|
+
writable: true
|
79
|
+
});
|
80
|
+
|
81
|
+
var winSize = [];
|
82
|
+
var err = this._handle.getWindowSize(winSize);
|
83
|
+
if (!err) {
|
84
|
+
this.columns = winSize[0];
|
85
|
+
this.rows = winSize[1];
|
86
|
+
}
|
87
|
+
}
|
88
|
+
inherits(WriteStream, net.Socket);
|
89
|
+
exports.WriteStream = WriteStream;
|
90
|
+
|
91
|
+
|
92
|
+
WriteStream.prototype.isTTY = true;
|
93
|
+
|
94
|
+
|
95
|
+
WriteStream.prototype._refreshSize = function() {
|
96
|
+
var oldCols = this.columns;
|
97
|
+
var oldRows = this.rows;
|
98
|
+
var winSize = [];
|
99
|
+
var err = this._handle.getWindowSize(winSize);
|
100
|
+
if (err) {
|
101
|
+
this.emit('error', errnoException(err, 'getWindowSize'));
|
102
|
+
return;
|
103
|
+
}
|
104
|
+
var newCols = winSize[0];
|
105
|
+
var newRows = winSize[1];
|
106
|
+
if (oldCols !== newCols || oldRows !== newRows) {
|
107
|
+
this.columns = newCols;
|
108
|
+
this.rows = newRows;
|
109
|
+
this.emit('resize');
|
110
|
+
}
|
111
|
+
};
|
112
|
+
|
113
|
+
|
114
|
+
// backwards-compat
|
115
|
+
WriteStream.prototype.cursorTo = function(x, y) {
|
116
|
+
require('readline').cursorTo(this, x, y);
|
117
|
+
};
|
118
|
+
WriteStream.prototype.moveCursor = function(dx, dy) {
|
119
|
+
require('readline').moveCursor(this, dx, dy);
|
120
|
+
};
|
121
|
+
WriteStream.prototype.clearLine = function(dir) {
|
122
|
+
require('readline').clearLine(this, dir);
|
123
|
+
};
|
124
|
+
WriteStream.prototype.clearScreenDown = function() {
|
125
|
+
require('readline').clearScreenDown(this);
|
126
|
+
};
|
127
|
+
WriteStream.prototype.getWindowSize = function() {
|
128
|
+
return [this.columns, this.rows];
|
129
|
+
};
|
@@ -0,0 +1,693 @@
|
|
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
|
+
var util = require('util');
|
24
|
+
|
25
|
+
exports.parse = urlParse;
|
26
|
+
exports.resolve = urlResolve;
|
27
|
+
exports.resolveObject = urlResolveObject;
|
28
|
+
exports.format = urlFormat;
|
29
|
+
|
30
|
+
exports.Url = Url;
|
31
|
+
|
32
|
+
function Url() {
|
33
|
+
this.protocol = null;
|
34
|
+
this.slashes = null;
|
35
|
+
this.auth = null;
|
36
|
+
this.host = null;
|
37
|
+
this.port = null;
|
38
|
+
this.hostname = null;
|
39
|
+
this.hash = null;
|
40
|
+
this.search = null;
|
41
|
+
this.query = null;
|
42
|
+
this.pathname = null;
|
43
|
+
this.path = null;
|
44
|
+
this.href = null;
|
45
|
+
}
|
46
|
+
|
47
|
+
// Reference: RFC 3986, RFC 1808, RFC 2396
|
48
|
+
|
49
|
+
// define these here so at least they only have to be
|
50
|
+
// compiled once on the first module load.
|
51
|
+
var protocolPattern = /^([a-z0-9.+-]+:)/i,
|
52
|
+
portPattern = /:[0-9]*$/,
|
53
|
+
|
54
|
+
// RFC 2396: characters reserved for delimiting URLs.
|
55
|
+
// We actually just auto-escape these.
|
56
|
+
delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
|
57
|
+
|
58
|
+
// RFC 2396: characters not allowed for various reasons.
|
59
|
+
unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),
|
60
|
+
|
61
|
+
// Allowed by RFCs, but cause of XSS attacks. Always escape these.
|
62
|
+
autoEscape = ['\''].concat(unwise),
|
63
|
+
// Characters that are never ever allowed in a hostname.
|
64
|
+
// Note that any invalid chars are also handled, but these
|
65
|
+
// are the ones that are *expected* to be seen, so we fast-path
|
66
|
+
// them.
|
67
|
+
nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
|
68
|
+
hostEndingChars = ['/', '?', '#'],
|
69
|
+
hostnameMaxLen = 255,
|
70
|
+
hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/,
|
71
|
+
hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/,
|
72
|
+
// protocols that can allow "unsafe" and "unwise" chars.
|
73
|
+
unsafeProtocol = {
|
74
|
+
'javascript': true,
|
75
|
+
'javascript:': true
|
76
|
+
},
|
77
|
+
// protocols that never have a hostname.
|
78
|
+
hostlessProtocol = {
|
79
|
+
'javascript': true,
|
80
|
+
'javascript:': true
|
81
|
+
},
|
82
|
+
// protocols that always contain a // bit.
|
83
|
+
slashedProtocol = {
|
84
|
+
'http': true,
|
85
|
+
'https': true,
|
86
|
+
'ftp': true,
|
87
|
+
'gopher': true,
|
88
|
+
'file': true,
|
89
|
+
'http:': true,
|
90
|
+
'https:': true,
|
91
|
+
'ftp:': true,
|
92
|
+
'gopher:': true,
|
93
|
+
'file:': true
|
94
|
+
},
|
95
|
+
querystring = require('querystring');
|
96
|
+
|
97
|
+
function urlParse(url, parseQueryString, slashesDenoteHost) {
|
98
|
+
if (url && util.isObject(url) && url instanceof Url) return url;
|
99
|
+
|
100
|
+
var u = new Url;
|
101
|
+
u.parse(url, parseQueryString, slashesDenoteHost);
|
102
|
+
return u;
|
103
|
+
}
|
104
|
+
|
105
|
+
Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
|
106
|
+
if (!util.isString(url)) {
|
107
|
+
throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
|
108
|
+
}
|
109
|
+
|
110
|
+
var rest = url;
|
111
|
+
|
112
|
+
// trim before proceeding.
|
113
|
+
// This is to support parse stuff like " http://foo.com \n"
|
114
|
+
rest = rest.trim();
|
115
|
+
|
116
|
+
var proto = protocolPattern.exec(rest);
|
117
|
+
if (proto) {
|
118
|
+
proto = proto[0];
|
119
|
+
var lowerProto = proto.toLowerCase();
|
120
|
+
this.protocol = lowerProto;
|
121
|
+
rest = rest.substr(proto.length);
|
122
|
+
}
|
123
|
+
|
124
|
+
// figure out if it's got a host
|
125
|
+
// user@server is *always* interpreted as a hostname, and url
|
126
|
+
// resolution will treat //foo/bar as host=foo,path=bar because that's
|
127
|
+
// how the browser resolves relative URLs.
|
128
|
+
if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
|
129
|
+
var slashes = rest.substr(0, 2) === '//';
|
130
|
+
if (slashes && !(proto && hostlessProtocol[proto])) {
|
131
|
+
rest = rest.substr(2);
|
132
|
+
this.slashes = true;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
if (!hostlessProtocol[proto] &&
|
137
|
+
(slashes || (proto && !slashedProtocol[proto]))) {
|
138
|
+
|
139
|
+
// there's a hostname.
|
140
|
+
// the first instance of /, ?, ;, or # ends the host.
|
141
|
+
//
|
142
|
+
// If there is an @ in the hostname, then non-host chars *are* allowed
|
143
|
+
// to the left of the last @ sign, unless some host-ending character
|
144
|
+
// comes *before* the @-sign.
|
145
|
+
// URLs are obnoxious.
|
146
|
+
//
|
147
|
+
// ex:
|
148
|
+
// http://a@b@c/ => user:a@b host:c
|
149
|
+
// http://a@b?@c => user:a host:c path:/?@c
|
150
|
+
|
151
|
+
// v0.12 TODO(isaacs): This is not quite how Chrome does things.
|
152
|
+
// Review our test case against browsers more comprehensively.
|
153
|
+
|
154
|
+
// find the first instance of any hostEndingChars
|
155
|
+
var hostEnd = -1;
|
156
|
+
for (var i = 0; i < hostEndingChars.length; i++) {
|
157
|
+
var hec = rest.indexOf(hostEndingChars[i]);
|
158
|
+
if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
|
159
|
+
hostEnd = hec;
|
160
|
+
}
|
161
|
+
|
162
|
+
// at this point, either we have an explicit point where the
|
163
|
+
// auth portion cannot go past, or the last @ char is the decider.
|
164
|
+
var auth, atSign;
|
165
|
+
if (hostEnd === -1) {
|
166
|
+
// atSign can be anywhere.
|
167
|
+
atSign = rest.lastIndexOf('@');
|
168
|
+
} else {
|
169
|
+
// atSign must be in auth portion.
|
170
|
+
// http://a@b/c@d => host:b auth:a path:/c@d
|
171
|
+
atSign = rest.lastIndexOf('@', hostEnd);
|
172
|
+
}
|
173
|
+
|
174
|
+
// Now we have a portion which is definitely the auth.
|
175
|
+
// Pull that off.
|
176
|
+
if (atSign !== -1) {
|
177
|
+
auth = rest.slice(0, atSign);
|
178
|
+
rest = rest.slice(atSign + 1);
|
179
|
+
this.auth = decodeURIComponent(auth);
|
180
|
+
}
|
181
|
+
|
182
|
+
// the host is the remaining to the left of the first non-host char
|
183
|
+
hostEnd = -1;
|
184
|
+
for (var i = 0; i < nonHostChars.length; i++) {
|
185
|
+
var hec = rest.indexOf(nonHostChars[i]);
|
186
|
+
if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
|
187
|
+
hostEnd = hec;
|
188
|
+
}
|
189
|
+
// if we still have not hit it, then the entire thing is a host.
|
190
|
+
if (hostEnd === -1)
|
191
|
+
hostEnd = rest.length;
|
192
|
+
|
193
|
+
this.host = rest.slice(0, hostEnd);
|
194
|
+
rest = rest.slice(hostEnd);
|
195
|
+
|
196
|
+
// pull out port.
|
197
|
+
this.parseHost();
|
198
|
+
|
199
|
+
// we've indicated that there is a hostname,
|
200
|
+
// so even if it's empty, it has to be present.
|
201
|
+
this.hostname = this.hostname || '';
|
202
|
+
|
203
|
+
// if hostname begins with [ and ends with ]
|
204
|
+
// assume that it's an IPv6 address.
|
205
|
+
var ipv6Hostname = this.hostname[0] === '[' &&
|
206
|
+
this.hostname[this.hostname.length - 1] === ']';
|
207
|
+
|
208
|
+
// validate a little.
|
209
|
+
if (!ipv6Hostname) {
|
210
|
+
var hostparts = this.hostname.split(/\./);
|
211
|
+
for (var i = 0, l = hostparts.length; i < l; i++) {
|
212
|
+
var part = hostparts[i];
|
213
|
+
if (!part) continue;
|
214
|
+
if (!part.match(hostnamePartPattern)) {
|
215
|
+
var newpart = '';
|
216
|
+
for (var j = 0, k = part.length; j < k; j++) {
|
217
|
+
if (part.charCodeAt(j) > 127) {
|
218
|
+
// we replace non-ASCII char with a temporary placeholder
|
219
|
+
// we need this to make sure size of hostname is not
|
220
|
+
// broken by replacing non-ASCII by nothing
|
221
|
+
newpart += 'x';
|
222
|
+
} else {
|
223
|
+
newpart += part[j];
|
224
|
+
}
|
225
|
+
}
|
226
|
+
// we test again with ASCII char only
|
227
|
+
if (!newpart.match(hostnamePartPattern)) {
|
228
|
+
var validParts = hostparts.slice(0, i);
|
229
|
+
var notHost = hostparts.slice(i + 1);
|
230
|
+
var bit = part.match(hostnamePartStart);
|
231
|
+
if (bit) {
|
232
|
+
validParts.push(bit[1]);
|
233
|
+
notHost.unshift(bit[2]);
|
234
|
+
}
|
235
|
+
if (notHost.length) {
|
236
|
+
rest = '/' + notHost.join('.') + rest;
|
237
|
+
}
|
238
|
+
this.hostname = validParts.join('.');
|
239
|
+
break;
|
240
|
+
}
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
if (this.hostname.length > hostnameMaxLen) {
|
246
|
+
this.hostname = '';
|
247
|
+
} else {
|
248
|
+
// hostnames are always lower case.
|
249
|
+
this.hostname = this.hostname.toLowerCase();
|
250
|
+
}
|
251
|
+
|
252
|
+
if (!ipv6Hostname) {
|
253
|
+
// IDNA Support: Returns a puny coded representation of "domain".
|
254
|
+
// It only converts the part of the domain name that
|
255
|
+
// has non ASCII characters. I.e. it dosent matter if
|
256
|
+
// you call it with a domain that already is in ASCII.
|
257
|
+
var domainArray = this.hostname.split('.');
|
258
|
+
var newOut = [];
|
259
|
+
for (var i = 0; i < domainArray.length; ++i) {
|
260
|
+
var s = domainArray[i];
|
261
|
+
newOut.push(s.match(/[^A-Za-z0-9_-]/) ?
|
262
|
+
'xn--' + punycode.encode(s) : s);
|
263
|
+
}
|
264
|
+
this.hostname = newOut.join('.');
|
265
|
+
}
|
266
|
+
|
267
|
+
var p = this.port ? ':' + this.port : '';
|
268
|
+
var h = this.hostname || '';
|
269
|
+
this.host = h + p;
|
270
|
+
this.href += this.host;
|
271
|
+
|
272
|
+
// strip [ and ] from the hostname
|
273
|
+
// the host field still retains them, though
|
274
|
+
if (ipv6Hostname) {
|
275
|
+
this.hostname = this.hostname.substr(1, this.hostname.length - 2);
|
276
|
+
if (rest[0] !== '/') {
|
277
|
+
rest = '/' + rest;
|
278
|
+
}
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
// now rest is set to the post-host stuff.
|
283
|
+
// chop off any delim chars.
|
284
|
+
if (!unsafeProtocol[lowerProto]) {
|
285
|
+
|
286
|
+
// First, make 100% sure that any "autoEscape" chars get
|
287
|
+
// escaped, even if encodeURIComponent doesn't think they
|
288
|
+
// need to be.
|
289
|
+
for (var i = 0, l = autoEscape.length; i < l; i++) {
|
290
|
+
var ae = autoEscape[i];
|
291
|
+
var esc = encodeURIComponent(ae);
|
292
|
+
if (esc === ae) {
|
293
|
+
esc = escape(ae);
|
294
|
+
}
|
295
|
+
rest = rest.split(ae).join(esc);
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
|
300
|
+
// chop off from the tail first.
|
301
|
+
var hash = rest.indexOf('#');
|
302
|
+
if (hash !== -1) {
|
303
|
+
// got a fragment string.
|
304
|
+
this.hash = rest.substr(hash);
|
305
|
+
rest = rest.slice(0, hash);
|
306
|
+
}
|
307
|
+
var qm = rest.indexOf('?');
|
308
|
+
if (qm !== -1) {
|
309
|
+
this.search = rest.substr(qm);
|
310
|
+
this.query = rest.substr(qm + 1);
|
311
|
+
if (parseQueryString) {
|
312
|
+
this.query = querystring.parse(this.query);
|
313
|
+
}
|
314
|
+
rest = rest.slice(0, qm);
|
315
|
+
} else if (parseQueryString) {
|
316
|
+
// no query string, but parseQueryString still requested
|
317
|
+
this.search = '';
|
318
|
+
this.query = {};
|
319
|
+
}
|
320
|
+
if (rest) this.pathname = rest;
|
321
|
+
if (slashedProtocol[lowerProto] &&
|
322
|
+
this.hostname && !this.pathname) {
|
323
|
+
this.pathname = '/';
|
324
|
+
}
|
325
|
+
|
326
|
+
//to support http.request
|
327
|
+
if (this.pathname || this.search) {
|
328
|
+
var p = this.pathname || '';
|
329
|
+
var s = this.search || '';
|
330
|
+
this.path = p + s;
|
331
|
+
}
|
332
|
+
|
333
|
+
// finally, reconstruct the href based on what has been validated.
|
334
|
+
this.href = this.format();
|
335
|
+
return this;
|
336
|
+
};
|
337
|
+
|
338
|
+
// format a parsed object into a url string
|
339
|
+
function urlFormat(obj) {
|
340
|
+
// ensure it's an object, and not a string url.
|
341
|
+
// If it's an obj, this is a no-op.
|
342
|
+
// this way, you can call url_format() on strings
|
343
|
+
// to clean up potentially wonky urls.
|
344
|
+
if (util.isString(obj)) obj = urlParse(obj);
|
345
|
+
if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
|
346
|
+
return obj.format();
|
347
|
+
}
|
348
|
+
|
349
|
+
Url.prototype.format = function() {
|
350
|
+
var auth = this.auth || '';
|
351
|
+
if (auth) {
|
352
|
+
auth = encodeURIComponent(auth);
|
353
|
+
auth = auth.replace(/%3A/i, ':');
|
354
|
+
auth += '@';
|
355
|
+
}
|
356
|
+
|
357
|
+
var protocol = this.protocol || '',
|
358
|
+
pathname = this.pathname || '',
|
359
|
+
hash = this.hash || '',
|
360
|
+
host = false,
|
361
|
+
query = '';
|
362
|
+
|
363
|
+
if (this.host) {
|
364
|
+
host = auth + this.host;
|
365
|
+
} else if (this.hostname) {
|
366
|
+
host = auth + (this.hostname.indexOf(':') === -1 ?
|
367
|
+
this.hostname :
|
368
|
+
'[' + this.hostname + ']');
|
369
|
+
if (this.port) {
|
370
|
+
host += ':' + this.port;
|
371
|
+
}
|
372
|
+
}
|
373
|
+
|
374
|
+
if (this.query &&
|
375
|
+
util.isObject(this.query) &&
|
376
|
+
Object.keys(this.query).length) {
|
377
|
+
query = querystring.stringify(this.query);
|
378
|
+
}
|
379
|
+
|
380
|
+
var search = this.search || (query && ('?' + query)) || '';
|
381
|
+
|
382
|
+
if (protocol && protocol.substr(-1) !== ':') protocol += ':';
|
383
|
+
|
384
|
+
// only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
|
385
|
+
// unless they had them to begin with.
|
386
|
+
if (this.slashes ||
|
387
|
+
(!protocol || slashedProtocol[protocol]) && host !== false) {
|
388
|
+
host = '//' + (host || '');
|
389
|
+
if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
|
390
|
+
} else if (!host) {
|
391
|
+
host = '';
|
392
|
+
}
|
393
|
+
|
394
|
+
if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
|
395
|
+
if (search && search.charAt(0) !== '?') search = '?' + search;
|
396
|
+
|
397
|
+
pathname = pathname.replace(/[?#]/g, function(match) {
|
398
|
+
return encodeURIComponent(match);
|
399
|
+
});
|
400
|
+
search = search.replace('#', '%23');
|
401
|
+
|
402
|
+
return protocol + host + pathname + search + hash;
|
403
|
+
};
|
404
|
+
|
405
|
+
function urlResolve(source, relative) {
|
406
|
+
return urlParse(source, false, true).resolve(relative);
|
407
|
+
}
|
408
|
+
|
409
|
+
Url.prototype.resolve = function(relative) {
|
410
|
+
return this.resolveObject(urlParse(relative, false, true)).format();
|
411
|
+
};
|
412
|
+
|
413
|
+
function urlResolveObject(source, relative) {
|
414
|
+
if (!source) return relative;
|
415
|
+
return urlParse(source, false, true).resolveObject(relative);
|
416
|
+
}
|
417
|
+
|
418
|
+
Url.prototype.resolveObject = function(relative) {
|
419
|
+
if (util.isString(relative)) {
|
420
|
+
var rel = new Url();
|
421
|
+
rel.parse(relative, false, true);
|
422
|
+
relative = rel;
|
423
|
+
}
|
424
|
+
|
425
|
+
var result = new Url();
|
426
|
+
Object.keys(this).forEach(function(k) {
|
427
|
+
result[k] = this[k];
|
428
|
+
}, this);
|
429
|
+
|
430
|
+
// hash is always overridden, no matter what.
|
431
|
+
// even href="" will remove it.
|
432
|
+
result.hash = relative.hash;
|
433
|
+
|
434
|
+
// if the relative url is empty, then there's nothing left to do here.
|
435
|
+
if (relative.href === '') {
|
436
|
+
result.href = result.format();
|
437
|
+
return result;
|
438
|
+
}
|
439
|
+
|
440
|
+
// hrefs like //foo/bar always cut to the protocol.
|
441
|
+
if (relative.slashes && !relative.protocol) {
|
442
|
+
// take everything except the protocol from relative
|
443
|
+
Object.keys(relative).forEach(function(k) {
|
444
|
+
if (k !== 'protocol')
|
445
|
+
result[k] = relative[k];
|
446
|
+
});
|
447
|
+
|
448
|
+
//urlParse appends trailing / to urls like http://www.example.com
|
449
|
+
if (slashedProtocol[result.protocol] &&
|
450
|
+
result.hostname && !result.pathname) {
|
451
|
+
result.path = result.pathname = '/';
|
452
|
+
}
|
453
|
+
|
454
|
+
result.href = result.format();
|
455
|
+
return result;
|
456
|
+
}
|
457
|
+
|
458
|
+
if (relative.protocol && relative.protocol !== result.protocol) {
|
459
|
+
// if it's a known url protocol, then changing
|
460
|
+
// the protocol does weird things
|
461
|
+
// first, if it's not file:, then we MUST have a host,
|
462
|
+
// and if there was a path
|
463
|
+
// to begin with, then we MUST have a path.
|
464
|
+
// if it is file:, then the host is dropped,
|
465
|
+
// because that's known to be hostless.
|
466
|
+
// anything else is assumed to be absolute.
|
467
|
+
if (!slashedProtocol[relative.protocol]) {
|
468
|
+
Object.keys(relative).forEach(function(k) {
|
469
|
+
result[k] = relative[k];
|
470
|
+
});
|
471
|
+
result.href = result.format();
|
472
|
+
return result;
|
473
|
+
}
|
474
|
+
|
475
|
+
result.protocol = relative.protocol;
|
476
|
+
if (!relative.host && !hostlessProtocol[relative.protocol]) {
|
477
|
+
var relPath = (relative.pathname || '').split('/');
|
478
|
+
while (relPath.length && !(relative.host = relPath.shift()));
|
479
|
+
if (!relative.host) relative.host = '';
|
480
|
+
if (!relative.hostname) relative.hostname = '';
|
481
|
+
if (relPath[0] !== '') relPath.unshift('');
|
482
|
+
if (relPath.length < 2) relPath.unshift('');
|
483
|
+
result.pathname = relPath.join('/');
|
484
|
+
} else {
|
485
|
+
result.pathname = relative.pathname;
|
486
|
+
}
|
487
|
+
result.search = relative.search;
|
488
|
+
result.query = relative.query;
|
489
|
+
result.host = relative.host || '';
|
490
|
+
result.auth = relative.auth;
|
491
|
+
result.hostname = relative.hostname || relative.host;
|
492
|
+
result.port = relative.port;
|
493
|
+
// to support http.request
|
494
|
+
if (result.pathname || result.search) {
|
495
|
+
var p = result.pathname || '';
|
496
|
+
var s = result.search || '';
|
497
|
+
result.path = p + s;
|
498
|
+
}
|
499
|
+
result.slashes = result.slashes || relative.slashes;
|
500
|
+
result.href = result.format();
|
501
|
+
return result;
|
502
|
+
}
|
503
|
+
|
504
|
+
var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
|
505
|
+
isRelAbs = (
|
506
|
+
relative.host ||
|
507
|
+
relative.pathname && relative.pathname.charAt(0) === '/'
|
508
|
+
),
|
509
|
+
mustEndAbs = (isRelAbs || isSourceAbs ||
|
510
|
+
(result.host && relative.pathname)),
|
511
|
+
removeAllDots = mustEndAbs,
|
512
|
+
srcPath = result.pathname && result.pathname.split('/') || [],
|
513
|
+
relPath = relative.pathname && relative.pathname.split('/') || [],
|
514
|
+
psychotic = result.protocol && !slashedProtocol[result.protocol];
|
515
|
+
|
516
|
+
// if the url is a non-slashed url, then relative
|
517
|
+
// links like ../.. should be able
|
518
|
+
// to crawl up to the hostname, as well. This is strange.
|
519
|
+
// result.protocol has already been set by now.
|
520
|
+
// Later on, put the first path part into the host field.
|
521
|
+
if (psychotic) {
|
522
|
+
result.hostname = '';
|
523
|
+
result.port = null;
|
524
|
+
if (result.host) {
|
525
|
+
if (srcPath[0] === '') srcPath[0] = result.host;
|
526
|
+
else srcPath.unshift(result.host);
|
527
|
+
}
|
528
|
+
result.host = '';
|
529
|
+
if (relative.protocol) {
|
530
|
+
relative.hostname = null;
|
531
|
+
relative.port = null;
|
532
|
+
if (relative.host) {
|
533
|
+
if (relPath[0] === '') relPath[0] = relative.host;
|
534
|
+
else relPath.unshift(relative.host);
|
535
|
+
}
|
536
|
+
relative.host = null;
|
537
|
+
}
|
538
|
+
mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
|
539
|
+
}
|
540
|
+
|
541
|
+
if (isRelAbs) {
|
542
|
+
// it's absolute.
|
543
|
+
result.host = (relative.host || relative.host === '') ?
|
544
|
+
relative.host : result.host;
|
545
|
+
result.hostname = (relative.hostname || relative.hostname === '') ?
|
546
|
+
relative.hostname : result.hostname;
|
547
|
+
result.search = relative.search;
|
548
|
+
result.query = relative.query;
|
549
|
+
srcPath = relPath;
|
550
|
+
// fall through to the dot-handling below.
|
551
|
+
} else if (relPath.length) {
|
552
|
+
// it's relative
|
553
|
+
// throw away the existing file, and take the new path instead.
|
554
|
+
if (!srcPath) srcPath = [];
|
555
|
+
srcPath.pop();
|
556
|
+
srcPath = srcPath.concat(relPath);
|
557
|
+
result.search = relative.search;
|
558
|
+
result.query = relative.query;
|
559
|
+
} else if (!util.isNullOrUndefined(relative.search)) {
|
560
|
+
// just pull out the search.
|
561
|
+
// like href='?foo'.
|
562
|
+
// Put this after the other two cases because it simplifies the booleans
|
563
|
+
if (psychotic) {
|
564
|
+
result.hostname = result.host = srcPath.shift();
|
565
|
+
//occationaly the auth can get stuck only in host
|
566
|
+
//this especialy happens in cases like
|
567
|
+
//url.resolveObject('mailto:local1@domain1', 'local2@domain2')
|
568
|
+
var authInHost = result.host && result.host.indexOf('@') > 0 ?
|
569
|
+
result.host.split('@') : false;
|
570
|
+
if (authInHost) {
|
571
|
+
result.auth = authInHost.shift();
|
572
|
+
result.host = result.hostname = authInHost.shift();
|
573
|
+
}
|
574
|
+
}
|
575
|
+
result.search = relative.search;
|
576
|
+
result.query = relative.query;
|
577
|
+
//to support http.request
|
578
|
+
if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
|
579
|
+
result.path = (result.pathname ? result.pathname : '') +
|
580
|
+
(result.search ? result.search : '');
|
581
|
+
}
|
582
|
+
result.href = result.format();
|
583
|
+
return result;
|
584
|
+
}
|
585
|
+
|
586
|
+
if (!srcPath.length) {
|
587
|
+
// no path at all. easy.
|
588
|
+
// we've already handled the other stuff above.
|
589
|
+
result.pathname = null;
|
590
|
+
//to support http.request
|
591
|
+
if (result.search) {
|
592
|
+
result.path = '/' + result.search;
|
593
|
+
} else {
|
594
|
+
result.path = null;
|
595
|
+
}
|
596
|
+
result.href = result.format();
|
597
|
+
return result;
|
598
|
+
}
|
599
|
+
|
600
|
+
// if a url ENDs in . or .., then it must get a trailing slash.
|
601
|
+
// however, if it ends in anything else non-slashy,
|
602
|
+
// then it must NOT get a trailing slash.
|
603
|
+
var last = srcPath.slice(-1)[0];
|
604
|
+
var hasTrailingSlash = (
|
605
|
+
(result.host || relative.host) && (last === '.' || last === '..') ||
|
606
|
+
last === '');
|
607
|
+
|
608
|
+
// strip single dots, resolve double dots to parent dir
|
609
|
+
// if the path tries to go above the root, `up` ends up > 0
|
610
|
+
var up = 0;
|
611
|
+
for (var i = srcPath.length; i >= 0; i--) {
|
612
|
+
last = srcPath[i];
|
613
|
+
if (last == '.') {
|
614
|
+
srcPath.splice(i, 1);
|
615
|
+
} else if (last === '..') {
|
616
|
+
srcPath.splice(i, 1);
|
617
|
+
up++;
|
618
|
+
} else if (up) {
|
619
|
+
srcPath.splice(i, 1);
|
620
|
+
up--;
|
621
|
+
}
|
622
|
+
}
|
623
|
+
|
624
|
+
// if the path is allowed to go above the root, restore leading ..s
|
625
|
+
if (!mustEndAbs && !removeAllDots) {
|
626
|
+
for (; up--; up) {
|
627
|
+
srcPath.unshift('..');
|
628
|
+
}
|
629
|
+
}
|
630
|
+
|
631
|
+
if (mustEndAbs && srcPath[0] !== '' &&
|
632
|
+
(!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
|
633
|
+
srcPath.unshift('');
|
634
|
+
}
|
635
|
+
|
636
|
+
if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
|
637
|
+
srcPath.push('');
|
638
|
+
}
|
639
|
+
|
640
|
+
var isAbsolute = srcPath[0] === '' ||
|
641
|
+
(srcPath[0] && srcPath[0].charAt(0) === '/');
|
642
|
+
|
643
|
+
// put the host back
|
644
|
+
if (psychotic) {
|
645
|
+
result.hostname = result.host = isAbsolute ? '' :
|
646
|
+
srcPath.length ? srcPath.shift() : '';
|
647
|
+
//occationaly the auth can get stuck only in host
|
648
|
+
//this especialy happens in cases like
|
649
|
+
//url.resolveObject('mailto:local1@domain1', 'local2@domain2')
|
650
|
+
var authInHost = result.host && result.host.indexOf('@') > 0 ?
|
651
|
+
result.host.split('@') : false;
|
652
|
+
if (authInHost) {
|
653
|
+
result.auth = authInHost.shift();
|
654
|
+
result.host = result.hostname = authInHost.shift();
|
655
|
+
}
|
656
|
+
}
|
657
|
+
|
658
|
+
mustEndAbs = mustEndAbs || (result.host && srcPath.length);
|
659
|
+
|
660
|
+
if (mustEndAbs && !isAbsolute) {
|
661
|
+
srcPath.unshift('');
|
662
|
+
}
|
663
|
+
|
664
|
+
if (!srcPath.length) {
|
665
|
+
result.pathname = null;
|
666
|
+
result.path = null;
|
667
|
+
} else {
|
668
|
+
result.pathname = srcPath.join('/');
|
669
|
+
}
|
670
|
+
|
671
|
+
//to support request.http
|
672
|
+
if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
|
673
|
+
result.path = (result.pathname ? result.pathname : '') +
|
674
|
+
(result.search ? result.search : '');
|
675
|
+
}
|
676
|
+
result.auth = relative.auth || result.auth;
|
677
|
+
result.slashes = result.slashes || relative.slashes;
|
678
|
+
result.href = result.format();
|
679
|
+
return result;
|
680
|
+
};
|
681
|
+
|
682
|
+
Url.prototype.parseHost = function() {
|
683
|
+
var host = this.host;
|
684
|
+
var port = portPattern.exec(host);
|
685
|
+
if (port) {
|
686
|
+
port = port[0];
|
687
|
+
if (port !== ':') {
|
688
|
+
this.port = port.substr(1);
|
689
|
+
}
|
690
|
+
host = host.substr(0, host.length - port.length);
|
691
|
+
}
|
692
|
+
if (host) this.hostname = host;
|
693
|
+
};
|