michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: 'use strict'; michael@0: michael@0: module.metadata = { michael@0: "stability": "unstable" michael@0: }; michael@0: michael@0: let unescape = decodeURIComponent; michael@0: exports.unescape = unescape; michael@0: michael@0: // encodes a string safely for application/x-www-form-urlencoded michael@0: // adheres to RFC 3986 michael@0: // see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURIComponent michael@0: function escape(query) { michael@0: return encodeURIComponent(query). michael@0: replace(/%20/g, '+'). michael@0: replace(/!/g, '%21'). michael@0: replace(/'/g, '%27'). michael@0: replace(/\(/g, '%28'). michael@0: replace(/\)/g, '%29'). michael@0: replace(/\*/g, '%2A'); michael@0: } michael@0: exports.escape = escape; michael@0: michael@0: // Converts an object of unordered key-vals to a string that can be passed michael@0: // as part of a request michael@0: function stringify(options, separator, assigner) { michael@0: separator = separator || '&'; michael@0: assigner = assigner || '='; michael@0: // Explicitly return null if we have null, and empty string, or empty object. michael@0: if (!options) michael@0: return ''; michael@0: michael@0: // If content is already a string, just return it as is. michael@0: if (typeof(options) == 'string') michael@0: return options; michael@0: michael@0: // At this point we have a k:v object. Iterate over it and encode each value. michael@0: // Arrays and nested objects will get encoded as needed. For example... michael@0: // michael@0: // { foo: [1, 2, { omg: 'bbq', 'all your base!': 'are belong to us' }], bar: 'baz' } michael@0: // michael@0: // will be encoded as michael@0: // michael@0: // foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz michael@0: // michael@0: // Keys (including '[' and ']') and values will be encoded with michael@0: // `escape` before returning. michael@0: // michael@0: // Execution was inspired by jQuery, but some details have changed and numeric michael@0: // array keys are included (whereas they are not in jQuery). michael@0: michael@0: let encodedContent = []; michael@0: function add(key, val) { michael@0: encodedContent.push(escape(key) + assigner + escape(val)); michael@0: } michael@0: michael@0: function make(key, value) { michael@0: if (value && typeof(value) === 'object') michael@0: Object.keys(value).forEach(function(name) { michael@0: make(key + '[' + name + ']', value[name]); michael@0: }); michael@0: else michael@0: add(key, value); michael@0: } michael@0: michael@0: Object.keys(options).forEach(function(name) { make(name, options[name]); }); michael@0: return encodedContent.join(separator); michael@0: michael@0: //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had michael@0: // trouble getting that working. It would also be nice to stay michael@0: // backwards-compat as long as possible. Keeping this in for now... michael@0: // let formData = Cc['@mozilla.org/files/formdata;1']. michael@0: // createInstance(Ci.nsIDOMFormData); michael@0: // for ([k, v] in Iterator(content)) { michael@0: // formData.append(k, v); michael@0: // } michael@0: // return formData; michael@0: } michael@0: exports.stringify = stringify; michael@0: michael@0: // Exporting aliases that nodejs implements just for the sake of michael@0: // interoperability. michael@0: exports.encode = stringify; michael@0: exports.serialize = stringify; michael@0: michael@0: // Note: That `stringify` and `parse` aren't bijective as we use `stringify` michael@0: // as it was implement in request module, but implement `parse` to match nodejs michael@0: // behavior. michael@0: // TODO: Make `stringify` implement API as in nodejs and figure out backwards michael@0: // compatibility. michael@0: function parse(query, separator, assigner) { michael@0: separator = separator || '&'; michael@0: assigner = assigner || '='; michael@0: let result = {}; michael@0: michael@0: if (typeof query !== 'string' || query.length === 0) michael@0: return result; michael@0: michael@0: query.split(separator).forEach(function(chunk) { michael@0: let pair = chunk.split(assigner); michael@0: let key = unescape(pair[0]); michael@0: let value = unescape(pair.slice(1).join(assigner)); michael@0: michael@0: if (!(key in result)) michael@0: result[key] = value; michael@0: else if (Array.isArray(result[key])) michael@0: result[key].push(value); michael@0: else michael@0: result[key] = [result[key], value]; michael@0: }); michael@0: michael@0: return result; michael@0: }; michael@0: exports.parse = parse; michael@0: // Exporting aliases that nodejs implements just for the sake of michael@0: // interoperability. michael@0: exports.decode = parse;