Flagrant Badassery

A JavaScript and regular expression centric blog

parseUri 1.2: Split URLs in JavaScript

I've just updated parseUri. If you haven't seen the older version, parseUri is a function which splits any well-formed URI into its parts, all of which are optional. Its combination of accuracy, flexibility, and brevity is unrivaled.

Highlights:

  • Comprehensively splits URIs, including splitting the query string into key/value pairs. (Enhanced)
  • Two parsing modes: loose and strict. (New)
  • Easy to use (returns an object, so you can do, e.g., parseUri(uri).anchor).
  • Offers convenient, pre-concatenated components (path = directory and file; authority = userInfo, host, and port; etc.)
  • Change the default names of URI parts without editing the function, by updating parseUri.options.key. (New)
  • Exceptionally lightweight (1 KB before minification or gzipping).
  • Released under the MIT License.

Try the demo, but make sure to come back and read the details below.

Details:

Older versions of this function used what's now called loose parsing mode (which is still the default in this version). Loose mode deviates slightly from the official generic URI spec (RFC 3986), but by doing so allows the function to split URIs in a way that most end users would expect intuitively. However, the finer details of loose mode preclude it from properly handling relative paths which do not start from root (e.g., "../file.html" or "dir/file.html"). On the other hand, strict mode attempts to split URIs according to RFC 3986. Specifically, in loose mode, directories don't need to end with a slash (e.g., the "dir" in "/dir?query" is treated as a directory rather than a file name), and the URI can start with an authority without being preceded by "//" (which means that the "yahoo.com" in "yahoo.com/search/" is treated as the host, rather than part of the directory path).

Since I've assumed that most developers will consistently want to use one mode or the other, the parsing mode is not specified as an argument when running parseUri, but rather as a property of the parseUri function itself. Simply run the following line of code to switch to strict mode:

parseUri.options.strictMode = true;

From that point forward, parseUri will work in strict mode (until you turn it back off).

The code:

// parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License

function parseUri (str) {
	var	o   = parseUri.options,
		m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
		uri = {},
		i   = 14;

	while (i--) uri[o.key[i]] = m[i] || "";

	uri[o.q.name] = {};
	uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
		if ($1) uri[o.q.name][$1] = $2;
	});

	return uri;
};

parseUri.options = {
	strictMode: false,
	key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
	q:   {
		name:   "queryKey",
		parser: /(?:^|&)([^&=]*)=?([^&]*)/g
	},
	parser: {
		strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
		loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
	}
};

You can download it or run the test suite.

parseUri has no dependencies, and has been tested in IE 5.5–7, Firefox 2.0.0.4, Opera 9.21, Safari 3.0.1 beta for Windows, and Swift 0.2.

