delete un-used code. (#218)

This commit is contained in:
Tingluo Huang
2019-12-16 17:05:26 -05:00
committed by GitHub
parent c3c66bb14a
commit d0a4a41a63
582 changed files with 155 additions and 66274 deletions

View File

@@ -1,264 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
internal static class CookieUtility
{
public static readonly String AcsMetadataRetrievalExceptionText = "Unable to retrieve ACS Metadata from '{0}'";
public static readonly String FedAuthCookieName = "FedAuth";
public static readonly String WindowsLiveSignOutUrl = "https://login.live.com/uilogout.srf";
public static readonly Uri WindowsLiveCookieDomain = new Uri("https://login.live.com/");
public static CookieCollection GetFederatedCookies(Uri cookieDomainAndPath)
{
CookieCollection result = null;
Cookie cookie = GetCookieEx(cookieDomainAndPath, FedAuthCookieName).FirstOrDefault();
if (cookie != null)
{
result = new CookieCollection();
result.Add(cookie);
for (Int32 x = 1; x < 50; x++)
{
String cookieName = FedAuthCookieName + x;
cookie = GetCookieEx(cookieDomainAndPath, cookieName).FirstOrDefault();
if (cookie != null)
{
result.Add(cookie);
}
else
{
break;
}
}
}
return result;
}
public static CookieCollection GetFederatedCookies(String[] token)
{
CookieCollection result = null;
if (token != null && token.Length > 0 && token[0] != null)
{
result = new CookieCollection();
result.Add(new Cookie(FedAuthCookieName, token[0]));
for (Int32 x = 1; x < token.Length; x++)
{
String cookieName = FedAuthCookieName + x;
if (token[x] != null)
{
Cookie cookie = new Cookie(cookieName, token[x]);
cookie.HttpOnly = true;
result.Add(cookie);
}
else
{
break;
}
}
}
return result;
}
public static CookieCollection GetFederatedCookies(IHttpResponse webResponse)
{
CookieCollection result = null;
IEnumerable<String> cookies = null;
if (webResponse.Headers.TryGetValues("Set-Cookie", out cookies))
{
foreach (String cookie in cookies)
{
if (cookie != null && cookie.StartsWith(CookieUtility.FedAuthCookieName, StringComparison.OrdinalIgnoreCase))
{
// Only take the security token field of the cookie, and discard the rest
String fedAuthToken = cookie.Split(';').FirstOrDefault();
Int32 index = fedAuthToken.IndexOf('=');
if (index > 0 && index < fedAuthToken.Length - 1)
{
String name = fedAuthToken.Substring(0, index);
String value = fedAuthToken.Substring(index + 1);
result = result ?? new CookieCollection();
result.Add(new Cookie(name, value));
}
}
}
}
return result;
}
public static CookieCollection GetAllCookies(Uri cookieDomainAndPath)
{
CookieCollection result = null;
List<Cookie> cookies = GetCookieEx(cookieDomainAndPath, null);
foreach (Cookie cookie in cookies)
{
if (result == null)
{
result = new CookieCollection();
}
result.Add(cookie);
}
return result;
}
public static void DeleteFederatedCookies(Uri cookieDomainAndPath)
{
CookieCollection cookies = GetFederatedCookies(cookieDomainAndPath);
if (cookies != null)
{
foreach (Cookie cookie in cookies)
{
DeleteCookieEx(cookieDomainAndPath, cookie.Name);
}
}
}
public static void DeleteWindowsLiveCookies()
{
DeleteAllCookies(WindowsLiveCookieDomain);
}
public static void DeleteAllCookies(Uri cookieDomainAndPath)
{
CookieCollection cookies = GetAllCookies(cookieDomainAndPath);
if (cookies != null)
{
foreach (Cookie cookie in cookies)
{
DeleteCookieEx(cookieDomainAndPath, cookie.Name);
}
}
}
public const UInt32 INTERNET_COOKIE_HTTPONLY = 0x00002000;
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool InternetGetCookieEx(
String url, String cookieName, StringBuilder cookieData, ref Int32 size, UInt32 flags, IntPtr reserved);
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool InternetSetCookieEx(
String url, String cookieName, String cookieData, UInt32 flags, IntPtr reserved);
public static Boolean DeleteCookieEx(Uri cookiePath, String cookieName)
{
UInt32 flags = INTERNET_COOKIE_HTTPONLY;
String path = cookiePath.ToString();
if (!path.EndsWith("/", StringComparison.Ordinal))
{
path = path + "/";
}
DateTime expiration = DateTime.UtcNow.AddYears(-1);
String cookieData = String.Format(CultureInfo.InvariantCulture, "{0}=0;expires={1};path=/;domain={2};httponly", cookieName, expiration.ToString("R"), cookiePath.Host);
return InternetSetCookieEx(path, null, cookieData, flags, IntPtr.Zero);
}
public static Boolean SetCookiesEx(
Uri cookiePath,
CookieCollection cookies)
{
String path = cookiePath.ToString();
if (!path.EndsWith("/", StringComparison.Ordinal))
{
path = path + "/";
}
Boolean successful = true;
foreach (Cookie cookie in cookies)
{
// This means it doesn't expire
if (cookie.Expires.Year == 1)
{
continue;
}
String cookieData = String.Format(CultureInfo.InvariantCulture,
"{0}; path={1}; domain={2}; expires={3}; httponly",
cookie.Value,
cookie.Path,
cookie.Domain,
cookie.Expires.ToString("ddd, dd-MMM-yyyy HH:mm:ss 'GMT'"));
successful &= InternetSetCookieEx(path, cookie.Name, cookieData, INTERNET_COOKIE_HTTPONLY, IntPtr.Zero);
}
return successful;
}
public static List<Cookie> GetCookieEx(Uri cookiePath, String cookieName)
{
UInt32 flags = INTERNET_COOKIE_HTTPONLY;
List<Cookie> cookies = new List<Cookie>();
Int32 size = 256;
StringBuilder cookieData = new StringBuilder(size);
String path = cookiePath.ToString();
if (!path.EndsWith("/", StringComparison.Ordinal))
{
path = path + "/";
}
if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero))
{
if (size < 0)
{
return cookies;
}
cookieData = new StringBuilder(size);
if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero))
{
return cookies;
}
}
if (cookieData.Length > 0)
{
String[] cookieSections = cookieData.ToString().Split(new char[] { ';' });
foreach (String cookieSection in cookieSections)
{
String[] cookieParts = cookieSection.Split(new char[] { '=' }, 2);
if (cookieParts.Length == 2)
{
Cookie cookie = new Cookie();
cookie.Name = cookieParts[0].TrimStart();
cookie.Value = cookieParts[1];
cookie.HttpOnly = true;
cookies.Add(cookie);
}
}
}
return cookies;
}
}
}

View File

@@ -1,95 +0,0 @@
using System;
using System.Net.Http;
using System.Security;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
/// <summary>
/// Currently it is impossible to get whether prompting is allowed from the credential itself without reproducing the logic
/// used by VssClientCredentials. Since this is a stop gap solution to get Windows integrated authentication to work against
/// AAD via ADFS for now this class will only support that one, non-interactive flow. We need to assess how much we want to
/// invest in this legacy stack rather than recommending people move to the VssConnect API for future authentication needs.
/// </summary>
[Serializable]
public sealed class VssAadCredential : FederatedCredential
{
private string username;
private SecureString password;
public VssAadCredential()
: base(null)
{
}
public VssAadCredential(VssAadToken initialToken)
: base(initialToken)
{
}
public VssAadCredential(string username)
: base(null)
{
this.username = username;
}
public VssAadCredential(string username, string password)
: base(null)
{
this.username = username;
if (password != null)
{
this.password = new SecureString();
foreach (char character in password)
{
this.password.AppendChar(character);
}
}
}
public VssAadCredential(string username, SecureString password)
: base(null)
{
this.username = username;
this.password = password;
}
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Aad;
}
}
internal string Username
{
get
{
return username;
}
}
internal SecureString Password => password;
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
bool isNonAuthenticationChallenge = false;
return VssFederatedCredential.IsVssFederatedAuthenticationChallenge(webResponse, out isNonAuthenticationChallenge) ?? false;
}
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
if (response == null && base.InitialToken == null)
{
return null;
}
return new VssAadTokenProvider(this);
}
}
}

View File

@@ -1,89 +0,0 @@
using System;
using System.Diagnostics;
using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Internal;
namespace GitHub.Services.Client
{
internal static class VssAadSettings
{
public const string DefaultAadInstance = "https://login.microsoftonline.com/";
public const string CommonTenant = "common";
// VSTS service principal.
public const string Resource = "499b84ac-1321-427f-aa17-267ca6975798";
// Visual Studio IDE client ID originally provisioned by Azure Tools.
public const string Client = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1";
// AAD Production Application tenant.
private const string ApplicationTenantId = "f8cdef31-a31e-4b4a-93e4-5f571e91255a";
#if !NETSTANDARD
public static Uri NativeClientRedirectUri
{
get
{
Uri nativeClientRedirect = null;
try
{
string nativeRedirect = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadNativeClientRedirect);
if (!string.IsNullOrEmpty(nativeRedirect))
{
Uri.TryCreate(nativeRedirect, UriKind.RelativeOrAbsolute, out nativeClientRedirect);
}
}
catch (Exception e)
{
Debug.WriteLine(string.Format("NativeClientRedirectUri: {0}", e));
}
return nativeClientRedirect ?? new Uri("urn:ietf:wg:oauth:2.0:oob");
}
}
public static string ClientId
{
get
{
string nativeRedirect = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadNativeClientIdentifier);
return nativeRedirect ?? VssAadSettings.Client;
}
}
#endif
public static string AadInstance
{
get
{
#if !NETSTANDARD
string aadInstance = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadInstance);
#else
string aadInstance = null;
#endif
if (string.IsNullOrWhiteSpace(aadInstance))
{
aadInstance = DefaultAadInstance;
}
else if (!aadInstance.EndsWith("/"))
{
aadInstance = aadInstance + "/";
}
return aadInstance;
}
}
#if !NETSTANDARD
/// <summary>
/// Application tenant either from a registry override or a constant
/// </summary>
public static string ApplicationTenant =>
VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadApplicationTenant)
?? VssAadSettings.ApplicationTenantId;
#endif
}
}

View File

@@ -1,124 +0,0 @@
using System;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
[Serializable]
public class VssAadToken : IssuedToken
{
private string accessToken;
private string accessTokenType;
private AuthenticationContext authenticationContext;
private UserCredential userCredential;
private VssAadTokenOptions options;
public VssAadToken(AuthenticationResult authentication)
{
// Prevent any attempt to store this token.
this.FromStorage = true;
if (!string.IsNullOrWhiteSpace(authentication.AccessToken))
{
this.Authenticated();
}
this.accessToken = authentication.AccessToken;
this.accessTokenType = authentication.AccessTokenType;
}
public VssAadToken(
string accessTokenType,
string accessToken)
{
// Prevent any attempt to store this token.
this.FromStorage = true;
if (!string.IsNullOrWhiteSpace(accessToken) && !string.IsNullOrWhiteSpace(accessTokenType))
{
this.Authenticated();
}
this.accessToken = accessToken;
this.accessTokenType = accessTokenType;
}
public VssAadToken(
AuthenticationContext authenticationContext,
UserCredential userCredential = null,
VssAadTokenOptions options = VssAadTokenOptions.None)
{
// Prevent any attempt to store this token.
this.FromStorage = true;
this.authenticationContext = authenticationContext;
this.userCredential = userCredential;
this.options = options;
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Aad;
}
}
public AuthenticationResult AcquireToken()
{
if (this.authenticationContext == null)
{
return null;
}
AuthenticationResult authenticationResult = null;
for (int index = 0; index < 3; index++)
{
try
{
if (this.userCredential == null && !options.HasFlag(VssAadTokenOptions.AllowDialog))
{
authenticationResult = authenticationContext.AcquireTokenSilentAsync(VssAadSettings.Resource, VssAadSettings.Client).ConfigureAwait(false).GetAwaiter().GetResult();
}
else
{
authenticationResult = authenticationContext.AcquireTokenAsync(VssAadSettings.Resource, VssAadSettings.Client, this.userCredential).ConfigureAwait(false).GetAwaiter().GetResult();
}
if (authenticationResult != null)
{
break;
}
}
catch (Exception x)
{
System.Diagnostics.Debug.WriteLine("Failed to get ADFS token: " + x.ToString());
}
}
return authenticationResult;
}
internal override void ApplyTo(IHttpRequest request)
{
AuthenticationResult authenticationResult = AcquireToken();
if (authenticationResult != null)
{
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"{authenticationResult.AccessTokenType} {authenticationResult.AccessToken}");
}
else if (!string.IsNullOrEmpty(this.accessTokenType) && !string.IsNullOrEmpty(this.accessToken))
{
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"{this.accessTokenType} {this.accessToken}");
}
}
}
[Flags]
public enum VssAadTokenOptions
{
None = 0,
AllowDialog = 1
}
}

View File

@@ -1,77 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
internal sealed class VssAadTokenProvider : IssuedTokenProvider
{
public VssAadTokenProvider(VssAadCredential credential)
: base(credential, null, null)
{
}
public override bool GetTokenIsInteractive
{
get
{
return false;
}
}
private VssAadToken GetVssAadToken()
{
AuthenticationContext authenticationContext = new AuthenticationContext(string.Concat(VssAadSettings.AadInstance, VssAadSettings.CommonTenant));
UserCredential userCredential = null;
VssAadCredential credential = this.Credential as VssAadCredential;
if (credential?.Username != null)
{
#if NETSTANDARD
// UserPasswordCredential does not currently exist for ADAL 3.13.5 for any non-desktop build.
userCredential = new UserCredential(credential.Username);
#else
if (credential.Password != null)
{
userCredential = new UserPasswordCredential(credential.Username, credential.Password);
}
else
{
userCredential = new UserCredential(credential.Username);
}
#endif
}
else
{
userCredential = new UserCredential();
}
return new VssAadToken(authenticationContext, userCredential);
}
/// <summary>
/// Temporary implementation since we don't have a good configuration story here at the moment.
/// </summary>
protected override Task<IssuedToken> OnGetTokenAsync(IssuedToken failedToken, CancellationToken cancellationToken)
{
// If we have already tried to authenticate with an AAD token retrieved from Windows integrated authentication and it is not working, clear out state.
if (failedToken != null && failedToken.CredentialType == VssCredentialsType.Aad && failedToken.IsAuthenticated)
{
this.CurrentToken = null;
return Task.FromResult<IssuedToken>(null);
}
try
{
return Task.FromResult<IssuedToken>(GetVssAadToken());
}
catch
{ }
return Task.FromResult<IssuedToken>(null);
}
}
}

View File

@@ -1,172 +0,0 @@
using System;
using System.Linq;
using System.Net;
using GitHub.Services.Common;
using GitHub.Services.Common.Internal;
namespace GitHub.Services.Client
{
/// <summary>
/// Provides federated authentication with a hosted <c>VssConnection</c> instance using cookies.
/// </summary>
[Serializable]
public sealed class VssFederatedCredential : FederatedCredential
{
/// <summary>
/// Initializes a new <c>VssFederatedCredential</c> instance.
/// </summary>
public VssFederatedCredential()
: this(true)
{
}
/// <summary>
/// Initializes a new <c>VssFederatedCredential</c> instance.
/// </summary>
public VssFederatedCredential(Boolean useCache)
: this(useCache, null)
{
}
/// <summary>
/// Initializes a new <c>VssFederatedCredential</c> instance.
/// </summary>
/// <param name="initialToken">The initial token if available</param>
public VssFederatedCredential(VssFederatedToken initialToken)
: this(false, initialToken)
{
}
public VssFederatedCredential(
Boolean useCache,
VssFederatedToken initialToken)
: base(initialToken)
{
#if !NETSTANDARD
if (useCache)
{
Storage = new VssClientCredentialStorage();
}
#endif
}
/// <summary>
///
/// </summary>
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Federated;
}
}
public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
{
bool isNonAuthenticationChallenge = false;
return IsVssFederatedAuthenticationChallenge(webResponse, out isNonAuthenticationChallenge) ?? isNonAuthenticationChallenge;
}
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
// The response is only null when attempting to determine the most appropriate token provider to
// use for the connection. The only way we should do anything here is if we have an initial token
// since that means we can present something without making a server call.
if (response == null && base.InitialToken == null)
{
return null;
}
Uri signInUrl = null;
String realm = String.Empty;
String issuer = String.Empty;
if (response != null)
{
var location = response.Headers.GetValues(HttpHeaders.Location).FirstOrDefault();
if (location == null)
{
location = response.Headers.GetValues(HttpHeaders.TfsFedAuthRedirect).FirstOrDefault();
}
if (!String.IsNullOrEmpty(location))
{
signInUrl = new Uri(location);
}
// Inform the server that we support the javascript notify "smart client" pattern for ACS auth
AddParameter(ref signInUrl, "protocol", "javascriptnotify");
// Do not automatically sign in with existing FedAuth cookie
AddParameter(ref signInUrl, "force", "1");
GetRealmAndIssuer(response, out realm, out issuer);
}
return new VssFederatedTokenProvider(this, serverUrl, signInUrl, issuer, realm);
}
internal static void GetRealmAndIssuer(
IHttpResponse response,
out String realm,
out String issuer)
{
realm = response.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
issuer = response.Headers.GetValues(HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
if (!String.IsNullOrWhiteSpace(issuer))
{
issuer = new Uri(issuer).GetLeftPart(UriPartial.Authority);
}
}
internal static Boolean? IsVssFederatedAuthenticationChallenge(
IHttpResponse webResponse,
out Boolean isNonAuthenticationChallenge)
{
isNonAuthenticationChallenge = false;
if (webResponse == null)
{
return false;
}
// Check to make sure that the redirect was issued from the Tfs service. We include the TfsServiceError
// header to avoid the possibility that a redirect from a non-tfs service is issued and we incorrectly
// launch the credentials UI.
if (webResponse.StatusCode == HttpStatusCode.Found ||
webResponse.StatusCode == HttpStatusCode.Redirect)
{
return webResponse.Headers.GetValues(HttpHeaders.Location).Any() && webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).Any();
}
else if (webResponse.StatusCode == HttpStatusCode.Unauthorized)
{
return webResponse.Headers.GetValues(HttpHeaders.WwwAuthenticate).Any(x => x.StartsWith("TFS-Federated", StringComparison.OrdinalIgnoreCase));
}
else if (webResponse.StatusCode == HttpStatusCode.Forbidden)
{
// This is not strictly an "authentication challenge" but it is a state the user can do something about so they can get access to the resource
// they are attempting to access. Specifically, the user will hit this when they need to update or create a profile required by business policy.
isNonAuthenticationChallenge = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRedirect).Any();
if (isNonAuthenticationChallenge)
{
return null;
}
}
return false;
}
private static void AddParameter(ref Uri uri, String name, String value)
{
if (uri.Query.IndexOf(String.Concat(name, "="), StringComparison.OrdinalIgnoreCase) < 0)
{
UriBuilder builder = new UriBuilder(uri);
builder.Query = String.Concat(builder.Query.TrimStart('?'), "&", name, "=", value);
uri = builder.Uri;
}
}
}
}

View File

@@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
/// <summary>
/// Provides a cookie-based authentication token.
/// </summary>
[Serializable]
public sealed class VssFederatedToken : IssuedToken
{
/// <summary>
/// Initializes a new <c>VssFederatedToken</c> instance using the specified cookies.
/// </summary>
/// <param name="cookies"></param>
public VssFederatedToken(CookieCollection cookies)
{
ArgumentUtility.CheckForNull(cookies, "cookies");
m_cookies = cookies;
}
/// <summary>
/// Returns the CookieCollection contained within this token. For internal use only.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public CookieCollection CookieCollection
{
get
{
return m_cookies;
}
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Federated;
}
}
internal override void ApplyTo(IHttpRequest request)
{
// From http://www.ietf.org/rfc/rfc2109.txt:
// Note: For backward compatibility, the separator in the Cookie header
// is semi-colon (;) everywhere.
//
// HttpRequestHeaders uses comma as the default separator, so instead of returning
// a list of cookies, the method returns one semicolon separated string.
IEnumerable<String> values = request.Headers.GetValues(s_cookieHeader);
request.Headers.SetValue(s_cookieHeader, GetHeaderValue(values));
}
private String GetHeaderValue(IEnumerable<String> cookieHeaders)
{
List<String> currentCookies = new List<String>();
if (cookieHeaders != null)
{
foreach (String value in cookieHeaders)
{
currentCookies.AddRange(value.Split(';').Select(x => x.Trim()));
}
}
currentCookies.RemoveAll(x => String.IsNullOrEmpty(x));
foreach (Cookie cookie in m_cookies)
{
// Remove all existing cookies that match the name of the cookie we are going to add.
currentCookies.RemoveAll(x => String.Equals(x.Substring(0, x.IndexOf('=')), cookie.Name, StringComparison.OrdinalIgnoreCase));
currentCookies.Add(String.Concat(cookie.Name, "=", cookie.Value));
}
return String.Join("; ", currentCookies);
}
private CookieCollection m_cookies;
private static readonly String s_cookieHeader = HttpRequestHeader.Cookie.ToString();
}
}

View File

@@ -1,157 +0,0 @@
using System;
using System.Net;
using System.Net.Http;
using GitHub.Services.Common;
using System.Globalization;
namespace GitHub.Services.Client
{
/// <summary>
/// Provides authentication for internet identities using single-sign-on cookies.
/// </summary>
internal sealed class VssFederatedTokenProvider : IssuedTokenProvider, ISupportSignOut
{
public VssFederatedTokenProvider(
VssFederatedCredential credential,
Uri serverUrl,
Uri signInUrl,
String issuer,
String realm)
: base(credential, serverUrl, signInUrl)
{
Issuer = issuer;
Realm = realm;
}
protected override String AuthenticationScheme
{
get
{
return "TFS-Federated";
}
}
protected override String AuthenticationParameter
{
get
{
if (String.IsNullOrEmpty(this.Issuer) && String.IsNullOrEmpty(this.Realm))
{
return String.Empty;
}
else
{
return String.Format(CultureInfo.InvariantCulture, "issuer=\"{0}\", realm=\"{1}\"", this.Issuer, this.Realm);
}
}
}
/// <summary>
/// Gets the federated credential from which this provider was created.
/// </summary>
public new VssFederatedCredential Credential
{
get
{
return (VssFederatedCredential)base.Credential;
}
}
/// <summary>
/// Gets a value indicating whether or not a call to get token will require interactivity.
/// </summary>
public override Boolean GetTokenIsInteractive
{
get
{
return this.CurrentToken == null;
}
}
/// <summary>
/// Gets the issuer for the token provider.
/// </summary>
public String Issuer
{
get;
private set;
}
/// <summary>
/// Gets the realm for the token provider.
/// </summary>
public String Realm
{
get;
private set;
}
protected internal override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (!base.IsAuthenticationChallenge(webResponse))
{
return false;
}
// This means we were proactively constructed without any connection information. In this case
// we return false to ensure that a new provider is reconstructed with all appropriate configuration
// to retrieve a new token.
if (this.SignInUrl == null)
{
return false;
}
String realm, issuer;
VssFederatedCredential.GetRealmAndIssuer(webResponse, out realm, out issuer);
return this.Realm.Equals(realm, StringComparison.OrdinalIgnoreCase) &&
this.Issuer.Equals(issuer, StringComparison.OrdinalIgnoreCase);
}
protected override IssuedToken OnValidatingToken(
IssuedToken token,
IHttpResponse webResponse)
{
// If the response has Set-Cookie headers, attempt to retrieve the FedAuth cookie from the response
// and replace the current token with the new FedAuth cookie. Note that the server only reissues the
// FedAuth cookie if it is issued for more than an hour.
CookieCollection fedAuthCookies = CookieUtility.GetFederatedCookies(webResponse);
if (fedAuthCookies != null)
{
// The reissued token should have the same user information as the previous one.
VssFederatedToken federatedToken = new VssFederatedToken(fedAuthCookies)
{
Properties = token.Properties,
UserId = token.UserId,
UserName = token.UserName
};
token = federatedToken;
}
return token;
}
public void SignOut(Uri signOutUrl, Uri replyToUrl, String identityProvider)
{
// The preferred implementation is to follow the signOutUrl with a browser and kill the browser whenever it
// arrives at the replyToUrl (or if it bombs out somewhere along the way).
// This will work for all Web-based identity providers (Live, Google, Yahoo, Facebook) supported by ACS provided that
// the TFS server has registered sign-out urls (in the TF Registry) for each of these.
// This is the long-term approach that should be pursued and probably the approach recommended to other
// clients which don't have direct access to the cookie store (TEE?)
// In the short term we are simply going to delete the TFS cookies and the Windows Live cookies that are exposed to this
// session. This has the drawback of not properly signing out of Live (you'd still be signed in to e.g. Hotmail, Xbox, MSN, etc.)
// but will allow the user to re-enter their live credentials and sign-in again to TFS.
// The other drawback is that the clients will have to be updated again when we pursue the implementation outlined above.
CookieUtility.DeleteFederatedCookies(replyToUrl);
if (!String.IsNullOrEmpty(identityProvider) && identityProvider.Equals("Windows Live ID", StringComparison.OrdinalIgnoreCase))
{
CookieUtility.DeleteWindowsLiveCookies();
}
}
}
}

View File

@@ -1,92 +0,0 @@
using System;
using System.Linq;
using System.Net;
using GitHub.Services.Common.Internal;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides a credential for basic authentication against a Visual Studio Service.
/// </summary>
public sealed class VssBasicCredential : FederatedCredential
{
/// <summary>
/// Initializes a new <c>VssBasicCredential</c> instance with no token specified.
/// </summary>
public VssBasicCredential()
: this((VssBasicToken)null)
{
}
/// <summary>
/// Initializes a new <c>VssBasicCredential</c> instance with the specified user name and password.
/// </summary>
/// <param name="userName">The user name</param>
/// <param name="password">The password</param>
public VssBasicCredential(
string userName,
string password)
: this(new VssBasicToken(new NetworkCredential(userName, password)))
{
}
/// <summary>
/// Initializes a new <c>VssBasicCredential</c> instance with the specified token.
/// </summary>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
public VssBasicCredential(ICredentials initialToken)
: this(new VssBasicToken(initialToken))
{
}
/// <summary>
/// Initializes a new <c>VssBasicCredential</c> instance with the specified token.
/// </summary>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
public VssBasicCredential(VssBasicToken initialToken)
: base(initialToken)
{
}
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Basic;
}
}
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (webResponse == null)
{
return false;
}
if (webResponse.StatusCode != HttpStatusCode.Found &&
webResponse.StatusCode != HttpStatusCode.Redirect &&
webResponse.StatusCode != HttpStatusCode.Unauthorized)
{
return false;
}
return webResponse.Headers.GetValues(HttpHeaders.WwwAuthenticate).Any(x => x.StartsWith("Basic", StringComparison.OrdinalIgnoreCase));
}
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
if (serverUrl.Scheme != "https")
{
String unsafeBasicAuthEnv = Environment.GetEnvironmentVariable("VSS_ALLOW_UNSAFE_BASICAUTH") ?? "false";
if (!Boolean.TryParse(unsafeBasicAuthEnv, out Boolean unsafeBasicAuth) || !unsafeBasicAuth)
{
throw new InvalidOperationException(CommonResources.BasicAuthenticationRequiresSsl());
}
}
return new BasicAuthTokenProvider(this, serverUrl);
}
}
}

View File

@@ -1,63 +0,0 @@
using System;
using System.Globalization;
using System.Net;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides a token for basic authentication of internet identities.
/// </summary>
public sealed class VssBasicToken : IssuedToken
{
/// <summary>
/// Initializes a new <c>BasicAuthToken</c> instance with the specified token value.
/// </summary>
/// <param name="credentials">The credentials which should be used for authentication</param>
public VssBasicToken(ICredentials credentials)
{
m_credentials = credentials;
}
internal ICredentials Credentials
{
get
{
return m_credentials;
}
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Basic;
}
}
internal override void ApplyTo(IHttpRequest request)
{
var basicCredential = m_credentials.GetCredential(request.RequestUri, "Basic");
if (basicCredential != null)
{
request.Headers.SetValue(Internal.HttpHeaders.Authorization, "Basic " + FormatBasicAuthHeader(basicCredential));
}
}
private static String FormatBasicAuthHeader(NetworkCredential credential)
{
String authHeader = String.Empty;
if (!String.IsNullOrEmpty(credential.Domain))
{
authHeader = String.Format(CultureInfo.InvariantCulture, "{0}\\{1}:{2}", credential.Domain, credential.UserName, credential.Password);
}
else
{
authHeader = String.Format(CultureInfo.InvariantCulture, "{0}:{1}", credential.UserName, credential.Password);
}
return Convert.ToBase64String(VssHttpRequestSettings.Encoding.GetBytes(authHeader));
}
private readonly ICredentials m_credentials;
}
}

View File

@@ -1,39 +0,0 @@
using System;
using System.Net;
namespace GitHub.Services.Common
{
internal sealed class BasicAuthTokenProvider : IssuedTokenProvider
{
public BasicAuthTokenProvider(
VssBasicCredential credential,
Uri serverUrl)
: base(credential, serverUrl, serverUrl)
{
}
protected override String AuthenticationScheme
{
get
{
return "Basic";
}
}
public new VssBasicCredential Credential
{
get
{
return (VssBasicCredential)base.Credential;
}
}
public override Boolean GetTokenIsInteractive
{
get
{
return base.CurrentToken == null;
}
}
}
}

View File

