Files
duplicati/Duplicati/Server/WebServer/CaptchaUtil.cs
T
Kenneth SkovhedeandGitHub 17e2a3dab4 Feature/add delete dialog (#2008)
Added a delete page with options to delete the local database, as well as the remote files.
Added a captcha to protect against automated attacks that would attempt to delete the remote files.

This fixes #1201
2016-10-13 21:45:31 +02:00

116 lines
5.7 KiB
C#

// Copyright (C) 2016, The Duplicati Team
// http://www.duplicati.com, info@duplicati.com
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
namespace Duplicati.Server.WebServer
{
/// <summary>
/// Helper class for creating Captcha images
/// </summary>
public static class CaptchaUtil
{
/// <summary>
/// A lookup string with characters to use
/// </summary>
private static readonly string DEFAULT_CHARS = "ACDEFGHJKLMNPQRTUVWXY34679";
/// <summary>
/// A range of possible brush colors
/// </summary>
private static readonly Brush[] BRUSH_COLORS =
typeof(Brushes)
.GetProperties(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)
.Where(x => x.PropertyType == typeof(Brush))
.Select(x => x.GetValue(null, null) as Brush)
.Where(x => x != null)
.ToArray();
/// <summary>
/// Creates a random answer.
/// </summary>
/// <returns>The random answer.</returns>
/// <param name="allowedchars">The list of allowed chars, supply a character multiple times to change frequency.</param>
/// <param name="minlength">The minimum answer length.</param>
/// <param name="maxlength">The maximum answer length.</param>
public static string CreateRandomAnswer(string allowedchars = null, int minlength = 10, int maxlength = 12)
{
allowedchars = allowedchars ?? DEFAULT_CHARS;
var rnd = new Random();
var len = rnd.Next(Math.Min(minlength, maxlength), Math.Max(minlength, maxlength) + 1);
if (len <= 0)
throw new ArgumentException($"The values ${minlength} and ${maxlength} gave a final length of {len} and it must be greater than 0");
return new string(Enumerable.Range(0, len).Select(x => allowedchars[rnd.Next(0, allowedchars.Length)]).ToArray());
}
/// <summary>
/// Creates a captcha image.
/// </summary>
/// <returns>The captcha image.</returns>
/// <param name="answer">The captcha solution string.</param>
/// <param name="size">The size of the image, omit to get a size based on the string.</param>
/// <param name="fontsize">The size of the font used to create the captcha.</param>
public static Bitmap CreateCaptcha(string answer, Size size = default(Size), int fontsize = 24)
{
if (size.Width == 0 || size.Height == 0)
size = new Size((answer.Length + 1) * fontsize, fontsize * 2);
var bmp = new Bitmap(size.Width, size.Height);
var rnd = new Random();
var stray_x = fontsize / 4;
var stray_y = size.Height / 4;
using (var graphics = Graphics.FromImage(bmp))
using (var font1 = new Font("Ariel", fontsize))
using (var font2 = new Font("Ariel", fontsize))
using (var font3 = new HatchBrush(HatchStyle.Shingle, Color.GhostWhite, Color.DarkBlue))
{
graphics.Clear(Color.White);
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
// Apply a some background string to make it hard to do OCR
foreach (var color in new[] { Color.Yellow, Color.LightGreen, Color.GreenYellow })
using (var brush = new SolidBrush(color))
graphics.DrawString(CreateRandomAnswer(minlength: answer.Length, maxlength: answer.Length), font2, brush, rnd.Next(-stray_x, stray_x), rnd.Next(-stray_y, stray_y));
var spacing = (size.Width / fontsize) + rnd.Next(0, stray_x);
// Create a vertical background lines
for (var i = rnd.Next(0, stray_x); i < size.Width; i += spacing)
using (var pen = new Pen(BRUSH_COLORS[rnd.Next(0, BRUSH_COLORS.Length)]))
graphics.DrawLine(pen, i + rnd.Next(-stray_x, stray_x), rnd.Next(0, stray_y), i + rnd.Next(-stray_x, stray_x), size.Height - rnd.Next(0, stray_y));
spacing = (size.Height / fontsize) + rnd.Next(0, stray_y);
// Create a horizontal background lines
for (var i = rnd.Next(0, stray_y); i < size.Height; i += spacing)
using (var pen = new Pen(BRUSH_COLORS[rnd.Next(0, BRUSH_COLORS.Length)]))
graphics.DrawLine(pen, rnd.Next(0, stray_x), i + rnd.Next(-stray_y, stray_y), size.Width - rnd.Next(0, stray_x), i + rnd.Next(-stray_y, stray_y));
// Draw the actual answer
graphics.DrawString(answer, font1, font3, ((size.Width - (fontsize * answer.Length)) / 2) + rnd.Next(-stray_x, stray_x), ((size.Height - (fontsize * 2)) / 2) + rnd.Next(-stray_y, stray_y));
return bmp;
}
}
}
}