Introduces a few changes to the code to generate captchas. Font size calculations are now done in pixels for consistency, and the width of the image is estimated from the width of the text being printed. Also fixes #2965.
130 lines
6.5 KiB
C#
130 lines
6.5 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>
|
|
/// Approximate the size in pixels of text drawn at the given fontsize
|
|
/// </summary>
|
|
private static int ApproxTextWidth(string text, FontFamily fontfamily, int fontsize)
|
|
{
|
|
using (var font = new Font(fontfamily, fontsize, GraphicsUnit.Pixel))
|
|
using (var graphics = Graphics.FromImage(new Bitmap(1, 1))) {
|
|
return (int) graphics.MeasureString(text, font).Width;
|
|
}
|
|
}
|
|
|
|
/// <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, in pixels.</param>
|
|
public static Bitmap CreateCaptcha(string answer, Size size = default(Size), int fontsize = 40)
|
|
{
|
|
var fontfamily = FontFamily.GenericSansSerif;
|
|
var text_width = ApproxTextWidth(answer, fontfamily, fontsize);
|
|
if (size.Width == 0 || size.Height == 0)
|
|
size = new Size((int) (text_width * 1.2), (int) (fontsize * 1.2));
|
|
|
|
var bmp = new Bitmap(size.Width, size.Height);
|
|
var rnd = new Random();
|
|
var stray_x = fontsize / 2;
|
|
var stray_y = size.Height / 4;
|
|
var ans_stray_x = fontsize / 3;
|
|
var ans_stray_y = size.Height / 6;
|
|
using (var graphics = Graphics.FromImage(bmp))
|
|
using (var font1 = new Font(fontfamily, fontsize, GraphicsUnit.Pixel))
|
|
using (var font2 = new Font(fontfamily, fontsize, GraphicsUnit.Pixel))
|
|
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 - text_width) / 2) + rnd.Next(-ans_stray_x, ans_stray_x), ((size.Height - fontsize) / 2) + rnd.Next(-ans_stray_y, ans_stray_y));
|
|
|
|
return bmp;
|
|
}
|
|
}
|
|
}
|
|
}
|