[PTRun][ValueGenerator]Add URI/URL features (#30648)

* move existing generator classes

* add new generators

* implement new generators

* fixes

* improvements

* shorten query tags

* make spellcheck happy

* add tests

* dev docs

* fix typos

* fix tests
This commit is contained in:
Heiko
2024-01-03 17:30:11 +01:00
committed by GitHub
parent 2a544583c0
commit 7c0f24df65
16 changed files with 475 additions and 13 deletions

View File

@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Base64
{
public class Base64DecodeRequest : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Base64 Decoding";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private string DataToDecode { get; set; }
public Base64DecodeRequest(string dataToDecode)
{
DataToDecode = dataToDecode ?? throw new ArgumentNullException(nameof(dataToDecode));
}
public bool Compute()
{
IsSuccessful = true;
try
{
Result = System.Convert.FromBase64String(DataToDecode);
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
if (Result != null)
{
return Encoding.UTF8.GetString(Result);
}
else
{
return string.Empty;
}
}
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Base64
{
public class Base64Request : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Base64 Encoding";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private byte[] DataToEncode { get; set; }
public Base64Request(byte[] dataToEncode)
{
DataToEncode = dataToEncode ?? throw new ArgumentNullException(nameof(dataToEncode));
}
public bool Compute()
{
IsSuccessful = true;
try
{
Result = Encoding.UTF8.GetBytes(System.Convert.ToBase64String(DataToEncode));
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
return Encoding.UTF8.GetString(Result);
}
}
}

View File

@@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using Wox.Plugin.Common.Win32;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
{
internal sealed class GUIDGenerator
{
// As defined in https://datatracker.ietf.org/doc/html/rfc4122#appendix-C
public static readonly Dictionary<string, Guid> PredefinedNamespaces = new Dictionary<string, Guid>()
{
{ "ns:dns", new Guid(0x6ba7b810, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) },
{ "ns:url", new Guid(0x6ba7b811, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) },
{ "ns:oid", new Guid(0x6ba7b812, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) },
{ "ns:x500", new Guid(0x6ba7b814, 0x9dad, 0x11d1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8) },
};
public static Guid V1()
{
GUIDDATA guiddata;
int uuidCreateResult = NativeMethods.UuidCreateSequential(out guiddata);
if (uuidCreateResult != Win32Constants.RPC_S_OK && uuidCreateResult != Win32Constants.RPC_S_UUID_LOCAL_ONLY)
{
throw new InvalidOperationException("Failed to create GUID version 1");
}
return new Guid(guiddata.Data1, guiddata.Data2, guiddata.Data3, guiddata.Data4);
}
public static Guid V3(Guid uuidNamespace, string uuidName)
{
return V3AndV5(uuidNamespace, uuidName, 3);
}
public static Guid V4()
{
return Guid.NewGuid();
}
public static Guid V5(Guid uuidNamespace, string uuidName)
{
return V3AndV5(uuidNamespace, uuidName, 5);
}
private static Guid V3AndV5(Guid uuidNamespace, string uuidName, short version)
{
byte[] namespaceBytes = uuidNamespace.ToByteArray();
byte[] networkEndianNamespaceBytes = namespaceBytes;
// Convert time_low, time_mid and time_hi_and_version to network order
int time_low = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(networkEndianNamespaceBytes.AsSpan()[0..4]));
short time_mid = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(networkEndianNamespaceBytes.AsSpan()[4..6]));
short time_hi_and_version = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(networkEndianNamespaceBytes.AsSpan()[6..8]));
Buffer.BlockCopy(BitConverter.GetBytes(time_low), 0, networkEndianNamespaceBytes, 0, 4);
Buffer.BlockCopy(BitConverter.GetBytes(time_mid), 0, networkEndianNamespaceBytes, 4, 2);
Buffer.BlockCopy(BitConverter.GetBytes(time_hi_and_version), 0, networkEndianNamespaceBytes, 6, 2);
byte[] nameBytes = Encoding.ASCII.GetBytes(uuidName);
byte[] namespaceAndNameBytes = new byte[networkEndianNamespaceBytes.Length + nameBytes.Length];
Buffer.BlockCopy(networkEndianNamespaceBytes, 0, namespaceAndNameBytes, 0, namespaceBytes.Length);
Buffer.BlockCopy(nameBytes, 0, namespaceAndNameBytes, networkEndianNamespaceBytes.Length, nameBytes.Length);
byte[] hash;
if (version == 3)
{
#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms
hash = MD5.HashData(namespaceAndNameBytes);
#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms
}
else if (version == 5)
{
#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
hash = SHA1.HashData(namespaceAndNameBytes);
#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms
}
else
{
throw new InvalidOperationException($"GUID version {version} does not exist");
}
byte[] result = new byte[16];
// Copy first 16-bytes of the hash into our Uuid result
Buffer.BlockCopy(hash, 0, result, 0, 16);
// Convert put time_low, time_mid and time_hi_and_version back to host order
time_low = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(result.AsSpan()[0..4]));
time_mid = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(result.AsSpan()[4..6]));
time_hi_and_version = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(result.AsSpan()[6..8]));
// Set version 'version' in time_hi_and_version field according to https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.3
time_hi_and_version &= 0x0FFF;
#pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand
time_hi_and_version = (short)(time_hi_and_version | (version << 12));
#pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand
Buffer.BlockCopy(BitConverter.GetBytes(time_low), 0, result, 0, 4);
Buffer.BlockCopy(BitConverter.GetBytes(time_mid), 0, result, 4, 2);
Buffer.BlockCopy(BitConverter.GetBytes(time_hi_and_version), 0, result, 6, 2);
// Set upper two bits to "10"
result[8] &= 0x3F;
result[8] |= 0x80;
return new Guid(result);
}
}
}

