Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed invalid user agent format exception for Mac OS. #895

Merged
merged 9 commits into from
Oct 11, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions Microsoft.Azure.Cosmos/src/EnvironmentInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;

internal sealed class EnvironmentInformation
{
private static readonly string clientId;
private const int MaxOperatingSystemString = 50;
private static readonly string clientSDKVersion;
private static readonly string directPackageVersion;
private static readonly string framework;
Expand All @@ -25,14 +28,18 @@ static EnvironmentInformation()
EnvironmentInformation.framework = RuntimeInformation.FrameworkDescription;
EnvironmentInformation.architecture = RuntimeInformation.ProcessArchitecture.ToString();
EnvironmentInformation.os = RuntimeInformation.OSDescription;
}

public EnvironmentInformation()
{
string now = DateTime.UtcNow.Ticks.ToString();
EnvironmentInformation.clientId = now.Substring(now.Length - 5); // 5 most significative digits
this.ClientId = now.Substring(now.Length - 5); // 5 most significative digits
}

/// <summary>
/// Unique identifier of a client
/// </summary>
public string ClientId => EnvironmentInformation.clientId;
public string ClientId { get; }

/// <summary>
/// Version of the current direct package.
Expand Down Expand Up @@ -61,10 +68,5 @@ static EnvironmentInformation()
/// </summary>
/// <seealso cref="RuntimeInformation.ProcessArchitecture"/>
public string ProcessArchitecture => EnvironmentInformation.architecture;

public override string ToString()
{
return $"{this.OperatingSystem}/{this.ProcessArchitecture} {this.ClientVersion}/{this.DirectVersion}-{this.RuntimeFramework} {this.ClientId}";
}
}
}
33 changes: 24 additions & 9 deletions Microsoft.Azure.Cosmos/src/UserAgentContainer.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System.Text.RegularExpressions;

