|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 'use strict'; |
|
5 |
|
6 const { |
|
7 URL, |
|
8 toFilename, |
|
9 fromFilename, |
|
10 isValidURI, |
|
11 getTLD, |
|
12 DataURL, |
|
13 isLocalURL } = require('sdk/url'); |
|
14 |
|
15 const { pathFor } = require('sdk/system'); |
|
16 const file = require('sdk/io/file'); |
|
17 const tabs = require('sdk/tabs'); |
|
18 const { decode } = require('sdk/base64'); |
|
19 |
|
20 const httpd = require('sdk/test/httpd'); |
|
21 const port = 8099; |
|
22 |
|
23 const defaultLocation = '{\'scheme\':\'about\',\'userPass\':null,\'host\':null,\'hostname\':null,\'port\':null,\'path\':\'addons\',\'pathname\':\'addons\',\'hash\':\'\',\'href\':\'about:addons\',\'origin\':\'about:\',\'protocol\':\'about:\',\'search\':\'\'}'.replace(/'/g, '"'); |
|
24 |
|
25 exports.testResolve = function(assert) { |
|
26 assert.equal(URL('bar', 'http://www.foo.com/').toString(), |
|
27 'http://www.foo.com/bar'); |
|
28 |
|
29 assert.equal(URL('bar', 'http://www.foo.com'), |
|
30 'http://www.foo.com/bar'); |
|
31 |
|
32 assert.equal(URL('http://bar.com/', 'http://foo.com/'), |
|
33 'http://bar.com/', |
|
34 'relative should override base'); |
|
35 |
|
36 assert.throws(function() { URL('blah'); }, |
|
37 /malformed URI: blah/i, |
|
38 'url.resolve() should throw malformed URI on base'); |
|
39 |
|
40 assert.throws(function() { URL('chrome://global'); }, |
|
41 /invalid URI: chrome:\/\/global/i, |
|
42 'url.resolve() should throw invalid URI on base'); |
|
43 |
|
44 assert.throws(function() { URL('chrome://foo/bar'); }, |
|
45 /invalid URI: chrome:\/\/foo\/bar/i, |
|
46 'url.resolve() should throw on bad chrome URI'); |
|
47 |
|
48 assert.equal(URL('', 'http://www.foo.com'), |
|
49 'http://www.foo.com/', |
|
50 'url.resolve() should add slash to end of domain'); |
|
51 }; |
|
52 |
|
53 exports.testParseHttp = function(assert) { |
|
54 var aUrl = 'http://sub.foo.com/bar?locale=en-US&otherArg=%20x%20#myhash'; |
|
55 var info = URL(aUrl); |
|
56 |
|
57 assert.equal(info.scheme, 'http'); |
|
58 assert.equal(info.protocol, 'http:'); |
|
59 assert.equal(info.host, 'sub.foo.com'); |
|
60 assert.equal(info.hostname, 'sub.foo.com'); |
|
61 assert.equal(info.port, null); |
|
62 assert.equal(info.userPass, null); |
|
63 assert.equal(info.path, '/bar?locale=en-US&otherArg=%20x%20#myhash'); |
|
64 assert.equal(info.pathname, '/bar'); |
|
65 assert.equal(info.href, aUrl); |
|
66 assert.equal(info.hash, '#myhash'); |
|
67 assert.equal(info.search, '?locale=en-US&otherArg=%20x%20'); |
|
68 }; |
|
69 |
|
70 exports.testParseHttpSearchAndHash = function (assert) { |
|
71 var info = URL('https://www.moz.com/some/page.html'); |
|
72 assert.equal(info.hash, ''); |
|
73 assert.equal(info.search, ''); |
|
74 |
|
75 var hashOnly = URL('https://www.sub.moz.com/page.html#justhash'); |
|
76 assert.equal(hashOnly.search, ''); |
|
77 assert.equal(hashOnly.hash, '#justhash'); |
|
78 |
|
79 var queryOnly = URL('https://www.sub.moz.com/page.html?my=query'); |
|
80 assert.equal(queryOnly.search, '?my=query'); |
|
81 assert.equal(queryOnly.hash, ''); |
|
82 |
|
83 var qMark = URL('http://www.moz.org?'); |
|
84 assert.equal(qMark.search, ''); |
|
85 assert.equal(qMark.hash, ''); |
|
86 |
|
87 var hash = URL('http://www.moz.org#'); |
|
88 assert.equal(hash.search, ''); |
|
89 assert.equal(hash.hash, ''); |
|
90 |
|
91 var empty = URL('http://www.moz.org?#'); |
|
92 assert.equal(hash.search, ''); |
|
93 assert.equal(hash.hash, ''); |
|
94 |
|
95 var strange = URL('http://moz.org?test1#test2?test3'); |
|
96 assert.equal(strange.search, '?test1'); |
|
97 assert.equal(strange.hash, '#test2?test3'); |
|
98 }; |
|
99 |
|
100 exports.testParseHttpWithPort = function(assert) { |
|
101 var info = URL('http://foo.com:5/bar'); |
|
102 assert.equal(info.port, 5); |
|
103 }; |
|
104 |
|
105 exports.testParseChrome = function(assert) { |
|
106 var info = URL('chrome://global/content/blah'); |
|
107 assert.equal(info.scheme, 'chrome'); |
|
108 assert.equal(info.host, 'global'); |
|
109 assert.equal(info.port, null); |
|
110 assert.equal(info.userPass, null); |
|
111 assert.equal(info.path, '/content/blah'); |
|
112 }; |
|
113 |
|
114 exports.testParseAbout = function(assert) { |
|
115 var info = URL('about:boop'); |
|
116 assert.equal(info.scheme, 'about'); |
|
117 assert.equal(info.host, null); |
|
118 assert.equal(info.port, null); |
|
119 assert.equal(info.userPass, null); |
|
120 assert.equal(info.path, 'boop'); |
|
121 }; |
|
122 |
|
123 exports.testParseFTP = function(assert) { |
|
124 var info = URL('ftp://1.2.3.4/foo'); |
|
125 assert.equal(info.scheme, 'ftp'); |
|
126 assert.equal(info.host, '1.2.3.4'); |
|
127 assert.equal(info.port, null); |
|
128 assert.equal(info.userPass, null); |
|
129 assert.equal(info.path, '/foo'); |
|
130 }; |
|
131 |
|
132 exports.testParseFTPWithUserPass = function(assert) { |
|
133 var info = URL('ftp://user:pass@1.2.3.4/foo'); |
|
134 assert.equal(info.userPass, 'user:pass'); |
|
135 }; |
|
136 |
|
137 exports.testToFilename = function(assert) { |
|
138 assert.throws( |
|
139 function() { toFilename('resource://nonexistent'); }, |
|
140 /resource does not exist: resource:\/\/nonexistent\//i, |
|
141 'toFilename() on nonexistent resources should throw' |
|
142 ); |
|
143 |
|
144 assert.throws( |
|
145 function() { toFilename('http://foo.com/'); }, |
|
146 /cannot map to filename: http:\/\/foo.com\//i, |
|
147 'toFilename() on http: URIs should raise error' |
|
148 ); |
|
149 |
|
150 try { |
|
151 assert.ok( |
|
152 /.*console\.xul$/.test(toFilename('chrome://global/content/console.xul')), |
|
153 'toFilename() w/ console.xul works when it maps to filesystem' |
|
154 ); |
|
155 } |
|
156 catch (e) { |
|
157 if (/chrome url isn\'t on filesystem/.test(e.message)) |
|
158 assert.pass('accessing console.xul in jar raises exception'); |
|
159 else |
|
160 assert.fail('accessing console.xul raises ' + e); |
|
161 } |
|
162 |
|
163 // TODO: Are there any chrome URLs that we're certain exist on the |
|
164 // filesystem? |
|
165 // assert.ok(/.*main\.js$/.test(toFilename('chrome://myapp/content/main.js'))); |
|
166 }; |
|
167 |
|
168 exports.testFromFilename = function(assert) { |
|
169 var profileDirName = require('sdk/system').pathFor('ProfD'); |
|
170 var fileUrl = fromFilename(profileDirName); |
|
171 assert.equal(URL(fileUrl).scheme, 'file', |
|
172 'toFilename() should return a file: url'); |
|
173 assert.equal(fromFilename(toFilename(fileUrl)), fileUrl); |
|
174 }; |
|
175 |
|
176 exports.testURL = function(assert) { |
|
177 assert.ok(URL('h:foo') instanceof URL, 'instance is of correct type'); |
|
178 assert.throws(function() URL(), |
|
179 /malformed URI: undefined/i, |
|
180 'url.URL should throw on undefined'); |
|
181 assert.throws(function() URL(''), |
|
182 /malformed URI: /i, |
|
183 'url.URL should throw on empty string'); |
|
184 assert.throws(function() URL('foo'), |
|
185 /malformed URI: foo/i, |
|
186 'url.URL should throw on invalid URI'); |
|
187 assert.ok(URL('h:foo').scheme, 'has scheme'); |
|
188 assert.equal(URL('h:foo').toString(), |
|
189 'h:foo', |
|
190 'toString should roundtrip'); |
|
191 // test relative + base |
|
192 assert.equal(URL('mypath', 'http://foo').toString(), |
|
193 'http://foo/mypath', |
|
194 'relative URL resolved to base'); |
|
195 // test relative + no base |
|
196 assert.throws(function() URL('path').toString(), |
|
197 /malformed URI: path/i, |
|
198 'no base for relative URI should throw'); |
|
199 |
|
200 let a = URL('h:foo'); |
|
201 let b = URL(a); |
|
202 assert.equal(b.toString(), |
|
203 'h:foo', |
|
204 'a URL can be initialized from another URL'); |
|
205 assert.notStrictEqual(a, b, |
|
206 'a URL initialized from another URL is not the same object'); |
|
207 assert.ok(a == 'h:foo', |
|
208 'toString is implicit when a URL is compared to a string via =='); |
|
209 assert.strictEqual(a + '', 'h:foo', |
|
210 'toString is implicit when a URL is concatenated to a string'); |
|
211 }; |
|
212 |
|
213 exports.testStringInterface = function(assert) { |
|
214 var EM = 'about:addons'; |
|
215 var a = URL(EM); |
|
216 |
|
217 // make sure the standard URL properties are enumerable and not the String interface bits |
|
218 assert.equal(Object.keys(a), |
|
219 'scheme,userPass,host,hostname,port,path,pathname,hash,href,origin,protocol,search', |
|
220 'enumerable key list check for URL.'); |
|
221 assert.equal( |
|
222 JSON.stringify(a), |
|
223 defaultLocation, |
|
224 'JSON.stringify should return a object with correct props and vals.'); |
|
225 |
|
226 // make sure that the String interface exists and works as expected |
|
227 assert.equal(a.indexOf(':'), EM.indexOf(':'), 'indexOf on URL works'); |
|
228 assert.equal(a.valueOf(), EM.valueOf(), 'valueOf on URL works.'); |
|
229 assert.equal(a.toSource(), EM.toSource(), 'toSource on URL works.'); |
|
230 assert.equal(a.lastIndexOf('a'), EM.lastIndexOf('a'), 'lastIndexOf on URL works.'); |
|
231 assert.equal(a.match('t:').toString(), EM.match('t:').toString(), 'match on URL works.'); |
|
232 assert.equal(a.toUpperCase(), EM.toUpperCase(), 'toUpperCase on URL works.'); |
|
233 assert.equal(a.toLowerCase(), EM.toLowerCase(), 'toLowerCase on URL works.'); |
|
234 assert.equal(a.split(':').toString(), EM.split(':').toString(), 'split on URL works.'); |
|
235 assert.equal(a.charAt(2), EM.charAt(2), 'charAt on URL works.'); |
|
236 assert.equal(a.charCodeAt(2), EM.charCodeAt(2), 'charCodeAt on URL works.'); |
|
237 assert.equal(a.concat(EM), EM.concat(a), 'concat on URL works.'); |
|
238 assert.equal(a.substr(2,3), EM.substr(2,3), 'substr on URL works.'); |
|
239 assert.equal(a.substring(2,3), EM.substring(2,3), 'substring on URL works.'); |
|
240 assert.equal(a.trim(), EM.trim(), 'trim on URL works.'); |
|
241 assert.equal(a.trimRight(), EM.trimRight(), 'trimRight on URL works.'); |
|
242 assert.equal(a.trimLeft(), EM.trimLeft(), 'trimLeft on URL works.'); |
|
243 } |
|
244 |
|
245 exports.testDataURLwithouthURI = function (assert) { |
|
246 let dataURL = new DataURL(); |
|
247 |
|
248 assert.equal(dataURL.base64, false, 'base64 is false for empty uri') |
|
249 assert.equal(dataURL.data, '', 'data is an empty string for empty uri') |
|
250 assert.equal(dataURL.mimeType, '', 'mimeType is an empty string for empty uri') |
|
251 assert.equal(Object.keys(dataURL.parameters).length, 0, 'parameters is an empty object for empty uri'); |
|
252 |
|
253 assert.equal(dataURL.toString(), 'data:,'); |
|
254 } |
|
255 |
|
256 exports.testDataURLwithMalformedURI = function (assert) { |
|
257 assert.throws(function() { |
|
258 let dataURL = new DataURL('http://www.mozilla.com/'); |
|
259 }, |
|
260 /Malformed Data URL: http:\/\/www.mozilla.com\//i, |
|
261 'DataURL raises an exception for malformed data uri' |
|
262 ); |
|
263 } |
|
264 |
|
265 exports.testDataURLparse = function (assert) { |
|
266 let dataURL = new DataURL('data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E'); |
|
267 |
|
268 assert.equal(dataURL.base64, false, 'base64 is false for non base64 data uri') |
|
269 assert.equal(dataURL.data, '<h1>Hello!</h1>', 'data is properly decoded') |
|
270 assert.equal(dataURL.mimeType, 'text/html', 'mimeType is set properly') |
|
271 assert.equal(Object.keys(dataURL.parameters).length, 1, 'one parameters specified'); |
|
272 assert.equal(dataURL.parameters['charset'], 'US-ASCII', 'charset parsed'); |
|
273 |
|
274 assert.equal(dataURL.toString(), 'data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E'); |
|
275 } |
|
276 |
|
277 exports.testDataURLparseBase64 = function (assert) { |
|
278 let text = 'Awesome!'; |
|
279 let b64text = 'QXdlc29tZSE='; |
|
280 let dataURL = new DataURL('data:text/plain;base64,' + b64text); |
|
281 |
|
282 assert.equal(dataURL.base64, true, 'base64 is true for base64 encoded data uri') |
|
283 assert.equal(dataURL.data, text, 'data is properly decoded') |
|
284 assert.equal(dataURL.mimeType, 'text/plain', 'mimeType is set properly') |
|
285 assert.equal(Object.keys(dataURL.parameters).length, 1, 'one parameters specified'); |
|
286 assert.equal(dataURL.parameters['base64'], '', 'parameter set without value'); |
|
287 assert.equal(dataURL.toString(), 'data:text/plain;base64,' + encodeURIComponent(b64text)); |
|
288 } |
|
289 |
|
290 exports.testIsValidURI = function (assert) { |
|
291 validURIs().forEach(function (aUri) { |
|
292 assert.equal(isValidURI(aUri), true, aUri + ' is a valid URL'); |
|
293 }); |
|
294 }; |
|
295 |
|
296 exports.testIsInvalidURI = function (assert) { |
|
297 invalidURIs().forEach(function (aUri) { |
|
298 assert.equal(isValidURI(aUri), false, aUri + ' is an invalid URL'); |
|
299 }); |
|
300 }; |
|
301 |
|
302 exports.testURLFromURL = function(assert) { |
|
303 let aURL = URL('http://mozilla.org'); |
|
304 let bURL = URL(aURL); |
|
305 assert.equal(aURL.toString(), bURL.toString(), 'Making a URL from a URL works'); |
|
306 }; |
|
307 |
|
308 exports.testTLD = function(assert) { |
|
309 let urls = [ |
|
310 { url: 'http://my.sub.domains.mozilla.co.uk', tld: 'co.uk' }, |
|
311 { url: 'http://my.mozilla.com', tld: 'com' }, |
|
312 { url: 'http://my.domains.mozilla.org.hk', tld: 'org.hk' }, |
|
313 { url: 'chrome://global/content/blah', tld: 'global' }, |
|
314 { url: 'data:text/plain;base64,QXdlc29tZSE=', tld: null }, |
|
315 { url: 'https://1.2.3.4', tld: null } |
|
316 ]; |
|
317 |
|
318 urls.forEach(function (uri) { |
|
319 assert.equal(getTLD(uri.url), uri.tld); |
|
320 assert.equal(getTLD(URL(uri.url)), uri.tld); |
|
321 }); |
|
322 } |
|
323 |
|
324 exports.testWindowLocationMatch = function (assert, done) { |
|
325 let server = httpd.startServerAsync(port); |
|
326 server.registerPathHandler('/index.html', function (request, response) { |
|
327 response.write('<html><head></head><body><h1>url tests</h1></body></html>'); |
|
328 }); |
|
329 |
|
330 let aUrl = 'http://localhost:' + port + '/index.html?q=aQuery#somehash'; |
|
331 let urlObject = URL(aUrl); |
|
332 |
|
333 tabs.open({ |
|
334 url: aUrl, |
|
335 onReady: function (tab) { |
|
336 tab.attach({ |
|
337 onMessage: function (loc) { |
|
338 for (let prop in loc) { |
|
339 assert.equal(urlObject[prop], loc[prop], prop + ' matches'); |
|
340 } |
|
341 |
|
342 tab.close(function() server.stop(done)); |
|
343 }, |
|
344 contentScript: '(' + function () { |
|
345 let res = {}; |
|
346 // `origin` is `null` in this context??? |
|
347 let props = 'hostname,port,pathname,hash,href,protocol,search'.split(','); |
|
348 props.forEach(function (prop) { |
|
349 res[prop] = window.location[prop]; |
|
350 }); |
|
351 self.postMessage(res); |
|
352 } + ')()' |
|
353 }); |
|
354 } |
|
355 }) |
|
356 }; |
|
357 |
|
358 exports.testURLInRegExpTest = function(assert) { |
|
359 let url = 'https://mozilla.org'; |
|
360 assert.equal((new RegExp(url).test(URL(url))), true, 'URL instances work in a RegExp test'); |
|
361 } |
|
362 |
|
363 exports.testLocalURL = function(assert) { |
|
364 [ |
|
365 'data:text/html;charset=utf-8,foo and bar', |
|
366 'data:text/plain,foo and bar', |
|
367 'resource://gre/modules/commonjs/', |
|
368 'chrome://browser/content/browser.xul' |
|
369 ].forEach(aUri => { |
|
370 assert.ok(isLocalURL(aUri), aUri + ' is a Local URL'); |
|
371 }) |
|
372 |
|
373 } |
|
374 |
|
375 exports.testLocalURLwithRemoteURL = function(assert) { |
|
376 validURIs().filter(url => !url.startsWith('data:')).forEach(aUri => { |
|
377 assert.ok(!isLocalURL(aUri), aUri + ' is an invalid Local URL'); |
|
378 }); |
|
379 } |
|
380 |
|
381 exports.testLocalURLwithInvalidURL = function(assert) { |
|
382 invalidURIs().concat([ |
|
383 'data:foo and bar', |
|
384 'resource:// must fail', |
|
385 'chrome:// here too' |
|
386 ]).forEach(aUri => { |
|
387 assert.ok(!isLocalURL(aUri), aUri + ' is an invalid Local URL'); |
|
388 }); |
|
389 } |
|
390 |
|
391 function validURIs() { |
|
392 return [ |
|
393 'http://foo.com/blah_blah', |
|
394 'http://foo.com/blah_blah/', |
|
395 'http://foo.com/blah_blah_(wikipedia)', |
|
396 'http://foo.com/blah_blah_(wikipedia)_(again)', |
|
397 'http://www.example.com/wpstyle/?p=364', |
|
398 'https://www.example.com/foo/?bar=baz&inga=42&quux', |
|
399 'http://✪df.ws/123', |
|
400 'http://userid:password@example.com:8080', |
|
401 'http://userid:password@example.com:8080/', |
|
402 'http://userid@example.com', |
|
403 'http://userid@example.com/', |
|
404 'http://userid@example.com:8080', |
|
405 'http://userid@example.com:8080/', |
|
406 'http://userid:password@example.com', |
|
407 'http://userid:password@example.com/', |
|
408 'http://142.42.1.1/', |
|
409 'http://142.42.1.1:8080/', |
|
410 'http://➡.ws/䨹', |
|
411 'http://⌘.ws', |
|
412 'http://⌘.ws/', |
|
413 'http://foo.com/blah_(wikipedia)#cite-1', |
|
414 'http://foo.com/blah_(wikipedia)_blah#cite-1', |
|
415 'http://foo.com/unicode_(✪)_in_parens', |
|
416 'http://foo.com/(something)?after=parens', |
|
417 'http://☺.damowmow.com/', |
|
418 'http://code.google.com/events/#&product=browser', |
|
419 'http://j.mp', |
|
420 'ftp://foo.bar/baz', |
|
421 'http://foo.bar/?q=Test%20URL-encoded%20stuff', |
|
422 'http://مثال.إختبار', |
|
423 'http://例子.测试', |
|
424 'http://उदाहरण.परीक्षा', |
|
425 'http://-.~_!$&\'()*+,;=:%40:80%2f::::::@example.com', |
|
426 'http://1337.net', |
|
427 'http://a.b-c.de', |
|
428 'http://223.255.255.254', |
|
429 // Also want to validate data-uris, localhost |
|
430 'http://localhost:8432/some-file.js', |
|
431 'data:text/plain;base64,', |
|
432 'data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E', |
|
433 'data:text/html;charset=utf-8,' |
|
434 ]; |
|
435 } |
|
436 |
|
437 // Some invalidURIs are valid according to the regex used, |
|
438 // can be improved in the future, but better to pass some |
|
439 // invalid URLs than prevent valid URLs |
|
440 |
|
441 function invalidURIs () { |
|
442 return [ |
|
443 // 'http://', |
|
444 // 'http://.', |
|
445 // 'http://..', |
|
446 // 'http://../', |
|
447 // 'http://?', |
|
448 // 'http://??', |
|
449 // 'http://??/', |
|
450 // 'http://#', |
|
451 // 'http://##', |
|
452 // 'http://##/', |
|
453 // 'http://foo.bar?q=Spaces should be encoded', |
|
454 'not a url', |
|
455 '//', |
|
456 '//a', |
|
457 '///a', |
|
458 '///', |
|
459 // 'http:///a', |
|
460 'foo.com', |
|
461 'http:// shouldfail.com', |
|
462 ':// should fail', |
|
463 // 'http://foo.bar/foo(bar)baz quux', |
|
464 // 'http://-error-.invalid/', |
|
465 // 'http://a.b--c.de/', |
|
466 // 'http://-a.b.co', |
|
467 // 'http://a.b-.co', |
|
468 // 'http://0.0.0.0', |
|
469 // 'http://10.1.1.0', |
|
470 // 'http://10.1.1.255', |
|
471 // 'http://224.1.1.1', |
|
472 // 'http://1.1.1.1.1', |
|
473 // 'http://123.123.123', |
|
474 // 'http://3628126748', |
|
475 // 'http://.www.foo.bar/', |
|
476 // 'http://www.foo.bar./', |
|
477 // 'http://.www.foo.bar./', |
|
478 // 'http://10.1.1.1', |
|
479 // 'http://10.1.1.254' |
|
480 ]; |
|
481 } |
|
482 |
|
483 require('sdk/test').run(exports); |