1.0.0-beta.2 • Published 6 years ago

uri-string-parser v1.0.0-beta.2

Weekly downloads
5
License
BSD-3-Clause
Repository
github
Last release
6 years ago

uri-string-parser

Uniform Resource Identifier String Parser

A non-destructive parser for URI and URL strings.

Installation

npm install --save uri-string-parser

Basic Usage

Basic usage without any options is the same as passing the string through a regular expression and then further parsing the authority to get the user information, hostname, and port.

Basic Usage 1

const uriStringParser = require('uri-string-parser')

var parsedURI = uriStringParser('http://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash')

// is the same as:

var parsedURI = {
  uri: {
    scheme: 'http',
    authority: 'user:pass@sub.host.com:8080',
    path: '/p/a/t/h',
    query: 'query=string',
    fragment: 'hash'
  },
  url: {
    protocol: 'http:',
    slashes: '//',
    username: 'user',
    password: 'pass',
    hostname: 'sub.host.com',
    port: '8080',
    pathname: '/p/a/t/h',
    search: '?query=string',
    hash: '#hash'
  }
}

Basic Usage 2

const uriStringParser = require('uri-string-parser');

const uris = [
  // A Full URL
  'http://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash',

  // A protocol relative URL (aka. network-path reference)
  '//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js',

  // A root relative URL (aka. absolute-path reference)
  '/assets/scripts/main.js',

  // Relative URLs (aka. relative-path reference)
  './assets/styles/main.css',
  '../fonts/open-sans.ttf',

  // * This parses correctly when the `assumeSchemeHasSlashes` option is NOT
  //   enabled.
  'images/logo.png',

  // URLs lacking a protocol
  // * Without advanced options, this won't be parsed properly.
  'www.google.com/search?q=cat+pictures',

  // URLs that don't use slashes
  // * Without advanced options, `no-reply@host.com` is considered a part of the
  //   path, not the authority. This may or may not be correct, depending on the
  //   protocol.
  'mailto:no-reply@host.com',

  // Invalid URL; Has protocol, has password, but no username
  // * Despite being invalid, it is parsed correctly.
  'http://:pass@sub.host.com:8080/p/a/t/h?query=string#hash',

  // Invalid URL; No protocol, has password, but no username
  // * Suffers the same problems as URLs lacking a protocol.
  ':pass@sub.host.com:8080/p/a/t/h?query=string#hash',

  // Invalid URL; A non-numeric port
  // * Without `assumeSchemeHasSlashes`, `www.google.com:` becomes the protocol
  //   and `foo` is considered a part of the path.
  'www.google.com:foo/p/a/t/h'
];

uris.forEach(function(uri) {
  var obj = uriStringParser.url(uri); // No Options!
  console.log(uri);
  console.log(JSON.stringify(obj, null, 2));
  console.log('');
});

/* CONSOLE OUTPUT =>
http://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash
{
  "protocol": "http:",
  "slashes": "//",
  "username": "user",
  "password": "pass",
  "hostname": "sub.host.com",
  "port": "8080",
  "pathname": "/p/a/t/h",
  "search": "?query=string",
  "hash": "#hash"
}

//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
{
  "slashes": "//",
  "hostname": "ajax.googleapis.com",
  "pathname": "/ajax/libs/jquery/3.3.1/jquery.min.js"
}

/assets/scripts/main.js
{
  "pathname": "/assets/scripts/main.js"
}

./assets/styles/main.css
{
  "pathname": "./assets/styles/main.css"
}

../fonts/open-sans.ttf
{
  "pathname": "../fonts/open-sans.ttf"
}

images/logo.png
{
  "pathname": "images/logo.png"
}

www.google.com/search?q=cat+pictures
{
  "pathname": "www.google.com/search",
  "search": "?q=cat+pictures"
}

mailto:no-reply@host.com
{
  "protocol": "mailto:",
  "pathname": "no-reply@host.com"
}

:pass@sub.host.com:8080/p/a/t/h?query=string#hash
{
  "pathname": ":pass@sub.host.com:8080/p/a/t/h",
  "search": "?query=string",
  "hash": "#hash"
}

www.google.com:foo/p/a/t/h
{
  "protocol": "www.google.com:",
  "pathname": "foo/p/a/t/h"
}
*/

Advanced Usage:

When to use advanced options:

If you expect to be handling:

  • URLs that have an authority, but no scheme.
  • Schemes that have an authority, but don't use double slashes
  • You have a need to try to parse otherwise invalid URLs

then use advanced options, otherwise, don't use advanced options! You will be making more work for yourself with relative paths!

Enabling slash assumptions allows resources lacking a scheme to be parsed correctly, but it creates problems for protocols that don't use two forward slashes (//). To get around this, all you have to do is identify expected schemes that you know will not be using slashes.

Each known scheme can be configured to allow assumptions or extract authority sections where it otherwise might not. By default, additional processing is turned off for known schemes. This allows assumptions to properly handle relative paths while a known scheme can be processed by the regular expression directly.

