Skip to content

Commit e450226

Browse files
committed
Added a friendly exception for invalid encryption keys
Also fixed a StackOverflow error in WebPushClient.SendNotification
1 parent 77eb971 commit e450226

File tree

3 files changed

+61
-16
lines changed

3 files changed

+61
-16
lines changed

WebPush.Test/WebPushClientTest.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Net;
77
using System.Net.Http;
8+
using WebPush.Model;
89

910
namespace WebPush.Test
1011
{
@@ -73,7 +74,8 @@ public void TestSetGcmApiKey()
7374
[TestMethod]
7475
public void TestSetGCMAPIKeyEmptyString()
7576
{
76-
Assert.ThrowsException<ArgumentException>(delegate { client.SetGcmApiKey(""); });
77+
Assert.ThrowsException<ArgumentException>(delegate
78+
{ client.SetGcmApiKey(""); });
7779
}
7880

7981
[TestMethod]
@@ -85,8 +87,7 @@ public void TestSetGcmApiKeyNonGcmPushService()
8587
var subscription = new PushSubscription(TestFirefoxEndpoint, TestPublicKey, TestPrivateKey);
8688
var message = client.GenerateRequestDetails(subscription, @"test payload");
8789

88-
IEnumerable<string> values;
89-
Assert.IsFalse(message.Headers.TryGetValues(@"Authorization", out values));
90+
Assert.IsFalse(message.Headers.TryGetValues(@"Authorization", out var values));
9091
}
9192

9293
[TestMethod]
@@ -98,15 +99,14 @@ public void TestSetGcmApiKeyNull()
9899
var subscription = new PushSubscription(TestGcmEndpoint, TestPublicKey, TestPrivateKey);
99100
var message = client.GenerateRequestDetails(subscription, @"test payload");
100101

101-
IEnumerable<string> values;
102-
Assert.IsFalse(message.Headers.TryGetValues("Authorization", out values));
102+
Assert.IsFalse(message.Headers.TryGetValues("Authorization", out var values));
103103
}
104104

105105
[TestMethod]
106106
public void TestSetVapidDetails()
107107
{
108108
client.SetVapidDetails(TestSubject, TestPublicKey, TestPrivateKey);
109-
109+
110110
var subscription = new PushSubscription(TestFirefoxEndpoint, TestPublicKey, TestPrivateKey);
111111
var message = client.GenerateRequestDetails(subscription, @"test payload");
112112
var authorizationHeader = message.Headers.GetValues(@"Authorization").First();
@@ -161,9 +161,21 @@ public void TestHandlingFailureMessages(HttpStatusCode status, string response,
161161
Assert.AreEqual(expectedMessage, actual.Message);
162162
}
163163

164-
private void TestSendNotification(HttpStatusCode status, string response=null)
164+
[TestMethod]
165+
[DataRow(1)]
166+
[DataRow(5)]
167+
[DataRow(10)]
168+
[DataRow(50)]
169+
public void TestHandleInvalidPublicKeys(int charactersToDrop)
165170
{
166-
var subscription = new PushSubscription(TestFcmEndpoint, TestPublicKey, TestPrivateKey);
171+
var invalidKey = TestPublicKey.Substring(0, TestPublicKey.Length - charactersToDrop);
172+
173+
Assert.ThrowsException<InvalidEncryptionDetailsException>(() => TestSendNotification(HttpStatusCode.OK, response: null, invalidKey));
174+
}
175+
176+
private void TestSendNotification(HttpStatusCode status, string response = null, string publicKey = TestPublicKey)
177+
{
178+
var subscription = new PushSubscription(TestFcmEndpoint, publicKey, TestPrivateKey);
167179
var httpContent = response == null ? null : new StringContent(response);
168180
httpMessageHandlerMock.When(TestFcmEndpoint).Respond(new HttpResponseMessage { StatusCode = status, Content = httpContent });
169181
client.SetVapidDetails(TestSubject, TestPublicKey, TestPrivateKey);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace WebPush.Model
4+
{
5+
public class InvalidEncryptionDetailsException : Exception
6+
{
7+
public InvalidEncryptionDetailsException(string message, PushSubscription pushSubscription)
8+
: base(message)
9+
{
10+
PushSubscription = pushSubscription;
11+
}
12+
13+
public PushSubscription PushSubscription { get; }
14+
}
15+
}

WebPush/WebPushClient.cs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.CompilerServices;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using WebPush.Model;
910
using WebPush.Util;
1011

1112
[assembly: InternalsVisibleTo("WebPush.Test")]
@@ -202,7 +203,7 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription,
202203
@"Unable to send a message with payload to this subscription since it doesn't have the required encryption key");
203204
}
204205