@@ -51,47 +51,7 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with default credentials.
/// </summary>
public VssCredentials()
: this(true)
{
}
/// <summary>
/// Initializes a new <c>VssCredentials</c> instance with default credentials if specified.
/// </summary>
/// <param name="useDefaultCredentials">True to use default windows credentials; otherwise, false</param>
public VssCredentials(bool useDefaultCredentials)
: this(new WindowsCredential(useDefaultCredentials))
{
}
/// <summary>
/// Initializes a new <c>VssCredentials</c> instance with the specified windows credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
public VssCredentials(WindowsCredential windowsCredential)
: this(windowsCredential, null)
{
}
/// <summary>
/// Initializes a new <c>VssCredentials</c> instance with the specified windows credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed, otherwise CredentialProptType.DoNotPrompt</param>
public VssCredentials(
WindowsCredential windowsCredential,
CredentialPromptType promptType)
: this(windowsCredential, null, promptType)
{
}
/// <summary>
/// Initializes a new <c>VssCredentials</c> instance with the specified issued token credential and
/// default windows credential.
/// </summary>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
public VssCredentials(FederatedCredential federatedCredential)
: this(new WindowsCredential(), federatedCredential)
: this(null)
{
}
@@ -99,12 +59,9 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
/// credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
public VssCredentials(
WindowsCredential windowsCredential,
FederatedCredential federatedCredential)
: this(windowsCredential, federatedCredential, EnvironmentUserInteractive
public VssCredentials(FederatedCredential federatedCredential)
: this(federatedCredential, EnvironmentUserInteractive
? CredentialPromptType.PromptIfNeeded : CredentialPromptType.DoNotPrompt)
{
}
@@ -113,14 +70,12 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
/// credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed, otherwise CredentialProptType.DoNotPrompt</param>
public VssCredentials(
WindowsCredential windowsCredential,
FederatedCredential federatedCredential,
CredentialPromptType promptType)
: this(windowsCredential, federatedCredential, promptType, null)
: this(federatedCredential, promptType, null)
{
}
@@ -128,16 +83,14 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
/// credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed; otherwise, CredentialProptType.DoNotPrompt</param>
/// <param name="scheduler">An optional <c>TaskScheduler</c> to ensure credentials prompting occurs on the UI thread</param>
public VssCredentials(
WindowsCredential windowsCredential,
FederatedCredential federatedCredential,
CredentialPromptType promptType,
TaskScheduler scheduler)
: this(windowsCredential, federatedCredential, promptType, scheduler, null)
: this(federatedCredential, promptType, scheduler, null)
{
}
@@ -145,13 +98,11 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
/// credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed; otherwise, CredentialProptType.DoNotPrompt</param>
/// <param name="scheduler">An optional <c>TaskScheduler</c> to ensure credentials prompting occurs on the UI thread</param>
/// <param name="credentialPrompt">An optional <c>IVssCredentialPrompt</c> to perform prompting for credentials</param>
public VssCredentials(
WindowsCredential windowsCredential,
FederatedCredential federatedCredential,
CredentialPromptType promptType,
TaskScheduler scheduler,
@@ -172,13 +123,6 @@ namespace GitHub.Services.Common
scheduler = TaskScheduler.Default;
}
if (windowsCredential != null)
{
m_windowsCredential = windowsCredential;
m_windowsCredential.Scheduler = scheduler;
m_windowsCredential.Prompt = credentialPrompt;
}
if (federatedCredential != null)
{
m_federatedCredential = federatedCredential;
@@ -199,16 +143,6 @@ namespace GitHub.Services.Common
return new VssCredentials(credential);
}
/// <summary>
/// Implicitly converts a <c>WindowsCredential</c> instance into a <c>VssCredentials</c> instance.
/// </summary>
/// <param name="credential">The windows credential instance</param>
/// <returns>A new <c>VssCredentials</c> instance which wraps the specified credential</returns>
public static implicit operator VssCredentials(WindowsCredential credential)
{
return new VssCredentials(credential);
}
/// <summary>
/// Gets or sets a value indicating whether or not interactive prompts are allowed.
/// </summary>
@@ -240,17 +174,6 @@ namespace GitHub.Services.Common
}
}
/// <summary>
/// Gets the windows credential to use for NTLM authentication with the server.
/// </summary>
public WindowsCredential Windows
{
get
{
return m_windowsCredential;
}
}
/// <summary>
/// A pluggable credential store.
/// Simply assign a storage implementation to this property
@@ -267,11 +190,6 @@ namespace GitHub.Services.Common
{
m_credentialStorage = value;
if (m_windowsCredential != null)
{
m_windowsCredential.Storage = value;
}
if (m_federatedCredential != null)
{
m_federatedCredential.Storage = value;
@@ -327,20 +245,6 @@ namespace GitHub.Services.Common
VssHttpEventSource.Log.IssuedTokenProviderCreated(traceActivity, tokenProvider);
}
}
else if (m_windowsCredential != null && m_windowsCredential.IsAuthenticationChallenge(webResponse))
{
if (tokenProvider != null)
{
VssHttpEventSource.Log.IssuedTokenProviderRemoved(traceActivity, tokenProvider);
}
tokenProvider = m_windowsCredential.CreateTokenProvider(serverUrl, webResponse, failedToken);
if (tokenProvider != null)
{
VssHttpEventSource.Log.IssuedTokenProviderCreated(traceActivity, tokenProvider);
}
}
m_currentProvider = tokenProvider;
}
@@ -356,7 +260,7 @@ namespace GitHub.Services.Common
/// <param name="provider">Stores the active token provider, if one exists</param>
/// <returns>True if a token provider was found, false otherwise</returns>
public bool TryGetTokenProvider(
Uri serverUrl,
Uri serverUrl,
out IssuedTokenProvider provider)
{
ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
@@ -371,11 +275,6 @@ namespace GitHub.Services.Common
m_currentProvider = m_federatedCredential.CreateTokenProvider(serverUrl, null, null);
}
if (m_currentProvider == null && m_windowsCredential != null)
{
m_currentProvider = m_windowsCredential.CreateTokenProvider(serverUrl, null, null);
}
if (m_currentProvider != null)
{
VssHttpEventSource.Log.IssuedTokenProviderCreated(VssTraceActivity.Current, m_currentProvider);
@@ -401,11 +300,6 @@ namespace GitHub.Services.Common
}
bool isChallenge = false;
if (m_windowsCredential != null)
{
isChallenge = m_windowsCredential.IsAuthenticationChallenge(webResponse);
}
if (!isChallenge && m_federatedCredential != null)
{
isChallenge = m_federatedCredential.IsAuthenticationChallenge(webResponse);
@@ -415,8 +309,8 @@ namespace GitHub.Services.Common
}
internal void SignOut(
Uri serverUrl,
Uri serviceLocation,
Uri serverUrl,
Uri serviceLocation,
string identityProvider)
{
// Remove the token in the storage and the current token provider. Note that we don't
@@ -450,110 +344,6 @@ namespace GitHub.Services.Common
tokenProviderWithSignOut.SignOut(serviceLocation, serverUrl, identityProvider);
}
#if !NETSTANDARD
/// <summary>
/// Loads stored credentials for the specified server if found. If no credentials are found in the windows
/// credential store for the specified server and options then default credentials are returned.
/// </summary>
/// <param name="serverUrl">The server location</param>
/// <param name="requireExactMatch">A value indicating whether or not an exact or partial match of the server is required</param>
/// <returns>A credentials object populated with stored credentials for the server if found</returns>
public static VssCredentials LoadCachedCredentials(
Uri serverUrl,
bool requireExactMatch)
{
return LoadCachedCredentials(null, serverUrl, requireExactMatch);
}
/// <summary>
/// Loads stored credentials for the specified server if found. If no credentials are found for the specified server and options then default credentials are returned.
/// This overload assumes that the credentials are to be stored under the TFS server's registry root
/// </summary>
/// <param name="featureRegistryKeyword">An optional application name for isolated credential storage in the registry</param>
/// <param name="serverUrl">The server location</param>
/// <param name="requireExactMatch">A value indicating whether or not an exact or partial match of the server is required</param>
/// <returns>A credentials object populated with stored credentials for the server if found</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public static VssCredentials LoadCachedCredentials(
string featureRegistryKeyword,
Uri serverUrl,
bool requireExactMatch)
{
ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
bool uriKnownToCachedProvider = false;
VssCredentials cred = LoadCachedCredentialsFromRegisteredProviders(serverUrl, out uriKnownToCachedProvider);
// If one of the registered credential providers had the target URI in its cache but failed to return a valid credential it means
// we should have had a cred but something went wrong (user canceled, user failed, auth source unavailable, etc.). In that case
// we Do Not want to carry on with the fallback to the VS registry/windows store credential caches. Even if that worked to get a
// credential it would put the user in a bad state (having an active, authenticated connection with an unexpected credential type).
if (cred == null && !uriKnownToCachedProvider)
{
WindowsCredential windowsCredential = null;
FederatedCredential federatedCredential = null;
CredentialsCacheManager credentialsCacheManager = new CredentialsCacheManager();
TfsCredentialCacheEntry cacheEntry = credentialsCacheManager.GetCredentials(featureRegistryKeyword, serverUrl, requireExactMatch, null);
if (cacheEntry != null)
{
if (cacheEntry.NonInteractive)
{
switch (cacheEntry.Type)
{
case CachedCredentialsType.ServiceIdentity:
VssServiceIdentityToken initialToken = null;
string initialTokenValue = ReadAuthorizationToken(cacheEntry.Attributes);
if (!string.IsNullOrEmpty(initialTokenValue))
{
initialToken = new VssServiceIdentityToken(initialTokenValue);
}
// Initialize the issued token credential using the stored token if it exists
federatedCredential = new VssServiceIdentityCredential(cacheEntry.Credentials.UserName,
cacheEntry.Credentials.Password,
initialToken);
break;
case CachedCredentialsType.Windows:
windowsCredential = new WindowsCredential(cacheEntry.Credentials);
break;
}
}
}
cred = new VssCredentials(windowsCredential ?? new WindowsCredential(true), federatedCredential, CredentialPromptType.DoNotPrompt);
}
return cred ?? new VssCredentials(new WindowsCredential(true), null, CredentialPromptType.DoNotPrompt);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static VssCredentials LoadCachedCredentialsFromRegisteredProviders(Uri serverUri, out bool knownUri)
{
LoadRegisteredCachedVssCredentialProviders();
bool uriKnownByAnyProvider = false;
VssCredentials cred = null;
foreach (var pair in m_loadedCachedVssCredentialProviders)
{
bool uriKnownToProvider = false;
cred = pair.Value?.GetCachedCredentials(serverUri, out uriKnownToProvider);
if (cred != null || uriKnownToProvider)
{
uriKnownByAnyProvider |= uriKnownToProvider;
break;
}
}
knownUri = uriKnownByAnyProvider;
return cred;
}
private static void LoadRegisteredCachedVssCredentialProviders()
{
CredentialsProviderRegistryHelper.LoadCachedVssCredentialProviders(ref m_loadedCachedVssCredentialProviders);
}
private static ConcurrentDictionary<string, ICachedVssCredentialProvider> m_loadedCachedVssCredentialProviders = new ConcurrentDictionary<string, ICachedVssCredentialProvider>();
#endif
[EditorBrowsable(EditorBrowsableState.Never)]
public static void WriteAuthorizationToken(
string token,
@@ -604,7 +394,6 @@ namespace GitHub.Services.Common
private object m_thisLock;
private CredentialPromptType m_promptType;
private IssuedTokenProvider m_currentProvider;
protected WindowsCredential m_windowsCredential;
protected FederatedCredential m_federatedCredential;
private IVssCredentialStorage m_credentialStorage;
}

View File

@@ -1,164 +0,0 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides federated authentication as a service identity with a Visual Studio Service.
/// </summary>
[Serializable]
public sealed class VssServiceIdentityCredential : FederatedCredential
{
/// <summary>
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password.
/// </summary>
/// <param name="userName">The user name</param>
/// <param name="password">The password</param>
public VssServiceIdentityCredential(
string userName,
string password)
: this(userName, password, null)
{
}
/// <summary>
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password. The
/// provided token, if not null, will be used before attempting authentication with the credentials.
/// </summary>
/// <param name="userName">The user name</param>
/// <param name="password">The password</param>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
public VssServiceIdentityCredential(
string userName,
string password,
VssServiceIdentityToken initialToken)
: this(userName, password, initialToken, null)
{
}
/// <summary>
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified access token.
/// </summary>
/// <param name="token">A token which may be used for authorization as the desired service identity</param>
public VssServiceIdentityCredential(VssServiceIdentityToken token)
: this(null, null, token, null)
{
}
/// <summary>
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password. The
/// provided token, if not null, will be used before attempting authentication with the credentials.
/// </summary>
/// <param name="userName">The user name</param>
/// <param name="password">The password</param>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
/// <param name="innerHandler">An optional HttpMessageHandler which if passed will be passed along to the TokenProvider when executing OnCreateTokenProvider </param>
public VssServiceIdentityCredential(
string userName,
string password,
VssServiceIdentityToken initialToken,
DelegatingHandler innerHandler)
: base(initialToken)
{
m_userName = userName;
m_password = password;
m_innerHandler = innerHandler;
}
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.ServiceIdentity;
}
}
/// <summary>
/// Gets the user name.
/// </summary>
public String UserName
{
get
{
return m_userName;
}
}
/// <summary>
/// Gets the password.
/// </summary>
internal String Password
{
get
{
return m_password;
}
}
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (webResponse == null)
{
return false;
}
if (webResponse.StatusCode == HttpStatusCode.Found ||
webResponse.StatusCode == HttpStatusCode.Redirect ||
webResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authRealm = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
var authIssuer = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
var wwwAuthenticate = webResponse.Headers.GetValues(Internal.HttpHeaders.WwwAuthenticate);
if (!String.IsNullOrEmpty(authIssuer) && !String.IsNullOrEmpty(authRealm))
{
return webResponse.StatusCode != HttpStatusCode.Unauthorized || wwwAuthenticate.Any(x => x.StartsWith("TFS-Federated", StringComparison.OrdinalIgnoreCase));
}
}
return false;
}
internal override string GetAuthenticationChallenge(IHttpResponse webResponse)
{
var authRealm = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
var authIssuer = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
return string.Format(CultureInfo.InvariantCulture, "TFS-Federated realm={0}, issuer={1}", authRealm, authIssuer);
}
/// <summary>
/// Creates a provider for retrieving security tokens for the provided credentials.
/// </summary>
/// <returns>An issued token provider for the current credential</returns>
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
// The response is only null when attempting to determine the most appropriate token provider to
// use for the connection. The only way we should do anything here is if we have an initial token
// since that means we can present something without making a server call.
if (response == null && base.InitialToken == null)
{
return null;
}
Uri signInUrl = null;
String realm = string.Empty;
if (response != null)
{
realm = response.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
signInUrl = new Uri(new Uri(response.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault()).GetLeftPart(UriPartial.Authority));
}
return new VssServiceIdentityTokenProvider(this, serverUrl, signInUrl, realm, m_innerHandler);
}
private readonly String m_userName;
private readonly String m_password;
[NonSerialized]
private readonly DelegatingHandler m_innerHandler = null;
}
}

View File

@@ -1,114 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GitHub.Services.Common.Internal;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides simple web token used for OAuth authentication.
/// </summary>
[Serializable]
public sealed class VssServiceIdentityToken : IssuedToken
{
/// <summary>
/// Initializes a new <c>VssServiceIdentityToken</c> instance with the specified token value.
/// </summary>
/// <param name="token">The token value as a string</param>
public VssServiceIdentityToken(string token)
{
ArgumentUtility.CheckStringForNullOrEmpty(token, "token");
m_token = token;
//.ValidFrom = DateTime.UtcNow;
// Read out the expiration time for the ValidTo field if we can find it
Dictionary<string, string> nameValues;
if (TryGetNameValues(token, out nameValues))
{
string expiresOnValue;
if (nameValues.TryGetValue(c_expiresName, out expiresOnValue))
{
// The time is represented as standard epoch
// base.ValidTo = s_epoch.AddSeconds(Convert.ToUInt64(expiresOnValue, CultureInfo.CurrentCulture));
}
}
}
public String Token
{
get
{
return m_token;
}
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.ServiceIdentity;
}
}
internal override void ApplyTo(IHttpRequest request)
{
request.Headers.SetValue(Internal.HttpHeaders.Authorization, "WRAP access_token=\"" + m_token + "\"");
}
internal static VssServiceIdentityToken ExtractToken(string responseValue)
{
// Extract the actual token string
string token = UriUtility.UrlDecode(responseValue
.Split('&')
.Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
.Split('=')[1], VssHttpRequestSettings.Encoding);
return new VssServiceIdentityToken(token);
}
internal static bool TryGetNameValues(
string token,
out Dictionary<string, string> tokenValues)
{
tokenValues = null;
if (string.IsNullOrEmpty(token))
{
return false;
}
tokenValues =
token
.Split('&')
.Aggregate(
new Dictionary<string, string>(),
(dict, rawNameValue) =>
{
if (rawNameValue == string.Empty)
{
return dict;
}
string[] nameValue = rawNameValue.Split('=');
if (nameValue.Length != 2)
{
return dict;
}
if (dict.ContainsKey(nameValue[0]) == true)
{
return dict;
}
dict.Add(UriUtility.UrlDecode(nameValue[0]), UriUtility.UrlDecode(nameValue[1]));
return dict;
});
return true;
}
private string m_token;
private const string c_expiresName = "ExpiresOn";
}
}

View File

@@ -1,201 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Services.Common.Diagnostics;
using GitHub.Services.Common.Internal;
namespace GitHub.Services.Common
{
internal sealed class VssServiceIdentityTokenProvider : IssuedTokenProvider
{
public VssServiceIdentityTokenProvider(
VssServiceIdentityCredential credential,
Uri serverUrl,
Uri signInUrl,
string realm,
DelegatingHandler innerHandler)
: this(credential, serverUrl, signInUrl, realm)
{
m_innerHandler = innerHandler;
}
public VssServiceIdentityTokenProvider(
VssServiceIdentityCredential credential,
Uri serverUrl,
Uri signInUrl,
string realm)
: base(credential, serverUrl, signInUrl)
{
Realm = realm;
}
protected override string AuthenticationParameter
{
get
{
if (string.IsNullOrEmpty(this.Realm) && this.SignInUrl == null)
{
return string.Empty;
}
else
{
return string.Format(CultureInfo.InvariantCulture, "issuer=\"{0}\", realm=\"{1}\"", this.SignInUrl, this.Realm);
}
}
}
protected override String AuthenticationScheme
{
get
{
return "TFS-Federated";
}
}
/// <summary>
/// Gets the simple web token credential from which this provider was created.
/// </summary>
public new VssServiceIdentityCredential Credential
{
get
{
return (VssServiceIdentityCredential)base.Credential;
}
}
/// <summary>
/// Gets a value indicating whether or not a call to get token will require interactivity.
/// </summary>
public override Boolean GetTokenIsInteractive
{
get
{
return false;
}
}
/// <summary>
/// Gets the realm for the token provider.
/// </summary>
public String Realm
{
get;
}
protected internal override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (!base.IsAuthenticationChallenge(webResponse))
{
return false;
}
// This means we were proactively constructed without any connection information. In this case
// we return false to ensure that a new provider is reconstructed with all appropriate configuration
// to retrieve a new token.
if (this.SignInUrl == null)
{
return false;
}
string authRealm = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
string authIssuer = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
Uri signInUrl = new Uri(new Uri(authIssuer).GetLeftPart(UriPartial.Authority), UriKind.Absolute);
// Make sure that the values match our stored values. This way if the values change we will be thrown
// away and a new instance with correct values will be constructed.
return this.Realm.Equals(authRealm, StringComparison.OrdinalIgnoreCase) &&
Uri.Compare(this.SignInUrl, signInUrl, UriComponents.AbsoluteUri, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0;
}
/// <summary>
/// Issues a request to synchronously retrieve a token for the associated credential.
/// </summary>
/// <param name="failedToken"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override async Task<IssuedToken> OnGetTokenAsync(
IssuedToken failedToken,
CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(this.Credential.UserName) ||
string.IsNullOrEmpty(this.Credential.Password))
{
return null;
}
VssTraceActivity traceActivity = VssTraceActivity.Current;
using (HttpClient client = new HttpClient(CreateMessageHandler(), false))
{
client.BaseAddress = this.SignInUrl;
KeyValuePair<string, string>[] values = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("wrap_name", this.Credential.UserName),
new KeyValuePair<string, string>("wrap_password", this.Credential.Password),
new KeyValuePair<string, string>("wrap_scope", this.Realm),
};
Uri url = new Uri("WRAPv0.9/", UriKind.Relative);
FormUrlEncodedContent content = new FormUrlEncodedContent(values);
using (HttpResponseMessage response = await client.PostAsync(url, content, cancellationToken).ConfigureAwait(false))
{
string responseValue = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return VssServiceIdentityToken.ExtractToken(responseValue);
}
else
{
VssHttpEventSource.Log.AuthenticationError(traceActivity, this, responseValue);
return null;
}
}
}
}
private HttpMessageHandler CreateMessageHandler()
{
var retryOptions = new VssHttpRetryOptions()
{
RetryableStatusCodes =
{
VssNetworkHelper.TooManyRequests,
HttpStatusCode.InternalServerError,
},
};
HttpMessageHandler innerHandler;
if (m_innerHandler != null)
{
if (m_innerHandler.InnerHandler == null)
{
m_innerHandler.InnerHandler = new HttpClientHandler();
}
innerHandler = m_innerHandler;
}
else
{
innerHandler = new HttpClientHandler();
}
// Inherit proxy setting from VssHttpMessageHandler
var httpClientHandler = innerHandler as HttpClientHandler;
if (httpClientHandler != null && VssHttpMessageHandler.DefaultWebProxy != null)
{
httpClientHandler.Proxy = VssHttpMessageHandler.DefaultWebProxy;
httpClientHandler.UseProxy = true;
}
return new VssHttpRetryMessageHandler(retryOptions, innerHandler);
}
private DelegatingHandler m_innerHandler = null;
}
}

View File

@@ -1,137 +0,0 @@
using System;
using System.Linq;
using System.Net;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides a credential for windows authentication against a Visual Studio Service.
/// </summary>
public sealed class WindowsCredential : IssuedTokenCredential
{
/// <summary>
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
/// and the default network credentials.
/// </summary>
public WindowsCredential()
: this(true)
{
}
/// <summary>
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
/// and the default network credentials, if specified.
/// </summary>
/// <param name="useDefaultCredentials">True if the default credentials should be used; otherwise, false</param>
public WindowsCredential(bool useDefaultCredentials)
: this(useDefaultCredentials ? CredentialCache.DefaultCredentials : null)
{
UseDefaultCredentials = useDefaultCredentials;
}
/// <summary>
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
/// and the specified network credentials.
/// </summary>
/// <param name="credentials">The windows credentials which should be used for authentication</param>
public WindowsCredential(ICredentials credentials)
: this(null)
{
m_credentials = credentials;
UseDefaultCredentials = credentials == CredentialCache.DefaultCredentials;
}
/// <summary>
/// Initializes a new <c>WindowsCredential</c> instance using the specified initial token.
/// </summary>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
public WindowsCredential(WindowsToken initialToken)
: base(initialToken)
{
}
/// <summary>
/// Gets the credentials associated with this windows credential.
/// </summary>
public ICredentials Credentials
{
get
{
return m_credentials;
}
set
{
m_credentials = value;
UseDefaultCredentials = Credentials == CredentialCache.DefaultCredentials;
}
}
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Windows;
}
}
/// <summary>
/// Gets a value indicating what value was passed to WindowsCredential(bool useDefaultCredentials) constructor
/// </summary>
public Boolean UseDefaultCredentials
{
get;
private set;
}
public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (webResponse == null)
{
return false;
}
if (webResponse.StatusCode == HttpStatusCode.Unauthorized &&
webResponse.Headers.GetValues(Internal.HttpHeaders.WwwAuthenticate).Any(x => AuthenticationSchemeValid(x)))
{
return true;
}
if (webResponse.StatusCode == HttpStatusCode.ProxyAuthenticationRequired &&
webResponse.Headers.GetValues(Internal.HttpHeaders.ProxyAuthenticate).Any(x => AuthenticationSchemeValid(x)))
{
return true;
}
return false;
}
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
// If we have no idea what kind of credentials we are supposed to be using, don't play a windows token on
// the first request.
if (response == null)
{
return null;
}
if (m_credentials != null)
{
this.InitialToken = new WindowsToken(m_credentials);
}
return new WindowsTokenProvider(this, serverUrl);
}
private static Boolean AuthenticationSchemeValid(String authenticateHeader)
{
return authenticateHeader.StartsWith("Basic", StringComparison.OrdinalIgnoreCase) ||
authenticateHeader.StartsWith("Digest", StringComparison.OrdinalIgnoreCase) ||
authenticateHeader.StartsWith("Negotiate", StringComparison.OrdinalIgnoreCase) ||
authenticateHeader.StartsWith("Ntlm", StringComparison.OrdinalIgnoreCase);
}
private ICredentials m_credentials;
}
}

View File

@@ -1,39 +0,0 @@
using System;
using System.Net;
namespace GitHub.Services.Common
{
public sealed class WindowsToken : IssuedToken, ICredentials
{
internal WindowsToken(ICredentials credentials)
{
this.Credentials = credentials;
}
public ICredentials Credentials
{
get;
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Windows;
}
}
internal override void ApplyTo(IHttpRequest request)
{
// Special-cased by the caller because we implement ICredentials
throw new InvalidOperationException();
}
NetworkCredential ICredentials.GetCredential(
Uri uri,
String authType)
{
return this.Credentials?.GetCredential(uri, authType);
}
}
}

View File

@@ -1,40 +0,0 @@
using System;
using System.Globalization;
using System.Net;
namespace GitHub.Services.Common
{
internal sealed class WindowsTokenProvider : IssuedTokenProvider
{
public WindowsTokenProvider(
WindowsCredential credential,
Uri serverUrl)
: base(credential, serverUrl, serverUrl)
{
}
protected override String AuthenticationScheme
{
get
{
return String.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", AuthenticationSchemes.Negotiate, AuthenticationSchemes.Ntlm, AuthenticationSchemes.Digest, AuthenticationSchemes.Basic);
}
}
public new WindowsCredential Credential
{
get
{
return (WindowsCredential)base.Credential;
}
}
public override Boolean GetTokenIsInteractive
{
get
{
return base.CurrentToken == null;
}
}
}
}

View File

@@ -5,9 +5,6 @@ using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
#if !NETSTANDARD
using System.Diagnostics.Eventing;
#endif
namespace GitHub.Services.Common.Diagnostics
{
@@ -838,13 +835,6 @@ namespace GitHub.Services.Common.Diagnostics
[NonEvent]
private void SetActivityId(VssTraceActivity activity)
{
#if !NETSTANDARD
if (activity != null)
{
Guid activityId = activity.Id;
EventProvider.SetActivityId(ref activityId);
}
#endif
}
[NonEvent]
@@ -876,15 +866,6 @@ namespace GitHub.Services.Common.Diagnostics
Action<Int32, String> writeEvent)
{
writeEvent(param0, message);
#if !NETSTANDARD
if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig)
{
foreach (String messagePart in SplitMessage(message))
{
writeEvent(param0, messagePart);
}
}
#endif
}
[NonEvent]
@@ -895,15 +876,6 @@ namespace GitHub.Services.Common.Diagnostics
Action<VssCredentialsType, Int32, String> writeEvent)
{
writeEvent(param0, param1, message);
#if !NETSTANDARD
if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig)
{
foreach (String messagePart in SplitMessage(message))
{
writeEvent(param0, param1, messagePart);
}
}
#endif
}
[NonEvent]
@@ -914,23 +886,10 @@ namespace GitHub.Services.Common.Diagnostics
Action<VssHttpMethod, String, String> writeEvent)
{
writeEvent(param0, param1, message);
#if !NETSTANDARD
if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig)
{
foreach (String messagePart in SplitMessage(message))
{
writeEvent(param0, param1, messagePart);
}
}
#endif
}
[NonEvent]
#if !NETSTANDARD
private unsafe void WriteEvent(
#else
private new unsafe void WriteEvent(
#endif
Int32 eventId,
Int32 param0,
String param1)

View File

@@ -1,8 +1,5 @@
using System;
using System.Diagnostics;
#if !NETSTANDARD
using System.Runtime.Remoting.Messaging;
#endif
using System.Runtime.Serialization;
namespace GitHub.Services.Common.Diagnostics
@@ -38,22 +35,11 @@ namespace GitHub.Services.Common.Diagnostics
/// </summary>
public static VssTraceActivity Current
{
#if !NETSTANDARD
get
{
return CallContext.LogicalGetData(VssTraceActivity.PropertyName) as VssTraceActivity;
}
private set
{
CallContext.LogicalSetData(VssTraceActivity.PropertyName, value);
}
#else
get
{
return null;
}
set { }
#endif
}
/// <summary>

View File

@@ -8,84 +8,28 @@ namespace GitHub.Services.Common.Internal
public static class HttpHeaders
{
public const String ActivityId = "ActivityId";
public const String ETag = "ETag";
public const String TfsVersion = "X-TFS-Version";
public const String TfsRedirect = "X-TFS-Redirect";
public const String TfsException = "X-TFS-Exception";
public const String TfsServiceError = "X-TFS-ServiceError";
public const String TfsSessionHeader = "X-TFS-Session";
public const String TfsSoapException = "X-TFS-SoapException";
public const String TfsFedAuthRealm = "X-TFS-FedAuthRealm";
public const String TfsFedAuthIssuer = "X-TFS-FedAuthIssuer";
public const String TfsFedAuthRedirect = "X-TFS-FedAuthRedirect";
public const String VssAuthorizationEndpoint = "X-VSS-AuthorizationEndpoint";
public const String VssPageHandlers = "X-VSS-PageHandlers";
public const String VssE2EID = "X-VSS-E2EID";
public const String VssOrchestrationId = "X-VSS-OrchestrationId";
public const String AuditCorrelationId = "X-VSS-Audit-CorrelationId";
public const String VssOriginUserAgent = "X-VSS-OriginUserAgent";
// Internal Headers that we use in our client.
public const string TfsInstanceHeader = "X-TFS-Instance";
public const string TfsVersionOneHeader = "X-VersionControl-Instance";
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tfs")]
public const string TfsImpersonate = "X-TFS-Impersonate";
public const string TfsSubjectDescriptorImpersonate = "X-TFS-SubjectDescriptorImpersonate";
public const string MsContinuationToken = "X-MS-ContinuationToken";
public const String VssUserData = "X-VSS-UserData";
public const String VssAgentHeader = "X-VSS-Agent";
public const String VssAuthenticateError = "X-VSS-AuthenticateError";
public const string VssReauthenticationAction = "X-VSS-ReauthenticationAction";
public const string RequestedWith = "X-Requested-With";
public const String VssRateLimitResource = "X-RateLimit-Resource";
public const String VssRateLimitDelay = "X-RateLimit-Delay";
public const String VssRateLimitLimit = "X-RateLimit-Limit";
public const String VssRateLimitRemaining = "X-RateLimit-Remaining";
public const String VssRateLimitReset = "X-RateLimit-Reset";
public const String RetryAfter = "Retry-After";
public const String VssGlobalMessage = "X-VSS-GlobalMessage";
public const String VssRequestRouted = "X-VSS-RequestRouted";
public const String VssUseRequestRouting = "X-VSS-UseRequestRouting";
public const string VssResourceTenant = "X-VSS-ResourceTenant";
public const String VssOverridePrompt = "X-VSS-OverridePrompt";
public const String VssOAuthS2STargetService = "X-VSS-S2STargetService";
public const String VssHostOfflineError = "X-VSS-HostOfflineError";
public const string VssForceMsaPassThrough = "X-VSS-ForceMsaPassThrough";
public const string VssRequestPriority = "X-VSS-RequestPriority";
// This header represents set of ';' delimited mappings (usually one) that are considered by DetermineAccessMapping API
public const string VssClientAccessMapping = "X-VSS-ClientAccessMapping";
// This header is used to download artifacts anonymously.
// N.B. Some resources secured with download tickets (e.g. TFVC files) are still retrieved with the download
// ticket in the query string.
public const string VssDownloadTicket = "X-VSS-DownloadTicket";
public const string IfModifiedSince = "If-Modified-Since";
public const string Authorization = "Authorization";
public const string Location = "Location";
public const string ProxyAuthenticate = "Proxy-Authenticate";
public const string WwwAuthenticate = "WWW-Authenticate";
public const string AfdIncomingRouteKey = "X-FD-RouteKey";
public const string AfdOutgoingRouteKey = "X-AS-RouteKey";
public const string AfdIncomingEndpointList = "X-FD-RouteKeyApplicationEndpointList";
public const string AfdOutgoingEndpointList = "X-AS-RouteKeyApplicationEndpointList";
public const string AfdResponseRef = "X-MSEdge-Ref";
public const string AfdIncomingClientIp = "X-FD-ClientIP";
public const string AfdIncomingSocketIp = "X-FD-SocketIP";
public const string AfdIncomingRef = "X-FD-Ref";
public const string AfdIncomingEventId = "X-FD-EventId";
public const string AfdIncomingEdgeEnvironment = "X-FD-EdgeEnvironment";
public const string AfdOutgoingQualityOfResponse = "X-AS-QualityOfResponse";
public const string AfdOutgoingClientIp = "X-MSEdge-ClientIP";
}
}