/// <summary>
/// Contains information about the user environment and helps identify requests.
/// </summary>
internal class UserAgentContainer : Documents.UserAgentContainer
{
private static readonly string cosmosBaseUserAgent;

static UserAgentContainer()
{
EnvironmentInformation environmentInformation = new EnvironmentInformation();
UserAgentContainer.cosmosBaseUserAgent = environmentInformation.ToString();
}
private const int MaxOperatingSystemString = 30;
private string cosmosBaseUserAgent;

public UserAgentContainer()
: base()
Expand All @@ -26,8 +22,27 @@ internal override string BaseUserAgent
{
get
{
return UserAgentContainer.cosmosBaseUserAgent;
if (this.cosmosBaseUserAgent == null)
{
this.cosmosBaseUserAgent = this.CreateBaseUserAgentString();
}

return this.cosmosBaseUserAgent;
}
}

private string CreateBaseUserAgentString()
{
EnvironmentInformation environmentInformation = new EnvironmentInformation();
string operatingSystem = environmentInformation.OperatingSystem;
if (operatingSystem.Length > MaxOperatingSystemString)
{
operatingSystem = operatingSystem.Substring(0, MaxOperatingSystemString);
}

// Regex replaces all special characters with empty space except . - | since they do not cause format exception for the user agent string.
// Do not change the cosmos-netstandard-sdk as it is required for reporting
return $"cosmos-netstandard-sdk/{environmentInformation.ClientVersion}" + Regex.Replace($"|{environmentInformation.DirectVersion}|{environmentInformation.ClientId}|{environmentInformation.ProcessArchitecture}|{operatingSystem}|{environmentInformation.RuntimeFramework}|", @"[^0-9a-zA-Z\.\|\-]+", " ");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Routing;
Expand Down Expand Up @@ -437,32 +440,6 @@ public void ValidateIfNonMatchRntbd()
ValidateIfNonMatch(client);
}

[TestMethod]
public void ValidateCustomUserAgentHeader()
{
const string suffix = " MyCustomUserAgent/1.0";
ConnectionPolicy policy = new ConnectionPolicy();
policy.UserAgentSuffix = suffix;
var expectedUserAgent = new Cosmos.UserAgentContainer().BaseUserAgent + suffix;
Assert.AreEqual(expectedUserAgent, policy.UserAgentContainer.UserAgent);

byte[] expectedUserAgentUTF8 = Encoding.UTF8.GetBytes(expectedUserAgent);
CollectionAssert.AreEqual(expectedUserAgentUTF8, policy.UserAgentContainer.UserAgentUTF8);
}

[TestMethod]
public void ValidateCustomUserAgentContainer()
{
const string suffix = " MyCustomUserAgent/1.0";
UserAgentContainer userAgentContainer = new Cosmos.UserAgentContainer();
userAgentContainer.Suffix = suffix;
string expectedUserAgent = new Cosmos.UserAgentContainer().BaseUserAgent + suffix;
Assert.AreEqual(expectedUserAgent, userAgentContainer.UserAgent);

byte[] expectedUserAgentUTF8 = Encoding.UTF8.GetBytes(expectedUserAgent);
CollectionAssert.AreEqual(expectedUserAgentUTF8, userAgentContainer.UserAgentUTF8);
}

[TestMethod]
public void ValidateVersionHeader()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Routing;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Collections;
using Microsoft.Azure.Documents.Routing;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class UserAgentTests
{
[TestMethod]
[DataRow(true)]
[DataRow(false)]
public async Task ValidateUserAgentHeaderWithMacOs(bool useMacOs)
{
this.SetEnvironmentInformation(useMacOs);

const string suffix = " UserApplicationName/1.0";

using (CosmosClient client = TestCommon.CreateCosmosClient(builder => builder.WithApplicationName(suffix)))
{
Cosmos.UserAgentContainer userAgentContainer = client.ClientOptions.GetConnectionPolicy().UserAgentContainer;

string userAgentString = userAgentContainer.UserAgent;
Assert.IsTrue(userAgentString.Contains(suffix));
if (useMacOs)
{
Assert.IsTrue(userAgentString.Contains("Darwin 18.0.0"));
}

Cosmos.Database db = await client.CreateDatabaseIfNotExistsAsync(Guid.NewGuid().ToString());
Assert.IsNotNull(db);
await db.DeleteAsync();
}
}

[TestMethod]
public void ValidateCustomUserAgentHeader()
{
const string suffix = " MyCustomUserAgent/1.0";
ConnectionPolicy policy = new ConnectionPolicy();
policy.UserAgentSuffix = suffix;
Assert.IsTrue(policy.UserAgentContainer.UserAgent.EndsWith(suffix));

byte[] expectedUserAgentUTF8 = Encoding.UTF8.GetBytes(policy.UserAgentContainer.UserAgent);
CollectionAssert.AreEqual(expectedUserAgentUTF8, policy.UserAgentContainer.UserAgentUTF8);
}

[TestMethod]
public void ValidateUniqueClientIdHeader()
{
using (CosmosClient client = TestCommon.CreateCosmosClient())
{
string firstClientId = this.GetClientIdFromCosmosClient(client);

using (CosmosClient innerClient = TestCommon.CreateCosmosClient())
{
string secondClientId = this.GetClientIdFromCosmosClient(innerClient);
Assert.AreNotEqual(firstClientId, secondClientId);
}
}
}

[TestMethod]
[DataRow(true)]
[DataRow(false)]
public void VerifyUserAgentContent(bool useMacOs)
{
this.SetEnvironmentInformation(useMacOs);

EnvironmentInformation envInfo = new EnvironmentInformation();
Cosmos.UserAgentContainer userAgentContainer = new Cosmos.UserAgentContainer();
string serialization = userAgentContainer.UserAgent;

Assert.IsTrue(serialization.Contains(envInfo.ProcessArchitecture));
string[] values = serialization.Split('|');
Assert.AreEqual($"cosmos-netstandard-sdk/{envInfo.ClientVersion}", values[0]);
Assert.AreEqual(envInfo.DirectVersion, values[1]);
Assert.AreEqual(envInfo.ClientId.Length, values[2].Length);
Assert.AreEqual(envInfo.ProcessArchitecture, values[3]);
Assert.IsTrue(!string.IsNullOrWhiteSpace(values[4]));
if (useMacOs)
{
Assert.AreEqual("Darwin 18.0.0 Darwin Kernel V", values[4]);
}
Assert.AreEqual(envInfo.RuntimeFramework, values[5]);
}

private void SetEnvironmentInformation(bool useMacOs)
{
//This changes the runtime information to simulate a max os x response. Windows user agent are tested by every other emulator test.
const string invalidOsField = "Darwin 18.0.0: Darwin/Kernel/Version 18.0.0: Wed Aug 22 20:13:40 PDT 2018; root:xnu-4903.201.2~1/RELEASE_X86_64";

FieldInfo fieldInfo = typeof(EnvironmentInformation).GetField("os", BindingFlags.Static | BindingFlags.NonPublic);
fieldInfo.SetValue(null, useMacOs ? invalidOsField : RuntimeInformation.OSDescription);
}

private string GetClientIdFromCosmosClient(CosmosClient client)
{
Cosmos.UserAgentContainer userAgentContainer = client.ClientOptions.GetConnectionPolicy().UserAgentContainer;
string userAgentString = userAgentContainer.UserAgent;
string clientId = userAgentString.Split('|')[2];
Assert.AreEqual(5, clientId.Length);
return clientId;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public void ThrowOnNullEndpoint()
public void UserAgentContainsEnvironmentInformation()
{
EnvironmentInformation environmentInformation = new EnvironmentInformation();
string expectedValue = environmentInformation.ToString();
string expectedValue = "cosmos-netstandard-sdk/" + environmentInformation.ClientVersion;
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions();
string userAgentSuffix = "testSuffix";
cosmosClientOptions.ApplicationName = userAgentSuffix;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,5 @@ public void ClientIdIsNotNull()
var envInfo = new EnvironmentInformation();
Assert.IsNotNull(envInfo.ClientId);
}

[TestMethod]
public void ToStringContainsAll()
j82w marked this conversation as resolved.
Show resolved Hide resolved
{
var envInfo = new EnvironmentInformation();
var serialization = envInfo.ToString();
Assert.IsTrue(serialization.Contains(envInfo.ClientVersion));
Assert.IsTrue(serialization.Contains(envInfo.ProcessArchitecture));
Assert.IsTrue(serialization.Contains(envInfo.RuntimeFramework));
Assert.IsTrue(serialization.Contains(envInfo.ClientId));
}
}
}