There Are 60 Responses So Far. »

  1. Nice function. I miss the ability to get the query string as a list of key/value pairs, with a second argument to specify the parameter separator (with the ampersand by default). It’ll make the code a bit longer but the function would become much more complete.

  2. Hi Iván, thanks. parseUri already returns key/value pairs for the query string in an object called queryKey. For example, to access the value of a query key called “search” you could write parseUri(uri).queryKey.search

    You can see this in action on the test page, when you click the Parse button. Does that meet your needs, or were you thinking of something different?

  3. Sorry Steve, I didn’t notice it. That was exactly what I was talking about!
    However, a minor improvement would be to accept as optional second parameter a string which holds in each char a possible argument separator, most people use the ampersand but according to the RFC any char except ‘?’ and ‘#’. See my post http://blog.netxus.es/blog/url-argument-separator

    I use myself the semi-colon ‘;’ as argument separator in some projects, since there is no need to escape it when used in XML/XHTML documents.

  4. Iván, I have no plans to support arbitrary delimiters in the query string because that would significantly complicate the code for the benefit of probably less than 0.1% of developers, and as you noted in your blog post, server side languages like PHP, ASP.net, etc. generally don’t support delimiters other than “&” without special configuration, if at all.

    However, it would be easy to manually change the code to support both “&” and “;” delimiters in your personal copy. Just change /&?([^&=]*)=?([^&]*)/g from within the uri.query.replace() statement to /[&;]?([^&;=]*)=?([^&;]*)/g (or, to support only semicolons, use /;?([^;=]*)=?([^;]*)/g).

    In any case, this does not affect the main URI parsing (only the splitting of the query string into key/value pairs), so you could also implement a separate function to specifically work with the query string with maximum flexibility.

  5. Steve,

    I get your point and quite agree with it. What about placing the query RE in the options object so it can be easily modified by those with especial needs?

  6. wow, that’s one crazy regexp right there…

  7. @Iván Montes,

    Good call. I’ve gone ahead and moved the query regex as well as the query object’s name into the options object and upped the version number from 1.1 to 1.2. Thanks for the suggestion.

  8. [...] parseUri 1.2: Split URLs in JavaScript easy url parsing (tags: code library parsing programming javascript url web webdev uri) [...]

  9. [...] ParseURL на яваскрипте – ParseURL на яваскрипте :) демо [...]

  10. parseUri 1.2: JavaScript URL feldolgozó…

    A php parse_url függvényét már ismerjük. Most ismerjük meg ugyanezt javascripthez is.
    Steven Levithan: parseUri 1.2: Split URLs in JavaScript

    Script:
    /* parseUri 1.2; MIT License
    By Steven Levithan <http://stevenlevithan.com> */

    var pars…

  11. [...] parseUri 1.2: Split URLs in JavaScript (tags: javascript parsing programming opensource) [...]

  12. [...] parseUri 1.2: Split URLs in JavaScript [...]

  13. Hey Steven,

    I couldn’t find any contact info so I’m just leaving a message here. I’m the maintainer of the CFJSON project and I’m trying to fix some things and would benefit from some regexp help and I know you’re quite good with them. If you think you could give me a hand shoot me an email (I put my email in the comment form) and I’ll tell you what I’m trying to fix. Something tells me you’ll be able to solve my problem without too much effort. Thanks in advance.

  14. Thomas, I just sent you an email. I’ll try to help if I can.

  15. [...] parseUri 1.2: Split URLs in JavaScript awesome [...]

  16. Just as a possibility, if you create an anchor, assign the url as href, then you can access the anchor’s host,port,protocol,search,hash. This worked in FF dunno if it will work in the rest. I’m just saying this because it might make your code shorter :)

    I hope it helped

  17. @Ariel Flesler:

    It definitely would not make the code shorter, if you wanted to keep the same functionality as is currently provided. But still, that’s an interesting idea, if it works. ;-)

  18. It looks like it doesn’t support the correct / standard URL parameter delimiter which is actually the ampersand entity ‘&amp;’ not a raw ampersand ‘&’. That’s precisely the functionality I am looking for as I’m getting an annoying error cropping up with one of my scripts that just uses a plain javascript split on ‘&’.

    That said, here are some of the top 10 xhtml errors:
    1. The use of a raw amperstand in a link query string. The w3c validator reports this as “cannot generate system identifier for general entity” because you’ve tried to create a new entity &xxxxxxx and not an encoded & amp ; in the string. Replace all & with &amp; in urls.
    http://elliottback.com/wp/archives/2005/08/14/ten-steps-to-valid-html/

  19. Um, no. This code deals with URIs, not HTML. And of course, &amp; is not the only HTML entity to deal with.

  20. As Safari 2.0 users may have noticed, the ‘queryString’ part isn’t filled because this version does not support a function as second parameter of String.prototype.replace() :-(

  21. I get the following error using your code

    o has no properties

    [Break on this error] m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),

    Please let me know.

    Thanks in advance!
    Neo

  22. @zcrux, does the demo page work for you?

  23. Hey there,

    Is the online demo using the same version of the JS that is available for download?

    I have a URL that will parse in the demo just fine, but returns undefined when doing this: document.write(parseUri(urls).queryKey.q);

    This is the URL btw: http://search.yahoo.com/search?p=flavor+flav&fr=yfp-t-501&toggle=1&cop=mss&ei=UTF-8

  24. @Raj, yes, the demo uses the same code, which you could have easily verified yourself (the source files are uncompressed). Your URL does not contain a q key in the query, so the line of code you posted above is working correctly.

  25. Steven,

    My apologies. Initially, I felt some apprehension about browsing straight to the JS files in the demo. Just being respectful.

    Then I got over it. :)

    Raj

  26. Thank you thank you thank you! In a short few weeks I’ve built at least two functions on top of this little gem and all my pages have components that will depend on them. Such a blessing!

  27. I *love* this function, it is so incredibly powerful and helpful! Thank you so much for it.

    For an open-source project I’m working on, I needed this same functionality inside of a Flash SWF. So, I’ve ported your 1.2.1 code to this regular AS3 function (not an object/class, though that would be easy to get from what I’ve done, too!).

    Since escaping all that reg-ex stuff to post here in the comments would be ridiculous, I’m going to post a URL here that can be used to retrieve a text file with the code in it.

    http://www.flensed.com/parseUri-AS3.txt

    Steven, if want to, grab that text file and place the formatted code somewhere on this page or in this comment, that way people who come here later won’t have to go to my site to find it. :)

  28. @Kyle Simpson and @Hat, thanks! Kyle, I’ll post your AS3 port here for posterity, but there’s no reason people shouldn’t get it from your site!

    //  ****************************
    //  Ported by Kyle Simpson from Javascript to AS3 from:
    //	parseUri 1.2.1
    //	(c) 2007 Steven Levithan <stevenlevithan.com>
    //	MIT License
    //  ****************************
    
    public function parseUri(str:String, strictMode:Boolean=false):Object {
    	var o:Object = new Object();
    	o.strictMode = strictMode;
    	o.key = new Array("source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor");
    	o.q = new Object();
    	o.q.name = "queryKey";
    	o.q.parser = /(?:^|&)([^&=]*)=?([^&]*)/g
    	o.parser = new Object();
    	o.parser.strict = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/
    	o.parser.loose = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
    
    	var m:Object = o.parser[o.strictMode ? "strict" : "loose"].exec(str);
    	var uri:Object = new Object();
    	var i:int = 14;
    	while (i--) uri[o.key[i]] = m[i] || "";
    	uri[o.q.name] = new Object();
    	uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
    		if ($1) uri[o.q.name][$1] = $2;
    	});
    	return uri;
    }
  29. Big thanks for that amazing function! It helps me a lot with my website programming!

  30. Nice job Steve, very useful… I thought I’d point out a real-world example of a slight problem I saw while using your code, though. For a URL like:

    http://www.zitnay.com/stuff/badurl.php?param=test@test

    it detects everything before the “@” as the userthus screwing up the rest of the parse.

    I realize the “@” should really be URL encoded to “%40″, but like I said, I found a real-world example of this on a live website. So, you might consider adding support for this case, at least in the loose version.

  31. Brilliant! Thanks, this code is powerful yet very friendly allowing non programmers (like myself) to implement it with ease :)

  32. Thanks, nice code!

    I just change the query string parse function. This decode query parameters and distinct ‘key&’ from ‘key=&’: first get ‘true’ as value, last — empty string.

    [code]
    uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2, $3) {
    if ($1) uri[o.q.name][decodeURIComponent($1)] = $2 ? decodeURIComponent($3) : true;
    });
    ...
    parseUri.options = {
    ...
    q: {
    name: "queryKey",
    parser: /(?:^|[&])([^&=]*)(=?)([^&]*)/g
    },
    [/code]

    And small shugar for strings:
    [code]
    // usage: "http://blog.stevenlevithan.com/archives/parseuri".parseUri().

    String.prototype.parseUri = function () { return parseUri(this.valueOf()); };
    [/code]

  33. Hi Steven,

    Just to let you know I have put together a jQuery plugin based on your URI parser, which includes a bit of added functionality.

    You can check it out here – http://projects.allmarkedup.com/jquery_url_parser/

    Let me know if you have any suggestions/improvements etc! And thanks for the excellent work.

  34. Steve,

    I was integrating your parser and noticed that the query parser function is “lossy”. For example, given the query string:

    “a=1&b=2&c=1&c=2&c=3″

    Parsing will result in this query hash: { a: 1, b: 2, c: 3 }

    Here is a rewritten parser function:


    if ($1) {
    if (! b9j.isValue(queryHash[$1])) {
    queryHash[$1] = $2;
    }
    else if (b9j.isArray(queryHash[$1])) {
    queryHash[$1].push($2);
    }
    else {
    queryHash[$1] = [ queryHash[$1], $2 ];
    }
    }

    You can find a full implementation here: http://appengine.bravo9.com/b9j/documentation/uri.html

  35. …oh yeah, and great parser, thanks!

  36. Awesome!!!! kudos Steven for such an elegant program! It served my purpose exactly the way i needed it!

    Thanks again….
    Raju

  37. Hi :)

    I’m new to jQuery and stumbled across your script whilst doing a uni assignment. I’m a bit lost as to how to recover array query strings. ie url?=campus[]=blahland.

    If I do parseUri(location).queryKey.campus%5B%5D it reports a JS error in Firebug.

    Is there a way to strip the [] from the query string so it’ll just be campus?

    Cheers,

    Brendan

  38. I’ve posted an interactive example of my
    JavaScript URI object
    :

    http://appengine.bravo9.com/b9j/example/uri/

  39. @Brendan, what’s an array query string? Assuming the URI you’re feeding this function is actually “url?campus[]=blahland”, you could access the value via parseUri(uri).queryKey["campus[]"]. Them’s JavaScript rules.

    @Robert, good point about e.g. “?c=1&c=2&c=3″. I’ll have to consider how to handle such cases in the next version of this function. Your approach (inserting an array into queryKey) seems pretty reasonable.

  40. [...] After someone suggested a way to match URLs and protocols with wildcards in LockCrypt, I started work implementing a URL which accepted wildcard (*) characters. The result is a class which takes a URL string as a constructor and breaks it apart into it’s component parts. The class is based on a JavaScript regex from Steve Levithan. [...]

  41. [...] parseUri 1.2: Split URLs in JavaScript (tags: javascript parse regex url uri) [...]

  42. Nice script! Could you please add support for domain/subdomain?

  43. Hello Steven,

    We would like to use your excellent code in our project over at
    http://kevin.vanzonneveld.net/techblog/article/phpjs_licensing/

    and in the near future at:
    http://phpjs.org

    We already noticed your code was MIT, but if you would like to be credited differently or have another comment, please drop a line okay?

  44. chrome?

  45. awesome! – nuff said!

  46. Cool script, but I am missing one important function. Function which creates URI back from uri object. My scenario is: parse URI, change URI (query string), write URI back to <a href=”…

  47. Great script! This is working really well for us with one exception. In Safari and Chrome the following URL will not parse:
    (Edit: Long URL removed.)

    I know it’s crazy… It appears to begin working when we limit the length to 450.

    Thanks again for the great script!

  48. @Matt Ruby, I haven’t done any related testing, but the problem may result from the portions of the regexes that deal with user info (user name, password). Those parts can result in a lot of backtracking with long URLs that don’t contain an @ sign, since JavaScript doesn’t have features such as possessive quantifiers, atomic groups, or duplicate subpattern numbers that would help me deal with the backtracking issues.

    If you don’t need support for the user and password properties returned by this script, one easy way to work around the issue is to change the following part of the regex (in both the strict and loose version):

    (?:(([^:@]*):?([^:@]*))?@)?

    To this:

    (?:([^:@]*:[^:@]*|[^:@]*)?@)?

    Then remove the “user” and “password” values from the parseUri.options.key array. If you try this out, please let me know if it solves the issue for you.

  49. @msznapka, on the demo page, if you look at the source, in the demo.js there’s a function called format that does something similar, and may be useful as a starting point for you.

  50. Works like a charm!
    I also changed i = 14; to i = 12;
    and uri[o.key[12]]… to uri[o.key[10]]…

    Thanks for your help!

    -Ruby

  51. @Matt Ruby, cool, thanks for reporting back. After giving this a few more minutes of thought, here’s a way you can get rid of the backtracking problem while keeping the user and password properties around. Replace the pattern I identified earlier (in both the strict and loose regexes) with this:

    (?:(([^:@]*)(?::([^:@]*))?)?@)?

    Everything else should be left the same compared to the original script. When I have some time to more fully review all of parseUri, I’ll include this change in the next version (after the current v1.2.1).

  52. Thanks again! I’ve made your suggested change and things are still working well.

    I look forward to next version.

    -Ruby

  53. I did a PHP port of this amazing function.
    I added 2 features.
    Hope it could be useful !!
    Have fun !

    LudoO

    <?php
    /*
    	PhpParseUri 1.0
    
    	PHP Port of parseUri 1.2.1
    		- added file:///
    		- added : windows drive detection
    
    	PHP Port: LudoO 2009 <pitaso.com>
    
    	Original JS : (c) 2007 Steven Levithan <stevenlevithan.com>	MIT License
    
    */
    function parseUri($str) {
    	global $parseUri_options;
    	$o   = $parseUri_options;
    	$r   = $o['parser'][$o['strictMode'] ? "strict" : "loose"];
    	preg_match($r, $str, $m);
    	$uri = array();
    	$i   = 15;
    
    	while ($i--) $uri[$o['key'][$i]] = $m[$i];
    
    	$uri[$o['q']['name']] = array();
    	preg_match_all($o['q']['parser'], $uri[$o['key'][13]], $n);
    	if ($n && sizeof($n)>0){
    		for ($i = 1; $i <= sizeof($n); $i++) {
    			$v =$n[$i];
    			if ($v) $uri[$o['q']['name']][$v[0]] = $v[1];
    		}
    	}
    	return $uri;
    };
    $parseUri_options = array(
    	strictMode => false,
    	key => array("source","protocol","authority","userInfo","user","password","host","port","relative","path","drive","directory","file","query","anchor"),
    	q =>   array(
    		name =>   "queryKey",
    		parser => '/(?:^|&)([^&=]*)=?([^&]*)/'
    	),
    	parser => array(
    		strict => '/^(?:([^:\/?#]+):)?(?:\/\/\/?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?(((?:\/(\w:))?((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/',
    		loose =>  '/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/\/?)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((?:\/(\w:))?(\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/'
    	)
    );
    ?>
    
  54. [...] Javascript does not have a built in method for parsing a url (to get the individual parts that make up a url). Here is a link to a great little function that does all you need, called parseURI. [...]

  55. this function does not work for urls with ipv6 addresses in them

    an example url:
    http://2001:4860:a003::68/search?q=parseUri+ipv6

  56. Thanks for this! One small issue: if a URL has multiple instances of the same parameter name (as can occur if multiple checkboxes are checked on a form) then queryKey will only contain the last value associated with this name. So if the query string is

    x=a&x=b

    then queryKey.x ends up as ‘b’, and the x=a pair is lost. It might be nicer if, say, an array of strings rather than a single string was assigned to the corresponding element of queryKey in such cases.

  57. Hi Steven,

    Once, i have implemented the URI parser in JavaScript. It looks like the method of String prototype, so you are able to parse input strings in JavaScript-style:

    var url = ‘http://blog.stevenlevithan.com/archives/parseuri’;
    var url_parsed = url.parseUrl();

    The source is located here:
    http://with-love-from-siberia.blogspot.com/2009/07/url-parsing-in-javascript.html

  58. [...] einem keine Lösungen anbietet, um relative Pfade in Absolute zu konvertieren. Es gibt sogar mehrere sehr gute Lösungen. Doch will ich wirklich 100 Zeilen Code schreiben nur um Links zu [...]

  59. I was using your parseUri class in JavaScript and noticed that the path “file.ext” is not considered a file by your script. It looks like it is expecting a preceding slash. I don’t know if this is a bug or just a feature that was left out. My work around was just some extra conditioning tests. Nice work on this class and thanks for posting it.

  60. @Mike, in non-strict mode, a URL starting with a filename (i.e., no preceding slash) is treated as a host, for reasons explained elsewhere. Switch to strict mode and you should be OK.

Post a Response

If you are about to post code, please escape your HTML entities (&amp;, &gt;, &lt;).