2019-10-10 00:52:42 -04:00
using System ;
using System.ComponentModel ;
using System.Security ;
using System.Text ;
2021-08-25 23:07:19 +02:00
using System.Text.RegularExpressions ;
2019-10-10 00:52:42 -04:00
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 ) ) ;
}
2019-10-14 10:47:41 -04:00
// Base64 is 6 bits -> char
// A byte is 8 bits
2019-10-10 00:52:42 -04:00
// When end user doing somthing like base64(user:password)
// The length of the leading content will cause different base64 encoding result on the password
2019-10-14 10:47:41 -04:00
// 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
2019-10-10 00:52:42 -04:00
public static String Base64StringEscapeShift1 ( String value )
{
return Base64StringEscapeShift ( value , 1 ) ;
}
public static String Base64StringEscapeShift2 ( String value )
{
return Base64StringEscapeShift ( value , 2 ) ;
}
2021-03-05 15:17:55 -06:00
// Used when we pass environment variables to docker to escape " with \"
public static String CommandLineArgumentEscape ( String value )
{
return value . Replace ( "\"" , "\\\"" ) ;
}
2019-10-10 00:52:42 -04:00
public static String ExpressionStringEscape ( String value )
{
2019-12-16 17:05:26 -05:00
return Expressions2 . Sdk . ExpressionUtility . StringEscape ( value ) ;
2019-10-10 00:52:42 -04:00
}
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 ) ;
}
2020-04-21 22:07:55 -04:00
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 ;
}
2021-08-25 23:07:19 +02:00
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.
2023-06-02 21:47:59 +02:00
2021-08-25 23:07:19 +02:00
// The secret secretpart1&secretpart2&secretpart3 would be split into 2 sections:
// 'secretpart1&secretpart2&' and 'secretpart3'. This method masks for the first section.
2023-06-02 21:47:59 +02:00
2021-08-25 23:07:19 +02:00
// 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 ( "&+" ) )
{
2023-09-15 00:33:33 -04:00
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 ) ;
}
2021-08-25 23:07:19 +02:00
}
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 ;
}
2019-10-10 00:52:42 -04:00
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 ) ;
2020-02-12 14:26:30 -05:00
return Convert . ToBase64String ( shiftArray ) ;
2019-10-10 00:52:42 -04:00
}
else
{
2020-02-12 14:26:30 -05:00
return Convert . ToBase64String ( bytes ) ;
2019-10-10 00:52:42 -04:00
}
}
2020-02-12 14:26:30 -05:00
private static String UriDataEscape (
2019-10-10 00:52:42 -04:00
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 ( ) ;
}
}
}