View File

@@ -1,553 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides path normalization/expansion for absolute, relative and UNC-style paths
/// and supports paths that contain more than 248 characters.
/// </summary>
/// <remarks>
/// This utility class can be used in place of the .NET Path and Directory classes
/// that throw System.IO.PathTooLongException when paths are longer than 248 characters
/// </remarks>
public static class LongPathUtility
{
private static Regex AbsolutePathRegEx = new Regex(@"^([a-zA-Z]:\\|\\\\)", RegexOptions.CultureInvariant | RegexOptions.Compiled);
private const int ERROR_FILE_NOT_FOUND = 2;
/// <summary>
/// Returns a list of directory names under the path specified, and optionally all subdirectories
/// </summary>
/// <param name="path">The directory to search</param>
/// <param name="recursiveSearch">Specifies whether the search operation should include only the currect directory or all subdirectories</param>
/// <returns>A list of all subdirectories</returns>
public static IEnumerable<string> EnumerateDirectories(string path, bool recursiveSearch)
{
var directoryPaths = new List<string>();
EnumerateDirectoriesInternal(directoryPaths, path, recursiveSearch);
return directoryPaths;
}
/// <summary>
/// Returns a list of file names under the path specified, and optionally within all subdirectories.
/// </summary>
/// <param name="path">The directory to search</param>
/// <param name="recursiveSearch">Specifies whether the search operation should include only the current directory or all subdirectories</param>
/// <returns>
/// A list of full file names(including path) contained in the directory specified that match the specified search pattern.</returns>
public static IEnumerable<string> EnumerateFiles(string path, bool recursiveSearch)
{
return EnumerateFiles(path, "*", recursiveSearch);
}
/// <summary>
/// Returns an enumerable collection of file names that match a search pattern in a specified path,
/// and optionally searches subdirectories.
/// </summary>
/// <param name="path">The directory to search</param>
/// <param name="matchPattern">The search string to match against the names of the files</param>
/// <param name="recursiveSearch">Specifies whether the search operation should include only the current directory or all subdirectories</param>
/// <returns>
/// A list of full file names(including path) contained in the directory specified (and subdirectories optionally) that match the specified pattern.
/// </returns>
public static IEnumerable<string> EnumerateFiles(string path, string matchPattern, bool recursiveSearch)
{
if (!DirectoryExists(path))
{
throw new DirectoryNotFoundException($"The path '{path}' is not a valid directory.");
}
var filePaths = new List<string>();
EnumerateFilesInternal(filePaths, path, matchPattern, recursiveSearch);
return filePaths;
}
/// <summary>
/// Returns true/false whether the file exists. This method inspects the
/// file system attributes and supports files without extensions (ex: DIRS, Sources). This method
/// supports file paths that are longer than 260 characters.
/// </summary>
/// <param name="filePath">The file path to inspect</param>
/// <returns>
/// True if the file exists or false if not
/// </returns>
public static bool FileExists(string filePath)
{
return FileOrDirectoryExists(filePath, isDirectory: false);
}
/// <summary>
/// Returns true/false whether the directory exists. This method inspects the
/// file system attributes and supports files without extensions (ex: DIRS, Sources). This method
/// supports file paths that are longer than 260 characters.
/// </summary>
/// <param name="directoryPath">The file path to inspect</param>
/// <returns>
/// True if the directory exists or false if not
/// </returns>
public static bool DirectoryExists(string directoryPath)
{
return FileOrDirectoryExists(directoryPath, isDirectory: true);
}
private static bool FileOrDirectoryExists(string filePath, bool isDirectory)
{
if (String.IsNullOrWhiteSpace(filePath))
{
throw new ArgumentException("A path to the file is required and cannot be null, empty or whitespace", "filePath");
}
bool pathExists = false;
// File names may or may not include an extension (ex: DIRS, Sources). We have to look at the attributes
// on the file system object in order to distinguish a directory from a file
var attributes = (FlagsAndAttributes)NativeMethods.GetFileAttributes(filePath);
if (attributes != FlagsAndAttributes.InvalidFileAttributes)
{
bool pathIsDirectory = (attributes & FlagsAndAttributes.Directory) == FlagsAndAttributes.Directory;
if (pathIsDirectory == isDirectory)
{
pathExists = true;
}
}
return pathExists;
}
/// <summary>
/// Returns the fully expanded/normalized path. This method supports paths that are
/// longer than 248 characters.
/// </summary>
/// <param name="path">The file or directory path</param>
/// <returns></returns>
public static string GetFullNormalizedPath(string path)
{
if (String.IsNullOrWhiteSpace(path))
{
throw new ArgumentException("A path is required and cannot be null, empty or whitespace", "path");
}
string outPath = path;
// We need the length of the absolute path in order to prepare a buffer of
// the correct size
uint bufferSize = NativeMethods.GetFullPathName(path, 0, null, null);
int lastWin32Error = Marshal.GetLastWin32Error();
if (bufferSize > 0)
{
var absolutePath = new StringBuilder((int)bufferSize);
uint length = NativeMethods.GetFullPathName(path, bufferSize, absolutePath, null);
lastWin32Error = Marshal.GetLastWin32Error();
if (length > 0)
{
outPath = absolutePath.ToString();
}
else
{
// Path resolution failed
throw new Win32Exception(
lastWin32Error,
String.Format(
CultureInfo.InvariantCulture,
"Path normalization/expansion failed. The path length was not returned by the Kernel32 subsystem for '{0}'.",
path
)
);
}
}
else
{
// Path resolution failed and the path length could not
// be determined
throw new Win32Exception(
lastWin32Error,
String.Format(
CultureInfo.InvariantCulture,
"Path normalization/expansion failed. A full path was not returned by the Kernel32 subsystem for '{0}'.",
path
)
);
}
return outPath != null ? outPath.TrimEnd('\\') : null;
}
/// <summary>
/// Determines whether the specified path is an absolute path or not.
/// </summary>
/// <param name="path">The path to be tested.</param>
/// <returns>
/// <c>true</c> if the path is absolute; otherwise, <c>false</c>.
/// </returns>
public static bool IsAbsolutePath(string path)
{
return LongPathUtility.AbsolutePathRegEx.Match(path).Success;
}
public static string RemoveExtendedLengthPathPrefix(string inPath)
{
string outPath = inPath;
if (!String.IsNullOrWhiteSpace(inPath))
{
if (inPath.StartsWith("\\", StringComparison.OrdinalIgnoreCase))
{
// ex: \\?\UNC\server\share to \\server\share
outPath = inPath.Replace(@"\\?\UNC", @"\");
// ex: \\?\c:\windows to c:\windows
outPath = outPath.Replace(@"\\?\", String.Empty);
}
}
return outPath;
}
private static string CombinePaths(string pathA, string pathB)
{
if (pathA == null)
{
throw new ArgumentNullException("pathA");
}
if (pathB == null)
{
throw new ArgumentNullException("pathB");
}
// The Path class does not suffer from the 248/260 character limitation
// that the File and Directory classes do.
return Path.Combine(
pathA.TrimEnd('\\'),
pathB.TrimStart('\\')
);
}
private static string ConvertToExtendedLengthPath(string path)
{
string extendedLengthPath = GetFullNormalizedPath(path);
if (!String.IsNullOrWhiteSpace(extendedLengthPath))
{
//no need to modify- it's already unicode
if (!extendedLengthPath.StartsWith(@"\\?", StringComparison.OrdinalIgnoreCase))
{
// ex: \\server\share
if (extendedLengthPath.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase))
{
// make it \\?\UNC\server\share
extendedLengthPath = String.Format(
CultureInfo.InvariantCulture,
@"\\?\UNC{0}",
extendedLengthPath.Substring(1)
);
}
else //not unicode already, and not UNC
{
extendedLengthPath = String.Format(
CultureInfo.InvariantCulture,
@"\\?\{0}",
extendedLengthPath
);
}
}
}
return extendedLengthPath;
}
private static IEnumerable<string> EnumerateDirectoriesInPath(string path)
{
SafeFindHandle handle = null;
var findData = new FindData();
var childDirectories = new List<string>();
using (handle = NativeMethods.FindFirstFile(CombinePaths(ConvertToExtendedLengthPath(path), "*"), findData))
{
if (!handle.IsInvalid)
{
bool searchComplete = false;
do
{
// skip the dot directories
if (!findData.fileName.Equals(@".") && !findData.fileName.Equals(@".."))
{
if ((findData.fileAttributes & (int)FileAttributes.Directory) != 0)
{
childDirectories.Add(RemoveExtendedLengthPathPrefix(CombinePaths(path, findData.fileName)));
}
}
if (NativeMethods.FindNextFile(handle, findData))
{
if (handle.IsInvalid)
{
throw new Win32Exception(
Marshal.GetLastWin32Error(),
String.Format(
CultureInfo.InvariantCulture,
"Enumerating subdirectories for path '{0}' failed.",
path
)
);
}
}
else
{
searchComplete = true;
}
} while (!searchComplete);
}
}
return childDirectories;
}
private static IEnumerable<string> EnumerateFilesInPath(string path, string matchPattern)
{
SafeFindHandle handle = null;
var findData = new FindData();
var fullFilePaths = new List<string>();
using (handle = NativeMethods.FindFirstFile(CombinePaths(ConvertToExtendedLengthPath(path), matchPattern), findData))
{
int lastWin32Error = Marshal.GetLastWin32Error();
if (handle.IsInvalid)
{
if (lastWin32Error != ERROR_FILE_NOT_FOUND)
{
throw new Win32Exception(
lastWin32Error,
String.Format(CultureInfo.InvariantCulture, "Enumerating files for path '{0}' failed.", path)
);
}
}
else
{
bool searchComplete = false;
do
{
// skip the dot directories
if (!findData.fileName.Equals(@".") && !findData.fileName.Equals(@".."))
{
if ((findData.fileAttributes & (int)FileAttributes.Directory) == 0)
{
fullFilePaths.Add(RemoveExtendedLengthPathPrefix(CombinePaths(path, findData.fileName)));
}
}
if (NativeMethods.FindNextFile(handle, findData))
{
lastWin32Error = Marshal.GetLastWin32Error();
if (handle.IsInvalid)
{
throw new Win32Exception(
lastWin32Error,
String.Format(
CultureInfo.InvariantCulture,
"Enumerating subdirectories for path '{0}' failed.",
path
)
);
}
}
else
{
searchComplete = true;
}
} while (!searchComplete);
}
}
return fullFilePaths;
}
private static void EnumerateFilesInternal(List<string> filePaths, string path, string matchPattern, bool recursiveSearch)
{
var fullFilePaths = EnumerateFilesInPath(path, matchPattern);
if (fullFilePaths.Any())
{
lock (filePaths)
{
filePaths.AddRange(fullFilePaths);
}
}
if (recursiveSearch)
{
var directorySearchPaths = EnumerateDirectoriesInPath(path);
if (directorySearchPaths.Any())
{
Parallel.ForEach(
directorySearchPaths,
(searchPath) =>
{
EnumerateFilesInternal(filePaths, searchPath, matchPattern, recursiveSearch);
}
);
}
}
}
public static void EnumerateDirectoriesInternal(List<string> directoryPaths, string path, bool recursiveSearch)
{
var directorySearchPaths = EnumerateDirectoriesInPath(path);
if (directorySearchPaths.Any())
{
lock (directoryPaths)
{
directoryPaths.AddRange(directorySearchPaths);
}
if (recursiveSearch)
{
// This will not ensure that the directory paths are added to the list
// in alphabetical order but does provide performance 2 - 4 times better than the
// canonical Directory.GetDirectories() method.
Parallel.ForEach(
directorySearchPaths,
(searchPath) =>
{
EnumerateDirectoriesInternal(directoryPaths, searchPath, recursiveSearch);
}
);
}
}
}
/// <summary>
/// Kernel32.dll native interop methods for use with utility file/path parsing
/// operations
/// </summary>
private static class NativeMethods
{
private const string Kernel32Dll = "kernel32.dll";
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindClose(IntPtr hFindFile);
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.alternateFileName")]
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.fileName")]
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern SafeFindHandle FindFirstFile(
[MarshalAs(UnmanagedType.LPTStr)]
string fileName,
[In, Out] FindData findFileData
);
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.alternateFileName")]
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.fileName")]
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindNextFile(SafeFindHandle hFindFile, [In, Out] FindData lpFindFileData);
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern int GetFileAttributes(string lpFileName);
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern uint GetFullPathName(
[MarshalAs(UnmanagedType.LPTStr)]
string lpFileName,
uint nBufferLength,
[Out]
StringBuilder lpBuffer,
StringBuilder lpFilePart
);
}
//for mapping to the WIN32_FIND_DATA native structure
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Reviewed.")]
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed.")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private sealed class FindData
{
// NOTE:
// Although it may seem correct to Marshal the string members of this class as UnmanagedType.LPWStr, they
// must explicitly remain UnmanagedType.ByValTStr with the size constraints noted. Otherwise we end up with
// COM Interop exceptions while trying to marshal the data across the PInvoke boundaries. We thus require the StyleCop
// suppressions on the NativeMethods.FindNextFile() method above.
public int fileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME creationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME lastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME lastWriteTime;
public int nFileSizeHigh;
public int nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string fileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string alternateFileName;
}
//A Win32 safe find handle in which a return value of -1 indicates it's invalid
private sealed class SafeFindHandle : Microsoft.Win32.SafeHandles.SafeHandleMinusOneIsInvalid
{
public SafeFindHandle()
: base(true)
{
return;
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
protected override bool ReleaseHandle()
{
return NativeMethods.FindClose(handle);
}
}
[Flags]
private enum FlagsAndAttributes : uint
{
None = 0x00000000,
Readonly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Directory = 0x00000010,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
SparseFile = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotContentIndexed = 0x00002000,
Encrypted = 0x00004000,
Write_Through = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x08000000,
DeleteOnClose = 0x04000000,
BackupSemantics = 0x02000000,
PosixSemantics = 0x01000000,
OpenReparsePoint = 0x00200000,
OpenNoRecall = 0x00100000,
FirstPipeInstance = 0x00080000,
InvalidFileAttributes = 0xFFFFFFFF // Returned by GetFileAttributes on Non existant path
}
}
}

View File

@@ -218,7 +218,6 @@ namespace GitHub.Services.Common
}
}
#if NETSTANDARD
/// <summary>
/// Portable compliant way to get a constructor with specified arguments. This will return a constructor that is public or private as long as the arguments match. NULL will be returned if there is no match.
/// Note that it will pick the first one it finds that matches, which is not necesarily the best match.
@@ -263,7 +262,6 @@ namespace GitHub.Services.Common
}
return null;
}
#endif
private static PropertyInfo GetPublicInstancePropertyInfo(Type type, string name)
{

View File

@@ -1,18 +1,4 @@
// ************************************************************************************************
// Microsoft Team Foundation
//
// Microsoft Confidential
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: VssStringComparer.cs
// Area: Team Foundation
// Classes: VssStringComparer
// Contents: The Team Foundation string comparison class provides inner classes
// that are used to provide semantic-specific Equals and Compare methods
// and a semantic-specific StringComparer instance. New semantics should
// be added on an as-needed basis.
// ************************************************************************************************
using System;
using System;
using System.Diagnostics;
namespace GitHub.Services.Common

View File

@@ -13,9 +13,6 @@ namespace GitHub.Services.Common.Internal
{
[EditorBrowsable(EditorBrowsableState.Never)]
#if !NETSTANDARD
[CLSCompliant(false)]
#endif
public static class XmlUtility
{
internal static FileStream OpenFile(String path, FileShare sharing, Boolean saveFile)

View File

@@ -5,16 +5,6 @@ using System.Diagnostics.CodeAnalysis;
namespace GitHub.Services.Common
{
public static class AdminConstants
{
/// <summary>
/// Each incoming web request is assigned a server process id, this constant defines
/// an element within the Context.Items[] to hold that value.
/// </summary>
public const String ServerProcessID = "serverProcessID";
public const String ApplicationName = "ApplicationName";
}
[GenerateSpecificConstants]
public static class IdentityConstants
{
@@ -352,370 +342,6 @@ namespace GitHub.Services.Common
public static readonly ISet<string> WhiteListedProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
public static class DirectoryRoleConstants
{
/// Name of the directory role that represents "Company Administrator/Global Admin"
public const string CompanyAdministrator = "Company Administrator";
}
// Used with Registration entries
[GenerateSpecificConstants]
public static class ToolNames
{
public const string Framework = "Framework";
[GenerateConstant]
public const string VersionControl = "VersionControl";
[GenerateConstant]
public const string WorkItemTracking = "WorkItemTracking";
[GenerateConstant]
public const string RemoteWorkItemTracking = "RemoteWorkItemTracking";
public const string CoreServices = "vstfs";
public const string Warehouse = "Reports";
[GenerateConstant]
public const string TeamBuild = "Build";
public const string ProxyServer = "ps";
public const string TeamFoundation = "vstfs";
public const string SharePoint = "Wss";
[GenerateConstant]
public const string TestManagement = "TestManagement";
public const string LabManagement = "LabManagement";
public const string ReleaseManagement = "ReleaseManagement";
public const string SyncService = "SyncService";
public const string TestRig = "TestRig";
public const string TSWebAccess = "TSWebAccess";
public const string ProjectServer = "ProjectServer";
public const string DeploymentRig = "DeploymentRig";
public const string TeamProjects = "TeamProjects"; // contains specific project registration entries (project portal, process guidance and doc url)
public const string Discussion = "Discussion";
[GenerateConstant]
public const string Requirements = "Requirements";
[GenerateConstant]
public const string Hyperlink = "Hyperlink";
public const string Classification = "Classification";
[GenerateConstant]
public const string Legacy = "Legacy";
[GenerateConstant]
public const string CodeSense = "CodeSense";
[GenerateConstant]
public const string Git = "Git";
[GenerateConstant]
public const string CodeReview = "CodeReview";
[GenerateConstant]
public const string ProjectDownload = "ProjectDownload";
public const string DistributedTask = "DistributedTask";
[GenerateConstant]
public const string Wiki = "Wiki";
public const string Search = "Search";
[GenerateConstant]
public const string GitHub = "GitHub";
}
// Artifact types
[GenerateSpecificConstants]
public static class ArtifactTypeNames
{
public const string Project = "TeamProject";
public const string Node = "Node";
public const string Collector = "Collector";
public const string TestResult = "TestResult";
[GenerateConstant]
public const string TcmResult = "TcmResult";
[GenerateConstant]
public const string TcmResultAttachment = "TcmResultAttachment";
[GenerateConstant]
public const string TcmTest = "TcmTest";
[GenerateConstant]
public const string Build = "Build";
public const string BuildAgent = "Agent";
public const string BuildDefinition = "Definition";
public const string BuildController = "Controller";
public const string BuildGroup = "Group";
public const string BuildRequest = "Request";
public const string BuildServiceHost = "ServiceHost";
[GenerateConstant]
public const string VersionedItem = "VersionedItem";
[GenerateConstant]
public const string LatestItemVersion = "LatestItemVersion";
[GenerateConstant]
public const string Changeset = "Changeset";
public const string Label = "Label";
[GenerateConstant]
public const string Shelveset = "Shelveset";
public const string ShelvedItem = "ShelvedItem";
[GenerateConstant]
public const string WorkItem = "WorkItem";
public const string Query = "Query";
public const string Results = "Results";
public const string LabEnvironment = "LabEnvironment";
public const string LabTemplate = "LabTemplate";
public const string LabSystem = "LabSystem";
public const string TeamProjectHostGroup = "TeamProjectHostGroup";
public const string TeamProjectLibraryShare = "TeamProjectLibraryShare";
public const string TeamProjectCollectionLibraryShare = "TeamProjectCollectionLibraryShare";
public const string TeamProjectCollectionHostGroup = "TeamProjectCollectionHostGroup";
public const string TestMachine = "TestMachine";
[GenerateConstant]
public const string Storyboard = "Storyboard";
[GenerateConstant]
public const string Commit = "Commit";
public const string LaunchLatestVersionedItem = "LaunchLatestVersionedItem";
[GenerateConstant]
public const string CodeReviewId = "CodeReviewId";
[GenerateConstant]
public const string CodeReviewSdkId = "ReviewId";
[GenerateConstant]
public const string PullRequestId = "PullRequestId";
[GenerateConstant]
public const string ProjectDownloadProject = "Project";
/// <summary>
/// A Git Ref
/// </summary>
[GenerateConstant]
public const string Ref = "Ref";
public const string TaskAgentPoolMaintenance = "PoolMaintenance";
[GenerateConstant]
public const string WikiPage = "WikiPage";
// GitHub
[GenerateConstant]
public const string PullRequest = "PullRequest";
[GenerateConstant]
public const string Issue = "Issue";
}
/// <summary>
/// Constant strings used in Notifications
/// </summary>
public static class NotificationConstants
{
/// <summary>
/// Macro used in subscriptions which will be replaced by the project name when evaluated
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.MyProjectNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String MyProjectNameMacro = "@@MyProjectName@@";
/// <summary>
/// Macro used in subscriptions which will be replaced by the subscriber's Display Name when evaluated
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.MyDisplayNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String MyDisplayNameMacro = "@@MyDisplayName@@";
/// <summary>
/// Macro used in subscriptions which will be replaced by the subscriber's Unique User Name when evaluated
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.MyUniqueNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String MyUniqueNameMacro = "@@MyUniqueName@@";
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.SingleQuoteNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String SingleQuoteNameMacro = "@@SQBDQ@@"; //SingleQuoteBetweenDoubleQuotes
[Obsolete]
public const String SingleQuoteValue = "\"'\""; //"'"
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DoubleQuoteNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String DoubleQuoteNameMacro = "@@DQBSQ@@"; //DoubleQuoteBetweenSingleQuotes
[Obsolete]
public const String DoubleQuoteValue = "'\"'"; //'"'
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.SingleQuoteCharMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String SingleQuoteCharMacro = "@@SingleQuote@@";
[Obsolete]
public const String SingleQuoteCharValue = "'";
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DoubleQuoteCharMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String DoubleQuoteCharMacro = "@@DoubleQuote@@";
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DoubleQuoteCharValue in assembly MS.VS.Services.Notifications.WebApi")]
public const String DoubleQuoteCharValue = "\"";
/// <summary>
/// Token used in subscription addresses to identify dynamic delivery targets computed from the source event
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DynamicTargetsToken in assembly MS.VS.Services.Notifications.WebApi")]
public const String DynamicTargetsToken = "@@";
/// <summary>
/// TeamFoundationIdentity property name for a user's custom list of Email addresses to receive notifications at
/// </summary>
public const String CustomNotificationAddressesIdentityProperty = "CustomNotificationAddresses";
/// <summary>
/// TeamFoundationIdentity propery name for a user's confirmed Email address to receive notifications. This is used in Hosted environments only.
/// </summary>
public const string ConfirmedNotificationAddressIdentityProperty = "ConfirmedNotificationAddress";
/// <summary>
/// The name of the WorkItemChangedEvent
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.WorkItemChangedEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const string WorkItemChangedEventTypeName = "WorkItemChangedEvent";
/// <summary>
/// The name of the BuildStatusChangedEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.BuildStatusChangeEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String BuildStatusChangeEventName = "BuildStatusChangeEvent";
/// <summary>
/// The name of the BuildCompletedEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.BuildCompletedEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String BuildCompletedEventName = "BuildCompletedEvent";
/// <summary>
/// The name of the CheckinEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.CheckinEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String CheckinEventName = "CheckinEvent";
/// <summary>
/// The name of the CodeReviewChangedEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.CodeReviewChangedEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String CodeReviewChangedEventName = "CodeReviewChangedEvent";
/// <summary>
/// The name of the GitPushEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.GitPushEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String GitPushEventName = "GitPushEvent";
/// <summary>
/// The name of the GitPullRequestEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.GitPullRequestEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String GitPullRequestEventName = "GitPullRequestEvent";
/// <summary>
/// The relative path to the alerts admin web page
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationUrlConstants.AlertsPageRelativePath in assembly MS.VS.Services.Notifications.WebApi")]
public const String AlertsPageRelativePath = "{0}#id={1}&showteams={2}";
/// <summary>
/// The alerts page name
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationUrlConstants.AlertsPage in assembly MS.VS.Services.Notifications.WebApi")]
public const String AlertsPage = "_Alerts";
/// <summary>
/// The admin alerts page
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationUrlConstants.AlertsAdminPage in assembly MS.VS.Services.Notifications.WebApi")]
public const String AlertsAdminPage = "_admin/_Alerts";
/// <summary>
/// Property used to keep track of how many confirmations were sent for this user. Used to limit the number
/// of confirmations a single user is allowed to send out for their account.
/// The value is updated and monitored by the SendEmailConfirmationJob.
/// </summary>
public const string EmailConfirmationSendDates = "EmailConfirmationSendDates";
/// <summary>
/// Prefix to denote that identity field value have been processed
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.ProcessedFlagCharacter in assembly MS.VS.Services.Notifications.WebApi")]
public const Char ProcessedFlagCharacter = (Char)7;
/// <summary>
/// Prefix to denote that identity field value have been processed and converted to TFID
/// </summary>
/// [Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.ProcessedTfIdFlagCharacter in assembly MS.VS.Services.Notifications.WebApi")]
public const Char ProcessedTfIdFlagCharacter = (Char)11;
/// <summary>
/// Prefix to denote that this is the start of displayname value for this identity field
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DisplayNameFlagCharacter in assembly MS.VS.Services.Notifications.WebApi")]
public const Char DisplayNameFlagCharacter = '|';
/// <summary>
/// Prefix to denote that this is the start of TFID value for this identity field
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.TfIdFlagCharacter in assembly MS.VS.Services.Notifications.WebApi")]
public const Char TfIdFlagCharacter = '%';
/// <summary>
/// Optional Feature flag to enable escaping Regex expressions when creating Notification subscriptions.
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.FeatureFlags.AllowUserRegexInMatchConditionFeatureFlag in assembly MS.VS.Services.Notifications.WebApi")]
public const string AllowUserRegexInMatchConditionFeatureFlag = "VisualStudio.Services.Notifications.AllowUserRegexInMatchCondition";
/// <summary>
/// The MDM scope name for the notification job
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.MDMNotificationJobScope in assembly MS.VS.Services.Notifications.WebApi")]
public const string MDMNotificationJobScope = "NotificationJob";
/// <summary>
/// Event processing delay KPI name
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.EventProcessingDelayKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string EventProcessingDelayKPI = "EventProcessingDelayInMs";
/// <summary>
/// Event processing delay KPI description
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.EventProcessingDelayKPIDesc in assembly MS.VS.Services.Notifications.WebApi")]
public const string EventProcessingDelayKPIDesc = "Time taken to start processing an event";
/// <summary>
/// The MDM scope name for the delivery job
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.MDMDeliveryJobscope in assembly MS.VS.Services.Notifications.WebApi")]
public const string MDMDeliveryJobscope = "NotificationDeliveryJob";
/// <summary>
/// Notification delivery delay KPI name
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.DeliveryDelayKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string DeliveryDelayKPI = "NotificationDeliveryDelayInMs";
/// <summary>
/// Notification delivery delay with retries KPI name
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.DeliveryDelayWithRetriesKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string DeliveryDelayWithRetriesKPI = "NotificationDeliveryDelayWithRetriesInMs";
/// <summary>
/// Total time taken between the event creation till the notification delivery
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.TotalProcessingTimeKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string TotalProcessingTimeKPI = "EventProcessingTimeInMs";
/// <summary>
/// Total time taken between the event creation till the notification delivery
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.TotalProcessingTimeWithRetriesKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string TotalProcessingTimeWithRetriesKPI = "EventProcessingTimeWithRetriesInMs";
/// <summary>
/// Notification delivery delay KPI description
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.DeliveryDelayKPIDesc in assembly MS.VS.Services.Notifications.WebApi")]
public const string DeliveryDelayKPIDesc = "Time taken to start deliverying a notification";
// caching key for our notification bridge interface
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.BridgeKey in assembly MS.VS.Services.Notifications.WebApi")]
public const String BridgeKey = "@NotifBridge";
// delivery retry count registryKey
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.RetryCountRegistryKey in assembly MS.VS.Services.Notifications.WebApi")]
public const string RetryCountRegistryKey = "NotificationRetryCount";
// delivery retry count default value
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.RetryCountDefaultValue in assembly MS.VS.Services.Notifications.WebApi")]
public const Int32 RetryCountDefaultValue = 5;
// the collection scope Guid
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.CollectionScope in assembly MS.VS.Services.Notifications.WebApi")]
public static Guid CollectionScope = new Guid("00000000-0000-636f-6c6c-656374696f6e");
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class LocationSecurityConstants
{
@@ -732,16 +358,6 @@ namespace GitHub.Services.Common
public const Int32 AllPermissions = Read | Write;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class SecuritySecurityConstants
{
public static readonly Guid NamespaceId = new Guid("9A82C708-BFBE-4F31-984C-E860C2196781");
public const char Separator = '/';
public const String RootToken = "";
public const int Read = 1;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class GraphSecurityConstants
{
@@ -753,158 +369,6 @@ namespace GitHub.Services.Common
public const int ReadByPersonalIdentifier = 2;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static class TeamProjectSecurityConstants
{
public static readonly Guid NamespaceId = new Guid("52D39943-CB85-4d7f-8FA8-C6BAAC873819");
// Existed in Orcas
public static readonly Int32 GenericRead = 1;
public static readonly Int32 GenericWrite = 2;
public static readonly Int32 Delete = 4;
public static readonly Int32 PublishTestResults = 8;
public static readonly Int32 AdministerBuild = 16;
public static readonly Int32 StartBuild = 32;
public static readonly Int32 EditBuildStatus = 64;
public static readonly Int32 UpdateBuild = 128;
public static readonly Int32 DeleteTestResults = 256;
public static readonly Int32 ViewTestResults = 512;
// Dev10 Beta1
public static readonly Int32 ManageTestEnvironments = 2048;
// Dev10 Beta2
public static readonly Int32 ManageTestConfigurations = 4096;
// Dev14 Update 2 / VSO (M91)
public static readonly Int32 WorkItemDelete = 8192;
// Dev14 Update 2 / VSO (M92)
public static readonly Int32 WorkItemMove = 16384;
// Dev14 Update 2 / VSO (M94)
public static readonly Int32 WorkItemPermanentlyDelete = 32768;
// Dev15 / VSO (M99)
public static readonly Int32 Rename = 65536;
/// <summary>
/// The permission required for setting project properties.
/// Introduced in Dev15 Update 2 / VSO (M116).
/// </summary>
public static readonly Int32 ManageProperties = 131072;
/// <summary>
/// The permission required for setting system project properties.
/// Introduced in Dev15 Update 2 / VSO (M116).
/// </summary>
/// <remarks>
/// This permission was excluded from AllPermissions to avoid being unintentionally granted.
/// </remarks>
public static readonly Int32 ManageSystemProperties = 262144;
/// <summary>
/// The permission required for bypassing the project property cache.
/// Introduced in Dev16 / VSO (M118).
/// </summary>
/// <remarks>
/// This permission was excluded from AllPermissions to avoid being unintentionally granted.
/// </remarks>
public static readonly Int32 BypassPropertyCache = 524288;
/// <summary>
/// The permission required for bypassing the rules while updating work items.
/// Introduced in Dev16 / VSO (M126).
/// </summary>
public static readonly Int32 BypassRules= 1048576;
/// <summary>
/// The permission required for suppressing notifications for work item updates.
/// Introduced in Dev16 / VSO (M126).
/// </summary>
public static readonly Int32 SuppressNotifications= 2097152;
/// <summary>
/// The permission required for updating project visibility.
/// Introduced in Dev16 / VSO (M131).
/// </summary>
public static readonly Int32 UpdateVisibility = 4194304;
/// <summary>
/// The permission required for changing the process of the team project
/// Introduced in Dev17 / VSO (M136).
/// </summary>
public static readonly Int32 ChangeProjectsProcess = 8388608;
/// <summary>
/// The permission required for granting access to backlog management. For stakeholder, this would disabled for private project and enabled for public project.
/// Introduced in Dev17 / VSO (M137).
/// </summary>
/// <remarks>
/// This permission was excluded from AllPermissions to avoid being unintentionally granted.
/// </remarks>
public static readonly Int32 AgileToolsBacklogManagement = 16777216;
/// <summary>
/// The permission required for granting access to backlog management. For stakeholder, this is always disabled.
/// Introduced in Dev17 / VSO (M150).
/// </summary>
/// <remarks>
/// This permission was excluded from AllPermissions to avoid being unintentionally granted.
/// </remarks>
public static readonly Int32 AgileToolsPlans = 33554432;
public static readonly Int32 AllPermissions =
GenericRead |
GenericWrite |
Delete |
PublishTestResults |
AdministerBuild |
StartBuild |
EditBuildStatus |
UpdateBuild |
DeleteTestResults |
ViewTestResults |
ManageTestEnvironments |
ManageTestConfigurations |
WorkItemDelete |
WorkItemMove |
WorkItemPermanentlyDelete |
Rename |
ManageProperties |
BypassRules |
SuppressNotifications |
UpdateVisibility |
ChangeProjectsProcess;
public const String ProjectTokenPrefix = "$PROJECT:";
public static String GetToken(String projectUri)
{
if (String.IsNullOrEmpty(projectUri) || !projectUri.StartsWith(ProjectTokenPrefix, StringComparison.OrdinalIgnoreCase))
{
if (projectUri == null)
{
projectUri = String.Empty;
}
return ProjectTokenPrefix + projectUri + ":";
}
return projectUri + ":";
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ContentValidationSecurityConstants
{
public static readonly Guid NamespaceId = new Guid("B1982126-CB90-4479-BDFD-CBF193241CB8");
public static readonly string ViolationsToken = "Violations";
public const int Read = 1;
public const int Write = 2;
}
public enum WinHttpErrorCode
{
WINHTTP_ERROR_BASE = 12000,

View File

@@ -124,9 +124,6 @@ namespace GitHub.Services.Common
LogException = (bool)info.GetValue("m_logException", typeof(bool));
ReportException = (bool)info.GetValue("m_reportException", typeof(bool));
ErrorCode = (int)info.GetValue("m_errorCode", typeof(int));
#if !NETSTANDARD
LogLevel = (EventLogEntryType)info.GetValue("m_logLevel", typeof(EventLogEntryType));
#endif
EventId = (int)info.GetValue("m_eventId", typeof(int));
}
@@ -137,9 +134,6 @@ namespace GitHub.Services.Common
info.AddValue("m_logException", LogException);
info.AddValue("m_reportException", ReportException);
info.AddValue("m_errorCode", ErrorCode);
#if !NETSTANDARD
info.AddValue("m_logLevel", LogLevel);
#endif
info.AddValue("m_eventId", EventId);
}
@@ -157,22 +151,6 @@ namespace GitHub.Services.Common
}
}
#if !NETSTANDARD
/// <summary>The event log entry type to use when logging the exception</summary>
/// <value>One of the event log entry types: </value>
public EventLogEntryType LogLevel
{
get
{
return m_logLevel;
}
set
{
m_logLevel = value;
}
}
#endif
/// <summary>A user-defined error code.</summary>
public int ErrorCode
{
@@ -279,10 +257,6 @@ namespace GitHub.Services.Common
private bool m_reportException;
private int m_errorCode;
#if !NETSTANDARD
private EventLogEntryType m_logLevel = EventLogEntryType.Warning;
#endif
private int m_eventId = DefaultExceptionEventId;
//From EventLog.cs in Framework.

View File

@@ -33,13 +33,7 @@ namespace GitHub.Services.Common
public VssHttpMessageHandler(
VssCredentials credentials,
VssHttpRequestSettings settings)
: this(credentials, settings,
#if !NETSTANDARD
new WebRequestHandler()
#else
new HttpClientHandler()
#endif
)
: this(credentials, settings, new HttpClientHandler())
{
}
@@ -76,13 +70,7 @@ namespace GitHub.Services.Common
m_transportHandler = transportHandler;
}
#if NETSTANDARD
//.Net Core does not recognize CredentialCache.DefaultCredentials if we wrap them with CredentialWrapper
bool isDefaultCredentials = credentials != null && credentials.Windows != null && credentials.Windows.UseDefaultCredentials;
ApplySettings(m_transportHandler, isDefaultCredentials ? CredentialCache.DefaultCredentials : m_credentialWrapper, this.Settings);
#else
ApplySettings(m_transportHandler, m_credentialWrapper, this.Settings);
#endif
}
/// <summary>
@@ -139,35 +127,6 @@ namespace GitHub.Services.Common
var traceInfo = VssHttpMessageHandlerTraceInfo.GetTraceInfo(request);
traceInfo?.TraceHandlerStartTime();
#if !NETSTANDARD
// This action is deferred from ApplySettings because we want don't want to do it if we aren't
// talking to an HTTPS endpoint.
if (!m_appliedClientCertificatesToTransportHandler &&
request.RequestUri.Scheme == "https")
{
WebRequestHandler webRequestHandler = m_transportHandler as WebRequestHandler;
if (webRequestHandler != null &&
this.Settings.ClientCertificateManager != null &&
this.Settings.ClientCertificateManager.ClientCertificates != null &&
this.Settings.ClientCertificateManager.ClientCertificates.Count > 0)
{
webRequestHandler.ClientCertificates.AddRange(this.Settings.ClientCertificateManager.ClientCertificates);
}
m_appliedClientCertificatesToTransportHandler = true;
}
if (!m_appliedServerCertificateValidationCallbackToTransportHandler &&
request.RequestUri.Scheme == "https")
{
WebRequestHandler webRequestHandler = m_transportHandler as WebRequestHandler;
if (webRequestHandler != null &&
this.Settings.ServerCertificateValidationCallback != null)
{
webRequestHandler.ServerCertificateValidationCallback = this.Settings.ServerCertificateValidationCallback;
}
m_appliedServerCertificateValidationCallbackToTransportHandler = true;
}
#else
if (!m_appliedClientCertificatesToTransportHandler &&
request.RequestUri.Scheme == "https")
{
@@ -201,7 +160,6 @@ namespace GitHub.Services.Common
{
request.Version = HttpVersion.Version11;
}
#endif
IssuedToken token = null;
IssuedTokenProvider provider;
@@ -540,16 +498,11 @@ namespace GitHub.Services.Common
}
}
private static IWebProxy s_defaultWebProxy =
#if !NETSTANDARD
WebRequest.DefaultWebProxy;
#else
// setting this to WebRequest.DefaultWebProxy in NETSTANDARD is causing a System.PlatformNotSupportedException
//.in System.Net.SystemWebProxy.IsBypassed. Comment in IsBypassed method indicates ".NET Core and .NET Native
// code will handle this exception and call into WinInet/WinHttp as appropriate to use the system proxy."
// This needs to be investigated further.
null;
#endif
// setting this to WebRequest.DefaultWebProxy in NETSTANDARD is causing a System.PlatformNotSupportedException
//.in System.Net.SystemWebProxy.IsBypassed. Comment in IsBypassed method indicates ".NET Core and .NET Native
// code will handle this exception and call into WinInet/WinHttp as appropriate to use the system proxy."
// This needs to be investigated further.
private static IWebProxy s_defaultWebProxy = null;
/// <summary>
/// Allows you to set a proxy to be used by all VssHttpMessageHandler requests without affecting the global WebRequest.DefaultWebProxy. If not set it returns the WebRequest.DefaultWebProxy.
@@ -585,13 +538,9 @@ namespace GitHub.Services.Common
private bool m_appliedServerCertificateValidationCallbackToTransportHandler;
private readonly HttpMessageHandler m_transportHandler;
#if NETSTANDARD
//.Net Core does not attempt NTLM schema on Linux, unless ICredentials is a CredentialCache instance
//This workaround may not be needed after this corefx fix is consumed: https://github.com/dotnet/corefx/pull/7923
private sealed class CredentialWrapper : CredentialCache, ICredentials
#else
private sealed class CredentialWrapper : ICredentials
#endif
{
public ICredentials InnerCredentials
{

View File

@@ -44,9 +44,7 @@ namespace GitHub.Services.Common
this.SuppressFedAuthRedirects = true;
this.ClientCertificateManager = null;
this.ServerCertificateValidationCallback = null;
#if NETSTANDARD
this.UseHttp11 = false;
#endif
// If different, we'll also add CurrentCulture to the request headers,
// but UICulture was added first, so it gets first preference
@@ -99,9 +97,7 @@ namespace GitHub.Services.Common
this.ClientCertificateManager = copy.ClientCertificateManager;
this.ServerCertificateValidationCallback = copy.ServerCertificateValidationCallback;
this.MaxRetryRequest = copy.MaxRetryRequest;
#if NETSTANDARD
this.UseHttp11 = copy.UseHttp11;
#endif
}
/// <summary>
@@ -144,7 +140,6 @@ namespace GitHub.Services.Common
set;
}
#if NETSTANDARD
/// <summary>
/// The .NET Core 2.1 runtime switched its HTTP default from HTTP 1.1 to HTTP 2.
/// This causes problems with some versions of the Curl handler on Linux.
@@ -156,7 +151,6 @@ namespace GitHub.Services.Common
get;
set;
}
#endif
/// <summary>
/// Gets or sets the maximum size allowed for response content buffering.
@@ -266,15 +260,6 @@ namespace GitHub.Services.Common
set;
}
#if !NETSTANDARD
/// <summary>
/// Optional implementation used to validate server certificate validation
/// </summary>
public RemoteCertificateValidationCallback ServerCertificateValidationCallback
{
get; set;
}
#else
/// <summary>
/// Optional implementation used to validate server certificate validation
/// </summary>
@@ -283,7 +268,6 @@ namespace GitHub.Services.Common
get;
set;
}
#endif
/// <summary>
/// Number of times to retry a request that has an ambient failure
@@ -359,13 +343,11 @@ namespace GitHub.Services.Common
request.Headers.Add(Internal.HttpHeaders.VssAgentHeader, this.AgentId);
}
#if NETSTANDARD
// Content is being sent as chunked by default in dotnet5.4, which differs than the .net 4.5 behaviour.
if (request.Content != null && !request.Content.Headers.ContentLength.HasValue && !request.Headers.TransferEncodingChunked.HasValue)
{
request.Content.Headers.ContentLength = request.Content.ReadAsByteArrayAsync().Result.Length;
}
#endif
return true;
}

View File

@@ -216,21 +216,13 @@ namespace GitHub.Services.Common
}
}
}
#if !NETSTANDARD
else if (ex is System.Data.Services.Client.DataServiceRequestException ||
ex is System.Data.Services.Client.DataServiceClientException)
{
// WCF exceptions
return true;
}
#endif
return false;
}
/// <summary>
/// Gets the HttpStatusCode which represents a throttling error.
/// </summary>
/// <summary>
/// Gets the HttpStatusCode which represents a throttling error.
/// </summary>
public const HttpStatusCode TooManyRequests = (HttpStatusCode)429;
}
}

