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 _captchaInstances = new Dictionary(); 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 combinationImage = new Image(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(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 hashSet = new HashSet(); while (hashSet.Count < _httpCaptcha.GroupLength) hashSet.Add(new CaptchaCombination(this._httpCaptcha)); List 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(); 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); } } }