Skip to content
This repository has been archived by the owner on Jul 15, 2018. It is now read-only.

Commit

Permalink
Binary is not an Array
Browse files Browse the repository at this point in the history
  • Loading branch information
olegp committed Jan 29, 2012
1 parent 9a972f0 commit 88925d9
Showing 1 changed file with 141 additions and 1 deletion.
142 changes: 141 additions & 1 deletion lib/utils/http.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var binary = require("binary");
var ByteArray = binary.ByteArray, ByteString = binary.ByteString;
var MemoryStream = require("io").MemoryStream;
var strings = require("common-utils/strings");

/**
* A utility class for implementing JSGI response filters. Each part of the
Expand Down Expand Up @@ -212,12 +213,37 @@ var urlEncode = exports.urlEncode = function(object) {
return buf.toString();
}

//character codes used for slicing and decoding
var SPACE = " ".charCodeAt(0);
var PERCENT = "%".charCodeAt(0);
var AMPERSAND = "&".charCodeAt(0);
var PLUS = "+".charCodeAt(0);
var EQUALS = "=".charCodeAt(0);

//character codes used for hex decoding
var CHAR_0 = "0".charCodeAt(0);
var CHAR_9 = "9".charCodeAt(0);
var CHAR_A = "A".charCodeAt(0);
var CHAR_F = "F".charCodeAt(0);
var CHAR_a = "a".charCodeAt(0);
var CHAR_f = "f".charCodeAt(0);

//used for multipart parsing
var HYPHEN = "-".charCodeAt(0);
var CR = "\r".charCodeAt(0);
var CRLF = new ByteString("\r\n", "ASCII");
var EMPTY_LINE = new ByteString("\r\n\r\n", "ASCII");

/**
* Find out whether the content type denotes a format this module can parse.
* @param {String} contentType a HTTP request Content-Type header
* @returns true if the content type can be parsed as form data by this module
*/
var isUrlEncoded = exports.isUrlEncoded = function(contentType) {
return contentType && strings.startsWith(
String(contentType).toLowerCase(), "application/x-www-form-urlencoded");
}

/**
* Find out whether the content type denotes a format this module can parse.
* @param {String} contentType a HTTP request Content-Type header
Expand All @@ -228,6 +254,119 @@ var isFileUpload = exports.isFileUpload = function(contentType) {
String(contentType).toLowerCase(), "multipart/form-data");
}

/**
* Parse a string or binary object representing a query string or post data into
* a JavaScript object structure using the specified encoding.
* @param {Binary|String} input a Binary object or string containing the
* URL-encoded parameters
* @param {Object} params optional parameter object to parse into. If undefined
* a new object is created and returned.
* @param {String} encoding a valid encoding name, defaults to UTF-8
* @returns the parsed parameter object
*/
var parseParameters = exports.parseParameters = function(input, params, encoding) {
if (!input) {
return params || {};
} else if (typeof input === "string" || input instanceof ByteString) {
// stream.read() should really return ByteArray in the first place...
input = input.toByteArray();
}
params = params || {};
encoding = encoding || "UTF-8";
var inputs = input.split(AMPERSAND);
for (var p in inputs) {
var param = inputs[p];
var s = param.split(EQUALS);
var name = s[0], value = s[1];
if (name && value) {
name = decodeToString(name, encoding);
value = decodeToString(value, encoding);
mergeParameter(params, name.trim(), value);
}
}
return params;
}

/**
* Adds a value to a parameter object using a square bracket property syntax.
* For example, parameter <code>foo[bar][][baz]=hello</code> will result in
* object structure <code>{foo: {bar: [{baz : "hello"}]}}</code>.
* @param {Object} params the top level parameter object
* @param {String} name the parameter name
* @param {String} value the parameter value
*/
var mergeParameter = exports.mergeParameter = function(params, name, value) {
// split "foo[bar][][baz]" into ["foo", "bar", "", "baz", ""]
if (name.match(/^[\w_\-\.]+(?:\[[^\]]*\]\s*)+$/)) {
var names = name.split(/\]\s*\[|\[|\]/).map(function(s) { return s.trim(); }).slice(0, -1);
mergeParameterInternal(params, names, value);
} else {
// not matching the foo[bar] pattern, add param as is
params[name] = value;
}
}

function mergeParameterInternal(params, names, value) {
if (names.length == 1) {
// a simple property - push or set depending on params' type
Array.isArray(params) ? params.push(value) : params[names[0]] = value;
} else {
// we have a property path - consume first token and recurse
var name = names.shift();
if (names[0]) {
// foo[bar] - parse as object property
var obj = params[name];
if (!(obj instanceof Object)) {
obj = {};
Array.isArray(params) ? params.push(obj) : params[name] = obj;
}
mergeParameterInternal(obj, names, value);
} else {
// foo[] - parse as array
var array = params[name];
if (!Array.isArray(array)) {
array = array == null ? [] : [array];
Array.isArray(params) ? params.push(array) : params[name] = array;
}
mergeParameterInternal(array, names, value);
}
}
}


//convert + to spaces, decode %ff hex sequences,
//then decode to string using the specified encoding.
function decodeToString(bytes, encoding) {
bytes = bytes.toArray();
var k = 0;
while((k = bytes.indexOf(PLUS, k)) > -1) {
bytes[k++] = SPACE;
}
var i, j = 0;
while((i = bytes.indexOf(PERCENT, j)) > -1) {
j = i;
while (bytes[i] == PERCENT && i++ <= bytes.length - 3) {
bytes[j++] = (convertHexDigit(bytes[i++]) << 4)
+ convertHexDigit(bytes[i++]);
}
if (i < bytes.length) {
bytes.copy(i, bytes.length, bytes, j);
}
bytes.length -= i - j;
}
return new ByteArray(bytes).decodeToString(encoding);
}

function convertHexDigit(byte) {
if (byte >= CHAR_0 && byte <= CHAR_9)
return byte - CHAR_0;
if (byte >= CHAR_a && byte <= CHAR_f)
return byte - CHAR_a + 10;
if (byte >= CHAR_A && byte <= CHAR_F)
return byte - CHAR_A + 10;
return 0;
}

/**
* Parses a multipart MIME input stream.
* Parses a multipart MIME input stream.
Expand Down Expand Up @@ -317,7 +456,8 @@ var parseFileUpload = exports.parseFileUpload = function(request, params, encodi
headers.push(line);
}
});
for each (var header in headers) {
for (var h in headers) {
var header = headers[h];
if (strings.startsWith(header.toLowerCase(), "content-disposition:")) {
data.name = getMimeParameter(header, "name");
data.filename = getMimeParameter(header, "filename");
Expand Down

0 comments on commit 88925d9

Please sign in to comment.