View File

@@ -10,30 +10,14 @@ namespace GitHub.Services.Common
// See Toolsets\Version\Version.props for more details.
public const String MajorVersion = "16";
public const String MinorVersion = "0";
public const String BuildVersion = "65000";
public const String PatchVersion = "0";
public const String ProductVersion = MajorVersion + "." + MinorVersion;
// Assembly version (i.e. strong name)
public const String AssemblyMajorVersion = "16";
public const String AssemblyMinorVersion = "0";
public const String AssemblyBuildVersion = "0";
public const String AssemblyPatchVersion = "0";
public const String AssemblyVersion = AssemblyMajorVersion + "." + AssemblyMinorVersion + "." + AssemblyBuildVersion + "." + AssemblyPatchVersion;
// File version
public const String FileMajorVersion = "16";
public const String FileMinorVersion = "255";
public const String FileBuildVersion = "65000";
public const String FilePatchVersion = "0";
public const String FileVersion = FileMajorVersion + "." + FileMinorVersion + "." + FileBuildVersion + "." + FilePatchVersion;
// Derived versions
public const String TfsMajorVersion = "8";
public const String TfsMinorVersion = "0";
public const String TfsProductVersion = TfsMajorVersion + "." + TfsMinorVersion;
// On-premises TFS install folder
public const String TfsInstallDirectory = "Azure DevOps Server 2019";
}
}

View File

@@ -1,80 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
using GitHub.Services.Common;
using GitHub.Services.Common.Internal;
using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Patch;
using GitHub.Services.WebApi.Patch.Json;
namespace GitHub.Core.WebApi
{
[GenerateAllConstants]
public enum ProjectState
{
/// <summary>
/// Project is in the process of being deleted.
/// </summary>
[EnumMember]
Deleting = 2,
/// <summary>
/// Project is in the process of being created.
/// </summary>
[EnumMember]
New = 0,
/// <summary>
/// Project is completely created and ready to use.
/// </summary>
[EnumMember]
WellFormed = 1,
/// <summary>
/// Project has been queued for creation, but the process has not yet started.
/// </summary>
[EnumMember]
CreatePending = 3,
/// <summary>
/// All projects regardless of state.
/// </summary>
[EnumMember]
All = -1, // Used for filtering.
/// <summary>
/// Project has not been changed.
/// </summary>
[EnumMember]
Unchanged = -2, // Used for updating projects.
/// <summary>
/// Project has been deleted.
/// </summary>
[EnumMember]
Deleted = 4, // Used for the project history.
}
public enum ProjectVisibility // Stored as a TINYINT
{
[ClientInternalUseOnly]
Unchanged = -1, // Used for updating projects.
/// <summary>
/// The project is only visible to users with explicit access.
/// </summary>
Private = 0,
/// <summary>
/// Enterprise level project visibility
/// </summary>
[ClientInternalUseOnly(omitFromTypeScriptDeclareFile: false)]
Organization = 1,
/// <summary>
/// The project is visible to all.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
Public = 2,
[ClientInternalUseOnly]
SystemPrivate = 3 // Soft-deleted projects
}
}

View File

@@ -1,105 +0,0 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.Core.WebApi
{
/// <summary>
/// Represents a shallow reference to a TeamProject.
/// </summary>
[DataContract]
public class TeamProjectReference : ISecuredObject
{
/// <summary>
/// Default constructor to ensure we set up the project state correctly for serialization.
/// </summary>
public TeamProjectReference()
{
State = ProjectState.Unchanged;
Visibility = ProjectVisibility.Unchanged;
}
/// <summary>
/// Project identifier.
/// </summary>
[DataMember(Order = 0, EmitDefaultValue = false)]
public Guid Id { get; set; }
/// <summary>
/// Project abbreviation.
/// </summary>
[DataMember(Order = 1, EmitDefaultValue = false)]
public string Abbreviation { get; set; }
/// <summary>
/// Project name.
/// </summary>
[DataMember(Order = 2, EmitDefaultValue = false)]
public string Name { get; set; }
/// <summary>
/// The project's description (if any).
/// </summary>
[DataMember(Order = 3, EmitDefaultValue = false)]
public string Description { get; set; }
/// <summary>
/// Url to the full version of the object.
/// </summary>
[DataMember(Order = 4, EmitDefaultValue = false)]
public string Url { get; set; }
/// <summary>
/// Project state.
/// </summary>
[DataMember(Order = 5)]
public ProjectState State { get; set; }
/// <summary>
/// Project revision.
/// </summary>
[DataMember(Order = 6, EmitDefaultValue = false)]
public Int64 Revision { get; set; }
/// <summary>
/// Project visibility.
/// </summary>
[DataMember(Order = 7)]
public ProjectVisibility Visibility { get; set; }
/// <summary>
/// Url to default team identity image.
/// </summary>
[DataMember(Order = 8, EmitDefaultValue = false)]
public String DefaultTeamImageUrl { get; set; }
/// <summary>
/// Project last update time.
/// </summary>
[DataMember(Order = 9)]
public DateTime LastUpdateTime { get; set; }
#region ISecuredObject
Guid ISecuredObject.NamespaceId => NamespaceId;
int ISecuredObject.RequiredPermissions => RequiredPermissions;
string ISecuredObject.GetToken()
{
return GetToken();
}
protected virtual Guid NamespaceId => TeamProjectSecurityConstants.NamespaceId;
protected virtual int RequiredPermissions => TeamProjectSecurityConstants.GenericRead;
protected virtual string GetToken()
{
// WE DON'T CARE THIS FOR NOW
return TeamProjectSecurityConstants.GetToken(Id.ToString("D"));
}
#endregion
}
}

View File

@@ -1,16 +0,0 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class AuthorizationHeader : BaseSecuredObject
{
[DataMember(EmitDefaultValue = false)]
public String Name { get; set; }
[DataMember(EmitDefaultValue = false)]
public String Value { get; set; }
}
}

View File

@@ -1,149 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
/// <summary>
/// Represents binding of data source for the service endpoint request.
/// </summary>
[DataContract]
public class DataSourceBindingBase : BaseSecuredObject
{
public DataSourceBindingBase()
{
}
protected DataSourceBindingBase(DataSourceBindingBase inputDefinitionToClone)
: this(inputDefinitionToClone, null)
{
}
protected DataSourceBindingBase(DataSourceBindingBase inputDefinitionToClone, ISecuredObject securedObject)
: base(securedObject)
{
this.DataSourceName = inputDefinitionToClone.DataSourceName;
this.EndpointId = inputDefinitionToClone.EndpointId;
this.Target = inputDefinitionToClone.Target;
this.ResultTemplate = inputDefinitionToClone.ResultTemplate;
this.EndpointUrl = inputDefinitionToClone.EndpointUrl;
this.ResultSelector = inputDefinitionToClone.ResultSelector;
this.RequestVerb = inputDefinitionToClone.RequestVerb;
this.RequestContent = inputDefinitionToClone.RequestContent;
this.CallbackContextTemplate = inputDefinitionToClone.CallbackContextTemplate;
this.CallbackRequiredTemplate = inputDefinitionToClone.CallbackRequiredTemplate;
this.InitialContextTemplate = inputDefinitionToClone.InitialContextTemplate;
inputDefinitionToClone.Parameters.Copy(this.Parameters);
this.CloneHeaders(inputDefinitionToClone.Headers);
}
/// <summary>
/// Gets or sets the name of the data source.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public string DataSourceName { get; set; }
/// <summary>
/// Gets or sets the parameters for the data source.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public Dictionary<string, string> Parameters
{
get
{
if (m_parameters == null)
{
m_parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
return m_parameters;
}
}
public DataSourceBindingBase Clone(ISecuredObject securedObject)
{
return new DataSourceBindingBase(this, securedObject);
}
private void CloneHeaders(List<AuthorizationHeader> headers)
{
if (headers == null)
{
return;
}
this.Headers = headers.Select(header => new AuthorizationHeader { Name = header.Name, Value = header.Value }).ToList();
}
/// <summary>
/// Gets or sets the endpoint Id.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String EndpointId { get; set; }
/// <summary>
/// Gets or sets the target of the data source.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String Target { get; set; }
/// <summary>
/// Gets or sets the result template.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String ResultTemplate { get; set; }
/// <summary>
/// Gets or sets http request verb
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String RequestVerb { get; set; }
/// <summary>
/// Gets or sets http request body
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String RequestContent { get; set; }
/// <summary>
/// Gets or sets the url of the service endpoint.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String EndpointUrl { get; set; }
/// <summary>
/// Gets or sets the result selector.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String ResultSelector { get; set; }
/// <summary>
/// Pagination format supported by this data source(ContinuationToken/SkipTop).
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String CallbackContextTemplate { get; set; }
/// <summary>
/// Subsequent calls needed?
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String CallbackRequiredTemplate { get; set; }
/// <summary>
/// Defines the initial value of the query params
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String InitialContextTemplate { get; set; }
/// <summary>
/// Gets or sets the authorization headers.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public List<AuthorizationHeader> Headers { get; set; }
private Dictionary<String, String> m_parameters;
}
}

View File

@@ -1,163 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using GitHub.DistributedTask.WebApi;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class ProcessParameters : BaseSecuredObject
{
public ProcessParameters()
: this(null)
{
}
public ProcessParameters(ISecuredObject securedObject)
: this(null, securedObject)
{
}
private ProcessParameters(ProcessParameters toClone, ISecuredObject securedObject)
: base(securedObject)
{
if (toClone != null)
{
if (toClone.Inputs.Count > 0)
{
Inputs.AddRange(toClone.Inputs.Select(i => i.Clone(securedObject)));
}
if (toClone.SourceDefinitions.Count > 0)
{
SourceDefinitions.AddRange(toClone.SourceDefinitions.Select(sd => sd.Clone(securedObject)));
}
if (toClone.DataSourceBindings.Count > 0)
{
DataSourceBindings.AddRange(toClone.DataSourceBindings.Select(dsb => dsb.Clone(securedObject)));
}
}
}
public IList<TaskInputDefinitionBase> Inputs
{
get
{
if (m_inputs == null)
{
m_inputs = new List<TaskInputDefinitionBase>();
}
return m_inputs;
}
}
public IList<TaskSourceDefinitionBase> SourceDefinitions
{
get
{
if (m_sourceDefinitions == null)
{
m_sourceDefinitions = new List<TaskSourceDefinitionBase>();
}
return m_sourceDefinitions;
}
}
public IList<DataSourceBindingBase> DataSourceBindings
{
get
{
if (m_dataSourceBindings == null)
{
m_dataSourceBindings = new List<DataSourceBindingBase>();
}
return m_dataSourceBindings;
}
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object obj)
{
var processParameters2 = obj as ProcessParameters;
if (processParameters2 == null)
{
return false;
}
if (this.Inputs == null && processParameters2.Inputs == null)
{
return true;
}
if ((this.Inputs != null && processParameters2.Inputs == null)
|| (this.Inputs == null && processParameters2.Inputs != null))
{
return false;
}
if (this.Inputs.Count != processParameters2.Inputs.Count)
{
return false;
}
var orderedProcessParameters1 = this.Inputs.Where(i => i != null).OrderBy(i => i.Name);
var orderedProcessParameters2 = processParameters2.Inputs.Where(i => i != null).OrderBy(i => i.Name);
if (!orderedProcessParameters1.OrderBy(i => i.Name).SequenceEqual(orderedProcessParameters2))
{
return false;
}
return true;
}
public ProcessParameters Clone(ISecuredObject securedObject = null)
{
return new ProcessParameters(this, securedObject);
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
SerializationHelper.Copy(ref m_serializedInputs, ref m_inputs, true);
SerializationHelper.Copy(ref m_serializedSourceDefinitions, ref m_sourceDefinitions, true);
SerializationHelper.Copy(ref m_serializedDataSourceBindings, ref m_dataSourceBindings, true);
}
[OnSerializing]
private void OnSerializing(StreamingContext context)
{
SerializationHelper.Copy(ref m_inputs, ref m_serializedInputs);
SerializationHelper.Copy(ref m_sourceDefinitions, ref m_serializedSourceDefinitions);
SerializationHelper.Copy(ref m_dataSourceBindings, ref m_serializedDataSourceBindings);
}
[OnSerialized]
private void OnSerialized(StreamingContext context)
{
m_serializedInputs = null;
m_serializedSourceDefinitions = null;
m_serializedDataSourceBindings = null;
}
[DataMember(Name = "Inputs", EmitDefaultValue = false)]
private List<TaskInputDefinitionBase> m_serializedInputs;
[DataMember(Name = "SourceDefinitions", EmitDefaultValue = false)]
private List<TaskSourceDefinitionBase> m_serializedSourceDefinitions;
[DataMember(Name = "DataSourceBindings", EmitDefaultValue = false)]
private List<DataSourceBindingBase> m_serializedDataSourceBindings;
private List<TaskInputDefinitionBase> m_inputs;
private List<TaskSourceDefinitionBase> m_sourceDefinitions;
private List<DataSourceBindingBase> m_dataSourceBindings;
}
}

View File

