/
HmacTokenTagHelper.cs
120 lines (101 loc) · 4.22 KB
/
HmacTokenTagHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp.Web.Middleware;
namespace SixLabors.ImageSharp.Web.TagHelpers;
/// <summary>
/// A <see cref="TagHelper"/> implementation targeting <img> element that allows the automatic generation of HMAC image processing protection tokens.
/// </summary>
[HtmlTargetElement("img", Attributes = SrcAttributeName, TagStructure = TagStructure.WithoutEndTag)]
public class HmacTokenTagHelper : UrlResolutionTagHelper
{
private const string SrcAttributeName = "src";
private readonly ImageSharpMiddlewareOptions options;
private readonly RequestAuthorizationUtilities authorizationUtilities;
/// <summary>
/// Initializes a new instance of the <see cref="HmacTokenTagHelper" /> class.
/// </summary>
/// <param name="options">The middleware configuration options.</param>
/// <param name="authorizationUtilities">Contains helpers that allow authorization of image requests.</param>
/// <param name="urlHelperFactory">The URL helper factory.</param>
/// <param name="htmlEncoder">The HTML encorder.</param>
public HmacTokenTagHelper(
IOptions<ImageSharpMiddlewareOptions> options,
RequestAuthorizationUtilities authorizationUtilities,
IUrlHelperFactory urlHelperFactory,
HtmlEncoder htmlEncoder)
: base(urlHelperFactory, htmlEncoder)
{
Guard.NotNull(options, nameof(options));
Guard.NotNull(authorizationUtilities, nameof(authorizationUtilities));
this.options = options.Value;
this.authorizationUtilities = authorizationUtilities;
}
/// <inheritdoc/>
public override int Order => 2;
/// <summary>
/// Gets or sets the source of the image.
/// </summary>
/// <remarks>
/// Passed through to the generated HTML in all cases.
/// </remarks>
[HtmlAttributeName(SrcAttributeName)]
public string? Src { get; set; }
/// <inheritdoc />
public override void Process(TagHelperContext context, TagHelperOutput output)
{
Guard.NotNull(context, nameof(context));
Guard.NotNull(output, nameof(output));
output.CopyHtmlAttribute(SrcAttributeName, context);
this.ProcessUrlAttribute(SrcAttributeName, output);
byte[] secret = this.options.HMACSecretKey;
if (secret is null || secret.Length == 0)
{
return;
}
// Retrieve the TagHelperOutput variation of the "src" attribute in case other TagHelpers in the
// pipeline have touched the value. If the value is already encoded this ImageTagHelper may
// not function properly.
string? src = output.Attributes[SrcAttributeName]?.Value as string;
if (string.IsNullOrWhiteSpace(src))
{
return;
}
string? hmac = this.authorizationUtilities.ComputeHMAC(src, CommandHandling.Sanitize);
if (hmac is not null)
{
this.Src = AddQueryString(src, hmac);
output.Attributes.SetAttribute(SrcAttributeName, this.Src);
}
}
private static string AddQueryString(
ReadOnlySpan<char> uri,
string hmac)
{
ReadOnlySpan<char> uriToBeAppended = uri;
ReadOnlySpan<char> anchorText = default;
// If there is an anchor, then the query string must be inserted before its first occurrence.
int anchorIndex = uri.IndexOf('#');
if (anchorIndex != -1)
{
anchorText = uri[anchorIndex..];
uriToBeAppended = uri[..anchorIndex];
}
int queryIndex = uriToBeAppended.IndexOf('?');
bool hasQuery = queryIndex != -1;
StringBuilder sb = new();
sb.Append(uriToBeAppended)
.Append(hasQuery ? '&' : '?')
.Append(UrlEncoder.Default.Encode(RequestAuthorizationUtilities.TokenCommand))
.Append('=')
.Append(UrlEncoder.Default.Encode(hmac))
.Append(anchorText);
return sb.ToString();
}
}