summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/querystring.js
blob: 9982a00ab8b6473a2c92df449d5d1158b7d55a07 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

module.metadata = {
  "stability": "unstable"
};

var unescape = decodeURIComponent;
exports.unescape = unescape;

// encodes a string safely for application/x-www-form-urlencoded
// adheres to RFC 3986
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURIComponent
function escape(query) {
  return encodeURIComponent(query).
    replace(/%20/g, '+').
    replace(/!/g, '%21').
    replace(/'/g, '%27').
    replace(/\(/g, '%28').
    replace(/\)/g, '%29').
    replace(/\*/g, '%2A');
}
exports.escape = escape;

// Converts an object of unordered key-vals to a string that can be passed
// as part of a request
function stringify(options, separator, assigner) {
  separator = separator || '&';
  assigner = assigner || '=';
  // Explicitly return null if we have null, and empty string, or empty object.
  if (!options)
    return '';

  // If content is already a string, just return it as is.
  if (typeof(options) == 'string')
    return options;

  // At this point we have a k:v object. Iterate over it and encode each value.
  // Arrays and nested objects will get encoded as needed. For example...
  //
  //   { foo: [1, 2, { omg: 'bbq', 'all your base!': 'are belong to us' }], bar: 'baz' }
  //
  // will be encoded as
  //
  //   foo[0]=1&foo[1]=2&foo[2][omg]=bbq&foo[2][all+your+base!]=are+belong+to+us&bar=baz
  //
  // Keys (including '[' and ']') and values will be encoded with
  // `escape` before returning.
  //
  // Execution was inspired by jQuery, but some details have changed and numeric
  // array keys are included (whereas they are not in jQuery).

  let encodedContent = [];
  function add(key, val) {
    encodedContent.push(escape(key) + assigner + escape(val));
  }

  function make(key, value) {
    if (value && typeof(value) === 'object')
      Object.keys(value).forEach(function(name) {
        make(key + '[' + name + ']', value[name]);
      });
    else
      add(key, value);
  }

  Object.keys(options).forEach(function(name) { make(name, options[name]); });
  return encodedContent.join(separator);

  //XXXzpao In theory, we can just use a FormData object on 1.9.3, but I had
  //        trouble getting that working. It would also be nice to stay
  //        backwards-compat as long as possible. Keeping this in for now...
  // let formData = Cc['@mozilla.org/files/formdata;1'].
  //                createInstance(Ci.nsIDOMFormData);
  // for ([k, v] in Iterator(content)) {
  //   formData.append(k, v);
  // }
  // return formData;
}
exports.stringify = stringify;

// Exporting aliases that nodejs implements just for the sake of
// interoperability.
exports.encode = stringify;
exports.serialize = stringify;

// Note: That `stringify` and `parse` aren't bijective as we use `stringify`
// as it was implement in request module, but implement `parse` to match nodejs
// behavior.
// TODO: Make `stringify` implement API as in nodejs and figure out backwards
// compatibility.
function parse(query, separator, assigner) {
  separator = separator || '&';
  assigner = assigner || '=';
  let result = {};

  if (typeof query !== 'string' || query.length === 0)
    return result;

  query.split(separator).forEach(function(chunk) {
    let pair = chunk.split(assigner);
    let key = unescape(pair[0]);
    let value = unescape(pair.slice(1).join(assigner));

    if (!(key in result))
      result[key] = value;
    else if (Array.isArray(result[key]))
      result[key].push(value);
    else
      result[key] = [result[key], value];
  });

  return result;
};
exports.parse = parse;
// Exporting aliases that nodejs implements just for the sake of
// interoperability.
exports.decode = parse;