@@ -1,254 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class TaskInputDefinitionBase : BaseSecuredObject
{
public TaskInputDefinitionBase()
{
InputType = TaskInputType.String;
DefaultValue = String.Empty;
Required = false;
HelpMarkDown = String.Empty;
}
protected TaskInputDefinitionBase(TaskInputDefinitionBase inputDefinitionToClone)
: this(inputDefinitionToClone, null)
{
}
protected TaskInputDefinitionBase(TaskInputDefinitionBase inputDefinitionToClone, ISecuredObject securedObject)
: base(securedObject)
{
this.DefaultValue = inputDefinitionToClone.DefaultValue;
this.InputType = inputDefinitionToClone.InputType;
this.Label = inputDefinitionToClone.Label;
this.Name = inputDefinitionToClone.Name;
this.Required = inputDefinitionToClone.Required;
this.HelpMarkDown = inputDefinitionToClone.HelpMarkDown;
this.VisibleRule = inputDefinitionToClone.VisibleRule;
this.GroupName = inputDefinitionToClone.GroupName;
if (inputDefinitionToClone.Validation != null)
{
this.Validation = inputDefinitionToClone.Validation.Clone(securedObject);
}
if (inputDefinitionToClone.m_aliases != null)
{
this.m_aliases = new List<String>(inputDefinitionToClone.m_aliases);
}
if (inputDefinitionToClone.m_options != null)
{
this.m_options = new Dictionary<String, String>(inputDefinitionToClone.m_options);
}
if (inputDefinitionToClone.m_properties != null)
{
this.m_properties = new Dictionary<String, String>(inputDefinitionToClone.m_properties);
}
}
public IList<String> Aliases
{
get
{
if (m_aliases == null)
{
m_aliases = new List<String>();
}
return m_aliases;
}
}
[DataMember(EmitDefaultValue = false)]
public String Name
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String Label
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String DefaultValue
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public Boolean Required
{
get;
set;
}
[DataMember(Name = "Type")]
public String InputType
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String HelpMarkDown
{
get;
set;
}
// VisibleRule should specify the condition at which this input is to be shown/displayed
// Typical format is "NAME OF THE DEPENDENT INPUT = VALUE TOBE BOUND"
[DataMember(EmitDefaultValue = false)]
public string VisibleRule
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public string GroupName
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public TaskInputValidation Validation
{
get;
set;
}
public Dictionary<String, String> Options
{
get
{
if (m_options == null)
{
m_options = new Dictionary<String, String>();
}
return m_options;
}
}
public Dictionary<String, String> Properties
{
get
{
if (m_properties == null)
{
m_properties = new Dictionary<String, String>();
}
return m_properties;
}
}
public virtual TaskInputDefinitionBase Clone(
ISecuredObject securedObject)
{
return new TaskInputDefinitionBase(this, securedObject);
}
public override int GetHashCode()
{
return this.Name.GetHashCode() ^ this.DefaultValue.GetHashCode() ^ this.Label.GetHashCode();
}
public override bool Equals(object obj)
{
var taskInput2 = obj as TaskInputDefinitionBase;
if (taskInput2 == null
|| !string.Equals(InputType, taskInput2.InputType, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(Label, taskInput2.Label, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(Name, taskInput2.Name, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(GroupName, taskInput2.GroupName, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(DefaultValue, taskInput2.DefaultValue, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(HelpMarkDown, taskInput2.HelpMarkDown, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(VisibleRule, taskInput2.VisibleRule, StringComparison.OrdinalIgnoreCase)
|| !this.Required.Equals(taskInput2.Required))
{
return false;
}
if (!AreListsEqual(Aliases, taskInput2.Aliases)
|| !AreDictionariesEqual(Properties, taskInput2.Properties)
|| !AreDictionariesEqual(Options, taskInput2.Options))
{
return false;
}
if ((Validation != null && taskInput2.Validation == null)
|| (Validation == null && taskInput2.Validation != null)
|| ((Validation != null && taskInput2.Validation != null)
&& !Validation.Equals(taskInput2.Validation)))
{
return false;
}
return true;
}
private bool AreDictionariesEqual(Dictionary<String, String> input1, Dictionary<String, String> input2)
{
if (input1 == null && input2 == null)
{
return true;
}
if ((input1 == null && input2 != null)
|| (input1 != null && input2 == null)
|| (input1.Count != input2.Count))
{
return false;
}
foreach (var key in input1.Keys)
{
if (!(input2.ContainsKey(key) && String.Equals(input1[key], input2[key], StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
return true;
}
private Boolean AreListsEqual(IList<String> list1, IList<String> list2)
{
if (list1.Count != list2.Count)
{
return false;
}
for (Int32 i = 0; i < list1.Count; i++)
{
if (!String.Equals(list1[i], list2[i], StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
return true;
}
[DataMember(Name = "Aliases", EmitDefaultValue = false)]
private List<String> m_aliases;
[DataMember(Name = "Options", EmitDefaultValue = false)]
private Dictionary<String, String> m_options;
[DataMember(Name = "Properties", EmitDefaultValue = false)]
private Dictionary<String, String> m_properties;
}
}

View File

@@ -1,14 +0,0 @@
using System.Runtime.Serialization;
using System;
namespace GitHub.DistributedTask.Common.Contracts
{
public static class TaskInputType
{
public const String String = "string";
public const String Repository = "repository";
public const String Boolean = "boolean";
public const String KeyValue = "keyvalue";
public const String FilePath = "filepath";
}
}

View File

@@ -1,59 +0,0 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class TaskInputValidation : BaseSecuredObject
{
public TaskInputValidation()
{
}
private TaskInputValidation(TaskInputValidation toClone, ISecuredObject securedObject)
: base(securedObject)
{
if (toClone != null)
{
this.Expression = toClone.Expression;
this.Message = toClone.Message;
}
}
/// <summary>
/// Conditional expression
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String Expression
{
get;
set;
}
/// <summary>
/// Message explaining how user can correct if validation fails
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String Message
{
get;
set;
}
public override int GetHashCode()
{
return Expression.GetHashCode() ^ Message.GetHashCode();
}
public TaskInputValidation Clone()
{
return this.Clone(null);
}
public TaskInputValidation Clone(ISecuredObject securedObject)
{
return new TaskInputValidation(this, securedObject);
}
}
}

View File

@@ -1,74 +0,0 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class TaskSourceDefinitionBase : BaseSecuredObject
{
public TaskSourceDefinitionBase()
{
AuthKey = String.Empty;
Endpoint = String.Empty;
Selector = String.Empty;
Target = String.Empty;
KeySelector = String.Empty;
}
protected TaskSourceDefinitionBase(TaskSourceDefinitionBase inputDefinitionToClone)
: this(inputDefinitionToClone, null)
{
}
protected TaskSourceDefinitionBase(TaskSourceDefinitionBase inputDefinitionToClone, ISecuredObject securedObject)
: base(securedObject)
{
this.Endpoint = inputDefinitionToClone.Endpoint;
this.Target = inputDefinitionToClone.Target;
this.AuthKey = inputDefinitionToClone.AuthKey;
this.Selector = inputDefinitionToClone.Selector;
this.KeySelector = inputDefinitionToClone.KeySelector;
}
public virtual TaskSourceDefinitionBase Clone(ISecuredObject securedObject)
{
return new TaskSourceDefinitionBase(this, securedObject);
}
[DataMember(EmitDefaultValue = false)]
public String Endpoint
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String Target
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String AuthKey
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String Selector
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String KeySelector
{
get;
set;
}
}
}

View File

@@ -1,22 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class AndNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
foreach (ExpressionNode parameter in Parameters)
{
if (!parameter.EvaluateBoolean(context))
{
return false;
}
}
return true;
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class CoalesceNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
EvaluationResult result = null;
foreach (ExpressionNode parameter in Parameters)
{
result = parameter.Evaluate(context);
if (result.Kind == ValueKind.Null)
{
continue;
}
if (result.Kind == ValueKind.String && String.IsNullOrEmpty(result.Value as String))
{
continue;
}
break;
}
return result?.Value;
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class JArrayAccessor : IReadOnlyArray
{
public JArrayAccessor(JArray jarray)
{
m_jarray = jarray;
}
public Int32 Count => m_jarray.Count;
public Object this[Int32 index] => m_jarray[index];
public IEnumerator<Object> GetEnumerator()
{
return m_jarray.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_jarray.GetEnumerator();
}
private readonly JArray m_jarray;
}
}

View File

@@ -1,56 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class JObjectAccessor : IReadOnlyObject
{
public JObjectAccessor(JObject jobject)
{
m_jobject = jobject;
}
public Int32 Count => m_jobject.Count;
public IEnumerable<String> Keys => (m_jobject as IDictionary<String, JToken>).Keys;
// This uses Select. Calling .Values directly throws an exception.
public IEnumerable<Object> Values => (m_jobject as IDictionary<String, JToken>).Select(x => x.Value);
public Object this[String key] => m_jobject[key];
public Boolean ContainsKey(String key)
{
return (m_jobject as IDictionary<String, JToken>).ContainsKey(key);
}
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
{
return (m_jobject as IDictionary<String, JToken>).Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (m_jobject as IDictionary<String, JToken>).Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
}
public Boolean TryGetValue(
String key,
out Object value)
{
if ((m_jobject as IDictionary<String, JToken>).TryGetValue(key, out JToken val))
{
value = val;
return true;
}
value = null;
return false;
}
private readonly JObject m_jobject;
}
}

View File

@@ -1,106 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Serialization;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class JsonDictionaryContractAccessor : IReadOnlyObject
{
public JsonDictionaryContractAccessor(
JsonDictionaryContract contract,
Object obj)
{
m_contract = contract;
m_obj = obj;
}
public Int32 Count
{
get
{
var genericMethod = s_getCountTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
return (Int32)genericMethod.Invoke(null, new[] { m_obj });
}
}
public IEnumerable<String> Keys
{
get
{
var genericMethod = s_getKeysTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
return genericMethod.Invoke(null, new[] { m_obj }) as IEnumerable<String>;
}
}
public IEnumerable<Object> Values => Keys.Select(x => this[x]);
public Object this[String key]
{
get
{
if (TryGetValue(key, out Object value))
{
return value;
}
throw new KeyNotFoundException(ExpressionResources.KeyNotFound(key));
}
}
public Boolean ContainsKey(String key)
{
return TryGetValue(key, out _);
}
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
{
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
}
public Boolean TryGetValue(
String key,
out Object value)
{
var genericMethod = s_tryGetValueTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
var tuple = genericMethod.Invoke(null, new[] { m_obj, key }) as Tuple<Boolean, Object>;
value = tuple.Item2;
return tuple.Item1;
}
private static Int32 GetCount<TValue>(IDictionary<String, TValue> dictionary)
{
return dictionary.Count;
}
private static IEnumerable<String> GetKeys<TValue>(IDictionary<String, TValue> dictionary)
{
return dictionary.Keys;
}
private static Tuple<Boolean, Object> TryGetValue<TValue>(
IDictionary<String, TValue> dictionary,
String key)
{
if (dictionary.TryGetValue(key, out TValue value))
{
return new Tuple<Boolean, Object>(true, value);
}
return new Tuple<Boolean, Object>(false, null);
}
private static Lazy<MethodInfo> s_getCountTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(GetCount), BindingFlags.NonPublic | BindingFlags.Static));
private static Lazy<MethodInfo> s_getKeysTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(GetKeys), BindingFlags.NonPublic | BindingFlags.Static));
private static Lazy<MethodInfo> s_tryGetValueTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(TryGetValue), BindingFlags.NonPublic | BindingFlags.Static));
private readonly JsonDictionaryContract m_contract;
private readonly Object m_obj;
}
}

View File

@@ -1,89 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Serialization;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class JsonObjectContractAccessor : IReadOnlyObject
{
public JsonObjectContractAccessor(
JsonObjectContract contract,
Object obj)
{
m_contract = contract;
m_obj = obj;
}
public Int32 Count => GetProperties().Count();
public IEnumerable<String> Keys => GetProperties().Select(x => x.PropertyName);
public IEnumerable<Object> Values => GetProperties().Select(x => x.ValueProvider.GetValue(m_obj));
public Object this[String key]
{
get
{
if (TryGetValue(key, out Object value))
{
return value;
}
throw new KeyNotFoundException(ExpressionResources.KeyNotFound(key));
}
}
public Boolean ContainsKey(String key)
{
return TryGetProperty(key, out _);
}
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
{
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
}
public Boolean TryGetValue(
String key,
out Object value)
{
if (TryGetProperty(key, out JsonProperty property))
{
value = property.ValueProvider.GetValue(m_obj);
return true;
}
value = null;
return false;
}
private IEnumerable<JsonProperty> GetProperties()
{
return m_contract.Properties.Where(x => !x.Ignored);
}
private Boolean TryGetProperty(
String key,
out JsonProperty property)
{
property = m_contract.Properties.GetClosestMatchProperty(key);
if (property != null && !property.Ignored)
{
return true;
}
property = null;
return false;
}
private readonly JsonObjectContract m_contract;
private readonly Object m_obj;
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class ListOfObjectAccessor : IReadOnlyArray
{
public ListOfObjectAccessor(IList<Object> list)
{
m_list = list;
}
public Int32 Count => m_list.Count;
public Object this[Int32 index] => m_list[index];
public IEnumerator<Object> GetEnumerator()
{
return m_list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_list.GetEnumerator();
}
private readonly IList<Object> m_list;
}
}

View File

@@ -1,37 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class ReadOnlyDictionaryOfStringObjectAccessor : IReadOnlyObject
{
public ReadOnlyDictionaryOfStringObjectAccessor(IReadOnlyDictionary<String, Object> dictionary)
{
m_dictionary = dictionary;
}
public Int32 Count => m_dictionary.Count;
public IEnumerable<String> Keys => m_dictionary.Keys;
public IEnumerable<Object> Values => m_dictionary.Values;
public Object this[String key] => m_dictionary[key];
public Boolean ContainsKey(String key) => m_dictionary.ContainsKey(key);
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator() => m_dictionary.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => m_dictionary.GetEnumerator();
public Boolean TryGetValue(
String key,
out Object value)
{
return m_dictionary.TryGetValue(key, out value);
}
private readonly IReadOnlyDictionary<String, Object> m_dictionary;
}
}

View File

@@ -1,54 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class ReadOnlyDictionaryOfStringStringAccessor : IReadOnlyObject
{
public ReadOnlyDictionaryOfStringStringAccessor(IReadOnlyDictionary<String, String> dictionary)
{
m_dictionary = dictionary;
}
public Int32 Count => m_dictionary.Count;
public IEnumerable<String> Keys => m_dictionary.Keys;
public IEnumerable<Object> Values => m_dictionary.Values.OfType<Object>();
public Object this[String key] => m_dictionary[key];
public Boolean ContainsKey(String key)
{
return m_dictionary.ContainsKey(key);
}
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
{
return m_dictionary.Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_dictionary.Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
}
public Boolean TryGetValue(
String key,
out Object value)
{
if (m_dictionary.TryGetValue(key, out String val))
{
value = val;
return true;
}
value = default;
return false;
}
private readonly IReadOnlyDictionary<String, String> m_dictionary;
}
}

View File

@@ -1,24 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class ReadOnlyListOfObjectAccessor : IReadOnlyArray
{
public ReadOnlyListOfObjectAccessor(IReadOnlyList<Object> list)
{
m_list = list;
}
public Int32 Count => m_list.Count;
public Object this[Int32 index] => m_list[index];
public IEnumerator<Object> GetEnumerator() => m_list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => m_list.GetEnumerator();
private readonly IReadOnlyList<Object> m_list;
}
}

View File

@@ -1,49 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class ContainerNode : ExpressionNode
{
public IReadOnlyList<ExpressionNode> Parameters => m_parameters.AsReadOnly();
public void AddParameter(ExpressionNode node)
{
m_parameters.Add(node);
node.Container = this;
}
public void ReplaceParameter(Int32 index, ExpressionNode node)
{
m_parameters[index] = node;
node.Container = this;
}
public override IEnumerable<T> GetParameters<T>()
{
List<T> matched = new List<T>();
Queue<IExpressionNode> parameters = new Queue<IExpressionNode>(this.Parameters);
while (parameters.Count > 0)
{
var parameter = parameters.Dequeue();
if (typeof(T).GetTypeInfo().IsAssignableFrom(parameter.GetType().GetTypeInfo()))
{
matched.Add((T)parameter);
}
foreach (var childParameter in parameter.GetParameters<T>())
{
parameters.Enqueue(childParameter);
}
}
return matched;
}
private readonly List<ExpressionNode> m_parameters = new List<ExpressionNode>();
}
}

View File

@@ -1,16 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class ContainsNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
String left = Parameters[0].EvaluateString(context) as String ?? String.Empty;
String right = Parameters[1].EvaluateString(context) as String ?? String.Empty;
return left.IndexOf(right, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
}

View File

@@ -1,46 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class ContainsValueNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override object EvaluateCore(EvaluationContext context)
{
EvaluationResult left = Parameters[0].Evaluate(context);
if (left.TryGetCollectionInterface(out Object collection))
{
EvaluationResult right = Parameters[1].Evaluate(context);
if (collection is IReadOnlyArray array)
{
foreach (var item in array)
{
var itemResult = EvaluationResult.CreateIntermediateResult(context, item, out _);
if (right.Equals(context, itemResult))
{
return true;
}
}
}
else if (collection is IReadOnlyObject obj)
{
foreach (var value in obj.Values)
{
var valueResult = EvaluationResult.CreateIntermediateResult(context, value, out _);
if (right.Equals(context, valueResult))
{
return true;
}
}
}
}
return false;
}
}
}

View File

@@ -1,19 +0,0 @@
using System;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public struct ConversionResult
{
/// <summary>
/// Result object after the conversion
/// </summary>
public Object Result;
/// <summary>
/// Memory overhead for the result object
/// </summary>
public ResultMemory ResultMemory;
}
}

View File

@@ -1,16 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class EndsWithNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
String left = Parameters[0].EvaluateString(context) ?? String.Empty;
String right = Parameters[1].EvaluateString(context) ?? String.Empty;
return left.EndsWith(right, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class EqualNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
return Parameters[0].Evaluate(context).Equals(context, Parameters[1].Evaluate(context));
}
}
}

View File

@@ -1,78 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using GitHub.DistributedTask.Logging;
using GitHub.Services.Common;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class EvaluationContext
{
internal EvaluationContext(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state,
EvaluationOptions options,
ExpressionNode node)
{
ArgumentUtility.CheckForNull(trace, nameof(trace));
ArgumentUtility.CheckForNull(secretMasker, nameof(secretMasker));
Trace = trace;
SecretMasker = secretMasker;
State = state;
// Copy the options
options = new EvaluationOptions(copy: options);
if (options.MaxMemory == 0)
{
// Set a reasonable default max memory
options.MaxMemory = 1048576; // 1 mb
}
Options = options;
Memory = new EvaluationMemory(options.MaxMemory, node);
m_traceResults = new Dictionary<ExpressionNode, String>();
m_traceMemory = new MemoryCounter(null, options.MaxMemory);
}
public ITraceWriter Trace { get; }
public ISecretMasker SecretMasker { get; }
public Object State { get; }
internal EvaluationMemory Memory { get; }
internal EvaluationOptions Options { get; }
internal void SetTraceResult(
ExpressionNode node,
EvaluationResult result)
{
// Remove if previously added. This typically should not happen. This could happen
// due to a badly authored function. So we'll handle it and track memory correctly.
if (m_traceResults.TryGetValue(node, out String oldValue))
{
m_traceMemory.Remove(oldValue);
m_traceResults.Remove(node);
}
// Check max memory
String value = ExpressionUtil.FormatValue(SecretMasker, result);
if (m_traceMemory.TryAdd(value))
{
// Store the result
m_traceResults[node] = value;
}
}
internal Boolean TryGetTraceResult(ExpressionNode node, out String value)
{
return m_traceResults.TryGetValue(node, out value);
}
private readonly Dictionary<ExpressionNode, String> m_traceResults = new Dictionary<ExpressionNode, String>();
private readonly MemoryCounter m_traceMemory;
}
}

View File

@@ -1,111 +0,0 @@
using System;
using System.Collections.Generic;
namespace GitHub.DistributedTask.Expressions
{
/// <summary>
/// This is an internal class only.
///
/// This class is used to track current memory consumption
/// across the entire expression evaluation.
/// </summary>
internal sealed class EvaluationMemory
{
internal EvaluationMemory(
Int32 maxBytes,
ExpressionNode node)
{
m_maxAmount = maxBytes;
m_node = node;
}
internal void AddAmount(
Int32 depth,
Int32 bytes,
Boolean trimDepth = false)
{
// Trim deeper depths
if (trimDepth)
{
while (m_maxActiveDepth > depth)
{
var amount = m_depths[m_maxActiveDepth];
if (amount > 0)
{
// Sanity check
if (amount > m_totalAmount)
{
throw new InvalidOperationException("Bytes to subtract exceeds total bytes");
}
// Subtract from the total
checked
{
m_totalAmount -= amount;
}
// Reset the amount
m_depths[m_maxActiveDepth] = 0;
}
m_maxActiveDepth--;
}
}
// Grow the depths
if (depth > m_maxActiveDepth)
{
// Grow the list
while (m_depths.Count <= depth)
{
m_depths.Add(0);
}
// Adjust the max active depth
m_maxActiveDepth = depth;
}
checked
{
// Add to the depth
m_depths[depth] += bytes;
// Add to the total
m_totalAmount += bytes;
}
// Check max
if (m_totalAmount > m_maxAmount)
{
throw new InvalidOperationException(ExpressionResources.ExceededAllowedMemory(m_node?.ConvertToExpression()));
}
}
internal static Int32 CalculateBytes(Object obj)
{
if (obj is String str)
{
// This measurement doesn't have to be perfect
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
checked
{
return c_stringBaseOverhead + ((str?.Length ?? 0) * sizeof(Char));
}
}
else
{
return c_minObjectSize;
}
}
private const Int32 c_minObjectSize = 24;
private const Int32 c_stringBaseOverhead = 26;
private readonly List<Int32> m_depths = new List<Int32>();
private readonly Int32 m_maxAmount;
private readonly ExpressionNode m_node;
private Int32 m_maxActiveDepth = -1;
private Int32 m_totalAmount;
}
}

View File

@@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class EvaluationOptions
{
public EvaluationOptions()
{
}
public EvaluationOptions(EvaluationOptions copy)
{
if (copy != null)
{
Converters = copy.Converters;
MaxMemory = copy.MaxMemory;
TimeZone = copy.TimeZone;
UseCollectionInterfaces = copy.UseCollectionInterfaces;
}
}
/// <summary>
/// Converters allow types to be coerced into data that is friendly
/// for expression functions to operate on it.
///
/// As each node in the expression tree is evaluated, converters are applied.
/// When a node's result matches a converter type, the result is intercepted
/// by the converter, and converter result is used instead.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public IDictionary<Type, Converter<Object, ConversionResult>> Converters { get; set; }
public Int32 MaxMemory { get; set; }
public TimeZoneInfo TimeZone { get; set; }
[EditorBrowsable(EditorBrowsableState.Never)]
public Boolean UseCollectionInterfaces { get; set; } // Feature flag for now behavior
}
}

View File

