Skip to content

Commit

Permalink
fix: use existing query params from blobstore response (#136)
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Robinson <daniel.robinson@pebble.tv>
  • Loading branch information
daniel-pebble authored Aug 10, 2024
1 parent c58adc9 commit 1f939a5
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 57 deletions.
19 changes: 15 additions & 4 deletions src/OrasProject.Oras/Content/Digest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,37 @@

using OrasProject.Oras.Exceptions;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text.RegularExpressions;

namespace OrasProject.Oras.Content;

internal static class Digest
{
private const string digestRegexPattern = @"[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+";
private static readonly Regex digestRegex = new Regex(digestRegexPattern, RegexOptions.Compiled);
private const string _digestRegexPattern = @"[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+";
private static readonly Regex _digestRegex = new Regex(_digestRegexPattern, RegexOptions.Compiled);

// List of registered and supported algorithms as per the specification
private static readonly HashSet<string> _supportedAlgorithms = new HashSet<string> { "sha256", "sha512" };

/// <summary>
/// Verifies the digest header and throws an exception if it is invalid.
/// </summary>
/// <param name="digest"></param>
internal static string Validate(string? digest)
{
if (string.IsNullOrEmpty(digest) || !digestRegex.IsMatch(digest))
if (string.IsNullOrEmpty(digest) || !_digestRegex.IsMatch(digest))
{
throw new InvalidDigestException($"Invalid digest: {digest}");
}

var algorithm = digest.Split(':')[0];
if (!_supportedAlgorithms.Contains(algorithm))
{
throw new InvalidDigestException($"Unrecognized, unregistered or unsupported digest algorithm: {algorithm}");
}

return digest;
}

Expand All @@ -48,4 +59,4 @@ internal static string ComputeSHA256(byte[] content)
var output = $"sha256:{BitConverter.ToString(hash).Replace("-", "")}";
return output.ToLower();
}
}
}
2 changes: 1 addition & 1 deletion src/OrasProject.Oras/Registry/Remote/BlobStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public async Task PushAsync(Descriptor expected, Stream content, CancellationTok
// add digest key to query string with expected digest value
var req = new HttpRequestMessage(HttpMethod.Put, new UriBuilder(url)
{
Query = $"digest={HttpUtility.UrlEncode(expected.Digest)}"
Query = $"{url.Query}&digest={HttpUtility.UrlEncode(expected.Digest)}"
}.Uri);
req.Content = new StreamContent(content);
req.Content.Headers.ContentLength = expected.Size;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace OrasProject.Oras.Registry.Remote;

internal static class HttpResponseMessageExtensions
{
private const string _dockerContentDigestHeader = "Docker-Content-Digest";

/// <summary>
/// Parses the error returned by the remote registry.
/// </summary>
Expand Down Expand Up @@ -75,7 +77,7 @@ public static async Task<Exception> ParseErrorResponseAsync(this HttpResponseMes
/// <exception cref="NotImplementedException"></exception>
public static void VerifyContentDigest(this HttpResponseMessage response, string expected)
{
if (!response.Content.Headers.TryGetValues("Docker-Content-Digest", out var digestValues))
if (!response.Headers.TryGetValues(_dockerContentDigestHeader, out var digestValues))
{
return;
}
Expand Down Expand Up @@ -154,7 +156,7 @@ public static async Task<Descriptor> GenerateDescriptorAsync(this HttpResponseMe

// 4. Validate Server Digest (if present)
string? serverDigest = null;
if (response.Content.Headers.TryGetValues("Docker-Content-Digest", out var serverHeaderDigest))
if (response.Headers.TryGetValues(_dockerContentDigestHeader, out var serverHeaderDigest))
{
serverDigest = serverHeaderDigest.FirstOrDefault();
if (!string.IsNullOrEmpty(serverDigest))
Expand Down
37 changes: 37 additions & 0 deletions tests/OrasProject.Oras.Tests/Content/ContentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
// limitations under the License.

using OrasProject.Oras.Content;
using OrasProject.Oras.Exceptions;
using System.Text;
using Xunit;

namespace OrasProject.Oras.Tests.Content;

public class CalculateDigest
{

/// <summary>
/// This method tests if the digest is calculated properly
/// </summary>
Expand All @@ -30,4 +32,39 @@ public void VerifiesIfDigestMatches()
var calculateHelloWorldDigest = Digest.ComputeSHA256(content);
Assert.Equal(helloWorldDigest, calculateHelloWorldDigest);
}

/// <summary>
/// This method tests if the digest validation passes for registered algorithms
/// </summary>
[Theory]
[InlineData("sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b")]
[InlineData("sha512:401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b372742c513925d98f76b340d9e59a4efdc45db9f5c640a21831b3d08be")]
public void Validate_ReturnsDigest_ForRegisteredAlgorithms(string validDigest)
{
var result = Digest.Validate(validDigest);
Assert.Equal(validDigest, result);
}

/// <summary>
/// This method tests if the digest validation throws an exception for unregistered or unsupported algorithms
/// </summary>
[Theory]
[InlineData("md5:098f6bcd4621d373cade4e832627b4f6")] // MD5, unregistered digest
[InlineData("sha1:3b8b5a6b79f6d1114a7b7e95b3e3bc74dd1b6a2a")] // SHA-1, unregistered digest
[InlineData("multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8")] // Multihash, unregistered digest
public void Validate_ThrowsException_ForUnregisteredAlgorithms(string invalidDigest)
{
Assert.Throws<InvalidDigestException>(() => Digest.Validate(invalidDigest));
}

/// <summary>
/// This method tests if the digest validation throws an exception for various invalid digest formats
/// </summary>
[Theory]
[InlineData("sha256:")] // Missing encoded portion
[InlineData("sha256+b64u!LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564")] // Invalid character in encoded portion
public void Validate_ThrowsException_ForInvalidDigestFormats(string invalidDigest)
{
Assert.Throws<InvalidDigestException>(() => Digest.Validate(invalidDigest));
}
}
Loading

0 comments on commit 1f939a5

Please sign in to comment.