Skip to content

Commit

Permalink
adding URII.escapeQuerySpace to control space escaping (+ or %20) in …
Browse files Browse the repository at this point in the history
…query string - closes #74
  • Loading branch information
rodneyrehm committed Aug 3, 2013
1 parent aaea802 commit b6ec1b6
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 32 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ URI.js is published under the [MIT license](http://www.opensource.org/licenses/m

### `[dev-version]` (master branch) ###

* adding setting `URI.escapeQuerySpace` to control if query string should escape spaces using `+` or `%20` - ([Issue #74](https://github.com/medialize/URI.js/issues/74))
* updating [Punycode.js](https://github.com/bestiejs/punycode.js/) to version 1.2.3
* fixing internal `strictEncodeURIComponent()` to work in Firefox 3.6 - ([Issue #91](https://github.com/medialize/URI.js/issues/91))
* fixing [`.normalizePath()`](http://medialize.github.io/URI.js/docs.html#normalize-path) to properly resolve `/.` and `/.//` to `/` - ([Issue #97](https://github.com/medialize/URI.js/issues/97))
Expand Down
19 changes: 18 additions & 1 deletion docs.html
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,7 @@ <h3 id="static-buildHost">URI.buildHost(<em>object</em> parts)</h3>
};
URI.buildHost(parts) === "example.org:8080";</pre>

<h3 id="static-buildQuery">URI.buildQuery(<em>object</em> data, [<em>boolean</em> duplicates])</h3>
<h3 id="static-buildQuery">URI.buildQuery(<em>object</em> data, [<em>boolean</em> duplicateQueryParameters], [<em>boolean</em> escapeQuerySpace])</h3>
<p>serializes the query string parameters</p>
<pre class="prettyprint lang-js">var data = {
foo: "bar",
Expand Down Expand Up @@ -1060,6 +1060,23 @@ <h3 id="static-buildQuery">URI.buildQuery(<em>object</em> data, [<em>boolean</em

withDuplicates === "?bar=1&amp;bar=1";
noDuplicates === "?bar=1";</pre>

<p id="setting-escapeQuerySpace">As of v1.11.0 you can configure query space en/decoding:</p>
<pre class="prettyprint lang-js">// prevent all new URI instances from escaping spaces in query strings:
URI.escapeQuerySpace = false; // default is true

// make a specific URI instance allow duplicates:
var withPlus = URI("?bar=hello+world")
.escapeQuerySpace(true)
.query(true).bar;

// make a specific URI instance avoid duplicates (default):
var withPercent = URI("?bar=hello%20world")
.escapeQuerySpace(false)
.query(true).bar;

withPlus === "hello world";
withPercent === "hello world";</pre>

<h2 id="encoding-decoding">Encoding and Decoding URLs</h2>

Expand Down
72 changes: 41 additions & 31 deletions src/URI.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,14 @@ URI._parts = function() {
query: null,
fragment: null,
// state
duplicateQueryParameters: URI.duplicateQueryParameters
duplicateQueryParameters: URI.duplicateQueryParameters,
escapeQuerySpace: URI.escapeQuerySpace
};
};
// state: allow duplicate query parameters (a=1&a=1)
URI.duplicateQueryParameters = false;
// state: replaces + with %20 (space in query strings)
URI.escapeQuerySpace = true;
// static properties
URI.protocol_expression = /^[a-z][a-z0-9-+-]*$/i;
URI.idn_expression = /[^a-z0-9\.-]/i;
Expand Down Expand Up @@ -303,12 +306,14 @@ URI.characters = {
}
}
};
URI.encodeQuery = function(string) {
return URI.encode(string + "").replace(/%20/g, '+');
URI.encodeQuery = function(string, escapeQuerySpace) {
var escaped = URI.encode(string + "");
return escapeQuerySpace ? escaped.replace(/%20/g, '+') : escaped;
};
URI.decodeQuery = function(string) {
URI.decodeQuery = function(string, escapeQuerySpace) {
string += "";
try {
return URI.decode((string + "").replace(/\+/g, '%20'));
return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string);
} catch(e) {
// we're not going to mess with weird encodings,
// give up and return the undecoded original string
Expand Down Expand Up @@ -467,7 +472,7 @@ URI.parseUserinfo = function(string, parts) {

return string;
};
URI.parseQuery = function(string) {
URI.parseQuery = function(string, escapeQuerySpace) {
if (!string) {
return {};
}
Expand All @@ -486,9 +491,9 @@ URI.parseQuery = function(string) {

for (var i = 0; i < length; i++) {
v = splits[i].split('=');
name = URI.decodeQuery(v.shift());
name = URI.decodeQuery(v.shift(), escapeQuerySpace);
// no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
value = v.length ? URI.decodeQuery(v.join('=')) : null;
value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null;

if (items[name]) {
if (typeof items[name] === "string") {
Expand Down Expand Up @@ -574,7 +579,7 @@ URI.buildUserinfo = function(parts) {

return t;
};
URI.buildQuery = function(data, duplicates) {
URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) {
// according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html
// being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed
// the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax!
Expand All @@ -589,24 +594,24 @@ URI.buildQuery = function(data, duplicates) {
unique = {};
for (i = 0, length = data[key].length; i < length; i++) {
if (data[key][i] !== undefined && unique[data[key][i] + ""] === undefined) {
t += "&" + URI.buildQueryParameter(key, data[key][i]);
if (duplicates !== true) {
t += "&" + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace);
if (duplicateQueryParameters !== true) {
unique[data[key][i] + ""] = true;
}
}
}
} else if (data[key] !== undefined) {
t += '&' + URI.buildQueryParameter(key, data[key]);
t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace);
}
}
}

return t.substring(1);
};
URI.buildQueryParameter = function(name, value) {
URI.buildQueryParameter = function(name, value, escapeQuerySpace) {
// http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded
// don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization
return URI.encodeQuery(name) + (value !== null ? "=" + URI.encodeQuery(value) : "");
return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? "=" + URI.encodeQuery(value, escapeQuerySpace) : "");
};

URI.addQuery = function(data, name, value) {
Expand Down Expand Up @@ -1441,23 +1446,23 @@ p.segment = function(segment, v, build) {
var q = p.query;
p.query = function(v, build) {
if (v === true) {
return URI.parseQuery(this._parts.query);
return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
} else if (typeof v === "function") {
var data = URI.parseQuery(this._parts.query);
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
var result = v.call(this, data);
this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters);
this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
this.build(!build);
return this;
} else if (v !== undefined && typeof v !== "string") {
this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters);
this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
this.build(!build);
return this;
} else {
return q.call(this, v, build);
}
};
p.setQuery = function(name, value, build) {
var data = URI.parseQuery(this._parts.query);
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);

if (typeof name === "object") {
for (var key in name) {
Expand All @@ -1471,7 +1476,7 @@ p.setQuery = function(name, value, build) {
throw new TypeError("URI.addQuery() accepts an object, string as the name parameter");
}

this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters);
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
if (typeof name !== "string") {
build = value;
}
Expand All @@ -1480,9 +1485,9 @@ p.setQuery = function(name, value, build) {
return this;
};
p.addQuery = function(name, value, build) {
var data = URI.parseQuery(this._parts.query);
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
URI.addQuery(data, name, value === undefined ? null : value);
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters);
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
if (typeof name !== "string") {
build = value;
}
Expand All @@ -1491,9 +1496,9 @@ p.addQuery = function(name, value, build) {
return this;
};
p.removeQuery = function(name, value, build) {
var data = URI.parseQuery(this._parts.query);
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
URI.removeQuery(data, name, value);
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters);
this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
if (typeof name !== "string") {
build = value;
}
Expand All @@ -1502,7 +1507,7 @@ p.removeQuery = function(name, value, build) {
return this;
};
p.hasQuery = function(name, value, withinArray) {
var data = URI.parseQuery(this._parts.query);
var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
return URI.hasQuery(data, name, value, withinArray);
};
p.setSearch = p.setQuery;
Expand Down Expand Up @@ -1623,7 +1628,7 @@ p.normalizeQuery = function(build) {
if (!this._parts.query.length) {
this._parts.query = null;
} else {
this.query(URI.parseQuery(this._parts.query));
this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace));
}

this.build(!build);
Expand Down Expand Up @@ -1697,18 +1702,18 @@ p.readable = function() {
var q = '';
for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) {
var kv = (qp[i] || "").split('=');
q += '&' + URI.decodeQuery(kv[0])
q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace)
.replace(/&/g, '%26');

if (kv[1] !== undefined) {
q += "=" + URI.decodeQuery(kv[1])
q += "=" + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace)
.replace(/&/g, '%26');
}
}
t += '?' + q.substring(1);
}

t += URI.decodeQuery(uri.hash());
t += URI.decodeQuery(uri.hash(), true);
return t;
};

Expand Down Expand Up @@ -1855,8 +1860,8 @@ p.equals = function(uri) {
return false;
}

one_map = URI.parseQuery(one_query);
two_map = URI.parseQuery(two_query);
one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace);
two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace);

for (key in one_map) {
if (hasOwn.call(one_map, key)) {
Expand Down Expand Up @@ -1890,5 +1895,10 @@ p.duplicateQueryParameters = function(v) {
return this;
};

p.escapeQuerySpace = function(v) {
this._parts.escapeQuerySpace = !!v;
return this;
};

return URI;
}));
32 changes: 32 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,38 @@ test("duplicateQueryParameters", function() {
u.addQuery('bar', 1);
equal(u.toString(), '?bar=1&bar=1&bar=1&bar=1', "parameters NOT de-duplicated after addQuery()");
});
test("escapeQuerySpace", function() {
var u = new URI('?bar=foo+bar&bam+baz=foo');
var data = u.query(true);

equal(data.bar, 'foo bar', "value un-spac-escaped");
equal(data['bam baz'], 'foo', "name un-spac-escaped");

u.escapeQuerySpace(false);
data = u.query(true);
equal(data.bar, 'foo+bar', "value not un-spac-escaped");
equal(data['bam+baz'], 'foo', "name not un-spac-escaped");

u.escapeQuerySpace(true);
data = u.query(true);

equal(data.bar, 'foo bar', "value un-spac-escaped again");
equal(data['bam baz'], 'foo', "name un-spac-escaped again");

u.escapeQuerySpace(false);

u.addQuery('alpha bravo', 'charlie delta');
equal(u.toString(), '?bar=foo%2Bbar&bam%2Bbaz=foo&alpha%20bravo=charlie%20delta', 'serialized un/escaped space');

URI.escapeQuerySpace = false;
u = new URI('?bar=foo+bar&bam+baz=foo');
data = u.query(true);
equal(data.bar, 'foo+bar', "value not un-spac-escaped by default");
equal(data['bam+baz'], 'foo', "name not un-spac-escaped by default");

// reset
URI.escapeQuerySpace = true;
});
test("hasQuery", function() {
var u = URI('?string=bar&list=one&list=two&number=123&null&empty=');

Expand Down

0 comments on commit b6ec1b6

Please sign in to comment.