@@ -1,828 +0,0 @@
using GitHub.DistributedTask.Expressions.CollectionAccessors;
using GitHub.Services.WebApi;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class EvaluationResult
{
internal EvaluationResult(
EvaluationContext context,
Int32 level,
Object val,
ValueKind kind,
Object raw)
: this(context, level, val, kind, raw, false)
{
}
internal EvaluationResult(
EvaluationContext context,
Int32 level,
Object val,
ValueKind kind,
Object raw,
Boolean omitTracing)
{
m_level = level;
Value = val;
Kind = kind;
Raw = raw;
m_omitTracing = omitTracing;
if (!omitTracing)
{
TraceValue(context);
}
}
public ValueKind Kind { get; }
/// <summary>
/// When a custom converter is applied to the node result, raw contains the original value
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Object Raw { get; }
public Object Value { get; }
public int CompareTo(
EvaluationContext context,
EvaluationResult right)
{
Object leftValue;
ValueKind leftKind;
switch (Kind)
{
case ValueKind.Boolean:
case ValueKind.DateTime:
case ValueKind.Number:
case ValueKind.String:
case ValueKind.Version:
leftValue = Value;
leftKind = Kind;
break;
default:
leftValue = ConvertToNumber(context); // Will throw or succeed
leftKind = ValueKind.Number;
break;
}
if (leftKind == ValueKind.Boolean)
{
Boolean b = right.ConvertToBoolean(context);
return ((Boolean)leftValue).CompareTo(b);
}
else if (leftKind == ValueKind.DateTime)
{
DateTimeOffset d = right.ConvertToDateTime(context);
return ((DateTimeOffset)leftValue).CompareTo(d);
}
else if (leftKind == ValueKind.Number)
{
Decimal d = right.ConvertToNumber(context);
return ((Decimal)leftValue).CompareTo(d);
}
else if (leftKind == ValueKind.String)
{
String s = right.ConvertToString(context);
return String.Compare(leftValue as String ?? String.Empty, s ?? String.Empty, StringComparison.OrdinalIgnoreCase);
}
else //if (leftKind == ValueKind.Version)
{
Version v = right.ConvertToVersion(context);
return (leftValue as Version).CompareTo(v);
}
}
public Boolean ConvertToBoolean(EvaluationContext context)
{
Boolean result;
switch (Kind)
{
case ValueKind.Boolean:
return (Boolean)Value; // Not converted. Don't trace.
case ValueKind.Number:
result = (Decimal)Value != 0m; // 0 converts to false, otherwise true.
TraceValue(context, result, ValueKind.Boolean);
return result;
case ValueKind.String:
result = !String.IsNullOrEmpty(Value as String);
TraceValue(context, result, ValueKind.Boolean);
return result;
case ValueKind.Array:
case ValueKind.DateTime:
case ValueKind.Object:
case ValueKind.Version:
result = true;
TraceValue(context, result, ValueKind.Boolean);
return result;
case ValueKind.Null:
result = false;
TraceValue(context, result, ValueKind.Boolean);
return result;
default: // Should never reach here.
throw new NotSupportedException($"Unable to convert value to Boolean. Unexpected value kind '{Kind}'.");
}
}
public DateTimeOffset ConvertToDateTime(EvaluationContext context)
{
DateTimeOffset result;
if (TryConvertToDateTime(context, out result))
{
return result;
}
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.DateTime);
}
public Object ConvertToNull(EvaluationContext context)
{
Object result;
if (TryConvertToNull(context, out result))
{
return result;
}
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.Null);
}
public Decimal ConvertToNumber(EvaluationContext context)
{
Decimal result;
if (TryConvertToNumber(context, out result))
{
return result;
}
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.Number);
}
public String ConvertToString(EvaluationContext context)
{
String result;
if (TryConvertToString(context, out result))
{
return result;
}
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.String);
}
public Version ConvertToVersion(EvaluationContext context)
{
Version result;
if (TryConvertToVersion(context, out result))
{
return result;
}
throw new TypeCastException(context?.SecretMasker, Value, fromKind: Kind, toKind: ValueKind.Version);
}
public Boolean Equals(
EvaluationContext context,
EvaluationResult right)
{
if (Kind == ValueKind.Boolean)
{
Boolean b = right.ConvertToBoolean(context);
return (Boolean)Value == b;
}
else if (Kind == ValueKind.DateTime)
{
DateTimeOffset d;
if (right.TryConvertToDateTime(context, out d))
{
return (DateTimeOffset)Value == d;
}
}
else if (Kind == ValueKind.Number)
{
Decimal d;
if (right.TryConvertToNumber(context, out d))
{
return (Decimal)Value == d;
}
}
else if (Kind == ValueKind.Version)
{
Version v;
if (right.TryConvertToVersion(context, out v))
{
return (Version)Value == v;
}
}
else if (Kind == ValueKind.String)
{
String s;
if (right.TryConvertToString(context, out s))
{
return String.Equals(
Value as String ?? String.Empty,
s ?? String.Empty,
StringComparison.OrdinalIgnoreCase);
}
}
else if (Kind == ValueKind.Array || Kind == ValueKind.Object)
{
return Kind == right.Kind && Object.ReferenceEquals(Value, right.Value);
}
else if (Kind == ValueKind.Null)
{
Object n;
if (right.TryConvertToNull(context, out n))
{
return true;
}
}
return false;
}
public Boolean TryConvertToDateTime(
EvaluationContext context,
out DateTimeOffset result)
{
switch (Kind)
{
case ValueKind.DateTime:
result = (DateTimeOffset)Value; // Not converted. Don't trace again.
return true;
case ValueKind.String:
if (TryParseDateTime(context?.Options, Value as String, out result))
{
TraceValue(context, result, ValueKind.DateTime);
return true;
}
TraceCoercionFailed(context, toKind: ValueKind.DateTime);
return false;
case ValueKind.Array:
case ValueKind.Boolean:
case ValueKind.Null:
case ValueKind.Number:
case ValueKind.Object:
case ValueKind.Version:
result = default;
TraceCoercionFailed(context, toKind: ValueKind.DateTime);
return false;
default: // Should never reach here.
throw new NotSupportedException($"Unable to determine whether value can be converted to Number. Unexpected value kind '{Kind}'.");
}
}
public Boolean TryConvertToNull(
EvaluationContext context,
out Object result)
{
switch (Kind)
{
case ValueKind.Null:
result = null; // Not converted. Don't trace again.
return true;
case ValueKind.String:
if (String.IsNullOrEmpty(Value as String))
{
result = null;
TraceValue(context, result, ValueKind.Null);
return true;
}
break;
}
result = null;
TraceCoercionFailed(context, toKind: ValueKind.Null);
return false;
}
public Boolean TryConvertToNumber(
EvaluationContext context,
out Decimal result)
{
switch (Kind)
{
case ValueKind.Boolean:
result = (Boolean)Value ? 1m : 0m;
TraceValue(context, result, ValueKind.Number);
return true;
case ValueKind.Number:
result = (Decimal)Value; // Not converted. Don't trace again.
return true;
case ValueKind.String:
String s = Value as String ?? String.Empty;
if (String.IsNullOrEmpty(s))
{
result = 0m;
TraceValue(context, result, ValueKind.Number);
return true;
}
if (Decimal.TryParse(s, s_numberStyles, CultureInfo.InvariantCulture, out result))
{
TraceValue(context, result, ValueKind.Number);
return true;
}
TraceCoercionFailed(context, toKind: ValueKind.Number);
return false;
case ValueKind.Array:
case ValueKind.DateTime:
case ValueKind.Object:
case ValueKind.Version:
result = default(Decimal);
TraceCoercionFailed(context, toKind: ValueKind.Number);
return false;
case ValueKind.Null:
result = 0m;
TraceValue(context, result, ValueKind.Number);
return true;
default: // Should never reach here.
throw new NotSupportedException($"Unable to determine whether value can be converted to Number. Unexpected value kind '{Kind}'.");
}
}
public Boolean TryConvertToString(
EvaluationContext context,
out String result)
{
switch (Kind)
{
case ValueKind.Boolean:
result = String.Format(CultureInfo.InvariantCulture, "{0}", Value);
TraceValue(context, result, ValueKind.String);
return true;
case ValueKind.DateTime:
result = ((DateTimeOffset)Value).ToString(ExpressionConstants.DateTimeFormat, CultureInfo.InvariantCulture);
TraceValue(context, result, ValueKind.String);
return true;
case ValueKind.Number:
result = ((Decimal)Value).ToString(ExpressionConstants.NumberFormat, CultureInfo.InvariantCulture);
TraceValue(context, result, ValueKind.String);
return true;
case ValueKind.String:
result = Value as String; // Not converted. Don't trace.
return true;
case ValueKind.Version:
result = (Value as Version).ToString();
TraceValue(context, result, ValueKind.String);
return true;
case ValueKind.Null:
result = String.Empty;
TraceValue(context, result, ValueKind.Null);
return true;
case ValueKind.Array:
case ValueKind.Object:
result = null;
TraceCoercionFailed(context, toKind: ValueKind.String);
return false;
default: // Should never reach here.
throw new NotSupportedException($"Unable to convert to String. Unexpected value kind '{Kind}'.");
}
}
public Boolean TryConvertToVersion(
EvaluationContext context,
out Version result)
{
switch (Kind)
{
case ValueKind.Boolean:
result = null;
TraceCoercionFailed(context, toKind: ValueKind.Version);
return false;
case ValueKind.Number:
if (Version.TryParse(ConvertToString(context), out result))
{
TraceValue(context, result, ValueKind.Version);
return true;
}
TraceCoercionFailed(context, toKind: ValueKind.Version);
return false;
case ValueKind.String:
String s = Value as String ?? String.Empty;
if (Version.TryParse(s, out result))
{
TraceValue(context, result, ValueKind.Version);
return true;
}
TraceCoercionFailed(context, toKind: ValueKind.Version);
return false;
case ValueKind.Version:
result = Value as Version; // Not converted. Don't trace again.
return true;
case ValueKind.Array:
case ValueKind.DateTime:
case ValueKind.Object:
case ValueKind.Null:
result = null;
TraceCoercionFailed(context, toKind: ValueKind.Version);
return false;
default: // Should never reach here.
throw new NotSupportedException($"Unable to convert to Version. Unexpected value kind '{Kind}'.");
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public Boolean TryGetCollectionInterface(out Object collection)
{
if ((Kind == ValueKind.Object || Kind == ValueKind.Array))
{
var obj = Value;
if (obj is IReadOnlyObject)
{
collection = obj;
return true;
}
else if (obj is IDictionary<String, String> dictionary1)
{
collection = new ReadOnlyDictionaryOfStringStringAccessor(new ReadOnlyDictionary<String, String>(dictionary1));
return true;
}
else if (obj is IDictionary<String, Object> dictionary2)
{
collection = new ReadOnlyDictionaryOfStringObjectAccessor(new ReadOnlyDictionary<String, Object>(dictionary2));
return true;
}
else if (obj is IReadOnlyDictionary<String, String> dictionary3)
{
collection = new ReadOnlyDictionaryOfStringStringAccessor(dictionary3);
return true;
}
else if (obj is IReadOnlyDictionary<String, Object> dictionary4)
{
collection = new ReadOnlyDictionaryOfStringObjectAccessor(dictionary4);
return true;
}
else if (obj is JObject jobject)
{
collection = new JObjectAccessor(jobject);
return true;
}
else if (obj is IReadOnlyArray)
{
collection = obj;
return true;
}
else if (obj is IList<Object> list1)
{
collection = new ListOfObjectAccessor(list1);
return true;
}
else if (obj is IReadOnlyList<Object> list2)
{
collection = new ReadOnlyListOfObjectAccessor(list2);
return true;
}
else if (obj is JArray jarray)
{
collection = new JArrayAccessor(jarray);
return true;
}
var contract = s_serializer.Value.ContractResolver.ResolveContract(obj.GetType());
if (contract is JsonObjectContract objectContract)
{
collection = new JsonObjectContractAccessor(objectContract, obj);
return true;
}
else if (contract is JsonDictionaryContract dictionaryContract && dictionaryContract.DictionaryKeyType == typeof(String))
{
collection = new JsonDictionaryContractAccessor(dictionaryContract, obj);
return true;
}
}
collection = null;
return false;
}
/// <summary>
/// Useful for working with values that are not the direct evaluation result of a parameter.
/// This allows ExpressionNode authors to leverage the coercion and comparision functions
/// for any values.
///
/// Also note, the value will be canonicalized (for example numeric types converted to decimal) and any
/// matching converters applied.
/// </summary>
public static EvaluationResult CreateIntermediateResult(
EvaluationContext context,
Object obj,
out ResultMemory conversionResultMemory)
{
var val = ExpressionUtil.ConvertToCanonicalValue(context?.Options, obj, out ValueKind kind, out Object raw, out conversionResultMemory);
return new EvaluationResult(context, 0, val, kind, raw, omitTracing: true);
}
private void TraceCoercionFailed(
EvaluationContext context,
ValueKind toKind)
{
if (!m_omitTracing)
{
TraceVerbose(context, String.Format(CultureInfo.InvariantCulture, "=> Unable to coerce {0} to {1}.", Kind, toKind));
}
}
private void TraceValue(EvaluationContext context)
{
if (!m_omitTracing)
{
TraceValue(context, Value, Kind);
}
}
private void TraceValue(
EvaluationContext context,
Object val,
ValueKind kind)
{
if (!m_omitTracing)
{
TraceVerbose(context, String.Concat("=> ", ExpressionUtil.FormatValue(context?.SecretMasker, val, kind)));
}
}
private void TraceVerbose(
EvaluationContext context,
String message)
{
if (!m_omitTracing)
{
context?.Trace.Verbose(String.Empty.PadLeft(m_level * 2, '.') + (message ?? String.Empty));
}
}
private static Boolean TryParseDateTime(
EvaluationOptions options,
String s,
out DateTimeOffset result)
{
if (String.IsNullOrEmpty(s))
{
result = default;
return false;
}
s = s.Trim();
var i = 0;
// Year, month, day, hour, min, sec
if (!ReadInt32(s, 4, 4, ref i, out Int32 year) ||
!ReadSeparator(s, ref i, new[] { '-', '/' }, out Char dateSeparator) ||
!ReadInt32(s, 1, 2, ref i, out Int32 month) ||
!ReadSeparator(s, ref i, dateSeparator) ||
!ReadInt32(s, 1, 2, ref i, out Int32 day) ||
!ReadSeparator(s, ref i, ' ', 'T') ||
!ReadInt32(s, 1, 2, ref i, out Int32 hour) ||
!ReadSeparator(s, ref i, ':') ||
!ReadInt32(s, 1, 2, ref i, out Int32 minute) ||
!ReadSeparator(s, ref i, ':') ||
!ReadInt32(s, 1, 2, ref i, out Int32 second))
{
result = default;
return false;
}
// Fraction of second
Int32 ticks;
if (ExpressionUtil.SafeCharAt(s, i) == '.')
{
i++;
if (!ReadDigits(s, 1, 7, ref i, out String digits))
{
result = default;
return false;
}
if (digits.Length < 7)
{
digits = digits.PadRight(7, '0');
}
ticks = Int32.Parse(digits, NumberStyles.None, CultureInfo.InvariantCulture);
}
else
{
ticks = 0;
}
TimeSpan offset;
// End of string indicates local time zone
if (i >= s.Length)
{
// Determine the offset
var timeZone = options?.TimeZone ?? TimeZoneInfo.Local;
try
{
var dateTime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified);
offset = timeZone.GetUtcOffset(dateTime);
}
catch
{
result = default;
return false;
}
}
// Offset, then end of string
else if (!ReadOffset(s, ref i, out offset) ||
i < s.Length)
{
result = default;
return false;
}
// Construct the DateTimeOffset
try
{
result = new DateTimeOffset(year, month, day, hour, minute, second, offset);
}
catch
{
result = default;
return false;
}
// Add fraction of second
if (ticks > 0)
{
result = result.AddTicks(ticks);
}
return true;
}
private static Boolean ReadDigits(
String str,
Int32 minLength,
Int32 maxLength,
ref Int32 index,
out String result)
{
var startIndex = index;
while (Char.IsDigit(ExpressionUtil.SafeCharAt(str, index)))
{
index++;
}
var length = index - startIndex;
if (length < minLength || length > maxLength)
{
result = default;
return false;
}
result = str.Substring(startIndex, length);
return true;
}
private static Boolean ReadInt32(
String str,
Int32 minLength,
Int32 maxLength,
ref Int32 index,
out Int32 result)
{
if (!ReadDigits(str, minLength, maxLength, ref index, out String digits))
{
result = default;
return false;
}
result = Int32.Parse(digits, NumberStyles.None, CultureInfo.InvariantCulture);
return true;
}
private static Boolean ReadSeparator(
String str,
ref Int32 index,
params Char[] allowed)
{
return ReadSeparator(str, ref index, allowed, out _);
}
private static Boolean ReadSeparator(
String str,
ref Int32 index,
Char[] allowed,
out Char separator)
{
separator = ExpressionUtil.SafeCharAt(str, index++);
foreach (var a in allowed)
{
if (separator == a)
{
return true;
}
}
separator = default;
return false;
}
private static Boolean ReadOffset(
String str,
ref Int32 index,
out TimeSpan offset)
{
// Z indicates UTC
if (ExpressionUtil.SafeCharAt(str, index) == 'Z')
{
index++;
offset = TimeSpan.Zero;
return true;
}
Boolean subtract;
// Negative
if (ExpressionUtil.SafeCharAt(str, index) == '-')
{
index++;
subtract = true;
}
// Positive
else if (ExpressionUtil.SafeCharAt(str, index) == '+')
{
index++;
subtract = false;
}
// Invalid
else
{
offset = default;
return false;
}
// Hour and minute
if (!ReadInt32(str, 1, 2, ref index, out Int32 hour) ||
!ReadSeparator(str, ref index, ':') ||
!ReadInt32(str, 1, 2, ref index, out Int32 minute))
{
offset = default;
return false;
}
// Construct the offset
if (subtract)
{
offset = TimeSpan.Zero.Subtract(new TimeSpan(hour, minute, 0));
}
else
{
offset = new TimeSpan(hour, minute, 0);
}
return true;
}
private static readonly NumberStyles s_numberStyles =
NumberStyles.AllowDecimalPoint |
NumberStyles.AllowLeadingSign |
NumberStyles.AllowLeadingWhite |
NumberStyles.AllowThousands |
NumberStyles.AllowTrailingWhite;
private static readonly Lazy<JsonSerializer> s_serializer = new Lazy<JsonSerializer>(() => JsonUtility.CreateJsonSerializer());
private readonly Int32 m_level;
private readonly Boolean m_omitTracing;
}
}

View File

@@ -1,37 +0,0 @@
using System;
using GitHub.DistributedTask.Logging;
using GitHub.Services.Common;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class EvaluationTraceWriter : ITraceWriter
{
public EvaluationTraceWriter(ITraceWriter trace, ISecretMasker secretMasker)
{
ArgumentUtility.CheckForNull(secretMasker, nameof(secretMasker));
m_trace = trace;
m_secretMasker = secretMasker;
}
public void Info(String message)
{
if (m_trace != null)
{
message = m_secretMasker.MaskSecrets(message);
m_trace.Info(message);
}
}
public void Verbose(String message)
{
if (m_trace != null)
{
message = m_secretMasker.MaskSecrets(message);
m_trace.Verbose(message);
}
}
private readonly ISecretMasker m_secretMasker;
private readonly ITraceWriter m_trace;
}
}

View File

@@ -1,52 +0,0 @@
using System;
using System.Collections.Generic;
namespace GitHub.DistributedTask.Expressions
{
internal static class ExpressionConstants
{
static ExpressionConstants()
{
AddFunction<AndNode>("and", 2, Int32.MaxValue);
AddFunction<CoalesceNode>("coalesce", 2, Int32.MaxValue);
AddFunction<ContainsNode>("contains", 2, 2);
AddFunction<ContainsValueNode>("containsValue", 2, 2);
AddFunction<EndsWithNode>("endsWith", 2, 2);
AddFunction<EqualNode>("eq", 2, 2);
AddFunction<FormatNode>("format", 1, Byte.MaxValue);
AddFunction<GreaterThanNode>("gt", 2, 2);
AddFunction<GreaterThanOrEqualNode>("ge", 2, 2);
AddFunction<LessThanNode>("lt", 2, 2);
AddFunction<JoinNode>("join", 2, 2);
AddFunction<LessThanOrEqualNode>("le", 2, 2);
AddFunction<InNode>("in", 2, Int32.MaxValue);
AddFunction<NotNode>("not", 1, 1);
AddFunction<NotEqualNode>("ne", 2, 2);
AddFunction<NotInNode>("notIn", 2, Int32.MaxValue);
AddFunction<OrNode>("or", 2, Int32.MaxValue);
AddFunction<StartsWithNode>("startsWith", 2, 2);
AddFunction<XorNode>("xor", 2, 2);
}
private static void AddFunction<T>(String name, Int32 minParameters, Int32 maxParameters)
where T : FunctionNode, new()
{
WellKnownFunctions.Add(name, new FunctionInfo<T>(name, minParameters, maxParameters));
}
internal static readonly String DateTimeFormat = @"yyyy\-MM\-dd\ HH\:mm\:sszzz";
internal static readonly Int32 MaxDepth = 50;
internal static readonly Int32 MaxLength = 21000; // Under 85,000 large object heap threshold, even if .NET switches to UTF-32
internal static readonly String NumberFormat = "0.#######";
internal static readonly Dictionary<String, IFunctionInfo> WellKnownFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
// Punctuation
internal const Char StartIndex = '[';
internal const Char StartParameter = '(';
internal const Char EndIndex = ']';
internal const Char EndParameter = ')';
internal const Char Separator = ',';
internal const Char Dereference = '.';
internal const Char Wildcard = '*';
}
}

View File

@@ -1,22 +0,0 @@
using System;
using GitHub.DistributedTask.Logging;
namespace GitHub.DistributedTask.Expressions
{
public class ExpressionException : Exception
{
internal ExpressionException(ISecretMasker secretMasker, String message)
{
if (secretMasker != null)
{
message = secretMasker.MaskSecrets(message);
}
m_message = message;
}
public override String Message => m_message;
private readonly String m_message;
}
}

View File

@@ -1,494 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using GitHub.DistributedTask.Logging;
using GitHub.Services.WebApi;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class ExpressionNode : IExpressionNode
{
internal ContainerNode Container { get; set; }
internal Int32 Level { get; private set; }
/// <summary>
/// The name is used for tracing. Normally the parser will set the name. However if a node
/// is added manually, then the name may not be set and will fallback to the type name.
/// </summary>
protected internal String Name
{
get
{
return !String.IsNullOrEmpty(m_name) ? m_name : this.GetType().Name;
}
set
{
m_name = value;
}
}
/// <summary>
/// Indicates whether the evalation result should be stored on the context and used
/// when the realized result is traced.
/// </summary>
protected abstract Boolean TraceFullyRealized { get; }
internal abstract String ConvertToExpression();
internal abstract String ConvertToRealizedExpression(EvaluationContext context);
/// <summary>
/// Evaluates the node
/// </summary>
protected virtual Object EvaluateCore(EvaluationContext context)
{
throw new InvalidOperationException($"Method {nameof(EvaluateCore)} not implemented");
}
/// <summary>
/// Evaluates the node
/// </summary>
/// <param name="context">The current expression context</param>
/// <param name="resultMemory">
/// Helps determine how much memory is being consumed across the evaluation of the expression.
/// </param>
protected virtual Object EvaluateCore(
EvaluationContext context,
out ResultMemory resultMemory)
{
resultMemory = null;
return EvaluateCore(context);
}
/// <summary>
/// INode entry point.
/// </summary>
public T Evaluate<T>(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state,
EvaluationOptions options = null)
{
if (Container != null)
{
// Do not localize. This is an SDK consumer error.
throw new NotSupportedException($"Expected {nameof(IExpressionNode)}.{nameof(Evaluate)} to be called on root node only.");
}
ISecretMasker originalSecretMasker = secretMasker;
try
{
secretMasker = secretMasker?.Clone() ?? new SecretMasker();
trace = new EvaluationTraceWriter(trace, secretMasker);
var context = new EvaluationContext(trace, secretMasker, state, options, this);
trace.Info($"Evaluating: {ConvertToExpression()}");
// String
if (typeof(T).Equals(typeof(String)))
{
String stringResult = EvaluateString(context);
TraceTreeResult(context, stringResult, ValueKind.String);
return (T)(Object)stringResult;
}
// Boolean
else if (typeof(T).Equals(typeof(Boolean)))
{
Boolean booleanResult = EvaluateBoolean(context);
TraceTreeResult(context, booleanResult, ValueKind.Boolean);
return (T)(Object)booleanResult;
}
// Version
else if (typeof(T).Equals(typeof(Version)))
{
Version versionResult = EvaluateVersion(context);
TraceTreeResult(context, versionResult, ValueKind.Version);
return (T)(Object)versionResult;
}
// DateTime types
else if (typeof(T).Equals(typeof(DateTimeOffset)))
{
DateTimeOffset dateTimeResult = EvaluateDateTime(context);
TraceTreeResult(context, dateTimeResult, ValueKind.DateTime);
return (T)(Object)dateTimeResult;
}
else if (typeof(T).Equals(typeof(DateTime)))
{
DateTimeOffset dateTimeResult = EvaluateDateTime(context);
TraceTreeResult(context, dateTimeResult, ValueKind.DateTime);
return (T)(Object)dateTimeResult.UtcDateTime;
}
TypeInfo typeInfo = typeof(T).GetTypeInfo();
if (typeInfo.IsPrimitive)
{
// Decimal
if (typeof(T).Equals(typeof(Decimal)))
{
Decimal decimalResult = EvaluateNumber(context);
TraceTreeResult(context, decimalResult, ValueKind.Number);
return (T)(Object)decimalResult;
}
// Other numeric types
else if (typeof(T).Equals(typeof(Byte)) ||
typeof(T).Equals(typeof(SByte)) ||
typeof(T).Equals(typeof(Int16)) ||
typeof(T).Equals(typeof(UInt16)) ||
typeof(T).Equals(typeof(Int32)) ||
typeof(T).Equals(typeof(UInt32)) ||
typeof(T).Equals(typeof(Int64)) ||
typeof(T).Equals(typeof(UInt64)) ||
typeof(T).Equals(typeof(Single)) ||
typeof(T).Equals(typeof(Double)))
{
Decimal decimalResult = EvaluateNumber(context);
trace.Verbose($"Converting expression result to type {typeof(T).Name}");
try
{
T numericResult = (T)Convert.ChangeType(decimalResult, typeof(T));
// Note, the value is converted back to decimal before tracing, in order to leverage the same
// util-formatting method used in other places.
TraceTreeResult(context, Convert.ToDecimal((Object)numericResult), ValueKind.Number);
return numericResult;
}
catch (Exception exception)
{
context.Trace.Verbose($"Failed to convert the result number into the type {typeof(T).Name}. {exception.Message}");
throw new TypeCastException(
secretMasker,
value: decimalResult,
fromKind: ValueKind.Number,
toType: typeof(T),
error: exception.Message);
}
}
}
// Generic evaluate
EvaluationResult result = Evaluate(context);
TraceTreeResult(context, result.Value, result.Kind);
// JToken
if (typeof(T).Equals(typeof(JToken)))
{
if (result.Value is null)
{
return default;
}
else if (result.Value is JToken)
{
return (T)result.Value;
}
else
{
return (T)(Object)JToken.FromObject(result.Value, JsonUtility.CreateJsonSerializer());
}
}
// Object or Array
else if (result.Kind == ValueKind.Object || result.Kind == ValueKind.Array)
{
Type resultType = result.Value.GetType();
context.Trace.Verbose($"Result type: {resultType.Name}");
if (typeInfo.IsAssignableFrom(resultType.GetTypeInfo()))
{
return (T)result.Value;
}
else
{
context.Trace.Verbose($"Unable to assign result to the type {typeof(T).Name}");
throw new TypeCastException(fromType: resultType, toType: typeof(T));
}
}
// Null
else if (result.Kind == ValueKind.Null)
{
return default;
}
// String
else if (result.Kind == ValueKind.String)
{
// Treat empty string as null
String stringResult = result.Value as String;
if (String.IsNullOrEmpty(stringResult))
{
return default;
}
// Otherwise deserialize
try
{
return JsonUtility.FromString<T>(stringResult);
}
catch (Exception exception) when (exception is JsonReaderException || exception is JsonSerializationException)
{
context.Trace.Verbose($"Failed to json-deserialize the result string into the type {typeof(T).Name}. {exception.Message}");
throw new TypeCastException(
context.SecretMasker,
value: stringResult,
fromKind: ValueKind.String,
toType: typeof(T),
error: exception.Message);
}
}
else
{
context.Trace.Verbose($"Unable to convert from kind {result.Kind} to the type {typeof(T).Name}");
throw new TypeCastException(
context.SecretMasker,
value: result.Value,
fromKind: result.Kind,
toType: typeof(T));
}
}
finally
{
if (secretMasker != null && secretMasker != originalSecretMasker)
{
(secretMasker as IDisposable)?.Dispose();
secretMasker = null;
}
}
}
/// <summary>
/// INode entry point.
/// </summary>
public Object Evaluate(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state,
EvaluationOptions options = null)
{
return Evaluate(trace, secretMasker, state, options, out _, out _);
}
/// <summary>
/// INode entry point.
/// </summary>
public Boolean EvaluateBoolean(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state)
{
return Evaluate<Boolean>(trace, secretMasker, state);
}
/// <summary>
/// INode entry point.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public EvaluationResult EvaluateResult(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state,
EvaluationOptions options)
{
var val = Evaluate(trace, secretMasker, state, options, out ValueKind kind, out Object raw);
return new EvaluationResult(null, 0, val, kind, raw, omitTracing: true);
}
/// <summary>
/// This function is intended only for ExpressionNode authors to call. The EvaluationContext
/// caches result-state specific to the evaluation instance.
/// </summary>
public EvaluationResult Evaluate(EvaluationContext context)
{
// Evaluate
Level = Container == null ? 0 : Container.Level + 1;
TraceVerbose(context, Level, $"Evaluating {Name}:");
var coreResult = EvaluateCore(context, out ResultMemory coreMemory);
if (coreMemory == null)
{
coreMemory = new ResultMemory();
}
// Convert to canonical value
var val = ExpressionUtil.ConvertToCanonicalValue(context.Options, coreResult, out ValueKind kind, out Object raw, out ResultMemory conversionMemory);
// The depth can be safely trimmed when the total size of the core result is known,
// or when the total size of the core result can easily be determined.
var trimDepth = coreMemory.IsTotal || (Object.ReferenceEquals(raw, null) && s_simpleKinds.Contains(kind));
// Account for the memory overhead of the core result
var coreBytes = coreMemory.Bytes ?? EvaluationMemory.CalculateBytes(raw ?? val);
context.Memory.AddAmount(Level, coreBytes, trimDepth);
// Account for the memory overhead of the conversion result
if (!Object.ReferenceEquals(raw, null))
{
if (conversionMemory == null)
{
conversionMemory = new ResultMemory();
}
var conversionBytes = conversionMemory.Bytes ?? EvaluationMemory.CalculateBytes(val);
context.Memory.AddAmount(Level, conversionBytes);
}
var result = new EvaluationResult(context, Level, val, kind, raw);
// Store the trace result
if (this.TraceFullyRealized)
{
context.SetTraceResult(this, result);
}
return result;
}
/// <summary>
/// This function is intended only for ExpressionNode authors to call during evaluation.
/// The EvaluationContext caches result-state specific to the evaluation instance.
/// </summary>
public Boolean EvaluateBoolean(EvaluationContext context)
{
return Evaluate(context).ConvertToBoolean(context);
}
/// <summary>
/// This function is intended only for ExpressionNode authors to call during evaluation.
/// The EvaluationContext caches result-state specific to the evaluation instance.
/// </summary>
public DateTimeOffset EvaluateDateTime(EvaluationContext context)
{
return Evaluate(context).ConvertToDateTime(context);
}
/// <summary>
/// This function is intended only for ExpressionNode authors to call during evaluation.
/// The EvaluationContext caches result-state specific to the evaluation instance.
/// </summary>
public Decimal EvaluateNumber(EvaluationContext context)
{
return Evaluate(context).ConvertToNumber(context);
}
/// <summary>
/// This function is intended only for ExpressionNode authors to call during evaluation.
/// The EvaluationContext caches result-state specific to the evaluation instance.
/// </summary>
public String EvaluateString(EvaluationContext context)
{
return Evaluate(context).ConvertToString(context);
}
/// <summary>
/// This function is intended only for ExpressionNode authors to call during evaluation.
/// The EvaluationContext caches result-state specific to the evaluation instance.
/// </summary>
public Version EvaluateVersion(EvaluationContext context)
{
return Evaluate(context).ConvertToVersion(context);
}
public virtual IEnumerable<T> GetParameters<T>() where T : IExpressionNode
{
return new T[0];
}
protected MemoryCounter CreateMemoryCounter(EvaluationContext context)
{
return new MemoryCounter(this, context.Options.MaxMemory);
}
private Object Evaluate(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state,
EvaluationOptions options,
out ValueKind kind,
out Object raw)
{
if (Container != null)
{
// Do not localize. This is an SDK consumer error.
throw new NotSupportedException($"Expected {nameof(IExpressionNode)}.{nameof(Evaluate)} to be called on root node only.");
}
ISecretMasker originalSecretMasker = secretMasker;
try
{
// Evaluate
secretMasker = secretMasker?.Clone() ?? new SecretMasker();
trace = new EvaluationTraceWriter(trace, secretMasker);
var context = new EvaluationContext(trace, secretMasker, state, options, this);
trace.Info($"Evaluating: {ConvertToExpression()}");
EvaluationResult result = Evaluate(context);
// Trace the result
TraceTreeResult(context, result.Value, result.Kind);
kind = result.Kind;
raw = result.Raw;
return result.Value;
}
finally
{
if (secretMasker != null && secretMasker != originalSecretMasker)
{
(secretMasker as IDisposable)?.Dispose();
secretMasker = null;
}
}
}
private void TraceTreeResult(
EvaluationContext context,
Object result,
ValueKind kind)
{
// Get the realized expression
String realizedExpression = ConvertToRealizedExpression(context);
// Format the result
String traceValue = ExpressionUtil.FormatValue(context.SecretMasker, result, kind);
// Only trace the realized expression if it is meaningfully different
if (!String.Equals(realizedExpression, traceValue, StringComparison.Ordinal))
{
if (kind == ValueKind.Number &&
String.Equals(realizedExpression, $"'{traceValue}'", StringComparison.Ordinal))
{
// Don't bother tracing the realized expression when the result is a number and the
// realized expresion is a precisely matching string.
}
else
{
context.Trace.Info($"Expanded: {realizedExpression}");
}
}
// Always trace the result
context.Trace.Info($"Result: {traceValue}");
}
private static void TraceVerbose(
EvaluationContext context,
Int32 level,
String message)
{
context.Trace.Verbose(String.Empty.PadLeft(level * 2, '.') + (message ?? String.Empty));
}
private static readonly ValueKind[] s_simpleKinds = new[]
{
ValueKind.Boolean,
ValueKind.DateTime,
ValueKind.Null,
ValueKind.Number,
ValueKind.String,
ValueKind.Version,
};
private String m_name;
}
}

View File