const uriStringParser = require('uri-string-parser');

const uris = [
  // A Full URL
  'http://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash',

  // A protocol relative URL (aka. network-path reference)
  '//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js',

  // A root relative URL (aka. absolute-path reference)
  '/assets/scripts/main.js',

  // Relative URLs (aka. relative-path reference)
  './assets/styles/main.css',
  '../fonts/open-sans.ttf',

  // Caveat: when assumptions are enabled, this relative URL isn't parsed
  // properly, despite being valid. To work around this limitation, ensure that
  // paths relative to the current directory start with `./`.
  'images/logo.png',

  // URLs lacking a protocol
  'www.google.com/search?q=cat+pictures',

  // URLs that don't use slashes
  'mailto:no-reply@host.com',

  // Invalid URL; Has password, but no username
  ':pass@sub.host.com:8080/p/a/t/h?query=string#hash',

  // Invalid URL; A non-numeric port is not a port, so it remains a part of the
  // hostname.
  'www.google.com:foo/p/a/t/h'
];

const parserOptions = {
  assumeSchemeHasSlashes: true,
  knownSchemes: [
    'mailto'
  ]
};

uris.forEach(function(uri) {
  var obj = uriStringParser.url(uri, options);
  console.log(uri);
  console.log(JSON.stringify(obj, null, 2));
  console.log('');
});

/* CONSOLE OUTPUT =>
http://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash
{
  "protocol": "http:",
  "slashes": "//",
  "username": "user",
  "password": "pass",
  "hostname": "sub.host.com",
  "port": "8080",
  "pathname": "/p/a/t/h",
  "search": "?query=string",
  "hash": "#hash"
}

//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
{
  "slashes": "//",
  "hostname": "ajax.googleapis.com",
  "pathname": "/ajax/libs/jquery/3.3.1/jquery.min.js"
}

/assets/scripts/main.js
{
  "pathname": "/assets/scripts/main.js"
}

./assets/styles/main.css
{
  "pathname": "./assets/styles/main.css"
}

../fonts/open-sans.ttf
{
  "pathname": "../fonts/open-sans.ttf"
}

images/logo.png
{
  "hostname": "images",
  "pathname": "/logo.png"
}

www.google.com/search?q=cat+pictures
{
  "hostname": "www.google.com",
  "pathname": "/search",
  "search": "?q=cat+pictures"
}

mailto:no-reply@host.com
{
  "protocol": "mailto:",
  "pathname": "no-reply@host.com"
}

:pass@sub.host.com:8080/p/a/t/h?query=string#hash
{
  "password": "pass",
  "hostname": "sub.host.com",
  "port": "8080",
  "pathname": "/p/a/t/h",
  "search": "?query=string",
  "hash": "#hash"
}

www.google.com:foo/p/a/t/h
{
  "hostname": "www.google.com:foo",
  "pathname": "/p/a/t/h"
}
*/

Notes

  • uri.path is identical to url.pathname
  • uri.scheme is the same thing as a url.protocol, but without the trailing :
    • scheme = 'http'
    • protocol = 'http:'
  • uri.query is the same thing as a url.search, but without the leading ?
    • query = 'lookup=dogs'
    • search = '?lookup=dogs'
  • uri.fragment is the same thing as a url.hash, but without the leading #
    • fragment = 'myhash'
    • hash = '#myhash'

API

uriStringParser(uri, options)

Returns a plain object containing a parsed URI. Parts of the URI not found during parsing will not be defined.

uri

  • Type: String

The URI to be parsed.

options

  • Optional
  • Type: Object
options.assumeSchemeHasSlashes
  • Optional
  • Type: Boolean
  • Default: false

When true, the code makes some assumptions about the URI passed to it. If it doesn't find a protocol that uses two slashes, it will prepend // to the string before processing it. After processing, it will remove slashes from the object before returning it.

This will improve accuracy for input URIs that don't have protocols, but it will cause URIs that have protocols that don't use double slashes to be handled incorrectly, such as mailto:. Absolute URIs that already have two slashes will be unaffected.

While the processing may be more accurate, no validation occurs. It isn't able to fix invalid URIs.

For example:

const uriStringParser = require('uri-string-parser')

var uriWithUserinfo = 'user:pass@sub.host.com:8080/p/a/t/h?query=string#hash'
var actualResult = uriStringParser.uri(uriWithUserinfo)
var expectedResult = uriStringParser.uri(
  uriWithUserinfo, 
  { assumeSchemeHasSlashes: true }
)