View File

@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Security.Cryptography;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.GUID
{
public class GUIDRequest : IComputeRequest
{
public byte[] Result { get; set; }
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private int Version { get; set; }
public string Description
{
get
{
switch (Version)
{
case 1:
return "Version 1: Time base GUID";
case 3:
case 5:
string hashAlgorithm;
if (Version == 3)
{
hashAlgorithm = HashAlgorithmName.MD5.ToString();
}
else
{
hashAlgorithm = HashAlgorithmName.SHA1.ToString();
}
return $"Version {Version} ({hashAlgorithm}): Namespace and name based GUID.";
case 4:
return "Version 4: Randomly generated GUID";
default:
return string.Empty;
}
}
}
private Guid? GuidNamespace { get; set; }
private string GuidName { get; set; }
private Guid GuidResult { get; set; }
private static readonly string NullNamespaceError = $"The first parameter needs to be a valid GUID or one of: {string.Join(", ", GUIDGenerator.PredefinedNamespaces.Keys)}";
public GUIDRequest(int version, string guidNamespace = null, string name = null)
{
Version = version;
if (Version < 1 || Version > 5 || Version == 2)
{
throw new ArgumentException("Unsupported GUID version. Supported versions are 1, 3, 4 and 5");
}
if (version == 3 || version == 5)
{
if (guidNamespace == null)
{
throw new ArgumentNullException(null, NullNamespaceError);
}
Guid guid;
if (GUIDGenerator.PredefinedNamespaces.TryGetValue(guidNamespace.ToLowerInvariant(), out guid))
{
GuidNamespace = guid;
}
else if (Guid.TryParse(guidNamespace, out guid))
{
GuidNamespace = guid;
}
else
{
throw new ArgumentNullException(null, NullNamespaceError);
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
else
{
GuidName = name;
}
}
else
{
GuidNamespace = null;
}
ErrorMessage = null;
}
public bool Compute()
{
IsSuccessful = true;
try
{
switch (Version)
{
case 1:
GuidResult = GUIDGenerator.V1();
break;
case 3:
GuidResult = GUIDGenerator.V3(GuidNamespace.Value, GuidName);
break;
case 4:
GuidResult = GUIDGenerator.V4();
break;
case 5:
GuidResult = GUIDGenerator.V5(GuidNamespace.Value, GuidName);
break;
}
Result = GuidResult.ToByteArray();
}
catch (InvalidOperationException e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
if (!IsSuccessful)
{
return ErrorMessage;
}
return GuidResult.ToString();
}
}
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Hashing
{
public class HashRequest : IComputeRequest
{
public byte[] Result { get; set; }
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
public string Description
{
get
{
return $"{AlgorithmName}({Encoding.UTF8.GetString(DataToHash)})";
}
}
public HashAlgorithmName AlgorithmName { get; set; }
private byte[] DataToHash { get; set; }
private static Dictionary<HashAlgorithmName, HashAlgorithm> _algorithms = new Dictionary<HashAlgorithmName, HashAlgorithm>()
{
#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms
{ HashAlgorithmName.MD5, MD5.Create() },
#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms
#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
{ HashAlgorithmName.SHA1, SHA1.Create() },
#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms
{ HashAlgorithmName.SHA256, SHA256.Create() },
{ HashAlgorithmName.SHA384, SHA384.Create() },
{ HashAlgorithmName.SHA512, SHA512.Create() },
};
public HashRequest(HashAlgorithmName algorithmName, byte[] dataToHash)
{
AlgorithmName = algorithmName;
DataToHash = dataToHash ?? throw new ArgumentNullException(nameof(dataToHash));
}
public bool Compute()
{
if (DataToHash == null)
{
ErrorMessage = "Null data passed to hash request";
Log.Exception(ErrorMessage, new InvalidOperationException(ErrorMessage), GetType());
IsSuccessful = false;
}
else
{
Result = _algorithms[AlgorithmName].ComputeHash(DataToHash);
IsSuccessful = true;
}
return IsSuccessful;
}
public string ResultToString()
{
StringBuilder sb = new StringBuilder();
foreach (var b in Result)
{
sb.Append(b.ToString("X2", null));
}
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Uri
{
public class DataEscapeRequest : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Data string escaped";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private string DataToEscape { get; set; }
public DataEscapeRequest(string dataToEscape)
{
DataToEscape = dataToEscape ?? throw new ArgumentNullException(nameof(dataToEscape));
}
public bool Compute()
{
IsSuccessful = true;
try
{
Result = Encoding.UTF8.GetBytes(System.Uri.EscapeDataString(DataToEscape));
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
return Encoding.UTF8.GetString(Result);
}
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Uri
{
public class DataUnescapeRequest : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Data string unescaped";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private string DataToUnescape { get; set; }
public DataUnescapeRequest(string dataToUnescape)
{
DataToUnescape = dataToUnescape ?? throw new ArgumentNullException(nameof(dataToUnescape));
}
public bool Compute()
{
IsSuccessful = true;
try
{
Result = Encoding.UTF8.GetBytes(System.Uri.UnescapeDataString(DataToUnescape));
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
if (Result != null)
{
return Encoding.UTF8.GetString(Result);
}
else
{
return string.Empty;
}
}
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Uri
{
public class HexEscapeRequest : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Hex escaped char";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private string DataToEscape { get; set; }
public HexEscapeRequest(string dataToEscape)
{
DataToEscape = dataToEscape ?? throw new ArgumentNullException(nameof(dataToEscape));
// Validate that we have only one character
if (dataToEscape.Length != 1)
{
throw new ArgumentOutOfRangeException(nameof(dataToEscape));
}
}
public bool Compute()
{
IsSuccessful = true;
try
{
char charToEscape = DataToEscape[0];
Result = Encoding.UTF8.GetBytes(System.Uri.HexEscape(charToEscape));
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
return Encoding.UTF8.GetString(Result);
}
}
}

View File

@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Text;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Uri
{
public class HexUnescapeRequest : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Hex char unescaped";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private string DataToUnescape { get; set; }
public HexUnescapeRequest(string dataToUnescape)
{
DataToUnescape = dataToUnescape ?? throw new ArgumentNullException(nameof(dataToUnescape));
}
public bool Compute()
{
IsSuccessful = true;
try
{
int index = 0;
if (System.Uri.IsHexEncoding(DataToUnescape, index))
{
Result = Encoding.UTF8.GetBytes(System.Uri.HexUnescape(DataToUnescape, ref index).ToString());
}
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
if (Result != null)
{
return Encoding.UTF8.GetString(Result);
}
else
{
return string.Empty;
}
}
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Security.Policy;
using System.Text;
using System.Web;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Uri
{
public class UrlDecodeRequest : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Decoded URL";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private string DataToDecode { get; set; }
public UrlDecodeRequest(string dataToDecode)
{
DataToDecode = dataToDecode ?? throw new ArgumentNullException(nameof(dataToDecode));
}
public bool Compute()
{
IsSuccessful = true;
try
{
Result = Encoding.UTF8.GetBytes(HttpUtility.UrlDecode(DataToDecode));
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
if (Result != null)
{
return Encoding.UTF8.GetString(Result);
}
else
{
return string.Empty;
}
}
}
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Security.Policy;
using System.Text;
using System.Web;
using Wox.Plugin.Logger;
namespace Community.PowerToys.Run.Plugin.ValueGenerator.Uri
{
public class UrlEncodeRequest : IComputeRequest
{
public byte[] Result { get; set; }
public string Description => "Encoded URL";
public bool IsSuccessful { get; set; }
public string ErrorMessage { get; set; }
private string DataToEncode { get; set; }
public UrlEncodeRequest(string dataToEncode)
{
DataToEncode = dataToEncode ?? throw new ArgumentNullException(nameof(dataToEncode));
}
public bool Compute()
{
IsSuccessful = true;
try
{
Result = Encoding.UTF8.GetBytes(HttpUtility.UrlEncode(DataToEncode));
}
catch (Exception e)
{
Log.Exception(e.Message, e, GetType());
ErrorMessage = e.Message;
IsSuccessful = false;
}
return IsSuccessful;
}
public string ResultToString()
{
return Encoding.UTF8.GetString(Result);
}
}
}