@@ -1,547 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class ExpressionParser
{
public ExpressionParser(): this(null)
{
}
public ExpressionParser(ExpressionParserOptions options)
{
m_parserOptions = options ?? new ExpressionParserOptions();
}
public IExpressionNode CreateTree(
String expression,
ITraceWriter trace,
IEnumerable<INamedValueInfo> namedValues,
IEnumerable<IFunctionInfo> functions)
{
var context = new ParseContext(expression, trace, namedValues, functions, allowUnknownKeywords: false, allowKeywordHyphens: m_parserOptions.AllowHyphens);
context.Trace.Info($"Parsing expression: <{expression}>");
return CreateTree(context);
}
public void ValidateSyntax(
String expression,
ITraceWriter trace)
{
var context = new ParseContext(expression, trace, namedValues: null, functions: null, allowUnknownKeywords: true, allowKeywordHyphens: m_parserOptions.AllowHyphens);
context.Trace.Info($"Validating expression syntax: <{expression}>");
CreateTree(context);
}
private static IExpressionNode CreateTree(ParseContext context)
{
while (TryGetNextToken(context))
{
switch (context.Token.Kind)
{
// Punctuation
case TokenKind.StartIndex:
HandleStartIndex(context);
break;
case TokenKind.EndIndex:
HandleEndIndex(context);
break;
case TokenKind.EndParameter:
HandleEndParameter(context);
break;
case TokenKind.Separator:
HandleSeparator(context);
break;
case TokenKind.Dereference:
HandleDereference(context);
break;
case TokenKind.Wildcard:
HandleWildcard(context);
break;
// Functions
case TokenKind.WellKnownFunction:
case TokenKind.ExtensionFunction:
HandleFunction(context);
break;
// Leaf values
case TokenKind.Boolean:
case TokenKind.Number:
case TokenKind.Version:
case TokenKind.String:
case TokenKind.ExtensionNamedValue:
HandleValue(context);
break;
// Unknown keyword
case TokenKind.UnknownKeyword:
HandleUnknownKeyword(context);
break;
// Malformed
case TokenKind.Unrecognized:
throw new ParseException(ParseExceptionKind.UnrecognizedValue, context.Token, context.Expression);
// Unexpected
case TokenKind.PropertyName: // PropertyName should never reach here (HandleDereference reads next token).
case TokenKind.StartParameter: // StartParameter is only expected by HandleFunction.
default:
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
// Validate depth.
if (context.Containers.Count >= ExpressionConstants.MaxDepth)
{
throw new ParseException(ParseExceptionKind.ExceededMaxDepth, token: null, expression: context.Expression);
}
}
// Validate all containers were closed.
if (context.Containers.Count > 0)
{
ContainerInfo container = context.Containers.Peek();
if (container.Node is FunctionNode)
{
throw new ParseException(ParseExceptionKind.UnclosedFunction, container.Token, context.Expression);
}
else
{
throw new ParseException(ParseExceptionKind.UnclosedIndexer, container.Token, context.Expression);
}
}
return context.Root;
}
private static bool TryGetNextToken(ParseContext context)
{
context.LastToken = context.Token;
if (context.Lexer.TryGetNextToken(ref context.Token))
{
// Adjust indent level.
int indentLevel = context.Containers.Count;
if (indentLevel > 0)
{
switch (context.Token.Kind)
{
case TokenKind.StartParameter:
case TokenKind.EndParameter:
case TokenKind.EndIndex:
indentLevel--;
break;
}
}
String indent = String.Empty.PadRight(indentLevel * 2, '.');
switch (context.Token.Kind)
{
// Literal values
case TokenKind.Boolean:
context.Trace.Verbose($"{indent}{ExpressionUtil.FormatValue(null, context.Token.ParsedValue, ValueKind.Boolean)}");
break;
case TokenKind.Number:
context.Trace.Verbose($"{indent}{ExpressionUtil.FormatValue(null, context.Token.ParsedValue, ValueKind.Number)}");
break;
case TokenKind.Version:
context.Trace.Verbose($"{indent}{ExpressionUtil.FormatValue(null, context.Token.ParsedValue, ValueKind.Version)}");
break;
case TokenKind.String:
context.Trace.Verbose($"{indent}{ExpressionUtil.FormatValue(null, context.Token.ParsedValue, ValueKind.String)}");
break;
// Property or unrecognized
case TokenKind.PropertyName:
case TokenKind.Unrecognized:
context.Trace.Verbose($"{indent}{context.Token.Kind} {ExpressionUtil.FormatValue(null, context.Token.RawValue, ValueKind.String)}");
break;
// Function or punctuation
case TokenKind.WellKnownFunction:
case TokenKind.ExtensionFunction:
case TokenKind.ExtensionNamedValue:
case TokenKind.Wildcard:
case TokenKind.UnknownKeyword:
case TokenKind.StartIndex:
case TokenKind.StartParameter:
case TokenKind.EndIndex:
case TokenKind.EndParameter:
case TokenKind.Separator:
case TokenKind.Dereference:
context.Trace.Verbose($"{indent}{context.Token.RawValue}");
break;
default: // Should never reach here.
throw new NotSupportedException($"Unexpected token kind: {context.Token.Kind}");
}
return true;
}
return false;
}
private static void HandleStartIndex(ParseContext context)
{
// Validate follows ")", "]", "*", or a property name.
if (context.LastToken == null ||
(context.LastToken.Kind != TokenKind.EndParameter && context.LastToken.Kind != TokenKind.EndIndex && context.LastToken.Kind != TokenKind.PropertyName && context.LastToken.Kind != TokenKind.ExtensionNamedValue && context.LastToken.Kind != TokenKind.UnknownKeyword && context.LastToken.Kind != TokenKind.Wildcard))
{
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
// Wrap the object being indexed into.
var indexer = new IndexerNode();
ExpressionNode obj = null;
if (context.Containers.Count > 0)
{
ContainerNode container = context.Containers.Peek().Node;
Int32 objIndex = container.Parameters.Count - 1;
obj = container.Parameters[objIndex];
container.ReplaceParameter(objIndex, indexer);
}
else
{
obj = context.Root;
context.Root = indexer;
}
indexer.AddParameter(obj);
// Update the container stack.
context.Containers.Push(new ContainerInfo() { Node = indexer, Token = context.Token });
}
private static void HandleDereference(ParseContext context)
{
// Validate follows ")", "]", "*", or a property name.
if (context.LastToken == null ||
(context.LastToken.Kind != TokenKind.EndParameter && context.LastToken.Kind != TokenKind.EndIndex && context.LastToken.Kind != TokenKind.PropertyName && context.LastToken.Kind != TokenKind.ExtensionNamedValue && context.LastToken.Kind != TokenKind.UnknownKeyword && context.LastToken.Kind != TokenKind.Wildcard))
{
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
// Wrap the object being indexed into.
var indexer = new IndexerNode();
ExpressionNode obj = null;
if (context.Containers.Count > 0)
{
ContainerNode container = context.Containers.Peek().Node;
Int32 objIndex = container.Parameters.Count - 1;
obj = container.Parameters[objIndex];
container.ReplaceParameter(objIndex, indexer);
}
else
{
obj = context.Root;
context.Root = indexer;
}
indexer.AddParameter(obj);
// Validate a token follows.
if (!TryGetNextToken(context))
{
throw new ParseException(ParseExceptionKind.ExpectedPropertyName, context.LastToken, context.Expression);
}
if (context.Token.Kind == TokenKind.PropertyName)
{
indexer.AddParameter(new LiteralValueNode(context.Token.RawValue));
}
else if (context.Token.Kind == TokenKind.Wildcard)
{
// For a wildcard we add a third parameter, a boolean set to true, so that we know it's a wildcard.
indexer.AddParameter(new LiteralValueNode(context.Token.RawValue));
indexer.AddParameter(new LiteralValueNode(true));
}
else
{
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
}
private static void HandleWildcard(ParseContext context)
{
// Validate follows "[".
if (context.LastToken == null ||
context.LastToken.Kind != TokenKind.StartIndex)
{
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
// When we have a wildcard, we add the wildcard and also third boolean parameter set to true.
// This lets us differentiate downstream from '*'.
context.Containers.Peek().Node.AddParameter(new LiteralValueNode(context.Token.RawValue));
context.Containers.Peek().Node.AddParameter(new LiteralValueNode(true));
}
private static void HandleEndParameter(ParseContext context)
{
ContainerInfo container = context.Containers.Count > 0 ? context.Containers.Peek() : null; // Validate:
if (container == null || // 1) Container is not null
!(container.Node is FunctionNode) || // 2) Container is a function
container.Node.Parameters.Count < GetMinParamCount(context, container.Token) || // 3) Not below min param threshold
container.Node.Parameters.Count > GetMaxParamCount(context, container.Token) || // 4) Not above max param threshold
context.LastToken.Kind == TokenKind.Separator) // 5) Last token is not a separator
{
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
context.Containers.Pop();
}
private static void HandleEndIndex(ParseContext context)
{
IndexerNode indexer = context.Containers.Count > 0 ? context.Containers.Peek().Node as IndexerNode : null;
// // Validate:
if (indexer == null || // 1) Container is an indexer
!(indexer.Parameters.Count == 2 || indexer.Parameters.Count == 3)) // 2) Can be 2 or 3 parameters. It's 3 parameters when we are using a filtered array since we
// set a boolean along with the wildcard.
{
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
context.Containers.Pop();
}
private static void HandleUnknownKeyword(ParseContext context)
{
// Validate.
if (!context.AllowUnknownKeywords)
{
throw new ParseException(ParseExceptionKind.UnrecognizedValue, context.Token, context.Expression);
}
// Try handle function.
if (HandleFunction(context, bestEffort: true))
{
return;
}
// Handle named value.
HandleValue(context);
}
private static void HandleValue(ParseContext context)
{
// Validate either A) is the first token OR B) follows "[" "(" or ",".
if (context.LastToken != null &&
context.LastToken.Kind != TokenKind.StartIndex &&
context.LastToken.Kind != TokenKind.StartParameter &&
context.LastToken.Kind != TokenKind.Separator)
{
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
// Create the node.
ExpressionNode node;
switch (context.Token.Kind)
{
case TokenKind.ExtensionNamedValue:
String name = context.Token.RawValue;
node = context.ExtensionNamedValues[name].CreateNode();
node.Name = name;
break;
case TokenKind.UnknownKeyword:
node = new UnknownNamedValueNode();
node.Name = context.Token.RawValue;
break;
default:
node = new LiteralValueNode(context.Token.ParsedValue);
break;
}
// Update the tree.
if (context.Root == null)
{
context.Root = node;
}
else
{
context.Containers.Peek().Node.AddParameter(node);
}
}
private static void HandleSeparator(ParseContext context)
{
ContainerInfo container = context.Containers.Count > 0 ? context.Containers.Peek() : null; // Validate:
if (container == null || // 1) Container is not null
!(container.Node is FunctionNode) || // 2) Container is a function
container.Node.Parameters.Count < 1 || // 3) At least one parameter
container.Node.Parameters.Count >= GetMaxParamCount(context, container.Token) ||// 4) Under max parameters threshold
context.LastToken.Kind == TokenKind.Separator) // 5) Last token is not a separator
{
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
}
private static Boolean HandleFunction(
ParseContext context,
Boolean bestEffort = false)
{
// Validate either A) is first token OR B) follows "," or "[" or "(".
if (context.LastToken != null &&
(context.LastToken.Kind != TokenKind.Separator &&
context.LastToken.Kind != TokenKind.StartIndex &&
context.LastToken.Kind != TokenKind.StartParameter))
{
if (bestEffort)
{
return false;
}
throw new ParseException(ParseExceptionKind.UnexpectedSymbol, context.Token, context.Expression);
}
// Validate '(' follows.
if (bestEffort)
{
Token nextToken = null;
if (!context.Lexer.TryPeekNextToken(ref nextToken) || nextToken.Kind != TokenKind.StartParameter)
{
return false;
}
TryGetNextToken(context);
}
else if (!TryGetNextToken(context) || context.Token.Kind != TokenKind.StartParameter)
{
throw new ParseException(ParseExceptionKind.ExpectedStartParameter, context.LastToken, context.Expression);
}
// Create the node.
FunctionNode node;
String name = context.LastToken.RawValue;
switch (context.LastToken.Kind)
{
case TokenKind.WellKnownFunction:
node = ExpressionConstants.WellKnownFunctions[name].CreateNode();
node.Name = name;
break;
case TokenKind.ExtensionFunction:
node = context.ExtensionFunctions[name].CreateNode();
node.Name = name;
break;
case TokenKind.UnknownKeyword:
node = new UnknownFunctionNode();
node.Name = name;
break;
default:
// Should never reach here.
throw new NotSupportedException($"Unexpected function token name: '{context.LastToken.Kind}'");
}
// Update the tree.
if (context.Root == null)
{
context.Root = node;
}
else
{
context.Containers.Peek().Node.AddParameter(node);
}
// Update the container stack.
context.Containers.Push(new ContainerInfo() { Node = node, Token = context.LastToken });
return true;
}
private static int GetMinParamCount(
ParseContext context,
Token token)
{
switch (token.Kind)
{
case TokenKind.WellKnownFunction:
return ExpressionConstants.WellKnownFunctions[token.RawValue].MinParameters;
case TokenKind.ExtensionFunction:
return context.ExtensionFunctions[token.RawValue].MinParameters;
case TokenKind.UnknownKeyword:
return 0;
default: // Should never reach here.
throw new NotSupportedException($"Unexpected token kind '{token.Kind}'. Unable to determine min param count.");
}
}
private static Int32 GetMaxParamCount(
ParseContext context,
Token token)
{
switch (token.Kind)
{
case TokenKind.WellKnownFunction:
return ExpressionConstants.WellKnownFunctions[token.RawValue].MaxParameters;
case TokenKind.ExtensionFunction:
return context.ExtensionFunctions[token.RawValue].MaxParameters;
case TokenKind.UnknownKeyword:
return Int32.MaxValue;
default: // Should never reach here.
throw new NotSupportedException($"Unexpected token kind '{token.Kind}'. Unable to determine max param count.");
}
}
private ExpressionParserOptions m_parserOptions;
private sealed class ContainerInfo
{
public ContainerNode Node { get; set; }
public Token Token { get; set; }
}
private sealed class ParseContext
{
public readonly Boolean AllowUnknownKeywords;
public readonly Stack<ContainerInfo> Containers = new Stack<ContainerInfo>();
public readonly String Expression;
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
public readonly LexicalAnalyzer Lexer;
public readonly ITraceWriter Trace;
public Token Token;
public Token LastToken;
public ExpressionNode Root;
public ParseContext(
String expression,
ITraceWriter trace,
IEnumerable<INamedValueInfo> namedValues,
IEnumerable<IFunctionInfo> functions,
Boolean allowUnknownKeywords = false,
Boolean allowKeywordHyphens = false)
{
Expression = expression ?? String.Empty;
if (Expression.Length > ExpressionConstants.MaxLength)
{
throw new ParseException(ParseExceptionKind.ExceededMaxLength, token: null, expression: Expression);
}
Trace = trace ?? new NoOperationTraceWriter();
foreach (INamedValueInfo namedValueInfo in (namedValues ?? new INamedValueInfo[0]))
{
ExtensionNamedValues.Add(namedValueInfo.Name, namedValueInfo);
}
foreach (IFunctionInfo functionInfo in (functions ?? new IFunctionInfo[0]))
{
ExtensionFunctions.Add(functionInfo.Name, functionInfo);
}
AllowUnknownKeywords = allowUnknownKeywords;
Lexer = new LexicalAnalyzer(Expression, namedValues: ExtensionNamedValues.Keys, functions: ExtensionFunctions.Keys, allowKeywordHyphens);
}
private class NoOperationTraceWriter : ITraceWriter
{
public void Info(String message)
{
}
public void Verbose(String message)
{
}
}
}
}
}

View File

@@ -1,13 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
public sealed class ExpressionParserOptions
{
public Boolean AllowHyphens
{
get;
set;
}
}
}

View File

@@ -1,211 +0,0 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using GitHub.DistributedTask.Logging;
using Newtonsoft.Json.Linq;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
internal static class ExpressionUtil
{
internal static Object ConvertToCanonicalValue(
EvaluationOptions options,
Object val,
out ValueKind kind,
out Object raw,
out ResultMemory conversionResultMemory)
{
// Apply converter
if (options?.Converters?.Count > 0 &&
!Object.ReferenceEquals(val, null) &&
options.Converters.TryGetValue(val.GetType(), out Converter<Object, ConversionResult> convert))
{
raw = val;
var conversionResult = convert(val);
val = conversionResult.Result;
conversionResultMemory = conversionResult.ResultMemory;
}
else
{
raw = null;
conversionResultMemory = null;
}
if (Object.ReferenceEquals(val, null))
{
kind = ValueKind.Null;
return null;
}
else if (val is IString str)
{
kind = ValueKind.String;
return str.GetString();
}
else if (val is IBoolean booleanValue)
{
kind = ValueKind.Boolean;
return booleanValue.GetBoolean();
}
else if (val is INumber num)
{
kind = ValueKind.Number;
return num.GetNumber();
}
else if (val is JToken)
{
var jtoken = val as JToken;
switch (jtoken.Type)
{
case JTokenType.Array:
kind = ValueKind.Array;
return jtoken;
case JTokenType.Boolean:
kind = ValueKind.Boolean;
return jtoken.ToObject<Boolean>();
case JTokenType.Float:
case JTokenType.Integer:
kind = ValueKind.Number;
// todo: test the extents of the conversion
return jtoken.ToObject<Decimal>();
case JTokenType.Null:
kind = ValueKind.Null;
return null;
case JTokenType.Object:
kind = ValueKind.Object;
return jtoken;
case JTokenType.String:
kind = ValueKind.String;
return jtoken.ToObject<String>();
}
}
else if (val is String)
{
kind = ValueKind.String;
return val;
}
else if (val is Version)
{
kind = ValueKind.Version;
return val;
}
else if (!val.GetType().GetTypeInfo().IsClass)
{
if (val is Boolean)
{
kind = ValueKind.Boolean;
return val;
}
else if (val is DateTimeOffset)
{
kind = ValueKind.DateTime;
return val;
}
else if (val is DateTime dateTime)
{
kind = ValueKind.DateTime;
switch (dateTime.Kind)
{
// When Local: convert to preferred time zone
case DateTimeKind.Local:
var targetTimeZone = options?.TimeZone ?? TimeZoneInfo.Local;
var localDateTimeOffset = new DateTimeOffset(dateTime);
return TimeZoneInfo.ConvertTime(localDateTimeOffset, targetTimeZone);
// When Unspecified: assume preferred time zone
case DateTimeKind.Unspecified:
var timeZone = options?.TimeZone ?? TimeZoneInfo.Local;
var offset = timeZone.GetUtcOffset(dateTime);
return new DateTimeOffset(dateTime, offset);
// When UTC: keep UTC
case DateTimeKind.Utc:
return new DateTimeOffset(dateTime);
default:
throw new NotSupportedException($"Unexpected DateTimeKind '{dateTime.Kind}'"); // Should never happen
}
}
else if (val is Decimal || val is Byte || val is SByte || val is Int16 || val is UInt16 || val is Int32 || val is UInt32 || val is Int64 || val is UInt64 || val is Single || val is Double)
{
kind = ValueKind.Number;
return Convert.ToDecimal(val);
}
else if (val is Enum)
{
var strVal = String.Format(CultureInfo.InvariantCulture, "{0:G}", val);
if (Decimal.TryParse(strVal, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out Decimal decVal))
{
kind = ValueKind.Number;
return decVal;
}
kind = ValueKind.String;
return strVal;
}
}
kind = ValueKind.Object;
return val;
}
internal static String FormatValue(
ISecretMasker secretMasker,
EvaluationResult evaluationResult)
{
return FormatValue(secretMasker, evaluationResult.Value, evaluationResult.Kind);
}
internal static String FormatValue(
ISecretMasker secretMasker,
Object value,
ValueKind kind)
{
switch (kind)
{
case ValueKind.Boolean:
return ((Boolean)value).ToString();
case ValueKind.DateTime:
var strDateTime = "(DateTime)" + ((DateTimeOffset)value).ToString(ExpressionConstants.DateTimeFormat, CultureInfo.InvariantCulture);
return secretMasker != null ? secretMasker.MaskSecrets(strDateTime) : strDateTime;
case ValueKind.Number:
var strNumber = ((Decimal)value).ToString(ExpressionConstants.NumberFormat, CultureInfo.InvariantCulture);
return secretMasker != null ? secretMasker.MaskSecrets(strNumber) : strNumber;
case ValueKind.String:
// Mask secrets before string-escaping.
var strValue = secretMasker != null ? secretMasker.MaskSecrets(value as String) : value as String;
return $"'{StringEscape(strValue)}'";
case ValueKind.Version:
String strVersion = secretMasker != null ? secretMasker.MaskSecrets(value.ToString()) : value.ToString();
return $"v{strVersion}";
case ValueKind.Array:
case ValueKind.Null:
case ValueKind.Object:
return kind.ToString();
default: // Should never reach here.
throw new NotSupportedException($"Unable to convert to realized expression. Unexpected value kind: {kind}");
}
}
internal static Char SafeCharAt(
String str,
Int32 index)
{
if (str.Length > index)
{
return str[index];
}
return '\0';
}
internal static String StringEscape(String value)
{
return String.IsNullOrEmpty(value) ? String.Empty : value.Replace("'", "''");
}
}
}

View File

@@ -1,394 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class FormatNode : FunctionNode
{
protected sealed override Object EvaluateCore(EvaluationContext context)
{
var format = Parameters[0].EvaluateString(context);
var index = 0;
var result = new FormatResultBuilder(this, context, CreateMemoryCounter(context));
while (index < format.Length)
{
var lbrace = format.IndexOf('{', index);
var rbrace = format.IndexOf('}', index);
// Left brace
if (lbrace >= 0 && (rbrace < 0 || rbrace > lbrace))
{
// Escaped left brace
if (ExpressionUtil.SafeCharAt(format, lbrace + 1) == '{')
{
result.Append(format.Substring(index, lbrace - index + 1));
index = lbrace + 2;
}
// Left brace, number, optional format specifiers, right brace
else if (rbrace > lbrace + 1 &&
ReadArgIndex(format, lbrace + 1, out Byte argIndex, out Int32 endArgIndex) &&
ReadFormatSpecifiers(format, endArgIndex + 1, out String formatSpecifiers, out rbrace))
{
// Check parameter count
if (argIndex > Parameters.Count - 2)
{
throw new FormatException(ExpressionResources.InvalidFormatArgIndex(format));
}
// Append the portion before the left brace
if (lbrace > index)
{
result.Append(format.Substring(index, lbrace - index));
}
// Append the arg
result.Append(argIndex, formatSpecifiers);
index = rbrace + 1;
}
else
{
throw new FormatException(ExpressionResources.InvalidFormatString(format));
}
}
// Right brace
else if (rbrace >= 0)
{
// Escaped right brace
if (ExpressionUtil.SafeCharAt(format, rbrace + 1) == '}')
{
result.Append(format.Substring(index, rbrace - index + 1));
index = rbrace + 2;
}
else
{
throw new FormatException(ExpressionResources.InvalidFormatString(format));
}
}
// Last segment
else
{
result.Append(format.Substring(index));
break;
}
}
return result.ToString();
}
private Boolean ReadArgIndex(
String str,
Int32 startIndex,
out Byte result,
out Int32 endIndex)
{
// Count the number of digits
var length = 0;
while (Char.IsDigit(ExpressionUtil.SafeCharAt(str, startIndex + length)))
{
length++;
}
// Validate at least one digit
if (length < 1)
{
result = default;
endIndex = default;
return false;
}
// Parse the number
endIndex = startIndex + length - 1;
return Byte.TryParse(str.Substring(startIndex, length), NumberStyles.None, CultureInfo.InvariantCulture, out result);
}
private Boolean ReadFormatSpecifiers(
String str,
Int32 startIndex,
out String result,
out Int32 rbrace)
{
// No format specifiers
var c = ExpressionUtil.SafeCharAt(str, startIndex);
if (c == '}')
{
result = String.Empty;
rbrace = startIndex;
return true;
}
// Validate starts with ":"
if (c != ':')
{
result = default;
rbrace = default;
return false;
}
// Read the specifiers
var specifiers = new StringBuilder();
var index = startIndex + 1;
while (true)
{
// Validate not the end of the string
if (index >= str.Length)
{
result = default;
rbrace = default;
return false;
}
c = str[index];
// Not right-brace
if (c != '}')
{
specifiers.Append(c);
index++;
}
// Escaped right-brace
else if (ExpressionUtil.SafeCharAt(str, index + 1) == '}')
{
specifiers.Append('}');
index += 2;
}
// Closing right-brace
else
{
result = specifiers.ToString();
rbrace = index;
return true;
}
}
}
private sealed class FormatResultBuilder
{
internal FormatResultBuilder(
FormatNode node,
EvaluationContext context,
MemoryCounter counter)
{
m_node = node;
m_context = context;
m_counter = counter;
m_cache = new ArgValue[node.Parameters.Count - 1];
}
// Build the final string. This is when lazy segments are evaluated.
public override String ToString()
{
return String.Join(
String.Empty,
m_segments.Select(obj =>
{
if (obj is Lazy<String> lazy)
{
return lazy.Value;
}
else
{
return obj as String;
}
}));
}
// Append a static value
internal void Append(String value)
{
if (value?.Length > 0)
{
// Track memory
m_counter.Add(value);
// Append the segment
m_segments.Add(value);
}
}
// Append an argument
internal void Append(
Int32 argIndex,
String formatSpecifiers)
{
// Delay execution until the final ToString
m_segments.Add(new Lazy<String>(() =>
{
String result;
// Get the arg from the cache
var argValue = m_cache[argIndex];
// Evaluate the arg and cache the result
if (argValue == null)
{
// The evaluation result is required when format specifiers are used. Otherwise the string
// result is required. Go ahead and store both values. Since ConvertToString produces tracing,
// we need to run that now so the tracing appears in order in the log.
var evaluationResult = m_node.Parameters[argIndex + 1].Evaluate(m_context);
var stringResult = evaluationResult.ConvertToString(m_context);
argValue = new ArgValue(evaluationResult, stringResult);
m_cache[argIndex] = argValue;
}
// No format specifiers
if (String.IsNullOrEmpty(formatSpecifiers))
{
result = argValue.StringResult;
}
// DateTime
else if (argValue.EvaluationResult.Kind == ValueKind.DateTime)
{
result = FormatDateTime((DateTimeOffset)argValue.EvaluationResult.Value, formatSpecifiers);
}
// Invalid
else
{
throw new FormatException(ExpressionResources.InvalidFormatSpecifiers(formatSpecifiers, argValue.EvaluationResult.Kind));
}
// Track memory
if (!String.IsNullOrEmpty(result))
{
m_counter.Add(result);
}
return result;
}));
}
private String FormatDateTime(
DateTimeOffset dateTime,
String specifiers)
{
var result = new StringBuilder();
var i = 0;
while (true)
{
// Get the next specifier
var specifier = GetNextSpecifier(specifiers, ref i);
// Check end of string
if (String.IsNullOrEmpty(specifier))
{
break;
}
// Append the value
switch (specifier)
{
case "yyyy":
case "yy":
case "MM":
case "dd":
case "HH":
case "mm":
case "ss":
case "ff":
case "fff":
case "ffff":
case "fffff":
case "ffffff":
case "fffffff":
case "zzz":
result.Append(dateTime.ToString(specifier));
break;
// .Net requires a leading % for some specifiers
case "M":
case "d":
case "H":
case "m":
case "s":
case "f":
case "K":
result.Append(dateTime.ToString("%" + specifier));
break;
default:
// Escaped character
if (specifier[0] == '\\')
{
result.Append(specifier[1]);
}
else if (specifier[0] == ' ')
{
result.Append(specifier);
}
// Unexpected
else
{
throw new FormatException(ExpressionResources.InvalidFormatSpecifiers(specifiers, ValueKind.DateTime));
}
break;
}
}
return result.ToString();
}
private String GetNextSpecifier(
String specifiers,
ref Int32 index)
{
// End of string
if (index >= specifiers.Length)
{
return String.Empty;
}
// Get the first char
var startIndex = index;
var c = specifiers[index++];
// Escaped
if (c == '\\')
{
// End of string
if (index >= specifiers.Length)
{
throw new FormatException(ExpressionResources.InvalidFormatSpecifiers(specifiers, ValueKind.DateTime));
}
index++;
}
// Find consecutive matches
else
{
while (index < specifiers.Length && specifiers[index] == c)
{
index++;
}
}
return specifiers.Substring(startIndex, index - startIndex);
}
private readonly ArgValue[] m_cache;
private readonly EvaluationContext m_context;
private readonly MemoryCounter m_counter;
private readonly FormatNode m_node;
private readonly List<Object> m_segments = new List<Object>();
}
/// <summary>
/// Stores an EvaluateResult and the value converted to a String.
/// </summary>
private sealed class ArgValue
{
public ArgValue(
EvaluationResult evaluationResult,
String stringResult)
{
EvaluationResult = evaluationResult;
StringResult = stringResult;
}
public EvaluationResult EvaluationResult { get; }
public String StringResult { get; }
}
}
}

View File

@@ -1,26 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
public class FunctionInfo<T> : IFunctionInfo
where T : FunctionNode, new()
{
public FunctionInfo(String name, Int32 minParameters, Int32 maxParameters)
{
Name = name;
MinParameters = minParameters;
MaxParameters = maxParameters;
}
public String Name { get; }
public Int32 MinParameters { get; }
public Int32 MaxParameters { get; }
public FunctionNode CreateNode()
{
return new T();
}
}
}

View File

@@ -1,45 +0,0 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class FunctionNode : ContainerNode
{
/// <summary>
/// Generally this should not be overridden. True indicates the result of the node is traced as part of the "expanded"
/// (i.e. "realized") trace information. Otherwise the node expression is printed, and parameters to the node may or
/// may not be fully realized - depending on each respective parameter's trace-fully-realized setting.
///
/// The purpose is so the end user can understand how their expression expanded at run time. For example, consider
/// the expression: eq(variables.publish, 'true'). The runtime-expanded expression may be: eq('true', 'true')
/// </summary>
protected override Boolean TraceFullyRealized => true;
internal sealed override String ConvertToExpression()
{
return String.Format(
CultureInfo.InvariantCulture,
"{0}({1})",
Name,
String.Join(", ", Parameters.Select(x => x.ConvertToExpression())));
}
internal sealed override String ConvertToRealizedExpression(EvaluationContext context)
{
// Check if the result was stored
if (context.TryGetTraceResult(this, out String result))
{
return result;
}
return String.Format(
CultureInfo.InvariantCulture,
"{0}({1})",
Name,
String.Join(", ", Parameters.Select(x => x.ConvertToRealizedExpression(context))));
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class GreaterThanNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
return Parameters[0].Evaluate(context).CompareTo(context, Parameters[1].Evaluate(context)) > 0;
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class GreaterThanOrEqualNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
return Parameters[0].Evaluate(context).CompareTo(context, Parameters[1].Evaluate(context)) >= 0;
}
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IBoolean
{
Boolean GetBoolean();
}
}

View File

@@ -1,70 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using GitHub.DistributedTask.Logging;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IExpressionNode
{
/// <summary>
/// Evaluates the expression and attempts to cast or deserialize the result to the specified
/// type. The specified type can either be simple type or a JSON-serializable class. Allowed
/// simple types are: Boolean, String, Version, Byte, SByte, Int16, UInt16, Int32, UInt32,
/// Int64, UInt64, Single, Double, or Decimal. When a JSON-serializable class is specified, the
/// following rules are applied: If the type of the evaluation result object, is assignable to
/// the specified type, then the result will be cast and returned. If the evaluation result
/// object is a String, it will be deserialized as the specified type. If the evaluation result
/// object is null, null will be returned.
/// </summary>
/// <param name="trace">Optional trace writer</param>
/// <param name="secretMasker">Optional secret masker</param>
/// <param name="state">State object for custom evaluation function nodes and custom named-value nodes</param>
T Evaluate<T>(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state,
EvaluationOptions options = null);
/// <summary>
/// Evaluates the expression and returns the result.
/// </summary>
/// <param name="trace">Optional trace writer</param>
/// <param name="secretMasker">Optional secret masker</param>
/// <param name="state">State object for custom evaluation function nodes and custom named-value nodes</param>
Object Evaluate(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state,
EvaluationOptions options = null);
/// <summary>
/// Evaluates the expression and casts the result to a Boolean.
/// </summary>
/// <param name="trace">Optional trace writer</param>
/// <param name="secretMasker">Optional secret masker</param>
/// <param name="state">State object for custom evaluation function nodes and custom named-value nodes</param>
Boolean EvaluateBoolean(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state);
IEnumerable<T> GetParameters<T>() where T : IExpressionNode;
/// <summary>
/// Evaluates the expression and returns the result, wrapped in the SDK helper
/// for converting, comparing, and traversing objects.
/// </summary>
/// <param name="trace">Optional trace writer</param>
/// <param name="secretMasker">Optional secret masker</param>
/// <param name="state">State object for custom evaluation function nodes and custom named-value nodes</param>
/// <param name="options">Evaluation options</param>
[EditorBrowsable(EditorBrowsableState.Never)]
EvaluationResult EvaluateResult(
ITraceWriter trace,
ISecretMasker secretMasker,
Object state,
EvaluationOptions options);
}
}

View File

@@ -1,12 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
public interface IFunctionInfo
{
String Name { get; }
Int32 MinParameters { get; }
Int32 MaxParameters { get; }
FunctionNode CreateNode();
}
}

View File

@@ -1,10 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
public interface INamedValueInfo
{
String Name { get; }
NamedValueNode CreateNode();
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public interface INumber
{
Decimal GetNumber();
}
}

View File

@@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IReadOnlyArray : IReadOnlyList<Object>
{
}
}

View File

@@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IReadOnlyObject : IReadOnlyDictionary<String, Object>
{
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IString
{
String GetString();
}
}

View File

@@ -1,10 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
public interface ITraceWriter
{
void Info(String message);
void Verbose(String message);
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class InNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
EvaluationResult left = Parameters[0].Evaluate(context);
for (Int32 i = 1; i < Parameters.Count; i++)
{
EvaluationResult right = Parameters[i].Evaluate(context);
if (left.Equals(context, right))
{
return true;
}
}
return false;
}
}
}

