291 lines
11 KiB
C#
291 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using ln.http.route;
|
|
using ln.json;
|
|
using ln.json.mapping;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.Drawing;
|
|
using SixLabors.ImageSharp.Drawing.Processing;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using SixLabors.ImageSharp.Processing;
|
|
|
|
namespace ln.http.helpers
|
|
{
|
|
|
|
public class HttpCaptcha : HttpRoute
|
|
{
|
|
private Dictionary<Guid, CaptchaInstance> _captchaInstances = new Dictionary<Guid, CaptchaInstance>();
|
|
private CaptchaEndpoints _captchaEndpoints;
|
|
|
|
public HtmlColor[] CaptchaColors { get; } = new HtmlColor[]
|
|
{
|
|
HtmlColor.Red, HtmlColor.Green, HtmlColor.Blue, HtmlColor.Yellow, HtmlColor.Violett, HtmlColor.turquoise
|
|
};
|
|
|
|
public Random _random = new Random(Environment.TickCount + Thread.CurrentThread.GetHashCode());
|
|
|
|
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(10);
|
|
public int SolutionLength { get; set; }
|
|
public int GroupLength { get; set; }
|
|
|
|
public HttpCaptcha(HttpRouter httpRouter, string mapPath)
|
|
:this(httpRouter, mapPath, 25, 5)
|
|
{}
|
|
public HttpCaptcha(HttpRouter httpRouter, string mapPath, int groupLength, int solutionLength)
|
|
: base(HttpMethod.ANY, mapPath)
|
|
{
|
|
_captchaEndpoints = new CaptchaEndpoints(this);
|
|
_routerDelegate = _captchaEndpoints.RouteRequest;
|
|
|
|
GroupLength = groupLength;
|
|
SolutionLength = solutionLength;
|
|
|
|
httpRouter.Map(this);
|
|
}
|
|
|
|
public CaptchaInstance CreateInstance() => new CaptchaInstance(this);
|
|
|
|
public bool Authorize(HttpRequest httpRequest)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
class CaptchaEndpoints : HttpEndpointController
|
|
{
|
|
private HttpCaptcha _httpCaptcha;
|
|
|
|
public CaptchaEndpoints(HttpCaptcha httpCaptcha)
|
|
{
|
|
_httpCaptcha = httpCaptcha;
|
|
}
|
|
|
|
[Map(HttpMethod.GET,"")]
|
|
public HttpResponse GetCaptcha()
|
|
{
|
|
return HttpResponse
|
|
.OK()
|
|
.Content(
|
|
_httpCaptcha
|
|
.CreateInstance()
|
|
.Json()
|
|
);
|
|
}
|
|
|
|
[Map(HttpMethod.GET, "/:instance/:combination")]
|
|
public HttpResponse DrawCombination(Guid instance, int combination)
|
|
{
|
|
int imageSize = 48;
|
|
|
|
if (!_httpCaptcha._captchaInstances.TryGetValue(instance, out CaptchaInstance captchaInstance) ||
|
|
((combination < 0) || (combination >= captchaInstance.Combinations.Length)))
|
|
return HttpResponse.NotFound();
|
|
|
|
CaptchaCombination captchaCombination = captchaInstance.Combinations[combination];
|
|
|
|
Image<Rgb24> combinationImage = new Image<Rgb24>(imageSize, imageSize, new Rgb24(255,255,255));
|
|
IPath combinationPath = null;
|
|
|
|
switch (captchaCombination.Form)
|
|
{
|
|
case CaptchaForm.Stern:
|
|
combinationPath = new Star((float)(imageSize / 2.0),
|
|
(float)(imageSize / 2.0), captchaCombination.Corners,
|
|
(float)(imageSize / 6.0), (float)(imageSize / 2.3));
|
|
break;
|
|
case CaptchaForm.Vieleck:
|
|
combinationPath = new RegularPolygon((float)(imageSize / 2.0),
|
|
(float)(imageSize / 2.0), captchaCombination.Corners,
|
|
(float)(imageSize / 2.3));
|
|
break;
|
|
default:
|
|
return HttpResponse.InternalServerError();
|
|
}
|
|
|
|
combinationImage.Mutate(x => x.Fill(captchaCombination.Color, combinationPath.RotateDegree(_httpCaptcha._random.Next(360))));
|
|
|
|
StreamedContent streamedContent = new StreamedContent("image/png");
|
|
combinationImage.SaveAsPng(streamedContent.ContentStream);
|
|
|
|
HttpResponse httpResponse = HttpResponse.OK().Content(streamedContent);
|
|
return httpResponse;
|
|
}
|
|
|
|
[Map(HttpMethod.POST, "/:instance")]
|
|
public HttpResponse Solve(Guid instance, [HttpArgumentSource(HttpArgumentSource.CONTENT)] JSONValue body)
|
|
{
|
|
if (!_httpCaptcha._captchaInstances.TryGetValue(instance, out CaptchaInstance captchaInstance))
|
|
return HttpResponse.NotFound();
|
|
|
|
if (body is JSONArray jsonSolution)
|
|
{
|
|
int[] solution = JSONMapper.DefaultMapper.FromJson<int[]>(jsonSolution);
|
|
|
|
if (captchaInstance.Solve(solution))
|
|
{
|
|
return HttpResponse.NoContent();
|
|
}
|
|
return HttpResponse.Forbidden();
|
|
}
|
|
else
|
|
{
|
|
return HttpResponse.BadRequest();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class CaptchaInstance : IDisposable
|
|
{
|
|
public Guid Guid { get; }
|
|
|
|
public CaptchaCombination[] Combinations { get; private set; }
|
|
public int[] Solution { get; private set; }
|
|
|
|
|
|
private HttpCaptcha _httpCaptcha;
|
|
private Timer _timer;
|
|
|
|
public CaptchaInstance(HttpCaptcha httpCaptcha)
|
|
{
|
|
_httpCaptcha = httpCaptcha;
|
|
Guid = Guid.NewGuid();
|
|
|
|
_httpCaptcha._captchaInstances.Add(Guid, this);
|
|
_timer = new Timer((state) => Dispose(), null, _httpCaptcha.Timeout, TimeSpan.Zero);
|
|
|
|
Initialize();
|
|
}
|
|
|
|
private void Initialize()
|
|
{
|
|
HashSet<CaptchaCombination> hashSet = new HashSet<CaptchaCombination>();
|
|
while (hashSet.Count < _httpCaptcha.GroupLength)
|
|
hashSet.Add(new CaptchaCombination(this._httpCaptcha));
|
|
|
|
List<CaptchaCombination> combinationPool = hashSet.ToList();
|
|
|
|
Combinations = new CaptchaCombination[combinationPool.Count];
|
|
for (int n = 0; n < Combinations.Length; n++)
|
|
{
|
|
Combinations[n] = combinationPool[_httpCaptcha._random.Next(combinationPool.Count)];
|
|
combinationPool.Remove(Combinations[n]);
|
|
}
|
|
|
|
hashSet.Clear();
|
|
|
|
while (hashSet.Count < _httpCaptcha.SolutionLength)
|
|
hashSet.Add(Combinations[_httpCaptcha._random.Next(Combinations.Length)]);
|
|
|
|
Solution = hashSet.Select(cs => Array.IndexOf(Combinations, cs)).ToArray();
|
|
Console.WriteLine(String.Join('-', Solution));
|
|
Array.Sort(Solution);
|
|
Console.WriteLine(String.Join('-', Solution));
|
|
}
|
|
|
|
public bool Solve(int[] possibleSolution)
|
|
{
|
|
lock (this)
|
|
{
|
|
try
|
|
{
|
|
possibleSolution = possibleSolution.Distinct().ToArray();
|
|
Array.Sort(possibleSolution);
|
|
|
|
return Solution.SequenceEqual(possibleSolution);
|
|
}
|
|
finally
|
|
{
|
|
Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
public JSONObject Json()
|
|
{
|
|
JSONObject json = new JSONObject()
|
|
.Add("uuid", Guid)
|
|
.Add("questions", Solution.Select(n => Combinations[n].ToString()).ToArray())
|
|
.Add("group", Combinations.Length);
|
|
;
|
|
return json;
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
{
|
|
lock (this)
|
|
{
|
|
_timer.Dispose();
|
|
_httpCaptcha?._captchaInstances.Remove(Guid);
|
|
}
|
|
}
|
|
|
|
public override string ToString() => String.Join(", ", Solution.Select(n => Combinations[n].ToString()));
|
|
}
|
|
|
|
public class CaptchaCombination
|
|
{
|
|
public HtmlColor Color { get; }
|
|
public int Corners { get; }
|
|
public CaptchaForm Form { get; }
|
|
|
|
public CaptchaCombination(HttpCaptcha httpCaptcha)
|
|
{
|
|
Color = httpCaptcha.CaptchaColors[httpCaptcha._random.Next(httpCaptcha.CaptchaColors.Length)];
|
|
Corners = 4 + httpCaptcha._random.Next(4);
|
|
|
|
CaptchaForm[] captchaForms = Enum.GetValues<CaptchaForm>();
|
|
Form = captchaForms[httpCaptcha._random.Next(captchaForms.Length)];
|
|
}
|
|
|
|
public override bool Equals(object? obj) => obj is CaptchaCombination other && Color.Equals(other.Color) &&
|
|
Corners.Equals(other.Corners) && Form.Equals(other.Form);
|
|
|
|
public override int GetHashCode() => Color.GetHashCode() ^ Corners ^ Form.GetHashCode();
|
|
|
|
public override string ToString() => $"ein {Form} mit {Corners} {(Form == CaptchaForm.Stern ? "Strahlen" : "Ecken")} in {Color.Name}";
|
|
}
|
|
|
|
public enum CaptchaForm
|
|
{
|
|
Stern,
|
|
Vieleck
|
|
}
|
|
|
|
public class HtmlColor
|
|
{
|
|
public static readonly HtmlColor Red = new HtmlColor(255, 0, 0, "Rot");
|
|
public static readonly HtmlColor Green = new HtmlColor(0, 255, 0, "Grün");
|
|
public static readonly HtmlColor Blue = new HtmlColor(0, 0, 255, "Blau");
|
|
public static readonly HtmlColor Yellow = new HtmlColor(240, 240, 0, "Gelb");
|
|
public static readonly HtmlColor Violett = new HtmlColor(255, 0, 255, "Lila");
|
|
public static readonly HtmlColor turquoise = new HtmlColor(0, 255, 255, "Türkis");
|
|
|
|
|
|
public byte R { get; }
|
|
public byte G { get; }
|
|
public byte B { get; }
|
|
|
|
public string Name { get; }
|
|
|
|
public HtmlColor(byte r, byte g, byte b) : this(r, g, b, null)
|
|
{
|
|
}
|
|
|
|
public HtmlColor(byte r, byte g, byte b, string name)
|
|
{
|
|
R = r;
|
|
G = g;
|
|
B = b;
|
|
Name = name;
|
|
}
|
|
|
|
public override string ToString() => $"#{Red:X2}{Green:X2}{Blue:X2}";
|
|
|
|
public static implicit operator Color(HtmlColor htmlColor) =>
|
|
new Rgb24(htmlColor.R, htmlColor.G, htmlColor.B);
|
|
}
|
|
|
|
}
|
|
} |