File size: 8,668 Bytes
7b715bc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | /*
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.
/// <summary>
/// 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.
/// </summary>
public sealed class VersionHeaderBuilder
{
private static readonly Lazy<string> s_environmentVersion = new Lazy<string>(GetEnvironmentVersion);
/// <summary>
/// The name of the header to set.
/// </summary>
public const string HeaderName = "x-goog-api-client";
private readonly List<string> _names = new List<string>();
private readonly List<string> _values = new List<string>();
/// <summary>
/// Appends the given name/version string to the list.
/// </summary>
/// <param name="name">The name. Must not be null or empty, or contain a space or a slash.</param>
/// <param name="version">The version. Must not be null, or contain a space or a slash.</param>
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);
}
}
/// <summary>
/// Appends a name/version string, taking the version from the version of the assembly
/// containing the given type.
/// </summary>
public VersionHeaderBuilder AppendAssemblyVersion(string name, System.Type type)
=> AppendVersion(name, FormatAssemblyVersion(type));
/// <summary>
/// Appends the .NET environment information to the list.
/// </summary>
public VersionHeaderBuilder AppendDotNetEnvironment() => AppendVersion("gl-dotnet", s_environmentVersion.Value);
/// <summary>
/// Whether the name or value that are supposed to be included in a header are valid
/// </summary>
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 = FormatVersion(Environment.Version);
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<TargetFrameworkAttribute>()?.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<AssemblyInformationalVersionAttribute>().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<AssemblyFileVersionAttribute>().FirstOrDefault()?.Version;
if (file != null)
{
return string.Join(".", file.Split('.').Take(3));
}
return FormatVersion(assembly.GetName().Version);
}
// Visible for testing
/// <summary>
/// Formats an AssemblyInformationalVersionAttribute value to avoid losing useful information,
/// but also avoid including unnecessary hex that is appended automatically.
/// </summary>
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"
/// <inheritdoc />
public override string ToString() => string.Join(" ", _names.Zip(_values, (name, value) => $"{name}/{value}"));
/// <summary>
/// Clones this VersionHeaderBuilder, creating an independent copy with the same names/values.
/// </summary>
/// <returns>A clone of this builder.</returns>
public VersionHeaderBuilder Clone()
{
var ret = new VersionHeaderBuilder();
ret._names.AddRange(_names);
ret._values.AddRange(_values);
return ret;
}
}
}
|