View File

@@ -1,452 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using GitHub.Services.WebApi;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class IndexerNode : ContainerNode
{
internal IndexerNode()
{
Name = "indexer";
}
protected sealed override Boolean TraceFullyRealized => true;
internal sealed override String ConvertToExpression()
{
return String.Format(
CultureInfo.InvariantCulture,
"{0}[{1}]",
Parameters[0].ConvertToExpression(),
Parameters[1].ConvertToExpression());
}
internal sealed override String ConvertToRealizedExpression(EvaluationContext context)
{
// Check if the result was stored
if (context.TryGetTraceResult(this, out String result))
{
return result;
}
return ConvertToExpression();
}
protected sealed override Object EvaluateCore(
EvaluationContext context,
out ResultMemory resultMemory)
{
EvaluationResult firstParameter = Parameters[0].Evaluate(context);
if (context.Options.UseCollectionInterfaces)
{
if (!firstParameter.TryGetCollectionInterface(out Object collection))
{
// Even if we can't get the collection interface, return empty filtered array if it is a wildcard.
if (Parameters.Count > 2)
{
resultMemory = null;
return new FilteredArray();
}
resultMemory = null;
return null;
}
// Handle operating on a filtered array
if (collection is FilteredArray filteredArray)
{
return HandleFilteredArray(context, filteredArray, out resultMemory);
}
// Handle operating on an object
else if (collection is IReadOnlyObject obj)
{
return HandleObject(context, obj, out resultMemory);
}
// Handle operating on an array
else if (collection is IReadOnlyArray array)
{
return HandleArray(context, array, out resultMemory);
}
resultMemory = null;
return null;
}
else
{
Object result = null;
if (firstParameter.Kind == ValueKind.Array && firstParameter.Value is JArray)
{
var jarray = firstParameter.Value as JArray;
EvaluationResult index = Parameters[1].Evaluate(context);
if (index.Kind == ValueKind.Number)
{
Decimal d = (Decimal)index.Value;
if (d >= 0m && d < (Decimal)jarray.Count && d == Math.Floor(d))
{
result = jarray[(Int32)d];
}
}
else if (index.Kind == ValueKind.String && !String.IsNullOrEmpty(index.Value as String))
{
Decimal d;
if (index.TryConvertToNumber(context, out d))
{
if (d >= 0m && d < (Decimal)jarray.Count && d == Math.Floor(d))
{
result = jarray[(Int32)d];
}
}
}
}
else if (firstParameter.Kind == ValueKind.Object)
{
if (firstParameter.Value is JObject)
{
var jobject = firstParameter.Value as JObject;
EvaluationResult index = Parameters[1].Evaluate(context);
String s;
if (index.TryConvertToString(context, out s))
{
result = jobject[s];
}
}
else if (firstParameter.Value is IDictionary<String, String>)
{
var dictionary = firstParameter.Value as IDictionary<String, String>;
EvaluationResult index = Parameters[1].Evaluate(context);
if (index.TryConvertToString(context, out String key))
{
if (!dictionary.TryGetValue(key, out String resultString))
{
result = null;
}
else
{
result = resultString;
}
}
}
else if (firstParameter.Value is IDictionary<String, Object>)
{
var dictionary = firstParameter.Value as IDictionary<String, Object>;
EvaluationResult index = Parameters[1].Evaluate(context);
String s;
if (index.TryConvertToString(context, out s))
{
if (!dictionary.TryGetValue(s, out result))
{
result = null;
}
}
}
else if (firstParameter.Value is IReadOnlyDictionary<String, String>)
{
var dictionary = firstParameter.Value as IReadOnlyDictionary<String, String>;
EvaluationResult index = Parameters[1].Evaluate(context);
if (index.TryConvertToString(context, out String key))
{
if (!dictionary.TryGetValue(key, out String resultString))
{
result = null;
}
else
{
result = resultString;
}
}
}
else if (firstParameter.Value is IReadOnlyDictionary<String, Object>)
{
var dictionary = firstParameter.Value as IReadOnlyDictionary<String, Object>;
EvaluationResult index = Parameters[1].Evaluate(context);
String s;
if (index.TryConvertToString(context, out s))
{
if (!dictionary.TryGetValue(s, out result))
{
result = null;
}
}
}
else
{
var contract = s_serializer.Value.ContractResolver.ResolveContract(firstParameter.Value.GetType());
var objectContract = contract as JsonObjectContract;
if (objectContract != null)
{
EvaluationResult index = Parameters[1].Evaluate(context);
if (index.TryConvertToString(context, out String key))
{
var property = objectContract.Properties.GetClosestMatchProperty(key);
if (property != null)
{
result = objectContract.Properties[property.PropertyName].ValueProvider.GetValue(firstParameter.Value);
}
}
}
else
{
var dictionaryContract = contract as JsonDictionaryContract;
if (dictionaryContract != null && dictionaryContract.DictionaryKeyType == typeof(String))
{
EvaluationResult index = Parameters[1].Evaluate(context);
if (index.TryConvertToString(context, out String key))
{
var genericMethod = s_tryGetValueTemplate.Value.MakeGenericMethod(dictionaryContract.DictionaryValueType);
resultMemory = null;
return genericMethod.Invoke(null, new[] { firstParameter.Value, key });
}
}
}
}
}
resultMemory = null;
return result;
}
}
private Object HandleFilteredArray(
EvaluationContext context,
FilteredArray filteredArray,
out ResultMemory resultMemory)
{
EvaluationResult indexResult = Parameters[1].Evaluate(context);
var indexHelper = new IndexHelper(indexResult, context);
Boolean isFilter;
if (Parameters.Count > 2)
{
isFilter = true;
if (!String.Equals(indexHelper.StringIndex, ExpressionConstants.Wildcard.ToString(), StringComparison.Ordinal))
{
throw new InvalidOperationException($"Unexpected filter '{indexHelper.StringIndex}'");
}
}
else
{
isFilter = false;
}
var result = new FilteredArray();
var counter = new MemoryCounter(this, context.Options.MaxMemory);
foreach (var item in filteredArray)
{
// Leverage the expression SDK to traverse the object
var itemResult = EvaluationResult.CreateIntermediateResult(context, item, out _);
if (itemResult.TryGetCollectionInterface(out Object nestedCollection))
{
// Apply the index to a child object
if (nestedCollection is IReadOnlyObject nestedObject)
{
if (isFilter)
{
foreach (var val in nestedObject.Values)
{
result.Add(val);
counter.Add(IntPtr.Size);
}
}
else if (indexHelper.HasStringIndex)
{
if (nestedObject.TryGetValue(indexHelper.StringIndex, out Object nestedObjectValue))
{
result.Add(nestedObjectValue);
counter.Add(IntPtr.Size);
}
}
}
// Apply the index to a child array
else if (nestedCollection is IReadOnlyArray nestedArray)
{
if (isFilter)
{
foreach (var val in nestedArray)
{
result.Add(val);
counter.Add(IntPtr.Size);
}
}
else if (indexHelper.HasIntegerIndex &&
indexHelper.IntegerIndex < nestedArray.Count)
{
result.Add(nestedArray[indexHelper.IntegerIndex]);
counter.Add(IntPtr.Size);
}
}
}
}
resultMemory = new ResultMemory { Bytes = counter.CurrentBytes };
return result;
}
private Object HandleObject(
EvaluationContext context,
IReadOnlyObject obj,
out ResultMemory resultMemory)
{
EvaluationResult indexResult = Parameters[1].Evaluate(context);
var indexHelper = new IndexHelper(indexResult, context);
if (indexHelper.HasStringIndex)
{
Boolean isFilter = Parameters.Count > 2;
if (isFilter)
{
var filteredArray = new FilteredArray();
var counter = new MemoryCounter(this, context.Options.MaxMemory);
foreach (var val in obj.Values)
{
filteredArray.Add(val);
counter.Add(IntPtr.Size);
}
resultMemory = new ResultMemory { Bytes = counter.CurrentBytes };
return filteredArray;
}
else if (obj.TryGetValue(indexHelper.StringIndex, out Object result))
{
resultMemory = null;
return result;
}
}
resultMemory = null;
return null;
}
private Object HandleArray(
EvaluationContext context,
IReadOnlyArray array,
out ResultMemory resultMemory)
{
// Similar to as above but for an array
EvaluationResult indexResult = Parameters[1].Evaluate(context);
var indexHelper = new IndexHelper(indexResult, context);
// When we are operating on a array and it has three parameters, with the second being a string * and the third being a true boolean, it's a filtered array.
if (Parameters.Count > 2)
{
var filtered = new FilteredArray();
var counter = new MemoryCounter(this, context.Options.MaxMemory);
foreach (var x in array)
{
filtered.Add(x);
counter.Add(IntPtr.Size);
}
resultMemory = new ResultMemory { Bytes = counter.CurrentBytes };
return filtered;
}
if (indexHelper.HasIntegerIndex && indexHelper.IntegerIndex < array.Count)
{
resultMemory = null;
return array[indexHelper.IntegerIndex];
}
resultMemory = null;
return null;
}
// todo: remove with feature flag cleanup for "UseCollectionInterfaces"
private static Object TryGetValue<TValue>(
IDictionary<String, TValue> dictionary,
String key)
{
TValue value;
if (!dictionary.TryGetValue(key, out value))
{
return null;
}
return value;
}
private class FilteredArray : IReadOnlyArray
{
public FilteredArray()
{
m_list = new List<Object>();
}
public void Add(Object o)
{
m_list.Add(o);
}
public Int32 Count => m_list.Count;
public Object this[Int32 index] => m_list[index];
public IEnumerator<Object> GetEnumerator() => m_list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => m_list.GetEnumerator();
private readonly IList<Object> m_list;
}
private class IndexHelper
{
public Boolean HasIntegerIndex => m_integerIndex.Value.Item1;
public Int32 IntegerIndex => m_integerIndex.Value.Item2;
public Boolean HasStringIndex => m_stringIndex.Value.Item1;
public String StringIndex => m_stringIndex.Value.Item2;
public IndexHelper(
EvaluationResult result,
EvaluationContext context)
{
m_result = result;
m_context = context;
m_integerIndex = new Lazy<Tuple<Boolean, Int32>>(() =>
{
if (m_result.TryConvertToNumber(m_context, out Decimal decimalIndex) &&
decimalIndex >= 0m)
{
return new Tuple<Boolean, Int32>(true, (Int32)Math.Floor(decimalIndex));
}
return new Tuple<Boolean, Int32>(false, default(Int32));
});
m_stringIndex = new Lazy<Tuple<Boolean, String>>(() =>
{
if (m_result.TryConvertToString(m_context, out String stringIndex))
{
return new Tuple<Boolean, String>(true, stringIndex);
}
return new Tuple<Boolean, String>(false, null);
});
}
private Lazy<Tuple<Boolean, Int32>> m_integerIndex;
private Lazy<Tuple<Boolean, String>> m_stringIndex;
private readonly EvaluationResult m_result;
private readonly EvaluationContext m_context;
}
// todo: remove these properties with feature flag cleanup for "UseCollectionInterfaces"
private static Lazy<JsonSerializer> s_serializer = new Lazy<JsonSerializer>(() => JsonUtility.CreateJsonSerializer());
private static Lazy<MethodInfo> s_tryGetValueTemplate = new Lazy<MethodInfo>(() => typeof(IndexerNode).GetTypeInfo().GetMethod(nameof(TryGetValue), BindingFlags.NonPublic | BindingFlags.Static));
}
}

View File

@@ -1,69 +0,0 @@
using System;
using System.Text;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class JoinNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => true;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
var items = Parameters[1].Evaluate(context);
if (items.TryGetCollectionInterface(out var collection) && collection is IReadOnlyArray array)
{
if (array.Count > 0)
{
var result = new StringBuilder();
var memory = new MemoryCounter(this, context.Options.MaxMemory);
// Append the first item
var item = array[0];
var itemResult = EvaluationResult.CreateIntermediateResult(context, item, out _);
if (itemResult.TryConvertToString(context, out String itemString))
{
memory.Add(itemString);
result.Append(itemString);
}
// More items?
if (array.Count > 1)
{
var separator = Parameters[0].EvaluateString(context);
for (var i = 1; i < array.Count; i++)
{
// Append the separator
memory.Add(separator);
result.Append(separator);
// Append the next item
var nextItem = array[i];
var nextItemResult = EvaluationResult.CreateIntermediateResult(context, nextItem, out _);
if (nextItemResult.TryConvertToString(context, out String nextItemString))
{
memory.Add(nextItemString);
result.Append(nextItemString);
}
}
}
return result.ToString();
}
else
{
return String.Empty;
}
}
else if (items.TryConvertToString(context, out String str))
{
return str;
}
else
{
return String.Empty;
}
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class LessThanNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
return Parameters[0].Evaluate(context).CompareTo(context, Parameters[1].Evaluate(context)) < 0;
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class LessThanOrEqualNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
return Parameters[0].Evaluate(context).CompareTo(context, Parameters[1].Evaluate(context)) <= 0;
}
}
}

View File

@@ -1,293 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class LexicalAnalyzer
{
public LexicalAnalyzer(String expression, IEnumerable<String> namedValues, IEnumerable<String> functions, Boolean allowKeywordHyphens)
{
m_expression = expression;
m_extensionNamedValues = new HashSet<String>(namedValues ?? new String[0], StringComparer.OrdinalIgnoreCase);
m_extensionFunctions = new HashSet<String>(functions ?? new String[0], StringComparer.OrdinalIgnoreCase);
m_allowKeyHyphens = allowKeywordHyphens;
}
public Boolean TryGetNextToken(ref Token token)
{
// Skip whitespace.
while (m_index < m_expression.Length && Char.IsWhiteSpace(m_expression[m_index]))
{
m_index++;
}
// Test end of string.
if (m_index >= m_expression.Length)
{
token = null;
return false;
}
// Read the first character to determine the type of token.
Char c = m_expression[m_index];
switch (c)
{
case ExpressionConstants.StartIndex:
token = new Token(TokenKind.StartIndex, c, m_index++);
break;
case ExpressionConstants.StartParameter:
token = new Token(TokenKind.StartParameter, c, m_index++);
break;
case ExpressionConstants.EndIndex:
token = new Token(TokenKind.EndIndex, c, m_index++);
break;
case ExpressionConstants.EndParameter:
token = new Token(TokenKind.EndParameter, c, m_index++);
break;
case ExpressionConstants.Separator:
token = new Token(TokenKind.Separator, c, m_index++);
break;
case ExpressionConstants.Wildcard:
token = new Token(TokenKind.Wildcard, c, m_index++);
break;
case '\'':
token = ReadStringToken();
break;
default:
if (c == '.')
{
if (m_lastToken == null ||
m_lastToken.Kind == TokenKind.Separator ||
m_lastToken.Kind == TokenKind.StartIndex ||
m_lastToken.Kind == TokenKind.StartParameter)
{
token = ReadNumberOrVersionToken();
}
else
{
token = new Token(TokenKind.Dereference, c, m_index++);
}
}
else if (c == '-' || (c >= '0' && c <= '9'))
{
token = ReadNumberOrVersionToken();
}
else
{
token = ReadKeywordToken(m_allowKeyHyphens);
}
break;
}
m_lastToken = token;
return true;
}
public Boolean TryPeekNextToken(ref Token token)
{
// Record the state.
Int32 index = m_index;
Token lastToken = m_lastToken;
// Get next token.
Boolean result = TryGetNextToken(ref token);
// Restore the state.
m_index = index;
m_lastToken = lastToken;
return result;
}
private Token ReadNumberOrVersionToken()
{
Int32 startIndex = m_index;
Int32 periods = 0;
do
{
if (m_expression[m_index] == '.')
{
periods++;
}
m_index++;
}
while (m_index < m_expression.Length && (!TestWhitespaceOrPunctuation(m_expression[m_index]) || m_expression[m_index] == '.'));
Int32 length = m_index - startIndex;
String str = m_expression.Substring(startIndex, length);
if (periods >= 2)
{
Version version;
if (Version.TryParse(str, out version))
{
return new Token(TokenKind.Version, str, startIndex, version);
}
}
else
{
// Note, NumberStyles.AllowThousands cannot be allowed since comma has special meaning as a token separator.
Decimal d;
if (Decimal.TryParse(
str,
NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign,
CultureInfo.InvariantCulture,
out d))
{
return new Token(TokenKind.Number, str, startIndex, d);
}
}
return new Token(TokenKind.Unrecognized, str, startIndex);
}
private Token ReadKeywordToken(bool allowHyphen)
{
// Read to the end of the keyword.
Int32 startIndex = m_index;
m_index++; // Skip the first char. It is already known to be the start of the keyword.
while (m_index < m_expression.Length && !TestWhitespaceOrPunctuation(m_expression[m_index]))
{
m_index++;
}
// Test if valid keyword character sequence.
Int32 length = m_index - startIndex;
String str = m_expression.Substring(startIndex, length);
if (TestKeyword(str, allowHyphen))
{
// Test if follows property dereference operator.
if (m_lastToken != null && m_lastToken.Kind == TokenKind.Dereference)
{
return new Token(TokenKind.PropertyName, str, startIndex);
}
// Boolean
if (str.Equals(Boolean.TrueString, StringComparison.OrdinalIgnoreCase))
{
return new Token(TokenKind.Boolean, str, startIndex, true);
}
else if (str.Equals(Boolean.FalseString, StringComparison.OrdinalIgnoreCase))
{
return new Token(TokenKind.Boolean, str, startIndex, false);
}
// Well-known function
else if (ExpressionConstants.WellKnownFunctions.ContainsKey(str))
{
return new Token(TokenKind.WellKnownFunction, str, startIndex);
}
// Extension value
else if (m_extensionNamedValues.Contains(str))
{
return new Token(TokenKind.ExtensionNamedValue, str, startIndex);
}
// Extension function
else if (m_extensionFunctions.Contains(str))
{
return new Token(TokenKind.ExtensionFunction, str, startIndex);
}
}
// Unknown keyword
return new Token(TokenKind.UnknownKeyword, str, startIndex);
}
private Token ReadStringToken()
{
Int32 startIndex = m_index;
Char c;
Boolean closed = false;
var str = new StringBuilder();
m_index++; // Skip the leading single-quote.
while (m_index < m_expression.Length)
{
c = m_expression[m_index++];
if (c == '\'')
{
// End of string.
if (m_index >= m_expression.Length || m_expression[m_index] != '\'')
{
closed = true;
break;
}
// Escaped single quote.
m_index++;
}
str.Append(c);
}
Int32 length = m_index - startIndex;
String rawValue = m_expression.Substring(startIndex, length);
if (closed)
{
return new Token(TokenKind.String, rawValue, startIndex, str.ToString());
}
return new Token(TokenKind.Unrecognized, rawValue, startIndex);
}
private static Boolean TestKeyword(String str, bool allowHyphen)
{
if (String.IsNullOrEmpty(str))
{
return false;
}
Char first = str[0];
if ((first >= 'a' && first <= 'z') ||
(first >= 'A' && first <= 'Z') ||
first == '_')
{
for (Int32 i = 1 ; i < str.Length ; i++)
{
Char c = str[i];
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' || (allowHyphen && c == '-'))
{
// OK
}
else
{
return false;
}
}
return true;
}
else
{
return false;
}
}
private static Boolean TestWhitespaceOrPunctuation(Char c)
{
switch (c)
{
case ExpressionConstants.StartIndex:
case ExpressionConstants.StartParameter:
case ExpressionConstants.EndIndex:
case ExpressionConstants.EndParameter:
case ExpressionConstants.Separator:
case ExpressionConstants.Dereference:
return true;
default:
return char.IsWhiteSpace(c);
}
}
private readonly String m_expression; // Raw expression string.
private readonly HashSet<String> m_extensionFunctions;
private readonly HashSet<String> m_extensionNamedValues;
private Int32 m_index; // Index of raw condition string.
private Token m_lastToken;
private readonly Boolean m_allowKeyHyphens;
}
}

View File

@@ -1,47 +0,0 @@
using System;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class LiteralValueNode : ExpressionNode
{
public LiteralValueNode(Object val)
{
ValueKind kind;
// Note, it is OK to pass null EvaluationOptions here since the parser does not support
// localized values. For example, if parsing local date-times were supported, then we
// would need to know the account's time zone at parse time. This is an OK limitation,
// since we can defer this type of problem to runtime, for example by adding a parseDate function.
Value = ExpressionUtil.ConvertToCanonicalValue(null, val, out kind, out _, out _);
Kind = kind;
Name = kind.ToString();
}
public ValueKind Kind { get; }
public Object Value { get; }
// Prevent the value from being stored on the evaluation context.
// This avoids unneccessarily duplicating the value in memory.
protected sealed override Boolean TraceFullyRealized => false;
internal sealed override String ConvertToExpression()
{
return ExpressionUtil.FormatValue(null, Value, Kind);
}
internal sealed override String ConvertToRealizedExpression(EvaluationContext context)
{
return ExpressionUtil.FormatValue(null, Value, Kind);
}
protected sealed override Object EvaluateCore(EvaluationContext context)
{
return Value;
}
}
}

View File

@@ -1,166 +0,0 @@
using System;
using System.ComponentModel;
using Newtonsoft.Json.Linq;
namespace GitHub.DistributedTask.Expressions
{
/// <summary>
/// Helper class for ExpressionNode authors. This class helps calculate memory overhead for a result object.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class MemoryCounter
{
internal MemoryCounter(
ExpressionNode node,
Int32? maxBytes)
{
m_node = node;
m_maxBytes = (maxBytes ?? 0) > 0 ? maxBytes.Value : Int32.MaxValue;
}
public Int32 CurrentBytes => m_currentBytes;
public void Add(Int32 amount)
{
if (!TryAdd(amount))
{
throw new InvalidOperationException(ExpressionResources.ExceededAllowedMemory(m_node?.ConvertToExpression()));
}
}
public void Add(String value)
{
Add(CalculateSize(value));
}
public void Add(
JToken value,
Boolean traverse)
{
// This measurement doesn't have to be perfect
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
if (value is null)
{
Add(MinObjectSize);
}
if (!traverse)
{
switch (value.Type)
{
case JTokenType.Bytes:
case JTokenType.String:
case JTokenType.Uri:
Add(value.ToObject<String>());
return;
case JTokenType.Property:
var property = value as JProperty;
Add(property.Name);
return;
default:
Add(MinObjectSize);
return;
}
}
do
{
// Descend as much as possible
while (true)
{
// Add bytes
Add(value, false);
// Descend
if (value.HasValues)
{
value = value.First;
}
// No more descendants
else
{
break;
}
}
// Next sibling or ancestor sibling
do
{
var sibling = value.Next;
// Sibling found
if (sibling != null)
{
value = sibling;
break;
}
// Ascend
value = value.Parent;
} while (value != null);
} while (value != null);
}
public void AddMinObjectSize()
{
Add(MinObjectSize);
}
public void Remove(String value)
{
m_currentBytes -= CalculateSize(value);
}
public static Int32 CalculateSize(String value)
{
// This measurement doesn't have to be perfect.
// https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/
Int32 bytes;
checked
{
bytes = StringBaseOverhead + ((value?.Length ?? 0) * 2);
}
return bytes;
}
internal Boolean TryAdd(Int32 amount)
{
try
{
checked
{
amount += m_currentBytes;
}
if (amount > m_maxBytes)
{
return false;
}
m_currentBytes = amount;
return true;
}
catch (OverflowException)
{
return false;
}
}
internal Boolean TryAdd(String value)
{
return TryAdd(CalculateSize(value));
}
internal const Int32 MinObjectSize = 24;
internal const Int32 StringBaseOverhead = 26;
private readonly Int32 m_maxBytes;
private readonly ExpressionNode m_node;
private Int32 m_currentBytes;
}
}

View File

@@ -1,20 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
public class NamedValueInfo<T> : INamedValueInfo
where T : NamedValueNode, new()
{
public NamedValueInfo(String name)
{
Name = name;
}
public String Name { get; }
public NamedValueNode CreateNode()
{
return new T();
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class NamedValueNode : ExpressionNode
{
internal sealed override string ConvertToExpression() => Name;
protected sealed override Boolean TraceFullyRealized => true;
internal sealed override String ConvertToRealizedExpression(EvaluationContext context)
{
// Check if the result was stored
if (context.TryGetTraceResult(this, out String result))
{
return result;
}
return Name;
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class NotEqualNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
return !Parameters[0].Evaluate(context).Equals(context, Parameters[1].Evaluate(context));
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class NotInNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
EvaluationResult left = Parameters[0].Evaluate(context);
for (Int32 i = 1; i < Parameters.Count; i++)
{
EvaluationResult right = Parameters[i].Evaluate(context);
if (left.Equals(context, right))
{
return false;
}
}
return true;
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class NotNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
return !Parameters[0].EvaluateBoolean(context);
}
}
}

View File

@@ -1,22 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class OrNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
foreach (ExpressionNode parameter in Parameters)
{
if (parameter.EvaluateBoolean(context))
{
return true;
}
}
return false;
}
}
}

View File

@@ -1,65 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
public sealed class ParseException : ExpressionException
{
internal ParseException(ParseExceptionKind kind, Token token, String expression)
: base(secretMasker: null, message: String.Empty)
{
Expression = expression;
Kind = kind;
RawToken = token?.RawValue;
TokenIndex = token?.Index ?? 0;
String description;
switch (kind)
{
case ParseExceptionKind.ExceededMaxDepth:
description = ExpressionResources.ExceededMaxExpressionDepth(ExpressionConstants.MaxDepth);
break;
case ParseExceptionKind.ExceededMaxLength:
description = ExpressionResources.ExceededMaxExpressionLength(ExpressionConstants.MaxLength);
break;
case ParseExceptionKind.ExpectedPropertyName:
description = ExpressionResources.ExpectedPropertyName();
break;
case ParseExceptionKind.ExpectedStartParameter:
description = ExpressionResources.ExpectedStartParameter();
break;
case ParseExceptionKind.UnclosedFunction:
description = ExpressionResources.UnclosedFunction();
break;
case ParseExceptionKind.UnclosedIndexer:
description = ExpressionResources.UnclosedIndexer();
break;
case ParseExceptionKind.UnexpectedSymbol:
description = ExpressionResources.UnexpectedSymbol();
break;
case ParseExceptionKind.UnrecognizedValue:
description = ExpressionResources.UnrecognizedValue();
break;
default: // Should never reach here.
throw new Exception($"Unexpected parse exception kind '{kind}'.");
}
if (token == null)
{
Message = ExpressionResources.ParseErrorWithFwlink(description);
}
else
{
Message = ExpressionResources.ParseErrorWithTokenInfo(description, RawToken, TokenIndex + 1, Expression);
}
}
internal String Expression { get; }
internal ParseExceptionKind Kind { get; }
internal String RawToken { get; }
internal Int32 TokenIndex { get; }
public sealed override String Message { get; }
}
}

View File

@@ -1,14 +0,0 @@
namespace GitHub.DistributedTask.Expressions
{
internal enum ParseExceptionKind
{
ExceededMaxDepth,
ExceededMaxLength,
ExpectedPropertyName,
ExpectedStartParameter,
UnclosedFunction,
UnclosedIndexer,
UnexpectedSymbol,
UnrecognizedValue,
}
}

View File

@@ -1,58 +0,0 @@
using System;
using System.ComponentModel;
namespace GitHub.DistributedTask.Expressions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public class ResultMemory
{
/// <summary>
/// Only set a non-null value when both of the following conditions are met:
/// 1) The result is a complex object. In other words, the result is
/// not a simple type: string, boolean, number, version, datetime, or null.
/// 2) The result is a newly created object.
///
/// <para>
/// For example, consider a function jsonParse() which takes a string parameter,
/// and returns a JToken object. The JToken object is newly created and a rough
/// measurement should be returned for the number of bytes it consumes in memory.
/// </para>
///
/// <para>
/// For another example, consider a function which returns a sub-object from a
/// complex parameter value. From the perspective of an individual function,
/// the size of the complex parameter value is unknown. In this situation, set the
/// value to IntPtr.Size.
/// </para>
///
/// <para>
/// When you are unsure, set the value to null. Null indicates the overhead of a
/// new pointer should be accounted for.
/// </para>
/// </summary>
public Int32? Bytes { get; set; }
/// <summary>
/// Indicates whether <c ref="Bytes" /> represents the total size of the result.
/// True indicates the accounting-overhead of downstream parameters can be discarded.
///
/// For <c ref="EvaluationOptions.Converters" />, this value is currently ignored.
///
/// <para>
/// For example, consider a funciton jsonParse() which takes a string paramter,
/// and returns a JToken object. The JToken object is newly created and a rough
/// measurement should be returned for the amount of bytes it consumes in memory.
/// Set the <c ref="IsTotal" /> to true, since new object contains no references
/// to previously allocated memory.
/// </para>
///
/// <para>
/// For another example, consider a function which wraps a complex parameter result.
/// <c ref="Bytes" /> should be set to the amount of newly allocated memory.
/// However since the object references previously allocated memory, set <c ref="IsTotal" />
/// to false.
/// </para>
/// </summary>
public Boolean IsTotal { get; set; }
}
}

View File

@@ -1,16 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class StartsWithNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
String left = Parameters[0].EvaluateString(context) ?? String.Empty;
String right = Parameters[1].EvaluateString(context) ?? String.Empty;
return left.StartsWith(right, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -1,28 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class Token
{
public Token(TokenKind kind, Char rawValue, Int32 index, Object parsedValue = null)
: this(kind, rawValue.ToString(), index, parsedValue)
{
}
public Token(TokenKind kind, String rawValue, Int32 index, Object parsedValue = null)
{
Kind = kind;
RawValue = rawValue;
Index = index;
ParsedValue = parsedValue;
}
public TokenKind Kind { get; }
public String RawValue { get; }
public Int32 Index { get; }
public Object ParsedValue { get; }
}
}

Some files were not shown because too many files have changed in this diff Show More