Bug 1419658 - let Basic http auth support ISO-8859-1 user/password, r=honzab
authorJunior Hsu <juhsu@mozilla.com>
Thu, 07 Dec 2017 01:31:00 +0200
changeset 395481 f6f90940e4173b4b85b6f76b1de01b0cb29573dd
parent 395480 9f2ab0c452336167d42bb8931e2b9fba930eed89
child 395482 c5fced4aa8fd5bb49c1ea78c752ccd7509b73cc6
push id98114
push userbtara@mozilla.com
push dateThu, 07 Dec 2017 13:46:14 +0000
treeherdermozilla-inbound@f6f90940e417 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs1419658
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1419658 - let Basic http auth support ISO-8859-1 user/password, r=honzab
netwerk/protocol/http/nsHttpBasicAuth.cpp
netwerk/test/unit/test_authentication.js
--- a/netwerk/protocol/http/nsHttpBasicAuth.cpp
+++ b/netwerk/protocol/http/nsHttpBasicAuth.cpp
@@ -2,19 +2,19 @@
 /* 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/. */
 
 // HttpLog.h should generally be included first
 #include "HttpLog.h"
 
 #include "nsHttpBasicAuth.h"
-#include "plbase64.h"
 #include "plstr.h"
 #include "nsString.h"
