[Pkg-javascript-commits] [node-media-typer] 01/02: Imported Upstream version 0.2.0

Andrew Kelley andrewrk-guest at moszumanska.debian.org
Sat Jun 28 16:15:49 UTC 2014


This is an automated email from the git hooks/post-receive script.

andrewrk-guest pushed a commit to branch master
in repository node-media-typer.

commit caee432085cf1f7e905dfd3910507063ed4a5358
Author: Andrew Kelley <superjoe30 at gmail.com>
Date:   Sat Jun 28 16:13:33 2014 +0000

    Imported Upstream version 0.2.0
---
 .gitignore   |   3 +
 .npmignore   |   3 +
 .travis.yml  |  11 +++
 HISTORY.md   |  16 ++++
 README.md    |  88 ++++++++++++++++++++
 index.js     | 261 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 package.json |  21 +++++
 test/test.js | 213 ++++++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 616 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3cd27af
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+coverage/
+node_modules/
+npm-debug.log
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..cd39b77
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+coverage/
+test/
+.travis.yml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..1ff243c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
+  - "0.11"
+matrix:
+  allow_failures:
+    - node_js: "0.11"
+  fast_finish: true
+script: "npm run-script test-travis"
+after_script: "npm install coveralls at 2.10.0 && cat ./coverage/lcov.info | coveralls"
diff --git a/HISTORY.md b/HISTORY.md
new file mode 100644
index 0000000..215cc22
--- /dev/null
+++ b/HISTORY.md
@@ -0,0 +1,16 @@
+0.2.0 / 2014-06-18
+==================
+
+  * Add `typer.format()` to format media types
+
+0.1.0 / 2014-06-17
+==================
+
+  * Accept `req` as argument to `parse`
+  * Accept `res` as argument to `parse`
+  * Parse media type with extra LWS between type and first parameter
+
+0.0.0 / 2014-06-13
+==================
+
+  * Initial implementation
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..338b7ce
--- /dev/null
+++ b/README.md
@@ -0,0 +1,88 @@
+# media-typer
+
+[![NPM version](https://badge.fury.io/js/media-typer.svg)](https://badge.fury.io/js/media-typer)
+[![Build Status](https://travis-ci.org/expressjs/media-typer.svg?branch=master)](https://travis-ci.org/expressjs/media-typer)
+[![Coverage Status](https://img.shields.io/coveralls/expressjs/media-typer.svg?branch=master)](https://coveralls.io/r/expressjs/media-typer)
+
+Simple RFC 6838 media type parser
+
+## Installation
+
+```sh
+$ npm install media-typer
+```
+
+## API
+
+```js
+var typer = require('media-typer')
+```
+
+### typer.parse(string)
+
+```js
+var obj = typer.parse('image/svg+xml; charset=utf-8')
+```
+
+Parse a media type string. This will return an object with the following
+properties (examples are shown for the string `'image/svg+xml; charset=utf-8'`):
+
+ - `type`: The type of the media type (always lower case). Example: `'image'`
+
+ - `subtype`: The subtype of the media type (always lower case). Example: `'svg'`
+
+ - `suffix`: The suffix of the media type (always lower case). Example: `'xml'`
+
+ - `parameters`: An object of the parameters in the media type (name of parameter always lower case). Example: `{charset: 'utf-8'}`
+
+### typer.parse(req)
+
+```js
+var obj = typer.parse(req)
+```
+
+Parse the `content-type` header from the given `req`. Short-cut for
+`typer.parse(req.headers['content-type'])`.
+
+### typer.parse(res)
+
+```js
+var obj = typer.parse(req)
+```
+
+Parse the `content-type` header set on the given `res`. Short-cut for
+`typer.parse(res.getHeader('content-type'))`.
+
+### typer.format(obj)
+
+```js
+var obj = typer.format({type: 'image', subtype: 'svg', suffix: 'xml'})
+```
+
+Format an object into a media type string. This will return a string of the
+mime type for the given object. For the properties of the object, see the
+documentation for `typer.parse(string)`.
+
+## License
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..cbf2e17
--- /dev/null
+++ b/index.js
@@ -0,0 +1,261 @@
+/*!
+ * media-typer
+ * Copyright(c) 2014 Douglas Christopher Wilson
+ * MIT Licensed
+ */
+
+/**
+ * RegExp to match *( ";" parameter ) in RFC 2616 sec 3.7
+ *
+ * parameter     = token "=" ( token | quoted-string )
+ * token         = 1*<any CHAR except CTLs or separators>
+ * separators    = "(" | ")" | "<" | ">" | "@"
+ *               | "," | ";" | ":" | "\" | <">
+ *               | "/" | "[" | "]" | "?" | "="
+ *               | "{" | "}" | SP | HT
+ * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
+ * qdtext        = <any TEXT except <">>
+ * quoted-pair   = "\" CHAR
+ * CHAR          = <any US-ASCII character (octets 0 - 127)>
+ * TEXT          = <any OCTET except CTLs, but including LWS>
+ * LWS           = [CRLF] 1*( SP | HT )
+ * CRLF          = CR LF
+ * CR            = <US-ASCII CR, carriage return (13)>
+ * LF            = <US-ASCII LF, linefeed (10)>
+ * SP            = <US-ASCII SP, space (32)>
+ * SHT           = <US-ASCII HT, horizontal-tab (9)>
+ * CTL           = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+ * OCTET         = <any 8-bit sequence of data>
+ */
+var paramRegExp = /; *([!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) *= *("(?:[ !\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u0020-\u007e])*"|[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+) */g;
+var textRegExp = /^[\u0020-\u007e\u0080-\u00ff]+$/
+var tokenRegExp = /^[!#$%&'\*\+\-\.0-9A-Z\^_`a-z\|~]+$/
+
+/**
+ * RegExp to match quoted-pair in RFC 2616
+ *
+ * quoted-pair = "\" CHAR
+ * CHAR        = <any US-ASCII character (octets 0 - 127)>
+ */
+var qescRegExp = /\\([\u0000-\u007f])/g;
+
+/**
+ * RegExp to match chars that must be quoted-pair in RFC 2616
+ */
+var quoteRegExp = /([\\"])/g;
+
+/**
+ * RegExp to match type in RFC 6838
+ *
+ * type-name = restricted-name
+ * subtype-name = restricted-name
+ * restricted-name = restricted-name-first *126restricted-name-chars
+ * restricted-name-first  = ALPHA / DIGIT
+ * restricted-name-chars  = ALPHA / DIGIT / "!" / "#" /
+ *                          "$" / "&" / "-" / "^" / "_"
+ * restricted-name-chars =/ "." ; Characters before first dot always
+ *                              ; specify a facet name
+ * restricted-name-chars =/ "+" ; Characters after last plus always
+ *                              ; specify a structured syntax suffix
+ * ALPHA =  %x41-5A / %x61-7A   ; A-Z / a-z
+ * DIGIT =  %x30-39             ; 0-9
+ */
+var subtypeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_.-]{0,126}$/
+var typeNameRegExp = /^[A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126}$/
+var typeRegExp = /^ *([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126}) *$/;
+
+/**
+ * Module exports.
+ */
+
+exports.format = format
+exports.parse = parse
+
+/**
+ * Format object to media type.
+ *
+ * @param {object} obj
+ * @return {string}
+ * @api public
+ */
+
+function format(obj) {
+  if (!obj || typeof obj !== 'object') {
+    throw new TypeError('argument obj is required')
+  }
+
+  var parameters = obj.parameters
+  var subtype = obj.subtype
+  var suffix = obj.suffix
+  var type = obj.type
+
+  if (!type || !typeNameRegExp.test(type)) {
+    throw new TypeError('invalid type')
+  }
+
+  if (!subtype || !subtypeNameRegExp.test(subtype)) {
+    throw new TypeError('invalid subtype')
+  }
+
+  // format as type/subtype
+  var string = type + '/' + subtype
+
+  // append +suffix
+  if (suffix) {
+    if (!typeNameRegExp.test(suffix)) {
+      throw new TypeError('invalid suffix')
+    }
+
+    string += '+' + suffix
+  }
+
+  // append parameters
+  if (parameters && typeof parameters === 'object') {
+    var param
+    var params = Object.keys(parameters).sort()
+
+    for (var i = 0; i < params.length; i++) {
+      param = params[i]
+
+      if (!tokenRegExp.test(param)) {
+        throw new TypeError('invalid parameter name')
+      }
+
+      string += '; ' + param + '=' + qstring(parameters[param])
+    }
+  }
+
+  return string
+}
+
+/**
+ * Parse media type to object.
+ *
+ * @param {string|object} string
+ * @return {Object}
+ * @api public
+ */
+
+function parse(string) {
+  if (!string) {
+    throw new TypeError('argument string is required')
+  }
+
+  // support req/res-like objects as argument
+  if (typeof string === 'object') {
+    string = getcontenttype(string)
+  }
+
+  if (typeof string !== 'string') {
+    throw new TypeError('argument string is required to be a string')
+  }
+
+  var index = string.indexOf(';')
+  var type = index !== -1
+    ? string.substr(0, index)
+    : string
+
+  var key
+  var match
+  var obj = splitType(type)
+  var params = {}
+  var value
+
+  paramRegExp.lastIndex = index
+
+  while (match = paramRegExp.exec(string)) {
+    key = match[1].toLowerCase()
+    value = match[2]
+
+    if (value[0] === '"') {
+      // remove quotes and escapes
+      value = value
+        .substr(1, value.length - 2)
+        .replace(qescRegExp, '$1')
+    }
+
+    params[key] = value
+  }
+
+  obj.parameters = params
+
+  return obj
+}
+
+/**
+ * Get content-type from req/res objects.
+ *
+ * @param {object}
+ * @return {Object}
+ * @api private
+ */
+
+function getcontenttype(obj) {
+  if (typeof obj.getHeader === 'function') {
+    // res-like
+    return obj.getHeader('content-type')
+  }
+
+  if (typeof obj.headers === 'object') {
+    // req-like
+    return obj.headers && obj.headers['content-type']
+  }
+}
+
+/**
+ * Quote a string if necessary.
+ *
+ * @param {string} val
+ * @return {string}
+ * @api private
+ */
+
+function qstring(val) {
+  var str = String(val)
+
+  // no need to quote tokens
+  if (tokenRegExp.test(str)) {
+    return str
+  }
+
+  if (str.length > 0 && !textRegExp.test(str)) {
+    throw new TypeError('invalid parameter value')
+  }
+
+  return '"' + str.replace(quoteRegExp, '\\$1') + '"'
+}
+
+/**
+ * Simply "type/subtype+siffx" into parts.
+ *
+ * @param {string} string
+ * @return {Object}
+ * @api private
+ */
+
+function splitType(string) {
+  var match = typeRegExp.exec(string.toLowerCase())
+
+  if (!match) {
+    throw new TypeError('invalid media type')
+  }
+
+  var type = match[1]
+  var subtype = match[2]
+  var suffix
+
+  // suffix after last +
+  var index = subtype.lastIndexOf('+')
+  if (index !== -1) {
+    suffix = subtype.substr(index + 1)
+    subtype = subtype.substr(0, index)
+  }
+
+  var obj = {
+    type: type,
+    subtype: subtype,
+    suffix: suffix
+  }
+
+  return obj
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..be2b2f0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+  "name": "media-typer",
+  "description": "Simple RFC 6838 media type parser and formatter",
+  "version": "0.2.0",
+  "author": "Douglas Christopher Wilson <doug at somethingdoug.com>",
+  "license": "MIT",
+  "repository": "expressjs/media-typer",
+  "devDependencies": {
+    "istanbul": "0.2.10",
+    "mocha": "~1.20.1",
+    "should": "~4.0.4"
+  },
+  "engines": {
+    "node": ">= 0.8.0"
+  },
+  "scripts": {
+    "test": "mocha --reporter dot test/",
+    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot test/",
+    "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec test/"
+  }
+}
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..7126eb0
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,213 @@
+
+var typer = require('..')
+var should = require('should')
+
+var invalidTypes = [
+  ' ',
+  'null',
+  'undefined',
+  '/',
+  'text/;plain',
+  'text/"plain"',
+  'text/p£ain',
+  'text/(plain)',
+  'text/@plain',
+  'text/plain,wrong',
+]
+
+describe('typer.format(obj)', function () {
+  it('should format basic type', function () {
+    var str = typer.format({type: 'text', subtype: 'html'})
+    str.should.equal('text/html')
+  })
+
+  it('should format type with suffix', function () {
+    var str = typer.format({type: 'image', subtype: 'svg', suffix: 'xml'})
+    str.should.equal('image/svg+xml')
+  })
+
+  it('should format type with parameter', function () {
+    var str = typer.format({
+      type: 'text',
+      subtype: 'html',
+      parameters: {charset: 'utf-8'}
+    })
+    str.should.equal('text/html; charset=utf-8')
+  })
+
+  it('should format type with parameter that needs quotes', function () {
+    var str = typer.format({
+      type: 'text',
+      subtype: 'html',
+      parameters: {foo: 'bar or "baz"'}
+    })
+    str.should.equal('text/html; foo="bar or \\"baz\\""')
+  })
+
+  it('should format type with parameter with empty value', function () {
+    var str = typer.format({
+      type: 'text',
+      subtype: 'html',
+      parameters: {foo: ''}
+    })
+    str.should.equal('text/html; foo=""')
+  })
+
+  it('should format type with multiple parameters', function () {
+    var str = typer.format({
+      type: 'text',
+      subtype: 'html',
+      parameters: {charset: 'utf-8', foo: 'bar', bar: 'baz'}
+    })
+    str.should.equal('text/html; bar=baz; charset=utf-8; foo=bar')
+  })
+
+  it('should require argument', function () {
+    typer.format.should.throw(/obj.*required/)
+  })
+
+  it('should reject non-objects', function () {
+    typer.format.bind(null, 7).should.throw(/obj.*required/)
+  })
+
+  it('should require type', function () {
+    typer.format.bind(null, {}).should.throw(/invalid type/)
+  })
+
+  it('should reject invalid type', function () {
+    typer.format.bind(null, {type: 'text/'}).should.throw(/invalid type/)
+  })
+
+  it('should require subtype', function () {
+    typer.format.bind(null, {type: 'text'}).should.throw(/invalid subtype/)
+  })
+
+  it('should reject invalid subtype', function () {
+    typer.format.bind(null, {type: 'text', subtype: 'html/'}).should.throw(/invalid subtype/)
+  })
+
+  it('should reject invalid suffix', function () {
+    var obj = {type: 'image', subtype: 'svg', suffix: 'xml\\'}
+    typer.format.bind(null, obj).should.throw(/invalid suffix/)
+  })
+
+  it('should reject invalid parameter name', function () {
+    var obj = {type: 'image', subtype: 'svg', parameters: {'foo/': 'bar'}}
+    typer.format.bind(null, obj).should.throw(/invalid parameter name/)
+  })
+
+  it('should reject invalid parameter value', function () {
+    var obj = {type: 'image', subtype: 'svg', parameters: {'foo': 'bar\u0000'}}
+    typer.format.bind(null, obj).should.throw(/invalid parameter value/)
+  })
+})
+
+describe('typer.parse(string)', function () {
+  it('should parse basic type', function () {
+    var type = typer.parse('text/html')
+    type.type.should.equal('text')
+    type.subtype.should.equal('html')
+  })
+
+  it('should parse with suffix', function () {
+    var type = typer.parse('image/svg+xml')
+    type.type.should.equal('image')
+    type.subtype.should.equal('svg')
+    type.suffix.should.equal('xml')
+  })
+
+  it('should parse parameters', function () {
+    var type = typer.parse('text/html; charset=utf-8; foo=bar')
+    type.type.should.equal('text')
+    type.subtype.should.equal('html')
+    type.parameters.should.have.property('charset', 'utf-8')
+    type.parameters.should.have.property('foo', 'bar')
+  })
+
+  it('should parse parameters with extra LWS', function () {
+    var type = typer.parse('text/html ; charset=utf-8 ; foo=bar')
+    type.type.should.equal('text')
+    type.subtype.should.equal('html')
+    type.parameters.should.have.property('charset', 'utf-8')
+    type.parameters.should.have.property('foo', 'bar')
+  })
+
+  it('should lower-case type', function () {
+    var type = typer.parse('IMAGE/SVG+XML')
+    type.type.should.equal('image')
+    type.subtype.should.equal('svg')
+    type.suffix.should.equal('xml')
+  })
+
+  it('should lower-case parameter names', function () {
+    var type = typer.parse('text/html; Charset=UTF-8')
+    type.parameters.should.have.property('charset', 'UTF-8')
+  })
+
+  it('should unquote parameter values', function () {
+    var type = typer.parse('text/html; charset="UTF-8"')
+    type.parameters.should.have.property('charset', 'UTF-8')
+  })
+
+  it('should unquote parameter values with escapes', function () {
+    var type = typer.parse('text/html; charset = "UT\\F-\\\\\\"8\\""')
+    type.parameters.should.have.property('charset', 'UTF-\\"8"')
+  })
+
+  it('should handle balanced quotes', function () {
+    var type = typer.parse('text/html; param="charset=\\"utf-8\\"; foo=bar"; bar=foo')
+    Object.keys(type.parameters).length.should.equal(2)
+    type.parameters.should.have.property('param', 'charset="utf-8"; foo=bar')
+    type.parameters.should.have.property('bar', 'foo')
+  })
+
+  invalidTypes.forEach(function (type) {
+    it('should throw on invalid media type ' + type, function () {
+      typer.parse.bind(null, type).should.throw(/invalid media type/)
+    })
+  })
+
+  it('should require argument', function () {
+    typer.parse.should.throw(/string.*required/)
+  })
+
+  it('should reject non-strings', function () {
+    typer.parse.bind(null, 7).should.throw(/string.*required/)
+  })
+})
+
+describe('typer.parse(req)', function () {
+  it('should parse content-type header', function () {
+    var req = {headers: {'content-type': 'text/html'}}
+    var type = typer.parse(req)
+    type.type.should.equal('text')
+    type.subtype.should.equal('html')
+  })
+
+  it('should reject objects without headers property', function () {
+    typer.parse.bind(null, {}).should.throw(/string.*required/)
+  })
+
+  it('should reject missing content-type', function () {
+    var req = {headers: {}}
+    typer.parse.bind(null, req).should.throw(/string.*required/)
+  })
+})
+
+describe('typer.parse(res)', function () {
+  it('should parse content-type header', function () {
+    var res = {getHeader: function(){ return 'text/html'}}
+    var type = typer.parse(res)
+    type.type.should.equal('text')
+    type.subtype.should.equal('html')
+  })
+
+  it('should reject objects without getHeader method', function () {
+    typer.parse.bind(null, {}).should.throw(/string.*required/)
+  })
+
+  it('should reject missing content-type', function () {
+    var res = {getHeader: function(){}}
+    typer.parse.bind(null, res).should.throw(/string.*required/)
+  })
+})

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-media-typer.git



More information about the Pkg-javascript-commits mailing list