// actualResult
// Authority is undefined, scheme and path are incorrect.
{
  scheme: 'user',
  path: 'pass@sub.host.com:8080/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}

// expectedResult
// Scheme is undefined. Authority and path have expected values.
{
  authority: 'user:pass@sub.host.com:8080',
  path: '/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}
const uriStringParser = require('uri-string-parser')

var uriWithPort = 'sub.host.com:8080/p/a/t/h?query=string#hash'
var actualResult2 = uriStringParser.uri(uriWithPort)
var expectedResult2 = uriStringParser.uri(uriWithPort, { assumeSchemeHasSlashes: true })

// actualResult2
// Authority is undefined, scheme and path are incorrect.
{
  scheme: 'sub.host.com',
  path: '8080/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}

// expectedResult2
// Scheme is undefined. Authority and path have expected values.
{
  authority: 'user:pass@sub.host.com:8080',
  path: '/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}
const uriStringParser = require('uri-string-parser')

var uriWithHostOnly = 'sub.host.com/p/a/t/h?query=string#hash'
var actualResult3 = uriStringParser.uri(uriWithHostOnly)
var expectedResult3 = uriStringParser.uri(uriWithHostOnly, { assumeSchemeHasSlashes: true })

// actualResult3
// Authority is undefined. Path is incorrect. Scheme is (correctly) undefined.
{
  path: 'sub.host.com/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}

// expectedResult3
// Scheme is undefined. Authority and path have expected values.
{
  authority: 'sub.host.com',
  path: '/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}
KNOWN LIMITATIONS OF ASSUMPTIONS

When assumptions are enabled, relative URLs that don't start with a current directory indicator (./) or parent directory indicator (../) aren't parsed properly, despite being valid.

To work around this limitation, ensure that paths relative to the current directory start with ./. Use ./assets/images/logo.png instead of assets/images/logo.png.

options.knownSchemes
  • Type: Array of Strings
  • Default: []

The regular expression provided by RFC3986 captures protocols that don't use double slashes inaccurately when assumeSchemeHasSlashes is enabled.

It is normal for protocols not using double slashes to not have an authority captured separately from the path. Depending on your protocol, this may or may not be intentional.

The assumeSchemeHasSlashes option can't handle this alone as it works under the assumption that the input URI doesn't have any protocol information. To get around this, we can provide a list of known schemes to modify the URI during internal processing. As with assumeSchemeHasSlashes, these internal modifications are non-destructive and are removed before the final object is returned.

const uriStringParser = require('uri-string-parser')

var uriWithSlashlessProtocol = 'someprotocol:user:pass@sub.host.com:8080/p/a/t/h?query=string#hash'
var actualResult = uriStringParser.uri(
  uriWithSlashlessProtocol,
  {
    assumeSchemeHasSlashes: true,
    knownSchemes: ['someprotocol'],
  }
)
var incorrectAssumptionResult = uriStringParser.uri(
  uriWithSlashlessProtocol,
  {
    assumeSchemeHasSlashes: true
  }
)
var expectedResult = uriStringParser.uri(
  uriWithSlashlessProtocol,
  {
    assumeSchemeHasSlashes: true,
    knownSchemes: ['someprotocol'],
    knownSchemesOptions: {
      'someprotocol': { parseSlashlessAuthority: true }
    }
  }
)

// actualResult
// Authority is undefined and path includes the "authority", which may or may
// not be correct depending on the protocol.
{
  scheme: 'someprotocol',
  path: 'use:pass@sub.host.com:8080/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}

// incorrectAssumptionResult
// Scheme is undefined. The protocol is included in the authority.
// Our assumptions just made things worse!
{
  authority: 'someprotocol:user:pass@sub.host.com:8080',
  path: '/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}

// expectedResult
// Scheme is correct. Authority and path have expected values.
// Whether or not authority should be undefined or prepended to path may depend
// on the protocol. This module separates them reliably and they can be 
// concatenated reliably based on the scheme later.
{
  scheme: 'someprotocol',
  authority: 'user:pass@sub.host.com:8080',
  path: '/p/a/t/h',
  query: 'query=string',
  fragment: 'hash'
}
options.knownSchemesOptions
  • Optional
  • Type: Object
  • Default: {}
  • Note: both "schemes" and "options" are plural.

This object uses a scheme supplied to knownSchemes as its keys and its values are plain objects that hold options for greater control over how known schemes are handled.

options.knownSchemesOptions.assumptionsAllowed
  • Optional
  • Type: Boolean
  • Default: false

If a known protocol is identified, setting this to true allows additional handling when options.assumeSchemeHasSlashes = true.

In many cases, this is not what is desired, so it is false by default for known schemes only. The options.knownSchemesOptions.parseSlashlessAuthority option is more likely to be what you are looking for.

options.knownSchemesOptions.parseSlashlessAuthority
  • Optional
  • Type: Boolean
  • Default: false

Normally, when two slashes don't follow the protocol, what could be considered the authority is included as a part of the path.

Setting this option to true will added double slashes after the protocol internally to allow for the authority to be separated from the path. As with options.assumeSchemeHasSlashes, these slashes are removed from the url object before it is finally returned.

uriStringParser.uri(uri, options)

Accepts the same arguments as uriStringParser, but returns just the URI portion of the object.

uriStringParser.url(uri, options)

Accepts the same arguments as uriStringParser, but returns just the URL portion of the object.

RESOURCES