negotiator
Advanced tools
Comparing version 0.5.3 to 0.6.0
@@ -0,1 +1,15 @@ | ||
0.6.0 / 2015-09-29 | ||
================== | ||
* Fix including type extensions in parameters in `Accept` parsing | ||
* Fix parsing `Accept` parameters with quoted equals | ||
* Fix parsing `Accept` parameters with quoted semicolons | ||
* Lazy-load modules from main entry point | ||
* perf: delay type concatenation until needed | ||
* perf: enable strict mode | ||
* perf: hoist regular expressions | ||
* perf: remove closures getting spec properties | ||
* perf: remove a closure from media type parsing | ||
* perf: remove property delete from media type parsing | ||
0.5.3 / 2015-05-10 | ||
@@ -2,0 +16,0 @@ ================== |
72
index.js
@@ -0,10 +1,32 @@ | ||
/*! | ||
* negotiator | ||
* Copyright(c) 2012 Federico Romero | ||
* Copyright(c) 2012-2014 Isaac Z. Schlueter | ||
* Copyright(c) 2015 Douglas Christopher Wilson | ||
* MIT Licensed | ||
*/ | ||
var preferredCharsets = require('./lib/charset'); | ||
var preferredEncodings = require('./lib/encoding'); | ||
var preferredLanguages = require('./lib/language'); | ||
var preferredMediaTypes = require('./lib/mediaType'); | ||
'use strict'; | ||
/** | ||
* Cached loaded submodules. | ||
* @private | ||
*/ | ||
var modules = Object.create(null); | ||
/** | ||
* Module exports. | ||
* @public | ||
*/ | ||
module.exports = Negotiator; | ||
Negotiator.Negotiator = Negotiator; | ||
module.exports.Negotiator = Negotiator; | ||
/** | ||
* Create a Negotiator instance from a request. | ||
* @param {object} request | ||
* @public | ||
*/ | ||
function Negotiator(request) { | ||
@@ -24,2 +46,3 @@ if (!(this instanceof Negotiator)) { | ||
Negotiator.prototype.charsets = function charsets(available) { | ||
var preferredCharsets = loadModule('charset').preferredCharsets; | ||
return preferredCharsets(this.request.headers['accept-charset'], available); | ||
@@ -34,2 +57,3 @@ }; | ||
Negotiator.prototype.encodings = function encodings(available) { | ||
var preferredEncodings = loadModule('encoding').preferredEncodings; | ||
return preferredEncodings(this.request.headers['accept-encoding'], available); | ||
@@ -44,2 +68,3 @@ }; | ||
Negotiator.prototype.languages = function languages(available) { | ||
var preferredLanguages = loadModule('language').preferredLanguages; | ||
return preferredLanguages(this.request.headers['accept-language'], available); | ||
@@ -54,2 +79,3 @@ }; | ||
Negotiator.prototype.mediaTypes = function mediaTypes(available) { | ||
var preferredMediaTypes = loadModule('mediaType').preferredMediaTypes; | ||
return preferredMediaTypes(this.request.headers.accept, available); | ||
@@ -67,1 +93,37 @@ }; | ||
Negotiator.prototype.preferredMediaTypes = Negotiator.prototype.mediaTypes; | ||
/** | ||
* Load the given module. | ||
* @private | ||
*/ | ||
function loadModule(moduleName) { | ||
var module = modules[moduleName]; | ||
if (module !== undefined) { | ||
return module; | ||
} | ||
// This uses a switch for static require analysis | ||
switch (moduleName) { | ||
case 'charset': | ||
module = require('./lib/charset'); | ||
break; | ||
case 'encoding': | ||
module = require('./lib/encoding'); | ||
break; | ||
case 'language': | ||
module = require('./lib/language'); | ||
break; | ||
case 'mediaType': | ||
module = require('./lib/mediaType'); | ||
break; | ||
default: | ||
throw new Error('Cannot find module \'' + moduleName + '\''); | ||
} | ||
// Store to prevent invoking require() | ||
modules[moduleName] = module; | ||
return module; | ||
} |
@@ -0,4 +1,31 @@ | ||
/** | ||
* negotiator | ||
* Copyright(c) 2012 Isaac Z. Schlueter | ||
* Copyright(c) 2014 Federico Romero | ||
* Copyright(c) 2014-2015 Douglas Christopher Wilson | ||
* MIT Licensed | ||
*/ | ||
'use strict'; | ||
/** | ||
* Module exports. | ||
* @public | ||
*/ | ||
module.exports = preferredCharsets; | ||
preferredCharsets.preferredCharsets = preferredCharsets; | ||
module.exports.preferredCharsets = preferredCharsets; | ||
/** | ||
* Module variables. | ||
* @private | ||
*/ | ||
var simpleCharsetRegExp = /^\s*(\S+?)\s*(?:;(.*))?$/; | ||
/** | ||
* Parse the Accept-Charset header. | ||
* @private | ||
*/ | ||
function parseAcceptCharset(accept) { | ||
@@ -21,4 +48,9 @@ var accepts = accept.split(','); | ||
function parseCharset(s, i) { | ||
var match = s.match(/^\s*(\S+?)\s*(?:;(.*))?$/); | ||
/** | ||
* Parse a charset from the Accept-Charset header. | ||
* @private | ||
*/ | ||
function parseCharset(str, i) { | ||
var match = simpleCharsetRegExp.exec(str); | ||
if (!match) return null; | ||
@@ -46,2 +78,7 @@ | ||
/** | ||
* Get the priority of a charset. | ||
* @private | ||
*/ | ||
function getCharsetPriority(charset, accepted, index) { | ||
@@ -61,2 +98,7 @@ var priority = {o: -1, q: 0, s: 0}; | ||
/** | ||
* Get the specificity of the charset. | ||
* @private | ||
*/ | ||
function specify(charset, spec, index) { | ||
@@ -78,2 +120,7 @@ var s = 0; | ||
/** | ||
* Get the preferred charsets from an Accept-Charset header. | ||
* @public | ||
*/ | ||
function preferredCharsets(accept, provided) { | ||
@@ -85,5 +132,6 @@ // RFC 2616 sec 14.2: no header = * | ||
// sorted list of all charsets | ||
return accepts.filter(isQuality).sort(compareSpecs).map(function getCharset(spec) { | ||
return spec.charset; | ||
}); | ||
return accepts | ||
.filter(isQuality) | ||
.sort(compareSpecs) | ||
.map(getFullCharset); | ||
} | ||
@@ -101,2 +149,7 @@ | ||
/** | ||
* Compare two specs. | ||
* @private | ||
*/ | ||
function compareSpecs(a, b) { | ||
@@ -106,4 +159,18 @@ return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; | ||
/** | ||
* Get full charset string. | ||
* @private | ||
*/ | ||
function getFullCharset(spec) { | ||
return spec.charset; | ||
} | ||
/** | ||
* Check if a spec has any quality. | ||
* @private | ||
*/ | ||
function isQuality(spec) { | ||
return spec.q > 0; | ||
} |
@@ -0,4 +1,31 @@ | ||
/** | ||
* negotiator | ||
* Copyright(c) 2012 Isaac Z. Schlueter | ||
* Copyright(c) 2014 Federico Romero | ||
* Copyright(c) 2014-2015 Douglas Christopher Wilson | ||
* MIT Licensed | ||
*/ | ||
'use strict'; | ||
/** | ||
* Module exports. | ||
* @public | ||
*/ | ||
module.exports = preferredEncodings; | ||
preferredEncodings.preferredEncodings = preferredEncodings; | ||
module.exports.preferredEncodings = preferredEncodings; | ||
/** | ||
* Module variables. | ||
* @private | ||
*/ | ||
var simpleEncodingRegExp = /^\s*(\S+?)\s*(?:;(.*))?$/; | ||
/** | ||
* Parse the Accept-Encoding header. | ||
* @private | ||
*/ | ||
function parseAcceptEncoding(accept) { | ||
@@ -37,5 +64,9 @@ var accepts = accept.split(','); | ||
function parseEncoding(s, i) { | ||
var match = s.match(/^\s*(\S+?)\s*(?:;(.*))?$/); | ||
/** | ||
* Parse an encoding from the Accept-Encoding header. | ||
* @private | ||
*/ | ||
function parseEncoding(str, i) { | ||
var match = simpleEncodingRegExp.exec(str); | ||
if (!match) return null; | ||
@@ -63,2 +94,7 @@ | ||
/** | ||
* Get the priority of an encoding. | ||
* @private | ||
*/ | ||
function getEncodingPriority(encoding, accepted, index) { | ||
@@ -78,2 +114,7 @@ var priority = {o: -1, q: 0, s: 0}; | ||
/** | ||
* Get the specificity of the encoding. | ||
* @private | ||
*/ | ||
function specify(encoding, spec, index) { | ||
@@ -95,2 +136,7 @@ var s = 0; | ||
/** | ||
* Get the preferred encodings from an Accept-Encoding header. | ||
* @public | ||
*/ | ||
function preferredEncodings(accept, provided) { | ||
@@ -101,5 +147,6 @@ var accepts = parseAcceptEncoding(accept || ''); | ||
// sorted list of all encodings | ||
return accepts.filter(isQuality).sort(compareSpecs).map(function getEncoding(spec) { | ||
return spec.encoding; | ||
}); | ||
return accepts | ||
.filter(isQuality) | ||
.sort(compareSpecs) | ||
.map(getFullEncoding); | ||
} | ||
@@ -117,2 +164,7 @@ | ||
/** | ||
* Compare two specs. | ||
* @private | ||
*/ | ||
function compareSpecs(a, b) { | ||
@@ -122,4 +174,18 @@ return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; | ||
/** | ||
* Get full encoding string. | ||
* @private | ||
*/ | ||
function getFullEncoding(spec) { | ||
return spec.encoding; | ||
} | ||
/** | ||
* Check if a spec has any quality. | ||
* @private | ||
*/ | ||
function isQuality(spec) { | ||
return spec.q > 0; | ||
} |
@@ -0,4 +1,31 @@ | ||
/** | ||
* negotiator | ||
* Copyright(c) 2012 Isaac Z. Schlueter | ||
* Copyright(c) 2014 Federico Romero | ||
* Copyright(c) 2014-2015 Douglas Christopher Wilson | ||
* MIT Licensed | ||
*/ | ||
'use strict'; | ||
/** | ||
* Module exports. | ||
* @public | ||
*/ | ||
module.exports = preferredLanguages; | ||
preferredLanguages.preferredLanguages = preferredLanguages; | ||
module.exports.preferredLanguages = preferredLanguages; | ||
/** | ||
* Module variables. | ||
* @private | ||
*/ | ||
var simpleLanguageRegExp = /^\s*(\S+?)(?:-(\S+?))?\s*(?:;(.*))?$/; | ||
/** | ||
* Parse the Accept-Language header. | ||
* @private | ||
*/ | ||
function parseAcceptLanguage(accept) { | ||
@@ -21,4 +48,9 @@ var accepts = accept.split(','); | ||
function parseLanguage(s, i) { | ||
var match = s.match(/^\s*(\S+?)(?:-(\S+?))?\s*(?:;(.*))?$/); | ||
/** | ||
* Parse a language from the Accept-Language header. | ||
* @private | ||
*/ | ||
function parseLanguage(str, i) { | ||
var match = simpleLanguageRegExp.exec(str); | ||
if (!match) return null; | ||
@@ -50,2 +82,7 @@ | ||
/** | ||
* Get the priority of a language. | ||
* @private | ||
*/ | ||
function getLanguagePriority(language, accepted, index) { | ||
@@ -65,2 +102,7 @@ var priority = {o: -1, q: 0, s: 0}; | ||
/** | ||
* Get the specificity of the language. | ||
* @private | ||
*/ | ||
function specify(language, spec, index) { | ||
@@ -88,2 +130,7 @@ var p = parseLanguage(language) | ||
/** | ||
* Get the preferred languages from an Accept-Language header. | ||
* @public | ||
*/ | ||
function preferredLanguages(accept, provided) { | ||
@@ -95,5 +142,6 @@ // RFC 2616 sec 14.4: no header = * | ||
// sorted list of all languages | ||
return accepts.filter(isQuality).sort(compareSpecs).map(function getLanguage(spec) { | ||
return spec.full; | ||
}); | ||
return accepts | ||
.filter(isQuality) | ||
.sort(compareSpecs) | ||
.map(getFullLanguage); | ||
} | ||
@@ -111,2 +159,7 @@ | ||
/** | ||
* Compare two specs. | ||
* @private | ||
*/ | ||
function compareSpecs(a, b) { | ||
@@ -116,4 +169,18 @@ return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; | ||
/** | ||
* Get full language string. | ||
* @private | ||
*/ | ||
function getFullLanguage(spec) { | ||
return spec.full; | ||
} | ||
/** | ||
* Check if a spec has any quality. | ||
* @private | ||
*/ | ||
function isQuality(spec) { | ||
return spec.q > 0; | ||
} |
@@ -9,5 +9,24 @@ /** | ||
'use strict'; | ||
/** | ||
* Module exports. | ||
* @public | ||
*/ | ||
module.exports = preferredMediaTypes; | ||
preferredMediaTypes.preferredMediaTypes = preferredMediaTypes; | ||
module.exports.preferredMediaTypes = preferredMediaTypes; | ||
/** | ||
* Module variables. | ||
* @private | ||
*/ | ||
var simpleMediaTypeRegExp = /^\s*(\S+?)\/([^;\s]+)\s*(?:;(.*))?$/; | ||
/** | ||
* Parse the Accept header. | ||
* @private | ||
*/ | ||
function parseAccept(accept) { | ||
@@ -28,31 +47,38 @@ var accepts = splitMediaTypes(accept); | ||
return accepts; | ||
}; | ||
} | ||
function parseMediaType(s, i) { | ||
var match = s.match(/\s*(\S+?)\/([^;\s]+)\s*(?:;(.*))?/); | ||
/** | ||
* Parse a media type from the Accept header. | ||
* @private | ||
*/ | ||
function parseMediaType(str, i) { | ||
var match = simpleMediaTypeRegExp.exec(str); | ||
if (!match) return null; | ||
var type = match[1], | ||
subtype = match[2], | ||
full = "" + type + "/" + subtype, | ||
params = {}, | ||
q = 1; | ||
var params = Object.create(null); | ||
var q = 1; | ||
var subtype = match[2]; | ||
var type = match[1]; | ||
if (match[3]) { | ||
params = match[3].split(';').map(function(s) { | ||
return s.trim().split('='); | ||
}).reduce(function (set, p) { | ||
var name = p[0].toLowerCase(); | ||
var value = p[1]; | ||
var kvps = splitParameters(match[3]).map(splitKeyValuePair); | ||
set[name] = value && value[0] === '"' && value[value.length - 1] === '"' | ||
? value.substr(1, value.length - 2) | ||
: value; | ||
for (var j = 0; j < kvps.length; j++) { | ||
var pair = kvps[j]; | ||
var key = pair[0].toLowerCase(); | ||
var val = pair[1]; | ||
return set; | ||
}, params); | ||
// get the value, unwrapping quotes | ||
var value = val && val[0] === '"' && val[val.length - 1] === '"' | ||
? val.substr(1, val.length - 2) | ||
: val; | ||
if (params.q != null) { | ||
q = parseFloat(params.q); | ||
delete params.q; | ||
if (key === 'q') { | ||
q = parseFloat(value); | ||
break; | ||
} | ||
// store parameter | ||
params[key] = value; | ||
} | ||
@@ -66,7 +92,11 @@ } | ||
q: q, | ||
i: i, | ||
full: full | ||
i: i | ||
}; | ||
} | ||
/** | ||
* Get the priority of a media type. | ||
* @private | ||
*/ | ||
function getMediaTypePriority(type, accepted, index) { | ||
@@ -86,2 +116,7 @@ var priority = {o: -1, q: 0, s: 0}; | ||
/** | ||
* Get the specificity of the media type. | ||
* @private | ||
*/ | ||
function specify(type, spec, index) { | ||
@@ -124,5 +159,9 @@ var p = parseMediaType(type); | ||
} | ||
} | ||
/** | ||
* Get the preferred media types from an Accept header. | ||
* @public | ||
*/ | ||
function preferredMediaTypes(accept, provided) { | ||
@@ -134,5 +173,6 @@ // RFC 2616 sec 14.2: no header = */* | ||
// sorted list of all types | ||
return accepts.filter(isQuality).sort(compareSpecs).map(function getType(spec) { | ||
return spec.full; | ||
}); | ||
return accepts | ||
.filter(isQuality) | ||
.sort(compareSpecs) | ||
.map(getFullType); | ||
} | ||
@@ -150,2 +190,7 @@ | ||
/** | ||
* Compare two specs. | ||
* @private | ||
*/ | ||
function compareSpecs(a, b) { | ||
@@ -155,2 +200,16 @@ return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; | ||
/** | ||
* Get full type string. | ||
* @private | ||
*/ | ||
function getFullType(spec) { | ||
return spec.type + '/' + spec.subtype; | ||
} | ||
/** | ||
* Check if a spec has any quality. | ||
* @private | ||
*/ | ||
function isQuality(spec) { | ||
@@ -160,2 +219,7 @@ return spec.q > 0; | ||
/** | ||
* Count the number of quotes in a string. | ||
* @private | ||
*/ | ||
function quoteCount(string) { | ||
@@ -173,2 +237,27 @@ var count = 0; | ||
/** | ||
* Split a key value pair. | ||
* @private | ||
*/ | ||
function splitKeyValuePair(str) { | ||
var index = str.indexOf('='); | ||
var key; | ||
var val; | ||
if (index === -1) { | ||
key = str; | ||
} else { | ||
key = str.substr(0, index); | ||
val = str.substr(index + 1); | ||
} | ||
return [key, val]; | ||
} | ||
/** | ||
* Split an Accept header into media types. | ||
* @private | ||
*/ | ||
function splitMediaTypes(accept) { | ||
@@ -190,1 +279,27 @@ var accepts = accept.split(','); | ||
} | ||
/** | ||
* Split a string of parameters. | ||
* @private | ||
*/ | ||
function splitParameters(str) { | ||
var parameters = str.split(';'); | ||
for (var i = 1, j = 0; i < parameters.length; i++) { | ||
if (quoteCount(parameters[j]) % 2 == 0) { | ||
parameters[++j] = parameters[i]; | ||
} else { | ||
parameters[j] += ';' + parameters[i]; | ||
} | ||
} | ||
// trim parameters | ||
parameters.length = j + 1; | ||
for (var i = 0; i < parameters.length; i++) { | ||
parameters[i] = parameters[i].trim(); | ||
} | ||
return parameters; | ||
} |
{ | ||
"name": "negotiator", | ||
"description": "HTTP content negotiation", | ||
"version": "0.5.3", | ||
"version": "0.6.0", | ||
"contributors": [ | ||
@@ -21,3 +21,3 @@ "Douglas Christopher Wilson <doug@somethingdoug.com>", | ||
"devDependencies": { | ||
"istanbul": "0.3.9", | ||
"istanbul": "0.3.21", | ||
"mocha": "~1.21.5" | ||
@@ -24,0 +24,0 @@ }, |
@@ -69,3 +69,3 @@ # negotiator | ||
availableLanguages = 'en', 'es', 'fr' | ||
availableLanguages = ['en', 'es', 'fr'] | ||
@@ -72,0 +72,0 @@ // Let's say Accept-Language header is 'en;q=0.8, es, pt' |
27747
760