Bug 1205137 - Add a PushSubscription serializer. r=mt,smaug

This commit is contained in:
Kit Cambridge 2015-12-14 16:28:19 -08:00
parent 203f21c771
commit 28dd80201b
6 changed files with 151 additions and 1 deletions

View File

@ -6,6 +6,7 @@
#include "mozilla/dom/PushManager.h"
#include "mozilla/Base64.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/unused.h"
@ -65,6 +66,26 @@ GetPermissionState(nsIPrincipal* aPrincipal,
return NS_OK;
}
void
SubscriptionToJSON(PushSubscriptionJSON& aJSON, const nsString& aEndpoint,
const nsTArray<uint8_t>& aRawP256dhKey,
const nsTArray<uint8_t>& aAuthSecret)
{
aJSON.mEndpoint.Construct();
aJSON.mEndpoint.Value() = aEndpoint;
aJSON.mKeys.mP256dh.Construct();
nsresult rv = Base64URLEncode(aRawP256dhKey.Length(),
aRawP256dhKey.Elements(),
aJSON.mKeys.mP256dh.Value());
Unused << NS_WARN_IF(NS_FAILED(rv));
aJSON.mKeys.mAuth.Construct();
rv = Base64URLEncode(aAuthSecret.Length(), aAuthSecret.Elements(),
aJSON.mKeys.mAuth.Value());
Unused << NS_WARN_IF(NS_FAILED(rv));
}
} // anonymous namespace
class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback
@ -123,6 +144,12 @@ PushSubscription::Unsubscribe(ErrorResult& aRv)
return p.forget();
}
void
PushSubscription::ToJSON(PushSubscriptionJSON& aJSON)
{
SubscriptionToJSON(aJSON, mEndpoint, mRawP256dhKey, mAuthSecret);
}
PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
const nsAString& aEndpoint,
const nsAString& aScope,
@ -507,6 +534,12 @@ WorkerPushSubscription::Unsubscribe(ErrorResult &aRv)
return p.forget();
}
void
WorkerPushSubscription::ToJSON(PushSubscriptionJSON& aJSON)
{
SubscriptionToJSON(aJSON, mEndpoint, mRawP256dhKey, mAuthSecret);
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerPushSubscription)
NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerPushSubscription)

View File

@ -104,6 +104,9 @@ public:
already_AddRefed<Promise>
Unsubscribe(ErrorResult& aRv);
void
ToJSON(PushSubscriptionJSON& aJSON);
protected:
~PushSubscription();
@ -197,6 +200,9 @@ public:
already_AddRefed<Promise>
Unsubscribe(ErrorResult& aRv);
void
ToJSON(PushSubscriptionJSON& aJSON);
protected:
~WorkerPushSubscription();

View File

@ -77,6 +77,47 @@ http://creativecommons.org/licenses/publicdomain/
});
}
function base64UrlDecode(s) {
s = s.replace(/-/g, '+').replace(/_/g, '/');
// Replace padding if it was stripped by the sender.
// See http://tools.ietf.org/html/rfc4648#section-4
switch (s.length % 4) {
case 0:
break; // No pad chars in this case
case 2:
s += '==';
break; // Two pad chars
case 3:
s += '=';
break; // One pad char
default:
throw new Error('Illegal base64url string!');
}
// With correct padding restored, apply the standard base64 decoder
var decoded = atob(s);
var array = new Uint8Array(new ArrayBuffer(decoded.length));
for (var i = 0; i < decoded.length; i++) {
array[i] = decoded.charCodeAt(i);
}
return array;
}
add_task(function* compareJSONSubscription() {
var json = pushSubscription.toJSON();
is(json.endpoint, pushSubscription.endpoint, "Wrong endpoint");
["p256dh", "auth"].forEach(keyName => {
isDeeply(
base64UrlDecode(json.keys[keyName]),
new Uint8Array(pushSubscription.getKey(keyName)),
"Mismatched Base64-encoded key: " + keyName
);
});
});
add_task(function* comparePublicKey() {
var data = yield sendRequestToWorker({ type: "publicKey" });
var p256dhKey = new Uint8Array(pushSubscription.getKey("p256dh"));

View File

@ -15,6 +15,18 @@ enum PushEncryptionKeyName
"auth"
};
dictionary PushSubscriptionKeys
{
ByteString p256dh;
ByteString auth;
};
dictionary PushSubscriptionJSON
{
USVString endpoint;
PushSubscriptionKeys keys;
};
[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
ChromeConstructor(DOMString pushEndpoint, DOMString scope,
ArrayBuffer? key, ArrayBuffer? authSecret)]
@ -24,7 +36,9 @@ interface PushSubscription
ArrayBuffer? getKey(PushEncryptionKeyName name);
[Throws, UseCounter]
Promise<boolean> unsubscribe();
jsonifier;
// Implements the custom serializer specified in Push API, section 9.
PushSubscriptionJSON toJSON();
// Used to set the principal from the JS implemented PushManager.
[Exposed=Window,ChromeOnly]

View File

@ -225,6 +225,9 @@ EncodeInputStream(nsIInputStream* aInputStream,
return NS_OK;
}
static const char kBase64URLAlphabet[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
} // namespace
namespace mozilla {
@ -355,4 +358,49 @@ Base64Decode(const nsAString& aBinaryData, nsAString& aString)
return rv;
}
nsresult
Base64URLEncode(uint32_t aLength, const uint8_t* aData, nsACString& aString)
{
// Don't encode empty strings.
if (aLength == 0) {
aString.Truncate();
return NS_OK;
}
// Check for overflow.
if ((static_cast<uint64_t>(aLength) * 6 + 7) / 8 > UINT32_MAX) {
return NS_ERROR_FAILURE;
}
if (!aString.SetLength((aLength * 8 + 5) / 6, fallible)) {
aString.Truncate();
return NS_ERROR_FAILURE;
}
char* rawBuffer = aString.BeginWriting();
uint32_t index = 0;
for (; index + 3 <= aLength; index += 3) {
*rawBuffer++ = kBase64URLAlphabet[aData[index] >> 2];
*rawBuffer++ = kBase64URLAlphabet[((aData[index] & 0x3) << 4) |
(aData[index + 1] >> 4)];
*rawBuffer++ = kBase64URLAlphabet[((aData[index + 1] & 0xf) << 2) |
(aData[index + 2] >> 6)];
*rawBuffer++ = kBase64URLAlphabet[aData[index + 2] & 0x3f];
}
uint32_t remaining = aLength - index;
if (remaining == 1) {
*rawBuffer++ = kBase64URLAlphabet[aData[index] >> 2];
*rawBuffer++ = kBase64URLAlphabet[((aData[index] & 0x3) << 4)];
} else if (remaining == 2) {
*rawBuffer++ = kBase64URLAlphabet[aData[index] >> 2];
*rawBuffer++ = kBase64URLAlphabet[((aData[index] & 0x3) << 4) |
(aData[index + 1] >> 4)];
*rawBuffer++ = kBase64URLAlphabet[((aData[index + 1] & 0xf) << 2)];
}
return NS_OK;
}
} // namespace mozilla

View File

@ -34,6 +34,14 @@ Base64Decode(const nsACString& aBinaryData, nsACString& aString);
nsresult
Base64Decode(const nsAString& aBinaryData, nsAString& aString);
/**
* Converts |aData| to an unpadded, Base64 URL-encoded string per RFC 4648.
* Aims to encode the data in constant time. The caller may free |aData| once
* this function returns.
*/
nsresult
Base64URLEncode(uint32_t aLength, const uint8_t* aData, nsACString& aString);
} // namespace mozilla
#endif