+#include "mozilla/Base64.h"
 
 namespace mozilla {
 namespace net {
 
 //-----------------------------------------------------------------------------
 // nsHttpBasicAuth <public>
 //-----------------------------------------------------------------------------
 
@@ -82,31 +82,31 @@ nsHttpBasicAuth::GenerateCredentials(nsI
     NS_ENSURE_ARG_POINTER(creds);
 
     *aFlags = 0;
 
     // we only know how to deal with Basic auth for http.
     bool isBasicAuth = !PL_strncasecmp(challenge, "basic", 5);
     NS_ENSURE_TRUE(isBasicAuth, NS_ERROR_UNEXPECTED);
 
-    // we work with ASCII around here
+    // we work with UTF-8 around here
     nsAutoCString userpass;
-    LossyCopyUTF16toASCII(user, userpass);
+    CopyUTF16toUTF8(user, userpass);
     userpass.Append(':'); // always send a ':' (see bug 129565)
-    if (password)
-        LossyAppendUTF16toASCII(password, userpass);
+    if (password) {
+        AppendUTF16toUTF8(password, userpass);
+    }
 
-    // plbase64.h provides this worst-case output buffer size calculation.
-    // use calloc, since PL_Base64Encode does not null terminate.
-    *creds = (char *) calloc(6 + ((userpass.Length() + 2)/3)*4 + 1, 1);
-    if (!*creds)
-        return NS_ERROR_OUT_OF_MEMORY;
+    nsAutoCString authString;
+    nsresult rv = Base64Encode(userpass, authString);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-    memcpy(*creds, "Basic ", 6);
-    PL_Base64Encode(userpass.get(), userpass.Length(), *creds + 6);
+    authString.InsertLiteral("Basic ", 0);
+
+    *creds = ToNewCString(authString);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHttpBasicAuth::GetAuthFlags(uint32_t *flags)
 {
     *flags = REQUEST_BASED | REUSABLE_CREDENTIALS | REUSABLE_CHALLENGE;
     return NS_OK;
--- a/netwerk/test/unit/test_authentication.js
+++ b/netwerk/test/unit/test_authentication.js
@@ -18,16 +18,17 @@ XPCOMUtils.defineLazyGetter(this, "PORT"
 });
 
 const FLAG_RETURN_FALSE   = 1 << 0;
 const FLAG_WRONG_PASSWORD = 1 << 1;
 const FLAG_BOGUS_USER = 1 << 2;
 const FLAG_PREVIOUS_FAILED = 1 << 3;
 const CROSS_ORIGIN = 1 << 4;
 const FLAG_NO_REALM = 1 << 5;
+const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6;
 
 const nsIAuthPrompt2 = Components.interfaces.nsIAuthPrompt2;
 const nsIAuthInformation = Components.interfaces.nsIAuthInformation;
 
 
 function AuthPrompt1(flags) {
   this.flags = flags;
 }
@@ -70,24 +71,29 @@ AuthPrompt1.prototype = {
     if (text.indexOf(String(PORT)) == -1)
       do_throw("Text must indicate the port");
     if (text.indexOf("-1") != -1)
       do_throw("Text must contain negative numbers");
 
     if (this.flags & FLAG_RETURN_FALSE)
       return false;
 
-    if (this.flags & FLAG_BOGUS_USER)
+    if (this.flags & FLAG_BOGUS_USER) {
       this.user = "foo\nbar";
+    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+      this.user = "é";
+    }
 
     user.value = this.user;
     if (this.flags & FLAG_WRONG_PASSWORD) {
       pw.value = this.pass + ".wrong";
       // Now clear the flag to avoid an infinite loop
       this.flags &= ~FLAG_WRONG_PASSWORD;
+    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+      pw.value = "é";
     } else {
       pw.value = this.pass;
     }
     return true;
   },
 
   promptPassword: function ap1_promptPW(title, text, realm, save, pwd) {
     do_throw("unexpected promptPassword call");
@@ -135,42 +141,47 @@ AuthPrompt2.prototype = {
       expectedFlags |= nsIAuthInformation.PREVIOUS_FAILED;
 
     if (this.flags & CROSS_ORIGIN)
       expectedFlags |= nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE;
 
     if (isNTLM)
       expectedFlags |= nsIAuthInformation.NEED_DOMAIN;
 
-    const kAllKnownFlags = 63; // Don't fail test for newly added flags
+    const kAllKnownFlags = 127; // Don't fail test for newly added flags
     do_check_eq(expectedFlags, authInfo.flags & kAllKnownFlags);
 
     var expectedScheme = isNTLM ? "ntlm" : isDigest ? "digest" : "basic";
     do_check_eq(expectedScheme, authInfo.authenticationScheme);
 
     // No passwords in the URL -> nothing should be prefilled
     do_check_eq(authInfo.username, "");
     do_check_eq(authInfo.password, "");
     do_check_eq(authInfo.domain, "");
 
     if (this.flags & FLAG_RETURN_FALSE)
     {
       this.flags |= FLAG_PREVIOUS_FAILED;
       return false;
     }
 
-    if (this.flags & FLAG_BOGUS_USER)
+    if (this.flags & FLAG_BOGUS_USER) {
       this.user = "foo\nbar";
+    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+      this.user = "é";
+    }
 
     authInfo.username = this.user;
     if (this.flags & FLAG_WRONG_PASSWORD) {
       authInfo.password = this.pass + ".wrong";
       this.flags |= FLAG_PREVIOUS_FAILED;
       // Now clear the flag to avoid an infinite loop
       this.flags &= ~FLAG_WRONG_PASSWORD;
+    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
+      authInfo.password = "é";
     } else {
       authInfo.password = this.pass;
       this.flags &= ~FLAG_PREVIOUS_FAILED;
     }
     return true;
   },
 
   asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
@@ -300,30 +311,31 @@ function makeChan(url, loadingUrl) {
       securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
       contentPolicyType: Components.interfaces.nsIContentPolicy.TYPE_OTHER
     });
 }
 
 var tests = [test_noauth, test_returnfalse1, test_wrongpw1, test_prompt1,
              test_prompt1CrossOrigin, test_prompt2CrossOrigin,
              test_returnfalse2, test_wrongpw2, test_prompt2, test_ntlm,
-             test_basicrealm, test_digest_noauth, test_digest,
+             test_basicrealm, test_nonascii, test_digest_noauth, test_digest,
              test_digest_bogus_user, test_short_digest, test_large_realm,
              test_large_domain];
 
 var current_test = 0;
 
 var httpserv = null;
 
 function run_test() {
   httpserv = new HttpServer();
 
   httpserv.registerPathHandler("/auth", authHandler);
   httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
   httpserv.registerPathHandler("/auth/realm", authRealm);
+  httpserv.registerPathHandler("/auth/non_ascii", authNonascii);
   httpserv.registerPathHandler("/auth/digest", authDigest);
   httpserv.registerPathHandler("/auth/short_digest", authShortDigest);
   httpserv.registerPathHandler("/largeRealm", largeRealm);
   httpserv.registerPathHandler("/largeDomain", largeDomain);
 
   httpserv.start(-1);
 
   tests[0]();
@@ -433,16 +445,26 @@ function test_basicrealm() {
 
   chan.notificationCallbacks = new RealmTestRequestor();
   listener.expectedCode = 401; // Unauthorized
   chan.asyncOpen2(listener);
 
   do_test_pending();
 }
 
+function test_nonascii() {
+  var chan = makeChan(URL + "/auth/non_ascii", URL);
+
+  chan.notificationCallbacks = new Requestor(FLAG_NON_ASCII_USER_PASSWORD, 2);
+  listener.expectedCode = 200; // OK
+  chan.asyncOpen2(listener);
+
+  do_test_pending();
+}
+
 function test_digest_noauth() {
   var chan = makeChan(URL + "/auth/digest", URL);
 
   //chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
   listener.expectedCode = 401; // Unauthorized
   chan.asyncOpen2(listener);
 
   do_test_pending();
@@ -522,16 +544,42 @@ function authNtlmSimple(metadata, respon
 function authRealm(metadata, response) {
   response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
   response.setHeader("WWW-Authenticate", 'Basic realm="\\"f\\oo_bar"', false);
   var body = "success";
 
   response.bodyOutputStream.write(body, body.length);
 }
 
+// /auth/nonAscii
+function authNonascii(metadata, response) {
+  // btoa("é:é"), but that function is not available here
+  var expectedHeader = "Basic w6k6w6k=";
+
+  var body;
+  if (metadata.hasHeader("Authorization") &&
+      metadata.getHeader("Authorization") == expectedHeader)
+  {
+    response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
+    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+    body = "success";
+  }
+  else
+  {
+    // didn't know é:é, failure
+    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
+    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+
+    body = "failed";
+  }
+
+  response.bodyOutputStream.write(body, body.length);
+}
+
 //
 // Digest functions
 // 
 function bytesFromString(str) {
  var converter =
    Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
      .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";