mirror of
https://github.com/actions/runner.git
synced 2025-12-10 04:06:57 +00:00
198 lines
6.8 KiB
C#
198 lines
6.8 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Security;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace GitHub.DistributedTask.Logging
|
|
{
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public delegate String ValueEncoder(String value);
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public static class ValueEncoders
|
|
{
|
|
public static String Base64StringEscape(String value)
|
|
{
|
|
return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
|
|
}
|
|
|
|
// Base64 is 6 bits -> char
|
|
// A byte is 8 bits
|
|
// When end user doing somthing like base64(user:password)
|
|
// The length of the leading content will cause different base64 encoding result on the password
|
|
// So we add base64(value shifted 1 and two bytes) as secret as well.
|
|
// B1 B2 B3 B4 B5 B6 B7
|
|
// 000000|00 0000|0000 00|000000| 000000|00 0000|0000 00|000000|
|
|
// Char1 Char2 Char3 Char4
|
|
// See the above, the first byte has a character beginning at index 0, the second byte has a character beginning at index 4, the third byte has a character beginning at index 2 and then the pattern repeats
|
|
// We register byte offsets for all these possible values
|
|
public static String Base64StringEscapeShift1(String value)
|
|
{
|
|
return Base64StringEscapeShift(value, 1);
|
|
}
|
|
|
|
public static String Base64StringEscapeShift2(String value)
|
|
{
|
|
return Base64StringEscapeShift(value, 2);
|
|
}
|
|
|
|
// Used when we pass environment variables to docker to escape " with \"
|
|
public static String CommandLineArgumentEscape(String value)
|
|
{
|
|
return value.Replace("\"", "\\\"");
|
|
}
|
|
|
|
public static String ExpressionStringEscape(String value)
|
|
{
|
|
return Expressions2.Sdk.ExpressionUtility.StringEscape(value);
|
|
}
|
|
|
|
public static String JsonStringEscape(String value)
|
|
{
|
|
// Convert to a JSON string and then remove the leading/trailing double-quote.
|
|
String jsonString = JsonConvert.ToString(value);
|
|
String jsonEscapedValue = jsonString.Substring(startIndex: 1, length: jsonString.Length - 2);
|
|
return jsonEscapedValue;
|
|
}
|
|
|
|
public static String UriDataEscape(String value)
|
|
{
|
|
return UriDataEscape(value, 65519);
|
|
}
|
|
|
|
public static String XmlDataEscape(String value)
|
|
{
|
|
return SecurityElement.Escape(value);
|
|
}
|
|
|
|
public static String TrimDoubleQuotes(String value)
|
|
{
|
|
var trimmed = string.Empty;
|
|
if (!string.IsNullOrEmpty(value) &&
|
|
value.Length > 8 &&
|
|
value.StartsWith('"') &&
|
|
value.EndsWith('"'))
|
|
{
|
|
trimmed = value.Substring(1, value.Length - 2);
|
|
}
|
|
|
|
return trimmed;
|
|
}
|
|
|
|
public static String PowerShellPreAmpersandEscape(String value)
|
|
{
|
|
// if the secret is passed to PS as a command and it causes an error, sections of it can be surrounded by color codes
|
|
// or printed individually.
|
|
|
|
// The secret secretpart1&secretpart2&secretpart3 would be split into 2 sections:
|
|
// 'secretpart1&secretpart2&' and 'secretpart3'. This method masks for the first section.
|
|
|
|
// The secret secretpart1&+secretpart2&secretpart3 would be split into 2 sections:
|
|
// 'secretpart1&+' and (no 's') 'ecretpart2&secretpart3'. This method masks for the first section.
|
|
|
|
var trimmed = string.Empty;
|
|
if (!string.IsNullOrEmpty(value) && value.Contains("&"))
|
|
{
|
|
var secretSection = string.Empty;
|
|
if (value.Contains("&+"))
|
|
{
|
|
int endIndex = value.IndexOf("&+") + "&+".Length;
|
|
|
|
// If string ends with "&+", grab the whole string
|
|
if (endIndex == value.Length)
|
|
{
|
|
secretSection = value;
|
|
}
|
|
else
|
|
{
|
|
secretSection = value.Substring(0, endIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
secretSection = value.Substring(0, value.LastIndexOf("&") + "&".Length);
|
|
}
|
|
|
|
// Don't mask short secrets
|
|
if (secretSection.Length >= 6)
|
|
{
|
|
trimmed = secretSection;
|
|
}
|
|
}
|
|
|
|
return trimmed;
|
|
}
|
|
|
|
public static String PowerShellPostAmpersandEscape(String value)
|
|
{
|
|
var trimmed = string.Empty;
|
|
if (!string.IsNullOrEmpty(value) && value.Contains("&"))
|
|
{
|
|
var secretSection = string.Empty;
|
|
if (value.Contains("&+"))
|
|
{
|
|
// +1 to skip the letter that got colored
|
|
secretSection = value.Substring(value.IndexOf("&+") + "&+".Length + 1);
|
|
}
|
|
else
|
|
{
|
|
secretSection = value.Substring(value.LastIndexOf("&") + "&".Length);
|
|
}
|
|
|
|
if (secretSection.Length >= 6)
|
|
{
|
|
trimmed = secretSection;
|
|
}
|
|
}
|
|
|
|
return trimmed;
|
|
}
|
|
|
|
private static string Base64StringEscapeShift(String value, int shift)
|
|
{
|
|
var bytes = Encoding.UTF8.GetBytes(value);
|
|
if (bytes.Length > shift)
|
|
{
|
|
var shiftArray = new byte[bytes.Length - shift];
|
|
Array.Copy(bytes, shift, shiftArray, 0, bytes.Length - shift);
|
|
return Convert.ToBase64String(shiftArray);
|
|
}
|
|
else
|
|
{
|
|
return Convert.ToBase64String(bytes);
|
|
}
|
|
}
|
|
|
|
private static String UriDataEscape(
|
|
String value,
|
|
Int32 maxSegmentSize)
|
|
{
|
|
if (value.Length <= maxSegmentSize)
|
|
{
|
|
return Uri.EscapeDataString(value);
|
|
}
|
|
|
|
// Workaround size limitation in Uri.EscapeDataString
|
|
var result = new StringBuilder();
|
|
var i = 0;
|
|
do
|
|
{
|
|
var length = Math.Min(value.Length - i, maxSegmentSize);
|
|
|
|
if (Char.IsHighSurrogate(value[i + length - 1]) && length > 1)
|
|
{
|
|
length--;
|
|
}
|
|
|
|
result.Append(Uri.EscapeDataString(value.Substring(i, length)));
|
|
i += length;
|
|
}
|
|
while (i < value.Length);
|
|
|
|
return result.ToString();
|
|
}
|
|
}
|
|
}
|