205-
var encryptedPayload = Encryptor.Encrypt(subscription.P256DH, subscription.Auth, payload);
206+
var encryptedPayload = EncryptPayload(subscription, payload);
206207

207208
request.Content = new ByteArrayContent(encryptedPayload.Payload);
208209
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
@@ -243,7 +244,8 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription,
243244
{
244245
cryptoKeyHeader += @";" + vapidHeaders["Crypto-Key"];
245246
}
246-
} else if (isFcm && !string.IsNullOrEmpty(currentGcmApiKey))
247+
}
248+
else if (isFcm && !string.IsNullOrEmpty(currentGcmApiKey))
247249
{
248250
request.Headers.TryAddWithoutValidation("Authorization", "key=" + currentGcmApiKey);
249251
}
@@ -252,6 +254,23 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription,
252254
return request;
253255
}
254256

257+
private static EncryptionResult EncryptPayload(PushSubscription subscription, string payload)
258+
{
259+
try
260+
{
261+
return Encryptor.Encrypt(subscription.P256DH, subscription.Auth, payload);
262+
}
263+
catch (Exception ex)
264+
{
265+
if (ex is FormatException || ex is ArgumentException)
266+
{
267+
throw new InvalidEncryptionDetailsException("Unable to encrypt the payload with the encryption key of this subscription.", subscription);
268+
}
269+
270+
throw;
271+
}
272+
}
273+
255274
/// <summary>
256275
/// To send a push notification call this method with a subscription, optional payload and any options
257276
/// Will exception if unsuccessful
@@ -265,9 +284,8 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription,
265284
public void SendNotification(PushSubscription subscription, string payload = null,
266285
Dictionary<string, object> options = null)
267286
{
268-
SendNotification(subscription, payload, options);
287+
SendNotificationAsync(subscription, payload, options).GetAwaiter().GetResult();
269288
}
270-
271289

272290
/// <summary>
273291
/// To send a push notification call this method with a subscription, optional payload and any options
@@ -294,7 +312,7 @@ public void SendNotification(PushSubscription subscription, string payload, stri
294312
var options = new Dictionary<string, object> { ["gcmAPIKey"] = gcmApiKey };
295313
SendNotification(subscription, payload, options);
296314
}
297-
315+
298316

299317
/// <summary>
300318
/// To send a push notification asynchronous call this method with a subscription, optional payload and any options
@@ -308,7 +326,7 @@ public void SendNotification(PushSubscription subscription, string payload, stri
308326
/// </param>
309327
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
310328
public async Task SendNotificationAsync(PushSubscription subscription, string payload = null,
311-
Dictionary<string, object> options = null, CancellationToken cancellationToken=default)
329+
Dictionary<string, object> options = null, CancellationToken cancellationToken = default)
312330
{
313331
var request = GenerateRequestDetails(subscription, payload, options);
314332
var response = await HttpClient.SendAsync(request, cancellationToken);
@@ -325,7 +343,7 @@ public async Task SendNotificationAsync(PushSubscription subscription, string pa
325343
/// <param name="vapidDetails">The vapid details for the notification.</param>
326344
/// <param name="cancellationToken"></param>
327345
public async Task SendNotificationAsync(PushSubscription subscription, string payload,
328-
VapidDetails vapidDetails, CancellationToken cancellationToken=default)
346+
VapidDetails vapidDetails, CancellationToken cancellationToken = default)
329347
{
330348
var options = new Dictionary<string, object> { ["vapidDetails"] = vapidDetails };
331349
await SendNotificationAsync(subscription, payload, options, cancellationToken);
@@ -339,7 +357,7 @@ public async Task SendNotificationAsync(PushSubscription subscription, string pa
339357
/// <param name="payload">The payload you wish to send to the user</param>
340358
/// <param name="gcmApiKey">The GCM API key</param>
341359
/// <param name="cancellationToken"></param>
342-
public async Task SendNotificationAsync(PushSubscription subscription, string payload, string gcmApiKey, CancellationToken cancellationToken=default)
360+
public async Task SendNotificationAsync(PushSubscription subscription, string payload, string gcmApiKey, CancellationToken cancellationToken = default)
343361
{
344362
var options = new Dictionary<string, object> { ["gcmAPIKey"] = gcmApiKey };
345363
await SendNotificationAsync(subscription, payload, options, cancellationToken);

0 commit comments

Comments
 (0)