Skip to content

Commit 7adf1c6

Browse files
Merge pull request #10 from Vu0r1-sec/master
Fix EmbeddedResource Path in Linux
2 parents b88b7d7 + d079172 commit 7adf1c6

File tree

2 files changed

+51
-29
lines changed

2 files changed

+51
-29
lines changed

IconCaptcha/IconCaptcha.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
<ItemGroup>
3434
<!-- Embed default icons -->
35-
<EmbeddedResource Include="Assets\**\*.png" LogicalName="%(RecursiveDir)%(Filename)%(Extension)"/>
35+
<EmbeddedResource Include="Assets\**\*.png"/>
3636
</ItemGroup>
3737

3838
<ItemGroup>

IconCaptcha/IconCaptchaService.cs

+50-28
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ public class IconCaptchaService
2929
private const string CaptchaFieldId = "ic-hf-id";
3030
private const string CaptchaFieldHoneypot = "ic-hf-hp";
3131
private const string CaptchaFieldToken = "_iconcaptcha-token";
32-
32+
3333
/// <summary>
3434
/// The default length of a captcha token.
3535
/// </summary>
3636
private const int CaptchaTokenLength = 20;
37-
37+
3838
/// <summary>
3939
/// The default image width of a captcha challenge, in pixels.
4040
/// </summary>
@@ -86,7 +86,7 @@ public class IconCaptchaService
8686
["dark"] = new(Mode.dark, new byte[] { 64, 64, 64 }),
8787
["legacy-dark"] = new(Mode.dark, new byte[] { 64, 64, 64 }),
8888
};
89-
89+
9090
private Random Rand { get; }
9191

9292
private ISessionProvider SessionProvider { get; }
@@ -101,7 +101,7 @@ public IconCaptchaService(ISessionProvider sessionProvider, IHttpContextAccessor
101101
Options = options;
102102
Rand = new Random();
103103
}
104-
104+
105105
private CaptchaSession _session;
106106
private CaptchaSession Session
107107
{
@@ -123,7 +123,7 @@ private CaptchaSession Session
123123
return _session;
124124
}
125125
}
126-
126+
127127
/// <summary>
128128
/// Generates and returns a secure random string which will serve as a CSRF token for the current session. After
129129
/// generating the token, it will be saved in the global session variable. The length of the token will be
@@ -135,9 +135,9 @@ private CaptchaSession Session
135135
public string Token()
136136
{
137137
// Make sure to only generate a token if none exists.
138-
if (Session.Token != null)
138+
if (Session.Token != null)
139139
return Session.Token;
140-
140+
141141
// Create a secure captcha session token.
142142
var bytes = RandomNumberGenerator.GetBytes(CaptchaTokenLength);
143143
var token = Convert.ToHexString(bytes);
@@ -200,7 +200,7 @@ public CaptchaResult GetCaptchaData(Payload payload)
200200
var iconPositions = new int[iconAmount];
201201
var iconIds = new List<int>();
202202
var correctIconId = -1;
203-
203+
204204
// Create a random 'icon position' order.
205205
var tempPositions = Enumerable.Range(0, iconAmount)
206206
.OrderBy(c => Rand.Next())
@@ -262,7 +262,7 @@ public CaptchaResult GetCaptchaData(Payload payload)
262262
Id = payload.CaptchaId
263263
};
264264
}
265-
265+
266266
/// <summary>
267267
/// Validates the user form submission. If the captcha is incorrect, it
268268
/// will set the global error variable and return FALSE, else TRUE.
@@ -297,7 +297,7 @@ public void ValidateSubmission()
297297
{
298298
throw new IconCaptchaSubmissionException(6, Options.Value.Messages.FormToken);
299299
}
300-
300+
301301
// Initialize the session.
302302
var sessionData = CreateSession(captchaId);
303303

@@ -351,9 +351,9 @@ public bool ValidateToken(string payloadToken, string headerToken = null)
351351
var options = Options.Value;
352352

353353
// Only validate if the token option is enabled.
354-
if (!options.Token)
354+
if (!options.Token)
355355
return true;
356-
356+
357357
var sessionToken = GetToken();
358358

