Bug 1243594 (part 2) - have rest.js automatically encode the request body as utf-8. r=gfritzsche

This commit is contained in:
Mark Hammond 2016-02-26 15:46:30 +11:00
parent 430ef188dc
commit 32d720116b
3 changed files with 97 additions and 9 deletions

View File

@ -322,9 +322,28 @@ RESTRequest.prototype = {
// Set HTTP request body.
if (method == "PUT" || method == "POST" || method == "PATCH") {
// Convert non-string bodies into JSON.
// Convert non-string bodies into JSON with utf-8 encoding. If a string
// is passed we assume they've already encoded it.
let contentType = headers["content-type"];
if (typeof data != "string") {
data = JSON.stringify(data);
if (!contentType) {
contentType = "application/json";
}
if (!contentType.includes("charset")) {
data = CommonUtils.encodeUTF8(data);
contentType += "; charset=utf-8";
} else {
// If someone handed us an object but also a custom content-type
// it's probably confused. We could go to even further lengths to
// respect it, but this shouldn't happen in practice.
Cu.reportError("rest.js found an object to JSON.stringify but also a " +
"content-type header with a charset specification. " +
"This probably isn't going to do what you expect");
}
}
if (!contentType) {
contentType = "text/plain";
}
this._log.debug(method + " Length: " + data.length);
@ -336,9 +355,8 @@ RESTRequest.prototype = {
.createInstance(Ci.nsIStringInputStream);
stream.setData(data, data.length);
let type = headers["content-type"] || "text/plain";
channel.QueryInterface(Ci.nsIUploadChannel);
channel.setUploadStream(stream, type, data.length);
channel.setUploadStream(stream, contentType, data.length);
}
// We must set this after setting the upload stream, otherwise it
// will always be 'PUT'. Yeah, I know.

View File

@ -220,6 +220,40 @@ add_test(function test_get_utf8() {
});
});
/**
* Test HTTP POST data is encoded as UTF-8 by default.
*/
add_test(function test_post_utf8() {
// We setup a handler that responds with exactly what it received.
// Given we've already tested above that responses are correctly utf-8
// decoded we can surmise that the correct response coming back means the
// input must also have been encoded.
let server = httpd_setup({"/echo": function(req, res) {
res.setStatusLine(req.httpVersion, 200, "OK");
res.setHeader("Content-Type", req.getHeader("content-type"));
// Get the body as bytes and write them back without touching them
let sis = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
sis.init(req.bodyInputStream);
let body = sis.read(sis.available());
sis.close()
res.write(body);
}});
let data = {copyright: "\xa9"}; // \xa9 is the copyright symbol
let request1 = new RESTRequest(server.baseURI + "/echo");
request1.post(data, function(error) {
do_check_null(error);
do_check_eq(request1.response.status, 200);
deepEqual(JSON.parse(request1.response.body), data);
do_check_eq(request1.response.headers["content-type"],
"application/json; charset=utf-8")
server.stop(run_next_test);
});
});
/**
* Test more variations of charset handling.
*/
@ -431,7 +465,7 @@ add_test(function test_put_json() {
do_check_eq(handler.request.method, "PUT");
do_check_eq(handler.request.body, JSON.stringify(sample_data));
do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
do_check_eq(handler.request.getHeader("Content-Type"), "application/json; charset=utf-8");
server.stop(run_next_test);
});
@ -461,6 +495,32 @@ add_test(function test_post_json() {
do_check_eq(handler.request.method, "POST");
do_check_eq(handler.request.body, JSON.stringify(sample_data));
do_check_eq(handler.request.getHeader("Content-Type"), "application/json; charset=utf-8");
server.stop(run_next_test);
});
});
/**
* The content-type will be text/plain without a charset if the 'data' argument
* to POST is already a string.
*/
add_test(function test_post_json() {
let handler = httpd_handler(200, "OK");
let server = httpd_setup({"/resource": handler});
let sample_data = "hello";
let request = new RESTRequest(server.baseURI + "/resource");
request.post(sample_data, function (error) {
do_check_eq(error, null);
do_check_eq(this.status, this.COMPLETED);
do_check_true(this.response.success);
do_check_eq(this.response.status, 200);
do_check_eq(this.response.body, "");
do_check_eq(handler.request.method, "POST");
do_check_eq(handler.request.body, sample_data);
do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
server.stop(run_next_test);

View File

@ -173,9 +173,13 @@ add_task(function* test_signUp() {
let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"});
let created = false;
// Note these strings must be unicode and not already utf-8 encoded.
let unicodeUsername = "andr\xe9@example.org"; // 'andré@example.org'
let unicodePassword = "p\xe4ssw\xf6rd"; // 'pässwörd'
let server = httpd_setup({
"/account/create": function(request, response) {
let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
body = CommonUtils.decodeUTF8(body);
let jsonBody = JSON.parse(body);
// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
@ -187,7 +191,7 @@ add_task(function* test_signUp() {
return;
}
if (jsonBody.email == "andré@example.org") {
if (jsonBody.email == unicodeUsername) {
do_check_eq("", request._queryString);
do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375");
@ -207,12 +211,15 @@ add_task(function* test_signUp() {
creationMessage_withKey.length);
return;
}
// just throwing here doesn't make any log noise, so have an assertion
// fail instead.
do_check_true(false, "unexpected email: " + jsonBody.email);
},
});
// Try to create an account without retrieving optional keys.
let client = new FxAccountsClient(server.baseURI);
let result = yield client.signUp('andré@example.org', 'pässwörd');
let result = yield client.signUp(unicodeUsername, unicodePassword);
do_check_eq("uid", result.uid);
do_check_eq("sessionToken", result.sessionToken);
do_check_eq(undefined, result.keyFetchToken);
@ -229,7 +236,7 @@ add_task(function* test_signUp() {
// Try to create an existing account. Triggers error path.
try {
result = yield client.signUp('andré@example.org', 'pässwörd');
result = yield client.signUp(unicodeUsername, unicodePassword);
do_throw("Expected to catch an exception");
} catch(expectedError) {
do_check_eq(101, expectedError.errno);
@ -258,12 +265,15 @@ add_task(function* test_signIn() {
email: "you@example.com"
});
// Note this strings must be unicode and not already utf-8 encoded.
let unicodeUsername = "m\xe9@example.com" // 'mé@example.com'
let server = httpd_setup({
"/account/login": function(request, response) {
let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
body = CommonUtils.decodeUTF8(body);
let jsonBody = JSON.parse(body);
if (jsonBody.email == "mé@example.com") {
if (jsonBody.email == unicodeUsername) {
do_check_eq("", request._queryString);
do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6");
response.setStatusLine(request.httpVersion, 200, "OK");
@ -298,7 +308,7 @@ add_task(function* test_signIn() {
// Login without retrieving optional keys
let client = new FxAccountsClient(server.baseURI);
let result = yield client.signIn('mé@example.com', 'bigsecret');
let result = yield client.signIn(unicodeUsername, 'bigsecret');
do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
do_check_eq(result.unwrapBKey,
"c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8");