New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

http-link-header

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

http-link-header - npm Package Compare versions

Comparing version 0.8.0 to 1.0.0

AUTHORS

468

lib/link.js

@@ -1,220 +0,60 @@

var querystring = require( 'querystring' )
var trim = require( './trim' )
'use strict'
/**
* Link
* @constructor
* @return {Link}
*/
function Link( value ) {
var COMPATIBLE_ENCODING_PATTERN = /^utf-?8|ascii|utf-?16-?le|ucs-?2|base-?64|latin-?1$/i
var WS_TRIM_PATTERN = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g
var WS_CHAR_PATTERN = /\s|\uFEFF|\xA0/
var WS_FOLD_PATTERN = /\r?\n[\x20\x09]+/g
var DELIMITER_PATTERN = /[;,"]/
var WS_DELIMITER_PATTERN = /[;,"]|\s/
if( !(this instanceof Link) ) {
return new Link( value )
}
/** @type {Array} URI references */
this.refs = []
var STATE = {
IDLE: 1 << 0,
URI: 1 << 1,
ATTR: 1 << 2,
}
/**
* General matching pattern
* @type {RegExp}
*/
Link.pattern = /(?:\<([^\>]+)\>)((\s*;\s*([a-z\*]+)=(("[^"]+")|('[^']+')|([^\,\;]+)))*)(\s*,\s*|$)/gi
/**
* Attribute matching pattern
* @type {RegExp}
*/
Link.attrPattern = /([a-z\*]+)=(?:(?:"([^"]+)")|(?:'([^']+)')|([^\,\;]+))/gi
/**
* Determines whether an encoding can be
* natively handled with a `Buffer`
* @param {String} value
* @return {Boolean}
*/
Link.isCompatibleEncoding = function( value ) {
return /^utf-?8|ascii|utf-?16-?le|ucs-?2|base-?64|latin-?1$/i.test( value )
function trim( value ) {
return value.replace( WS_TRIM_PATTERN, '' )
}
/**
* Format a given extended attribute and it's value
* @param {String} attr
* @param {Object} data
* @return {String}
*/
Link.formatExtendedAttribute = function( attr, data ) {
var encoding = ( data.encoding || 'utf-8' ).toUpperCase()
var language = data.language || 'en'
var encodedValue = ''
if( Buffer.isBuffer( data.value ) && Link.isCompatibleEncoding( encoding ) ) {
encodedValue = data.value.toString( encoding )
} else if( Buffer.isBuffer( data.value ) ) {
encodedValue = data.value.toString( 'hex' )
.replace( /[0-9a-f]{2}/gi, '%$1' )
} else {
encodedValue = querystring.escape( data.value )
}
return attr + '=' + encoding + '\'' +
language + '\'' + encodedValue
function hasWhitespace( value ) {
return WS_CHAR_PATTERN.test( value )
}
/**
* Format a given attribute and it's value
* @param {String} attr
* @param {String|Object} value
* @return {String}
*/
Link.formatAttribute = function( attr, value ) {
// NOTE: Properly test this condition
if( /\*$/.test( attr ) || typeof value !== 'string' )
return Link.formatExtendedAttribute( attr, value )
// Strictly, not all values matching this
// selector would need quotes, but it's better to be safe
var needsQuotes = /[^a-z]/i.test( value )
if( needsQuotes ) {
// We don't need to escape <SP> <,> <;>
value = querystring.escape( value )
.replace( /%20/g, ' ' )
.replace( /%2C/g, ',' )
.replace( /%3B/g, ';' )
value = '"' + value + '"'
function skipWhitespace( value, offset ) {
while( hasWhitespace( value[offset] ) ) {
offset++
}
return attr + '=' + value
return offset
}
/**
* Parses an extended value and attempts to decode it
* @internal
* @param {String} value
* @return {Object}
*/
Link.parseExtendedValue = function( value ) {
var parts = /([^']+)?(?:'([^']+)')?(.+)/.exec( value )
return {
language: parts[2].toLowerCase(),
encoding: Link.isCompatibleEncoding( parts[1] ) ?
null : parts[1].toLowerCase(),
value: Link.isCompatibleEncoding( parts[1] ) ?
querystring.unescape( parts[3] ) : parts[3]
}
function needsQuotes( value ) {
return WS_DELIMITER_PATTERN.test( value )
}
/**
* Set an attribute on a link ref
* @param {Object} link
* @param {String} attr
* @param {String} value
*/
Link.setAttr = function( link, attr, value ) {
class Link {
// Occurrences after the first "rel" MUST be ignored by parsers
// @see RFC 5988, Section 5.3: Relation Type
if( attr === 'rel' && link[ attr ] != null )
return link
/**
* Link
* @constructor
* @param {String} [value]
* @returns {Link}
*/
constructor( value ) {
if( Array.isArray( link[ attr ] ) ) {
link[ attr ].push( value )
} else if( link[ attr ] != null ) {
link[ attr ] = [ link[ attr ], value ]
} else {
link[ attr ] = value
}
/** @type {Array} URI references */
this.refs = []
return link
if( value ) {
this.parse( value )
}
}
/**
* Parses uri attributes
*/
Link.parseParams = function( link, uri ) {
var kvs = {}
var params = /(.+)\?(.+)/gi.exec( uri )
if( !params ) {
return link
}
params = params[2].split('&')
for( var i = 0; i < params.length; i++ ) {
var param = params[i].split('=');
kvs[ param[0] ] = param[1]
}
Link.setAttr( link, 'params', kvs )
return link
}
/**
* Parses out URI attributes
* @internal
* @param {Object} link
* @param {String} parts
* @return {Object} link
*/
Link.parseAttrs = function( link, parts ) {
var match = null
var attr = ''
var value = ''
var attrs = ''
var uriAttrs = /<(.*)>;\s*(.*)/gi.exec( parts )
if( uriAttrs ) {
attrs = uriAttrs[2]
link = Link.parseParams( link, uriAttrs[1] )
}
while( match = Link.attrPattern.exec( attrs ) ) {
attr = match[1].toLowerCase()
value = match[4] || match[3] || match[2]
if( /\*$/.test( attr ) ) {
Link.setAttr( link, attr, Link.parseExtendedValue( value ) )
} else if( /%/.test( value ) ) {
Link.setAttr( link, attr, querystring.unescape( value ) )
} else {
Link.setAttr( link, attr, value )
}
}
return link
}
Link.parse = function( value ) {
return new Link().parse( value )
}
/**
* Link prototype
* @type {Object}
*/
Link.prototype = {
constructor: Link,
/**
* Get refs with given relation type
* @param {String} value
* @return {Array<Object>}
* @returns {Array<Object>}
*/
rel: function( value ) {
rel( value ) {

@@ -231,3 +71,3 @@ var links = []

},
}

@@ -238,5 +78,5 @@ /**

* @param {String} value
* @return {Array<Object>}
* @returns {Array<Object>}
*/
get: function( attr, value ) {
get( attr, value ) {

@@ -255,31 +95,120 @@ attr = attr.toLowerCase()

},
}
set: function( link ) {
set( link ) {
this.refs.push( link )
return this
},
}
has: function( attr, value ) {
has( attr, value ) {
return this.get( attr, value ) != null
},
}
parse: function( value ) {
parse( value, offset ) {
// Unfold folded lines
value = trim( value )
.replace( /\r?\n[\x20\x09]+/g, '' )
offset = offset || 0
value = offset ? value.slice( offset ) : value
var match = null
// Trim & unfold folded lines
value = trim( value ).replace( WS_FOLD_PATTERN, '' )
while( match = Link.pattern.exec( value ) ) {
var link = Link.parseAttrs({ uri: match[1] }, match[0] )
this.refs.push( link )
var state = STATE.IDLE
var length = value.length
var offset = 0
var ref = null
while( offset < length ) {
if( state === STATE.IDLE ) {
if( hasWhitespace( value[offset] ) ) {
offset++
continue
} else if( value[offset] === '<' ) {
var end = value.indexOf( '>', offset )
if( end === -1 ) throw new Error( 'Expected end of URI delimiter at offset ' + offset )
ref = { uri: value.slice( offset + 1, end ) }
this.refs.push( ref )
offset = end
state = STATE.URI
} else {
throw new Error( 'Unexpected character "' + value[offset] + '" at offset ' + offset )
}
offset++
} else if( state === STATE.URI ) {
if( hasWhitespace( value[offset] ) ) {
offset++
continue
} else if( value[offset] === ';' ) {
state = STATE.ATTR
offset++
} else if( value[offset] === ',' ) {
state = STATE.IDLE
offset++
} else {
throw new Error( 'Unexpected character "' + value[offset] + '" at offset ' + offset )
}
} else if( state === STATE.ATTR ) {
if( value[offset] ===';' || hasWhitespace( value[offset] ) ) {
offset++
continue
}
var end = value.indexOf( '=', offset )
if( end === -1 ) throw new Error( 'Expected attribute delimiter at offset ' + offset )
var attr = trim( value.slice( offset, end ) ).toLowerCase()
var attrValue = ''
offset = end + 1
offset = skipWhitespace( value, offset )
if( value[offset] === '"' ) {
offset++
while( offset < length ) {
if( value[offset] === '"' ) {
offset++; break
}
if( value[offset] === '\\' ) {
offset++
}
attrValue += value[offset]
offset++
}
} else {
var end = offset + 1
while( !DELIMITER_PATTERN.test( value[end] ) && end < length ) {
end++
}
attrValue = value.slice( offset, end )
offset = end
}
if( ref[ attr ] && Link.isSingleOccurenceAttr( attr ) ) {
// Ignore multiples of attributes which may only appear once
} else if( attr[ attr.length - 1 ] === '*' ) {
ref[ attr ] = Link.parseExtendedValue( attrValue )
} else {
attrValue = attr === 'rel' || attr === 'type' ?
attrValue.toLowerCase() : attrValue
if( ref[ attr ] != null ) {
if( Array.isArray( ref[ attr ] ) ) {
ref[ attr ].push( attrValue )
} else {
ref[ attr ] = [ ref[ attr ], attrValue ]
}
} else {
ref[ attr ] = attrValue
}
}
switch( value[offset] ) {
case ',': state = STATE.IDLE; break
case ';': state = STATE.ATTR; break
}
offset++
} else {
throw new Error( 'Unknown parser state "' + state + '"' )
}
}
ref = null
return this
},
}
toString: function() {
toString() {

@@ -301,7 +230,114 @@ var refs = []

},
}
}
// Exports
/**
* Determines whether an encoding can be
* natively handled with a `Buffer`
* @param {String} value
* @returns {Boolean}
*/
Link.isCompatibleEncoding = function( value ) {
return COMPATIBLE_ENCODING_PATTERN.test( value )
}
Link.parse = function( value, offset ) {
return new Link().parse( value, offset )
}
Link.isSingleOccurenceAttr = function( attr ) {
return attr === 'rel' || attr === 'type' || attr === 'media' ||
attr === 'title' || attr === 'title*'
}
Link.isTokenAttr = function( attr ) {
return attr === 'rel' || attr === 'type' || attr === 'anchor'
}
Link.escapeQuotes = function( value ) {
return value.replace( /"/g, '\\"' )
}
/**
* Parses an extended value and attempts to decode it
* @internal
* @param {String} value
* @return {Object}
*/
Link.parseExtendedValue = function( value ) {
var parts = /([^']+)?(?:'([^']+)')?(.+)/.exec( value )
return {
language: parts[2].toLowerCase(),
encoding: Link.isCompatibleEncoding( parts[1] ) ?
null : parts[1].toLowerCase(),
value: Link.isCompatibleEncoding( parts[1] ) ?
decodeURIComponent( parts[3] ) : parts[3]
}
}
/**
* Format a given extended attribute and it's value
* @param {String} attr
* @param {Object} data
* @return {String}
*/
Link.formatExtendedAttribute = function( attr, data ) {
var encoding = ( data.encoding || 'utf-8' ).toUpperCase()
var language = data.language || 'en'
var encodedValue = ''
if( Buffer.isBuffer( data.value ) && Link.isCompatibleEncoding( encoding ) ) {
encodedValue = data.value.toString( encoding )
} else if( Buffer.isBuffer( data.value ) ) {
encodedValue = data.value.toString( 'hex' )
.replace( /[0-9a-f]{2}/gi, '%$1' )
} else {
encodedValue = encodeURIComponent( data.value )
}
return attr + '=' + encoding + '\'' +
language + '\'' + encodedValue
}
/**
* Format a given attribute and it's value
* @param {String} attr
* @param {String|Object} value
* @return {String}
*/
Link.formatAttribute = function( attr, value ) {
if( Array.isArray( value ) ) {
return value.map(( item ) => {
return Link.formatAttribute( attr, item )
}).join( '; ' )
}
if( attr[ attr.length - 1 ] === '*' || typeof value !== 'string' ) {
return Link.formatExtendedAttribute( attr, value )
}
if( Link.isTokenAttr( attr ) ) {
value = needsQuotes( value ) ?
'"' + Link.escapeQuotes( value ) + '"' :
Link.escapeQuotes( value )
} else if( needsQuotes( value ) ) {
value = encodeURIComponent( value )
// We don't need to escape <SP> <,> <;> within quotes
value = value
.replace( /%20/g, ' ' )
.replace( /%2C/g, ',' )
.replace( /%3B/g, ';' )
value = '"' + value + '"'
}
return attr + '=' + value
}
module.exports = Link
{
"name": "http-link-header",
"version": "0.8.0",
"description": "Parse & format HTTP link headers according to RFC 5988",
"version": "1.0.0",
"description": "Parse & format HTTP link headers according to RFC 8288",
"author": "Jonas Hermsmeier <jhermsmeier@gmail.com> (https://jhermsmeier.de)",

@@ -9,4 +9,6 @@ "license": "MIT",

"rfc5988",
"rfc8288",
"rfc",
"5988",
"8288",
"http",

@@ -17,6 +19,10 @@ "link",

"main": "lib/link.js",
"scripts": {
"benchmark": "node benchmark",
"test": "mocha --ui tdd"
},
"dependencies": {},
"devDependencies": {
"matcha": "~0.7.0",
"mocha": "~3.2.0"
"mocha": "^5.2.0",
"nanobench": "^2.1.1"
},

@@ -31,9 +37,5 @@ "homepage": "https://github.com/jhermsmeier/node-http-link-header",

},
"directories": {
"test": "test"
},
"scripts": {
"benchmark": "matcha --reporter plain",
"test": "mocha --ui tdd"
"engines": {
"node": ">=4.0.0"
}
}

@@ -7,5 +7,5 @@ # HTTP Link Header

Parse & format HTTP link headers according to [RFC 5988]
Parse & format HTTP link headers according to [RFC 8288]
[RFC 5988]: https://tools.ietf.org/html/rfc5988
[RFC 8288]: https://tools.ietf.org/html/rfc8288

@@ -24,3 +24,3 @@ ## Install via [npm](https://npmjs.com)

**Parse a HTTP link header**
### Parsing a HTTP link header

@@ -41,3 +41,3 @@ ```js

**Check whether it has a reference with a given attribute & value**
### Checking whether it has a reference with a given attribute & value

@@ -49,3 +49,3 @@ ```js

**Retrieve a reference with a given attribute & value**
### Retrieving a reference with a given attribute & value

@@ -66,3 +66,3 @@ ```js

**Set references**
### Setting references

@@ -80,8 +80,8 @@ ```js

**Parse multiple headers**
### Parsing multiple headers
```js
var links = new LinkHeader()
var link = new LinkHeader()
links.parse( '<example.com>; rel="example"; title="Example Website"' )
link.parse( '<example.com>; rel="example"; title="Example Website"' )
> Link {

@@ -93,3 +93,3 @@ refs: [

links.parse( '<example-01.com>; rel="alternate"; title="Alternate Example Domain"' )
link.parse( '<example-01.com>; rel="alternate"; title="Alternate Example Domain"' )
> Link {

@@ -102,3 +102,3 @@ refs: [

links.parse( '<example-02.com>; rel="alternate"; title="Second Alternate Example Domain"' )
link.parse( '<example-02.com>; rel="alternate"; title="Second Alternate Example Domain"' )
> Link {

@@ -113,7 +113,21 @@ refs: [

**Stringify to HTTP header format**
### Handling extended attributes
```js
link.parse( '</extended-attr-example>; rel=start; title*=UTF-8\'en\'%E2%91%A0%E2%93%AB%E2%85%93%E3%8F%A8%E2%99%B3%F0%9D%84%9E%CE%BB' )
```
```js
> Link {
refs: [
{ uri: '/extended-attr-example', rel: 'start', 'title*': { language: 'en', encoding: null, value: '①⓫⅓㏨♳𝄞λ' } }
]
}
```
### Stringifying to HTTP header format
```js
link.toString()
> '<example.com>; rel="example"; title="Example Website", <example-01.com>; rel="alternate"; title="Alternate Example Domain"'
> '<example.com>; rel=example; title="Example Website", <example-01.com>; rel=alternate; title="Alternate Example Domain"'
```

@@ -128,5 +142,7 @@

```
http-link-header
parse .......................................... 204,355 op/s
toString ....................................... 485,465 op/s
# http-link-header .parse() ⨉ 1000000
ok ~1.29 s (1 s + 289696759 ns)
# http-link-header #toString() ⨉ 1000000
ok ~554 ms (0 s + 553782657 ns)
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc