Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static ISystemWebAdapterBuilder AddSystemWebAdapters(this IServiceCollect
services.AddHttpContextAccessor();
services.AddSingleton<IHttpRuntime>(_ => HttpRuntimeFactory.Create());
services.AddSingleton<Cache>();
services.AddSingleton<BrowserCapabilitiesFactory>();
services.AddSingleton<IBrowserCapabilitiesFactory, BrowserCapabilitiesFactory>();
services.AddTransient<IStartupFilter, HttpContextStartupFilter>();

return new SystemWebAdapterBuilder(services)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.SystemWebAdapters;

#if NET6_0_OR_GREATER
public interface IBrowserCapabilitiesFactory
{
IHttpBrowserCapabilityFeature Create(HttpRequestCore request);
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.SystemWebAdapters;

#if NET6_0_OR_GREATER
public interface IHttpBrowserCapabilityFeature
{
string? this[string key] { get; }
}
#endif
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.SystemWebAdapters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;

namespace System.Web.Configuration;

Expand All @@ -18,7 +23,7 @@ namespace System.Web.Configuration;
/// to do custom stuff. These are not implemented in the framework and are potentially added by uses. For now, we don't
/// support that pattern.
/// </summary>
internal class BrowserCapabilitiesFactory
internal sealed class BrowserCapabilitiesFactory : IBrowserCapabilitiesFactory
{
// Func will return true if we want to end the processing of further funcs
private readonly Func<string, ParsedBrowserResult, bool>[] _processList;
Expand All @@ -40,7 +45,30 @@ public BrowserCapabilitiesFactory()
};
}

public ParsedBrowserResult Process(string userAgent)
IHttpBrowserCapabilityFeature IBrowserCapabilitiesFactory.Create(HttpRequestCore request)
{
var userAgent = request.Headers.UserAgent;

if (string.IsNullOrWhiteSpace(userAgent))
{
return EmptyBrowserFeatures.Instance;
}
else if (request.HttpContext.RequestServices.GetService<IMemoryCache>() is { } cache)
{
return cache.GetOrCreate<IHttpBrowserCapabilityFeature>(userAgent, entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(2);

return Parse(userAgent);
});
}
else
{
return Parse(userAgent);
}
}

public IHttpBrowserCapabilityFeature Parse(string userAgent)
{
var dictionary = new ParsedBrowserResult();

Expand Down Expand Up @@ -621,7 +649,23 @@ private bool UcbrowserProcess(string userAgent, ParsedBrowserResult dictionary)
return false;
}

private struct RegexResult
private sealed class ParsedBrowserResult : Dictionary<string, string?>, IHttpBrowserCapabilityFeature
{
public ParsedBrowserResult()
: base(StringComparer.OrdinalIgnoreCase)
{
}

[Conditional("NotNeededYet")]
[Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Retained in case we need the data later")]
public void AddBrowser(string browser)
{
}

string? IHttpBrowserCapabilityFeature.this[string key] => TryGetValue(key, out var value) ? value : null;
}

private readonly struct RegexResult
{
private readonly Match? _match;
private readonly Match? _match2;
Expand Down Expand Up @@ -655,7 +699,7 @@ public RegexResult(Match? match, Match? match2)
}
}

private class RegexWorker
private sealed class RegexWorker
{
private readonly Regex _regex;
private readonly Regex? _regex2;
Expand All @@ -673,4 +717,11 @@ public RegexWorker(string pattern, string? pattern2 = null)
public RegexResult Process(string userAgent)
=> new(_regex?.Match(userAgent), _regex2?.Match(userAgent));
}

private sealed class EmptyBrowserFeatures : IHttpBrowserCapabilityFeature
{
public static IHttpBrowserCapabilityFeature Instance { get; } = new EmptyBrowserFeatures();

public string? this[string key] => null;
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,92 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.SystemWebAdapters;
using Microsoft.Extensions.DependencyInjection;

namespace System.Web.Configuration;

public class HttpCapabilitiesBase
{
private readonly ParsedBrowserResult _data;
private readonly HttpContextCore _context;

private FeatureReference<IHttpBrowserCapabilityFeature> _capability;

private protected HttpCapabilitiesBase(HttpContextCore context)
{
_context = context;
_capability = FeatureReference<IHttpBrowserCapabilityFeature>.Default;
}

private protected HttpCapabilitiesBase(BrowserCapabilitiesFactory factory, string userAgent)
private IHttpBrowserCapabilityFeature Capability
{
_data = factory.Process(userAgent);
get
{
if (_capability.Fetch(_context.Features) is { } existing)
{
return existing;
}

return _capability.Update(_context.Features, _context.RequestServices.GetRequiredService<IBrowserCapabilitiesFactory>().Create(_context.Request));
}
}

public string? Browser => _data["browser"];
public string? Browser => Capability["browser"];

public string? Version => Capability["version"];

public int MajorVersion => GetInt("majorversion");

public double MinorVersion => GetDouble("minorversion");

public string? Platform => Capability["platform"];

public string? Version => _data["version"];
public bool Crawler => GetBoolean("crawler");

public int MajorVersion => _data.GetInt("majorversion");
public string? Type => Capability["type"];

public double MinorVersion => _data.GetDouble("minorversion");
public string? PreferredRequestEncoding => Capability["preferredRequestEncoding"];

public string? Platform => _data["platform"];
public string? this[string key] => Capability[key];

public bool Crawler => _data.GetBoolean("crawler");
public bool IsMobileDevice => GetBoolean("isMobileDevice");

public bool IsMobileDevice => _data.GetBoolean("isMobileDevice");
private int GetInt(string key) => int.TryParse(Capability[key], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) ? result : throw new HttpUnhandledException($"Invalid string from browser capabilities '{key}'");

private bool GetBoolean(string key) => bool.TryParse(Capability[key], out var result) && result;

private double GetDouble(string key)
{
const NumberStyles Style = NumberStyles.Float | NumberStyles.AllowDecimalPoint;

var value = Capability[key];

if (value is not null)
{
if (double.TryParse(value, Style, CultureInfo.InvariantCulture, out var result))
{
return result;
}

// Handle if there's more than one decimal i.e. .4.1 -> .4
var firstDecimal = value.IndexOf('.', StringComparison.Ordinal);

if (firstDecimal != -1)
{
var nextDecimal = value.IndexOf('.', firstDecimal + 1);

if (nextDecimal != -1)
{
if (double.TryParse(value.AsSpan()[..nextDecimal], Style, CultureInfo.InvariantCulture, out var result2))
{
return result2;
}
}
}
}

throw new HttpUnhandledException($"Invalid string from browser capabilities '{key}'");
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ T:Microsoft.AspNetCore.SystemWebAdapters.SessionState.ISessionState

# We manually type forward this only for .NET 4.7.2+
T:System.Web.SameSiteMode

T:Microsoft.AspNetCore.SystemWebAdapters.IBrowserCapabilitiesFactory
T:Microsoft.AspNetCore.SystemWebAdapters.IHttpBrowserCapabilityFeature
Original file line number Diff line number Diff line change
Expand Up @@ -525,9 +525,12 @@ internal HttpCapabilitiesBase() { }
public string Browser { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public bool Crawler { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public bool IsMobileDevice { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string this[string key] { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public int MajorVersion { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public double MinorVersion { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string Platform { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string PreferredRequestEncoding { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string Type { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
public string Version { get { throw new System.PlatformNotSupportedException("Only supported when running on ASP.NET Core or System.Web");} }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ namespace System.Web;

public class HttpBrowserCapabilities : HttpCapabilitiesBase
{
internal HttpBrowserCapabilities(BrowserCapabilitiesFactory factory, string userAgent)
: base(factory, userAgent)
internal HttpBrowserCapabilities(HttpContextCore core)
: base(core)
{
}
}
20 changes: 1 addition & 19 deletions src/Microsoft.AspNetCore.SystemWebAdapters/HttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,25 +196,7 @@ public bool IsLocal

public string? UserHostName => _request.HttpContext.Connection.RemoteIpAddress?.ToString();

public HttpBrowserCapabilities Browser
{
get
{
if (_browser is null)
{
var factory = _request.HttpContext.RequestServices.GetService<BrowserCapabilitiesFactory>();

if (factory is null)
{
throw new InvalidOperationException("Browser capabilities requires AddSystemWebAdapters() to be called on service collection");
}

_browser = new(factory, _request.Headers.UserAgent);
}

return _browser;
}
}
public HttpBrowserCapabilities Browser => _browser ??= new(_request.HttpContext);

public string? this[string key] => Params[key];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public void VerifyCapabilities(TestData data)
var browserFactory = new BrowserCapabilitiesFactory();

// Act
var result = browserFactory.Process(data.UserAgent);
var result = browserFactory.Parse(data.UserAgent);

// Assert
Assert.Equal(data.Browser, result["browser"]);
Expand Down