[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 475 additions and 13 deletions

View File

@ -1600,6 +1600,7 @@ UAL
uap
udit
uefi
uesc
UHash
UIA
UIEx
@ -1608,6 +1609,7 @@ ums
uncompilable
UNCPRIORITY
UNDNAME
unescape
UNICODETEXT
uninstantiated
uniquifier
@ -1626,6 +1628,7 @@ UOI
Updatelayout
UPGRADINGPRODUCTCODE
Uptool
urld
Usb
USEDEFAULT
USEFILEATTRIBUTES

View File

@ -1,6 +1,6 @@
# Value Generator Plugin
The Value Generator plugin is used to generate hashes for strings, to calculate base64 encodings and to generate GUIDs versions 1, 3, 4 and 5.
The Value Generator plugin is used to generate hashes for strings, to calculate base64 encodings, escape and encode URLs/URIs and to generate GUIDs versions 1, 3, 4 and 5.
![Image of Value Generator plugin](/doc/images/launcher/plugin/community.valuegenerator.png)
@ -10,7 +10,7 @@ The Value Generator plugin is used to generate hashes for strings, to calculate
- The result of `string ResultToString()` will be used for the Result's title
- The `Description` field will be used for the Result's subtitle
### [`HashRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Hashing/HashRequest.cs)
### [`HashRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Hashing/HashRequest.cs)
- Implements IComputeRequest
- Supports the hashing algorithms from System.Security.Cryptography:
- MD5
@ -20,19 +20,19 @@ The Value Generator plugin is used to generate hashes for strings, to calculate
- SHA512
- If other algorithms are added to System.Security.Cryptography, they can be added to the `_algorithms` dictionary. [`InputParser.ParseInput()`](#inputparser) will need to return a `HashRequest` for the algorithm in the query
### [`Base64Request`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64Request.cs)
### [`Base64Request`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Base64/Base64Request.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the base64 encoding of the byte array passed in the constructor
### [`Base64DecodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs)
### [`Base64DecodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Base64/Base64DecodeRequest.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the decoded byte array of the base64 string passed in the constructor
### [`GUIDRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs)
### [`GUIDRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/GUID/GUIDRequest.cs)
- Implements IComputeRequest
- Uses the [`GUIDGenerator`](#guidgenerator) class to generate or compute the requested GUID
### [`GUIDGenerator`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDGenerator.cs)
### [`GUIDGenerator`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/GUID/GUIDGenerator.cs)
- Utility class for generating or calculating GUIDs
- Generating GUID versions 1 and 4 is done using builtin APIs. [`UuidCreateSequential`](https://learn.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidcreatesequential) for version 1 and `System.Guid.NewGuid()` for version 4
- Versions 3 and 5 take two parameters, a namespace and a name
@ -40,6 +40,32 @@ The Value Generator plugin is used to generate hashes for strings, to calculate
- The `PredefinedNamespaces` dictionary contains aliases for the predefined namespaces
- The name can be any string
### [`UrlEncodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/UrlEncodeRequest.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the encoded url converted using `HttpUtility.UrlEncode()`.
### [`UrlDecodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/UrlDecodeRequest.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the decoded url converted using `HttpUtility.UrlDecode()`.
### [`DataEscapeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/DataEscapeRequest.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the escaped data string converted using `System.Uri.EscapeDataString()`.
### [`DataUnescapeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/DataUnescapeRequest.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the unescaped data string converted using `System.Uri.UnescapeDataString()`.
### [`HexEscapeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/HexEscapeRequest.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the escaped data string converted using `System.Uri.HexEscape()`.
- Only single characters are supported as input.
### [`HexUnescapeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/Uri/HexUnescapeRequest.cs)
- Implements IComputeRequest
- `Compute()` will populate `Result` with the unescaped data string converted using `System.Uri.HexUnescape()`.
- Only the first hexadecimal character in the string gets unescaped. The rest of the user input is ignored.
### [`InputParser`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs)
- It is responsible only for parsing the query from the user
- Based on the user query, the `ParseInput()` method must return an object that implements the `IComputeRequest` interface or it must throw one of `FormatException` or `ArgumentException`
@ -51,6 +77,6 @@ The Value Generator plugin is used to generate hashes for strings, to calculate
> The error message will not be shown to the user but a log message will be created
### Adding a new value generator
1. To add a new value generator, create a folder under `/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/` and inside it add a class that implements `IComputeRequest`.
1. To add a new value generator, create a folder under `/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Generators/` and inside it add a class that implements `IComputeRequest`.
2. Add any utility classes that are specific to the new generator inside the same folder to keep them separated from the other generators.
3. Modify the `InputParser.ParseInput()` to handle a request for the new generator and return an instance of the class you created in step 1

View File

@ -30,12 +30,23 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
[DataRow("base99 abc", null)]
[DataRow("base64s abc", null)]
[DataRow("base64d abc=", typeof(Base64.Base64DecodeRequest))]
public void ParserTest(string input, Type? expectedRequestType)
[DataRow("url http://googl.de/u oii d", typeof(Uri.UrlEncodeRequest))]
[DataRow("urld http://googl.de/u oii d=oo", typeof(Uri.UrlDecodeRequest))]
[DataRow("esc:data jjdje332j 3 3l2jl32", typeof(Uri.DataEscapeRequest))]
[DataRow("esc:hex d", typeof(Uri.HexEscapeRequest))]
[DataRow("esc:hex 4", typeof(Uri.HexEscapeRequest))]
[DataRow("esc:hex ", typeof(Uri.HexEscapeRequest), true)]
[DataRow("esc:hex z44", typeof(Uri.HexEscapeRequest), true)]
[DataRow("uesc:data jjdje332j 3 3l2jl32", typeof(Uri.DataUnescapeRequest))]
[DataRow("uesc:hex %21", typeof(Uri.HexUnescapeRequest))]
[DataRow("uesc:hex 4", typeof(Uri.HexUnescapeRequest))]
[DataRow("uesc:hex ", typeof(Uri.HexUnescapeRequest))]
[DataRow("uesc:hex z44", typeof(Uri.HexUnescapeRequest))]
public void ParserTest(string input, Type? expectedRequestType, bool expectException = false)
{
var parser = new InputParser();
var query = new Query(input);
var expectException = false;
string? command = null;
if (query.Terms.Count == 0)
{
@ -79,15 +90,24 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
private static bool CommandIsKnown(string command)
{
string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64", "base64d" };
if (hashes.Contains(command.ToLowerInvariant()))
{
return true;
}
Regex regex = new Regex("^(guid|uuid)([1345]{0,1}|v[1345]{1})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
Regex regexGuiUUID = new Regex("^(guid|uuid)([1345]{0,1}|v[1345]{1})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (regexGuiUUID.IsMatch(command))
{
return true;
}
return regex.IsMatch(command);
string[] uriCommands = new string[] { "url", "urld", "esc:hex", "uesc:hex", "esc:data", "uesc:data" };
if (uriCommands.Contains(command.ToLowerInvariant()))
{
return true;
}
return false;
}
}
}

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);
}
}
}

View File

@ -8,6 +8,7 @@ using System.Text;
using Community.PowerToys.Run.Plugin.ValueGenerator.Base64;
using Community.PowerToys.Run.Plugin.ValueGenerator.GUID;
using Community.PowerToys.Run.Plugin.ValueGenerator.Hashing;
using Community.PowerToys.Run.Plugin.ValueGenerator.Uri;
using Wox.Plugin;
using Wox.Plugin.Logger;
@ -127,6 +128,69 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new Base64DecodeRequest(content);
}
else if (command.StartsWith("esc:", StringComparison.OrdinalIgnoreCase))
{
// Escape things
if (command.Equals("esc:data", StringComparison.OrdinalIgnoreCase))
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new DataEscapeRequest(content);
}
else if (command.Equals("esc:hex", StringComparison.OrdinalIgnoreCase))
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
// This is only for single chars
if (content.Length > 1)
{
throw new ArgumentException($"Invalid Query: {query.RawUserQuery} (To many characters.)");
}
else if (content.Length == 0)
{
throw new FormatException($"Invalid Query: {query.RawUserQuery}");
}
request = new HexEscapeRequest(content);
}
else
{
throw new FormatException($"Invalid Query: {query.RawUserQuery}");
}
}
else if (command.StartsWith("uesc:", StringComparison.OrdinalIgnoreCase))
{
// Unescape things
if (command.Equals("uesc:data", StringComparison.OrdinalIgnoreCase))
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new DataUnescapeRequest(content);
}
else if (command.Equals("uesc:hex", StringComparison.OrdinalIgnoreCase))
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new HexUnescapeRequest(content);
}
else
{
throw new FormatException($"Invalid Query: {query.RawUserQuery}");
}
}
else if (command.Equals("url", StringComparison.OrdinalIgnoreCase))
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new UrlEncodeRequest(content);
}
else if (command.Equals("urld", StringComparison.OrdinalIgnoreCase))
{
int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new UrlDecodeRequest(content);
}
else
{
throw new FormatException($"Invalid Query: {query.RawUserQuery}");

View File

@ -93,7 +93,16 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
try
{
IComputeRequest computeRequest = _inputParser.ParseInput(query);
results.Add(GetResult(computeRequest));
var result = GetResult(computeRequest);
if (!string.IsNullOrEmpty(result.Title))
{
results.Add(result);
}
else
{
return results;
}
}
catch (ArgumentException e)
{