Comparing version 0.0.4 to 0.1.0
190
index.js
'use strict'; | ||
var required = require('requires-port') | ||
, lolcation = require('./lolcation') | ||
, qs = require('querystringify'); | ||
// | ||
// MOARE: Mother Of All Regular Expressions. | ||
// | ||
var regexp = /^(?:(?:(([^:\/#\?]+:)?(?:(?:\/\/)(?:(?:(?:([^:@\/#\?]+)(?:\:([^:@\/#\?]*))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((?:\/?(?:[^\/\?#]+\/+)*)(?:[^\?#]*)))?(\?[^#]+)?)(#.*)?/ | ||
, keys = ',,protocol,username,password,host,hostname,port,pathname,query,hash'.split(',') | ||
, parts = keys.length; | ||
/** | ||
* A DOM and Node.js compatible URL parser which leverages the DOM to do the | ||
* actual parsing of our URLs. | ||
* The actual URL instance. Instead of returning an object we've opted-in to | ||
* create an actual constructor as it's much more memory efficient and | ||
* faster and it pleases my CDO. | ||
* | ||
* @type {Function} | ||
* @param {String} url URL that needs to be parsed. | ||
* @param {Boolean} qs Also parse the query string. | ||
* @returns {Object} A parsed URL. | ||
* @constructor | ||
* @param {String} address URL we want to parse. | ||
* @param {Boolean|function} parser Parser for the query string. | ||
* @param {Object} location Location defaults for relative paths. | ||
* @api public | ||
*/ | ||
var parse = 'undefined' !== typeof document ? function parse(url, qs) { | ||
var div = document.createElement('div') | ||
, data = {} | ||
, key | ||
, a; | ||
function URL(address, location, parser) { | ||
if (!(this instanceof URL)) return new URL(address, location, parser); | ||
// | ||
// Uses an innerHTML property to obtain an absolute URL for older browser | ||
// support like IE6. | ||
// The following if statements allows this module two have compatibility with | ||
// 2 different API: | ||
// | ||
// @see http://grack.com/blog/2009/11/17/absolutizing-url-in-javascript/ | ||
// 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments | ||
// where the boolean indicates that the query string should also be parsed. | ||
// | ||
div.innerHTML = '<a href="' + url + '"/>'; | ||
a = div.firstChild; | ||
// 2. The `URL` interface of the browser which accepts a URL, object as | ||
// arguments. The supplied object will be used as default values / fall-back | ||
// for relative paths. | ||
// | ||
// Transform it from a readOnly object to a read/writable object so we can | ||
// change some parsed values. This is required if we ever want to override | ||
// a port number etc. (as browsers remove port 443 and 80 from the URL's). | ||
// | ||
for (key in a) { | ||
if ('string' === typeof a[key] || 'number' === typeof a[key]) { | ||
data[key] = a[key]; | ||
} | ||
} | ||
if ('object' !== typeof location) { parser = location; location = null; } | ||
if (parser && 'function' !== typeof parser) parser = qs.parse; | ||
// | ||
// encodeURI and decodeURI are needed to normalize URL between IE and non-IE, | ||
// since IE doesn't encode the href property value and return it | ||
// | ||
// @see http://jsfiddle.net/Yq9M8/1/ | ||
// | ||
data.href = encodeURI(decodeURI(data.href)); | ||
location = lolcation(location); | ||
// | ||
// If we don't obtain a port number (e.g. when using zombie) then try | ||
// and guess at a value from the 'href' value. | ||
// | ||
if (!data.port) { | ||
var splits = (data.href || '').split('/'); | ||
if (splits.length > 2) { | ||
var host = splits[2] | ||
, atSignIndex = host.lastIndexOf('@'); | ||
for (var i = 0, bits = regexp.exec(address), key; i < parts; key = keys[++i]) { | ||
if (key) { | ||
this[key] = bits[i] || location[key] || ''; | ||
if (~atSignIndex) host = host.slice(atSignIndex + 1); | ||
splits = host.split(':'); | ||
if (splits.length === 2) data.port = splits[1]; | ||
// | ||
// The protocol, host, host name should always be lower cased even if they | ||
// are supplied in uppercase. This way, when people generate an `origin` | ||
// it be correct. | ||
// | ||
if (i === 2 || i === 5 || i === 6) this[key] = this[key].toLowerCase(); | ||
} | ||
@@ -65,93 +58,60 @@ } | ||
// | ||
// IE quirk: The `protocol` is parsed as ":" when a protocol agnostic URL | ||
// is used. In this case we extract the value from the `href` value. In | ||
// addition to that, it's possible in IE11 that the protocol is an string for | ||
// relative URL's. | ||
// Also parse the supplied query string in to an object. If we're supplied | ||
// with a custom parser as function use that instead of the default build-in | ||
// parser. | ||
// | ||
// @see https://github.com/primus/primus/issues/242 | ||
// | ||
if (!data.protocol || ':' === data.protocol) { | ||
data.protocol = data.href.substr(0, data.href.indexOf(':') + 1); | ||
} | ||
if (parser) this.query = parser(this.query); | ||
// | ||
// Safari 5.1.7 (windows) quirk: When parsing a URL without a port number | ||
// the `port` in the data object will default to "0" instead of the expected | ||
// "". We're going to do an explicit check on "0" and force it to "". | ||
// We should not add port numbers if they are already the default port number | ||
// for a given protocol. As the host also contains the port number we're going | ||
// override it with the hostname which contains no port number. | ||
// | ||
if ('0' === data.port) data.port = ''; | ||
// | ||
// Browsers do not parse authorization information, so we need to extract | ||
// that from the URL. | ||
// | ||
if (~data.href.indexOf('@') && !data.auth) { | ||
var start = data.protocol.length + 2; | ||
data.auth = data.href.slice(start, data.href.indexOf(data.pathname, start)).split('@')[0]; | ||
if (!required(this.port, this.protocol)) { | ||
this.host = this.hostname; | ||
this.port = ''; | ||
} | ||
if (qs && 'string' === typeof data.query) { | ||
data.query = querystring(data.query || data.search); | ||
} | ||
data.query = data.query || data.search; | ||
return data; | ||
} : require('url').parse; | ||
/** | ||
* Simple query string parser. | ||
* | ||
* @param {String} query The query string that needs to be parsed. | ||
* @returns {Object} | ||
* @api public | ||
*/ | ||
function querystring(query) { | ||
var parser = /([^=?&]+)=([^&]*)/g | ||
, result = {} | ||
, part; | ||
// | ||
// Little nifty parsing hack, leverage the fact that RegExp.exec increments | ||
// the lastIndex property so we can continue executing this loop until we've | ||
// parsed all results. | ||
// The href is just the compiled result. | ||
// | ||
for (; | ||
part = parser.exec(query); | ||
result[decodeURIComponent(part[1])] = decodeURIComponent(part[2]) | ||
); | ||
return result; | ||
this.href = this.toString(); | ||
} | ||
/** | ||
* Transform a query string to an object. | ||
* Transform the properties back in to a valid and full URL string. | ||
* | ||
* @param {Object} obj Object that should be transformed. | ||
* @param {Function} stringify Optional query stringify function. | ||
* @returns {String} | ||
* @api public | ||
*/ | ||
function querystringify(obj, prefix) { | ||
prefix = prefix || ''; | ||
URL.prototype.toString = function toString(stringify) { | ||
if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify; | ||
var pairs = []; | ||
var result = this.protocol +'//' | ||
, query; | ||
// | ||
// Optionally prefix with a '?' if needed | ||
// | ||
if ('string' !== typeof prefix) prefix = '?'; | ||
if (this.username) result += this.username +':'+ this.password +'@'; | ||
for (var key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
pairs.push(encodeURIComponent(key) +'='+ encodeURIComponent(obj[key])); | ||
} | ||
result += this.hostname; | ||
if (this.port) result += ':'+ this.port; | ||
result += this.pathname; | ||
if (this.query) { | ||
if ('object' === typeof this.query) query = stringify(this.query); | ||
else query = this.query; | ||
result += (query.charAt(0) === '?' ? '' : '?') + query; | ||
} | ||
return prefix + pairs.join('&'); | ||
} | ||
if (this.hash) result += this.hash; | ||
return result; | ||
}; | ||
// | ||
// Expose the module. | ||
// Expose the URL parser. | ||
// | ||
parse.querystringify = querystringify; | ||
parse.querystring = querystring; | ||
module.exports = parse; | ||
URL.qs = qs; | ||
module.exports = URL; |
{ | ||
"name": "url-parse", | ||
"version": "0.0.4", | ||
"version": "0.1.0", | ||
"description": "Parse URL in node using the URL module and in the browser using the DOM", | ||
@@ -10,3 +10,3 @@ "main": "index.js", | ||
"test-travis": "istanbul cover node_modules/.bin/_mocha --report lcovonly -- --reporter spec --ui bdd test.js", | ||
"browserify": "browserify index.js -o browserified.js" | ||
"browserify": "browserify index.js -o dist/url-parse.js --standalone URLParse" | ||
}, | ||
@@ -53,3 +53,7 @@ "keywords": [ | ||
"url": "https://github.com/unshiftio/url-parse" | ||
}, | ||
"dependencies": { | ||
"querystringify": "0.0.x", | ||
"requires-port": "0.0.x" | ||
} | ||
} |
# url-parse | ||
[![Made by unshift](https://img.shields.io/badge/made%20by-unshift-00ffcc.svg?style=flat-square)](http://unshift.io)[![Version npm](http://img.shields.io/npm/v/url-parse.svg?style=flat-square)](http://browsenpm.org/package/url-parse)[![Build Status](http://img.shields.io/travis/unshiftio/url-parse/master.svg?style=flat-square)](https://travis-ci.org/unshiftio/url-parse)[![Dependencies](https://img.shields.io/david/unshiftio/url-parse.svg?style=flat-square)](https://david-dm.org/unshiftio/url-parse)[![Coverage Status](http://img.shields.io/coveralls/unshiftio/url-parse/master.svg?style=flat-square)](https://coveralls.io/r/unshiftio/url-parse?branch=master)[![IRC channel](http://img.shields.io/badge/IRC-irc.freenode.net%23unshift-00a8ff.svg?style=flat-square)](http://webchat.freenode.net/?channels=unshift) | ||
[![Build Status](https://travis-ci.org/unshiftio/url-parse.svg?branch=master)](https://travis-ci.org/unshiftio/url-parse) | ||
[![NPM version](https://badge.fury.io/js/url-parse.svg)](http://badge.fury.io/js/url-parse) | ||
[![Coverage Status](https://img.shields.io/coveralls/unshiftio/url-parse.svg)](https://coveralls.io/r/unshiftio/url-parse?branch=master) | ||
The `url-parse` method exposes two different API interfaces. The `url` interface | ||
that you know from Node.js and the new `URL` interface that is available in the | ||
latest browsers. | ||
When required on node it will expose the `url` module's `.parse` method. When | ||
required in the browser it will offload the URL parsing to the `<a>` element in | ||
the DOM. This allows the module to be really tiny on the browser and still be | ||
usable on node. | ||
Since `0.1` we've moved away from using the DOM's `<a>` element for URL parsing | ||
and moving to a full Regular Expression solution. The main reason for this | ||
change is to make the URL parser available in different JavaScript environments | ||
as you don't always have access to the DOM like `Worker` environments. This | ||
module still have a really small foot print as this module's main intention is | ||
to be bundled with client-side code. | ||
In addition to parsing URL's it also has a really simple query string parser and | ||
query string stringify. | ||
In addition to URL parsing we also expose the bundled `querystringify` module. | ||
@@ -31,33 +33,45 @@ ## Installation | ||
var parse = require('url-parse'); | ||
var URL = require('url-parse'); | ||
``` | ||
To parse an URL simply call the `parse` method with the URL that needs to be | ||
To parse an URL simply call the `URL` method with the URL that needs to be | ||
transformed in to an object. | ||
```js | ||
var url = parse('https://github.com/foo/bar'); | ||
var url = new URL('https://github.com/foo/bar'); | ||
``` | ||
The URL should now be somewhat the same as the node.js's `url.parse` output. The | ||
notable exception being that in the browser not all properties would be set to | ||
null. | ||
The `new` keyword is optional but it will save you an extra function invocation. | ||
In the example above we've demonstrated the URL interface, but as said in the | ||
module description we also support the node.js interface. So you could also use | ||
the library in this way: | ||
### parse.querystring() | ||
```js | ||
'use strict'; | ||
Parse the given query string and return an object representation from it. | ||
```js | ||
parse.querystring('foo=bar&bar=foo'); | ||
parse.querystring('?foo=bar&bar=foo'); | ||
var parse = require('url-parse') | ||
, url = parse('https://github.com/foo/bar', true); | ||
``` | ||
### parse.querystringify() | ||
The returned `url` instance contains the following properties: | ||
Take an object and make a query string from it. | ||
- `protocol`: Without slashes `http:`. | ||
- `username`: Username of basic authentication. | ||
- `password`: Password of basic authentication. | ||
- `host`: Host name with port number. | ||
- `hostname`: Host name without port number. | ||
- `port`: Optional port number. | ||
- `pathname`: URL path. | ||
- `query`: Prefixed with `?` | ||
- `hash`: Prefixed with `#` | ||
## URL.stringify() | ||
The returned `url` object comes with a custom `toString` method which will | ||
generate a full URL again when called. The method accepts an extra function | ||
which will stringify the query string for you. If you don't supply a function we | ||
will use our default method. | ||
```js | ||
parse.querystringify({ foo: 'bar' }); // foo=bar | ||
parse.querystringify({ foo: 'bar' }, true); // ?foo=bar | ||
parse.querystringify({ foo: 'bar' }, '&'); // &foo=bar | ||
var location = url.toString(); // http://example.com/whatever/?qs=32 | ||
``` | ||
@@ -64,0 +78,0 @@ |
131
test.js
@@ -5,5 +5,3 @@ describe('url-parse', function () { | ||
var assume = require('assume') | ||
, parse = require('./') | ||
, qs = parse.querystring | ||
, qsify = parse.querystringify; | ||
, parse = require('./'); | ||
@@ -14,53 +12,104 @@ it('exposes parse as a function', function () { | ||
describe('#querystringify', function () { | ||
var obj = { | ||
foo: 'bar', | ||
bar: 'foo' | ||
}; | ||
it('parsers the query string', function () { | ||
var url = 'http://google.com/?foo=bar' | ||
, data = parse(url, true); | ||
it('is exposed as method', function () { | ||
assume(qsify).is.a('function'); | ||
}); | ||
assume(data.query).is.a('object'); | ||
assume(data.query.foo).equals('bar'); | ||
}); | ||
it('transforms an object', function () { | ||
assume(qsify(obj)).equals('foo=bar&bar=foo'); | ||
}); | ||
it('allows a custom function as parser', function () { | ||
var url = 'http://google.com/?foo=bar' | ||
, data = parse(url, function () { return '1337'; }); | ||
it('can optionally prefix', function () { | ||
assume(qsify(obj, true)).equals('?foo=bar&bar=foo'); | ||
}); | ||
assume(data.query).equals('1337'); | ||
}); | ||
it('can prefix with custom things', function () { | ||
assume(qsify(obj, '&')).equals('&foo=bar&bar=foo'); | ||
}); | ||
it('allows a custom stringify function', function () { | ||
var url = 'http://google.com/?foo=bar' | ||
, data = parse(url, true) | ||
, str; | ||
str = data.toString(function () { return 'lolcakes'; }); | ||
assume(str).equals('http://google.com/?lolcakes'); | ||
}); | ||
describe('querystring', function () { | ||
it('is exposed as method', function () { | ||
assume(qs).is.a('function'); | ||
}); | ||
it('allows a custom location object', function () { | ||
var url = '/foo?foo=bar' | ||
, data = parse(url, parse('http://google.com')); | ||
it('will parse a querystring to an object', function () { | ||
var obj = qs('foo=bar'); | ||
assume(data.href).equals('http://google.com/foo?foo=bar'); | ||
}); | ||
assume(obj).is.a('object'); | ||
assume(obj.foo).equals('bar'); | ||
}); | ||
it('is blob: location aware', function () { | ||
var blob = {"hash":"","search":"","pathname":"https%3A//gist.github.com/3f272586-6dac-4e29-92d0-f674f2dde618","port":"","hostname":"","host":"","protocol":"blob:","origin":"https://gist.github.com","href":"blob:https%3A//gist.github.com/3f272586-6dac-4e29-92d0-f674f2dde618"} | ||
, url = '/unshiftio/url-parse' | ||
, data = parse(url, blob); | ||
it('will parse the `query` property of the parse', function () { | ||
var url = parse('https://google.com?foo=bar') | ||
, obj = qs(url.query); | ||
assume(data.href).equals('https://gist.github.com/unshiftio/url-parse'); | ||
}); | ||
assume(obj).is.a('object'); | ||
assume(obj.foo).equals('bar'); | ||
}); | ||
it('converts protocol to lowercase', function () { | ||
var url = 'HTTP://example.com'; | ||
it('will also work if querystring is prefixed with ?', function () { | ||
var obj = qs('?foo=bar&shizzle=mynizzle'); | ||
assume(parse(url).protocol).equals('http:'); | ||
}); | ||
assume(obj).is.a('object'); | ||
assume(obj.foo).equals('bar'); | ||
assume(obj.shizzle).equals('mynizzle'); | ||
}); | ||
it('converts hostname to lowercase', function () { | ||
var url = 'HTTP://fOo.eXaMPle.com'; | ||
assume(parse(url).hostname).equals('foo.example.com'); | ||
}); | ||
it('does not lowercase the USER:PASS', function () { | ||
var url = 'HTTP://USER:PASS@EXAMPLE.COM'; | ||
assume(parse(url).username).equals('USER'); | ||
assume(parse(url).password).equals('PASS'); | ||
}); | ||
it('does not lowercase the path', function () { | ||
var url = 'HTTP://X.COM/Y/Z'; | ||
assume(parse(url).pathname).equals('/Y/Z'); | ||
}); | ||
it('removes default port numbers', function () { | ||
var url = 'http://example.com:80' | ||
, parsed = parse(url); | ||
assume(parsed.port).equals(''); | ||
assume(parsed.host).equals('example.com'); | ||
assume(parsed.hostname).equals('example.com'); | ||
assume(parsed.href).equals('http://example.com'); | ||
}); | ||
it('accepts @ in pathnames', function () { | ||
var url = 'http://mt0.google.com/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s='; | ||
assume(parse(url).pathname).equals('/vt/lyrs=m@114&hl=en&src=api&x=2&y=2&z=3&s='); | ||
}); | ||
it('accepts multiple ???', function () { | ||
var url = 'http://mt0.google.com/vt/lyrs=m@114???&hl=en&src=api&x=2&y=2&z=3&s='; | ||
assume(parse(url).query).equals('???&hl=en&src=api&x=2&y=2&z=3&s='); | ||
}); | ||
describe('fuzzy', function () { | ||
var fuzz = require('./fuzzy') | ||
, times = 10; | ||
for (var i = 0; i < times; i++) { | ||
(function (spec) { | ||
it('parses: '+ spec.href, function () { | ||
var url = parse(spec.href) | ||
, prop; | ||
for (prop in spec) { | ||
assume(url[prop]).equals(spec[prop]); | ||
} | ||
}); | ||
})(fuzz()); | ||
} | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
15219
8
276
81
2
2
+ Addedquerystringify@0.0.x
+ Addedrequires-port@0.0.x
+ Addedquerystringify@0.0.4(transitive)
+ Addedrequires-port@0.0.1(transitive)