From 3fe035bcfac000933fecac47d7699223b8ec65b2 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 13:50:15 +0100 Subject: [PATCH 01/11] Detach Google.Apis.Auth from Google.Apis This as a *lot* of copies of dependencies - later commits will try to reduce this set. --- .../ApplicationContext.cs | 53 ++ .../ExistingDependencies/BackOffHandler.cs | 186 +++++ .../ExistingDependencies/BaseClientService.cs | 373 +++++++++ .../ClientServiceRequest.cs | 440 +++++++++++ .../ConfigurableHttpClient.cs | 50 ++ .../ConfigurableMessageHandler.cs | 713 ++++++++++++++++++ .../ExistingDependencies/Delegates.cs | 12 + .../ExistingDependencies/ETagAction.cs | 46 ++ .../ExponentialBackOff.cs | 99 +++ .../ExponentialBackOffInitializer.cs | 79 ++ .../ExistingDependencies/Features.cs | 32 + .../ExistingDependencies/FileDataStore.cs | 183 +++++ .../GoogleApiException.cs | 100 +++ .../ExistingDependencies/HttpClientFactory.cs | 167 ++++ .../ExistingDependencies/HttpConsts.cs | 42 ++ .../ExistingDependencies/HttpExtenstions.cs | 51 ++ .../HttpRequestMessageExtenstions.cs | 97 +++ .../HttpResponseMessageExtensions.cs | 82 ++ .../ExistingDependencies/IBackOff.cs | 33 + .../ExistingDependencies/IClientService.cs | 92 +++ .../IClientServiceRequest.cs | 80 ++ .../ExistingDependencies/IClock.cs | 56 ++ .../IConfigurableHttpClientInitializer.cs | 33 + .../ExistingDependencies/IDataStore.cs | 53 ++ .../IDirectResponseSchema.cs | 36 + .../IHttpClientFactory.cs | 51 ++ .../IHttpExceptionHandler.cs | 63 ++ .../IHttpExecuteInterceptor.cs | 36 + .../IHttpUnsuccessfulResponseHandler.cs | 65 ++ .../ExistingDependencies/IJsonSerializer.cs | 23 + .../ExistingDependencies/ILogger.cs | 62 ++ .../ExistingDependencies/IParameter.cs | 39 + .../ExistingDependencies/ISerializer.cs | 43 ++ .../ExistingDependencies/JsonExplicitNull.cs | 100 +++ .../JsonExplicitNullAttribute.cs | 26 + .../MaxUrlLengthInterceptor.cs | 72 ++ .../NewtonsoftJsonSerializer.cs | 231 ++++++ .../ExistingDependencies/NullLogger.cs | 59 ++ .../ParameterCollection.cs | 166 ++++ .../ExistingDependencies/ParameterUtils.cs | 177 +++++ .../ParameterValidator.cs | 79 ++ .../ExistingDependencies/RequestBuilder.cs | 335 ++++++++ .../ExistingDependencies/RequestError.cs | 95 +++ .../RequestParameterAttribute.cs | 82 ++ .../ExistingDependencies/SingleError.cs | 60 ++ .../ExistingDependencies/StandardResponse.cs | 37 + .../StreamInterceptionHandler.cs | 122 +++ .../StringValueAttribute.cs | 36 + .../ExistingDependencies/TaskExtensions.cs | 51 ++ .../TwoWayDelegatingHandler.cs | 67 ++ .../ExistingDependencies/UriPatcher.cs | 122 +++ .../ExistingDependencies/Utilities.cs | 208 +++++ .../VersionHeaderBuilder.cs | 202 +++++ .../VisibleForTestOnly.cs | 29 + .../Google.Apis.Auth/Google.Apis.Auth.csproj | 12 +- Src/Support/GoogleApisClient.sln | 4 +- 56 files changed, 5937 insertions(+), 5 deletions(-) create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ApplicationContext.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/BackOffHandler.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/BaseClientService.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ClientServiceRequest.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ConfigurableHttpClient.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ConfigurableMessageHandler.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/Delegates.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ETagAction.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOff.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOffInitializer.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/Features.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/FileDataStore.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/GoogleApiException.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpClientFactory.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpConsts.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpExtenstions.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpRequestMessageExtenstions.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpResponseMessageExtensions.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IBackOff.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IClientService.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IClientServiceRequest.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IClock.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IConfigurableHttpClientInitializer.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IDataStore.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IDirectResponseSchema.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpClientFactory.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpExceptionHandler.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpExecuteInterceptor.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpUnsuccessfulResponseHandler.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IJsonSerializer.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ILogger.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IParameter.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ISerializer.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNull.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNullAttribute.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/MaxUrlLengthInterceptor.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/NewtonsoftJsonSerializer.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/NullLogger.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterCollection.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterUtils.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterValidator.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/RequestBuilder.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/RequestError.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/RequestParameterAttribute.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/SingleError.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/StandardResponse.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/StreamInterceptionHandler.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/StringValueAttribute.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/TaskExtensions.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/TwoWayDelegatingHandler.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/UriPatcher.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/VersionHeaderBuilder.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/VisibleForTestOnly.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ApplicationContext.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ApplicationContext.cs new file mode 100644 index 00000000000..d316446bae7 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ApplicationContext.cs @@ -0,0 +1,53 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Google.Apis.Logging; +using System; + +namespace Google +{ + /// Defines the context in which this library runs. It allows setting up custom loggers. + public static class ApplicationContext + { + private static ILogger logger; + + // For testing + internal static void Reset() => logger = null; + + /// Returns the logger used within this application context. + /// It creates a if no logger was registered previously + public static ILogger Logger + { + get + { + // Register the default null-logger if no other one was set. + return logger ?? (logger = new NullLogger()); + } + } + + /// Registers a logger with this application context. + /// Thrown if a logger was already registered. + public static void RegisterLogger(ILogger loggerToRegister) + { + // TODO(peleyal): Reconsider why the library should contain only one logger. Also consider using Tracing! + if (logger != null && !(logger is NullLogger)) + { + throw new InvalidOperationException("A logger was already registered with this context."); + } + logger = loggerToRegister; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/BackOffHandler.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/BackOffHandler.cs new file mode 100644 index 00000000000..dc12abfa7dd --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/BackOffHandler.cs @@ -0,0 +1,186 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +using Google.Apis.Logging; +using Google.Apis.Util; + +namespace Google.Apis.Http +{ + /// + /// A thread-safe back-off handler which handles an abnormal HTTP response or an exception with + /// . + /// + public class BackOffHandler : IHttpUnsuccessfulResponseHandler, IHttpExceptionHandler + { + private static readonly ILogger Logger = ApplicationContext.Logger.ForType(); + + /// An initializer class to initialize a back-off handler. + public class Initializer + { + /// Gets the back-off policy used by this back-off handler. + public IBackOff BackOff { get; private set; } + + /// + /// Gets or sets the maximum time span to wait. If the back-off instance returns a greater time span than + /// this value, this handler returns false to both HandleExceptionAsync and + /// HandleResponseAsync. Default value is 16 seconds per a retry request. + /// + public TimeSpan MaxTimeSpan { get; set; } + + /// + /// Gets or sets a delegate function which indicates whether this back-off handler should handle an + /// abnormal HTTP response. The default is . + /// + public Func HandleUnsuccessfulResponseFunc { get; set; } + + /// + /// Gets or sets a delegate function which indicates whether this back-off handler should handle an + /// exception. The default is . + /// + public Func HandleExceptionFunc { get; set; } + + /// Default function which handles server errors (503). + public static readonly Func DefaultHandleUnsuccessfulResponseFunc = + (r) => (int)r.StatusCode == 503; + + /// + /// Default function which handles exception which aren't + /// or + /// . Those exceptions represent a task or an operation + /// which was canceled and shouldn't be retried. + /// + public static readonly Func DefaultHandleExceptionFunc = + (ex) => !(ex is TaskCanceledException || ex is OperationCanceledException); + + /// Constructs a new initializer by the given back-off. + public Initializer(IBackOff backOff) + { + BackOff = backOff; + HandleExceptionFunc = DefaultHandleExceptionFunc; + HandleUnsuccessfulResponseFunc = DefaultHandleUnsuccessfulResponseFunc; + MaxTimeSpan = TimeSpan.FromSeconds(16); + } + } + + /// Gets the back-off policy used by this back-off handler. + public IBackOff BackOff { get; private set; } + + /// + /// Gets the maximum time span to wait. If the back-off instance returns a greater time span, the handle method + /// returns false. Default value is 16 seconds per a retry request. + /// + public TimeSpan MaxTimeSpan { get; private set; } + + /// + /// Gets a delegate function which indicates whether this back-off handler should handle an abnormal HTTP + /// response. The default is . + /// + public Func HandleUnsuccessfulResponseFunc { get; private set; } + + /// + /// Gets a delegate function which indicates whether this back-off handler should handle an exception. The + /// default is . + /// + public Func HandleExceptionFunc { get; private set; } + + /// Constructs a new back-off handler with the given back-off. + /// The back-off policy. + public BackOffHandler(IBackOff backOff) + : this(new Initializer(backOff)) + { + } + + /// Constructs a new back-off handler with the given initializer. + public BackOffHandler(Initializer initializer) + { + BackOff = initializer.BackOff; + MaxTimeSpan = initializer.MaxTimeSpan; + HandleExceptionFunc = initializer.HandleExceptionFunc; + HandleUnsuccessfulResponseFunc = initializer.HandleUnsuccessfulResponseFunc; + } + + #region IHttpUnsuccessfulResponseHandler + + /// + public virtual async Task HandleResponseAsync(HandleUnsuccessfulResponseArgs args) + { + // if the func returns true try to handle this current failed try + if (HandleUnsuccessfulResponseFunc != null && HandleUnsuccessfulResponseFunc(args.Response)) + { + return await HandleAsync(args.SupportsRetry, args.CurrentFailedTry, args.CancellationToken) + .ConfigureAwait(false); + } + return false; + } + + #endregion + + #region IHttpExceptionHandler + + /// + public virtual async Task HandleExceptionAsync(HandleExceptionArgs args) + { + // if the func returns true try to handle this current failed try + if (HandleExceptionFunc != null && HandleExceptionFunc(args.Exception)) + { + return await HandleAsync(args.SupportsRetry, args.CurrentFailedTry, args.CancellationToken) + .ConfigureAwait(false); + } + return false; + } + + #endregion + + /// + /// Handles back-off. In case the request doesn't support retry or the back-off time span is greater than the + /// maximum time span allowed for a request, the handler returns false. Otherwise the current thread + /// will block for x milliseconds (x is defined by the instance), and this handler + /// returns true. + /// + private async Task HandleAsync(bool supportsRetry, int currentFailedTry, + CancellationToken cancellationToken) + { + if (!supportsRetry || BackOff.MaxNumOfRetries < currentFailedTry) + { + return false; + } + + TimeSpan ts = BackOff.GetNextBackOff(currentFailedTry); + if (ts > MaxTimeSpan || ts < TimeSpan.Zero) + { + return false; + } + + await Wait(ts, cancellationToken).ConfigureAwait(false); + Logger.Debug("Back-Off handled the error. Waited {0}ms before next retry...", ts.TotalMilliseconds); + return true; + } + + /// Waits the given time span. Overriding this method is recommended for mocking purposes. + /// TimeSpan to wait (and block the current thread). + /// The cancellation token in case the user wants to cancel the operation in + /// the middle. + protected virtual async Task Wait(TimeSpan ts, CancellationToken cancellationToken) + { + await Task.Delay(ts, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/BaseClientService.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/BaseClientService.cs new file mode 100644 index 00000000000..fd953204f96 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/BaseClientService.cs @@ -0,0 +1,373 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Google.Apis.Discovery; +using Google.Apis.Http; +using Google.Apis.Json; +using Google.Apis.Logging; +using Google.Apis.Requests; +using Google.Apis.Responses; +using Google.Apis.Testing; +using Google.Apis.Util; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Google.Apis.Services +{ + /// + /// A base class for a client service which provides common mechanism for all services, like + /// serialization and GZip support. It should be safe to use a single service instance to make server requests + /// concurrently from multiple threads. + /// This class adds a special to the + /// execute interceptor list, which uses the given + /// Authenticator. It calls to its applying authentication method, and injects the "Authorization" header in the + /// request. + /// If the given Authenticator implements , this + /// class adds the Authenticator to the 's unsuccessful + /// response handler list. + /// + public abstract class BaseClientService : IClientService + { + /// The class logger. + private static readonly ILogger Logger = ApplicationContext.Logger.ForType(); + + /// The default maximum allowed length of a URL string for GET requests. + [VisibleForTestOnly] + public const uint DefaultMaxUrlLength = 2048; + + #region Initializer + + /// An initializer class for the client service. + public class Initializer + { + /// + /// Gets or sets the factory for creating instance. If this + /// property is not set the service uses a new instance. + /// + public IHttpClientFactory HttpClientFactory { get; set; } + + /// + /// Gets or sets a HTTP client initializer which is able to customize properties on + /// and + /// . + /// + public IConfigurableHttpClientInitializer HttpClientInitializer { get; set; } + + /// + /// Get or sets the exponential back-off policy used by the service. Default value is + /// UnsuccessfulResponse503, which means that exponential back-off is used on 503 abnormal HTTP + /// response. + /// If the value is set to None, no exponential back-off policy is used, and it's up to the user to + /// configure the in an + /// to set a specific back-off + /// implementation (using ). + /// + public ExponentialBackOffPolicy DefaultExponentialBackOffPolicy { get; set; } + + /// Gets or sets whether this service supports GZip. Default value is true. + public bool GZipEnabled { get; set; } + + /// + /// Gets or sets the serializer. Default value is . + /// + public ISerializer Serializer { get; set; } + + /// Gets or sets the API Key. Default value is null. + public string ApiKey { get; set; } + + /// + /// Gets or sets Application name to be used in the User-Agent header. Default value is null. + /// + public string ApplicationName { get; set; } + + /// + /// Maximum allowed length of a URL string for GET requests. Default value is 2048. If the value is + /// set to 0, requests will never be modified due to URL string length. + /// + public uint MaxUrlLength { get; set; } + + /// + /// Gets or sets the base URI to use for the service. If the value is null, + /// the default base URI for the service is used. + /// + public string BaseUri { get; set; } + + /// + /// Builder for the x-goog-api-client header, collecting version information. + /// Services automatically add the API library version to this. + /// Most users will never need to configure this, but higher level abstraction Google libraries + /// may add their own version here. + /// + public VersionHeaderBuilder VersionHeaderBuilder { get; } + + /// + /// Determines whether request parameters are validated (client-side) by default. + /// Defaults to true. This can be overridden on a per-request basis using . + /// + public bool ValidateParameters { get; set; } = true; + + /// Constructs a new initializer with default values. + public Initializer() + { + GZipEnabled = true; + Serializer = new NewtonsoftJsonSerializer(); + DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.UnsuccessfulResponse503; + MaxUrlLength = DefaultMaxUrlLength; + VersionHeaderBuilder = new VersionHeaderBuilder() + .AppendDotNetEnvironment(); + } + + // HttpRequestMessage.Headers fails if any of these characters are included in a User-Agent header. + private const string InvalidApplicationNameCharacters = "\"(),:;<=>?@[\\]{}"; + + internal void Validate() + { + if (ApplicationName != null && ApplicationName.Any(c => InvalidApplicationNameCharacters.Contains(c))) + { + throw new ArgumentException("Invalid Application name", nameof(ApplicationName)); + } + } + } + + #endregion + + /// Constructs a new base client with the specified initializer. + protected BaseClientService(Initializer initializer) + { + initializer.Validate(); + // Note that GetType() will get the *actual* type, which will be the service type in the API-specific library. + // That's the version we want to append. + // It's important that we clone the VersionHeaderBuilder, so we don't modify the initializer - otherwise + // a single initializer can't be used for multiple services (which can be useful). + string versionHeader = initializer.VersionHeaderBuilder.Clone() + .AppendAssemblyVersion("gdcl", GetType()) + .ToString(); + // Set the right properties by the initializer's properties. + GZipEnabled = initializer.GZipEnabled; + Serializer = initializer.Serializer; + ApiKey = initializer.ApiKey; + ApplicationName = initializer.ApplicationName; + BaseUriOverride = initializer.BaseUri; + ValidateParameters = initializer.ValidateParameters; + if (ApplicationName == null) + { + Logger.Warning("Application name is not set. Please set Initializer.ApplicationName property"); + } + HttpClientInitializer = initializer.HttpClientInitializer; + + // Create a HTTP client for this service. + HttpClient = CreateHttpClient(initializer, versionHeader); + } + + /// + /// Determines whether or not request parameters should be validated client-side. + /// This may be overridden on a per-request basis. + /// + internal bool ValidateParameters { get; } + + /// + /// The BaseUri provided in the initializer, which may be null. + /// + protected string BaseUriOverride { get; } + + /// Returns true if this service contains the specified feature. + private bool HasFeature(Features feature) + { + return Features.Contains(Utilities.GetEnumStringValue(feature)); + } + + private ConfigurableHttpClient CreateHttpClient(Initializer initializer, string versionHeader) + { + // If factory wasn't set use the default HTTP client factory. + var factory = initializer.HttpClientFactory ?? new HttpClientFactory(); + var args = new CreateHttpClientArgs + { + GZipEnabled = GZipEnabled, + ApplicationName = ApplicationName, + GoogleApiClientHeader = versionHeader + }; + + // Add the user's input initializer. + if (HttpClientInitializer != null) + { + args.Initializers.Add(HttpClientInitializer); + } + + // Add exponential back-off initializer if necessary. + if (initializer.DefaultExponentialBackOffPolicy != ExponentialBackOffPolicy.None) + { + args.Initializers.Add(new ExponentialBackOffInitializer(initializer.DefaultExponentialBackOffPolicy, + CreateBackOffHandler)); + } + + var httpClient = factory.CreateHttpClient(args); + if (initializer.MaxUrlLength > 0) + { + httpClient.MessageHandler.AddExecuteInterceptor(new MaxUrlLengthInterceptor(initializer.MaxUrlLength)); + } + return httpClient; + } + + /// + /// Creates the back-off handler with . + /// Overrides this method to change the default behavior of back-off handler (e.g. you can change the maximum + /// waited request's time span, or create a back-off handler with you own implementation of + /// ). + /// + protected virtual BackOffHandler CreateBackOffHandler() + { + // TODO(peleyal): consider return here interface and not the concrete class + return new BackOffHandler(new ExponentialBackOff()); + } + + #region IClientService Members + + /// + public ConfigurableHttpClient HttpClient { get; private set; } + + /// + public IConfigurableHttpClientInitializer HttpClientInitializer { get; private set; } + + /// + public bool GZipEnabled { get; private set; } + + /// + public string ApiKey { get; private set; } + + /// + public string ApplicationName { get; private set; } + + /// + public void SetRequestSerailizedContent(HttpRequestMessage request, object body) + { + request.SetRequestSerailizedContent(this, body, GZipEnabled); + } + + #region Serialization + + /// + public ISerializer Serializer { get; private set; } + + /// + public virtual string SerializeObject(object obj) => Serializer.Serialize(obj); + + /// + public virtual async Task DeserializeResponse(HttpResponseMessage response) + { + var text = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + // If a string is request, don't parse the response. + if (Type.Equals(typeof(T), typeof(string))) + { + return (T)(object)text; + } + + // Check if there was an error returned. The error node is returned in both paths + // Deserialize the stream based upon the format of the stream. + if (HasFeature(Discovery.Features.LegacyDataResponse)) + { + // Legacy path (deprecated!) + StandardResponse sr = null; + try + { + sr = Serializer.Deserialize>(text); + } + catch (Exception ex) + { + throw new GoogleApiException(Name, + $"Failed to parse response from server as {Serializer.Format ?? "unknown format"} [" + text + "]", ex); + } + + if (sr.Error != null) + { + throw new GoogleApiException(Name, "Server error - " + sr.Error) + { + Error = sr.Error + }; + } + + if (sr.Data == null) + { + throw new GoogleApiException(Name, "The response could not be deserialized."); + } + return sr.Data; + } + + // New path: Deserialize the object directly. + T result = default(T); + try + { + result = Serializer.Deserialize(text); + } + catch (Exception ex) + { + throw new GoogleApiException(Name, $"Failed to parse response from server as {Serializer.Format ?? "unknown format"} [" + text + "]", ex); + } + + // TODO(peleyal): is this the right place to check ETag? it isn't part of deserialization! + // If this schema/object provides an error container, check it. + var eTag = response.Headers.ETag != null ? response.Headers.ETag.Tag : null; + if (result is IDirectResponseSchema && eTag != null) + { + (result as IDirectResponseSchema).ETag = eTag; + } + return result; + } + + /// + public virtual Task DeserializeError(HttpResponseMessage response) => + response.DeserializeErrorAsync(Name, Serializer); + + #endregion + + #region Abstract Members + + /// + public abstract string Name { get; } + + /// + public abstract string BaseUri { get; } + + /// + public abstract string BasePath { get; } + + /// The URI used for batch operations. + public virtual string BatchUri { get { return null; } } + + /// The path used for batch operations. + public virtual string BatchPath { get { return null; } } + + /// + public abstract IList Features { get; } + + #endregion + + #endregion + + /// + public virtual void Dispose() + { + if (HttpClient != null) + { + HttpClient.Dispose(); + } + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ClientServiceRequest.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ClientServiceRequest.cs new file mode 100644 index 00000000000..6e25ccb5a7a --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ClientServiceRequest.cs @@ -0,0 +1,440 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Google.Apis.Discovery; +using Google.Apis.Http; +using Google.Apis.Logging; +using Google.Apis.Requests.Parameters; +using Google.Apis.Services; +using Google.Apis.Testing; +using Google.Apis.Util; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Requests +{ + /// + /// Represents an abstract request base class to make requests to a service. + /// + public abstract class ClientServiceRequest + { + /// Unsuccessful response handlers for this request only. + protected List _unsuccessfulResponseHandlers; + /// Exception handlers for this request only. + protected List _exceptionHandlers; + /// Execute interceptors for this request only. + protected List _executeInterceptors; + + /// + /// Credential to use for this request. + /// If implements + /// then it will also be included as a handler of an unsuccessful response. + /// + public IHttpExecuteInterceptor Credential { get; set; } + + /// + /// Add an unsuccessful response handler for this request only. + /// + /// The unsuccessful response handler. Must not be null. + public void AddUnsuccessfulResponseHandler(IHttpUnsuccessfulResponseHandler handler) + { + handler.ThrowIfNull(nameof(handler)); + if (_unsuccessfulResponseHandlers == null) + { + _unsuccessfulResponseHandlers = new List(); + } + _unsuccessfulResponseHandlers.Add(handler); + } + + /// + /// Add an exception handler for this request only. + /// + /// The exception handler. Must not be null. + public void AddExceptionHandler(IHttpExceptionHandler handler) + { + handler.ThrowIfNull(nameof(handler)); + if (_exceptionHandlers == null) + { + _exceptionHandlers = new List(); + } + _exceptionHandlers.Add(handler); + } + + /// + /// Add an execute interceptor for this request only. + /// If the request is retried, the interceptor will be called on each attempt. + /// + /// The execute interceptor. Must not be null. + public void AddExecuteInterceptor(IHttpExecuteInterceptor handler) + { + handler.ThrowIfNull(nameof(handler)); + if (_executeInterceptors == null) + { + _executeInterceptors = new List(); + } + _executeInterceptors.Add(handler); + } + } + + /// + /// Represents an abstract, strongly typed request base class to make requests to a service. + /// Supports a strongly typed response. + /// + /// The type of the response object + public abstract class ClientServiceRequest : ClientServiceRequest, IClientServiceRequest + { + /// The class logger. + private static readonly ILogger Logger = ApplicationContext.Logger.ForType>(); + + /// The service on which this request will be executed. + private readonly IClientService service; + + /// Defines whether the E-Tag will be used in a specified way or be ignored. + public ETagAction ETagAction { get; set; } + + /// + /// Gets or sets the callback for modifying HTTP requests made by this service request. + /// + public Action ModifyRequest { get; set; } + + /// + /// Override for service-wide validation configuration in BaseClientService.Initializer.ValidateParameters. + /// If this is null (the default) then the value from the service initializer is used to determine + /// whether or not parameters should be validated client-side. If this is non-null, it overrides + /// whatever value is specified in the service. + /// + public bool? ValidateParameters { get; set; } + + #region IClientServiceRequest Properties + + /// + public abstract string MethodName { get; } + + /// + public abstract string RestPath { get; } + + /// + public abstract string HttpMethod { get; } + + /// + public IDictionary RequestParameters { get; private set; } + + /// + public IClientService Service + { + get { return service; } + } + + #endregion + + /// Creates a new service request. + protected ClientServiceRequest(IClientService service) + { + this.service = service; + } + + /// + /// Initializes request's parameters. Inherited classes MUST override this method to add parameters to the + /// dictionary. + /// + protected virtual void InitParameters() + { + RequestParameters = new Dictionary(); + } + + #region Execution + + /// + public TResponse Execute() + { + try + { + using (var response = ExecuteUnparsedAsync(CancellationToken.None).Result) + { + return ParseResponse(response).Result; + } + } + catch (AggregateException aex) + { + // If an exception was thrown during the tasks, unwrap and throw it. + ExceptionDispatchInfo.Capture(aex.InnerException ?? aex).Throw(); + // Won't get here, but compiler requires it + throw; + } + } + + /// + public Stream ExecuteAsStream() + { + // TODO(peleyal): should we copy the stream, and dispose the response? + try + { + // Sync call. + var response = ExecuteUnparsedAsync(CancellationToken.None).Result; + return response.Content.ReadAsStreamAsync().Result; + } + catch (AggregateException aex) + { + // If an exception was thrown during the tasks, unwrap and throw it. + throw aex.InnerException; + } + catch (Exception ex) + { + throw ex; + } + } + + /// + public async Task ExecuteAsync() + { + return await ExecuteAsync(CancellationToken.None).ConfigureAwait(false); + } + + /// + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + using (var response = await ExecuteUnparsedAsync(cancellationToken).ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + return await ParseResponse(response).ConfigureAwait(false); + } + } + + /// + public async Task ExecuteAsStreamAsync() + { + return await ExecuteAsStreamAsync(CancellationToken.None).ConfigureAwait(false); + } + + /// + public async Task ExecuteAsStreamAsync(CancellationToken cancellationToken) + { + // TODO(peleyal): should we copy the stream, and dispose the response? + var response = await ExecuteUnparsedAsync(cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + } + + #region Helpers + + /// Sync executes the request without parsing the result. + private async Task ExecuteUnparsedAsync(CancellationToken cancellationToken) + { + using (var request = CreateRequest()) + { + return await service.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + } + } + + /// Parses the response and deserialize the content into the requested response object. + private async Task ParseResponse(HttpResponseMessage response) + { + if (response.IsSuccessStatusCode) + { + return await service.DeserializeResponse(response).ConfigureAwait(false); + } + var error = await service.DeserializeError(response).ConfigureAwait(false); + throw new GoogleApiException(service.Name) + { + Error = error, + HttpStatusCode = response.StatusCode + }; + } + + #endregion + + #endregion + + /// + public HttpRequestMessage CreateRequest(Nullable overrideGZipEnabled = null) + { + var builder = CreateBuilder(); + var request = builder.CreateRequest(); + object body = GetBody(); + request.SetRequestSerailizedContent(service, body, overrideGZipEnabled.HasValue + ? overrideGZipEnabled.Value : service.GZipEnabled); + AddETag(request); + if (_unsuccessfulResponseHandlers != null) + { + request.Properties.Add(ConfigurableMessageHandler.UnsuccessfulResponseHandlerKey, _unsuccessfulResponseHandlers); + } + if (_exceptionHandlers != null) + { + request.Properties.Add(ConfigurableMessageHandler.ExceptionHandlerKey, _exceptionHandlers); + } + if (_executeInterceptors != null) + { + request.Properties.Add(ConfigurableMessageHandler.ExecuteInterceptorKey, _executeInterceptors); + } + if (Credential != null) + { + request.Properties.Add(ConfigurableMessageHandler.CredentialKey, Credential); + } + ModifyRequest?.Invoke(request); + return request; + } + + /// + /// Creates the which is used to generate a request. + /// + /// + /// A new builder instance which contains the HTTP method and the right Uri with its path and query parameters. + /// + private RequestBuilder CreateBuilder() + { + var builder = new RequestBuilder() + { + BaseUri = new Uri(Service.BaseUri), + Path = RestPath, + Method = HttpMethod, + }; + + // Init parameters. + if (service.ApiKey != null) + { + builder.AddParameter(RequestParameterType.Query, "key", service.ApiKey); + } + var parameters = ParameterUtils.CreateParameterDictionary(this); + AddParameters(builder, ParameterCollection.FromDictionary(parameters)); + return builder; + } + + /// Generates the right URL for this request. + protected string GenerateRequestUri() => CreateBuilder().BuildUri().AbsoluteUri; + + /// Returns the body of this request. + /// The body of this request. + protected virtual object GetBody() => null; + + #region ETag + + /// + /// Adds the right ETag action (e.g. If-Match) header to the given HTTP request if the body contains ETag. + /// + private void AddETag(HttpRequestMessage request) + { + IDirectResponseSchema body = GetBody() as IDirectResponseSchema; + if (body != null && !string.IsNullOrEmpty(body.ETag)) + { + var etag = body.ETag; + ETagAction action = ETagAction == ETagAction.Default ? GetDefaultETagAction(HttpMethod) : ETagAction; + // TODO: ETag-related headers are added without validation at the moment, because it is known + // that some services are returning unquoted etags (see rfc7232). + // Once all services are fixed, change back to the commented-out code that validates the header. + switch (action) + { + case ETagAction.IfMatch: + //request.Headers.IfMatch.Add(new EntityTagHeaderValue(etag)); + request.Headers.TryAddWithoutValidation("If-Match", etag); + break; + case ETagAction.IfNoneMatch: + //request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue(etag)); + request.Headers.TryAddWithoutValidation("If-None-Match", etag); + break; + } + } + } + + /// Returns the default ETagAction for a specific HTTP verb. + [VisibleForTestOnly] + public static ETagAction GetDefaultETagAction(string httpMethod) + { + switch (httpMethod) + { + // Incoming data should only be updated if it has been changed on the server. + case HttpConsts.Get: + return ETagAction.IfNoneMatch; + + // Outgoing data should only be committed if it hasn't been changed on the server. + case HttpConsts.Put: + case HttpConsts.Post: + case HttpConsts.Patch: + case HttpConsts.Delete: + return ETagAction.IfMatch; + + default: + return ETagAction.Ignore; + } + } + + #endregion + + #region Parameters + + /// Adds path and query parameters to the given requestBuilder. + private void AddParameters(RequestBuilder requestBuilder, ParameterCollection inputParameters) + { + bool validateParameters = ValidateParameters ?? (Service as BaseClientService)?.ValidateParameters ?? true; + + foreach (var parameter in inputParameters) + { + if (!RequestParameters.TryGetValue(parameter.Key, out IParameter parameterDefinition)) + { + throw new GoogleApiException(Service.Name, + $"Invalid parameter \"{parameter.Key}\" was specified"); + } + + string value = parameter.Value; + if (validateParameters && + !ParameterValidator.ValidateParameter(parameterDefinition, value, out string error)) + { + throw new GoogleApiException(Service.Name, + $"Parameter validation failed for \"{parameterDefinition.Name}\" : {error}"); + } + + if (value == null) // If the parameter is null, use the default value. + { + value = parameterDefinition.DefaultValue; + } + + switch (parameterDefinition.ParameterType) + { + case "path": + requestBuilder.AddParameter(RequestParameterType.Path, parameter.Key, value); + break; + case "query": + // If the parameter is optional and no value is given, don't add to url. + if (!Object.Equals(value, parameterDefinition.DefaultValue) || parameterDefinition.IsRequired) + { + requestBuilder.AddParameter(RequestParameterType.Query, parameter.Key, value); + } + break; + default: + throw new GoogleApiException(service.Name, + $"Unsupported parameter type \"{parameterDefinition.ParameterType}\" for \"{parameterDefinition.Name}\""); + } + } + + // Check if there is a required parameter which wasn't set. + foreach (var parameter in RequestParameters.Values) + { + if (parameter.IsRequired && !inputParameters.ContainsKey(parameter.Name)) + { + throw new GoogleApiException(service.Name, + $"Parameter \"{parameter.Name}\" is missing"); + } + } + } + + #endregion + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ConfigurableHttpClient.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ConfigurableHttpClient.cs new file mode 100644 index 00000000000..ed84068e722 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ConfigurableHttpClient.cs @@ -0,0 +1,50 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Net.Http; + +namespace Google.Apis.Http +{ + /// + /// Configurable HTTP client inherits from and contains a reference to + /// . + /// + public class ConfigurableHttpClient : HttpClient + { + /// Gets the configurable message handler. + public ConfigurableMessageHandler MessageHandler { get; private set; } + + /// Constructs a new HTTP client. + /// This is equivalent to calling ConfigurableHttpClient(handler, true) + public ConfigurableHttpClient(ConfigurableMessageHandler handler) + : this(handler, true) + { + } + + /// + /// Constructs a new HTTP client. + /// + /// The handler for this client to use. + /// Whether the created + /// should dispose of the internal message handler or not when it iself is disposed. + public ConfigurableHttpClient(ConfigurableMessageHandler handler, bool disposeHandler) + : base (handler, disposeHandler) + { + MessageHandler = handler; + DefaultRequestHeaders.ExpectContinue = false; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ConfigurableMessageHandler.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ConfigurableMessageHandler.cs new file mode 100644 index 00000000000..cc12532d571 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ConfigurableMessageHandler.cs @@ -0,0 +1,713 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Google.Apis.Logging; +using Google.Apis.Testing; +using System.Net.Http.Headers; + +namespace Google.Apis.Http +{ + /// + /// A message handler which contains the main logic of our HTTP requests. It contains a list of + /// s for handling abnormal responses, a list of + /// s for handling exception in a request and a list of + /// s for intercepting a request before it has been sent to the server. + /// It also contains important properties like number of tries, follow redirect, etc. + /// + public class ConfigurableMessageHandler : DelegatingHandler + { + private const string QuotaProjectHeaderName = "x-goog-user-project"; + + /// The class logger. + private static readonly ILogger Logger = ApplicationContext.Logger.ForType(); + + /// Maximum allowed number of tries. + [VisibleForTestOnly] + public const int MaxAllowedNumTries = 20; + + /// + /// Key for unsuccessful response handlers in an properties. + /// + public const string UnsuccessfulResponseHandlerKey = "__UnsuccessfulResponseHandlerKey"; + + /// + /// Key for exception handlers in an properties. + /// + public const string ExceptionHandlerKey = "__ExceptionHandlerKey"; + + /// + /// Key for execute handlers in an properties. + /// + public const string ExecuteInterceptorKey = "__ExecuteInterceptorKey"; + + /// + /// Key for a stream response interceptor provider in an properties. + /// + public const string ResponseStreamInterceptorProviderKey = "__ResponseStreamInterceptorProviderKey"; + + /// + /// Key for a credential in a properties. + /// + public const string CredentialKey = "__CredentialKey"; + + /// + /// Key for request specific max retries. + /// + public const string MaxRetriesKey = "__MaxRetriesKey"; + + /// The current API version of this client library. + private static readonly string ApiVersion = Google.Apis.Util.Utilities.GetLibraryVersion(); + + /// The User-Agent suffix header which contains the . + private static readonly string UserAgentSuffix = "google-api-dotnet-client/" + ApiVersion + " (gzip)"; + + #region IHttpUnsuccessfulResponseHandler, IHttpExceptionHandler and IHttpExecuteInterceptor lists + + #region Lock objects + + // The following lock objects are used to lock the list of handlers and interceptors in order to be able to + // iterate over them from several threads and to keep this class thread-safe. + private readonly object unsuccessfulResponseHandlersLock = new object(); + private readonly object exceptionHandlersLock = new object(); + private readonly object executeInterceptorsLock = new object(); + + #endregion + + /// A list of . + private readonly IList unsuccessfulResponseHandlers = + new List(); + + /// A list of . + private readonly IList exceptionHandlers = + new List(); + + /// A list of . + private readonly IList executeInterceptors = + new List(); + + /// + /// Gets a list of s. + /// + /// Since version 1.10, and + /// were added in order to keep this class thread-safe. + /// More information is available on + /// #592. + /// + /// + [Obsolete("Use AddUnsuccessfulResponseHandler or RemoveUnsuccessfulResponseHandler instead.")] + public IList UnsuccessfulResponseHandlers + { + get { return unsuccessfulResponseHandlers; } + } + + /// Adds the specified handler to the list of unsuccessful response handlers. + public void AddUnsuccessfulResponseHandler(IHttpUnsuccessfulResponseHandler handler) + { + lock (unsuccessfulResponseHandlersLock) + { + unsuccessfulResponseHandlers.Add(handler); + } + } + + /// Removes the specified handler from the list of unsuccessful response handlers. + public void RemoveUnsuccessfulResponseHandler(IHttpUnsuccessfulResponseHandler handler) + { + lock (unsuccessfulResponseHandlersLock) + { + unsuccessfulResponseHandlers.Remove(handler); + } + } + + /// + /// Gets a list of s. + /// + /// Since version 1.10, and were added + /// in order to keep this class thread-safe. More information is available on + /// #592. + /// + /// + [Obsolete("Use AddExceptionHandler or RemoveExceptionHandler instead.")] + public IList ExceptionHandlers + { + get { return exceptionHandlers; } + } + + /// Adds the specified handler to the list of exception handlers. + public void AddExceptionHandler(IHttpExceptionHandler handler) + { + lock (exceptionHandlersLock) + { + exceptionHandlers.Add(handler); + } + } + + /// Removes the specified handler from the list of exception handlers. + public void RemoveExceptionHandler(IHttpExceptionHandler handler) + { + lock (exceptionHandlersLock) + { + exceptionHandlers.Remove(handler); + } + } + + /// + /// Gets a list of s. + /// + /// Since version 1.10, and were + /// added in order to keep this class thread-safe. More information is available on + /// #592. + /// + /// + [Obsolete("Use AddExecuteInterceptor or RemoveExecuteInterceptor instead.")] + public IList ExecuteInterceptors + { + get { return executeInterceptors; } + } + + /// Adds the specified interceptor to the list of execute interceptors. + public void AddExecuteInterceptor(IHttpExecuteInterceptor interceptor) + { + lock (executeInterceptorsLock) + { + executeInterceptors.Add(interceptor); + } + } + + /// Removes the specified interceptor from the list of execute interceptors. + public void RemoveExecuteInterceptor(IHttpExecuteInterceptor interceptor) + { + lock (executeInterceptorsLock) + { + executeInterceptors.Remove(interceptor); + } + } + + #endregion + + private int _loggingRequestId = 0; + + private ILogger _instanceLogger = Logger; + + /// + /// For testing only. + /// This defaults to the static , but can be overridden for fine-grain testing. + /// + internal ILogger InstanceLogger + { + get { return _instanceLogger; } + set { _instanceLogger = value.ForType(); } + } + + /// Number of tries. Default is 3. + private int numTries = 3; + + /// + /// Gets or sets the number of tries that will be allowed to execute. Retries occur as a result of either + /// or which handles the + /// abnormal HTTP response or exception before being terminated. + /// Set 1 for not retrying requests. The default value is 3. + /// + /// The number of allowed redirects (3xx) is defined by . This property defines + /// only the allowed tries for >=400 responses, or when an exception is thrown. For example if you set + /// to 1 and to 5, the library will send up to five redirect + /// requests, but will not send any retry requests due to an error HTTP status code. + /// + /// + public int NumTries + { + get { return numTries; } + set + { + if (value > MaxAllowedNumTries || value < 1) + { + throw new ArgumentOutOfRangeException("NumTries"); + } + numTries = value; + } + } + + /// Number of redirects allowed. Default is 10. + private int numRedirects = 10; + + /// + /// Gets or sets the number of redirects that will be allowed to execute. The default value is 10. + /// See for more information. + /// + public int NumRedirects + { + get { return numRedirects; } + set + { + if (value > MaxAllowedNumTries || value < 1) + { + throw new ArgumentOutOfRangeException("NumRedirects"); + } + numRedirects = value; + } + } + + /// + /// Gets or sets whether the handler should follow a redirect when a redirect response is received. Default + /// value is true. + /// + public bool FollowRedirect { get; set; } + + /// Gets or sets whether logging is enabled. Default value is true. + public bool IsLoggingEnabled { get; set; } + + /// + /// Specifies the type(s) of request/response events to log. + /// + [Flags] + public enum LogEventType + { + /// + /// Log no request/response information. + /// + None = 0, + + /// + /// Log the request URI. + /// + RequestUri = 1, + + /// + /// Log the request headers. + /// + RequestHeaders = 2, + + /// + /// Log the request body. The body is assumed to be ASCII, and non-printable charaters are replaced by '.'. + /// Warning: This causes the body content to be buffered in memory, so use with care for large requests. + /// + RequestBody = 4, + + /// + /// Log the response status. + /// + ResponseStatus = 8, + + /// + /// Log the response headers. + /// + ResponseHeaders = 16, + + /// + /// Log the response body. The body is assumed to be ASCII, and non-printable characters are replaced by '.'. + /// Warning: This causes the body content to be buffered in memory, so use with care for large responses. + /// + ResponseBody = 32, + + /// + /// Log abnormal response messages. + /// + ResponseAbnormal = 64, + } + + /// + /// The request/response types to log. + /// + public LogEventType LogEvents { get; set; } + + /// Gets or sets the application name which will be used on the User-Agent header. + public string ApplicationName { get; set; } + + /// Gets or sets the value set for the x-goog-api-client header. + public string GoogleApiClientHeader { get; set; } + + /// + /// The credential to apply to all requests made with this client, + /// unless theres a specific call credential set. + /// If implements + /// then it will also be included as a handler of an unsuccessful response. + /// + public IHttpExecuteInterceptor Credential { get; set; } + + /// Constructs a new configurable message handler. + public ConfigurableMessageHandler(HttpMessageHandler httpMessageHandler) + : base(httpMessageHandler) + { + // set default values + FollowRedirect = true; + IsLoggingEnabled = true; + LogEvents = LogEventType.RequestUri | LogEventType.ResponseStatus | LogEventType.ResponseAbnormal; + } + + private void LogHeaders(string initialText, HttpHeaders headers1, HttpHeaders headers2) + { + var headers = (headers1 ?? Enumerable.Empty>>()) + .Concat(headers2 ?? Enumerable.Empty>>()).ToList(); + var args = new object[headers.Count * 2]; + var fmt = new StringBuilder(headers.Count * 32); + fmt.Append(initialText); + var argBuilder = new StringBuilder(); + for (int i = 0; i < headers.Count; i++) + { + fmt.Append($"\n [{{{i * 2}}}] '{{{1 + i * 2}}}'"); + args[i * 2] = headers[i].Key; + argBuilder.Clear(); + args[1 + i * 2] = string.Join("; ", headers[i].Value); + } + InstanceLogger.Debug(fmt.ToString(), args); + } + + private async Task LogBody(string fmtText, HttpContent content) + { + // This buffers the body content within the HttpContent if required. + var bodyBytes = content != null ? await content.ReadAsByteArrayAsync().ConfigureAwait(false) : new byte[0]; + char[] bodyChars = new char[bodyBytes.Length]; + for (int i = 0; i < bodyBytes.Length; i++) + { + var b = bodyBytes[i]; + bodyChars[i] = b >= 32 && b <= 126 ? (char)b : '.'; + } + InstanceLogger.Debug(fmtText, new string(bodyChars)); + } + + /// + /// The main logic of sending a request to the server. This send method adds the User-Agent header to a request + /// with and the library version. It also calls interceptors before each attempt, + /// and unsuccessful response handler or exception handlers when abnormal response or exception occurred. + /// + protected override async Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + var loggable = IsLoggingEnabled && InstanceLogger.IsDebugEnabled; + string loggingRequestId = ""; + if (loggable) + { + loggingRequestId = Interlocked.Increment(ref _loggingRequestId).ToString("X8"); + } + + int maxRetries = GetEffectiveMaxRetries(request); + int triesRemaining = maxRetries; + int redirectRemaining = NumRedirects; + + Exception lastException = null; + + // Set User-Agent header. + var userAgent = (ApplicationName == null ? "" : ApplicationName + " ") + UserAgentSuffix; + // TODO: setting the User-Agent won't work on Silverlight. We may need to create a special callback here to + // set it correctly. + request.Headers.Add("User-Agent", userAgent); + var apiClientHeader = GoogleApiClientHeader; + if (apiClientHeader != null) + { + request.Headers.Add("x-goog-api-client", apiClientHeader); + } + + HttpResponseMessage response = null; + do // While (triesRemaining > 0) + { + response?.Dispose(); + response = null; + lastException = null; + + // Check after previous response (if any) has been disposed of. + cancellationToken.ThrowIfCancellationRequested(); + + // We keep a local list of the interceptors, since we can't call await inside lock. + List interceptors; + lock (executeInterceptorsLock) + { + interceptors = executeInterceptors.ToList(); + } + if (request.Properties.TryGetValue(ExecuteInterceptorKey, out var interceptorsValue) && + interceptorsValue is List perCallinterceptors) + { + interceptors.AddRange(perCallinterceptors); + } + + // Intercept the request. + foreach (var interceptor in interceptors) + { + await interceptor.InterceptAsync(request, cancellationToken).ConfigureAwait(false); + } + + // Before having the credential intercept the call, check that quota project hasn't + // been added as a header. Quota project cannot be added except through the credential. + CheckValidAfterInterceptors(request); + + await CredentialInterceptAsync(request, cancellationToken).ConfigureAwait(false); + + if (loggable) + { + if ((LogEvents & LogEventType.RequestUri) != 0) + { + InstanceLogger.Debug("Request[{0}] (triesRemaining={1}) URI: '{2}'", loggingRequestId, triesRemaining, request.RequestUri); + } + if ((LogEvents & LogEventType.RequestHeaders) != 0) + { + LogHeaders($"Request[{loggingRequestId}] Headers:", request.Headers, request.Content?.Headers); + } + if ((LogEvents & LogEventType.RequestBody) != 0) + { + await LogBody($"Request[{loggingRequestId}] Body: '{{0}}'", request.Content).ConfigureAwait(false); + } + } + try + { + // Send the request! + response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + lastException = ex; + } + + // Decrease the number of retries. + if (response == null || ((int)response.StatusCode >= 400 || (int)response.StatusCode < 200)) + { + triesRemaining--; + } + + // Exception was thrown, try to handle it. + if (response == null) + { + var exceptionHandled = false; + + // We keep a local list of the handlers, since we can't call await inside lock. + List handlers; + lock (exceptionHandlersLock) + { + handlers = exceptionHandlers.ToList(); + } + if (request.Properties.TryGetValue(ExceptionHandlerKey, out var handlersValue) && + handlersValue is List perCallHandlers) + { + handlers.AddRange(perCallHandlers); + } + + // Try to handle the exception with each handler. + foreach (var handler in handlers) + { + exceptionHandled |= await handler.HandleExceptionAsync(new HandleExceptionArgs + { + Request = request, + Exception = lastException, + TotalTries = maxRetries, + CurrentFailedTry = maxRetries - triesRemaining, + CancellationToken = cancellationToken + }).ConfigureAwait(false); + } + + if (!exceptionHandled) + { + InstanceLogger.Error(lastException, + "Response[{0}] Exception was thrown while executing a HTTP request and it wasn't handled", loggingRequestId); + throw lastException; + } + else if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0) + { + InstanceLogger.Debug("Response[{0}] Exception {1} was thrown, but it was handled by an exception handler", + loggingRequestId, lastException.Message); + } + } + else + { + if (loggable) + { + if ((LogEvents & LogEventType.ResponseStatus) != 0) + { + InstanceLogger.Debug("Response[{0}] Response status: {1} '{2}'", loggingRequestId, response.StatusCode, response.ReasonPhrase); + } + if ((LogEvents & LogEventType.ResponseHeaders) != 0) + { + LogHeaders($"Response[{loggingRequestId}] Headers:", response.Headers, response.Content?.Headers); + } + if ((LogEvents & LogEventType.ResponseBody) != 0) + { + await LogBody($"Response[{loggingRequestId}] Body: '{{0}}'", response.Content).ConfigureAwait(false); + } + } + if (response.IsSuccessStatusCode) + { + // No need to retry, the response was successful. + triesRemaining = 0; + } + else + { + bool errorHandled = false; + + // We keep a local list of the handlers, since we can't call await inside lock. + List handlers; + lock (unsuccessfulResponseHandlersLock) + { + handlers = unsuccessfulResponseHandlers.ToList(); + } + if (request.Properties.TryGetValue(UnsuccessfulResponseHandlerKey, out var handlersValue) && + handlersValue is List perCallHandlers) + { + handlers.AddRange(perCallHandlers); + } + + var handlerArgs = new HandleUnsuccessfulResponseArgs + { + Request = request, + Response = response, + TotalTries = maxRetries, + CurrentFailedTry = maxRetries - triesRemaining, + CancellationToken = cancellationToken + }; + + // Try to handle the abnormal HTTP response with each handler. + foreach (var handler in handlers) + { + try + { + errorHandled |= await handler.HandleResponseAsync(handlerArgs).ConfigureAwait(false); + } + catch when (DisposeAndReturnFalse(response)) { } + + bool DisposeAndReturnFalse(IDisposable disposable) + { + disposable.Dispose(); + return false; + } + } + + errorHandled |= await CredentialHandleResponseAsync(handlerArgs).ConfigureAwait(false); + + if (!errorHandled) + { + if (FollowRedirect && HandleRedirect(response)) + { + if (redirectRemaining-- == 0) + { + triesRemaining = 0; + } + + errorHandled = true; + if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0) + { + InstanceLogger.Debug("Response[{0}] Redirect response was handled successfully. Redirect to {1}", + loggingRequestId, response.Headers.Location); + } + } + else + { + if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0) + { + InstanceLogger.Debug("Response[{0}] An abnormal response wasn't handled. Status code is {1}", + loggingRequestId, response.StatusCode); + } + + // No need to retry, because no handler handled the abnormal response. + triesRemaining = 0; + } + } + else if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0) + { + InstanceLogger.Debug("Response[{0}] An abnormal response was handled by an unsuccessful response handler. " + + "Status Code is {1}", loggingRequestId, response.StatusCode); + } + } + } + } while (triesRemaining > 0); // Not a successful status code but it was handled. + + // If the response is null, we should throw the last exception. + if (response == null) + { + InstanceLogger.Error(lastException, "Request[{0}] Exception was thrown while executing a HTTP request", loggingRequestId); + throw lastException; + } + else if (!response.IsSuccessStatusCode && loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0) + { + InstanceLogger.Debug("Response[{0}] Abnormal response is being returned. Status Code is {1}", loggingRequestId, response.StatusCode); + } + + return response; + } + + private void CheckValidAfterInterceptors(HttpRequestMessage request) + { + if (request.Headers.Contains(QuotaProjectHeaderName)) + { + throw new InvalidOperationException($"{QuotaProjectHeaderName} header can only be added through the credential or through the ClientBuilder."); + } + } + + private async Task CredentialInterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var effectiveCredential = GetEffectiveCredential(request); + if (effectiveCredential != null) + { + await effectiveCredential.InterceptAsync(request, cancellationToken).ConfigureAwait(false); + } + } + + private async Task CredentialHandleResponseAsync(HandleUnsuccessfulResponseArgs args) + { + var effectiveCredential = GetEffectiveCredential(args.Request); + if (effectiveCredential is IHttpUnsuccessfulResponseHandler handler) + { + return await handler.HandleResponseAsync(args).ConfigureAwait(false); + } + + return false; + } + + private IHttpExecuteInterceptor GetEffectiveCredential(HttpRequestMessage request) => + (request.Properties.TryGetValue(CredentialKey, out var cred) && cred is IHttpExecuteInterceptor callCredential) + ? callCredential : Credential; + + private int GetEffectiveMaxRetries(HttpRequestMessage request) => + (request.Properties.TryGetValue(MaxRetriesKey, out var maxRetries) && maxRetries is int perRequestMaxRetries) + ? perRequestMaxRetries : NumTries; + + /// + /// Handles redirect if the response's status code is redirect, redirects are turned on, and the header has + /// a location. + /// When the status code is 303 the method on the request is changed to a GET as per the RFC2616 + /// specification. On a redirect, it also removes the Authorization and all If-* request headers. + /// + /// Whether this method changed the request and handled redirect successfully. + private bool HandleRedirect(HttpResponseMessage message) + { + // TODO(peleyal): think if it's better to move that code to RedirectUnsucessfulResponseHandler + var uri = message.Headers.Location; + if (!message.IsRedirectStatusCode() || uri == null) + { + return false; + } + + var request = message.RequestMessage; + request.RequestUri = new Uri(request.RequestUri, uri); + // Status code for a resource that has moved to a new URI and should be retrieved using GET. + if (message.StatusCode == HttpStatusCode.SeeOther) + { + request.Method = HttpMethod.Get; + } + // Clear Authorization and If-* headers. + request.Headers.Remove("Authorization"); + request.Headers.IfMatch.Clear(); + request.Headers.IfNoneMatch.Clear(); + request.Headers.IfModifiedSince = null; + request.Headers.IfUnmodifiedSince = null; + request.Headers.Remove("If-Range"); + return true; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/Delegates.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/Delegates.cs new file mode 100644 index 00000000000..365a4fef70c --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/Delegates.cs @@ -0,0 +1,12 @@ +namespace Google.Apis.Http +{ + /// + /// A delegate used to intercept stream data without modifying it. + /// The parameters should always be validated before the delegate is called, + /// so the delegate itself does not need to validate them again. + /// + /// The buffer containing the data. + /// The offset into the buffer. + /// The number of bytes being read/written. + public delegate void StreamInterceptor(byte[] buffer, int offset, int count); +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ETagAction.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ETagAction.cs new file mode 100644 index 00000000000..9d744492f43 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ETagAction.cs @@ -0,0 +1,46 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +namespace Google.Apis +{ + /// + /// Defines the behaviour/header used for sending an etag along with a request. + /// + public enum ETagAction + { + /// + /// The default etag behaviour will be determined by the type of the request. + /// + Default, + + /// + /// The ETag won't be added to the header of the request. + /// + Ignore, + + /// + /// The ETag will be added as an "If-Match" header. + /// A request sent with an "If-Match" header will only succeed if both ETags are identical. + /// + IfMatch, + + /// + /// The ETag will be added as an "If-None-Match" header. + /// A request sent with an "If-None-Match" header will only succeed if ETags are not identical. + /// + IfNoneMatch, + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOff.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOff.cs new file mode 100644 index 00000000000..7160fdf608f --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOff.cs @@ -0,0 +1,99 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Util +{ + /// + /// Implementation of that increases the back-off period for each retry attempt using a + /// randomization function that grows exponentially. In addition, it also adds a randomize number of milliseconds + /// for each attempt. + /// + public class ExponentialBackOff : IBackOff + { + /// The maximum allowed number of retries. + private const int MaxAllowedNumRetries = 20; + + private readonly TimeSpan deltaBackOff; + /// + /// Gets the delta time span used to generate a random milliseconds to add to the next back-off. + /// If the value is then the generated back-off will be exactly 1, 2, 4, + /// 8, 16, etc. seconds. A valid value is between zero and one second. The default value is 250ms, which means + /// that the generated back-off will be [0.75-1.25]sec, [1.75-2.25]sec, [3.75-4.25]sec, and so on. + /// + public TimeSpan DeltaBackOff + { + get { return deltaBackOff; } + } + + private readonly int maxNumOfRetries; + /// Gets the maximum number of retries. Default value is 10. + public int MaxNumOfRetries + { + get { return maxNumOfRetries; } + } + + /// The random instance which generates a random number to add the to next back-off. + private Random random = new Random(); + + /// Constructs a new exponential back-off with default values. + public ExponentialBackOff() + : this(TimeSpan.FromMilliseconds(250)) + { + } + + /// Constructs a new exponential back-off with the given delta and maximum retries. + public ExponentialBackOff(TimeSpan deltaBackOff, int maximumNumOfRetries = 10) + { + if (deltaBackOff < TimeSpan.Zero || deltaBackOff > TimeSpan.FromSeconds(1)) + { + throw new ArgumentOutOfRangeException("deltaBackOff"); + } + if (maximumNumOfRetries < 0 || maximumNumOfRetries > MaxAllowedNumRetries) + { + throw new ArgumentOutOfRangeException("deltaBackOff"); + } + + this.deltaBackOff = deltaBackOff; + this.maxNumOfRetries = maximumNumOfRetries; + } + + #region IBackOff Members + + /// + public TimeSpan GetNextBackOff(int currentRetry) + { + if (currentRetry <= 0) + { + throw new ArgumentOutOfRangeException("currentRetry"); + } + if (currentRetry > MaxNumOfRetries) + { + return TimeSpan.MinValue; + } + + // Generate a random number of milliseconds and add it to the current exponential number. + var randomMilli = (double)random.Next( + (int)(DeltaBackOff.TotalMilliseconds * -1), + (int)(DeltaBackOff.TotalMilliseconds * 1)); + int backOffMilli = (int)(Math.Pow(2.0, (double)currentRetry - 1) * 1000 + randomMilli); + return TimeSpan.FromMilliseconds(backOffMilli); + } + + #endregion + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOffInitializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOffInitializer.cs new file mode 100644 index 00000000000..c026141ecf4 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOffInitializer.cs @@ -0,0 +1,79 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Google.Apis.Http +{ + /// + /// Indicates if exponential back-off is used automatically on exceptions in a service requests and \ or when 503 + /// responses is returned form the server. + /// + [Flags] + public enum ExponentialBackOffPolicy + { + /// Exponential back-off is disabled. + None = 0, + /// Exponential back-off is enabled only for exceptions. + Exception = 1, + /// Exponential back-off is enabled only for 503 HTTP Status code. + UnsuccessfulResponse503 = 2 + } + + /// + /// An initializer which adds exponential back-off as exception handler and \ or unsuccessful response handler by + /// the given . + /// + public class ExponentialBackOffInitializer : IConfigurableHttpClientInitializer + { + /// Gets or sets the used back-off policy. + private ExponentialBackOffPolicy Policy { get; set; } + + /// Gets or sets the back-off handler creation function. + private Func CreateBackOff { get; set; } + + /// + /// Constructs a new back-off initializer with the given policy and back-off handler create function. + /// + public ExponentialBackOffInitializer(ExponentialBackOffPolicy policy, Func createBackOff) + { + Policy = policy; + CreateBackOff = createBackOff; + } + + /// + public void Initialize(ConfigurableHttpClient httpClient) + { + var backOff = CreateBackOff(); + + // Add exception handler and \ or unsuccessful response handler. + if ((Policy & ExponentialBackOffPolicy.Exception) == ExponentialBackOffPolicy.Exception) + { + httpClient.MessageHandler.AddExceptionHandler(backOff); + } + + if ((Policy & ExponentialBackOffPolicy.UnsuccessfulResponse503) == + ExponentialBackOffPolicy.UnsuccessfulResponse503) + { + httpClient.MessageHandler.AddUnsuccessfulResponseHandler(backOff); + } + } + } + +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/Features.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/Features.cs new file mode 100644 index 00000000000..887a8e68b50 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/Features.cs @@ -0,0 +1,32 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Google.Apis.Util; + +namespace Google.Apis.Discovery +{ + /// + /// Specifies a list of features which can be defined within the discovery document of a service. + /// + public enum Features + { + /// + /// If this feature is specified, then the data of a response is encapsulated within a "data" resource. + /// + [StringValue("dataWrapper")] + LegacyDataResponse, + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/FileDataStore.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/FileDataStore.cs new file mode 100644 index 00000000000..06fe1b44fed --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/FileDataStore.cs @@ -0,0 +1,183 @@ +/* +Copyright 2017 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// TODO: This does not support UWP Storage. + +using Google.Apis.Json; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Google.Apis.Util.Store +{ + /// + /// File data store that implements . This store creates a different file for each + /// combination of type and key. This file data store stores a JSON format of the specified object. + /// + public class FileDataStore : IDataStore + { + private const string XdgDataHomeSubdirectory = "google-filedatastore"; + private static readonly Task CompletedTask = Task.FromResult(0); + + readonly string folderPath; + /// Gets the full folder path. + public string FolderPath { get { return folderPath; } } + + /// + /// Constructs a new file data store. If fullPath is false the path will be used as relative to + /// Environment.SpecialFolder.ApplicationData" on Windows, or $HOME on Linux and MacOS, + /// otherwise the input folder will be treated as absolute. + /// The folder is created if it doesn't exist yet. + /// + /// Folder path. + /// + /// Defines whether the folder parameter is absolute or relative to + /// Environment.SpecialFolder.ApplicationData on Windows, or$HOME on Linux and MacOS. + /// + public FileDataStore(string folder, bool fullPath = false) + { + folderPath = fullPath + ? folder + : Path.Combine(GetHomeDirectory(), folder); + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + } + + private string GetHomeDirectory() + { + string appData = Environment.GetEnvironmentVariable("APPDATA"); + if (!string.IsNullOrEmpty(appData)) + { + // This is almost certainly windows. + // This path must be the same between the desktop FileDataStore and this netstandard FileDataStore. + return appData; + } + string home = Environment.GetEnvironmentVariable("HOME"); + if (!string.IsNullOrEmpty(home)) + { + // This is almost certainly Linux or MacOS. + // Follow the XDG Base Directory Specification: https://specifications.freedesktop.org/basedir-spec/latest/index.html + // Store data in subdirectory of $XDG_DATA_HOME if it exists, defaulting to $HOME/.local/share if not set. + string xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + if (string.IsNullOrEmpty(xdgDataHome)) + { + xdgDataHome = Path.Combine(home, ".local", "share"); + } + return Path.Combine(xdgDataHome, XdgDataHomeSubdirectory); + } + throw new PlatformNotSupportedException("Relative FileDataStore paths not supported on this platform."); + } + + /// + /// Stores the given value for the given key. It creates a new file (named ) in + /// . + /// + /// The type to store in the data store. + /// The key. + /// The value to store in the data store. + public Task StoreAsync(string key, T value) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException("Key MUST have a value"); + } + + var serialized = NewtonsoftJsonSerializer.Instance.Serialize(value); + var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T))); + File.WriteAllText(filePath, serialized); + return CompletedTask; + } + + /// + /// Deletes the given key. It deletes the named file in + /// . + /// + /// The key to delete from the data store. + public Task DeleteAsync(string key) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException("Key MUST have a value"); + } + + var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T))); + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + return CompletedTask; + } + + /// + /// Returns the stored value for the given key or null if the matching file ( + /// in doesn't exist. + /// + /// The type to retrieve. + /// The key to retrieve from the data store. + /// The stored object. + public Task GetAsync(string key) + { + if (string.IsNullOrEmpty(key)) + { + throw new ArgumentException("Key MUST have a value"); + } + + TaskCompletionSource tcs = new TaskCompletionSource(); + var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T))); + if (File.Exists(filePath)) + { + try + { + var obj = File.ReadAllText(filePath); + tcs.SetResult(NewtonsoftJsonSerializer.Instance.Deserialize(obj)); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + } + else + { + tcs.SetResult(default(T)); + } + return tcs.Task; + } + + /// + /// Clears all values in the data store. This method deletes all files in . + /// + public Task ClearAsync() + { + if (Directory.Exists(folderPath)) + { + Directory.Delete(folderPath, true); + Directory.CreateDirectory(folderPath); + } + + return CompletedTask; + } + + /// Creates a unique stored key based on the key and the class type. + /// The object key. + /// The type to store or retrieve. + public static string GenerateStoredKey(string key, Type t) + { + return string.Format("{0}-{1}", t.FullName, key); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/GoogleApiException.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/GoogleApiException.cs new file mode 100644 index 00000000000..399590f0610 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/GoogleApiException.cs @@ -0,0 +1,100 @@ +/* +Copyright 2010 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Net; + +using Google.Apis.Requests; +using Google.Apis.Util; + +namespace Google +{ + /// Represents an exception thrown by an API Service. + public class GoogleApiException : Exception + { + private readonly bool _hasMessage; + + /// Gets the service name which related to this exception. + public string ServiceName { get; } + + /// Creates an API Service exception. + public GoogleApiException(string serviceName, string message, Exception inner) + : base(message, inner) + { + ServiceName = serviceName.ThrowIfNull(nameof(serviceName)); + _hasMessage = message is object; + } + + /// Creates an API Service exception. + public GoogleApiException(string serviceName, string message) : this(serviceName, message, null) + { } + + /// + /// Creates an API Service exception with no message. + /// + /// + /// may still contain useful information if the + /// and/or properties are set. + /// + public GoogleApiException(string serviceName) : this(serviceName, null, null) + { } + + /// The Error which was returned from the server, or null if unavailable. + public RequestError Error { get; set; } + + private string ErrorMessage => + Error?.Message ?? "No error message was specified."; + + private string ContentMessage => + Error is null + ? "No error details were specified." + : (Error.IsOnlyRawContent + ? $"No JSON error details were specified.{Environment.NewLine}" + + (string.IsNullOrWhiteSpace(Error.ErrorResponseContent) + ? "Raw error details are empty or white spaces only." + : $"Raw error details are: {Error.ErrorResponseContent}") + : $"{Error}"); + + /// The HTTP status code which was returned along with this error, or 0 if unavailable. + public HttpStatusCode HttpStatusCode { get; set; } + + private string HttpStatusCodeMessage => + HttpStatusCode > 0 + ? $"HttpStatusCode is {HttpStatusCode}." + : "No HttpStatusCode was specified."; + + private string ServiceNameMessage => $"The service {ServiceName} has thrown an exception."; + + private string CombinedMessage => $"{ServiceNameMessage} {HttpStatusCodeMessage} {ErrorMessage}"; + + /// + public override string Message => + // We just override the default message which is very generic in Exception + _hasMessage ? base.Message : CombinedMessage; + + /// + /// Returns a summary of this exception. + /// + /// A summary of this exception. + public override string ToString() => + // base.ToString() prints Message, and if we have overwritten Message with DefaultMessage + // then ToString() will contain some duplicate information. But that's OK. + $"{ServiceNameMessage}{Environment.NewLine}" + + $"{HttpStatusCodeMessage}{Environment.NewLine}" + + $"{ContentMessage}{Environment.NewLine}" + + $"{base.ToString()}"; + } +} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpClientFactory.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpClientFactory.cs new file mode 100644 index 00000000000..6d6a136ec57 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpClientFactory.cs @@ -0,0 +1,167 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Net; +using System.Net.Http; + +namespace Google.Apis.Http +{ + /// The default implementation of the HTTP client factory. + public class HttpClientFactory : IHttpClientFactory + { + /// + /// Creates a new instance of that + /// will set the given proxy on HTTP clients created by this factory. + /// + /// The proxy to set on HTTP clients created by this factory. + /// May be null, in which case no proxy will be used. + public static HttpClientFactory ForProxy(IWebProxy proxy) => + new HttpClientFactory(proxy); + + /// + /// Creates a new instance of . + /// + public HttpClientFactory() : this(null) + { } + + /// + /// Creates a new instance of that + /// will set the given proxy on HTTP clients created by this factory. + /// + /// The proxy to set on HTTP clients created by this factory. + /// May be null, in which case no proxy will be used. + protected HttpClientFactory(IWebProxy proxy) => Proxy = proxy; + + /// + /// Gets the proxy to use when creating HTTP clients, if any. + /// May be null, in which case, no proxy will be set for HTTP clients + /// created by this factory. + /// + public IWebProxy Proxy { get; } + + /// + public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) + { + // Create the handler. + var handler = CreateHandler(args); + var configurableHandler = new ConfigurableMessageHandler(handler) + { + ApplicationName = args.ApplicationName, + GoogleApiClientHeader = args.GoogleApiClientHeader + }; + + // Create the client. + var client = new ConfigurableHttpClient(configurableHandler); + foreach (var initializer in args.Initializers) + { + initializer.Initialize(client); + } + + return client; + } + + /// Creates a HTTP message handler. Override this method to mock a message handler. + protected virtual HttpMessageHandler CreateHandler(CreateHttpClientArgs args) + { + // We need to handle three situations in order to intercept uncompressed data where necessary + // while using the built-in decompression where possible. + // - No compression requested + // - Compression requested but not supported by HttpClientHandler (easy; just GzipDeflateHandler on top of an interceptor on top of HttpClientHandler) + // - Compression requested and HttpClientHandler (complex: create two different handlers and decide which to use on a per-request basis) + + var clientHandler = CreateAndConfigureClientHandler(); + + if (!args.GZipEnabled) + { + // Simple: nothing will be decompressing content, so we can just intercept the original handler. + return new StreamInterceptionHandler(clientHandler); + } + else if (!clientHandler.SupportsAutomaticDecompression) + { + // Simple: we have to create our own decompression handler anyway, so there's still just a single chain. + var interceptionHandler = new StreamInterceptionHandler(clientHandler); + return new GzipDeflateHandler(interceptionHandler); + } + else + { + // Complex: we want to use a simple handler with no interception but with built-in decompression + // for requests that wouldn't perform interception anyway, and a longer chain for interception cases. + clientHandler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; + + return new TwoWayDelegatingHandler( + // Normal handler (with built-in decompression) when there's no interception. + clientHandler, + // Alternative handler for requests that might be intercepted, and need that interception to happen + // before decompression. We need to delegate to a new client handler that *doesn't* + new GzipDeflateHandler(new StreamInterceptionHandler(CreateAndConfigureClientHandler())), + request => StreamInterceptionHandler.GetInterceptorProvider(request) != null); + } + } + + /// + /// Creates a simple client handler with redirection and compression disabled. + /// + private HttpClientHandler CreateAndConfigureClientHandler() + { + var handler = CreateClientHandler(); + if (handler.SupportsRedirectConfiguration) + { + handler.AllowAutoRedirect = false; + } + if (handler.SupportsAutomaticDecompression) + { + handler.AutomaticDecompression = DecompressionMethods.None; + } + return handler; + } + + /// + /// Create a for use when communicating with the server. + /// Please read the remarks closely before overriding this method. + /// + /// + /// When overriding this method, please observe the following: + /// + /// + /// and + /// + /// of the returned instance are configured after this method returns. + /// Configuring these within this method will have no effect. + /// + /// + /// is set in this method to + /// if value is not null. You may override that behaviour. + /// + /// + /// Return a new instance of an for each call to this method. + /// + /// + /// This method may be called once, or more than once, when initializing a single client service. + /// + /// + /// + /// A suitable . + protected virtual HttpClientHandler CreateClientHandler() + { + var client = new HttpClientHandler(); + if (Proxy != null) + { + client.Proxy = Proxy; + } + return client; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpConsts.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpConsts.cs new file mode 100644 index 00000000000..11e236bc8c9 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpConsts.cs @@ -0,0 +1,42 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Google.Apis.Http +{ + /// HTTP constants. + public static class HttpConsts + { + /// Http GET request + public const string Get = "GET"; + + /// Http DELETE request + public const string Delete = "DELETE"; + + /// Http PUT request + public const string Put = "PUT"; + + /// Http POST request + public const string Post = "POST"; + + /// Http PATCH request + public const string Patch = "PATCH"; + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpExtenstions.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpExtenstions.cs new file mode 100644 index 00000000000..579d8e1232d --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpExtenstions.cs @@ -0,0 +1,51 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Net; +using System.Net.Http; + +namespace Google.Apis.Http +{ + /// + /// Extension methods to and + /// . + /// + public static class HttpExtenstions + { + /// Returns true if the response contains one of the redirect status codes. + internal static bool IsRedirectStatusCode(this HttpResponseMessage message) + { + switch (message.StatusCode) + { + case HttpStatusCode.Moved: + case HttpStatusCode.Redirect: + case HttpStatusCode.RedirectMethod: + case HttpStatusCode.TemporaryRedirect: + return true; + default: + return false; + } + } + + /// A Google.Apis utility method for setting an empty HTTP content. + public static HttpContent SetEmptyContent(this HttpRequestMessage request) + { + request.Content = new ByteArrayContent(new byte[0]); + request.Content.Headers.ContentLength = 0; + return request.Content; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpRequestMessageExtenstions.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpRequestMessageExtenstions.cs new file mode 100644 index 00000000000..b8619b138a0 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpRequestMessageExtenstions.cs @@ -0,0 +1,97 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; + +using System.IO.Compression; + +using Google.Apis.Services; + +namespace Google.Apis.Requests +{ + /// Extension methods to . + static class HttpRequestMessageExtenstions + { + /// + /// Sets the content of the request by the given body and the the required GZip configuration. + /// + /// The request. + /// The service. + /// The body of the future request. If null do nothing. + /// + /// Indicates if the content will be wrapped in a GZip stream, or a regular string stream will be used. + /// + internal static void SetRequestSerailizedContent(this HttpRequestMessage request, + IClientService service, object body, bool gzipEnabled) + { + if (body == null) + { + return; + } + HttpContent content = null; + + var mediaType = "application/" + service.Serializer.Format; + var serializedObject = service.SerializeObject(body); + if (gzipEnabled) + { + content = CreateZipContent(serializedObject); + content.Headers.ContentType = new MediaTypeHeaderValue(mediaType) + { + CharSet = Encoding.UTF8.WebName + }; + } + else + { + content = new StringContent(serializedObject, Encoding.UTF8, mediaType); + } + + request.Content = content; + } + + /// Creates a GZip content based on the given content. + /// Content to GZip. + /// GZiped HTTP content. + internal static HttpContent CreateZipContent(string content) + { + var stream = CreateGZipStream(content); + var sc = new StreamContent(stream); + sc.Headers.ContentEncoding.Add("gzip"); + return sc; + } + + /// Creates a GZip stream by the given serialized object. + private static Stream CreateGZipStream(string serializedObject) + { + byte[] bytes = System.Text.Encoding.UTF8.GetBytes(serializedObject); + using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) + { + using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true)) + { + gzip.Write(bytes, 0, bytes.Length); + } + + // Reset the stream to the beginning. It doesn't work otherwise! + ms.Position = 0; + byte[] compressed = new byte[ms.Length]; + ms.Read(compressed, 0, compressed.Length); + return new MemoryStream(compressed); + } + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpResponseMessageExtensions.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpResponseMessageExtensions.cs new file mode 100644 index 00000000000..de1dc030a1d --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpResponseMessageExtensions.cs @@ -0,0 +1,82 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Google.Apis.Requests; +using Google.Apis.Util; +using Newtonsoft.Json; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Google.Apis.Responses +{ + internal static class HttpResponseMessageExtensions + { + /// + /// Attempts to deserialize a from the . + /// + /// + /// This method will throw a if: + /// + /// The or its are null. + /// Or the deserialization attempt throws a . + /// Or the deserilization attempt returns null. + /// + /// Any exception thrown while reading the + /// will be bubbled up. + /// Otherwie this method will returned the deserialized . + /// + internal static async Task DeserializeErrorAsync(this HttpResponseMessage response, string name, ISerializer serializer) + { + if (response?.Content is null) + { + // We throw GoogleApiException (instead of NullArgumentException) + // as a more friendly way to signal users that something went wrong, + // which in this case is very unlikely to have been because of a bug + // in calling code. + throw new GoogleApiException(name) + { + HttpStatusCode = response?.StatusCode ?? default + }; + } + // If we can't even read the response, let that exception bubble up. + // Note that very likely this will never happen here, as we are usually reading from a buffer. + // If there were actual problems reading the content, HttpClient has already thrown when + // filling the buffer in. + var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + RequestError error; + try + { + error = serializer.Deserialize>(responseText)?.Error; + } + catch (JsonException ex) + { + throw new GoogleApiException(name, message: null, ex) + { + HttpStatusCode = response.StatusCode, + Error = new RequestError { ErrorResponseContent = responseText } + }; + } + if (error is null) + { + throw new GoogleApiException(name) + { + HttpStatusCode = response.StatusCode, + Error = new RequestError { ErrorResponseContent = responseText } + }; + } + error.ErrorResponseContent = responseText; + return error; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IBackOff.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IBackOff.cs new file mode 100644 index 00000000000..32528c4bae4 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IBackOff.cs @@ -0,0 +1,33 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Util +{ + /// Strategy interface to control back-off between retry attempts. + public interface IBackOff + { + /// + /// Gets the a time span to wait before next retry. If the current retry reached the maximum number of retries, + /// the returned value is . + /// + TimeSpan GetNextBackOff(int currentRetry); + + /// Gets the maximum number of retries. + int MaxNumOfRetries { get; } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientService.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientService.cs new file mode 100644 index 00000000000..6d05f14c429 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientService.cs @@ -0,0 +1,92 @@ +/* +Copyright 2010 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +using Google.Apis.Discovery; +using Google.Apis.Http; +using Google.Apis.Requests; + +namespace Google.Apis.Services +{ + /// + /// Client service contains all the necessary information a Google service requires. + /// Each concrete has a reference to a service for + /// important properties like API key, application name, base Uri, etc. + /// This service interface also contains serialization methods to serialize an object to stream and deserialize a + /// stream into an object. + /// + public interface IClientService : IDisposable + { + /// Gets the HTTP client which is used to create requests. + ConfigurableHttpClient HttpClient { get; } + + /// + /// Gets a HTTP client initializer which is able to custom properties on + /// and + /// . + /// + IConfigurableHttpClientInitializer HttpClientInitializer { get; } + + /// Gets the service name. + string Name { get; } + + /// Gets the BaseUri of the service. All request paths should be relative to this URI. + string BaseUri { get; } + + /// Gets the BasePath of the service. + string BasePath { get; } + + /// Gets the supported features by this service. + IList Features { get; } + + /// Gets or sets whether this service supports GZip. + bool GZipEnabled { get; } + + /// Gets the API-Key (DeveloperKey) which this service uses for all requests. + string ApiKey { get; } + + /// Gets the application name to be used in the User-Agent header. + string ApplicationName { get; } + + /// + /// Sets the content of the request by the given body and the this service's configuration. + /// First the body object is serialized by the Serializer and then, if GZip is enabled, the content will be + /// wrapped in a GZip stream, otherwise a regular string stream will be used. + /// + void SetRequestSerailizedContent(HttpRequestMessage request, object body); + + #region Serialization Methods + + /// Gets the Serializer used by this service. + ISerializer Serializer { get; } + + /// Serializes an object into a string representation. + string SerializeObject(object data); + + /// Deserializes a response into the specified object. + Task DeserializeResponse(HttpResponseMessage response); + + /// Deserializes an error response into a object. + /// If no error is found in the response. + Task DeserializeError(HttpResponseMessage response); + + #endregion + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientServiceRequest.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientServiceRequest.cs new file mode 100644 index 00000000000..43c00957d53 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientServiceRequest.cs @@ -0,0 +1,80 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +using Google.Apis.Discovery; +using Google.Apis.Services; + +namespace Google.Apis.Requests +{ + /// A client service request which supports both sync and async execution to get the stream. + public interface IClientServiceRequest + { + /// Gets the name of the method to which this request belongs. + string MethodName { get; } + + /// Gets the rest path of this request. + string RestPath { get; } + + /// Gets the HTTP method of this request. + string HttpMethod { get; } + + /// Gets the parameters information for this specific request. + IDictionary RequestParameters { get; } + + /// Gets the service which is related to this request. + IClientService Service { get; } + + /// Creates a HTTP request message with all path and query parameters, ETag, etc. + /// + /// If null use the service default GZip behavior. Otherwise indicates if GZip is enabled or disabled. + /// + HttpRequestMessage CreateRequest(Nullable overrideGZipEnabled = null); + + /// Executes the request asynchronously and returns the result stream. + Task ExecuteAsStreamAsync(); + + /// Executes the request asynchronously and returns the result stream. + /// A cancellation token to cancel operation. + Task ExecuteAsStreamAsync(CancellationToken cancellationToken); + + /// Executes the request and returns the result stream. + Stream ExecuteAsStream(); + } + + /// + /// A client service request which inherits from and represents a specific + /// service request with the given response type. It supports both sync and async execution to get the response. + /// + public interface IClientServiceRequest : IClientServiceRequest + { + /// Executes the request asynchronously and returns the result object. + Task ExecuteAsync(); + + /// Executes the request asynchronously and returns the result object. + /// A cancellation token to cancel operation. + Task ExecuteAsync(CancellationToken cancellationToken); + + /// Executes the request and returns the result object. + TResponse Execute(); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IClock.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IClock.cs new file mode 100644 index 00000000000..6848b0c1514 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IClock.cs @@ -0,0 +1,56 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Util +{ + /// Clock wrapper for getting the current time. + public interface IClock + { + /// + /// Gets a object that is set to the current date and time on this computer, + /// expressed as the local time. + /// + [Obsolete("System local time is almost always inappropriate to use. If you really need this, call UtcNow and then call ToLocalTime on the result")] + DateTime Now { get; } + + /// + /// Gets a object that is set to the current date and time on this computer, + /// expressed as UTC time. + /// + DateTime UtcNow { get; } + } + + /// + /// A default clock implementation that wraps the + /// and properties. + /// + public class SystemClock : IClock + { + /// Constructs a new system clock. + protected SystemClock() { } + + /// The default instance. + public static readonly IClock Default = new SystemClock(); + + /// + public DateTime Now => DateTime.Now; + + /// + public DateTime UtcNow => DateTime.UtcNow; + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IConfigurableHttpClientInitializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IConfigurableHttpClientInitializer.cs new file mode 100644 index 00000000000..96eb729beed --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IConfigurableHttpClientInitializer.cs @@ -0,0 +1,33 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Google.Apis.Http +{ + /// + /// HTTP client initializer for changing the default behavior of HTTP client. + /// Use this initializer to change default values like timeout and number of tries. + /// + public interface IConfigurableHttpClientInitializer + { + /// Initializes a HTTP client after it was created. + void Initialize(ConfigurableHttpClient httpClient); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IDataStore.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IDataStore.cs new file mode 100644 index 00000000000..6e440bd2923 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IDataStore.cs @@ -0,0 +1,53 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Threading.Tasks; + +namespace Google.Apis.Util.Store +{ + /// + /// Stores and manages data objects, where the key is a string and the value is an object. + /// + /// null keys are not allowed. + /// + /// + public interface IDataStore + { + /// Asynchronously stores the given value for the given key (replacing any existing value). + /// The type to store in the data store. + /// The key. + /// The value to store. + Task StoreAsync(string key, T value); + + /// + /// Asynchronously deletes the given key. The type is provided here as well because the "real" saved key should + /// contain type information as well, so the data store will be able to store the same key for different types. + /// + /// The type to delete from the data store. + /// The key to delete. + Task DeleteAsync(string key); + + /// Asynchronously returns the stored value for the given key or null if not found. + /// The type to retrieve from the data store. + /// The key to retrieve its value. + /// The stored object. + Task GetAsync(string key); + + /// Asynchronously clears all values in the data store. + Task ClearAsync(); + } +} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IDirectResponseSchema.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IDirectResponseSchema.cs new file mode 100644 index 00000000000..6415dca8693 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IDirectResponseSchema.cs @@ -0,0 +1,36 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Newtonsoft.Json; + +namespace Google.Apis.Requests +{ + /// + /// Interface containing additional response-properties which will be added to every schema type which is + /// a direct response to a request. + /// + public interface IDirectResponseSchema + { + /// + /// The e-tag of this response. + /// + /// + /// Will be set by the service deserialization method, + /// or the by json response parser if implemented on service. + /// + string ETag { get; set; } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpClientFactory.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpClientFactory.cs new file mode 100644 index 00000000000..bde5e95420a --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpClientFactory.cs @@ -0,0 +1,51 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Collections.Generic; + +namespace Google.Apis.Http +{ + /// Arguments for creating a HTTP client. + public class CreateHttpClientArgs + { + /// Gets or sets whether GZip is enabled. + public bool GZipEnabled { get; set; } + + /// Gets or sets the application name that is sent in the User-Agent header. + public string ApplicationName { get; set; } + + /// Gets a list of initializers to initialize the HTTP client instance. + public IList Initializers { get; private set; } + + /// Gets or sets the value for the x-goog-api-client header + public string GoogleApiClientHeader { get; set; } + + /// Constructs a new argument instance. + public CreateHttpClientArgs() + { + Initializers = new List(); + } + } + + /// + /// HTTP client factory creates configurable HTTP clients. A unique HTTP client should be created for each service. + /// + public interface IHttpClientFactory + { + /// Creates a new configurable HTTP client. + ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpExceptionHandler.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpExceptionHandler.cs new file mode 100644 index 00000000000..2ad9cf11502 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpExceptionHandler.cs @@ -0,0 +1,63 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Http +{ + /// Argument class to . + public class HandleExceptionArgs + { + /// Gets or sets the sent request. + public HttpRequestMessage Request { get; set; } + + /// Gets or sets the exception which occurred during sending the request. + public Exception Exception { get; set; } + + /// Gets or sets the total number of tries to send the request. + public int TotalTries { get; set; } + + /// Gets or sets the current failed try. + public int CurrentFailedTry { get; set; } + + /// Gets an indication whether a retry will occur if the handler returns true. + public bool SupportsRetry + { + get { return TotalTries - CurrentFailedTry > 0; } + } + + /// Gets or sets the request's cancellation token. + public CancellationToken CancellationToken { get; set; } + } + + /// Exception handler is invoked when an exception is thrown during a HTTP request. + public interface IHttpExceptionHandler + { + /// + /// Handles an exception thrown when sending a HTTP request. + /// A simple rule must be followed, if you modify the request object in a way that the exception can be + /// resolved, you must return true. + /// + /// + /// Handle exception argument which properties such as the request, exception, current failed try. + /// + /// Whether this handler has made a change that requires the request to be resent. + Task HandleExceptionAsync(HandleExceptionArgs args); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpExecuteInterceptor.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpExecuteInterceptor.cs new file mode 100644 index 00000000000..3ffcbaa4080 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpExecuteInterceptor.cs @@ -0,0 +1,36 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Http +{ + /// + /// HTTP request execute interceptor to intercept a before it has + /// been sent. Sample usage is attaching "Authorization" header to a request. + /// + public interface IHttpExecuteInterceptor + { + /// + /// Invoked before the request is being sent. + /// + /// The HTTP request message. + /// Cancellation token to cancel the operation. + Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpUnsuccessfulResponseHandler.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpUnsuccessfulResponseHandler.cs new file mode 100644 index 00000000000..3c0b9a11347 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpUnsuccessfulResponseHandler.cs @@ -0,0 +1,65 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Http +{ + /// Argument class to . + public class HandleUnsuccessfulResponseArgs + { + /// Gets or sets the sent request. + public HttpRequestMessage Request { get; set; } + + /// Gets or sets the abnormal response. + public HttpResponseMessage Response { get; set; } + + /// Gets or sets the total number of tries to send the request. + public int TotalTries { get; set; } + + /// Gets or sets the current failed try. + public int CurrentFailedTry { get; set; } + + /// Gets an indication whether a retry will occur if the handler returns true. + public bool SupportsRetry + { + get { return TotalTries - CurrentFailedTry > 0; } + } + + /// Gets or sets the request's cancellation token. + public CancellationToken CancellationToken { get; set; } + } + + /// + /// Unsuccessful response handler which is invoked when an abnormal HTTP response is returned when sending a HTTP + /// request. + /// + public interface IHttpUnsuccessfulResponseHandler + { + /// + /// Handles an abnormal response when sending a HTTP request. + /// A simple rule must be followed, if you modify the request object in a way that the abnormal response can + /// be resolved, you must return true. + /// + /// + /// Handle response argument which contains properties such as the request, response, current failed try. + /// + /// Whether this handler has made a change that requires the request to be resent. + Task HandleResponseAsync(HandleUnsuccessfulResponseArgs args); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IJsonSerializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IJsonSerializer.cs new file mode 100644 index 00000000000..83e0cf0a9c9 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IJsonSerializer.cs @@ -0,0 +1,23 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +namespace Google.Apis.Json +{ + /// Represents a JSON serializer. + public interface IJsonSerializer : ISerializer + { + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ILogger.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ILogger.cs new file mode 100644 index 00000000000..dbdbbd2d8e5 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ILogger.cs @@ -0,0 +1,62 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Logging +{ + /// Describes a logging interface which is used for outputting messages. + public interface ILogger + { + /// Gets an indication whether debug output is logged or not. + bool IsDebugEnabled { get; } + + /// Returns a logger which will be associated with the specified type. + /// Type to which this logger belongs. + /// A type-associated logger. + ILogger ForType(Type type); + + /// Returns a logger which will be associated with the specified type. + /// A type-associated logger. + ILogger ForType(); + + /// Logs a debug message. + /// The message to log. + /// String.Format arguments (if applicable). + void Debug(string message, params object[] formatArgs); + + /// Logs an info message. + /// The message to log. + /// String.Format arguments (if applicable). + void Info(string message, params object[] formatArgs); + + /// Logs a warning. + /// The message to log. + /// String.Format arguments (if applicable). + void Warning(string message, params object[] formatArgs); + + /// Logs an error message resulting from an exception. + /// + /// The message to log. + /// String.Format arguments (if applicable). + void Error(Exception exception, string message, params object[] formatArgs); + + /// Logs an error message. + /// The message to log. + /// String.Format arguments (if applicable). + void Error(string message, params object[] formatArgs); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IParameter.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IParameter.cs new file mode 100644 index 00000000000..6ae191b186a --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IParameter.cs @@ -0,0 +1,39 @@ +/* +Copyright 2010 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Collections.Generic; + +namespace Google.Apis.Discovery +{ + /// Represents a parameter for a method. + public interface IParameter + { + /// Gets the name of the parameter. + string Name { get; } + + /// Gets the pattern that this parameter must follow. + string Pattern { get; } + + /// Gets an indication whether this parameter is optional or required. + bool IsRequired { get; } + + /// Gets the default value of this parameter. + string DefaultValue { get; } + + /// Gets the type of the parameter. + string ParameterType { get; } + } +} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ISerializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ISerializer.cs new file mode 100644 index 00000000000..ad296e1f84e --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ISerializer.cs @@ -0,0 +1,43 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.IO; + +namespace Google.Apis +{ + /// Serialization interface that supports serialize and deserialize methods. + public interface ISerializer + { + /// Gets the application format this serializer supports (e.g. "json", "xml", etc.). + string Format { get; } + + /// Serializes the specified object into a Stream. + void Serialize(object obj, Stream target); + + /// Serializes the specified object into a string. + string Serialize(object obj); + + /// Deserializes the string into an object. + T Deserialize(string input); + + /// Deserializes the string into an object. + object Deserialize(string input, Type type); + + /// Deserializes the stream into an object. + T Deserialize(Stream input); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNull.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNull.cs new file mode 100644 index 00000000000..22ca29405d7 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNull.cs @@ -0,0 +1,100 @@ +/* +Copyright 2010 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Google.Apis.Json +{ + /// + /// Provides values which are explicitly expressed as null when converted to JSON. + /// + public static class JsonExplicitNull + { + /// + /// Get an that is explicitly expressed as null when converted to JSON. + /// + /// An that is explicitly expressed as null when converted to JSON. + public static IList ForIList() => ExplicitNullList.Instance; + + [JsonExplicitNull] + private sealed class ExplicitNullList : IList + { + public static ExplicitNullList Instance = new ExplicitNullList(); + + public T this[int index] + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public int Count { get { throw new NotSupportedException(); } } + + public bool IsReadOnly { get { throw new NotSupportedException(); } } + + public void Add(T item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(T item) + { + throw new NotSupportedException(); + } + + public void CopyTo(T[] array, int arrayIndex) + { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotSupportedException(); + } + + public int IndexOf(T item) + { + throw new NotSupportedException(); + } + + public void Insert(int index, T item) + { + throw new NotSupportedException(); + } + + public bool Remove(T item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotSupportedException(); + } + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNullAttribute.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNullAttribute.cs new file mode 100644 index 00000000000..6a4da6a7cd0 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNullAttribute.cs @@ -0,0 +1,26 @@ +/* +Copyright 2016 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Json +{ + /// + /// All values of a type with this attribute are represented as a literal null in JSON. + /// + [AttributeUsage(AttributeTargets.Class)] + public class JsonExplicitNullAttribute : Attribute { } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/MaxUrlLengthInterceptor.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/MaxUrlLengthInterceptor.cs new file mode 100644 index 00000000000..324ba399b41 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/MaxUrlLengthInterceptor.cs @@ -0,0 +1,72 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +using Google.Apis.Testing; + +namespace Google.Apis.Http +{ + /// + /// Intercepts HTTP GET requests with a URLs longer than a specified maximum number of characters. + /// The interceptor will change such requests as follows: + /// + /// The request's method will be changed to POST + /// A X-HTTP-Method-Override header will be added with the value GET + /// Any query parameters from the URI will be moved into the body of the request. + /// If query parameters are moved, the content type is set to application/x-www-form-urlencoded + /// + /// + [VisibleForTestOnly] + public class MaxUrlLengthInterceptor : IHttpExecuteInterceptor + { + private readonly uint maxUrlLength; + + ///Constructs a new Max URL length interceptor with the given max length. + public MaxUrlLengthInterceptor(uint maxUrlLength) + { + this.maxUrlLength = maxUrlLength; + } + + /// + public Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (request.Method != HttpMethod.Get || request.RequestUri.AbsoluteUri.Length <= maxUrlLength) + { + return Task.FromResult(0); + } + // Change the method to POST. + request.Method = HttpMethod.Post; + var query = request.RequestUri.Query; + if (!String.IsNullOrEmpty(query)) + { + // Move query parameters to the body (without the "?"). + request.Content = new StringContent(query.Substring(1)); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + var requestString = request.RequestUri.ToString(); + // The new request URI is the old one minus the "?" and everything that follows, since we moved the + // query params to the body. For example: "www.example.com/?q=foo" => "www.example.com/". + request.RequestUri = new Uri(requestString.Remove(requestString.IndexOf("?"))); + } + request.Headers.Add("X-HTTP-Method-Override", "GET"); + return Task.FromResult(0); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/NewtonsoftJsonSerializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/NewtonsoftJsonSerializer.cs new file mode 100644 index 00000000000..0c71c925c34 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/NewtonsoftJsonSerializer.cs @@ -0,0 +1,231 @@ +/* +Copyright 2017 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Google.Apis.Util; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.IO; +using System.Reflection; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Json +{ + /// + /// A JSON converter which honers RFC 3339 and the serialized date is accepted by Google services. + /// + public class RFC3339DateTimeConverter : JsonConverter + { + /// + public override bool CanRead => false; + + /// + public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + throw new NotImplementedException("Unnecessary because CanRead is false."); + } + + /// + public override bool CanConvert(Type objectType) => + // Convert DateTime only. + objectType == typeof(DateTime) || objectType == typeof(Nullable); + + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value != null) + { + DateTime date = (DateTime)value; + serializer.Serialize(writer, Utilities.ConvertToRFC3339(date)); + } + } + } + + /// + /// A JSON converter to write null literals into JSON when explicitly requested. + /// + public class ExplicitNullConverter : JsonConverter + { + /// + public override bool CanRead => false; + + /// + public override bool CanConvert(Type objectType) => objectType.GetTypeInfo().GetCustomAttributes(typeof(JsonExplicitNullAttribute), false).Any(); + + /// + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException("Unnecessary because CanRead is false."); + } + + /// + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteNull(); + } + + /// + /// A JSON contract resolver to apply and as necessary. + /// + /// + /// Using a contract resolver is recommended in the Json.NET performance tips: https://www.newtonsoft.com/json/help/html/Performance.htm#JsonConverters + /// + public class NewtonsoftJsonContractResolver : DefaultContractResolver + { + private static readonly JsonConverter DateTimeConverter = new RFC3339DateTimeConverter(); + private static readonly JsonConverter ExplicitNullConverter = new ExplicitNullConverter(); + + /// + protected override JsonContract CreateContract(Type objectType) + { + JsonContract contract = base.CreateContract(objectType); + + if (DateTimeConverter.CanConvert(objectType)) + { + contract.Converter = DateTimeConverter; + } + else if (ExplicitNullConverter.CanConvert(objectType)) + { + contract.Converter = ExplicitNullConverter; + } + + return contract; + } + } + + /// Class for serialization and deserialization of JSON documents using the Newtonsoft Library. + public class NewtonsoftJsonSerializer : IJsonSerializer + { + private readonly JsonSerializerSettings settings; + private readonly JsonSerializer serializer; + + /// The default instance of the Newtonsoft JSON Serializer, with default settings. + public static NewtonsoftJsonSerializer Instance { get; } = new NewtonsoftJsonSerializer(); + + /// + /// Constructs a new instance with the default serialization settings, equivalent to . + /// + public NewtonsoftJsonSerializer() : this(CreateDefaultSettings()) + { + } + + /// + /// Constructs a new instance with the given settings. + /// + /// The settings to apply when serializing and deserializing. Must not be null. + public NewtonsoftJsonSerializer(JsonSerializerSettings settings) + { + Utilities.ThrowIfNull(settings, nameof(settings)); + this.settings = settings; + serializer = JsonSerializer.Create(settings); + } + + /// + /// Creates a new instance of with the same behavior + /// as the ones used in . This method is expected to be used to construct + /// settings which are then passed to . + /// + /// A new set of default settings. + public static JsonSerializerSettings CreateDefaultSettings() => + new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + ContractResolver = new NewtonsoftJsonContractResolver() + }; + + /// + public string Format => "json"; + + /// + public void Serialize(object obj, Stream target) + { + using (var writer = new StreamWriter(target)) + { + if (obj == null) + { + obj = string.Empty; + } + serializer.Serialize(writer, obj); + } + } + + /// + public string Serialize(object obj) + { + using (TextWriter tw = new StringWriter()) + { + if (obj == null) + { + obj = string.Empty; + } + serializer.Serialize(tw, obj); + return tw.ToString(); + } + } + + /// + public T Deserialize(string input) + { + if (string.IsNullOrEmpty(input)) + { + return default(T); + } + return JsonConvert.DeserializeObject(input, settings); + } + + /// + public object Deserialize(string input, Type type) + { + if (string.IsNullOrEmpty(input)) + { + return null; + } + return JsonConvert.DeserializeObject(input, type, settings); + } + + /// + public T Deserialize(Stream input) + { + // Convert the JSON document into an object. + using (StreamReader streamReader = new StreamReader(input)) + { + return (T)serializer.Deserialize(streamReader, typeof(T)); + } + } + + /// + /// Deserializes the given stream but reads from it asynchronously, observing the given cancellation token. + /// Note that this means the complete JSON is read before it is deserialized into objects. + /// + /// The type to convert to. + /// The stream to read from. + /// Cancellation token for the operation. + /// The deserialized object. + public async Task DeserializeAsync(Stream input, CancellationToken cancellationToken) + { + using (StreamReader streamReader = new StreamReader(input)) + { + string json = await streamReader.ReadToEndAsync().WithCancellationToken(cancellationToken).ConfigureAwait(false); + using (var reader = new JsonTextReader(new StringReader(json))) + { + return (T) serializer.Deserialize(reader, typeof(T)); + } + } + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/NullLogger.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/NullLogger.cs new file mode 100644 index 00000000000..79ba38475f8 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/NullLogger.cs @@ -0,0 +1,59 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Logging +{ + /// + /// Represents a NullLogger which does not do any logging. + /// + public class NullLogger : ILogger + { + /// + public bool IsDebugEnabled + { + get { return false; } + } + + /// + public ILogger ForType(Type type) + { + return new NullLogger(); + } + + /// + public ILogger ForType() + { + return new NullLogger(); + } + + /// + public void Info(string message, params object[] formatArgs) {} + + /// + public void Warning(string message, params object[] formatArgs) {} + + /// + public void Debug(string message, params object[] formatArgs) {} + + /// + public void Error(Exception exception, string message, params object[] formatArgs) {} + + /// + public void Error(string message, params object[] formatArgs) {} + } +} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterCollection.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterCollection.cs new file mode 100644 index 00000000000..c3c0d85ec3c --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterCollection.cs @@ -0,0 +1,166 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections; +using System.Collections.Generic; + +using Google.Apis.Util; + +namespace Google.Apis.Requests.Parameters +{ + /// A collection of parameters (key value pairs). May contain duplicate keys. + public class ParameterCollection : List> + { + /// Constructs a new parameter collection. + public ParameterCollection() : base() { } + + /// Constructs a new parameter collection from the given collection. + public ParameterCollection(IEnumerable> collection) : base(collection) { } + + /// Adds a single parameter to this collection. + public void Add(string key, string value) + { + Add(new KeyValuePair(key, value)); + } + + /// Returns true if this parameter is set within the collection. + public bool ContainsKey(string key) + { + key.ThrowIfNullOrEmpty("key"); + string value; + return TryGetValue(key, out value); + } + + /// + /// Tries to find the a key within the specified key value collection. Returns true if the key was found. + /// If a pair was found the out parameter value will contain the value of that pair. + /// + public bool TryGetValue(string key, out string value) + { + key.ThrowIfNullOrEmpty("key"); + + foreach (KeyValuePair pair in this) + { + // Check if this pair matches the specified key name. + if (pair.Key.Equals(key)) + { + value = pair.Value; + return true; + } + } + + // No result found. + value = null; + return false; + } + + /// + /// Returns the value of the first matching key, or throws a KeyNotFoundException if the parameter is not + /// present within the collection. + /// + public string GetFirstMatch(string key) + { + string val; + if (!TryGetValue(key, out val)) + { + throw new KeyNotFoundException("Parameter with the name '" + key + "' was not found."); + } + return val; + } + + /// + /// Returns all matches for the specified key. May return an empty enumeration if the key is not present. + /// + public IEnumerable GetAllMatches(string key) + { + key.ThrowIfNullOrEmpty("key"); + + foreach (KeyValuePair pair in this) + { + // Check if this pair matches the specified key name. + if (pair.Key.Equals(key)) + { + yield return pair.Value; + } + } + } + + /// + /// Returns all matches for the specified key. May return an empty enumeration if the key is not present. + /// + public IEnumerable this[string key] + { + get { return GetAllMatches(key); } + } + + /// + /// Creates a parameter collection from the specified URL encoded query string. + /// Example: + /// The query string "foo=bar&chocolate=cookie" would result in two parameters (foo and bar) + /// with the values "bar" and "cookie" set. + /// + public static ParameterCollection FromQueryString(string qs) + { + var collection = new ParameterCollection(); + var qsParam = qs.Split('&'); + foreach (var param in qsParam) + { + // Split the parameter into key and value. + var info = param.Split(new[] { '=' }); + if (info.Length == 2) + { + collection.Add(Uri.UnescapeDataString(info[0]), Uri.UnescapeDataString(info[1])); + } + else + { + throw new ArgumentException(string.Format( + "Invalid query string [{0}]. Invalid part [{1}]", qs, param)); + } + } + + return collection; + } + + /// + /// Creates a parameter collection from the specified dictionary. + /// If the value is an enumerable, a parameter pair will be added for each value. + /// Otherwise the value will be converted into a string using the .ToString() method. + /// + public static ParameterCollection FromDictionary(IDictionary dictionary) + { + var collection = new ParameterCollection(); + foreach (KeyValuePair pair in dictionary) + { + // Try parsing the value of the pair as an enumerable. + var valueAsEnumerable = pair.Value as IEnumerable; + if (!(pair.Value is string) && valueAsEnumerable != null) + { + foreach (var value in valueAsEnumerable) + { + collection.Add(pair.Key, Util.Utilities.ConvertToString(value)); + } + } + else + { + // Otherwise just convert it to a string. + collection.Add(pair.Key, pair.Value == null ? null : Util.Utilities.ConvertToString(pair.Value)); + } + } + return collection; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterUtils.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterUtils.cs new file mode 100644 index 00000000000..2cffd2e6691 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterUtils.cs @@ -0,0 +1,177 @@ +/* +Copyright 2013 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Linq; +using System.Reflection; + +using Google.Apis.Logging; +using Google.Apis.Util; + +namespace Google.Apis.Requests.Parameters +{ + /// + /// Utility class for iterating on properties in a request object. + /// + public static class ParameterUtils + { + private static readonly ILogger Logger = ApplicationContext.Logger.ForType(typeof(ParameterUtils)); + + /// + /// Creates a with all the specified parameters in + /// the input request. It uses reflection to iterate over all properties with + /// attribute. + /// + /// + /// A request object which contains properties with + /// attribute. Those properties will be serialized + /// to the returned . + /// + /// + /// A which contains the all the given object required + /// values. + /// + public static FormUrlEncodedContent CreateFormUrlEncodedContent(object request) + { + IList> list = new List>(); + IterateParameters(request, (type, name, value) => + { + list.Add(new KeyValuePair(name, value.ToString())); + }); + return new FormUrlEncodedContent(list); + } + + /// + /// Creates a parameter dictionary by using reflection to iterate over all properties with + /// attribute. + /// + /// + /// A request object which contains properties with + /// attribute. Those properties will be set + /// in the output dictionary. + /// + public static IDictionary CreateParameterDictionary(object request) + { + var dict = new Dictionary(); + IterateParameters(request, (type, name, value) => + { + if (dict.TryGetValue(name, out var existingValue)) + { + // Repeated enum query parameters end up with two properties: a single + // one, and a Repeatable (where the T is always non-nullable, whether or not the parameter + // is optional). If both properties are set, we fail. Note that this delegate is called + // for nullable enum properties with a null value, annoyingly... if that happens and we then + // see a non-null value, we'll overwrite it. If that happens when we've already got a non-null + // value, we'll ignore it. + if (existingValue is null && value is object) + { + // Overwrite null value with non-null value + dict[name] = value; + } + else if (value is null) + { + // Ignore new null value + } + else + { + // Throw if we see a second null value + throw new InvalidOperationException( + $"The query parameter '{name}' is set by multiple properties. For repeated enum query parameters, ensure that only one property is set to a non-null value."); + } + } + else + { + dict.Add(name, value); + } + }); + return dict; + } + + /// + /// Sets query parameters in the given builder with all all properties with the + /// attribute. + /// + /// The request builder + /// + /// A request object which contains properties with + /// attribute. Those properties will be set in the + /// given request builder object + /// + public static void InitParameters(RequestBuilder builder, object request) + { + IterateParameters(request, (type, name, value) => + { + builder.AddParameter(type, name, value.ToString()); + }); + } + + /// + /// Iterates over all properties in the request + /// object and invokes the specified action for each of them. + /// + /// A request object + /// An action to invoke which gets the parameter type, name and its value + private static void IterateParameters(object request, Action action) + { + // Use reflection to build the parameter dictionary. + foreach (PropertyInfo property in request.GetType().GetProperties(BindingFlags.Instance | + BindingFlags.Public)) + { + // Retrieve the RequestParameterAttribute. + RequestParameterAttribute attribute = + property.GetCustomAttributes(typeof(RequestParameterAttribute), false).FirstOrDefault() as + RequestParameterAttribute; + if (attribute == null) + { + continue; + } + + // Get the name of this parameter from the attribute, if it doesn't exist take a lower-case variant of + // property name. + string name = attribute.Name ?? property.Name.ToLower(); + + var propertyType = property.PropertyType; + var value = property.GetValue(request, null); + + // Call action with the type name and value. + if (propertyType.GetTypeInfo().IsValueType || value != null) + { + if (attribute.Type == RequestParameterType.UserDefinedQueries) + { + if (typeof(IEnumerable>).IsAssignableFrom(value.GetType())) + { + foreach (var pair in (IEnumerable>)value) + { + action(RequestParameterType.Query, pair.Key, pair.Value); + } + } + else + { + Logger.Warning("Parameter marked with RequestParameterType.UserDefinedQueries attribute " + + "was not of type IEnumerable> and will be skipped."); + } + } + else + { + action(attribute.Type, name, value); + } + } + } + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterValidator.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterValidator.cs new file mode 100644 index 00000000000..3c634ed504e --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterValidator.cs @@ -0,0 +1,79 @@ +/* +Copyright 2010 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Linq; +using System.Text.RegularExpressions; + +using Google.Apis.Discovery; +using Google.Apis.Testing; +using Google.Apis.Util; + +namespace Google.Apis.Requests.Parameters +{ + /// Logic for validating a parameter. + public static class ParameterValidator + { + /// Validates a parameter value against the methods regex. + [VisibleForTestOnly] + [Obsolete("Use ValidateParameter instead")] + public static bool ValidateRegex(IParameter param, string paramValue) => ValidateRegex(param, paramValue, out _); + + /// Validates a parameter value against the methods regex. + [VisibleForTestOnly] + internal static bool ValidateRegex(IParameter param, string paramValue, out string error) + { + if (string.IsNullOrEmpty(param.Pattern) || new Regex(param.Pattern).IsMatch(paramValue)) + { + error = null; + return true; + } + else + { + error = $"The value did not match the regular expression {param.Pattern}"; + return false; + } + } + + /// Validates if a parameter is valid. + [Obsolete("Use the overload with error output instead")] + public static bool ValidateParameter(IParameter parameter, string value) => ValidateParameter(parameter, value, out _); + + /// Validates if a parameter is valid. + public static bool ValidateParameter(IParameter parameter, string value, out string error) + { + if (string.IsNullOrEmpty(value)) + { + // Fail if a required parameter is not present. + if (parameter.IsRequired) + { + error = "The parameter value must be a non-empty string"; + return false; + } + else + { + error = null; + return true; + } + } + else + { + // The parameter has value so validate the regex. + return ValidateRegex(parameter, value, out error); + } + } + } +} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestBuilder.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestBuilder.cs new file mode 100644 index 00000000000..5a4d5ea3f91 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestBuilder.cs @@ -0,0 +1,335 @@ +/* +Copyright 2012 Google Inc + +Licensed under the Apache License, Version 2.0(the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; + +using Google.Apis.Http; +using Google.Apis.Logging; +using Google.Apis.Util; + +namespace Google.Apis.Requests +{ + /// Utility class for building a URI using or a HTTP request using + /// from the query and path parameters of a REST call. + public class RequestBuilder + { + static RequestBuilder() + { + UriPatcher.PatchUriQuirks(); + } + + private static readonly ILogger Logger = ApplicationContext.Logger.ForType(); + + /// Pattern to get the groups that are part of the path. + private static Regex PathParametersPattern = new Regex(@"{[^{}]*}*"); + + /// Supported HTTP methods. + private static IEnumerable SupportedMethods = new List + { + HttpConsts.Get, HttpConsts.Post, HttpConsts.Put, HttpConsts.Delete, HttpConsts.Patch + }; + + /// + /// A dictionary containing the parameters which will be inserted into the path of the URI. These parameters + /// will be substituted into the URI path where the path contains "{key}". See + /// http://tools.ietf.org/html/rfc6570 for more information. + /// + private IDictionary> PathParameters { get; set; } + + /// + /// A dictionary containing the parameters which will apply to the query portion of this request. + /// + private List> QueryParameters { get; set; } + + /// The base URI for this request (usually applies to the service itself). + public Uri BaseUri { get; set; } + + /// + /// The path portion of this request. It's appended to the and the parameters are + /// substituted from the dictionary. + /// + public string Path { get; set; } + + /// The HTTP method used for this request. + private string method; + + /// The HTTP method used for this request (such as GET, PUT, POST, etc...). + /// The default Value is . + public string Method + { + get { return method; } + set + { + if (!SupportedMethods.Contains(value)) + throw new ArgumentOutOfRangeException("Method"); + method = value; + } + } + + /// Construct a new request builder. + /// TODO(peleyal): Consider using the Factory pattern here. + public RequestBuilder() + { + PathParameters = new Dictionary>(); + QueryParameters = new List>(); + Method = HttpConsts.Get; + } + + /// Constructs a Uri as defined by the parts of this request builder. + public Uri BuildUri() + { + var restPath = BuildRestPath(); + + if (QueryParameters.Count > 0) + { + // In case the path already contains '?' - we should add '&'. Otherwise add '?'. + restPath.Append(restPath.ToString().Contains("?") ? "&" : "?"); + + // If parameter value is empty - just add the "name", otherwise "name=value" + restPath.Append(String.Join("&", QueryParameters.Select( + x => string.IsNullOrEmpty(x.Value) ? + Uri.EscapeDataString(x.Key) : + String.Format("{0}={1}", Uri.EscapeDataString(x.Key), Uri.EscapeDataString(x.Value))) + .ToArray())); + } + + return UriJoin(this.BaseUri, restPath.ToString()); + } + + private Uri UriJoin(Uri baseUri, string path) + { + // This emulates the subset of behaviour we require from Uri(Uri, string). + // Except it does not treat ':' as a special, scheme-indicating, character in path. + if (path == "") + { + return baseUri; + } + var baseUriStr = baseUri.AbsoluteUri; + if (path.StartsWith("/")) + { + // Path is absolute; remove any path on the base URI. + baseUriStr = Regex.Replace(baseUriStr, "^([^:]+://[^/]+)/.*$", "$1"); + } + else + { + if (!path.StartsWith("?") && !path.StartsWith("#")) + { + while (!baseUriStr.EndsWith("/")) + { + baseUriStr = baseUriStr.Substring(0, baseUriStr.Length - 1); + } + } + } + return new Uri(baseUriStr + path); + } + + /// Operator list that can appear in the path argument. + private const string OPERATORS = "+#./;?&|!@="; + + /// + /// Builds the REST path string builder based on and the URI template spec + /// http://tools.ietf.org/html/rfc6570. + /// + /// + private StringBuilder BuildRestPath() + { + if (string.IsNullOrEmpty(Path)) + { + return new StringBuilder(string.Empty); + } + + var restPath = new StringBuilder(Path); + var matches = PathParametersPattern.Matches(restPath.ToString()); + foreach (var match in matches) + { + var matchStr = match.ToString(); + // Strip the first and last characters: '{' and '}'. + var content = matchStr.Substring(1, matchStr.Length - 2); + + var op = string.Empty; + // If the content's first character is an operator, save and remove it from the content string. + if (OPERATORS.Contains(content[0].ToString())) + { + op = content[0].ToString(); + content = content.Substring(1); + } + + var newContent = new StringBuilder(); + + // Iterate over all possible parameters. + var parameters = content.Split(','); + for (var index = 0; index < parameters.Length; ++index) + { + var parameter = parameters[index]; + + var parameterName = parameter; + var containStar = false; + var numOfChars = 0; + + // Check if it ends with '*'. + if (parameterName[parameterName.Length - 1] == '*') + { + containStar = true; + parameterName = parameterName.Substring(0, parameterName.Length - 1); + } + // Check if it contains :n which means we should only use the first n characters of this parameter. + if (parameterName.Contains(":")) + { + if (!int.TryParse(parameterName.Substring(parameterName.IndexOf(":") + 1), out numOfChars)) + { + throw new ArgumentException( + string.Format("Can't parse number after ':' in Path \"{0}\". Parameter is \"{1}\"", + Path, parameterName), Path); + } + parameterName = parameterName.Substring(0, parameterName.IndexOf(":")); + } + + // We can improve the following if statement, but for readability we will leave it like that. + var joiner = op; + var start = op; + switch (op) + { + case "+": + start = index == 0 ? "" : ","; + joiner = ","; + break; + case ".": + if (!containStar) + { + joiner = ","; + } + break; + case "/": + if (!containStar) + { + joiner = ","; + } + break; + case "#": + start = index == 0 ? "#" : ","; + joiner = ","; + break; + + case "?": + start = (index == 0 ? "?" : "&") + parameterName + "="; + joiner = ","; + if (containStar) + { + joiner = "&" + parameterName + "="; + } + break; + case "&": + case ";": + start = op + parameterName + "="; + joiner = ","; + if (containStar) + { + joiner = op + parameterName + "="; + } + break; + // No operator, in that case just ','. + default: + if (index > 0) + { + start = ","; + } + joiner = ","; + break; + } + + // Check if a path parameter equals the name which appears in the REST path. + if (PathParameters.ContainsKey(parameterName)) + { + var value = string.Join(joiner, PathParameters[parameterName]); + + // Check if we need to use a substring of the value. + if (numOfChars != 0 && numOfChars < value.Length) + { + value = value.Substring(0, numOfChars); + } + + if (op != "+" && op != "#" && PathParameters[parameterName].Count == 1) + { + value = Uri.EscapeDataString(value); + } + + value = start + value; + newContent.Append(value); + } + else + { + throw new ArgumentException( + string.Format("Path \"{0}\" misses a \"{1}\" parameter", Path, parameterName), Path); + } + } + + if (op == ";") + { + if (newContent[newContent.Length - 1] == '=') + { + newContent = newContent.Remove(newContent.Length - 1, 1); + } + newContent = newContent.Replace("=;", ";"); + } + restPath = restPath.Replace(matchStr, newContent.ToString()); + } + return restPath; + } + + /// Adds a parameter value. + /// Type of the parameter (must be 'Path' or 'Query'). + /// Parameter name. + /// Parameter value. + public void AddParameter(RequestParameterType type, string name, string value) + { + name.ThrowIfNull("name"); + if (value == null) + { + Logger.Warning("Add parameter should not get null values. type={0}, name={1}", type, name); + return; + } + switch (type) + { + case RequestParameterType.Path: + if (!PathParameters.ContainsKey(name)) + { + PathParameters[name] = new List { value }; + } + else + { + PathParameters[name].Add(value); + } + break; + case RequestParameterType.Query: + QueryParameters.Add(new KeyValuePair(name, value)); + break; + default: + throw new ArgumentOutOfRangeException("type"); + } + } + + /// Creates a new HTTP request message. + public HttpRequestMessage CreateRequest() + { + return new HttpRequestMessage(new HttpMethod(Method), BuildUri()); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestError.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestError.cs new file mode 100644 index 00000000000..eca7f1480d6 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestError.cs @@ -0,0 +1,95 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Collections.Generic; +using System.Text; + +using Google.Apis.Util; + +namespace Google.Apis.Requests +{ + /// + /// Collection of server errors + /// + public class RequestError + { + /// + /// Enumeration of known error codes which may occur during a request. + /// + public enum ErrorCodes + { + /// + /// The ETag condition specified caused the ETag verification to fail. + /// Depending on the ETagAction of the request this either means that a change to the object has been + /// made on the server, or that the object in question is still the same and has not been changed. + /// + ETagConditionFailed = 412 + } + + /// + /// Contains a list of all errors + /// + public IList Errors { get; set; } + + /// + /// The error code returned + /// + public int Code { get; set; } + + /// + /// The error message returned + /// + public string Message { get; set; } + + /// + /// The full content of the error response that + /// this instance was created from. + /// + /// + /// The response may contain custom information that is not represented + /// by any of the properties in . + /// + public string ErrorResponseContent { get; set; } + + internal bool IsOnlyRawContent => + Message is null && Code == 0 && Errors.IsNullOrEmpty(); + + /// + /// Returns a string summary of this error + /// + /// A string summary of this error + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(GetType().FullName).Append(Message).AppendFormat(" [{0}]", Code).AppendLine(); + if (Errors.IsNullOrEmpty()) + { + sb.AppendLine("No individual errors"); + } + else + { + sb.AppendLine("Errors ["); + foreach (SingleError err in Errors) + { + sb.Append('\t').AppendLine(err.ToString()); + } + sb.AppendLine("]"); + } + + return sb.ToString(); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestParameterAttribute.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestParameterAttribute.cs new file mode 100644 index 00000000000..b4c08668b6b --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestParameterAttribute.cs @@ -0,0 +1,82 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Util +{ + /// + /// An attribute which is used to specially mark a property for reflective purposes, + /// assign a name to the property and indicate it's location in the request as either + /// in the path or query portion of the request URL. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public class RequestParameterAttribute : Attribute + { + private readonly string name; + private readonly RequestParameterType type; + + /// Gets the name of the parameter. + public string Name { get { return name; } } + + /// Gets the type of the parameter, Path or Query. + public RequestParameterType Type { get { return type; } } + + /// + /// Constructs a new property attribute to be a part of a REST URI. + /// This constructor uses as the parameter's type. + /// + /// + /// The name of the parameter. If the parameter is a path parameter this name will be used to substitute the + /// string value into the path, replacing {name}. If the parameter is a query parameter, this parameter will be + /// added to the query string, in the format "name=value". + /// + public RequestParameterAttribute(string name) + : this(name, RequestParameterType.Query) + { + + } + + /// Constructs a new property attribute to be a part of a REST URI. + /// + /// The name of the parameter. If the parameter is a path parameter this name will be used to substitute the + /// string value into the path, replacing {name}. If the parameter is a query parameter, this parameter will be + /// added to the query string, in the format "name=value". + /// + /// The type of the parameter, either Path, Query or UserDefinedQueries. + public RequestParameterAttribute(string name, RequestParameterType type) + { + this.name = name; + this.type = type; + } + } + + /// Describe the type of this parameter (Path, Query or UserDefinedQueries). + public enum RequestParameterType + { + /// A path parameter which is inserted into the path portion of the request URI. + Path, + + /// A query parameter which is inserted into the query portion of the request URI. + Query, + + /// + /// A group of user-defined parameters that will be added in to the query portion of the request URI. If this + /// type is being used, the name of the RequestParameterAttirbute is meaningless. + /// + UserDefinedQueries + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/SingleError.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/SingleError.cs new file mode 100644 index 00000000000..d1581055430 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/SingleError.cs @@ -0,0 +1,60 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +namespace Google.Apis.Requests +{ + /// + /// A single server error + /// + public class SingleError + { + /// + /// The domain in which the error occured + /// + public string Domain { get; set; } + + /// + /// The reason the error was thrown + /// + public string Reason { get; set; } + + /// + /// The error message + /// + public string Message { get; set; } + + /// + /// Type of the location + /// + public string LocationType { get; set; } + + /// + /// Location where the error was thrown + /// + public string Location { get; set; } + + /// + /// Returns a string summary of this error + /// + /// A string summary of this error + public override string ToString() + { + return string.Format( + "Message[{0}] Location[{1} - {2}] Reason[{3}] Domain[{4}]", Message, Location, LocationType, Reason, + Domain); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/StandardResponse.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/StandardResponse.cs new file mode 100644 index 00000000000..b1625b7c46e --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/StandardResponse.cs @@ -0,0 +1,37 @@ +/* +Copyright 2010 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Google.Apis.Requests; +using Newtonsoft.Json; + +namespace Google.Apis.Util +{ + /// + /// Calls to Google Api return StandardResponses as Json with + /// two properties Data, being the return type of the method called + /// and Error, being any errors that occure. + /// + public sealed class StandardResponse + { + /// May be null if call failed. + [JsonProperty("data")] + public InnerType Data { get; set; } + + /// May be null if call succedded. + [JsonProperty("error")] + public RequestError Error { get; set; } + } +} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/StreamInterceptionHandler.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/StreamInterceptionHandler.cs new file mode 100644 index 00000000000..88cec4b07f3 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/StreamInterceptionHandler.cs @@ -0,0 +1,122 @@ +/* +Copyright 2017 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Http +{ + /// + /// An HttpMessageHandler that (conditionally) intercepts response streams, allowing inline + /// stream processing. An interceptor provider function is fetched from each request via the + /// property; + /// if the property is not present on the request (or is null), the response will definitely not be + /// intercepted. If the property is present and non-null, the interceptor provider is called for + /// the response. This may return a null reference, indicating that interception isn't required, and + /// the response can be returned as-is. Otherwise, we use a + /// with an intercepting stream which passes all data read to the interceptor. + /// + internal sealed class StreamInterceptionHandler : DelegatingHandler + { + internal StreamInterceptionHandler(HttpMessageHandler handler) : base(handler) + { + } + + /// + /// For each request, check whether we + /// + /// + /// + /// + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var responseTask = base.SendAsync(request, cancellationToken); + var provider = GetInterceptorProvider(request); + return provider == null ? responseTask : ReplaceAsync(responseTask, provider); + } + + private async Task ReplaceAsync(Task responseTask, Func interceptorProvider) + { + var response = await responseTask.ConfigureAwait(false); + var interceptor = interceptorProvider(response); + if (interceptor != null) + { + response.Content = new StreamReplacingHttpContent(response.Content, stream => new InterceptingStream(stream, interceptor)); + } + return response; + } + + internal static Func GetInterceptorProvider(HttpRequestMessage request) + { + request.Properties.TryGetValue(ConfigurableMessageHandler.ResponseStreamInterceptorProviderKey, out var property); + // If anyone adds a property of the wrong type, just ignore it. + return property as Func; + } + + private sealed class InterceptingStream : Stream + { + private readonly Stream _original; + private readonly StreamInterceptor _interceptor; + + // We're a read-only stream, and we can't seek (as the interceptor will assume the content is read sequentially). + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => false; + + // Although we say we can't seek, we can return the length and current position. + // This may never be used, but is harmless to expose. + public override long Length => _original.Length; + public override long Position { get => _original.Position; set => throw new NotSupportedException(); } + + internal InterceptingStream(Stream original, StreamInterceptor interceptor) + { + _original = original; + _interceptor = interceptor; + } + + // Read methods which need to intercept the content. + public override int Read(byte[] buffer, int offset, int count) + { + // Perform the actual read, and then intercept just the content we read. + int ret = _original.Read(buffer, offset, count); + _interceptor(buffer, offset, ret); + return ret; + } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + // Perform the actual read, and then intercept just the content we read. + var ret = await _original.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + _interceptor(buffer, offset, ret); + return ret; + } + + // Delegating methods + protected override void Dispose(bool disposing) => _original.Dispose(); + + // Almost certainly pointless, but harmless. + public override void Flush() => _original.Flush(); + + // Unsupported methods + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/StringValueAttribute.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/StringValueAttribute.cs new file mode 100644 index 00000000000..f2b49000523 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/StringValueAttribute.cs @@ -0,0 +1,36 @@ +/* +Copyright 2011 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Util +{ + /// Defines an attribute containing a string representation of the member. + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class StringValueAttribute : Attribute + { + private readonly string text; + /// The text which belongs to this member. + public string Text { get { return text; } } + + /// Creates a new string value attribute with the specified text. + public StringValueAttribute(string text) + { + text.ThrowIfNull("text"); + this.text = text; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/TaskExtensions.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/TaskExtensions.cs new file mode 100644 index 00000000000..dd456ca2bb0 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/TaskExtensions.cs @@ -0,0 +1,51 @@ +/* +Copyright 2020 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Util +{ + // Note: this is duplicated between Google.Apis.Auth and Google.Apis.Core so it can stay internal. Please + // change both at the same time. + internal static class TaskExtensions + { + /// + /// Returns a task which can be cancelled by the given cancellation token, but otherwise observes the original + /// task's state. This does *not* cancel any work that the original task was doing, and should be used carefully. + /// + internal static Task WithCancellationToken(this Task task, CancellationToken cancellationToken) + { + if (!cancellationToken.CanBeCanceled) + { + return task; + } + + return ImplAsync(); + + // Separate async method to allow the above optimization to avoid creating any new state machines etc. + async Task ImplAsync() + { + var cts = new TaskCompletionSource(); + using (cancellationToken.Register(() => cts.TrySetCanceled())) + { + var completedTask = await Task.WhenAny(task, cts.Task).ConfigureAwait(false); + return await completedTask.ConfigureAwait(false); + } + } + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/TwoWayDelegatingHandler.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/TwoWayDelegatingHandler.cs new file mode 100644 index 00000000000..eb813967486 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/TwoWayDelegatingHandler.cs @@ -0,0 +1,67 @@ +/* +Copyright 2017 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Http +{ + /// + /// An HttpMessageHandler that delegates to one of two inner handlers based on a condition + /// checked on each request. + /// + internal sealed class TwoWayDelegatingHandler : DelegatingHandler + { + private readonly AccessibleDelegatingHandler _alternativeHandler; + private readonly Func _useAlternative; + private bool disposed = false; + + internal TwoWayDelegatingHandler(HttpMessageHandler normalHandler, HttpMessageHandler alternativeHandler, Func useAlternative) + : base(normalHandler) + { + _alternativeHandler = new AccessibleDelegatingHandler(alternativeHandler); + _useAlternative = useAlternative; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => + _useAlternative(request) ? _alternativeHandler.InternalSendAsync(request, cancellationToken) : base.SendAsync(request, cancellationToken); + + protected override void Dispose(bool disposing) + { + if (disposing && !disposed) + { + disposed = true; + _alternativeHandler.Dispose(); + } + base.Dispose(disposing); + } + + /// + /// Handler to wrap another, just so that we can effectively expose its SendAsync method. + /// + private sealed class AccessibleDelegatingHandler : DelegatingHandler + { + internal AccessibleDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler) + { + } + + internal Task InternalSendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => + SendAsync(request, cancellationToken); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/UriPatcher.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/UriPatcher.cs new file mode 100644 index 00000000000..a93dbd39f31 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/UriPatcher.cs @@ -0,0 +1,122 @@ +/* +Copyright 2016 Google Inc + +Licensed under the Apache License, Version 2.0(the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; +using System.Reflection; + +namespace Google.Apis.Util +{ + /// + /// Workarounds for some unfortunate behaviors in the .NET Framework's + /// implementation of System.Uri + /// + /// + /// UriPatcher lets us work around some unfortunate behaviors in the .NET Framework's + /// implementation of System.Uri. + /// + /// == Problem 1: Slashes and dots + /// + /// Prior to .NET 4.5, System.Uri would always unescape "%2f" ("/") and "%5c" ("\\"). + /// Relative path components were also compressed. + /// + /// As a result, this: "http://www.example.com/.%2f.%5c./" + /// ... turned into this: "http://www.example.com/" + /// + /// This breaks API requests where slashes or dots appear in path parameters. Such requests + /// arise, for example, when these characters appear in the name of a GCS object. + /// + /// == Problem 2: Fewer unreserved characters + /// + /// Unless IDN/IRI parsing is enabled -- which it is not, by default, prior to .NET 4.5 -- + /// Uri.EscapeDataString uses the set of "unreserved" characters from RFC 2396 instead of the + /// newer, *smaller* list from RFC 3986. We build requests using URI templating as described + /// by RFC 6570, which specifies that the latter definition (RFC 3986) should be used. + /// + /// This breaks API requests with parameters including any of: !*'() + /// + /// == Solutions + /// + /// Though the default behaviors changed in .NET 4.5, these "quirks" remain for compatibility + /// unless the application explicitly targets the new runtime. Usually, that means adding a + /// TargetFrameworkAttribute to the entry assembly. + /// + /// Applications running on .NET 4.0 or later can also set "DontUnescapePathDotsAndSlashes" + /// and enable IDN/IRI parsing using app.config or web.config. + /// + /// As a class library, we can't control app.config or the entry assembly, so we can't take + /// either approach. Instead, we resort to reflection trickery to try to solve these problems + /// if we detect they exist. Sorry. + /// + public static class UriPatcher + { + /// + /// Patch URI quirks in System.Uri. See class summary for details. + /// + public static void PatchUriQuirks() + { + var uriParser = typeof(System.Uri).GetTypeInfo().Assembly.GetType("System.UriParser"); + if (uriParser == null) { return; } + + // Is "%2f" unescaped for http: or https: URIs? + if (new Uri("http://example.com/%2f").AbsolutePath == "//" || + new Uri("https://example.com/%2f").AbsolutePath == "//") + { + // Call System.UriParser.Http[s]Uri.SetUpdatableFlags(UriSyntaxFlags.None) + // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/_UriSyntax.cs#L87 + // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/_UriSyntax.cs#L77 + // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/_UriSyntax.cs#L352 + + var setUpdatableFlagsMethod = uriParser.GetMethod("SetUpdatableFlags", + BindingFlags.Instance | BindingFlags.NonPublic); + if (setUpdatableFlagsMethod != null) + { + Action setUriParserUpdatableFlags = (fieldName) => + { + var parserField = uriParser.GetField(fieldName, + BindingFlags.Static | BindingFlags.NonPublic); + if (parserField == null) { return; } + var parserInstance = parserField.GetValue(null); + if (parserInstance == null) { return; } + setUpdatableFlagsMethod.Invoke(parserInstance, new object[] { 0 }); + }; + + // Make the change for the http: and https: URI parsers. + setUriParserUpdatableFlags("HttpUri"); + setUriParserUpdatableFlags("HttpsUri"); + } + } + + // Is "*" considered "unreserved"? + if (Uri.EscapeDataString("*") == "*") + { + // Set UriParser.s_QuirksVersion to at least UriQuirksVersion.V3 + // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/_UriSyntax.cs#L114 + // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/UriHelper.cs#L701 + + var quirksField = uriParser.GetField("s_QuirksVersion", + BindingFlags.Static | BindingFlags.NonPublic); + if (quirksField != null) + { + int quirksVersion = (int)quirksField.GetValue(null); + if (quirksVersion <= 2) + { + quirksField.SetValue(null, 3); + } + } + } + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs new file mode 100644 index 00000000000..f71457b206b --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs @@ -0,0 +1,208 @@ +/* +Copyright 2010 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Google.Apis.Testing; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Google.Apis.Util +{ + /// A utility class which contains helper methods and extension methods. + public static class Utilities + { + /// Returns the version of the core library. + [VisibleForTestOnly] + public static string GetLibraryVersion() + { + return Regex.Match(typeof(Utilities).GetTypeInfo().Assembly.FullName, "Version=([\\d\\.]+)").Groups[1].ToString(); + } + + /// + /// A Google.Apis utility method for throwing an if the object is + /// null. + /// + public static T ThrowIfNull(this T obj, string paramName) + { + if (obj == null) + { + throw new ArgumentNullException(paramName); + } + + return obj; + } + + /// + /// A Google.Apis utility method for throwing an if the string is + /// null or empty. + /// + /// The original string. + public static string ThrowIfNullOrEmpty(this string str, string paramName) + { + if (string.IsNullOrEmpty(str)) + { + throw new ArgumentException("Parameter was empty", paramName); + } + return str; + } + + /// Returns true in case the enumerable is null or empty. + internal static bool IsNullOrEmpty(this IEnumerable coll) + { + return coll == null || coll.Count() == 0; + } + + /// + /// Checks that the given value is in fact defined in the enum used as the type argument of the method. + /// + /// The enum type to check the value within. + /// The value to check. + /// The name of the parameter whose value is being tested. + /// if it was a defined value + public static T CheckEnumValue(T value, string paramName) where T : struct + { + CheckArgument( + Enum.IsDefined(typeof(T), value), + paramName, + "Value {0} not defined in enum {1}", value, typeof(T).Name); + return value; + } + + /// + /// Checks that given argument-based condition is met, throwing an otherwise. + /// + /// The (already evaluated) condition to check. + /// The name of the parameter whose value is being tested. + /// The format string to use to create the exception message if the + /// condition is not met. + /// The first argument to the format string. + /// The second argument to the format string. + public static void CheckArgument(bool condition, string paramName, string format, T1 arg0, T2 arg1) + { + if (!condition) + { + throw new ArgumentException(string.Format(format, arg0, arg1), paramName); + } + } + + /// + /// A Google.Apis utility method for returning the first matching custom attribute (or null) of the specified member. + /// + public static T GetCustomAttribute(this MemberInfo info) where T : Attribute + { + object[] results = info.GetCustomAttributes(typeof(T), false).ToArray(); + return results.Length == 0 ? null : (T)results[0]; + } + + /// Returns the defined string value of an Enum. + internal static string GetStringValue(this Enum value) + { + FieldInfo entry = value.GetType().GetField(value.ToString()); + entry.ThrowIfNull("value"); + + // If set, return the value. + var attribute = entry.GetCustomAttribute(); + if (attribute != null) + { + return attribute.Text; + } + + // Otherwise, throw an exception. + throw new ArgumentException( + string.Format("Enum value '{0}' does not contain a StringValue attribute", entry), "value"); + } + + /// + /// Returns the defined string value of an Enum. Use for test purposes or in other Google.Apis projects. + /// + public static string GetEnumStringValue(Enum value) + { + return value.GetStringValue(); + } + + /// + /// Tries to convert the specified object to a string. Uses custom type converters if available. + /// Returns null for a null object. + /// + [VisibleForTestOnly] + public static string ConvertToString(object o) + { + if (o == null) + { + return null; + } + + if (o.GetType().GetTypeInfo().IsEnum) + { + // Try to convert the Enum value using the StringValue attribute. + var enumType = o.GetType(); + FieldInfo field = enumType.GetField(o.ToString()); + StringValueAttribute attribute = field.GetCustomAttribute(); + return attribute != null ? attribute.Text : o.ToString(); + } + + if (o is DateTime) + { + // Honor RFC3339. + return ConvertToRFC3339((DateTime)o); + } + + if (o is bool) + { + return o.ToString().ToLowerInvariant(); + } + + return o.ToString(); + } + + /// Converts the input date into a RFC3339 string (http://www.ietf.org/rfc/rfc3339.txt). + internal static string ConvertToRFC3339(DateTime date) + { + if (date.Kind == DateTimeKind.Unspecified) + { + date = date.ToUniversalTime(); + } + return date.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", DateTimeFormatInfo.InvariantInfo); + } + + /// + /// Parses the input string and returns if the input is a valid + /// representation of a date. Otherwise it returns null. + /// + public static DateTime? GetDateTimeFromString(string raw) + { + DateTime result; + if (!DateTime.TryParse(raw, out result)) + { + return null; + } + return result; + } + + /// Returns a string (by RFC3339) form the input instance. + public static string GetStringFromDateTime(DateTime? date) + { + if (!date.HasValue) + { + return null; + } + return ConvertToRFC3339(date.Value); + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/VersionHeaderBuilder.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/VersionHeaderBuilder.cs new file mode 100644 index 00000000000..c1adecc83df --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/VersionHeaderBuilder.cs @@ -0,0 +1,202 @@ +/* +Copyright 2019 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using Google.Apis.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Versioning; + +namespace Google.Apis.Requests +{ + // Note: this code is copied from GAX: + // https://github.com/googleapis/gax-dotnet/blob/main/Google.Api.Gax/VersionHeaderBuilder.cs + // The duplication is annoying, but hard to avoid due to dependencies. + // Any changes should be made in both places. + + /// + /// Helps build version strings for the x-goog-api-client header. + /// The value is a space-separated list of name/value pairs, where the value + /// should be a semantic version string. Names must be unique. + /// + public sealed class VersionHeaderBuilder + { + private static readonly Lazy s_environmentVersion = new Lazy(GetEnvironmentVersion); + + /// + /// The name of the header to set. + /// + public const string HeaderName = "x-goog-api-client"; + private readonly List _names = new List(); + private readonly List _values = new List(); + + /// + /// Appends the given name/version string to the list. + /// + /// The name. Must not be null or empty, or contain a space or a slash. + /// The version. Must not be null, or contain a space or a slash. + public VersionHeaderBuilder AppendVersion(string name, string version) + { + Utilities.ThrowIfNull(name, nameof(name)); + Utilities.ThrowIfNull(version, nameof(version)); + // Names can't be empty, but versions can. (We use the empty string to indicate an unknown version.) + + CheckArgument(name.Length > 0 && !name.Contains(" ") && !name.Contains("/"), nameof(name), $"Invalid name: {name}"); + CheckArgument(!version.Contains(" ") && !version.Contains("/"), nameof(version), $"Invalid version: {version}"); + CheckArgument(!_names.Contains(name), nameof(name), "Names in version headers must be unique"); + _names.Add(name); + _values.Add(version); + return this; + } + + // This is in GaxPreconditions in the original code. + private static void CheckArgument(bool condition, string paramName, string message) + { + if (!condition) + { + throw new ArgumentException(message, paramName); + } + } + + /// + /// Appends a name/version string, taking the version from the version of the assembly + /// containing the given type. + /// + public VersionHeaderBuilder AppendAssemblyVersion(string name, System.Type type) + => AppendVersion(name, FormatAssemblyVersion(type)); + + /// + /// Appends the .NET environment information to the list. + /// + public VersionHeaderBuilder AppendDotNetEnvironment() => AppendVersion("gl-dotnet", s_environmentVersion.Value); + + /// + /// Whether the name or value that are supposed to be included in a header are valid + /// + private static bool IsHeaderNameValueValid(string nameOrValue) => + !nameOrValue.Contains(" ") && !nameOrValue.Contains("/"); + + private static string GetEnvironmentVersion() + { + // We can pick between the version reported by System.Environment.Version, or the version in the + // entry assembly, if any. Neither gives us exactly what we might want, + string systemEnvironmentVersion = +#if NETSTANDARD1_3 + null; +#else + FormatVersion(Environment.Version); +#endif + string entryAssemblyVersion = GetEntryAssemblyVersionOrNull(); + + return entryAssemblyVersion ?? systemEnvironmentVersion ?? ""; + } + + private static string GetEntryAssemblyVersionOrNull() + { + try + { + // Assembly.GetEntryAssembly() isn't available in netstandard1.3. Attempt to fetch it with reflection, which is ugly but should work. + // This is a slightly more robust version of the code we previously used in Microsoft.Extensions.PlatformAbstractions. + var getEntryAssemblyMethod = typeof(Assembly) + .GetTypeInfo() + .DeclaredMethods + .Where(m => m.Name == "GetEntryAssembly" && m.IsStatic && m.GetParameters().Length == 0 && m.ReturnType == typeof(Assembly)) + .FirstOrDefault(); + if (getEntryAssemblyMethod == null) + { + return null; + } + Assembly entryAssembly = (Assembly) getEntryAssemblyMethod.Invoke(null, new object[0]); + var frameworkName = entryAssembly?.GetCustomAttribute()?.FrameworkName; + return frameworkName == null ? null : FormatVersion(new FrameworkName(frameworkName).Version); + } + catch + { + // If we simply can't get the version for whatever reason, don't fail. + return null; + } + } + + private static string FormatAssemblyVersion(System.Type type) + { + // Prefer AssemblyInformationalVersion, then AssemblyFileVersion, + // then AssemblyVersion. + + var assembly = type.GetTypeInfo().Assembly; + var info = assembly.GetCustomAttributes().FirstOrDefault()?.InformationalVersion; + if (info != null && IsHeaderNameValueValid(info)) // Skip informational version if it's not a valid header value + { + return FormatInformationalVersion(info); + } + var file = assembly.GetCustomAttributes().FirstOrDefault()?.Version; + if (file != null) + { + return string.Join(".", file.Split('.').Take(3)); + } + return FormatVersion(assembly.GetName().Version); + } + + // Visible for testing + + /// + /// Formats an AssemblyInformationalVersionAttribute value to avoid losing useful information, + /// but also avoid including unnecessary hex that is appended automatically. + /// + internal static string FormatInformationalVersion(string info) + { + // In some cases, the runtime includes 40 hex digits after a + or period in InformationalVersion. + // In precisely those cases, we strip this. + int signIndex = Math.Max(info.LastIndexOf('.'), info.LastIndexOf('+')); + if (signIndex == -1 || signIndex != info.Length - 41) + { + return info; + } + for (int i = signIndex + 1; i < info.Length; i++) + { + char c = info[i]; + bool isHex = (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); + if (!isHex) + { + return info; + } + } + return info.Substring(0, signIndex); + } + + private static string FormatVersion(Version version) => + version != null ? + $"{version.Major}.{version.Minor}.{(version.Build != -1 ? version.Build : 0)}" : + ""; // Empty string means "unknown" + + /// + public override string ToString() => string.Join(" ", _names.Zip(_values, (name, value) => $"{name}/{value}")); + + /// + /// Clones this VersionHeaderBuilder, creating an independent copy with the same names/values. + /// + /// A clone of this builder. + public VersionHeaderBuilder Clone() + { + var ret = new VersionHeaderBuilder(); + ret._names.AddRange(_names); + ret._values.AddRange(_values); + return ret; + } + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/VisibleForTestOnly.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/VisibleForTestOnly.cs new file mode 100644 index 00000000000..b45d2d68915 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/VisibleForTestOnly.cs @@ -0,0 +1,29 @@ +/* +Copyright 2010 Google Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +using System; + +namespace Google.Apis.Testing +{ + /// + /// Marker Attribute to indicate a Method/Class/Property has been made more visible for purpose of testing. + /// Mark the member as internal and make the testing assembly a friend using + /// [assembly: InternalsVisibleTo("Full.Name.Of.Testing.Assembly")] + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | + AttributeTargets.Field)] + public class VisibleForTestOnly : Attribute { } +} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj b/Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj index 75b2cc61ff0..6d59d76549a 100644 --- a/Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj +++ b/Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj @@ -23,12 +23,16 @@ Supported Platforms: - .Net Standard 2.0 + + + + - - - + + + @@ -37,6 +41,8 @@ Supported Platforms: + + diff --git a/Src/Support/GoogleApisClient.sln b/Src/Support/GoogleApisClient.sln index e1f2f1c9901..418f74e47d9 100644 --- a/Src/Support/GoogleApisClient.sln +++ b/Src/Support/GoogleApisClient.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29613.14 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33530.505 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Google.Apis.Core", "Google.Apis.Core\Google.Apis.Core.csproj", "{556BEB88-1F3B-4EFA-B912-828C90AC3BEB}" EndProject From 57c9f1fe78c2396183588a413ebd6c7879ce3038 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 13:54:24 +0100 Subject: [PATCH 02/11] Extract out "request with a credential" This lets us remove BaseClientService and ClientServiceRequest. (That should then let us remove a lot more dependencies.) --- .../ExistingDependencies/BaseClientService.cs | 373 --------------- .../ClientServiceRequest.cs | 440 ------------------ .../ICredentialedRequest.cs | 17 + .../Google.Apis.Auth/OAuth2/ITokenAccess.cs | 4 +- .../OAuth2/RequestExtensions.cs | 4 +- 5 files changed, 20 insertions(+), 818 deletions(-) delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/BaseClientService.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ClientServiceRequest.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ICredentialedRequest.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/BaseClientService.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/BaseClientService.cs deleted file mode 100644 index fd953204f96..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/BaseClientService.cs +++ /dev/null @@ -1,373 +0,0 @@ -/* -Copyright 2013 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using Google.Apis.Discovery; -using Google.Apis.Http; -using Google.Apis.Json; -using Google.Apis.Logging; -using Google.Apis.Requests; -using Google.Apis.Responses; -using Google.Apis.Testing; -using Google.Apis.Util; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Google.Apis.Services -{ - /// - /// A base class for a client service which provides common mechanism for all services, like - /// serialization and GZip support. It should be safe to use a single service instance to make server requests - /// concurrently from multiple threads. - /// This class adds a special to the - /// execute interceptor list, which uses the given - /// Authenticator. It calls to its applying authentication method, and injects the "Authorization" header in the - /// request. - /// If the given Authenticator implements , this - /// class adds the Authenticator to the 's unsuccessful - /// response handler list. - /// - public abstract class BaseClientService : IClientService - { - /// The class logger. - private static readonly ILogger Logger = ApplicationContext.Logger.ForType(); - - /// The default maximum allowed length of a URL string for GET requests. - [VisibleForTestOnly] - public const uint DefaultMaxUrlLength = 2048; - - #region Initializer - - /// An initializer class for the client service. - public class Initializer - { - /// - /// Gets or sets the factory for creating instance. If this - /// property is not set the service uses a new instance. - /// - public IHttpClientFactory HttpClientFactory { get; set; } - - /// - /// Gets or sets a HTTP client initializer which is able to customize properties on - /// and - /// . - /// - public IConfigurableHttpClientInitializer HttpClientInitializer { get; set; } - - /// - /// Get or sets the exponential back-off policy used by the service. Default value is - /// UnsuccessfulResponse503, which means that exponential back-off is used on 503 abnormal HTTP - /// response. - /// If the value is set to None, no exponential back-off policy is used, and it's up to the user to - /// configure the in an - /// to set a specific back-off - /// implementation (using ). - /// - public ExponentialBackOffPolicy DefaultExponentialBackOffPolicy { get; set; } - - /// Gets or sets whether this service supports GZip. Default value is true. - public bool GZipEnabled { get; set; } - - /// - /// Gets or sets the serializer. Default value is . - /// - public ISerializer Serializer { get; set; } - - /// Gets or sets the API Key. Default value is null. - public string ApiKey { get; set; } - - /// - /// Gets or sets Application name to be used in the User-Agent header. Default value is null. - /// - public string ApplicationName { get; set; } - - /// - /// Maximum allowed length of a URL string for GET requests. Default value is 2048. If the value is - /// set to 0, requests will never be modified due to URL string length. - /// - public uint MaxUrlLength { get; set; } - - /// - /// Gets or sets the base URI to use for the service. If the value is null, - /// the default base URI for the service is used. - /// - public string BaseUri { get; set; } - - /// - /// Builder for the x-goog-api-client header, collecting version information. - /// Services automatically add the API library version to this. - /// Most users will never need to configure this, but higher level abstraction Google libraries - /// may add their own version here. - /// - public VersionHeaderBuilder VersionHeaderBuilder { get; } - - /// - /// Determines whether request parameters are validated (client-side) by default. - /// Defaults to true. This can be overridden on a per-request basis using . - /// - public bool ValidateParameters { get; set; } = true; - - /// Constructs a new initializer with default values. - public Initializer() - { - GZipEnabled = true; - Serializer = new NewtonsoftJsonSerializer(); - DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.UnsuccessfulResponse503; - MaxUrlLength = DefaultMaxUrlLength; - VersionHeaderBuilder = new VersionHeaderBuilder() - .AppendDotNetEnvironment(); - } - - // HttpRequestMessage.Headers fails if any of these characters are included in a User-Agent header. - private const string InvalidApplicationNameCharacters = "\"(),:;<=>?@[\\]{}"; - - internal void Validate() - { - if (ApplicationName != null && ApplicationName.Any(c => InvalidApplicationNameCharacters.Contains(c))) - { - throw new ArgumentException("Invalid Application name", nameof(ApplicationName)); - } - } - } - - #endregion - - /// Constructs a new base client with the specified initializer. - protected BaseClientService(Initializer initializer) - { - initializer.Validate(); - // Note that GetType() will get the *actual* type, which will be the service type in the API-specific library. - // That's the version we want to append. - // It's important that we clone the VersionHeaderBuilder, so we don't modify the initializer - otherwise - // a single initializer can't be used for multiple services (which can be useful). - string versionHeader = initializer.VersionHeaderBuilder.Clone() - .AppendAssemblyVersion("gdcl", GetType()) - .ToString(); - // Set the right properties by the initializer's properties. - GZipEnabled = initializer.GZipEnabled; - Serializer = initializer.Serializer; - ApiKey = initializer.ApiKey; - ApplicationName = initializer.ApplicationName; - BaseUriOverride = initializer.BaseUri; - ValidateParameters = initializer.ValidateParameters; - if (ApplicationName == null) - { - Logger.Warning("Application name is not set. Please set Initializer.ApplicationName property"); - } - HttpClientInitializer = initializer.HttpClientInitializer; - - // Create a HTTP client for this service. - HttpClient = CreateHttpClient(initializer, versionHeader); - } - - /// - /// Determines whether or not request parameters should be validated client-side. - /// This may be overridden on a per-request basis. - /// - internal bool ValidateParameters { get; } - - /// - /// The BaseUri provided in the initializer, which may be null. - /// - protected string BaseUriOverride { get; } - - /// Returns true if this service contains the specified feature. - private bool HasFeature(Features feature) - { - return Features.Contains(Utilities.GetEnumStringValue(feature)); - } - - private ConfigurableHttpClient CreateHttpClient(Initializer initializer, string versionHeader) - { - // If factory wasn't set use the default HTTP client factory. - var factory = initializer.HttpClientFactory ?? new HttpClientFactory(); - var args = new CreateHttpClientArgs - { - GZipEnabled = GZipEnabled, - ApplicationName = ApplicationName, - GoogleApiClientHeader = versionHeader - }; - - // Add the user's input initializer. - if (HttpClientInitializer != null) - { - args.Initializers.Add(HttpClientInitializer); - } - - // Add exponential back-off initializer if necessary. - if (initializer.DefaultExponentialBackOffPolicy != ExponentialBackOffPolicy.None) - { - args.Initializers.Add(new ExponentialBackOffInitializer(initializer.DefaultExponentialBackOffPolicy, - CreateBackOffHandler)); - } - - var httpClient = factory.CreateHttpClient(args); - if (initializer.MaxUrlLength > 0) - { - httpClient.MessageHandler.AddExecuteInterceptor(new MaxUrlLengthInterceptor(initializer.MaxUrlLength)); - } - return httpClient; - } - - /// - /// Creates the back-off handler with . - /// Overrides this method to change the default behavior of back-off handler (e.g. you can change the maximum - /// waited request's time span, or create a back-off handler with you own implementation of - /// ). - /// - protected virtual BackOffHandler CreateBackOffHandler() - { - // TODO(peleyal): consider return here interface and not the concrete class - return new BackOffHandler(new ExponentialBackOff()); - } - - #region IClientService Members - - /// - public ConfigurableHttpClient HttpClient { get; private set; } - - /// - public IConfigurableHttpClientInitializer HttpClientInitializer { get; private set; } - - /// - public bool GZipEnabled { get; private set; } - - /// - public string ApiKey { get; private set; } - - /// - public string ApplicationName { get; private set; } - - /// - public void SetRequestSerailizedContent(HttpRequestMessage request, object body) - { - request.SetRequestSerailizedContent(this, body, GZipEnabled); - } - - #region Serialization - - /// - public ISerializer Serializer { get; private set; } - - /// - public virtual string SerializeObject(object obj) => Serializer.Serialize(obj); - - /// - public virtual async Task DeserializeResponse(HttpResponseMessage response) - { - var text = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - // If a string is request, don't parse the response. - if (Type.Equals(typeof(T), typeof(string))) - { - return (T)(object)text; - } - - // Check if there was an error returned. The error node is returned in both paths - // Deserialize the stream based upon the format of the stream. - if (HasFeature(Discovery.Features.LegacyDataResponse)) - { - // Legacy path (deprecated!) - StandardResponse sr = null; - try - { - sr = Serializer.Deserialize>(text); - } - catch (Exception ex) - { - throw new GoogleApiException(Name, - $"Failed to parse response from server as {Serializer.Format ?? "unknown format"} [" + text + "]", ex); - } - - if (sr.Error != null) - { - throw new GoogleApiException(Name, "Server error - " + sr.Error) - { - Error = sr.Error - }; - } - - if (sr.Data == null) - { - throw new GoogleApiException(Name, "The response could not be deserialized."); - } - return sr.Data; - } - - // New path: Deserialize the object directly. - T result = default(T); - try - { - result = Serializer.Deserialize(text); - } - catch (Exception ex) - { - throw new GoogleApiException(Name, $"Failed to parse response from server as {Serializer.Format ?? "unknown format"} [" + text + "]", ex); - } - - // TODO(peleyal): is this the right place to check ETag? it isn't part of deserialization! - // If this schema/object provides an error container, check it. - var eTag = response.Headers.ETag != null ? response.Headers.ETag.Tag : null; - if (result is IDirectResponseSchema && eTag != null) - { - (result as IDirectResponseSchema).ETag = eTag; - } - return result; - } - - /// - public virtual Task DeserializeError(HttpResponseMessage response) => - response.DeserializeErrorAsync(Name, Serializer); - - #endregion - - #region Abstract Members - - /// - public abstract string Name { get; } - - /// - public abstract string BaseUri { get; } - - /// - public abstract string BasePath { get; } - - /// The URI used for batch operations. - public virtual string BatchUri { get { return null; } } - - /// The path used for batch operations. - public virtual string BatchPath { get { return null; } } - - /// - public abstract IList Features { get; } - - #endregion - - #endregion - - /// - public virtual void Dispose() - { - if (HttpClient != null) - { - HttpClient.Dispose(); - } - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ClientServiceRequest.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ClientServiceRequest.cs deleted file mode 100644 index 6e25ccb5a7a..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/ClientServiceRequest.cs +++ /dev/null @@ -1,440 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using Google.Apis.Discovery; -using Google.Apis.Http; -using Google.Apis.Logging; -using Google.Apis.Requests.Parameters; -using Google.Apis.Services; -using Google.Apis.Testing; -using Google.Apis.Util; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; - -namespace Google.Apis.Requests -{ - /// - /// Represents an abstract request base class to make requests to a service. - /// - public abstract class ClientServiceRequest - { - /// Unsuccessful response handlers for this request only. - protected List _unsuccessfulResponseHandlers; - /// Exception handlers for this request only. - protected List _exceptionHandlers; - /// Execute interceptors for this request only. - protected List _executeInterceptors; - - /// - /// Credential to use for this request. - /// If implements - /// then it will also be included as a handler of an unsuccessful response. - /// - public IHttpExecuteInterceptor Credential { get; set; } - - /// - /// Add an unsuccessful response handler for this request only. - /// - /// The unsuccessful response handler. Must not be null. - public void AddUnsuccessfulResponseHandler(IHttpUnsuccessfulResponseHandler handler) - { - handler.ThrowIfNull(nameof(handler)); - if (_unsuccessfulResponseHandlers == null) - { - _unsuccessfulResponseHandlers = new List(); - } - _unsuccessfulResponseHandlers.Add(handler); - } - - /// - /// Add an exception handler for this request only. - /// - /// The exception handler. Must not be null. - public void AddExceptionHandler(IHttpExceptionHandler handler) - { - handler.ThrowIfNull(nameof(handler)); - if (_exceptionHandlers == null) - { - _exceptionHandlers = new List(); - } - _exceptionHandlers.Add(handler); - } - - /// - /// Add an execute interceptor for this request only. - /// If the request is retried, the interceptor will be called on each attempt. - /// - /// The execute interceptor. Must not be null. - public void AddExecuteInterceptor(IHttpExecuteInterceptor handler) - { - handler.ThrowIfNull(nameof(handler)); - if (_executeInterceptors == null) - { - _executeInterceptors = new List(); - } - _executeInterceptors.Add(handler); - } - } - - /// - /// Represents an abstract, strongly typed request base class to make requests to a service. - /// Supports a strongly typed response. - /// - /// The type of the response object - public abstract class ClientServiceRequest : ClientServiceRequest, IClientServiceRequest - { - /// The class logger. - private static readonly ILogger Logger = ApplicationContext.Logger.ForType>(); - - /// The service on which this request will be executed. - private readonly IClientService service; - - /// Defines whether the E-Tag will be used in a specified way or be ignored. - public ETagAction ETagAction { get; set; } - - /// - /// Gets or sets the callback for modifying HTTP requests made by this service request. - /// - public Action ModifyRequest { get; set; } - - /// - /// Override for service-wide validation configuration in BaseClientService.Initializer.ValidateParameters. - /// If this is null (the default) then the value from the service initializer is used to determine - /// whether or not parameters should be validated client-side. If this is non-null, it overrides - /// whatever value is specified in the service. - /// - public bool? ValidateParameters { get; set; } - - #region IClientServiceRequest Properties - - /// - public abstract string MethodName { get; } - - /// - public abstract string RestPath { get; } - - /// - public abstract string HttpMethod { get; } - - /// - public IDictionary RequestParameters { get; private set; } - - /// - public IClientService Service - { - get { return service; } - } - - #endregion - - /// Creates a new service request. - protected ClientServiceRequest(IClientService service) - { - this.service = service; - } - - /// - /// Initializes request's parameters. Inherited classes MUST override this method to add parameters to the - /// dictionary. - /// - protected virtual void InitParameters() - { - RequestParameters = new Dictionary(); - } - - #region Execution - - /// - public TResponse Execute() - { - try - { - using (var response = ExecuteUnparsedAsync(CancellationToken.None).Result) - { - return ParseResponse(response).Result; - } - } - catch (AggregateException aex) - { - // If an exception was thrown during the tasks, unwrap and throw it. - ExceptionDispatchInfo.Capture(aex.InnerException ?? aex).Throw(); - // Won't get here, but compiler requires it - throw; - } - } - - /// - public Stream ExecuteAsStream() - { - // TODO(peleyal): should we copy the stream, and dispose the response? - try - { - // Sync call. - var response = ExecuteUnparsedAsync(CancellationToken.None).Result; - return response.Content.ReadAsStreamAsync().Result; - } - catch (AggregateException aex) - { - // If an exception was thrown during the tasks, unwrap and throw it. - throw aex.InnerException; - } - catch (Exception ex) - { - throw ex; - } - } - - /// - public async Task ExecuteAsync() - { - return await ExecuteAsync(CancellationToken.None).ConfigureAwait(false); - } - - /// - public async Task ExecuteAsync(CancellationToken cancellationToken) - { - using (var response = await ExecuteUnparsedAsync(cancellationToken).ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - return await ParseResponse(response).ConfigureAwait(false); - } - } - - /// - public async Task ExecuteAsStreamAsync() - { - return await ExecuteAsStreamAsync(CancellationToken.None).ConfigureAwait(false); - } - - /// - public async Task ExecuteAsStreamAsync(CancellationToken cancellationToken) - { - // TODO(peleyal): should we copy the stream, and dispose the response? - var response = await ExecuteUnparsedAsync(cancellationToken).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - } - - #region Helpers - - /// Sync executes the request without parsing the result. - private async Task ExecuteUnparsedAsync(CancellationToken cancellationToken) - { - using (var request = CreateRequest()) - { - return await service.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - } - } - - /// Parses the response and deserialize the content into the requested response object. - private async Task ParseResponse(HttpResponseMessage response) - { - if (response.IsSuccessStatusCode) - { - return await service.DeserializeResponse(response).ConfigureAwait(false); - } - var error = await service.DeserializeError(response).ConfigureAwait(false); - throw new GoogleApiException(service.Name) - { - Error = error, - HttpStatusCode = response.StatusCode - }; - } - - #endregion - - #endregion - - /// - public HttpRequestMessage CreateRequest(Nullable overrideGZipEnabled = null) - { - var builder = CreateBuilder(); - var request = builder.CreateRequest(); - object body = GetBody(); - request.SetRequestSerailizedContent(service, body, overrideGZipEnabled.HasValue - ? overrideGZipEnabled.Value : service.GZipEnabled); - AddETag(request); - if (_unsuccessfulResponseHandlers != null) - { - request.Properties.Add(ConfigurableMessageHandler.UnsuccessfulResponseHandlerKey, _unsuccessfulResponseHandlers); - } - if (_exceptionHandlers != null) - { - request.Properties.Add(ConfigurableMessageHandler.ExceptionHandlerKey, _exceptionHandlers); - } - if (_executeInterceptors != null) - { - request.Properties.Add(ConfigurableMessageHandler.ExecuteInterceptorKey, _executeInterceptors); - } - if (Credential != null) - { - request.Properties.Add(ConfigurableMessageHandler.CredentialKey, Credential); - } - ModifyRequest?.Invoke(request); - return request; - } - - /// - /// Creates the which is used to generate a request. - /// - /// - /// A new builder instance which contains the HTTP method and the right Uri with its path and query parameters. - /// - private RequestBuilder CreateBuilder() - { - var builder = new RequestBuilder() - { - BaseUri = new Uri(Service.BaseUri), - Path = RestPath, - Method = HttpMethod, - }; - - // Init parameters. - if (service.ApiKey != null) - { - builder.AddParameter(RequestParameterType.Query, "key", service.ApiKey); - } - var parameters = ParameterUtils.CreateParameterDictionary(this); - AddParameters(builder, ParameterCollection.FromDictionary(parameters)); - return builder; - } - - /// Generates the right URL for this request. - protected string GenerateRequestUri() => CreateBuilder().BuildUri().AbsoluteUri; - - /// Returns the body of this request. - /// The body of this request. - protected virtual object GetBody() => null; - - #region ETag - - /// - /// Adds the right ETag action (e.g. If-Match) header to the given HTTP request if the body contains ETag. - /// - private void AddETag(HttpRequestMessage request) - { - IDirectResponseSchema body = GetBody() as IDirectResponseSchema; - if (body != null && !string.IsNullOrEmpty(body.ETag)) - { - var etag = body.ETag; - ETagAction action = ETagAction == ETagAction.Default ? GetDefaultETagAction(HttpMethod) : ETagAction; - // TODO: ETag-related headers are added without validation at the moment, because it is known - // that some services are returning unquoted etags (see rfc7232). - // Once all services are fixed, change back to the commented-out code that validates the header. - switch (action) - { - case ETagAction.IfMatch: - //request.Headers.IfMatch.Add(new EntityTagHeaderValue(etag)); - request.Headers.TryAddWithoutValidation("If-Match", etag); - break; - case ETagAction.IfNoneMatch: - //request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue(etag)); - request.Headers.TryAddWithoutValidation("If-None-Match", etag); - break; - } - } - } - - /// Returns the default ETagAction for a specific HTTP verb. - [VisibleForTestOnly] - public static ETagAction GetDefaultETagAction(string httpMethod) - { - switch (httpMethod) - { - // Incoming data should only be updated if it has been changed on the server. - case HttpConsts.Get: - return ETagAction.IfNoneMatch; - - // Outgoing data should only be committed if it hasn't been changed on the server. - case HttpConsts.Put: - case HttpConsts.Post: - case HttpConsts.Patch: - case HttpConsts.Delete: - return ETagAction.IfMatch; - - default: - return ETagAction.Ignore; - } - } - - #endregion - - #region Parameters - - /// Adds path and query parameters to the given requestBuilder. - private void AddParameters(RequestBuilder requestBuilder, ParameterCollection inputParameters) - { - bool validateParameters = ValidateParameters ?? (Service as BaseClientService)?.ValidateParameters ?? true; - - foreach (var parameter in inputParameters) - { - if (!RequestParameters.TryGetValue(parameter.Key, out IParameter parameterDefinition)) - { - throw new GoogleApiException(Service.Name, - $"Invalid parameter \"{parameter.Key}\" was specified"); - } - - string value = parameter.Value; - if (validateParameters && - !ParameterValidator.ValidateParameter(parameterDefinition, value, out string error)) - { - throw new GoogleApiException(Service.Name, - $"Parameter validation failed for \"{parameterDefinition.Name}\" : {error}"); - } - - if (value == null) // If the parameter is null, use the default value. - { - value = parameterDefinition.DefaultValue; - } - - switch (parameterDefinition.ParameterType) - { - case "path": - requestBuilder.AddParameter(RequestParameterType.Path, parameter.Key, value); - break; - case "query": - // If the parameter is optional and no value is given, don't add to url. - if (!Object.Equals(value, parameterDefinition.DefaultValue) || parameterDefinition.IsRequired) - { - requestBuilder.AddParameter(RequestParameterType.Query, parameter.Key, value); - } - break; - default: - throw new GoogleApiException(service.Name, - $"Unsupported parameter type \"{parameterDefinition.ParameterType}\" for \"{parameterDefinition.Name}\""); - } - } - - // Check if there is a required parameter which wasn't set. - foreach (var parameter in RequestParameters.Values) - { - if (parameter.IsRequired && !inputParameters.ContainsKey(parameter.Name)) - { - throw new GoogleApiException(service.Name, - $"Parameter \"{parameter.Name}\" is missing"); - } - } - } - - #endregion - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ICredentialedRequest.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ICredentialedRequest.cs new file mode 100644 index 00000000000..67c58f8f7d9 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ICredentialedRequest.cs @@ -0,0 +1,17 @@ +using Google.Apis.Http; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Google.Apis.Auth.ExistingDependencies; + +/// +/// TODO: Document this. +/// +public interface ICredentialedRequest +{ + /// + /// Credential to use for this request. + /// + public IHttpExecuteInterceptor Credential { get; set; } +} diff --git a/Src/Support/Google.Apis.Auth/OAuth2/ITokenAccess.cs b/Src/Support/Google.Apis.Auth/OAuth2/ITokenAccess.cs index e66b4279745..5ecdb538a3e 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/ITokenAccess.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/ITokenAccess.cs @@ -17,14 +17,12 @@ limitations under the License. using System.Threading; using System.Threading.Tasks; -using Google.Apis.Auth.OAuth2.Responses; - namespace Google.Apis.Auth.OAuth2 { /// /// Allows direct retrieval of access tokens to authenticate requests. /// This is necessary for workflows where you don't want to use - /// to access the API. + /// BaseClientService to access the API. /// (e.g. gRPC that implemenents the entire HTTP2 stack internally). /// public interface ITokenAccess diff --git a/Src/Support/Google.Apis.Auth/OAuth2/RequestExtensions.cs b/Src/Support/Google.Apis.Auth/OAuth2/RequestExtensions.cs index 6b1dc8c06e3..4841e8c33af 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/RequestExtensions.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/RequestExtensions.cs @@ -14,8 +14,8 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Http; -using Google.Apis.Requests; using Google.Apis.Util; using System; @@ -36,7 +36,7 @@ public static class RequestExtensions /// The request which requires a credential. Must not be null. /// The credential to use for this request only. Must not be null. /// - public static T AddCredential(this T request, ICredential credential) where T : ClientServiceRequest + public static T AddCredential(this T request, ICredential credential) where T : ICredentialedRequest { request.ThrowIfNull(nameof(request)); credential.ThrowIfNull(nameof(credential)); From e96de5236ebacfe3248265037f8a9b60707df1b2 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:01:04 +0100 Subject: [PATCH 03/11] Remove HttpClientFactory and associated classes (There may be more.) We can use a separate default auth-specific HttpClientFactory without nearly as many features. --- .../AuthHttpClientFactory.cs | 16 ++ .../ExistingDependencies/HttpClientFactory.cs | 167 ------------------ .../IHttpClientFactory.cs | 2 + .../StreamInterceptionHandler.cs | 122 ------------- .../TwoWayDelegatingHandler.cs | 67 ------- .../Google.Apis.Auth/Google.Apis.Auth.csproj | 4 - .../OAuth2/Flows/AuthorizationCodeFlow.cs | 3 +- .../OAuth2/Flows/IHttpAuthorizationFlow.cs | 2 +- .../OAuth2/GoogleCredential.cs | 2 +- .../OAuth2/IGoogleCredential.cs | 2 +- .../OAuth2/ServiceCredential.cs | 3 +- 11 files changed, 25 insertions(+), 365 deletions(-) create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/AuthHttpClientFactory.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpClientFactory.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/StreamInterceptionHandler.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/TwoWayDelegatingHandler.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/AuthHttpClientFactory.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/AuthHttpClientFactory.cs new file mode 100644 index 00000000000..a2f25c28735 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/AuthHttpClientFactory.cs @@ -0,0 +1,16 @@ +using Google.Apis.Http; +using System; + +namespace Google.Apis.Auth.ExistingDependencies; + +/// +/// Default implementation of IHttpClientFactory just for auth. +/// Avoids us requiring the full HttpClientFactory. +/// +internal class AuthHttpClientFactory : IHttpClientFactory +{ + public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) + { + throw new NotImplementedException(); + } +} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpClientFactory.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpClientFactory.cs deleted file mode 100644 index 6d6a136ec57..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpClientFactory.cs +++ /dev/null @@ -1,167 +0,0 @@ -/* -Copyright 2013 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System.Net; -using System.Net.Http; - -namespace Google.Apis.Http -{ - /// The default implementation of the HTTP client factory. - public class HttpClientFactory : IHttpClientFactory - { - /// - /// Creates a new instance of that - /// will set the given proxy on HTTP clients created by this factory. - /// - /// The proxy to set on HTTP clients created by this factory. - /// May be null, in which case no proxy will be used. - public static HttpClientFactory ForProxy(IWebProxy proxy) => - new HttpClientFactory(proxy); - - /// - /// Creates a new instance of . - /// - public HttpClientFactory() : this(null) - { } - - /// - /// Creates a new instance of that - /// will set the given proxy on HTTP clients created by this factory. - /// - /// The proxy to set on HTTP clients created by this factory. - /// May be null, in which case no proxy will be used. - protected HttpClientFactory(IWebProxy proxy) => Proxy = proxy; - - /// - /// Gets the proxy to use when creating HTTP clients, if any. - /// May be null, in which case, no proxy will be set for HTTP clients - /// created by this factory. - /// - public IWebProxy Proxy { get; } - - /// - public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) - { - // Create the handler. - var handler = CreateHandler(args); - var configurableHandler = new ConfigurableMessageHandler(handler) - { - ApplicationName = args.ApplicationName, - GoogleApiClientHeader = args.GoogleApiClientHeader - }; - - // Create the client. - var client = new ConfigurableHttpClient(configurableHandler); - foreach (var initializer in args.Initializers) - { - initializer.Initialize(client); - } - - return client; - } - - /// Creates a HTTP message handler. Override this method to mock a message handler. - protected virtual HttpMessageHandler CreateHandler(CreateHttpClientArgs args) - { - // We need to handle three situations in order to intercept uncompressed data where necessary - // while using the built-in decompression where possible. - // - No compression requested - // - Compression requested but not supported by HttpClientHandler (easy; just GzipDeflateHandler on top of an interceptor on top of HttpClientHandler) - // - Compression requested and HttpClientHandler (complex: create two different handlers and decide which to use on a per-request basis) - - var clientHandler = CreateAndConfigureClientHandler(); - - if (!args.GZipEnabled) - { - // Simple: nothing will be decompressing content, so we can just intercept the original handler. - return new StreamInterceptionHandler(clientHandler); - } - else if (!clientHandler.SupportsAutomaticDecompression) - { - // Simple: we have to create our own decompression handler anyway, so there's still just a single chain. - var interceptionHandler = new StreamInterceptionHandler(clientHandler); - return new GzipDeflateHandler(interceptionHandler); - } - else - { - // Complex: we want to use a simple handler with no interception but with built-in decompression - // for requests that wouldn't perform interception anyway, and a longer chain for interception cases. - clientHandler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; - - return new TwoWayDelegatingHandler( - // Normal handler (with built-in decompression) when there's no interception. - clientHandler, - // Alternative handler for requests that might be intercepted, and need that interception to happen - // before decompression. We need to delegate to a new client handler that *doesn't* - new GzipDeflateHandler(new StreamInterceptionHandler(CreateAndConfigureClientHandler())), - request => StreamInterceptionHandler.GetInterceptorProvider(request) != null); - } - } - - /// - /// Creates a simple client handler with redirection and compression disabled. - /// - private HttpClientHandler CreateAndConfigureClientHandler() - { - var handler = CreateClientHandler(); - if (handler.SupportsRedirectConfiguration) - { - handler.AllowAutoRedirect = false; - } - if (handler.SupportsAutomaticDecompression) - { - handler.AutomaticDecompression = DecompressionMethods.None; - } - return handler; - } - - /// - /// Create a for use when communicating with the server. - /// Please read the remarks closely before overriding this method. - /// - /// - /// When overriding this method, please observe the following: - /// - /// - /// and - /// - /// of the returned instance are configured after this method returns. - /// Configuring these within this method will have no effect. - /// - /// - /// is set in this method to - /// if value is not null. You may override that behaviour. - /// - /// - /// Return a new instance of an for each call to this method. - /// - /// - /// This method may be called once, or more than once, when initializing a single client service. - /// - /// - /// - /// A suitable . - protected virtual HttpClientHandler CreateClientHandler() - { - var client = new HttpClientHandler(); - if (Proxy != null) - { - client.Proxy = Proxy; - } - return client; - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpClientFactory.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpClientFactory.cs index bde5e95420a..f1cad0dc81a 100644 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpClientFactory.cs +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/IHttpClientFactory.cs @@ -16,6 +16,8 @@ limitations under the License. using System.Collections.Generic; +// TODO: Remove entirely for the sake of + namespace Google.Apis.Http { /// Arguments for creating a HTTP client. diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/StreamInterceptionHandler.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/StreamInterceptionHandler.cs deleted file mode 100644 index 88cec4b07f3..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/StreamInterceptionHandler.cs +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2017 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Google.Apis.Http -{ - /// - /// An HttpMessageHandler that (conditionally) intercepts response streams, allowing inline - /// stream processing. An interceptor provider function is fetched from each request via the - /// property; - /// if the property is not present on the request (or is null), the response will definitely not be - /// intercepted. If the property is present and non-null, the interceptor provider is called for - /// the response. This may return a null reference, indicating that interception isn't required, and - /// the response can be returned as-is. Otherwise, we use a - /// with an intercepting stream which passes all data read to the interceptor. - /// - internal sealed class StreamInterceptionHandler : DelegatingHandler - { - internal StreamInterceptionHandler(HttpMessageHandler handler) : base(handler) - { - } - - /// - /// For each request, check whether we - /// - /// - /// - /// - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var responseTask = base.SendAsync(request, cancellationToken); - var provider = GetInterceptorProvider(request); - return provider == null ? responseTask : ReplaceAsync(responseTask, provider); - } - - private async Task ReplaceAsync(Task responseTask, Func interceptorProvider) - { - var response = await responseTask.ConfigureAwait(false); - var interceptor = interceptorProvider(response); - if (interceptor != null) - { - response.Content = new StreamReplacingHttpContent(response.Content, stream => new InterceptingStream(stream, interceptor)); - } - return response; - } - - internal static Func GetInterceptorProvider(HttpRequestMessage request) - { - request.Properties.TryGetValue(ConfigurableMessageHandler.ResponseStreamInterceptorProviderKey, out var property); - // If anyone adds a property of the wrong type, just ignore it. - return property as Func; - } - - private sealed class InterceptingStream : Stream - { - private readonly Stream _original; - private readonly StreamInterceptor _interceptor; - - // We're a read-only stream, and we can't seek (as the interceptor will assume the content is read sequentially). - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => false; - - // Although we say we can't seek, we can return the length and current position. - // This may never be used, but is harmless to expose. - public override long Length => _original.Length; - public override long Position { get => _original.Position; set => throw new NotSupportedException(); } - - internal InterceptingStream(Stream original, StreamInterceptor interceptor) - { - _original = original; - _interceptor = interceptor; - } - - // Read methods which need to intercept the content. - public override int Read(byte[] buffer, int offset, int count) - { - // Perform the actual read, and then intercept just the content we read. - int ret = _original.Read(buffer, offset, count); - _interceptor(buffer, offset, ret); - return ret; - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - // Perform the actual read, and then intercept just the content we read. - var ret = await _original.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); - _interceptor(buffer, offset, ret); - return ret; - } - - // Delegating methods - protected override void Dispose(bool disposing) => _original.Dispose(); - - // Almost certainly pointless, but harmless. - public override void Flush() => _original.Flush(); - - // Unsupported methods - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/TwoWayDelegatingHandler.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/TwoWayDelegatingHandler.cs deleted file mode 100644 index eb813967486..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/TwoWayDelegatingHandler.cs +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2017 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Google.Apis.Http -{ - /// - /// An HttpMessageHandler that delegates to one of two inner handlers based on a condition - /// checked on each request. - /// - internal sealed class TwoWayDelegatingHandler : DelegatingHandler - { - private readonly AccessibleDelegatingHandler _alternativeHandler; - private readonly Func _useAlternative; - private bool disposed = false; - - internal TwoWayDelegatingHandler(HttpMessageHandler normalHandler, HttpMessageHandler alternativeHandler, Func useAlternative) - : base(normalHandler) - { - _alternativeHandler = new AccessibleDelegatingHandler(alternativeHandler); - _useAlternative = useAlternative; - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => - _useAlternative(request) ? _alternativeHandler.InternalSendAsync(request, cancellationToken) : base.SendAsync(request, cancellationToken); - - protected override void Dispose(bool disposing) - { - if (disposing && !disposed) - { - disposed = true; - _alternativeHandler.Dispose(); - } - base.Dispose(disposing); - } - - /// - /// Handler to wrap another, just so that we can effectively expose its SendAsync method. - /// - private sealed class AccessibleDelegatingHandler : DelegatingHandler - { - internal AccessibleDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler) - { - } - - internal Task InternalSendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => - SendAsync(request, cancellationToken); - } - } -} diff --git a/Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj b/Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj index 6d59d76549a..c26b87d1053 100644 --- a/Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj +++ b/Src/Support/Google.Apis.Auth/Google.Apis.Auth.csproj @@ -23,10 +23,6 @@ Supported Platforms: - .Net Standard 2.0 - - - - diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Flows/AuthorizationCodeFlow.cs b/Src/Support/Google.Apis.Auth/OAuth2/Flows/AuthorizationCodeFlow.cs index 1f2ed326249..e80a3f06895 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Flows/AuthorizationCodeFlow.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Flows/AuthorizationCodeFlow.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Auth.OAuth2.Requests; using Google.Apis.Auth.OAuth2.Responses; using Google.Apis.Http; @@ -203,7 +204,7 @@ public AuthorizationCodeFlow(Initializer initializer) // Set the HTTP client. DefaultExponentialBackOffPolicy = initializer.DefaultExponentialBackOffPolicy; - HttpClientFactory = initializer.HttpClientFactory ?? new HttpClientFactory(); + HttpClientFactory = initializer.HttpClientFactory ?? new AuthHttpClientFactory(); var httpArgs = new CreateHttpClientArgs(); // Add exponential back-off initializer if necessary. diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Flows/IHttpAuthorizationFlow.cs b/Src/Support/Google.Apis.Auth/OAuth2/Flows/IHttpAuthorizationFlow.cs index b08ff294c8f..c26d7fdfced 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Flows/IHttpAuthorizationFlow.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Flows/IHttpAuthorizationFlow.cs @@ -27,7 +27,7 @@ internal interface IHttpAuthorizationFlow : IAuthorizationCodeFlow /// given HTTP client factory. /// /// The http client factory to be used by the new instance. - /// May be null, in which case the default will be used. + /// May be null, in which case the default will be used. /// A new instance with the same type as this but that will use /// to obtain an to be used for token related operations. IHttpAuthorizationFlow WithHttpClientFactory(IHttpClientFactory httpClientFactory); diff --git a/Src/Support/Google.Apis.Auth/OAuth2/GoogleCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/GoogleCredential.cs index 1a53e28fcdc..b2af0fe26c9 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/GoogleCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/GoogleCredential.cs @@ -327,7 +327,7 @@ GoogleAuthConsts.EnvironmentQuotaProject is string environmentQuotaProject /// Creates a copy of this credential with the specified HTTP client factory. /// /// The HTTP client factory to be used by the new credential. - /// May be null, in which case the default will be used. + /// May be null, in which case the default will be used. public virtual GoogleCredential CreateWithHttpClientFactory(IHttpClientFactory factory) => new GoogleCredential(credential.WithHttpClientFactory(factory)); diff --git a/Src/Support/Google.Apis.Auth/OAuth2/IGoogleCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/IGoogleCredential.cs index 9b86708e62b..d4191854548 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/IGoogleCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/IGoogleCredential.cs @@ -76,7 +76,7 @@ internal interface IGoogleCredential : ICredential, ITokenAccessWithHeaders /// given HTTP client factory. /// /// The http client factory to be used by the new instance. - /// May be null in which case the default will be used. + /// May be null in which case the default will be used. /// A new instance with the same type as this but that will use /// to obtain an to be used for token and other operations. IGoogleCredential WithHttpClientFactory(IHttpClientFactory httpClientFactory); diff --git a/Src/Support/Google.Apis.Auth/OAuth2/ServiceCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/ServiceCredential.cs index bd0d8a925e6..fb0c2ab8783 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/ServiceCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/ServiceCredential.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Auth.OAuth2.Responses; using Google.Apis.Http; using Google.Apis.Logging; @@ -215,7 +216,7 @@ public ServiceCredential(Initializer initializer) DefaultExponentialBackOffPolicy = initializer.DefaultExponentialBackOffPolicy; HttpClientInitializers = new List(initializer.HttpClientInitializers).AsReadOnly(); - HttpClientFactory = initializer.HttpClientFactory ?? new HttpClientFactory(); + HttpClientFactory = initializer.HttpClientFactory ?? new AuthHttpClientFactory(); HttpClient = HttpClientFactory.CreateHttpClient(BuildCreateHttpClientArgs()); _refreshManager = new TokenRefreshManager(RequestAccessTokenAsync, Clock, Logger); From a6d6c4522c04f7175fe5852eefe0420b7ac89feb Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:10:27 +0100 Subject: [PATCH 04/11] Remove request-building code We can hand-craft the serialization of these requests (and fix the responses, too) --- .../ParameterCollection.cs | 166 --------- .../ExistingDependencies/ParameterUtils.cs | 177 --------- .../ParameterValidator.cs | 79 ----- .../ExistingDependencies/RequestBuilder.cs | 335 ------------------ .../RequestParameterAttribute.cs | 82 ----- .../Requests/AuthorizationCodeRequestUrl.cs | 15 +- .../Requests/AuthorizationCodeTokenRequest.cs | 4 +- .../Requests/AuthorizationRequestUrl.cs | 5 - .../Requests/GoogleAssertionTokenRequest.cs | 3 +- .../GoogleAuthorizationCodeRequestUrl.cs | 11 +- .../Requests/GoogleRevokeTokenRequest.cs | 12 +- .../OAuth2/Requests/IamSignBlobRequest.cs | 2 + .../ImpersonationAccessTokenRequest.cs | 2 + .../Requests/ImpersonationOIdCTokenRequest.cs | 2 + .../OAuth2/Requests/ImpersonationRequest.cs | 2 + .../OAuth2/Requests/RefreshTokenRequest.cs | 3 +- .../OAuth2/Requests/RequestExtensions.cs | 4 +- .../OAuth2/Requests/StsTokenRequest.cs | 10 +- .../OAuth2/Requests/TokenRequest.cs | 11 +- .../Requests/TokenRequestExtenstions.cs | 4 +- 20 files changed, 30 insertions(+), 899 deletions(-) delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterCollection.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterUtils.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterValidator.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/RequestBuilder.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/RequestParameterAttribute.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterCollection.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterCollection.cs deleted file mode 100644 index c3c0d85ec3c..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterCollection.cs +++ /dev/null @@ -1,166 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Collections; -using System.Collections.Generic; - -using Google.Apis.Util; - -namespace Google.Apis.Requests.Parameters -{ - /// A collection of parameters (key value pairs). May contain duplicate keys. - public class ParameterCollection : List> - { - /// Constructs a new parameter collection. - public ParameterCollection() : base() { } - - /// Constructs a new parameter collection from the given collection. - public ParameterCollection(IEnumerable> collection) : base(collection) { } - - /// Adds a single parameter to this collection. - public void Add(string key, string value) - { - Add(new KeyValuePair(key, value)); - } - - /// Returns true if this parameter is set within the collection. - public bool ContainsKey(string key) - { - key.ThrowIfNullOrEmpty("key"); - string value; - return TryGetValue(key, out value); - } - - /// - /// Tries to find the a key within the specified key value collection. Returns true if the key was found. - /// If a pair was found the out parameter value will contain the value of that pair. - /// - public bool TryGetValue(string key, out string value) - { - key.ThrowIfNullOrEmpty("key"); - - foreach (KeyValuePair pair in this) - { - // Check if this pair matches the specified key name. - if (pair.Key.Equals(key)) - { - value = pair.Value; - return true; - } - } - - // No result found. - value = null; - return false; - } - - /// - /// Returns the value of the first matching key, or throws a KeyNotFoundException if the parameter is not - /// present within the collection. - /// - public string GetFirstMatch(string key) - { - string val; - if (!TryGetValue(key, out val)) - { - throw new KeyNotFoundException("Parameter with the name '" + key + "' was not found."); - } - return val; - } - - /// - /// Returns all matches for the specified key. May return an empty enumeration if the key is not present. - /// - public IEnumerable GetAllMatches(string key) - { - key.ThrowIfNullOrEmpty("key"); - - foreach (KeyValuePair pair in this) - { - // Check if this pair matches the specified key name. - if (pair.Key.Equals(key)) - { - yield return pair.Value; - } - } - } - - /// - /// Returns all matches for the specified key. May return an empty enumeration if the key is not present. - /// - public IEnumerable this[string key] - { - get { return GetAllMatches(key); } - } - - /// - /// Creates a parameter collection from the specified URL encoded query string. - /// Example: - /// The query string "foo=bar&chocolate=cookie" would result in two parameters (foo and bar) - /// with the values "bar" and "cookie" set. - /// - public static ParameterCollection FromQueryString(string qs) - { - var collection = new ParameterCollection(); - var qsParam = qs.Split('&'); - foreach (var param in qsParam) - { - // Split the parameter into key and value. - var info = param.Split(new[] { '=' }); - if (info.Length == 2) - { - collection.Add(Uri.UnescapeDataString(info[0]), Uri.UnescapeDataString(info[1])); - } - else - { - throw new ArgumentException(string.Format( - "Invalid query string [{0}]. Invalid part [{1}]", qs, param)); - } - } - - return collection; - } - - /// - /// Creates a parameter collection from the specified dictionary. - /// If the value is an enumerable, a parameter pair will be added for each value. - /// Otherwise the value will be converted into a string using the .ToString() method. - /// - public static ParameterCollection FromDictionary(IDictionary dictionary) - { - var collection = new ParameterCollection(); - foreach (KeyValuePair pair in dictionary) - { - // Try parsing the value of the pair as an enumerable. - var valueAsEnumerable = pair.Value as IEnumerable; - if (!(pair.Value is string) && valueAsEnumerable != null) - { - foreach (var value in valueAsEnumerable) - { - collection.Add(pair.Key, Util.Utilities.ConvertToString(value)); - } - } - else - { - // Otherwise just convert it to a string. - collection.Add(pair.Key, pair.Value == null ? null : Util.Utilities.ConvertToString(pair.Value)); - } - } - return collection; - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterUtils.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterUtils.cs deleted file mode 100644 index 2cffd2e6691..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterUtils.cs +++ /dev/null @@ -1,177 +0,0 @@ -/* -Copyright 2013 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Linq; -using System.Reflection; - -using Google.Apis.Logging; -using Google.Apis.Util; - -namespace Google.Apis.Requests.Parameters -{ - /// - /// Utility class for iterating on properties in a request object. - /// - public static class ParameterUtils - { - private static readonly ILogger Logger = ApplicationContext.Logger.ForType(typeof(ParameterUtils)); - - /// - /// Creates a with all the specified parameters in - /// the input request. It uses reflection to iterate over all properties with - /// attribute. - /// - /// - /// A request object which contains properties with - /// attribute. Those properties will be serialized - /// to the returned . - /// - /// - /// A which contains the all the given object required - /// values. - /// - public static FormUrlEncodedContent CreateFormUrlEncodedContent(object request) - { - IList> list = new List>(); - IterateParameters(request, (type, name, value) => - { - list.Add(new KeyValuePair(name, value.ToString())); - }); - return new FormUrlEncodedContent(list); - } - - /// - /// Creates a parameter dictionary by using reflection to iterate over all properties with - /// attribute. - /// - /// - /// A request object which contains properties with - /// attribute. Those properties will be set - /// in the output dictionary. - /// - public static IDictionary CreateParameterDictionary(object request) - { - var dict = new Dictionary(); - IterateParameters(request, (type, name, value) => - { - if (dict.TryGetValue(name, out var existingValue)) - { - // Repeated enum query parameters end up with two properties: a single - // one, and a Repeatable (where the T is always non-nullable, whether or not the parameter - // is optional). If both properties are set, we fail. Note that this delegate is called - // for nullable enum properties with a null value, annoyingly... if that happens and we then - // see a non-null value, we'll overwrite it. If that happens when we've already got a non-null - // value, we'll ignore it. - if (existingValue is null && value is object) - { - // Overwrite null value with non-null value - dict[name] = value; - } - else if (value is null) - { - // Ignore new null value - } - else - { - // Throw if we see a second null value - throw new InvalidOperationException( - $"The query parameter '{name}' is set by multiple properties. For repeated enum query parameters, ensure that only one property is set to a non-null value."); - } - } - else - { - dict.Add(name, value); - } - }); - return dict; - } - - /// - /// Sets query parameters in the given builder with all all properties with the - /// attribute. - /// - /// The request builder - /// - /// A request object which contains properties with - /// attribute. Those properties will be set in the - /// given request builder object - /// - public static void InitParameters(RequestBuilder builder, object request) - { - IterateParameters(request, (type, name, value) => - { - builder.AddParameter(type, name, value.ToString()); - }); - } - - /// - /// Iterates over all properties in the request - /// object and invokes the specified action for each of them. - /// - /// A request object - /// An action to invoke which gets the parameter type, name and its value - private static void IterateParameters(object request, Action action) - { - // Use reflection to build the parameter dictionary. - foreach (PropertyInfo property in request.GetType().GetProperties(BindingFlags.Instance | - BindingFlags.Public)) - { - // Retrieve the RequestParameterAttribute. - RequestParameterAttribute attribute = - property.GetCustomAttributes(typeof(RequestParameterAttribute), false).FirstOrDefault() as - RequestParameterAttribute; - if (attribute == null) - { - continue; - } - - // Get the name of this parameter from the attribute, if it doesn't exist take a lower-case variant of - // property name. - string name = attribute.Name ?? property.Name.ToLower(); - - var propertyType = property.PropertyType; - var value = property.GetValue(request, null); - - // Call action with the type name and value. - if (propertyType.GetTypeInfo().IsValueType || value != null) - { - if (attribute.Type == RequestParameterType.UserDefinedQueries) - { - if (typeof(IEnumerable>).IsAssignableFrom(value.GetType())) - { - foreach (var pair in (IEnumerable>)value) - { - action(RequestParameterType.Query, pair.Key, pair.Value); - } - } - else - { - Logger.Warning("Parameter marked with RequestParameterType.UserDefinedQueries attribute " + - "was not of type IEnumerable> and will be skipped."); - } - } - else - { - action(attribute.Type, name, value); - } - } - } - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterValidator.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterValidator.cs deleted file mode 100644 index 3c634ed504e..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/ParameterValidator.cs +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2010 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Linq; -using System.Text.RegularExpressions; - -using Google.Apis.Discovery; -using Google.Apis.Testing; -using Google.Apis.Util; - -namespace Google.Apis.Requests.Parameters -{ - /// Logic for validating a parameter. - public static class ParameterValidator - { - /// Validates a parameter value against the methods regex. - [VisibleForTestOnly] - [Obsolete("Use ValidateParameter instead")] - public static bool ValidateRegex(IParameter param, string paramValue) => ValidateRegex(param, paramValue, out _); - - /// Validates a parameter value against the methods regex. - [VisibleForTestOnly] - internal static bool ValidateRegex(IParameter param, string paramValue, out string error) - { - if (string.IsNullOrEmpty(param.Pattern) || new Regex(param.Pattern).IsMatch(paramValue)) - { - error = null; - return true; - } - else - { - error = $"The value did not match the regular expression {param.Pattern}"; - return false; - } - } - - /// Validates if a parameter is valid. - [Obsolete("Use the overload with error output instead")] - public static bool ValidateParameter(IParameter parameter, string value) => ValidateParameter(parameter, value, out _); - - /// Validates if a parameter is valid. - public static bool ValidateParameter(IParameter parameter, string value, out string error) - { - if (string.IsNullOrEmpty(value)) - { - // Fail if a required parameter is not present. - if (parameter.IsRequired) - { - error = "The parameter value must be a non-empty string"; - return false; - } - else - { - error = null; - return true; - } - } - else - { - // The parameter has value so validate the regex. - return ValidateRegex(parameter, value, out error); - } - } - } -} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestBuilder.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestBuilder.cs deleted file mode 100644 index 5a4d5ea3f91..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestBuilder.cs +++ /dev/null @@ -1,335 +0,0 @@ -/* -Copyright 2012 Google Inc - -Licensed under the Apache License, Version 2.0(the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Text.RegularExpressions; - -using Google.Apis.Http; -using Google.Apis.Logging; -using Google.Apis.Util; - -namespace Google.Apis.Requests -{ - /// Utility class for building a URI using or a HTTP request using - /// from the query and path parameters of a REST call. - public class RequestBuilder - { - static RequestBuilder() - { - UriPatcher.PatchUriQuirks(); - } - - private static readonly ILogger Logger = ApplicationContext.Logger.ForType(); - - /// Pattern to get the groups that are part of the path. - private static Regex PathParametersPattern = new Regex(@"{[^{}]*}*"); - - /// Supported HTTP methods. - private static IEnumerable SupportedMethods = new List - { - HttpConsts.Get, HttpConsts.Post, HttpConsts.Put, HttpConsts.Delete, HttpConsts.Patch - }; - - /// - /// A dictionary containing the parameters which will be inserted into the path of the URI. These parameters - /// will be substituted into the URI path where the path contains "{key}". See - /// http://tools.ietf.org/html/rfc6570 for more information. - /// - private IDictionary> PathParameters { get; set; } - - /// - /// A dictionary containing the parameters which will apply to the query portion of this request. - /// - private List> QueryParameters { get; set; } - - /// The base URI for this request (usually applies to the service itself). - public Uri BaseUri { get; set; } - - /// - /// The path portion of this request. It's appended to the and the parameters are - /// substituted from the dictionary. - /// - public string Path { get; set; } - - /// The HTTP method used for this request. - private string method; - - /// The HTTP method used for this request (such as GET, PUT, POST, etc...). - /// The default Value is . - public string Method - { - get { return method; } - set - { - if (!SupportedMethods.Contains(value)) - throw new ArgumentOutOfRangeException("Method"); - method = value; - } - } - - /// Construct a new request builder. - /// TODO(peleyal): Consider using the Factory pattern here. - public RequestBuilder() - { - PathParameters = new Dictionary>(); - QueryParameters = new List>(); - Method = HttpConsts.Get; - } - - /// Constructs a Uri as defined by the parts of this request builder. - public Uri BuildUri() - { - var restPath = BuildRestPath(); - - if (QueryParameters.Count > 0) - { - // In case the path already contains '?' - we should add '&'. Otherwise add '?'. - restPath.Append(restPath.ToString().Contains("?") ? "&" : "?"); - - // If parameter value is empty - just add the "name", otherwise "name=value" - restPath.Append(String.Join("&", QueryParameters.Select( - x => string.IsNullOrEmpty(x.Value) ? - Uri.EscapeDataString(x.Key) : - String.Format("{0}={1}", Uri.EscapeDataString(x.Key), Uri.EscapeDataString(x.Value))) - .ToArray())); - } - - return UriJoin(this.BaseUri, restPath.ToString()); - } - - private Uri UriJoin(Uri baseUri, string path) - { - // This emulates the subset of behaviour we require from Uri(Uri, string). - // Except it does not treat ':' as a special, scheme-indicating, character in path. - if (path == "") - { - return baseUri; - } - var baseUriStr = baseUri.AbsoluteUri; - if (path.StartsWith("/")) - { - // Path is absolute; remove any path on the base URI. - baseUriStr = Regex.Replace(baseUriStr, "^([^:]+://[^/]+)/.*$", "$1"); - } - else - { - if (!path.StartsWith("?") && !path.StartsWith("#")) - { - while (!baseUriStr.EndsWith("/")) - { - baseUriStr = baseUriStr.Substring(0, baseUriStr.Length - 1); - } - } - } - return new Uri(baseUriStr + path); - } - - /// Operator list that can appear in the path argument. - private const string OPERATORS = "+#./;?&|!@="; - - /// - /// Builds the REST path string builder based on and the URI template spec - /// http://tools.ietf.org/html/rfc6570. - /// - /// - private StringBuilder BuildRestPath() - { - if (string.IsNullOrEmpty(Path)) - { - return new StringBuilder(string.Empty); - } - - var restPath = new StringBuilder(Path); - var matches = PathParametersPattern.Matches(restPath.ToString()); - foreach (var match in matches) - { - var matchStr = match.ToString(); - // Strip the first and last characters: '{' and '}'. - var content = matchStr.Substring(1, matchStr.Length - 2); - - var op = string.Empty; - // If the content's first character is an operator, save and remove it from the content string. - if (OPERATORS.Contains(content[0].ToString())) - { - op = content[0].ToString(); - content = content.Substring(1); - } - - var newContent = new StringBuilder(); - - // Iterate over all possible parameters. - var parameters = content.Split(','); - for (var index = 0; index < parameters.Length; ++index) - { - var parameter = parameters[index]; - - var parameterName = parameter; - var containStar = false; - var numOfChars = 0; - - // Check if it ends with '*'. - if (parameterName[parameterName.Length - 1] == '*') - { - containStar = true; - parameterName = parameterName.Substring(0, parameterName.Length - 1); - } - // Check if it contains :n which means we should only use the first n characters of this parameter. - if (parameterName.Contains(":")) - { - if (!int.TryParse(parameterName.Substring(parameterName.IndexOf(":") + 1), out numOfChars)) - { - throw new ArgumentException( - string.Format("Can't parse number after ':' in Path \"{0}\". Parameter is \"{1}\"", - Path, parameterName), Path); - } - parameterName = parameterName.Substring(0, parameterName.IndexOf(":")); - } - - // We can improve the following if statement, but for readability we will leave it like that. - var joiner = op; - var start = op; - switch (op) - { - case "+": - start = index == 0 ? "" : ","; - joiner = ","; - break; - case ".": - if (!containStar) - { - joiner = ","; - } - break; - case "/": - if (!containStar) - { - joiner = ","; - } - break; - case "#": - start = index == 0 ? "#" : ","; - joiner = ","; - break; - - case "?": - start = (index == 0 ? "?" : "&") + parameterName + "="; - joiner = ","; - if (containStar) - { - joiner = "&" + parameterName + "="; - } - break; - case "&": - case ";": - start = op + parameterName + "="; - joiner = ","; - if (containStar) - { - joiner = op + parameterName + "="; - } - break; - // No operator, in that case just ','. - default: - if (index > 0) - { - start = ","; - } - joiner = ","; - break; - } - - // Check if a path parameter equals the name which appears in the REST path. - if (PathParameters.ContainsKey(parameterName)) - { - var value = string.Join(joiner, PathParameters[parameterName]); - - // Check if we need to use a substring of the value. - if (numOfChars != 0 && numOfChars < value.Length) - { - value = value.Substring(0, numOfChars); - } - - if (op != "+" && op != "#" && PathParameters[parameterName].Count == 1) - { - value = Uri.EscapeDataString(value); - } - - value = start + value; - newContent.Append(value); - } - else - { - throw new ArgumentException( - string.Format("Path \"{0}\" misses a \"{1}\" parameter", Path, parameterName), Path); - } - } - - if (op == ";") - { - if (newContent[newContent.Length - 1] == '=') - { - newContent = newContent.Remove(newContent.Length - 1, 1); - } - newContent = newContent.Replace("=;", ";"); - } - restPath = restPath.Replace(matchStr, newContent.ToString()); - } - return restPath; - } - - /// Adds a parameter value. - /// Type of the parameter (must be 'Path' or 'Query'). - /// Parameter name. - /// Parameter value. - public void AddParameter(RequestParameterType type, string name, string value) - { - name.ThrowIfNull("name"); - if (value == null) - { - Logger.Warning("Add parameter should not get null values. type={0}, name={1}", type, name); - return; - } - switch (type) - { - case RequestParameterType.Path: - if (!PathParameters.ContainsKey(name)) - { - PathParameters[name] = new List { value }; - } - else - { - PathParameters[name].Add(value); - } - break; - case RequestParameterType.Query: - QueryParameters.Add(new KeyValuePair(name, value)); - break; - default: - throw new ArgumentOutOfRangeException("type"); - } - } - - /// Creates a new HTTP request message. - public HttpRequestMessage CreateRequest() - { - return new HttpRequestMessage(new HttpMethod(Method), BuildUri()); - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestParameterAttribute.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestParameterAttribute.cs deleted file mode 100644 index b4c08668b6b..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/RequestParameterAttribute.cs +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; - -namespace Google.Apis.Util -{ - /// - /// An attribute which is used to specially mark a property for reflective purposes, - /// assign a name to the property and indicate it's location in the request as either - /// in the path or query portion of the request URL. - /// - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] - public class RequestParameterAttribute : Attribute - { - private readonly string name; - private readonly RequestParameterType type; - - /// Gets the name of the parameter. - public string Name { get { return name; } } - - /// Gets the type of the parameter, Path or Query. - public RequestParameterType Type { get { return type; } } - - /// - /// Constructs a new property attribute to be a part of a REST URI. - /// This constructor uses as the parameter's type. - /// - /// - /// The name of the parameter. If the parameter is a path parameter this name will be used to substitute the - /// string value into the path, replacing {name}. If the parameter is a query parameter, this parameter will be - /// added to the query string, in the format "name=value". - /// - public RequestParameterAttribute(string name) - : this(name, RequestParameterType.Query) - { - - } - - /// Constructs a new property attribute to be a part of a REST URI. - /// - /// The name of the parameter. If the parameter is a path parameter this name will be used to substitute the - /// string value into the path, replacing {name}. If the parameter is a query parameter, this parameter will be - /// added to the query string, in the format "name=value". - /// - /// The type of the parameter, either Path, Query or UserDefinedQueries. - public RequestParameterAttribute(string name, RequestParameterType type) - { - this.name = name; - this.type = type; - } - } - - /// Describe the type of this parameter (Path, Query or UserDefinedQueries). - public enum RequestParameterType - { - /// A path parameter which is inserted into the path portion of the request URI. - Path, - - /// A query parameter which is inserted into the query portion of the request URI. - Query, - - /// - /// A group of user-defined parameters that will be added in to the query portion of the request URI. If this - /// type is being used, the name of the RequestParameterAttirbute is meaningless. - /// - UserDefinedQueries - } -} diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationCodeRequestUrl.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationCodeRequestUrl.cs index d608f991981..997bc1c3b31 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationCodeRequestUrl.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationCodeRequestUrl.cs @@ -14,13 +14,8 @@ You may obtain a copy of the License at limitations under the License. */ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - using Google.Apis.Requests; -using Google.Apis.Requests.Parameters; +using System; namespace Google.Apis.Auth.OAuth2.Requests { @@ -43,12 +38,8 @@ public AuthorizationCodeRequestUrl(Uri authorizationServerUrl) /// Creates a which is used to request the authorization code. public Uri Build() { - var builder = new RequestBuilder() - { - BaseUri = AuthorizationServerUrl - }; - ParameterUtils.InitParameters(builder, this); - return builder.BuildUri(); + // FIXME: Implement the code without using common RequestBuilder stuff. + return null; } } } diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationCodeTokenRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationCodeTokenRequest.cs index 2adaa41cd7e..82aa1f6765a 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationCodeTokenRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationCodeTokenRequest.cs @@ -14,7 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ -using System; +// FIXME: Write code to create an HttpRequestMessage for this. namespace Google.Apis.Auth.OAuth2.Requests { @@ -25,13 +25,11 @@ namespace Google.Apis.Auth.OAuth2.Requests public class AuthorizationCodeTokenRequest : TokenRequest { /// Gets or sets the authorization code received from the authorization server. - [Google.Apis.Util.RequestParameterAttribute("code")] public string Code { get; set; } /// /// Gets or sets the redirect URI parameter matching the redirect URI parameter in the authorization request. /// - [Google.Apis.Util.RequestParameterAttribute("redirect_uri")] public string RedirectUri { get; set; } /// diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationRequestUrl.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationRequestUrl.cs index 712797f1c2e..a20e6799c13 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationRequestUrl.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/AuthorizationRequestUrl.cs @@ -29,11 +29,9 @@ public class AuthorizationRequestUrl /// token for requesting an access token (implicit grant), or space separated registered extension /// values. See http://tools.ietf.org/html/rfc6749#section-3.1.1 for more details /// - [Google.Apis.Util.RequestParameterAttribute("response_type", Google.Apis.Util.RequestParameterType.Query)] public string ResponseType { get; set; } /// Gets or sets the client identifier. - [Google.Apis.Util.RequestParameterAttribute("client_id", Google.Apis.Util.RequestParameterType.Query)] public string ClientId { get; set; } /// @@ -41,21 +39,18 @@ public class AuthorizationRequestUrl /// client after a successful authorization grant, as specified in /// http://tools.ietf.org/html/rfc6749#section-3.1.2 or null for none. /// - [Google.Apis.Util.RequestParameterAttribute("redirect_uri", Google.Apis.Util.RequestParameterType.Query)] public string RedirectUri { get; set; } /// /// Gets or sets space-separated list of scopes, as specified in http://tools.ietf.org/html/rfc6749#section-3.3 /// or null for none. /// - [Google.Apis.Util.RequestParameterAttribute("scope", Google.Apis.Util.RequestParameterType.Query)] public string Scope { get; set; } /// /// Gets or sets the state (an opaque value used by the client to maintain state between the request and /// callback, as mentioned in http://tools.ietf.org/html/rfc6749#section-3.1.2.2 or null for none. /// - [Google.Apis.Util.RequestParameterAttribute("state", Google.Apis.Util.RequestParameterType.Query)] public string State { get; set; } private readonly Uri authorizationServerUrl; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleAssertionTokenRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleAssertionTokenRequest.cs index 57154e6da4f..3b38b66b4be 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleAssertionTokenRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleAssertionTokenRequest.cs @@ -15,6 +15,8 @@ limitations under the License. */ +// FIXME: Write code to create an HttpRequestMessage for this. + namespace Google.Apis.Auth.OAuth2.Requests { /// @@ -24,7 +26,6 @@ namespace Google.Apis.Auth.OAuth2.Requests public class GoogleAssertionTokenRequest : TokenRequest { /// Gets or sets the JWT (including signature). - [Google.Apis.Util.RequestParameterAttribute("assertion")] public string Assertion { get; set; } /// diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleAuthorizationCodeRequestUrl.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleAuthorizationCodeRequestUrl.cs index 54fbcf62df1..8fdd4a29260 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleAuthorizationCodeRequestUrl.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleAuthorizationCodeRequestUrl.cs @@ -17,6 +17,8 @@ limitations under the License. using System; using System.Collections.Generic; +// FIXME: Write code to create an HttpRequestMessage for this. + namespace Google.Apis.Auth.OAuth2.Requests { /// @@ -30,7 +32,6 @@ public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl /// Gets or sets the access type. Set online to request on-line access or offline to request /// off-line access or null for the default behavior. The default value is offline. /// - [Google.Apis.Util.RequestParameterAttribute("access_type", Google.Apis.Util.RequestParameterType.Query)] public string AccessType { get; set; } /// @@ -39,14 +40,12 @@ public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl /// See OpenIDConnect documentation /// for details. /// - [Google.Apis.Util.RequestParameterAttribute("prompt", Google.Apis.Util.RequestParameterType.Query)] public string Prompt { get; set; } /// /// Gets or sets prompt for consent behavior auto to request auto-approval orforce to force the /// approval UI to show, or null for the default behavior. /// - [Google.Apis.Util.RequestParameterAttribute("approval_prompt", Google.Apis.Util.RequestParameterType.Query)] [Obsolete("Unused for Google OpenID; use the 'Prompt' property instead.")] public string ApprovalPrompt { get; set; } @@ -56,7 +55,6 @@ public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl /// hint to the Authentication Server. Passing this hint will either pre-fill the email box on the sign-in form /// or select the proper multi-login session, thereby simplifying the login flow. /// - [Google.Apis.Util.RequestParameterAttribute("login_hint", Google.Apis.Util.RequestParameterType.Query)] public string LoginHint { get; set; } /// @@ -66,8 +64,6 @@ public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl /// authorizations granted to this user/application combination for other scopes. /// /// Currently unsupported for installed apps. - [Google.Apis.Util.RequestParameterAttribute("include_granted_scopes", - Google.Apis.Util.RequestParameterType.Query)] public string IncludeGrantedScopes { get; set; } /// @@ -75,7 +71,6 @@ public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl /// a random value generated by your app that enables replay protection. /// See https://developers.google.com/identity/protocols/OpenIDConnect for more details. /// - [Google.Apis.Util.RequestParameterAttribute("nonce", Google.Apis.Util.RequestParameterType.Query)] public string Nonce { get; set; } /// @@ -86,8 +81,6 @@ public class GoogleAuthorizationCodeRequestUrl : AuthorizationCodeRequestUrl /// The name of this parameter is used only for the constructor and will not end up in the resultant query /// string. /// - [Google.Apis.Util.RequestParameterAttribute("user_defined_query_params", - Google.Apis.Util.RequestParameterType.UserDefinedQueries)] public IEnumerable> UserDefinedQueryParams { get; set; } /// diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleRevokeTokenRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleRevokeTokenRequest.cs index a6be5caedf9..c1ce461d379 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleRevokeTokenRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/GoogleRevokeTokenRequest.cs @@ -16,8 +16,7 @@ limitations under the License. using System; -using Google.Apis.Requests; -using Google.Apis.Requests.Parameters; +// FIXME: Write code to create an HttpRequestMessage for this. namespace Google.Apis.Auth.OAuth2.Requests { @@ -35,7 +34,6 @@ public Uri RevokeTokenUrl } /// Gets or sets the token to revoke. - [Google.Apis.Util.RequestParameterAttribute("token")] public string Token { get; set; } public GoogleRevokeTokenRequest(Uri revokeTokenUrl) @@ -46,12 +44,8 @@ public GoogleRevokeTokenRequest(Uri revokeTokenUrl) /// Creates a which is used to request the authorization code. public Uri Build() { - var builder = new RequestBuilder() - { - BaseUri = revokeTokenUrl - }; - ParameterUtils.InitParameters(builder, this); - return builder.BuildUri(); + // FIXME: Implement the code without using common RequestBuilder stuff. + return null; } } } diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/IamSignBlobRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/IamSignBlobRequest.cs index 5a916aaefb8..ac9fc5d60ee 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/IamSignBlobRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/IamSignBlobRequest.cs @@ -17,6 +17,8 @@ limitations under the License. using Newtonsoft.Json; using System.Collections.Generic; +// FIXME: Write code to create an HttpRequestMessage for this. + namespace Google.Apis.Auth.OAuth2.Requests { internal class IamSignBlobRequest diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationAccessTokenRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationAccessTokenRequest.cs index 14f167ff271..c5219ea9a49 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationAccessTokenRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationAccessTokenRequest.cs @@ -17,6 +17,8 @@ limitations under the License. using Newtonsoft.Json; using System.Collections.Generic; +// FIXME: Write code to create an HttpRequestMessage for this. + namespace Google.Apis.Auth.OAuth2.Requests { /// diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationOIdCTokenRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationOIdCTokenRequest.cs index 1b3293d779b..43a74e26c6e 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationOIdCTokenRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationOIdCTokenRequest.cs @@ -16,6 +16,8 @@ limitations under the License. using Newtonsoft.Json; +// FIXME: Write code to create an HttpRequestMessage for this. + namespace Google.Apis.Auth.OAuth2.Requests { /// diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationRequest.cs index 9b7714e2733..539f4224424 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/ImpersonationRequest.cs @@ -17,6 +17,8 @@ limitations under the License. using Newtonsoft.Json; using System.Collections.Generic; +// FIXME: Write code to create an HttpRequestMessage for this. + namespace Google.Apis.Auth.OAuth2.Requests { internal abstract class ImpersonationRequest diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/RefreshTokenRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/RefreshTokenRequest.cs index 6456406400a..6fb27334d6a 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/RefreshTokenRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/RefreshTokenRequest.cs @@ -14,6 +14,8 @@ You may obtain a copy of the License at limitations under the License. */ +// FIXME: Write code to create an HttpRequestMessage for this. + namespace Google.Apis.Auth.OAuth2.Requests { /// @@ -23,7 +25,6 @@ namespace Google.Apis.Auth.OAuth2.Requests public class RefreshTokenRequest : TokenRequest { /// Gets or sets the Refresh token issued to the client. - [Google.Apis.Util.RequestParameterAttribute("refresh_token")] public string RefreshToken { get; set; } /// diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs index dbf3f1b37ad..6e799b409b4 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs @@ -17,7 +17,6 @@ limitations under the License. using Google.Apis.Auth.OAuth2.Responses; using Google.Apis.Json; using Google.Apis.Logging; -using Google.Apis.Requests.Parameters; using Google.Apis.Util; using System.Net.Http; using System.Net.Http.Headers; @@ -82,9 +81,10 @@ internal static async Task PostFormAsync(this object request, Htt string url, AuthenticationHeaderValue authenticationHeaderValue, IClock clock, ILogger logger, CancellationToken taskCancellationToken) { + // TODO: Reimplement without the common parameter code we had before. var httpRequest = new HttpRequestMessage(HttpMethod.Post, url) { - Content = ParameterUtils.CreateFormUrlEncodedContent(request) + Content = new StringContent("FIXME") }; if (authenticationHeaderValue is not null) { diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsTokenRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsTokenRequest.cs index f941a5c688b..5617eb01486 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsTokenRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsTokenRequest.cs @@ -14,7 +14,6 @@ You may obtain a copy of the License at limitations under the License. */ -using Google.Apis.Util; using System.Net.Http.Headers; namespace Google.Apis.Auth.OAuth2.Requests @@ -45,47 +44,40 @@ internal StsTokenRequest( /// Gets the grant type for this request. /// Only urn:ietf:params:oauth:grant-type:token-exchange is currently supported. /// - [RequestParameter("grant_type")] public string GrantType { get; } /// /// The audience for which the requested token is intended. For instance: /// "//iam.googleapis.com/projects/my-project-id/locations/global/workloadIdentityPools/my-pool-id/providers/my-provider-id" /// - [RequestParameter("audience")] public string Audience { get; } /// /// The space-delimited list of desired scopes for the requested token as defined in /// http://tools.ietf.org/html/rfc6749#section-3.3. /// - [RequestParameter("scope")] public string Scope { get; } /// /// The type of the requested security token. /// Only urn:ietf:params:oauth:token-type:access_token is currently supported. /// - [RequestParameter("requested_token_type")] public string RequestedTokenType { get; } /// /// In terms of Google 3PI support, this is the 3PI credential. /// - [RequestParameter("subject_token")] public string SubjectToken { get; } /// /// The subject token type. /// - [RequestParameter("subject_token_type")] public string SubjectTokenType { get; } /// /// Google specific STS token request options. /// May be null. /// - [RequestParameter("options")] public string GoogleOptions { get; } /// @@ -93,5 +85,7 @@ internal StsTokenRequest( /// May be null. /// public AuthenticationHeaderValue AuthenticationHeader { get; } + + // FIXME: Probably hand-write code to create an appropriate HttpRequestMessage. } } diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/TokenRequest.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/TokenRequest.cs index f18c97420ff..2f4e3167b90 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/TokenRequest.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/TokenRequest.cs @@ -14,13 +14,10 @@ You may obtain a copy of the License at limitations under the License. */ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - namespace Google.Apis.Auth.OAuth2.Requests { + // FIXME: Write code to create an HttpRequestMessage for this. + /// /// OAuth 2.0 request for an access token as specified in http://tools.ietf.org/html/rfc6749#section-4. /// @@ -29,22 +26,18 @@ public class TokenRequest /// /// Gets or sets space-separated list of scopes as specified in http://tools.ietf.org/html/rfc6749#section-3.3. /// - [Google.Apis.Util.RequestParameterAttribute("scope")] public string Scope { get; set; } /// /// Gets or sets the Grant type. Sets authorization_code or password or client_credentials /// or refresh_token or absolute URI of the extension grant type. /// - [Google.Apis.Util.RequestParameterAttribute("grant_type")] public string GrantType { get; set; } /// Gets or sets the client Identifier. - [Google.Apis.Util.RequestParameterAttribute("client_id")] public string ClientId { get; set; } /// Gets or sets the client Secret. - [Google.Apis.Util.RequestParameterAttribute("client_secret")] public string ClientSecret { get; set; } } } diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/TokenRequestExtenstions.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/TokenRequestExtenstions.cs index 90b089c0456..64356552081 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/TokenRequestExtenstions.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/TokenRequestExtenstions.cs @@ -22,8 +22,10 @@ limitations under the License. namespace Google.Apis.Auth.OAuth2.Requests { + // TODO: Only use this if we really need to... + /// Extension methods to . - public static class TokenRequestExtenstions + public static class TokenRequestExtensions { /// /// Executes the token request in order to receive a From 3dc9b115838dcb59cf78f285dc01f16f4206f3b6 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:13:31 +0100 Subject: [PATCH 05/11] Remove FileDataStore This forces the user to specify a data store when creating a GoogleWebAuthorizationBroken, but that's probably okay. --- .../ExistingDependencies/FileDataStore.cs | 183 ------------------ .../OAuth2/GoogleWebAuthorizationBroker.cs | 28 +-- 2 files changed, 6 insertions(+), 205 deletions(-) delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/FileDataStore.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/FileDataStore.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/FileDataStore.cs deleted file mode 100644 index 06fe1b44fed..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/FileDataStore.cs +++ /dev/null @@ -1,183 +0,0 @@ -/* -Copyright 2017 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// TODO: This does not support UWP Storage. - -using Google.Apis.Json; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Google.Apis.Util.Store -{ - /// - /// File data store that implements . This store creates a different file for each - /// combination of type and key. This file data store stores a JSON format of the specified object. - /// - public class FileDataStore : IDataStore - { - private const string XdgDataHomeSubdirectory = "google-filedatastore"; - private static readonly Task CompletedTask = Task.FromResult(0); - - readonly string folderPath; - /// Gets the full folder path. - public string FolderPath { get { return folderPath; } } - - /// - /// Constructs a new file data store. If fullPath is false the path will be used as relative to - /// Environment.SpecialFolder.ApplicationData" on Windows, or $HOME on Linux and MacOS, - /// otherwise the input folder will be treated as absolute. - /// The folder is created if it doesn't exist yet. - /// - /// Folder path. - /// - /// Defines whether the folder parameter is absolute or relative to - /// Environment.SpecialFolder.ApplicationData on Windows, or$HOME on Linux and MacOS. - /// - public FileDataStore(string folder, bool fullPath = false) - { - folderPath = fullPath - ? folder - : Path.Combine(GetHomeDirectory(), folder); - if (!Directory.Exists(folderPath)) - { - Directory.CreateDirectory(folderPath); - } - } - - private string GetHomeDirectory() - { - string appData = Environment.GetEnvironmentVariable("APPDATA"); - if (!string.IsNullOrEmpty(appData)) - { - // This is almost certainly windows. - // This path must be the same between the desktop FileDataStore and this netstandard FileDataStore. - return appData; - } - string home = Environment.GetEnvironmentVariable("HOME"); - if (!string.IsNullOrEmpty(home)) - { - // This is almost certainly Linux or MacOS. - // Follow the XDG Base Directory Specification: https://specifications.freedesktop.org/basedir-spec/latest/index.html - // Store data in subdirectory of $XDG_DATA_HOME if it exists, defaulting to $HOME/.local/share if not set. - string xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); - if (string.IsNullOrEmpty(xdgDataHome)) - { - xdgDataHome = Path.Combine(home, ".local", "share"); - } - return Path.Combine(xdgDataHome, XdgDataHomeSubdirectory); - } - throw new PlatformNotSupportedException("Relative FileDataStore paths not supported on this platform."); - } - - /// - /// Stores the given value for the given key. It creates a new file (named ) in - /// . - /// - /// The type to store in the data store. - /// The key. - /// The value to store in the data store. - public Task StoreAsync(string key, T value) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException("Key MUST have a value"); - } - - var serialized = NewtonsoftJsonSerializer.Instance.Serialize(value); - var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T))); - File.WriteAllText(filePath, serialized); - return CompletedTask; - } - - /// - /// Deletes the given key. It deletes the named file in - /// . - /// - /// The key to delete from the data store. - public Task DeleteAsync(string key) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException("Key MUST have a value"); - } - - var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T))); - if (File.Exists(filePath)) - { - File.Delete(filePath); - } - return CompletedTask; - } - - /// - /// Returns the stored value for the given key or null if the matching file ( - /// in doesn't exist. - /// - /// The type to retrieve. - /// The key to retrieve from the data store. - /// The stored object. - public Task GetAsync(string key) - { - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentException("Key MUST have a value"); - } - - TaskCompletionSource tcs = new TaskCompletionSource(); - var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T))); - if (File.Exists(filePath)) - { - try - { - var obj = File.ReadAllText(filePath); - tcs.SetResult(NewtonsoftJsonSerializer.Instance.Deserialize(obj)); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - } - else - { - tcs.SetResult(default(T)); - } - return tcs.Task; - } - - /// - /// Clears all values in the data store. This method deletes all files in . - /// - public Task ClearAsync() - { - if (Directory.Exists(folderPath)) - { - Directory.Delete(folderPath, true); - Directory.CreateDirectory(folderPath); - } - - return CompletedTask; - } - - /// Creates a unique stored key based on the key and the class type. - /// The object key. - /// The type to store or retrieve. - public static string GenerateStoredKey(string key, Type t) - { - return string.Format("{0}-{1}", t.FullName, key); - } - } -} diff --git a/Src/Support/Google.Apis.Auth/OAuth2/GoogleWebAuthorizationBroker.cs b/Src/Support/Google.Apis.Auth/OAuth2/GoogleWebAuthorizationBroker.cs index 0d795f151fc..ccf86f93fa9 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/GoogleWebAuthorizationBroker.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/GoogleWebAuthorizationBroker.cs @@ -21,6 +21,7 @@ limitations under the License. using System.Threading.Tasks; using Google.Apis.Auth.OAuth2.Flows; +using Google.Apis.Util; using Google.Apis.Util.Store; namespace Google.Apis.Auth.OAuth2 @@ -34,22 +35,10 @@ namespace Google.Apis.Auth.OAuth2 /// public class GoogleWebAuthorizationBroker { - // It's unforunate this is a public field. But it cannot be changed due to backward compatibility. - /// The folder which is used by the . - /// - /// The reason that this is not 'private const' is that a user can change it and store the credentials in a - /// different location. - /// - public static string Folder = "Google.Apis.Auth"; - /// /// Asynchronously authorizes the specified user. /// Requires user interaction; see remarks for more details. /// - /// - /// In case no data store is specified, will be used by - /// default. - /// /// The client secrets. /// /// The scopes which indicate the Google API access your application is requesting. @@ -75,10 +64,6 @@ public static async Task AuthorizeAsync(ClientSecrets clientSecr /// Asynchronously authorizes the specified user. /// Requires user interaction; see remarks for more details. /// - /// - /// In case no data store is specified, will be used by - /// default. - /// /// /// The client secrets stream. The authorization code flow constructor is responsible for disposing the stream. /// @@ -87,12 +72,12 @@ public static async Task AuthorizeAsync(ClientSecrets clientSecr /// /// The user to authorize. /// Cancellation token to cancel an operation. - /// The data store, if not specified a file data store will be used. + /// The data store. /// The code receiver, if not specified a local server code receiver will be used. /// User credential. public static async Task AuthorizeAsync(Stream clientSecretsStream, IEnumerable scopes, string user, CancellationToken taskCancellationToken, - IDataStore dataStore = null, ICodeReceiver codeReceiver = null) + IDataStore dataStore, ICodeReceiver codeReceiver = null) { var initializer = new GoogleAuthorizationCodeFlow.Initializer { @@ -136,16 +121,15 @@ public static async Task ReauthorizeAsync(UserCredential userCredential, /// /// The user to authorize. /// Cancellation token to cancel an operation. - /// The data store, if not specified a file data store will be used. + /// The data store. Must not be null. /// The code receiver, if not specified a local server code receiver will be used. /// User credential. public static async Task AuthorizeAsync( GoogleAuthorizationCodeFlow.Initializer initializer, IEnumerable scopes, string user, - CancellationToken taskCancellationToken, IDataStore dataStore = null, - ICodeReceiver codeReceiver = null) + CancellationToken taskCancellationToken, IDataStore dataStore, ICodeReceiver codeReceiver = null) { initializer.Scopes = scopes; - initializer.DataStore = dataStore ?? new FileDataStore(Folder); + initializer.DataStore = dataStore.ThrowIfNull(nameof(dataStore)); var flow = new GoogleAuthorizationCodeFlow(initializer); codeReceiver = codeReceiver ?? new LocalServerCodeReceiver(); From 5f2d31c2c300bc9dc97645498014650d0189a5b0 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:30:52 +0100 Subject: [PATCH 06/11] Remove unused classes --- .../HttpRequestMessageExtenstions.cs | 97 -------------- .../HttpResponseMessageExtensions.cs | 82 ------------ .../ExistingDependencies/IClientService.cs | 92 ------------- .../IClientServiceRequest.cs | 80 ------------ .../ExistingDependencies/UriPatcher.cs | 122 ------------------ 5 files changed, 473 deletions(-) delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpRequestMessageExtenstions.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpResponseMessageExtensions.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IClientService.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IClientServiceRequest.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/UriPatcher.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpRequestMessageExtenstions.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpRequestMessageExtenstions.cs deleted file mode 100644 index b8619b138a0..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpRequestMessageExtenstions.cs +++ /dev/null @@ -1,97 +0,0 @@ -/* -Copyright 2013 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System.IO; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; - -using System.IO.Compression; - -using Google.Apis.Services; - -namespace Google.Apis.Requests -{ - /// Extension methods to . - static class HttpRequestMessageExtenstions - { - /// - /// Sets the content of the request by the given body and the the required GZip configuration. - /// - /// The request. - /// The service. - /// The body of the future request. If null do nothing. - /// - /// Indicates if the content will be wrapped in a GZip stream, or a regular string stream will be used. - /// - internal static void SetRequestSerailizedContent(this HttpRequestMessage request, - IClientService service, object body, bool gzipEnabled) - { - if (body == null) - { - return; - } - HttpContent content = null; - - var mediaType = "application/" + service.Serializer.Format; - var serializedObject = service.SerializeObject(body); - if (gzipEnabled) - { - content = CreateZipContent(serializedObject); - content.Headers.ContentType = new MediaTypeHeaderValue(mediaType) - { - CharSet = Encoding.UTF8.WebName - }; - } - else - { - content = new StringContent(serializedObject, Encoding.UTF8, mediaType); - } - - request.Content = content; - } - - /// Creates a GZip content based on the given content. - /// Content to GZip. - /// GZiped HTTP content. - internal static HttpContent CreateZipContent(string content) - { - var stream = CreateGZipStream(content); - var sc = new StreamContent(stream); - sc.Headers.ContentEncoding.Add("gzip"); - return sc; - } - - /// Creates a GZip stream by the given serialized object. - private static Stream CreateGZipStream(string serializedObject) - { - byte[] bytes = System.Text.Encoding.UTF8.GetBytes(serializedObject); - using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) - { - using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true)) - { - gzip.Write(bytes, 0, bytes.Length); - } - - // Reset the stream to the beginning. It doesn't work otherwise! - ms.Position = 0; - byte[] compressed = new byte[ms.Length]; - ms.Read(compressed, 0, compressed.Length); - return new MemoryStream(compressed); - } - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpResponseMessageExtensions.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpResponseMessageExtensions.cs deleted file mode 100644 index de1dc030a1d..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpResponseMessageExtensions.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Google.Apis.Requests; -using Google.Apis.Util; -using Newtonsoft.Json; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Google.Apis.Responses -{ - internal static class HttpResponseMessageExtensions - { - /// - /// Attempts to deserialize a from the . - /// - /// - /// This method will throw a if: - /// - /// The or its are null. - /// Or the deserialization attempt throws a . - /// Or the deserilization attempt returns null. - /// - /// Any exception thrown while reading the - /// will be bubbled up. - /// Otherwie this method will returned the deserialized . - /// - internal static async Task DeserializeErrorAsync(this HttpResponseMessage response, string name, ISerializer serializer) - { - if (response?.Content is null) - { - // We throw GoogleApiException (instead of NullArgumentException) - // as a more friendly way to signal users that something went wrong, - // which in this case is very unlikely to have been because of a bug - // in calling code. - throw new GoogleApiException(name) - { - HttpStatusCode = response?.StatusCode ?? default - }; - } - // If we can't even read the response, let that exception bubble up. - // Note that very likely this will never happen here, as we are usually reading from a buffer. - // If there were actual problems reading the content, HttpClient has already thrown when - // filling the buffer in. - var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - RequestError error; - try - { - error = serializer.Deserialize>(responseText)?.Error; - } - catch (JsonException ex) - { - throw new GoogleApiException(name, message: null, ex) - { - HttpStatusCode = response.StatusCode, - Error = new RequestError { ErrorResponseContent = responseText } - }; - } - if (error is null) - { - throw new GoogleApiException(name) - { - HttpStatusCode = response.StatusCode, - Error = new RequestError { ErrorResponseContent = responseText } - }; - } - error.ErrorResponseContent = responseText; - return error; - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientService.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientService.cs deleted file mode 100644 index 6d05f14c429..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientService.cs +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2010 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; - -using Google.Apis.Discovery; -using Google.Apis.Http; -using Google.Apis.Requests; - -namespace Google.Apis.Services -{ - /// - /// Client service contains all the necessary information a Google service requires. - /// Each concrete has a reference to a service for - /// important properties like API key, application name, base Uri, etc. - /// This service interface also contains serialization methods to serialize an object to stream and deserialize a - /// stream into an object. - /// - public interface IClientService : IDisposable - { - /// Gets the HTTP client which is used to create requests. - ConfigurableHttpClient HttpClient { get; } - - /// - /// Gets a HTTP client initializer which is able to custom properties on - /// and - /// . - /// - IConfigurableHttpClientInitializer HttpClientInitializer { get; } - - /// Gets the service name. - string Name { get; } - - /// Gets the BaseUri of the service. All request paths should be relative to this URI. - string BaseUri { get; } - - /// Gets the BasePath of the service. - string BasePath { get; } - - /// Gets the supported features by this service. - IList Features { get; } - - /// Gets or sets whether this service supports GZip. - bool GZipEnabled { get; } - - /// Gets the API-Key (DeveloperKey) which this service uses for all requests. - string ApiKey { get; } - - /// Gets the application name to be used in the User-Agent header. - string ApplicationName { get; } - - /// - /// Sets the content of the request by the given body and the this service's configuration. - /// First the body object is serialized by the Serializer and then, if GZip is enabled, the content will be - /// wrapped in a GZip stream, otherwise a regular string stream will be used. - /// - void SetRequestSerailizedContent(HttpRequestMessage request, object body); - - #region Serialization Methods - - /// Gets the Serializer used by this service. - ISerializer Serializer { get; } - - /// Serializes an object into a string representation. - string SerializeObject(object data); - - /// Deserializes a response into the specified object. - Task DeserializeResponse(HttpResponseMessage response); - - /// Deserializes an error response into a object. - /// If no error is found in the response. - Task DeserializeError(HttpResponseMessage response); - - #endregion - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientServiceRequest.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientServiceRequest.cs deleted file mode 100644 index 43c00957d53..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/IClientServiceRequest.cs +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -using Google.Apis.Discovery; -using Google.Apis.Services; - -namespace Google.Apis.Requests -{ - /// A client service request which supports both sync and async execution to get the stream. - public interface IClientServiceRequest - { - /// Gets the name of the method to which this request belongs. - string MethodName { get; } - - /// Gets the rest path of this request. - string RestPath { get; } - - /// Gets the HTTP method of this request. - string HttpMethod { get; } - - /// Gets the parameters information for this specific request. - IDictionary RequestParameters { get; } - - /// Gets the service which is related to this request. - IClientService Service { get; } - - /// Creates a HTTP request message with all path and query parameters, ETag, etc. - /// - /// If null use the service default GZip behavior. Otherwise indicates if GZip is enabled or disabled. - /// - HttpRequestMessage CreateRequest(Nullable overrideGZipEnabled = null); - - /// Executes the request asynchronously and returns the result stream. - Task ExecuteAsStreamAsync(); - - /// Executes the request asynchronously and returns the result stream. - /// A cancellation token to cancel operation. - Task ExecuteAsStreamAsync(CancellationToken cancellationToken); - - /// Executes the request and returns the result stream. - Stream ExecuteAsStream(); - } - - /// - /// A client service request which inherits from and represents a specific - /// service request with the given response type. It supports both sync and async execution to get the response. - /// - public interface IClientServiceRequest : IClientServiceRequest - { - /// Executes the request asynchronously and returns the result object. - Task ExecuteAsync(); - - /// Executes the request asynchronously and returns the result object. - /// A cancellation token to cancel operation. - Task ExecuteAsync(CancellationToken cancellationToken); - - /// Executes the request and returns the result object. - TResponse Execute(); - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/UriPatcher.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/UriPatcher.cs deleted file mode 100644 index a93dbd39f31..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/UriPatcher.cs +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2016 Google Inc - -Licensed under the Apache License, Version 2.0(the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Reflection; - -namespace Google.Apis.Util -{ - /// - /// Workarounds for some unfortunate behaviors in the .NET Framework's - /// implementation of System.Uri - /// - /// - /// UriPatcher lets us work around some unfortunate behaviors in the .NET Framework's - /// implementation of System.Uri. - /// - /// == Problem 1: Slashes and dots - /// - /// Prior to .NET 4.5, System.Uri would always unescape "%2f" ("/") and "%5c" ("\\"). - /// Relative path components were also compressed. - /// - /// As a result, this: "http://www.example.com/.%2f.%5c./" - /// ... turned into this: "http://www.example.com/" - /// - /// This breaks API requests where slashes or dots appear in path parameters. Such requests - /// arise, for example, when these characters appear in the name of a GCS object. - /// - /// == Problem 2: Fewer unreserved characters - /// - /// Unless IDN/IRI parsing is enabled -- which it is not, by default, prior to .NET 4.5 -- - /// Uri.EscapeDataString uses the set of "unreserved" characters from RFC 2396 instead of the - /// newer, *smaller* list from RFC 3986. We build requests using URI templating as described - /// by RFC 6570, which specifies that the latter definition (RFC 3986) should be used. - /// - /// This breaks API requests with parameters including any of: !*'() - /// - /// == Solutions - /// - /// Though the default behaviors changed in .NET 4.5, these "quirks" remain for compatibility - /// unless the application explicitly targets the new runtime. Usually, that means adding a - /// TargetFrameworkAttribute to the entry assembly. - /// - /// Applications running on .NET 4.0 or later can also set "DontUnescapePathDotsAndSlashes" - /// and enable IDN/IRI parsing using app.config or web.config. - /// - /// As a class library, we can't control app.config or the entry assembly, so we can't take - /// either approach. Instead, we resort to reflection trickery to try to solve these problems - /// if we detect they exist. Sorry. - /// - public static class UriPatcher - { - /// - /// Patch URI quirks in System.Uri. See class summary for details. - /// - public static void PatchUriQuirks() - { - var uriParser = typeof(System.Uri).GetTypeInfo().Assembly.GetType("System.UriParser"); - if (uriParser == null) { return; } - - // Is "%2f" unescaped for http: or https: URIs? - if (new Uri("http://example.com/%2f").AbsolutePath == "//" || - new Uri("https://example.com/%2f").AbsolutePath == "//") - { - // Call System.UriParser.Http[s]Uri.SetUpdatableFlags(UriSyntaxFlags.None) - // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/_UriSyntax.cs#L87 - // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/_UriSyntax.cs#L77 - // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/_UriSyntax.cs#L352 - - var setUpdatableFlagsMethod = uriParser.GetMethod("SetUpdatableFlags", - BindingFlags.Instance | BindingFlags.NonPublic); - if (setUpdatableFlagsMethod != null) - { - Action setUriParserUpdatableFlags = (fieldName) => - { - var parserField = uriParser.GetField(fieldName, - BindingFlags.Static | BindingFlags.NonPublic); - if (parserField == null) { return; } - var parserInstance = parserField.GetValue(null); - if (parserInstance == null) { return; } - setUpdatableFlagsMethod.Invoke(parserInstance, new object[] { 0 }); - }; - - // Make the change for the http: and https: URI parsers. - setUriParserUpdatableFlags("HttpUri"); - setUriParserUpdatableFlags("HttpsUri"); - } - } - - // Is "*" considered "unreserved"? - if (Uri.EscapeDataString("*") == "*") - { - // Set UriParser.s_QuirksVersion to at least UriQuirksVersion.V3 - // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/_UriSyntax.cs#L114 - // https://github.com/Microsoft/referencesource/blob/d925d870f3cb3f6a/System/net/System/UriHelper.cs#L701 - - var quirksField = uriParser.GetField("s_QuirksVersion", - BindingFlags.Static | BindingFlags.NonPublic); - if (quirksField != null) - { - int quirksVersion = (int)quirksField.GetValue(null); - if (quirksVersion <= 2) - { - quirksField.SetValue(null, 3); - } - } - } - } - } -} From d6e692b2b783ce39c93d2c68504909b342932ab9 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:36:13 +0100 Subject: [PATCH 07/11] Remove NewtonsoftJsonSerializer We'll need a replacement "default serializer" but that doesn't need to be public. --- .../ExistingDependencies/JsonExplicitNull.cs | 100 -------- .../JsonExplicitNullAttribute.cs | 26 -- .../NewtonsoftJsonSerializer.cs | 231 ------------------ .../ReplacementSerializer.cs | 19 ++ .../Google.Apis.Auth/JsonWebSignature.cs | 2 +- ...AccountCredential.AwsSecurityCredential.cs | 3 +- .../OAuth2/AwsExternalAccountCredential.cs | 3 +- .../OAuth2/DefaultCredentialProvider.cs | 9 +- .../FileSourcedExternalAccountCredential.cs | 3 +- .../Flows/GoogleAuthorizationCodeFlow.cs | 3 +- .../OAuth2/GoogleClientSecrets.cs | 5 +- .../OAuth2/Requests/RequestExtensions.cs | 5 +- .../OAuth2/Requests/StsRequestTokenBuilder.cs | 3 +- .../OAuth2/Responses/TokenResponse.cs | 5 +- .../OAuth2/ServiceAccountCredential.cs | 5 +- .../UrlSourcedExternalAccountCredential.cs | 3 +- Src/Support/Google.Apis.Auth/SignedToken.cs | 5 +- 17 files changed, 51 insertions(+), 379 deletions(-) delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNull.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNullAttribute.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/NewtonsoftJsonSerializer.cs create mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ReplacementSerializer.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNull.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNull.cs deleted file mode 100644 index 22ca29405d7..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNull.cs +++ /dev/null @@ -1,100 +0,0 @@ -/* -Copyright 2010 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Google.Apis.Json -{ - /// - /// Provides values which are explicitly expressed as null when converted to JSON. - /// - public static class JsonExplicitNull - { - /// - /// Get an that is explicitly expressed as null when converted to JSON. - /// - /// An that is explicitly expressed as null when converted to JSON. - public static IList ForIList() => ExplicitNullList.Instance; - - [JsonExplicitNull] - private sealed class ExplicitNullList : IList - { - public static ExplicitNullList Instance = new ExplicitNullList(); - - public T this[int index] - { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } - - public int Count { get { throw new NotSupportedException(); } } - - public bool IsReadOnly { get { throw new NotSupportedException(); } } - - public void Add(T item) - { - throw new NotSupportedException(); - } - - public void Clear() - { - throw new NotSupportedException(); - } - - public bool Contains(T item) - { - throw new NotSupportedException(); - } - - public void CopyTo(T[] array, int arrayIndex) - { - throw new NotSupportedException(); - } - - public IEnumerator GetEnumerator() - { - throw new NotSupportedException(); - } - - public int IndexOf(T item) - { - throw new NotSupportedException(); - } - - public void Insert(int index, T item) - { - throw new NotSupportedException(); - } - - public bool Remove(T item) - { - throw new NotSupportedException(); - } - - public void RemoveAt(int index) - { - throw new NotSupportedException(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotSupportedException(); - } - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNullAttribute.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNullAttribute.cs deleted file mode 100644 index 6a4da6a7cd0..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/JsonExplicitNullAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright 2016 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; - -namespace Google.Apis.Json -{ - /// - /// All values of a type with this attribute are represented as a literal null in JSON. - /// - [AttributeUsage(AttributeTargets.Class)] - public class JsonExplicitNullAttribute : Attribute { } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/NewtonsoftJsonSerializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/NewtonsoftJsonSerializer.cs deleted file mode 100644 index 0c71c925c34..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/NewtonsoftJsonSerializer.cs +++ /dev/null @@ -1,231 +0,0 @@ -/* -Copyright 2017 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using Google.Apis.Util; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using System; -using System.IO; -using System.Reflection; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Google.Apis.Json -{ - /// - /// A JSON converter which honers RFC 3339 and the serialized date is accepted by Google services. - /// - public class RFC3339DateTimeConverter : JsonConverter - { - /// - public override bool CanRead => false; - - /// - public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, - JsonSerializer serializer) - { - throw new NotImplementedException("Unnecessary because CanRead is false."); - } - - /// - public override bool CanConvert(Type objectType) => - // Convert DateTime only. - objectType == typeof(DateTime) || objectType == typeof(Nullable); - - /// - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (value != null) - { - DateTime date = (DateTime)value; - serializer.Serialize(writer, Utilities.ConvertToRFC3339(date)); - } - } - } - - /// - /// A JSON converter to write null literals into JSON when explicitly requested. - /// - public class ExplicitNullConverter : JsonConverter - { - /// - public override bool CanRead => false; - - /// - public override bool CanConvert(Type objectType) => objectType.GetTypeInfo().GetCustomAttributes(typeof(JsonExplicitNullAttribute), false).Any(); - - /// - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new NotImplementedException("Unnecessary because CanRead is false."); - } - - /// - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteNull(); - } - - /// - /// A JSON contract resolver to apply and as necessary. - /// - /// - /// Using a contract resolver is recommended in the Json.NET performance tips: https://www.newtonsoft.com/json/help/html/Performance.htm#JsonConverters - /// - public class NewtonsoftJsonContractResolver : DefaultContractResolver - { - private static readonly JsonConverter DateTimeConverter = new RFC3339DateTimeConverter(); - private static readonly JsonConverter ExplicitNullConverter = new ExplicitNullConverter(); - - /// - protected override JsonContract CreateContract(Type objectType) - { - JsonContract contract = base.CreateContract(objectType); - - if (DateTimeConverter.CanConvert(objectType)) - { - contract.Converter = DateTimeConverter; - } - else if (ExplicitNullConverter.CanConvert(objectType)) - { - contract.Converter = ExplicitNullConverter; - } - - return contract; - } - } - - /// Class for serialization and deserialization of JSON documents using the Newtonsoft Library. - public class NewtonsoftJsonSerializer : IJsonSerializer - { - private readonly JsonSerializerSettings settings; - private readonly JsonSerializer serializer; - - /// The default instance of the Newtonsoft JSON Serializer, with default settings. - public static NewtonsoftJsonSerializer Instance { get; } = new NewtonsoftJsonSerializer(); - - /// - /// Constructs a new instance with the default serialization settings, equivalent to . - /// - public NewtonsoftJsonSerializer() : this(CreateDefaultSettings()) - { - } - - /// - /// Constructs a new instance with the given settings. - /// - /// The settings to apply when serializing and deserializing. Must not be null. - public NewtonsoftJsonSerializer(JsonSerializerSettings settings) - { - Utilities.ThrowIfNull(settings, nameof(settings)); - this.settings = settings; - serializer = JsonSerializer.Create(settings); - } - - /// - /// Creates a new instance of with the same behavior - /// as the ones used in . This method is expected to be used to construct - /// settings which are then passed to . - /// - /// A new set of default settings. - public static JsonSerializerSettings CreateDefaultSettings() => - new JsonSerializerSettings - { - NullValueHandling = NullValueHandling.Ignore, - MetadataPropertyHandling = MetadataPropertyHandling.Ignore, - ContractResolver = new NewtonsoftJsonContractResolver() - }; - - /// - public string Format => "json"; - - /// - public void Serialize(object obj, Stream target) - { - using (var writer = new StreamWriter(target)) - { - if (obj == null) - { - obj = string.Empty; - } - serializer.Serialize(writer, obj); - } - } - - /// - public string Serialize(object obj) - { - using (TextWriter tw = new StringWriter()) - { - if (obj == null) - { - obj = string.Empty; - } - serializer.Serialize(tw, obj); - return tw.ToString(); - } - } - - /// - public T Deserialize(string input) - { - if (string.IsNullOrEmpty(input)) - { - return default(T); - } - return JsonConvert.DeserializeObject(input, settings); - } - - /// - public object Deserialize(string input, Type type) - { - if (string.IsNullOrEmpty(input)) - { - return null; - } - return JsonConvert.DeserializeObject(input, type, settings); - } - - /// - public T Deserialize(Stream input) - { - // Convert the JSON document into an object. - using (StreamReader streamReader = new StreamReader(input)) - { - return (T)serializer.Deserialize(streamReader, typeof(T)); - } - } - - /// - /// Deserializes the given stream but reads from it asynchronously, observing the given cancellation token. - /// Note that this means the complete JSON is read before it is deserialized into objects. - /// - /// The type to convert to. - /// The stream to read from. - /// Cancellation token for the operation. - /// The deserialized object. - public async Task DeserializeAsync(Stream input, CancellationToken cancellationToken) - { - using (StreamReader streamReader = new StreamReader(input)) - { - string json = await streamReader.ReadToEndAsync().WithCancellationToken(cancellationToken).ConfigureAwait(false); - using (var reader = new JsonTextReader(new StringReader(json))) - { - return (T) serializer.Deserialize(reader, typeof(T)); - } - } - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ReplacementSerializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ReplacementSerializer.cs new file mode 100644 index 00000000000..d01e0d3f126 --- /dev/null +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ReplacementSerializer.cs @@ -0,0 +1,19 @@ +using Google.Apis.Auth.OAuth2; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.Apis.Auth.ExistingDependencies; + +// The replacement for NewtonsoftJsonSerializer +internal class ReplacementSerializer +{ + internal static string Serialize(T value) => default; + + internal static T Deserialize(string json) => default; + + public static Task DeserializeAsync(Stream stream, CancellationToken cancellationToken) => default; + + public static T Deserialize(Stream stream) => default; +} diff --git a/Src/Support/Google.Apis.Auth/JsonWebSignature.cs b/Src/Support/Google.Apis.Auth/JsonWebSignature.cs index 3c767624413..d1128ab0af2 100644 --- a/Src/Support/Google.Apis.Auth/JsonWebSignature.cs +++ b/Src/Support/Google.Apis.Auth/JsonWebSignature.cs @@ -51,7 +51,7 @@ public static Task VerifySignedTokenAsync( /// If the token is invalid or expired. /// The type of the payload to return, so user code can validate /// additional claims. Should extend . Payload information will be deserialized - /// using . + /// using the default serializer. public async static Task VerifySignedTokenAsync( string signedJwt, SignedTokenVerificationOptions options = null, CancellationToken cancellationToken = default) where TPayload : Payload diff --git a/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.AwsSecurityCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.AwsSecurityCredential.cs index 1e541ede3df..2f6886117ec 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.AwsSecurityCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.AwsSecurityCredential.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Json; using Newtonsoft.Json; using System; @@ -97,7 +98,7 @@ internal static async Task MaybeFromMetadataAsync(AwsMet && credentialJson != "") { // Deserialize the credentials - var deserializedCredentials = NewtonsoftJsonSerializer.Instance.Deserialize(credentialJson); + var deserializedCredentials = ReplacementSerializer.Deserialize(credentialJson); if (deserializedCredentials.IsSuccess && !string.IsNullOrEmpty(deserializedCredentials.AccessKeyId) diff --git a/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.cs index aa2cba3e6eb..2ce6657b0d9 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Http; using Google.Apis.Json; using Google.Apis.Util; @@ -140,7 +141,7 @@ protected async override Task GetSubjectTokenAsyncImpl(CancellationToken var subjectToken = AwsSignedSubjectToken.Create(awsSecurityCredentials, awsRegion, new Uri(regionalizedVerificationUrl), Audience, Clock); - return Uri.EscapeDataString(NewtonsoftJsonSerializer.Instance.Serialize(subjectToken)); + return Uri.EscapeDataString(ReplacementSerializer.Serialize(subjectToken)); } /// diff --git a/Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs b/Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs index 55f0e8417bd..527163e79dd 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs @@ -18,7 +18,7 @@ limitations under the License. using System.IO; using System.Threading; using System.Threading.Tasks; - +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Auth.OAuth2.Flows; using Google.Apis.Auth.OAuth2.Responses; using Google.Apis.Json; @@ -165,7 +165,7 @@ internal GoogleCredential CreateDefaultCredentialFromStream(Stream stream) JsonCredentialParameters credentialParameters; try { - credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize(stream); + credentialParameters = ReplacementSerializer.Deserialize(stream); } catch (Exception e) { @@ -180,8 +180,7 @@ internal async Task CreateDefaultCredentialFromStreamAsync(Str JsonCredentialParameters credentialParameters; try { - credentialParameters = await NewtonsoftJsonSerializer.Instance - .DeserializeAsync(stream, cancellationToken) + credentialParameters = await ReplacementSerializer.DeserializeAsync(stream, cancellationToken) .ConfigureAwait(false); } catch (Exception e) @@ -197,7 +196,7 @@ internal GoogleCredential CreateDefaultCredentialFromJson(string json) JsonCredentialParameters credentialParameters; try { - credentialParameters = NewtonsoftJsonSerializer.Instance.Deserialize(json); + credentialParameters = ReplacementSerializer.Deserialize(json); } catch (Exception e) { diff --git a/Src/Support/Google.Apis.Auth/OAuth2/FileSourcedExternalAccountCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/FileSourcedExternalAccountCredential.cs index cd12981387b..1fdd0b84352 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/FileSourcedExternalAccountCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/FileSourcedExternalAccountCredential.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Http; using Google.Apis.Json; using System.Collections.Generic; @@ -98,7 +99,7 @@ protected override async Task GetSubjectTokenAsyncImpl(CancellationToken } else { - var jsonResponse = NewtonsoftJsonSerializer.Instance.Deserialize>(fileContent); + var jsonResponse = ReplacementSerializer.Deserialize>(fileContent); subjectToken = jsonResponse[SubjectTokenJsonFieldName]; } diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs b/Src/Support/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs index 09e5b89f416..ff0d463c01a 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Auth.OAuth2.Requests; using Google.Apis.Auth.OAuth2.Responses; using Google.Apis.Http; @@ -120,7 +121,7 @@ public override async Task RevokeTokenAsync(string userId, string token, if (!response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - var error = NewtonsoftJsonSerializer.Instance.Deserialize(content); + var error = ReplacementSerializer.Deserialize(content); throw new TokenResponseException(error, response.StatusCode); } diff --git a/Src/Support/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs b/Src/Support/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs index 105e41cd649..ad06a980583 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Json; using System; using System.IO; @@ -58,11 +59,11 @@ public static GoogleClientSecrets Load(Stream stream) => /// Loads the Google client secret from the input stream. public static GoogleClientSecrets FromStream(Stream stream) => - NewtonsoftJsonSerializer.Instance.Deserialize(stream); + ReplacementSerializer.Deserialize(stream); /// Asynchronously loads the Google client secret from the input stream. public static Task FromStreamAsync(Stream stream, CancellationToken cancellationToken = default) => - NewtonsoftJsonSerializer.Instance.DeserializeAsync(stream, cancellationToken); + ReplacementSerializer.DeserializeAsync(stream, cancellationToken); /// Loads the Google client secret from a JSON file. public static GoogleClientSecrets FromFile(string clientSecretsFilePath) diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs index 6e799b409b4..70d5ae1e556 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Auth.OAuth2.Responses; using Google.Apis.Json; using Google.Apis.Logging; @@ -36,7 +37,7 @@ internal static async Task PostJsonAsync( { var httpRequest = new HttpRequestMessage(HttpMethod.Post, url) { - Content = new StringContent(NewtonsoftJsonSerializer.Instance.Serialize(request), Encoding.UTF8, "application/json") + Content = new StringContent(ReplacementSerializer.Serialize(request), Encoding.UTF8, "application/json") }; return await httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); @@ -51,7 +52,7 @@ internal static async Task PostJsonAsync( { var response = await request.PostJsonAsync(httpClient, url, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); - return await NewtonsoftJsonSerializer.Instance.DeserializeAsync( + return await ReplacementSerializer.DeserializeAsync( await response.Content.ReadAsStreamAsync().ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsRequestTokenBuilder.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsRequestTokenBuilder.cs index e30b5914055..81817f37b84 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsRequestTokenBuilder.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsRequestTokenBuilder.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Json; using System; using System.Collections.Generic; @@ -88,7 +89,7 @@ public StsTokenRequest Build() : null; var options = (authenticationHeader is null && WorkforcePoolUserProject is string) - ? NewtonsoftJsonSerializer.Instance.Serialize(new { userProject = WorkforcePoolUserProject }) + ? ReplacementSerializer.Serialize(new { userProject = WorkforcePoolUserProject }) : null; var scope = Scopes?.Any() == true ? string.Join(" ", Scopes) : null; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Responses/TokenResponse.cs b/Src/Support/Google.Apis.Auth/OAuth2/Responses/TokenResponse.cs index 0ff798b4376..8dca1bd75b6 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Responses/TokenResponse.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Responses/TokenResponse.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Json; using Google.Apis.Logging; using Google.Apis.Util; @@ -159,7 +160,7 @@ public static async Task FromHttpResponseAsync(HttpResponseMessag // "{"error": {"code": 404, "message": "...", "errors": [{"message": "...", ...}], "status": "NOT_FOUND"}}" var error = response.RequestMessage?.RequestUri?.AbsoluteUri.StartsWith(GoogleAuthConsts.IamServiceAccountEndpointCommonPrefix) == true ? new TokenErrorResponse { Error = content } : - NewtonsoftJsonSerializer.Instance.Deserialize(content); + ReplacementSerializer.Deserialize(content); throw new TokenResponseException(error, response.StatusCode); } @@ -176,7 +177,7 @@ public static async Task FromHttpResponseAsync(HttpResponseMessag else { typeName = nameof(TokenResponse); - newToken = NewtonsoftJsonSerializer.Instance.Deserialize(content); + newToken = ReplacementSerializer.Deserialize(content); } // We make some modifications to the token before returning, to guarantee consistency // for our code across endpoint usage. diff --git a/Src/Support/Google.Apis.Auth/OAuth2/ServiceAccountCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/ServiceAccountCredential.cs index 362f7baf926..b3bdb438adf 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/ServiceAccountCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/ServiceAccountCredential.cs @@ -26,6 +26,7 @@ limitations under the License. using Google.Apis.Json; using Google.Apis.Util; using Google.Apis.Http; +using Google.Apis.Auth.ExistingDependencies; #if NETSTANDARD1_3 || NETSTANDARD2_0 || NET461 using RsaKey = System.Security.Cryptography.RSA; @@ -439,7 +440,7 @@ private string CreateJwtAccessTokenForOidc(OidcTokenOptions options, DateTime is private string CreateAssertionFromPayload(JsonWebSignature.Payload payload) { string serializedHeader = CreateSerializedHeader(); - string serializedPayload = NewtonsoftJsonSerializer.Instance.Serialize(payload); + string serializedPayload = ReplacementSerializer.Serialize(payload); var assertion = new StringBuilder(); assertion.Append(TokenEncodingHelpers.UrlSafeBase64Encode(serializedHeader)) @@ -490,7 +491,7 @@ private string CreateSerializedHeader() KeyId = KeyId }; - return NewtonsoftJsonSerializer.Instance.Serialize(header); + return ReplacementSerializer.Serialize(header); } /// diff --git a/Src/Support/Google.Apis.Auth/OAuth2/UrlSourcedExternalAccountCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/UrlSourcedExternalAccountCredential.cs index cce70374742..36d4a92e63d 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/UrlSourcedExternalAccountCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/UrlSourcedExternalAccountCredential.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Http; using Google.Apis.Json; using System; @@ -124,7 +125,7 @@ protected async override Task GetSubjectTokenAsyncImpl(CancellationToken return responseText; } - var jsonResponse = NewtonsoftJsonSerializer.Instance.Deserialize>(responseText); + var jsonResponse = ReplacementSerializer.Deserialize>(responseText); return jsonResponse[SubjectTokenJsonFieldName]; } diff --git a/Src/Support/Google.Apis.Auth/SignedToken.cs b/Src/Support/Google.Apis.Auth/SignedToken.cs index e0c85bcc545..cf4a0ab2650 100644 --- a/Src/Support/Google.Apis.Auth/SignedToken.cs +++ b/Src/Support/Google.Apis.Auth/SignedToken.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Json; using Google.Apis.Util; using System; @@ -76,8 +77,8 @@ internal static SignedToken FromSignedToken(string sign var encodedPayload = parts[1]; // Decode the three parts of the JWT: header.payload.signature - var headerValue = NewtonsoftJsonSerializer.Instance.Deserialize(TokenEncodingHelpers.Base64UrlToString(encodedHeader)); - var payloadValue = NewtonsoftJsonSerializer.Instance.Deserialize(TokenEncodingHelpers.Base64UrlToString(encodedPayload)); + var headerValue = ReplacementSerializer.Deserialize(TokenEncodingHelpers.Base64UrlToString(encodedHeader)); + var payloadValue = ReplacementSerializer.Deserialize(TokenEncodingHelpers.Base64UrlToString(encodedPayload)); var signature = TokenEncodingHelpers.Base64UrlDecode(parts[2]); return new SignedToken(encodedHeader, encodedPayload, headerValue, payloadValue, signature); From 0ddb19b35289177821d136b5f57f0fe858690baa Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:40:16 +0100 Subject: [PATCH 08/11] Remove more unused code --- .../ExistingDependencies/Features.cs | 32 ------ .../MaxUrlLengthInterceptor.cs | 72 ------------ .../ExistingDependencies/StandardResponse.cs | 37 ------ .../StringValueAttribute.cs | 36 ------ .../ExistingDependencies/Utilities.cs | 108 +----------------- 5 files changed, 1 insertion(+), 284 deletions(-) delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/Features.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/MaxUrlLengthInterceptor.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/StandardResponse.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/StringValueAttribute.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/Features.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/Features.cs deleted file mode 100644 index 887a8e68b50..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/Features.cs +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using Google.Apis.Util; - -namespace Google.Apis.Discovery -{ - /// - /// Specifies a list of features which can be defined within the discovery document of a service. - /// - public enum Features - { - /// - /// If this feature is specified, then the data of a response is encapsulated within a "data" resource. - /// - [StringValue("dataWrapper")] - LegacyDataResponse, - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/MaxUrlLengthInterceptor.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/MaxUrlLengthInterceptor.cs deleted file mode 100644 index 324ba399b41..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/MaxUrlLengthInterceptor.cs +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright 2013 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; - -using Google.Apis.Testing; - -namespace Google.Apis.Http -{ - /// - /// Intercepts HTTP GET requests with a URLs longer than a specified maximum number of characters. - /// The interceptor will change such requests as follows: - /// - /// The request's method will be changed to POST - /// A X-HTTP-Method-Override header will be added with the value GET - /// Any query parameters from the URI will be moved into the body of the request. - /// If query parameters are moved, the content type is set to application/x-www-form-urlencoded - /// - /// - [VisibleForTestOnly] - public class MaxUrlLengthInterceptor : IHttpExecuteInterceptor - { - private readonly uint maxUrlLength; - - ///Constructs a new Max URL length interceptor with the given max length. - public MaxUrlLengthInterceptor(uint maxUrlLength) - { - this.maxUrlLength = maxUrlLength; - } - - /// - public Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - if (request.Method != HttpMethod.Get || request.RequestUri.AbsoluteUri.Length <= maxUrlLength) - { - return Task.FromResult(0); - } - // Change the method to POST. - request.Method = HttpMethod.Post; - var query = request.RequestUri.Query; - if (!String.IsNullOrEmpty(query)) - { - // Move query parameters to the body (without the "?"). - request.Content = new StringContent(query.Substring(1)); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); - var requestString = request.RequestUri.ToString(); - // The new request URI is the old one minus the "?" and everything that follows, since we moved the - // query params to the body. For example: "www.example.com/?q=foo" => "www.example.com/". - request.RequestUri = new Uri(requestString.Remove(requestString.IndexOf("?"))); - } - request.Headers.Add("X-HTTP-Method-Override", "GET"); - return Task.FromResult(0); - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/StandardResponse.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/StandardResponse.cs deleted file mode 100644 index b1625b7c46e..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/StandardResponse.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2010 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using Google.Apis.Requests; -using Newtonsoft.Json; - -namespace Google.Apis.Util -{ - /// - /// Calls to Google Api return StandardResponses as Json with - /// two properties Data, being the return type of the method called - /// and Error, being any errors that occure. - /// - public sealed class StandardResponse - { - /// May be null if call failed. - [JsonProperty("data")] - public InnerType Data { get; set; } - - /// May be null if call succedded. - [JsonProperty("error")] - public RequestError Error { get; set; } - } -} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/StringValueAttribute.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/StringValueAttribute.cs deleted file mode 100644 index f2b49000523..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/StringValueAttribute.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; - -namespace Google.Apis.Util -{ - /// Defines an attribute containing a string representation of the member. - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - public class StringValueAttribute : Attribute - { - private readonly string text; - /// The text which belongs to this member. - public string Text { get { return text; } } - - /// Creates a new string value attribute with the specified text. - public StringValueAttribute(string text) - { - text.ThrowIfNull("text"); - this.text = text; - } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs index f71457b206b..334eebb2bb4 100644 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs @@ -14,13 +14,11 @@ You may obtain a copy of the License at limitations under the License. */ -using Google.Apis.Testing; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; -using System.Reflection; using System.Text.RegularExpressions; +using System.Reflection; namespace Google.Apis.Util { @@ -28,7 +26,6 @@ namespace Google.Apis.Util public static class Utilities { /// Returns the version of the core library. - [VisibleForTestOnly] public static string GetLibraryVersion() { return Regex.Match(typeof(Utilities).GetTypeInfo().Assembly.FullName, "Version=([\\d\\.]+)").Groups[1].ToString(); @@ -101,108 +98,5 @@ public static void CheckArgument(bool condition, string paramName, strin } } - /// - /// A Google.Apis utility method for returning the first matching custom attribute (or null) of the specified member. - /// - public static T GetCustomAttribute(this MemberInfo info) where T : Attribute - { - object[] results = info.GetCustomAttributes(typeof(T), false).ToArray(); - return results.Length == 0 ? null : (T)results[0]; - } - - /// Returns the defined string value of an Enum. - internal static string GetStringValue(this Enum value) - { - FieldInfo entry = value.GetType().GetField(value.ToString()); - entry.ThrowIfNull("value"); - - // If set, return the value. - var attribute = entry.GetCustomAttribute(); - if (attribute != null) - { - return attribute.Text; - } - - // Otherwise, throw an exception. - throw new ArgumentException( - string.Format("Enum value '{0}' does not contain a StringValue attribute", entry), "value"); - } - - /// - /// Returns the defined string value of an Enum. Use for test purposes or in other Google.Apis projects. - /// - public static string GetEnumStringValue(Enum value) - { - return value.GetStringValue(); - } - - /// - /// Tries to convert the specified object to a string. Uses custom type converters if available. - /// Returns null for a null object. - /// - [VisibleForTestOnly] - public static string ConvertToString(object o) - { - if (o == null) - { - return null; - } - - if (o.GetType().GetTypeInfo().IsEnum) - { - // Try to convert the Enum value using the StringValue attribute. - var enumType = o.GetType(); - FieldInfo field = enumType.GetField(o.ToString()); - StringValueAttribute attribute = field.GetCustomAttribute(); - return attribute != null ? attribute.Text : o.ToString(); - } - - if (o is DateTime) - { - // Honor RFC3339. - return ConvertToRFC3339((DateTime)o); - } - - if (o is bool) - { - return o.ToString().ToLowerInvariant(); - } - - return o.ToString(); - } - - /// Converts the input date into a RFC3339 string (http://www.ietf.org/rfc/rfc3339.txt). - internal static string ConvertToRFC3339(DateTime date) - { - if (date.Kind == DateTimeKind.Unspecified) - { - date = date.ToUniversalTime(); - } - return date.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", DateTimeFormatInfo.InvariantInfo); - } - - /// - /// Parses the input string and returns if the input is a valid - /// representation of a date. Otherwise it returns null. - /// - public static DateTime? GetDateTimeFromString(string raw) - { - DateTime result; - if (!DateTime.TryParse(raw, out result)) - { - return null; - } - return result; - } - - /// Returns a string (by RFC3339) form the input instance. - public static string GetStringFromDateTime(DateTime? date) - { - if (!date.HasValue) - { - return null; - } - return ConvertToRFC3339(date.Value); - } } } From 7c6920d6c195a8a11caa6a82c84ea4fca8cae0fd Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:44:54 +0100 Subject: [PATCH 09/11] Remove more unused code. --- .../ExistingDependencies/HttpConsts.cs | 42 ---- .../ExistingDependencies/HttpExtenstions.cs | 10 +- .../IDirectResponseSchema.cs | 36 ---- .../ExistingDependencies/IJsonSerializer.cs | 23 -- .../ExistingDependencies/IParameter.cs | 39 ---- .../ExistingDependencies/ISerializer.cs | 43 ---- .../ExistingDependencies/Utilities.cs | 2 +- .../VersionHeaderBuilder.cs | 202 ------------------ .../Google.Apis.Auth/JsonWebSignature.cs | 1 - ...AccountCredential.AwsSecurityCredential.cs | 1 - .../OAuth2/AwsExternalAccountCredential.cs | 1 - .../OAuth2/DefaultCredentialProvider.cs | 9 +- .../FileSourcedExternalAccountCredential.cs | 1 - .../Flows/GoogleAuthorizationCodeFlow.cs | 2 - .../OAuth2/GoogleClientSecrets.cs | 1 - .../OAuth2/Requests/RequestExtensions.cs | 1 - .../OAuth2/Requests/StsRequestTokenBuilder.cs | 1 - .../OAuth2/Responses/TokenResponse.cs | 1 - .../OAuth2/ServiceAccountCredential.cs | 1 - .../UrlSourcedExternalAccountCredential.cs | 1 - Src/Support/Google.Apis.Auth/SignedToken.cs | 1 - 21 files changed, 6 insertions(+), 413 deletions(-) delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/HttpConsts.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IDirectResponseSchema.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IJsonSerializer.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/IParameter.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ISerializer.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/VersionHeaderBuilder.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpConsts.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpConsts.cs deleted file mode 100644 index 11e236bc8c9..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpConsts.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2013 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Google.Apis.Http -{ - /// HTTP constants. - public static class HttpConsts - { - /// Http GET request - public const string Get = "GET"; - - /// Http DELETE request - public const string Delete = "DELETE"; - - /// Http PUT request - public const string Put = "PUT"; - - /// Http POST request - public const string Post = "POST"; - - /// Http PATCH request - public const string Patch = "PATCH"; - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpExtenstions.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpExtenstions.cs index 579d8e1232d..461f2ba1e62 100644 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpExtenstions.cs +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/HttpExtenstions.cs @@ -23,7 +23,7 @@ namespace Google.Apis.Http /// Extension methods to and /// . /// - public static class HttpExtenstions + internal static class HttpExtensions { /// Returns true if the response contains one of the redirect status codes. internal static bool IsRedirectStatusCode(this HttpResponseMessage message) @@ -39,13 +39,5 @@ internal static bool IsRedirectStatusCode(this HttpResponseMessage message) return false; } } - - /// A Google.Apis utility method for setting an empty HTTP content. - public static HttpContent SetEmptyContent(this HttpRequestMessage request) - { - request.Content = new ByteArrayContent(new byte[0]); - request.Content.Headers.ContentLength = 0; - return request.Content; - } } } diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IDirectResponseSchema.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IDirectResponseSchema.cs deleted file mode 100644 index 6415dca8693..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/IDirectResponseSchema.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using Newtonsoft.Json; - -namespace Google.Apis.Requests -{ - /// - /// Interface containing additional response-properties which will be added to every schema type which is - /// a direct response to a request. - /// - public interface IDirectResponseSchema - { - /// - /// The e-tag of this response. - /// - /// - /// Will be set by the service deserialization method, - /// or the by json response parser if implemented on service. - /// - string ETag { get; set; } - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IJsonSerializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IJsonSerializer.cs deleted file mode 100644 index 83e0cf0a9c9..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/IJsonSerializer.cs +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -namespace Google.Apis.Json -{ - /// Represents a JSON serializer. - public interface IJsonSerializer : ISerializer - { - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/IParameter.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/IParameter.cs deleted file mode 100644 index 6ae191b186a..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/IParameter.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2010 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System.Collections.Generic; - -namespace Google.Apis.Discovery -{ - /// Represents a parameter for a method. - public interface IParameter - { - /// Gets the name of the parameter. - string Name { get; } - - /// Gets the pattern that this parameter must follow. - string Pattern { get; } - - /// Gets an indication whether this parameter is optional or required. - bool IsRequired { get; } - - /// Gets the default value of this parameter. - string DefaultValue { get; } - - /// Gets the type of the parameter. - string ParameterType { get; } - } -} \ No newline at end of file diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ISerializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ISerializer.cs deleted file mode 100644 index ad296e1f84e..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/ISerializer.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using System; -using System.IO; - -namespace Google.Apis -{ - /// Serialization interface that supports serialize and deserialize methods. - public interface ISerializer - { - /// Gets the application format this serializer supports (e.g. "json", "xml", etc.). - string Format { get; } - - /// Serializes the specified object into a Stream. - void Serialize(object obj, Stream target); - - /// Serializes the specified object into a string. - string Serialize(object obj); - - /// Deserializes the string into an object. - T Deserialize(string input); - - /// Deserializes the string into an object. - object Deserialize(string input, Type type); - - /// Deserializes the stream into an object. - T Deserialize(Stream input); - } -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs index 334eebb2bb4..71ff29894a8 100644 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/Utilities.cs @@ -23,7 +23,7 @@ limitations under the License. namespace Google.Apis.Util { /// A utility class which contains helper methods and extension methods. - public static class Utilities + internal static class Utilities { /// Returns the version of the core library. public static string GetLibraryVersion() diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/VersionHeaderBuilder.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/VersionHeaderBuilder.cs deleted file mode 100644 index c1adecc83df..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/VersionHeaderBuilder.cs +++ /dev/null @@ -1,202 +0,0 @@ -/* -Copyright 2019 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -using Google.Apis.Util; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Runtime.Versioning; - -namespace Google.Apis.Requests -{ - // Note: this code is copied from GAX: - // https://github.com/googleapis/gax-dotnet/blob/main/Google.Api.Gax/VersionHeaderBuilder.cs - // The duplication is annoying, but hard to avoid due to dependencies. - // Any changes should be made in both places. - - /// - /// Helps build version strings for the x-goog-api-client header. - /// The value is a space-separated list of name/value pairs, where the value - /// should be a semantic version string. Names must be unique. - /// - public sealed class VersionHeaderBuilder - { - private static readonly Lazy s_environmentVersion = new Lazy(GetEnvironmentVersion); - - /// - /// The name of the header to set. - /// - public const string HeaderName = "x-goog-api-client"; - private readonly List _names = new List(); - private readonly List _values = new List(); - - /// - /// Appends the given name/version string to the list. - /// - /// The name. Must not be null or empty, or contain a space or a slash. - /// The version. Must not be null, or contain a space or a slash. - public VersionHeaderBuilder AppendVersion(string name, string version) - { - Utilities.ThrowIfNull(name, nameof(name)); - Utilities.ThrowIfNull(version, nameof(version)); - // Names can't be empty, but versions can. (We use the empty string to indicate an unknown version.) - - CheckArgument(name.Length > 0 && !name.Contains(" ") && !name.Contains("/"), nameof(name), $"Invalid name: {name}"); - CheckArgument(!version.Contains(" ") && !version.Contains("/"), nameof(version), $"Invalid version: {version}"); - CheckArgument(!_names.Contains(name), nameof(name), "Names in version headers must be unique"); - _names.Add(name); - _values.Add(version); - return this; - } - - // This is in GaxPreconditions in the original code. - private static void CheckArgument(bool condition, string paramName, string message) - { - if (!condition) - { - throw new ArgumentException(message, paramName); - } - } - - /// - /// Appends a name/version string, taking the version from the version of the assembly - /// containing the given type. - /// - public VersionHeaderBuilder AppendAssemblyVersion(string name, System.Type type) - => AppendVersion(name, FormatAssemblyVersion(type)); - - /// - /// Appends the .NET environment information to the list. - /// - public VersionHeaderBuilder AppendDotNetEnvironment() => AppendVersion("gl-dotnet", s_environmentVersion.Value); - - /// - /// Whether the name or value that are supposed to be included in a header are valid - /// - private static bool IsHeaderNameValueValid(string nameOrValue) => - !nameOrValue.Contains(" ") && !nameOrValue.Contains("/"); - - private static string GetEnvironmentVersion() - { - // We can pick between the version reported by System.Environment.Version, or the version in the - // entry assembly, if any. Neither gives us exactly what we might want, - string systemEnvironmentVersion = -#if NETSTANDARD1_3 - null; -#else - FormatVersion(Environment.Version); -#endif - string entryAssemblyVersion = GetEntryAssemblyVersionOrNull(); - - return entryAssemblyVersion ?? systemEnvironmentVersion ?? ""; - } - - private static string GetEntryAssemblyVersionOrNull() - { - try - { - // Assembly.GetEntryAssembly() isn't available in netstandard1.3. Attempt to fetch it with reflection, which is ugly but should work. - // This is a slightly more robust version of the code we previously used in Microsoft.Extensions.PlatformAbstractions. - var getEntryAssemblyMethod = typeof(Assembly) - .GetTypeInfo() - .DeclaredMethods - .Where(m => m.Name == "GetEntryAssembly" && m.IsStatic && m.GetParameters().Length == 0 && m.ReturnType == typeof(Assembly)) - .FirstOrDefault(); - if (getEntryAssemblyMethod == null) - { - return null; - } - Assembly entryAssembly = (Assembly) getEntryAssemblyMethod.Invoke(null, new object[0]); - var frameworkName = entryAssembly?.GetCustomAttribute()?.FrameworkName; - return frameworkName == null ? null : FormatVersion(new FrameworkName(frameworkName).Version); - } - catch - { - // If we simply can't get the version for whatever reason, don't fail. - return null; - } - } - - private static string FormatAssemblyVersion(System.Type type) - { - // Prefer AssemblyInformationalVersion, then AssemblyFileVersion, - // then AssemblyVersion. - - var assembly = type.GetTypeInfo().Assembly; - var info = assembly.GetCustomAttributes().FirstOrDefault()?.InformationalVersion; - if (info != null && IsHeaderNameValueValid(info)) // Skip informational version if it's not a valid header value - { - return FormatInformationalVersion(info); - } - var file = assembly.GetCustomAttributes().FirstOrDefault()?.Version; - if (file != null) - { - return string.Join(".", file.Split('.').Take(3)); - } - return FormatVersion(assembly.GetName().Version); - } - - // Visible for testing - - /// - /// Formats an AssemblyInformationalVersionAttribute value to avoid losing useful information, - /// but also avoid including unnecessary hex that is appended automatically. - /// - internal static string FormatInformationalVersion(string info) - { - // In some cases, the runtime includes 40 hex digits after a + or period in InformationalVersion. - // In precisely those cases, we strip this. - int signIndex = Math.Max(info.LastIndexOf('.'), info.LastIndexOf('+')); - if (signIndex == -1 || signIndex != info.Length - 41) - { - return info; - } - for (int i = signIndex + 1; i < info.Length; i++) - { - char c = info[i]; - bool isHex = (c >= '0' && c <= '9') - || (c >= 'a' && c <= 'f') - || (c >= 'A' && c <= 'F'); - if (!isHex) - { - return info; - } - } - return info.Substring(0, signIndex); - } - - private static string FormatVersion(Version version) => - version != null ? - $"{version.Major}.{version.Minor}.{(version.Build != -1 ? version.Build : 0)}" : - ""; // Empty string means "unknown" - - /// - public override string ToString() => string.Join(" ", _names.Zip(_values, (name, value) => $"{name}/{value}")); - - /// - /// Clones this VersionHeaderBuilder, creating an independent copy with the same names/values. - /// - /// A clone of this builder. - public VersionHeaderBuilder Clone() - { - var ret = new VersionHeaderBuilder(); - ret._names.AddRange(_names); - ret._values.AddRange(_values); - return ret; - } - } -} diff --git a/Src/Support/Google.Apis.Auth/JsonWebSignature.cs b/Src/Support/Google.Apis.Auth/JsonWebSignature.cs index d1128ab0af2..c7a5bf526c0 100644 --- a/Src/Support/Google.Apis.Auth/JsonWebSignature.cs +++ b/Src/Support/Google.Apis.Auth/JsonWebSignature.cs @@ -14,7 +14,6 @@ You may obtain a copy of the License at limitations under the License. */ -using Google.Apis.Json; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.AwsSecurityCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.AwsSecurityCredential.cs index 2f6886117ec..2319880ee0a 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.AwsSecurityCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.AwsSecurityCredential.cs @@ -15,7 +15,6 @@ limitations under the License. */ using Google.Apis.Auth.ExistingDependencies; -using Google.Apis.Json; using Newtonsoft.Json; using System; using System.IO; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.cs index 2ce6657b0d9..d65feb37549 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/AwsExternalAccountCredential.cs @@ -16,7 +16,6 @@ limitations under the License. using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Http; -using Google.Apis.Json; using Google.Apis.Util; using System; using System.Collections.Generic; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs b/Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs index 527163e79dd..eec81cd8efd 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/DefaultCredentialProvider.cs @@ -14,16 +14,15 @@ You may obtain a copy of the License at limitations under the License. */ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Auth.OAuth2.Flows; using Google.Apis.Auth.OAuth2.Responses; -using Google.Apis.Json; using Google.Apis.Logging; using Google.Apis.Util; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace Google.Apis.Auth.OAuth2 { diff --git a/Src/Support/Google.Apis.Auth/OAuth2/FileSourcedExternalAccountCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/FileSourcedExternalAccountCredential.cs index 1fdd0b84352..0ccae405dcc 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/FileSourcedExternalAccountCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/FileSourcedExternalAccountCredential.cs @@ -16,7 +16,6 @@ limitations under the License. using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Http; -using Google.Apis.Json; using System.Collections.Generic; using System.IO; using System.Threading; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs b/Src/Support/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs index ff0d463c01a..e3be9f277f0 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Flows/GoogleAuthorizationCodeFlow.cs @@ -18,8 +18,6 @@ limitations under the License. using Google.Apis.Auth.OAuth2.Requests; using Google.Apis.Auth.OAuth2.Responses; using Google.Apis.Http; -using Google.Apis.Json; -using Google.Apis.Util; using System; using System.Collections.Generic; using System.Net.Http; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs b/Src/Support/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs index ad06a980583..4ca2f41672d 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/GoogleClientSecrets.cs @@ -15,7 +15,6 @@ limitations under the License. */ using Google.Apis.Auth.ExistingDependencies; -using Google.Apis.Json; using System; using System.IO; using System.Threading; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs index 70d5ae1e556..c57bec59077 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/RequestExtensions.cs @@ -16,7 +16,6 @@ limitations under the License. using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Auth.OAuth2.Responses; -using Google.Apis.Json; using Google.Apis.Logging; using Google.Apis.Util; using System.Net.Http; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsRequestTokenBuilder.cs b/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsRequestTokenBuilder.cs index 81817f37b84..d36b4809891 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsRequestTokenBuilder.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Requests/StsRequestTokenBuilder.cs @@ -15,7 +15,6 @@ limitations under the License. */ using Google.Apis.Auth.ExistingDependencies; -using Google.Apis.Json; using System; using System.Collections.Generic; using System.Linq; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/Responses/TokenResponse.cs b/Src/Support/Google.Apis.Auth/OAuth2/Responses/TokenResponse.cs index 8dca1bd75b6..b2e81cf3e92 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/Responses/TokenResponse.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/Responses/TokenResponse.cs @@ -15,7 +15,6 @@ limitations under the License. */ using Google.Apis.Auth.ExistingDependencies; -using Google.Apis.Json; using Google.Apis.Logging; using Google.Apis.Util; using System; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/ServiceAccountCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/ServiceAccountCredential.cs index b3bdb438adf..61c657d7d7b 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/ServiceAccountCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/ServiceAccountCredential.cs @@ -23,7 +23,6 @@ limitations under the License. using System.Threading; using System.Threading.Tasks; using Google.Apis.Auth.OAuth2.Requests; -using Google.Apis.Json; using Google.Apis.Util; using Google.Apis.Http; using Google.Apis.Auth.ExistingDependencies; diff --git a/Src/Support/Google.Apis.Auth/OAuth2/UrlSourcedExternalAccountCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/UrlSourcedExternalAccountCredential.cs index 36d4a92e63d..b0add0b4396 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/UrlSourcedExternalAccountCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/UrlSourcedExternalAccountCredential.cs @@ -16,7 +16,6 @@ limitations under the License. using Google.Apis.Auth.ExistingDependencies; using Google.Apis.Http; -using Google.Apis.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/Src/Support/Google.Apis.Auth/SignedToken.cs b/Src/Support/Google.Apis.Auth/SignedToken.cs index cf4a0ab2650..d9a239e9f12 100644 --- a/Src/Support/Google.Apis.Auth/SignedToken.cs +++ b/Src/Support/Google.Apis.Auth/SignedToken.cs @@ -15,7 +15,6 @@ limitations under the License. */ using Google.Apis.Auth.ExistingDependencies; -using Google.Apis.Json; using Google.Apis.Util; using System; using System.Security.Cryptography; From 21204506e07009045498ebffe91bd562313313c5 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:50:46 +0100 Subject: [PATCH 10/11] More unused code. --- .../ExistingDependencies/Delegates.cs | 12 ----- .../ExistingDependencies/ETagAction.cs | 46 ------------------- 2 files changed, 58 deletions(-) delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/Delegates.cs delete mode 100644 Src/Support/Google.Apis.Auth/ExistingDependencies/ETagAction.cs diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/Delegates.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/Delegates.cs deleted file mode 100644 index 365a4fef70c..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/Delegates.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Google.Apis.Http -{ - /// - /// A delegate used to intercept stream data without modifying it. - /// The parameters should always be validated before the delegate is called, - /// so the delegate itself does not need to validate them again. - /// - /// The buffer containing the data. - /// The offset into the buffer. - /// The number of bytes being read/written. - public delegate void StreamInterceptor(byte[] buffer, int offset, int count); -} diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ETagAction.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ETagAction.cs deleted file mode 100644 index 9d744492f43..00000000000 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/ETagAction.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2011 Google Inc - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -namespace Google.Apis -{ - /// - /// Defines the behaviour/header used for sending an etag along with a request. - /// - public enum ETagAction - { - /// - /// The default etag behaviour will be determined by the type of the request. - /// - Default, - - /// - /// The ETag won't be added to the header of the request. - /// - Ignore, - - /// - /// The ETag will be added as an "If-Match" header. - /// A request sent with an "If-Match" header will only succeed if both ETags are identical. - /// - IfMatch, - - /// - /// The ETag will be added as an "If-None-Match" header. - /// A request sent with an "If-None-Match" header will only succeed if ETags are not identical. - /// - IfNoneMatch, - } -} From fe48aaa5466682967a03f68958eda4a72b334f5f Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 15 May 2023 14:51:03 +0100 Subject: [PATCH 11/11] Add notes about what could be hidden/removed, potentially --- .../ExistingDependencies/ExponentialBackOffInitializer.cs | 7 ++++--- .../ExistingDependencies/GoogleApiException.cs | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOffInitializer.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOffInitializer.cs index c026141ecf4..4d6fff2aa8d 100644 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOffInitializer.cs +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/ExponentialBackOffInitializer.cs @@ -15,9 +15,10 @@ limitations under the License. */ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; + +// ExponentialBackOffInitializer could be internal to Google.Apis.Auth (with code duplication). +// We add it to the AuthorizationCodeFlow and ServiceCredential, but we don't expose it. +// ExponentialBackOffPolicy is exposed via the DefaultExponentialBackOffPolicy propert in AuthorizationCodeFlow and ExternalAccountCredential. namespace Google.Apis.Http { diff --git a/Src/Support/Google.Apis.Auth/ExistingDependencies/GoogleApiException.cs b/Src/Support/Google.Apis.Auth/ExistingDependencies/GoogleApiException.cs index 399590f0610..7e3c3e064bd 100644 --- a/Src/Support/Google.Apis.Auth/ExistingDependencies/GoogleApiException.cs +++ b/Src/Support/Google.Apis.Auth/ExistingDependencies/GoogleApiException.cs @@ -14,6 +14,9 @@ You may obtain a copy of the License at limitations under the License. */ +// Note: the only reference to this is in ServiceAccountCredential. If we throw a different exception, +// e.g. InvalidOperationException, we could remove GoogleApiException, SingleError and RequestError. + using System; using System.Net;