Skip to content

Commit 7f8d7cd

Browse files
author
Bart Koelman
committed
Makes the hashing algorithm used for ETags pluggable.
1 parent a552602 commit 7f8d7cd

File tree

6 files changed

+80
-28
lines changed

6 files changed

+80
-28
lines changed

src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ private void AddSerializationLayer()
285285
_services.AddScoped(typeof(AtomicOperationsResponseSerializer));
286286
_services.AddScoped(sp => sp.GetRequiredService<IJsonApiSerializerFactory>().GetSerializer());
287287
_services.AddScoped<IResourceObjectBuilder, ResponseResourceObjectBuilder>();
288+
_services.AddSingleton<IFingerprintGenerator, FingerprintGenerator>();
288289
_services.AddSingleton<IETagGenerator, ETagGenerator>();
289290
}
290291

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,26 @@
1-
using System.Linq;
2-
using System.Security.Cryptography;
3-
using System.Text;
41
using Microsoft.Net.Http.Headers;
52

63
namespace JsonApiDotNetCore.Serialization
74
{
85
/// <inheritdoc />
96
internal sealed class ETagGenerator : IETagGenerator
107
{
11-
private static readonly uint[] LookupTable = Enumerable.Range(0, 256).Select(ToLookupEntry).ToArray();
8+
private readonly IFingerprintGenerator _fingerprintGenerator;
129

13-
private static uint ToLookupEntry(int index)
10+
public ETagGenerator(IFingerprintGenerator fingerprintGenerator)
1411
{
15-
string hex = index.ToString("X2");
16-
return hex[0] + ((uint)hex[1] << 16);
12+
ArgumentGuard.NotNull(fingerprintGenerator, nameof(fingerprintGenerator));
13+
14+
_fingerprintGenerator = fingerprintGenerator;
1715
}
1816

1917
/// <inheritdoc />
2018
public EntityTagHeaderValue Generate(string requestUrl, string responseBody)
2119
{
22-
byte[] buffer = Encoding.UTF8.GetBytes(requestUrl + "|" + responseBody);
23-
24-
using HashAlgorithm hashAlgorithm = MD5.Create();
25-
byte[] hash = hashAlgorithm.ComputeHash(buffer);
20+
string fingerprint = _fingerprintGenerator.Generate(ArrayFactory.Create(requestUrl, responseBody));
21+
string eTagValue = "\"" + fingerprint + "\"";
2622

27-
string eTagValue = "\"" + ByteArrayToHex(hash) + "\"";
2823
return EntityTagHeaderValue.Parse(eTagValue);
2924
}
30-
31-
// https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa
32-
private static string ByteArrayToHex(byte[] bytes)
33-
{
34-
char[] buffer = new char[bytes.Length * 2];
35-
36-
for (int index = 0; index < bytes.Length; index++)
37-
{
38-
uint value = LookupTable[bytes[index]];
39-
buffer[2 * index] = (char)value;
40-
buffer[2 * index + 1] = (char)(value >> 16);
41-
}
42-
43-
return new string(buffer);
44-
}
4525
}
4626
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Security.Cryptography;
4+
using System.Text;
5+
6+
namespace JsonApiDotNetCore.Serialization
7+
{
8+
/// <inheritdoc />
9+
internal sealed class FingerprintGenerator : IFingerprintGenerator
10+
{
11+
private static readonly byte[] Separator = Encoding.UTF8.GetBytes("|");
12+
private static readonly uint[] LookupTable = Enumerable.Range(0, 256).Select(ToLookupEntry).ToArray();
13+
14+
private static uint ToLookupEntry(int index)
15+
{
16+
string hex = index.ToString("X2");
17+
return hex[0] + ((uint)hex[1] << 16);
18+
}
19+
20+
/// <inheritdoc />
21+
public string Generate(IEnumerable<string> elements)
22+
{
23+
ArgumentGuard.NotNull(elements, nameof(elements));
24+
25+
using var hasher = IncrementalHash.CreateHash(HashAlgorithmName.MD5);
26+
27+
foreach (string element in elements)
28+
{
29+
byte[] buffer = Encoding.UTF8.GetBytes(element);
30+
hasher.AppendData(buffer);
31+
hasher.AppendData(Separator);
32+
}
33+
34+
byte[] hash = hasher.GetHashAndReset();
35+
return ByteArrayToHex(hash);
36+
}
37+
38+
private static string ByteArrayToHex(byte[] bytes)
39+
{
40+
// https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa
41+
42+
char[] buffer = new char[bytes.Length * 2];
43+
44+
for (int index = 0; index < bytes.Length; index++)
45+
{
46+
uint value = LookupTable[bytes[index]];
47+
buffer[2 * index] = (char)value;
48+
buffer[2 * index + 1] = (char)(value >> 16);
49+
}
50+
51+
return new string(buffer);
52+
}
53+
}
54+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Collections.Generic;
2+
3+
namespace JsonApiDotNetCore.Serialization
4+
{
5+
/// <summary>
6+
/// Provides a method to generate a fingerprint for a collection of string values.
7+
/// </summary>
8+
public interface IFingerprintGenerator
9+
{
10+
/// <summary>
11+
/// Generates a fingerprint for the specified elements.
12+
/// </summary>
13+
public string Generate(IEnumerable<string> elements);
14+
}
15+
}

src/JsonApiDotNetCore/Serialization/JsonApiReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
8686

8787
private async Task<string> GetRequestBodyAsync(Stream bodyStream)
8888
{
89-
using var reader = new StreamReader(bodyStream);
89+
using var reader = new StreamReader(bodyStream, leaveOpen: true);
9090
return await reader.ReadToEndAsync();
9191
}
9292

test/TestBuildingBlocks/IntegrationTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Net.Http;
33
using System.Net.Http.Headers;
4+
using System.Text;
45
using System.Threading.Tasks;
56
using JsonApiDotNetCore.Middleware;
67
using Newtonsoft.Json;
@@ -60,6 +61,7 @@ public abstract class IntegrationTest
6061
{
6162
requestText = requestText.Replace("atomic__", "atomic:");
6263
request.Content = new StringContent(requestText);
64+
request.Content.Headers.ContentLength = Encoding.UTF8.GetByteCount(requestText);
6365

6466
if (contentType != null)
6567
{

0 commit comments

Comments
 (0)