359359
// If the token is empty but the option is enabled, the token was never requested.
@@ -385,9 +385,9 @@ public bool ValidateToken(string payloadToken, string headerToken = null)
385385
/// <returns>TRUE if the correct icon was selected, FALSE if not.</returns>
386386
public bool SetSelectedAnswer(Payload payload)
387387
{
388-
if (payload == null)
388+
if (payload == null)
389389
return false;
390-
390+
391391
// Check if the captcha ID and required other payload data is set.
392392
if (payload.CaptchaId == default || payload.XPos == null || payload.YPos == null || payload.Width == null)
393393
{
@@ -488,11 +488,9 @@ public async Task GetImage(long identifier)
488488
var themeIconColor = Options.Value.Themes[sessionData.Mode].Icons;
489489
var iconPath = Path.Combine(iconsDirectoryPath, themeIconColor.ToString());
490490

491-
// Generate the captcha image.
492-
await using var placeholderStream = isEmbedded
493-
? GetType().Assembly.GetManifestResourceStream(placeholder)
494-
: File.OpenRead(placeholder);
491+
await using var placeholderStream = GetImageStream(isEmbedded, placeholder);
495492

493+
// Generate the captcha image.
496494
var generatedImage = GenerateImage(sessionData, iconPath, placeholderStream, isEmbedded);
497495

498496
// Set the content type header to the PNG MIME-type.
@@ -511,6 +509,34 @@ public async Task GetImage(long identifier)
511509
await generatedImage.CopyToAsync(httpContext.Response.Body);
512510
}
513511

512+
/// <summary>
513+
/// Retrieves an image stream either from the file system or from embedded resources within the assembly.
514+
/// </summary>
515+
/// <param name="isEmbedded">Indicates whether the image is an embedded resource.</param>
516+
/// <param name="file">The file path or resource name.</param>
517+
/// <returns>A Stream representing the image.</returns>
518+
/// <exception cref="FileNotFoundException">Thrown if the embedded resource is not found.</exception>
519+
private Stream GetImageStream(bool isEmbedded, string file)
520+
{
521+
if (!isEmbedded)
522+
return File.OpenRead(file);
523+
524+
var assembly = GetType().Assembly;
525+
526+
// Format the resource name to match the embedded resource naming convention
527+
var resourceRef = file.Replace('/', '.').Replace('\\', '.');
528+
resourceRef = $"{assembly.GetName().Name}.Assets.{resourceRef}";
529+
530+
// Retrieve the embedded resource stream or throw an exception if not found
531+
var stream = assembly.GetManifestResourceStream(resourceRef);
532+
if (stream == null)
533+
{
534+
throw new FileNotFoundException($"Resource '{resourceRef}' not found in embedded resources.");
535+
}
536+
537+
return stream;
538+
}
539+
514540
/// <summary>
515541
/// Returns a generated image containing the icons for the current captcha instance. The icons will be copied
516542
/// onto a placeholder image, located at the $placeholderPath. The icons will be randomly rotated and flipped
@@ -539,11 +565,7 @@ private Stream GenerateImage(CaptchaChallenge challenge,
539565
id =>
540566
{
541567
var icon = Path.Combine(iconPath, $"icon-{id}.png");
542-
543-
using var stream = embeddedFiles
544-
? GetType().Assembly.GetManifestResourceStream(icon)
545-
: File.OpenRead(icon);
546-
568+
using var stream = GetImageStream(embeddedFiles, icon);
547569
return CreateImage(stream);
548570
});
549571

@@ -590,15 +612,15 @@ private Stream GenerateImage(CaptchaChallenge challenge,
590612
if (rotateEnabled)
591613
{
592614
var degree = Rand.Next(1, 4);
593-
615+
594616
// Only if the 'degree' is not the same as what it would already be at.
595617
if (degree != 4)
596618
{
597619
var rotated = new SKBitmap(
598620
degree % 2 == 0 ? icon.Width : icon.Height,
599621
degree % 2 == 0 ? icon.Height : icon.Width
600622
);
601-
623+
602624
var surface = new SKCanvas(rotated);
603625
surface.Translate(rotated.Width / 2, rotated.Height / 2);
604626
surface.RotateDegrees(degree * 90);
@@ -661,7 +683,7 @@ private static SKBitmap CreateImage(Stream file)
661683

662684
return SKBitmap.FromImage(image);
663685
}
664-
686+
665687
/// <summary>
666688
/// Returns the clicked icon position based on the X and Y position and the captcha width.
667689
/// </summary>
@@ -679,7 +701,7 @@ private int DetermineClickedIcon(int clickedXPos, int clickedYPos, int captchaWi
679701

680702
return (int)Math.Floor(clickedXPos / ((decimal)captchaWidth / iconAmount));
681703
}
682-
704+
683705
/// <summary>
684706
/// Calculates the amount of times 1 or more other icons can be present in the captcha image besides the correct icon.
685707
/// Each other icons should be at least present 1 time more than the correct icon. When calculating the icon
@@ -749,7 +771,7 @@ private CaptchaChallenge CreateSession(long identifier = 0)
749771

750772
Session.Add(identifier, sessionData);
751773
}
752-
774+
753775
return sessionData;
754776
}
755777

0 commit comments

Comments
 (0)