Sanitize text pasted into hex text boxes (squashed PR #3684)
* Sanitize text pasted into hex text boxes Trim `0x` and `$` prefixes and whitespace pasted into `HexTextBox` and `WatchValueBox`. Prevent pasting non-hex text. Add `ClipboardEventTextBox` control with `OnPaste` event * Fall back to trapping paste keyboard shortcuts on Linux * Adjust code style, seal `PasteEventArgs` * Use slightly more sophisticated shared method for sanitizing hex strings * Use moderately more sophisticated method for sanitizing hex strings * More `string.Empty` * Add some comments * Code style * Remove superfluous format check
This commit is contained in:
parent
ec8ba06dbe
commit
1b961f248d
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using System.Windows.Forms;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk.CustomControls
|
||||
{
|
||||
public class ClipboardEventTextBox : TextBox
|
||||
{
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
// WM_PASTE is also sent when pasting through the OS context menu, but doesn't work on Mono
|
||||
const int WM_PASTE = 0x302;
|
||||
|
||||
if (m.Msg is WM_PASTE && !OSTailoredCode.IsUnixHost)
|
||||
{
|
||||
if (OnPasteInternal())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
protected override bool ProcessCmdKey(ref Message m, Keys keyData)
|
||||
{
|
||||
if (!ReadOnly && OSTailoredCode.IsUnixHost && keyData is (Keys.Control | Keys.V) or (Keys.Shift | Keys.Insert))
|
||||
{
|
||||
return OnPasteInternal();
|
||||
}
|
||||
|
||||
return base.ProcessCmdKey(ref m, keyData);
|
||||
}
|
||||
|
||||
/// <returns><see langword="true"/> if regular paste handling should be prevented.</returns>
|
||||
private bool OnPasteInternal()
|
||||
{
|
||||
bool containsText;
|
||||
string text;
|
||||
|
||||
try
|
||||
{
|
||||
containsText = Clipboard.ContainsText();
|
||||
text = containsText ? Clipboard.GetText() : string.Empty;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Clipboard is busy? No idea if this ever happens in practice
|
||||
return true;
|
||||
}
|
||||
|
||||
var args = new PasteEventArgs(containsText, text);
|
||||
OnPaste(args);
|
||||
return args.Handled;
|
||||
}
|
||||
|
||||
protected virtual void OnPaste(PasteEventArgs e)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Paste <paramref name="text"/> at selected position without exceeding the <see cref="TextBoxBase.MaxLength"/> limit.
|
||||
/// The pasted string will be truncated if necessary.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not raise <see cref="OnPaste"/>.
|
||||
/// </remarks>
|
||||
public void PasteWithMaxLength(string text)
|
||||
{
|
||||
if (MaxLength > 0)
|
||||
{
|
||||
var availableLength = MaxLength - TextLength + SelectionLength;
|
||||
if (text.Length > availableLength)
|
||||
{
|
||||
text = text.Substring(startIndex: 0, length: availableLength);
|
||||
}
|
||||
}
|
||||
Paste(text);
|
||||
}
|
||||
|
||||
protected sealed class PasteEventArgs : EventArgs
|
||||
{
|
||||
public bool ContainsText { get; }
|
||||
public string Text { get; }
|
||||
/// <summary>Prevents regular paste handling if set to <see langword="true"/>.</summary>
|
||||
public bool Handled { get; set; }
|
||||
|
||||
public PasteEventArgs(bool containsText, string text)
|
||||
{
|
||||
ContainsText = containsText;
|
||||
Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Client.EmuHawk.CustomControls;
|
||||
using BizHawk.Common.StringExtensions;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
|
||||
|
@ -15,7 +15,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
void SetFromRawInt(int? rawInt);
|
||||
}
|
||||
|
||||
public class HexTextBox : TextBox, INumberBox
|
||||
public class HexTextBox : ClipboardEventTextBox, INumberBox
|
||||
{
|
||||
private string _addressFormatStr = "";
|
||||
private long? _maxSize;
|
||||
|
@ -135,6 +135,18 @@ namespace BizHawk.Client.EmuHawk
|
|||
base.OnTextChanged(e);
|
||||
}
|
||||
|
||||
protected override void OnPaste(PasteEventArgs e)
|
||||
{
|
||||
if (e.ContainsText)
|
||||
{
|
||||
string text = e.Text.CleanHex();
|
||||
PasteWithMaxLength(text);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
base.OnPaste(e);
|
||||
}
|
||||
|
||||
public int? ToRawInt()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
|
|
|
@ -3,11 +3,13 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using BizHawk.Client.Common;
|
||||
using BizHawk.Client.EmuHawk.CustomControls;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using BizHawk.Common.StringExtensions;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
public class WatchValueBox : TextBox, INumberBox
|
||||
public class WatchValueBox : ClipboardEventTextBox, INumberBox
|
||||
{
|
||||
private WatchSize _size = WatchSize.Byte;
|
||||
private WatchDisplayType _type = WatchDisplayType.Hex;
|
||||
|
@ -431,6 +433,18 @@ namespace BizHawk.Client.EmuHawk
|
|||
base.OnTextChanged(e);
|
||||
}
|
||||
|
||||
protected override void OnPaste(PasteEventArgs e)
|
||||
{
|
||||
if (Type is WatchDisplayType.Hex && e.ContainsText)
|
||||
{
|
||||
string text = e.Text.CleanHex();
|
||||
PasteWithMaxLength(text);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
base.OnPaste(e);
|
||||
}
|
||||
|
||||
public int? ToRawInt()
|
||||
{
|
||||
try
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BizHawk.Common.StringExtensions
|
||||
{
|
||||
|
@ -33,6 +34,23 @@ namespace BizHawk.Common.StringExtensions
|
|||
/// </returns>
|
||||
public static string OnlyHex(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsHex)).ToUpperInvariant();
|
||||
|
||||
/// <returns>
|
||||
/// A copy of <paramref name="raw"/> in uppercase after removing <c>0x</c>/<c>$</c> prefixes and all whitespace, or
|
||||
/// <see cref="string.Empty"/> if <paramref name="raw"/> contains other non-hex characters.
|
||||
/// </returns>
|
||||
public static string CleanHex(this string? raw)
|
||||
{
|
||||
if (raw is not null && CleanHexRegex.Match(raw) is { Success: true} match)
|
||||
{
|
||||
return match.Groups["hex"].Value.OnlyHex();
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
private static readonly Regex CleanHexRegex = new(@"^\s*(?:0x|\$)?(?<hex>[0-9A-Fa-f\s]+)\s*$");
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
public static ushort ParseU16FromHex(ReadOnlySpan<char> str)
|
||||
=> ushort.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using BizHawk.Common.StringExtensions;
|
||||
|
||||
namespace BizHawk.Tests.Common.StringExtensions
|
||||
{
|
||||
[TestClass]
|
||||
public class NumericStringExtensionTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void TesCleanHex()
|
||||
{
|
||||
Assert.AreEqual("0123456789ABCDEFABCDEF", "0123456789ABCDEFabcdef".CleanHex());
|
||||
Assert.AreEqual("ABCDEF", "0xABCDEF".CleanHex());
|
||||
Assert.AreEqual("ABCDEF", "$ABCDEF".CleanHex());
|
||||
Assert.AreEqual("ABCDEF", " AB CD\nEF ".CleanHex());
|
||||
Assert.AreEqual("ABCDEF", " 0xABCDEF ".CleanHex());
|
||||
|
||||
Assert.AreEqual(string.Empty, (null as string).CleanHex());
|
||||
Assert.AreEqual(string.Empty, string.Empty.CleanHex());
|
||||
Assert.AreEqual(string.Empty, "0x$ABCDEF".CleanHex());
|
||||
Assert.AreEqual(string.Empty, "$0xABCDEF".CleanHex());
|
||||
Assert.AreEqual(string.Empty, "$$ABCDEF".CleanHex());
|
||||
Assert.AreEqual(string.Empty, "ABCDEF$".CleanHex());
|
||||
Assert.AreEqual(string.Empty, "A!B.C(DE)F".CleanHex());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue