From 42879f6c87c8e339a2d38c978c07edbbc2ba2355 Mon Sep 17 00:00:00 2001 From: Eilon Lipton Date: Fri, 19 Mar 2021 12:35:42 -0700 Subject: [PATCH] Remove WebView dependency to AspNetCore.App The Blazor Desktop project must not depend on Microsoft.AspNetCore.App because it needs to run on almost any platform, including Android/iOS. But AspNetCore.App doesn't run on those platforms. This changes WebView to not use any packages that are part of AspNetCore.App, such as StaticFiles and Http.Abstractions. Instead, it has copies of those files. --- .../src/FileExtensionContentTypeProvider.cs | 473 +++++++++++++++++ .../WebView/src/IContentTypeProvider.cs | 27 + ...osoft.AspNetCore.Components.WebView.csproj | 3 +- .../WebView/WebView/src/PathString.cs | 482 ++++++++++++++++++ .../WebView/WebView/src/PathStringHelper.cs | 73 +++ .../WebView/WebView/src/QueryString.cs | 304 +++++++++++ .../WebView/WebView/src/Resources.resx | 123 +++++ .../WebView/src/StaticContentProvider.cs | 1 - .../WebView/src/StaticWebAssetsLoader.cs | 1 - 9 files changed, 1483 insertions(+), 4 deletions(-) create mode 100644 src/Components/WebView/WebView/src/FileExtensionContentTypeProvider.cs create mode 100644 src/Components/WebView/WebView/src/IContentTypeProvider.cs create mode 100644 src/Components/WebView/WebView/src/PathString.cs create mode 100644 src/Components/WebView/WebView/src/PathStringHelper.cs create mode 100644 src/Components/WebView/WebView/src/QueryString.cs create mode 100644 src/Components/WebView/WebView/src/Resources.resx diff --git a/src/Components/WebView/WebView/src/FileExtensionContentTypeProvider.cs b/src/Components/WebView/WebView/src/FileExtensionContentTypeProvider.cs new file mode 100644 index 000000000000..bdd24208a06c --- /dev/null +++ b/src/Components/WebView/WebView/src/FileExtensionContentTypeProvider.cs @@ -0,0 +1,473 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// NOTE: This file is copied from src/Middleware/StaticFiles/src/IContentTypeProvider.cs +// and made internal with a namespace change. +// It can't be referenced directly from the StaticFiles package because that would cause this package to require +// Microsoft.AspNetCore.App, thus preventing it from being used anywhere ASP.NET Core isn't supported (such as +// various platforms that .NET MAUI runs on, such as Android and iOS). + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNetCore.Components.WebView +{ + /// + /// Provides a mapping between file extensions and MIME types. + /// + internal class FileExtensionContentTypeProvider : IContentTypeProvider + { + // Notes: + // - This table was initially copied from IIS and has many legacy entries we will maintain for backwards compatibility. + // - We only plan to add new entries where we expect them to be applicable to a majority of developers such as being + // used in the project templates. + #region Extension mapping table + /// + /// Creates a new provider with a set of default mappings. + /// + public FileExtensionContentTypeProvider() + : this(new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { ".323", "text/h323" }, + { ".3g2", "video/3gpp2" }, + { ".3gp2", "video/3gpp2" }, + { ".3gp", "video/3gpp" }, + { ".3gpp", "video/3gpp" }, + { ".aac", "audio/aac" }, + { ".aaf", "application/octet-stream" }, + { ".aca", "application/octet-stream" }, + { ".accdb", "application/msaccess" }, + { ".accde", "application/msaccess" }, + { ".accdt", "application/msaccess" }, + { ".acx", "application/internet-property-stream" }, + { ".adt", "audio/vnd.dlna.adts" }, + { ".adts", "audio/vnd.dlna.adts" }, + { ".afm", "application/octet-stream" }, + { ".ai", "application/postscript" }, + { ".aif", "audio/x-aiff" }, + { ".aifc", "audio/aiff" }, + { ".aiff", "audio/aiff" }, + { ".appcache", "text/cache-manifest" }, + { ".application", "application/x-ms-application" }, + { ".art", "image/x-jg" }, + { ".asd", "application/octet-stream" }, + { ".asf", "video/x-ms-asf" }, + { ".asi", "application/octet-stream" }, + { ".asm", "text/plain" }, + { ".asr", "video/x-ms-asf" }, + { ".asx", "video/x-ms-asf" }, + { ".atom", "application/atom+xml" }, + { ".au", "audio/basic" }, + { ".avi", "video/x-msvideo" }, + { ".axs", "application/olescript" }, + { ".bas", "text/plain" }, + { ".bcpio", "application/x-bcpio" }, + { ".bin", "application/octet-stream" }, + { ".bmp", "image/bmp" }, + { ".c", "text/plain" }, + { ".cab", "application/vnd.ms-cab-compressed" }, + { ".calx", "application/vnd.ms-office.calx" }, + { ".cat", "application/vnd.ms-pki.seccat" }, + { ".cdf", "application/x-cdf" }, + { ".chm", "application/octet-stream" }, + { ".class", "application/x-java-applet" }, + { ".clp", "application/x-msclip" }, + { ".cmx", "image/x-cmx" }, + { ".cnf", "text/plain" }, + { ".cod", "image/cis-cod" }, + { ".cpio", "application/x-cpio" }, + { ".cpp", "text/plain" }, + { ".crd", "application/x-mscardfile" }, + { ".crl", "application/pkix-crl" }, + { ".crt", "application/x-x509-ca-cert" }, + { ".csh", "application/x-csh" }, + { ".css", "text/css" }, + { ".csv", "text/csv" }, // https://tools.ietf.org/html/rfc7111#section-5.1 + { ".cur", "application/octet-stream" }, + { ".dcr", "application/x-director" }, + { ".deploy", "application/octet-stream" }, + { ".der", "application/x-x509-ca-cert" }, + { ".dib", "image/bmp" }, + { ".dir", "application/x-director" }, + { ".disco", "text/xml" }, + { ".dlm", "text/dlm" }, + { ".doc", "application/msword" }, + { ".docm", "application/vnd.ms-word.document.macroEnabled.12" }, + { ".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, + { ".dot", "application/msword" }, + { ".dotm", "application/vnd.ms-word.template.macroEnabled.12" }, + { ".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" }, + { ".dsp", "application/octet-stream" }, + { ".dtd", "text/xml" }, + { ".dvi", "application/x-dvi" }, + { ".dvr-ms", "video/x-ms-dvr" }, + { ".dwf", "drawing/x-dwf" }, + { ".dwp", "application/octet-stream" }, + { ".dxr", "application/x-director" }, + { ".eml", "message/rfc822" }, + { ".emz", "application/octet-stream" }, + { ".eot", "application/vnd.ms-fontobject" }, + { ".eps", "application/postscript" }, + { ".etx", "text/x-setext" }, + { ".evy", "application/envoy" }, + { ".exe", "application/vnd.microsoft.portable-executable" }, // https://www.iana.org/assignments/media-types/application/vnd.microsoft.portable-executable + { ".fdf", "application/vnd.fdf" }, + { ".fif", "application/fractals" }, + { ".fla", "application/octet-stream" }, + { ".flr", "x-world/x-vrml" }, + { ".flv", "video/x-flv" }, + { ".gif", "image/gif" }, + { ".gtar", "application/x-gtar" }, + { ".gz", "application/x-gzip" }, + { ".h", "text/plain" }, + { ".hdf", "application/x-hdf" }, + { ".hdml", "text/x-hdml" }, + { ".hhc", "application/x-oleobject" }, + { ".hhk", "application/octet-stream" }, + { ".hhp", "application/octet-stream" }, + { ".hlp", "application/winhlp" }, + { ".hqx", "application/mac-binhex40" }, + { ".hta", "application/hta" }, + { ".htc", "text/x-component" }, + { ".htm", "text/html" }, + { ".html", "text/html" }, + { ".htt", "text/webviewhtml" }, + { ".hxt", "text/html" }, + { ".ical", "text/calendar" }, + { ".icalendar", "text/calendar" }, + { ".ico", "image/x-icon" }, + { ".ics", "text/calendar" }, + { ".ief", "image/ief" }, + { ".ifb", "text/calendar" }, + { ".iii", "application/x-iphone" }, + { ".inf", "application/octet-stream" }, + { ".ins", "application/x-internet-signup" }, + { ".isp", "application/x-internet-signup" }, + { ".IVF", "video/x-ivf" }, + { ".jar", "application/java-archive" }, + { ".java", "application/octet-stream" }, + { ".jck", "application/liquidmotion" }, + { ".jcz", "application/liquidmotion" }, + { ".jfif", "image/pjpeg" }, + { ".jpb", "application/octet-stream" }, + { ".jpe", "image/jpeg" }, + { ".jpeg", "image/jpeg" }, + { ".jpg", "image/jpeg" }, + { ".js", "application/javascript" }, + { ".json", "application/json" }, + { ".jsx", "text/jscript" }, + { ".latex", "application/x-latex" }, + { ".lit", "application/x-ms-reader" }, + { ".lpk", "application/octet-stream" }, + { ".lsf", "video/x-la-asf" }, + { ".lsx", "video/x-la-asf" }, + { ".lzh", "application/octet-stream" }, + { ".m13", "application/x-msmediaview" }, + { ".m14", "application/x-msmediaview" }, + { ".m1v", "video/mpeg" }, + { ".m2ts", "video/vnd.dlna.mpeg-tts" }, + { ".m3u", "audio/x-mpegurl" }, + { ".m4a", "audio/mp4" }, + { ".m4v", "video/mp4" }, + { ".man", "application/x-troff-man" }, + { ".manifest", "application/x-ms-manifest" }, + { ".map", "text/plain" }, + { ".markdown", "text/markdown" }, + { ".md", "text/markdown" }, + { ".mdb", "application/x-msaccess" }, + { ".mdp", "application/octet-stream" }, + { ".me", "application/x-troff-me" }, + { ".mht", "message/rfc822" }, + { ".mhtml", "message/rfc822" }, + { ".mid", "audio/mid" }, + { ".midi", "audio/mid" }, + { ".mix", "application/octet-stream" }, + { ".mmf", "application/x-smaf" }, + { ".mno", "text/xml" }, + { ".mny", "application/x-msmoney" }, + { ".mov", "video/quicktime" }, + { ".movie", "video/x-sgi-movie" }, + { ".mp2", "video/mpeg" }, + { ".mp3", "audio/mpeg" }, + { ".mp4", "video/mp4" }, + { ".mp4v", "video/mp4" }, + { ".mpa", "video/mpeg" }, + { ".mpe", "video/mpeg" }, + { ".mpeg", "video/mpeg" }, + { ".mpg", "video/mpeg" }, + { ".mpp", "application/vnd.ms-project" }, + { ".mpv2", "video/mpeg" }, + { ".ms", "application/x-troff-ms" }, + { ".msi", "application/octet-stream" }, + { ".mso", "application/octet-stream" }, + { ".mvb", "application/x-msmediaview" }, + { ".mvc", "application/x-miva-compiled" }, + { ".nc", "application/x-netcdf" }, + { ".nsc", "video/x-ms-asf" }, + { ".nws", "message/rfc822" }, + { ".ocx", "application/octet-stream" }, + { ".oda", "application/oda" }, + { ".odc", "text/x-ms-odc" }, + { ".ods", "application/oleobject" }, + { ".oga", "audio/ogg" }, + { ".ogg", "video/ogg" }, + { ".ogv", "video/ogg" }, + { ".ogx", "application/ogg" }, + { ".one", "application/onenote" }, + { ".onea", "application/onenote" }, + { ".onetoc", "application/onenote" }, + { ".onetoc2", "application/onenote" }, + { ".onetmp", "application/onenote" }, + { ".onepkg", "application/onenote" }, + { ".osdx", "application/opensearchdescription+xml" }, + { ".otf", "font/otf" }, + { ".p10", "application/pkcs10" }, + { ".p12", "application/x-pkcs12" }, + { ".p7b", "application/x-pkcs7-certificates" }, + { ".p7c", "application/pkcs7-mime" }, + { ".p7m", "application/pkcs7-mime" }, + { ".p7r", "application/x-pkcs7-certreqresp" }, + { ".p7s", "application/pkcs7-signature" }, + { ".pbm", "image/x-portable-bitmap" }, + { ".pcx", "application/octet-stream" }, + { ".pcz", "application/octet-stream" }, + { ".pdf", "application/pdf" }, + { ".pfb", "application/octet-stream" }, + { ".pfm", "application/octet-stream" }, + { ".pfx", "application/x-pkcs12" }, + { ".pgm", "image/x-portable-graymap" }, + { ".pko", "application/vnd.ms-pki.pko" }, + { ".pma", "application/x-perfmon" }, + { ".pmc", "application/x-perfmon" }, + { ".pml", "application/x-perfmon" }, + { ".pmr", "application/x-perfmon" }, + { ".pmw", "application/x-perfmon" }, + { ".png", "image/png" }, + { ".pnm", "image/x-portable-anymap" }, + { ".pnz", "image/png" }, + { ".pot", "application/vnd.ms-powerpoint" }, + { ".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" }, + { ".potx", "application/vnd.openxmlformats-officedocument.presentationml.template" }, + { ".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" }, + { ".ppm", "image/x-portable-pixmap" }, + { ".pps", "application/vnd.ms-powerpoint" }, + { ".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" }, + { ".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }, + { ".ppt", "application/vnd.ms-powerpoint" }, + { ".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" }, + { ".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, + { ".prf", "application/pics-rules" }, + { ".prm", "application/octet-stream" }, + { ".prx", "application/octet-stream" }, + { ".ps", "application/postscript" }, + { ".psd", "application/octet-stream" }, + { ".psm", "application/octet-stream" }, + { ".psp", "application/octet-stream" }, + { ".pub", "application/x-mspublisher" }, + { ".qt", "video/quicktime" }, + { ".qtl", "application/x-quicktimeplayer" }, + { ".qxd", "application/octet-stream" }, + { ".ra", "audio/x-pn-realaudio" }, + { ".ram", "audio/x-pn-realaudio" }, + { ".rar", "application/octet-stream" }, + { ".ras", "image/x-cmu-raster" }, + { ".rf", "image/vnd.rn-realflash" }, + { ".rgb", "image/x-rgb" }, + { ".rm", "application/vnd.rn-realmedia" }, + { ".rmi", "audio/mid" }, + { ".roff", "application/x-troff" }, + { ".rpm", "audio/x-pn-realaudio-plugin" }, + { ".rtf", "application/rtf" }, + { ".rtx", "text/richtext" }, + { ".scd", "application/x-msschedule" }, + { ".sct", "text/scriptlet" }, + { ".sea", "application/octet-stream" }, + { ".setpay", "application/set-payment-initiation" }, + { ".setreg", "application/set-registration-initiation" }, + { ".sgml", "text/sgml" }, + { ".sh", "application/x-sh" }, + { ".shar", "application/x-shar" }, + { ".sit", "application/x-stuffit" }, + { ".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12" }, + { ".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide" }, + { ".smd", "audio/x-smd" }, + { ".smi", "application/octet-stream" }, + { ".smx", "audio/x-smd" }, + { ".smz", "audio/x-smd" }, + { ".snd", "audio/basic" }, + { ".snp", "application/octet-stream" }, + { ".spc", "application/x-pkcs7-certificates" }, + { ".spl", "application/futuresplash" }, + { ".spx", "audio/ogg" }, + { ".src", "application/x-wais-source" }, + { ".ssm", "application/streamingmedia" }, + { ".sst", "application/vnd.ms-pki.certstore" }, + { ".stl", "application/vnd.ms-pki.stl" }, + { ".sv4cpio", "application/x-sv4cpio" }, + { ".sv4crc", "application/x-sv4crc" }, + { ".svg", "image/svg+xml" }, + { ".svgz", "image/svg+xml" }, + { ".swf", "application/x-shockwave-flash" }, + { ".t", "application/x-troff" }, + { ".tar", "application/x-tar" }, + { ".tcl", "application/x-tcl" }, + { ".tex", "application/x-tex" }, + { ".texi", "application/x-texinfo" }, + { ".texinfo", "application/x-texinfo" }, + { ".tgz", "application/x-compressed" }, + { ".thmx", "application/vnd.ms-officetheme" }, + { ".thn", "application/octet-stream" }, + { ".tif", "image/tiff" }, + { ".tiff", "image/tiff" }, + { ".toc", "application/octet-stream" }, + { ".tr", "application/x-troff" }, + { ".trm", "application/x-msterminal" }, + { ".ts", "video/vnd.dlna.mpeg-tts" }, + { ".tsv", "text/tab-separated-values" }, + { ".ttc", "application/x-font-ttf" }, + { ".ttf", "application/x-font-ttf" }, + { ".tts", "video/vnd.dlna.mpeg-tts" }, + { ".txt", "text/plain" }, + { ".u32", "application/octet-stream" }, + { ".uls", "text/iuls" }, + { ".ustar", "application/x-ustar" }, + { ".vbs", "text/vbscript" }, + { ".vcf", "text/x-vcard" }, + { ".vcs", "text/plain" }, + { ".vdx", "application/vnd.ms-visio.viewer" }, + { ".vml", "text/xml" }, + { ".vsd", "application/vnd.visio" }, + { ".vss", "application/vnd.visio" }, + { ".vst", "application/vnd.visio" }, + { ".vsto", "application/x-ms-vsto" }, + { ".vsw", "application/vnd.visio" }, + { ".vsx", "application/vnd.visio" }, + { ".vtx", "application/vnd.visio" }, + { ".wasm", "application/wasm" }, + { ".wav", "audio/wav" }, + { ".wax", "audio/x-ms-wax" }, + { ".wbmp", "image/vnd.wap.wbmp" }, + { ".wcm", "application/vnd.ms-works" }, + { ".wdb", "application/vnd.ms-works" }, + { ".webm", "video/webm" }, + { ".webmanifest", "application/manifest+json" }, // https://w3c.github.io/manifest/#media-type-registration + { ".webp", "image/webp" }, + { ".wks", "application/vnd.ms-works" }, + { ".wm", "video/x-ms-wm" }, + { ".wma", "audio/x-ms-wma" }, + { ".wmd", "application/x-ms-wmd" }, + { ".wmf", "application/x-msmetafile" }, + { ".wml", "text/vnd.wap.wml" }, + { ".wmlc", "application/vnd.wap.wmlc" }, + { ".wmls", "text/vnd.wap.wmlscript" }, + { ".wmlsc", "application/vnd.wap.wmlscriptc" }, + { ".wmp", "video/x-ms-wmp" }, + { ".wmv", "video/x-ms-wmv" }, + { ".wmx", "video/x-ms-wmx" }, + { ".wmz", "application/x-ms-wmz" }, + { ".woff", "application/font-woff" }, // https://www.w3.org/TR/WOFF/#appendix-b + { ".woff2", "font/woff2" }, // https://www.w3.org/TR/WOFF2/#IMT + { ".wps", "application/vnd.ms-works" }, + { ".wri", "application/x-mswrite" }, + { ".wrl", "x-world/x-vrml" }, + { ".wrz", "x-world/x-vrml" }, + { ".wsdl", "text/xml" }, + { ".wtv", "video/x-ms-wtv" }, + { ".wvx", "video/x-ms-wvx" }, + { ".x", "application/directx" }, + { ".xaf", "x-world/x-vrml" }, + { ".xaml", "application/xaml+xml" }, + { ".xap", "application/x-silverlight-app" }, + { ".xbap", "application/x-ms-xbap" }, + { ".xbm", "image/x-xbitmap" }, + { ".xdr", "text/plain" }, + { ".xht", "application/xhtml+xml" }, + { ".xhtml", "application/xhtml+xml" }, + { ".xla", "application/vnd.ms-excel" }, + { ".xlam", "application/vnd.ms-excel.addin.macroEnabled.12" }, + { ".xlc", "application/vnd.ms-excel" }, + { ".xlm", "application/vnd.ms-excel" }, + { ".xls", "application/vnd.ms-excel" }, + { ".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" }, + { ".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" }, + { ".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, + { ".xlt", "application/vnd.ms-excel" }, + { ".xltm", "application/vnd.ms-excel.template.macroEnabled.12" }, + { ".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" }, + { ".xlw", "application/vnd.ms-excel" }, + { ".xml", "text/xml" }, + { ".xof", "x-world/x-vrml" }, + { ".xpm", "image/x-xpixmap" }, + { ".xps", "application/vnd.ms-xpsdocument" }, + { ".xsd", "text/xml" }, + { ".xsf", "text/xml" }, + { ".xsl", "text/xml" }, + { ".xslt", "text/xml" }, + { ".xsn", "application/octet-stream" }, + { ".xtp", "application/octet-stream" }, + { ".xwd", "image/x-xwindowdump" }, + { ".z", "application/x-compress" }, + { ".zip", "application/x-zip-compressed" }, + }) + { + } + #endregion + + /// + /// Creates a lookup engine using the provided mapping. + /// It is recommended that the IDictionary instance use StringComparer.OrdinalIgnoreCase. + /// + /// + public FileExtensionContentTypeProvider(IDictionary mapping) + { + if (mapping == null) + { + throw new ArgumentNullException(nameof(mapping)); + } + Mappings = mapping; + } + + /// + /// The cross reference table of file extensions and content-types. + /// + public IDictionary Mappings { get; private set; } + + /// + /// Given a file path, determine the MIME type + /// + /// A file path + /// The resulting MIME type + /// True if MIME type could be determined + public bool TryGetContentType(string subpath, [MaybeNullWhen(false)] out string contentType) + { + var extension = GetExtension(subpath); + if (extension == null) + { + contentType = null; + return false; + } + return Mappings.TryGetValue(extension, out contentType); + } + + private static string? GetExtension(string path) + { + // Don't use Path.GetExtension as that may throw an exception if there are + // invalid characters in the path. Invalid characters should be handled + // by the FileProviders + + if (string.IsNullOrWhiteSpace(path)) + { + return null; + } + + int index = path.LastIndexOf('.'); + if (index < 0) + { + return null; + } + + return path.Substring(index); + } + } +} diff --git a/src/Components/WebView/WebView/src/IContentTypeProvider.cs b/src/Components/WebView/WebView/src/IContentTypeProvider.cs new file mode 100644 index 000000000000..6322de063267 --- /dev/null +++ b/src/Components/WebView/WebView/src/IContentTypeProvider.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// NOTE: This file is copied from src/Middleware/StaticFiles/src/FileExtensionContentTypeProvider.cs +// and made internal with a namespace change. +// It can't be referenced directly from the StaticFiles package because that would cause this package to require +// Microsoft.AspNetCore.App, thus preventing it from being used anywhere ASP.NET Core isn't supported (such as +// various platforms that .NET MAUI runs on, such as Android and iOS). + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.AspNetCore.Components.WebView +{ + /// + /// Used to look up MIME types given a file path + /// + internal interface IContentTypeProvider + { + /// + /// Given a file path, determine the MIME type + /// + /// A file path + /// The resulting MIME type + /// True if MIME type could be determined + bool TryGetContentType(string subpath, [MaybeNullWhen(false)] out string contentType); + } +} diff --git a/src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj b/src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj index 7cf89005ecfb..269755ed4f20 100644 --- a/src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj +++ b/src/Components/WebView/WebView/src/Microsoft.AspNetCore.Components.WebView.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -28,7 +28,6 @@ - diff --git a/src/Components/WebView/WebView/src/PathString.cs b/src/Components/WebView/WebView/src/PathString.cs new file mode 100644 index 000000000000..085e1c966acf --- /dev/null +++ b/src/Components/WebView/WebView/src/PathString.cs @@ -0,0 +1,482 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// NOTE: This file is copied from src/Http/Http.Abstractions/src/PathString.cs +// and made internal with a namespace change. +// It can't be referenced directly from the StaticFiles package because that would cause this package to require +// Microsoft.AspNetCore.App, thus preventing it from being used anywhere ASP.NET Core isn't supported (such as +// various platforms that .NET MAUI runs on, such as Android and iOS). + +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; + +namespace Microsoft.AspNetCore.Components.WebView +{ + /// + /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string + /// + [TypeConverter(typeof(PathStringConverter))] + internal readonly struct PathString : IEquatable + { + /// + /// Represents the empty path. This field is read-only. + /// + public static readonly PathString Empty = new(string.Empty); + + /// + /// Initialize the path string with a given value. This value must be in unescaped format. Use + /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format. + /// + /// The unescaped path to be assigned to the Value property. + public PathString(string? value) + { + if (!string.IsNullOrEmpty(value) && value[0] != '/') + { + throw new ArgumentException(Resources.FormatException_PathMustStartWithSlash(nameof(value)), nameof(value)); + } + Value = value; + } + + /// + /// The unescaped path value + /// + public string? Value { get; } + + /// + /// True if the path is not empty + /// + [MemberNotNullWhen(true, nameof(Value))] + public bool HasValue + { + get { return !string.IsNullOrEmpty(Value); } + } + + /// + /// Provides the path string escaped in a way which is correct for combining into the URI representation. + /// + /// The escaped path value + public override string ToString() + { + return ToUriComponent(); + } + + /// + /// Provides the path string escaped in a way which is correct for combining into the URI representation. + /// + /// The escaped path value + public string ToUriComponent() + { + if (!HasValue) + { + return string.Empty; + } + + var value = Value; + var i = 0; + for (; i < value.Length; i++) + { + if (!PathStringHelper.IsValidPathChar(value[i]) || PathStringHelper.IsPercentEncodedChar(value, i)) + { + break; + } + } + + if (i < value.Length) + { + return ToEscapedUriComponent(value, i); + } + + return value; + } + + private static string ToEscapedUriComponent(string value, int i) + { + StringBuilder? buffer = null; + + var start = 0; + var count = i; + var requiresEscaping = false; + + while (i < value.Length) + { + var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(value, i); + if (PathStringHelper.IsValidPathChar(value[i]) || isPercentEncodedChar) + { + if (requiresEscaping) + { + // the current segment requires escape + buffer ??= new StringBuilder(value.Length * 3); + buffer.Append(Uri.EscapeDataString(value.Substring(start, count))); + + requiresEscaping = false; + start = i; + count = 0; + } + + if (isPercentEncodedChar) + { + count += 3; + i += 3; + } + else + { + count++; + i++; + } + } + else + { + if (!requiresEscaping) + { + // the current segment doesn't require escape + buffer ??= new StringBuilder(value.Length * 3); + buffer.Append(value, start, count); + + requiresEscaping = true; + start = i; + count = 0; + } + + count++; + i++; + } + } + + if (count == value.Length && !requiresEscaping) + { + return value; + } + else + { + if (count > 0) + { + buffer ??= new StringBuilder(value.Length * 3); + + if (requiresEscaping) + { + buffer.Append(Uri.EscapeDataString(value.Substring(start, count))); + } + else + { + buffer.Append(value, start, count); + } + } + + return buffer?.ToString() ?? string.Empty; + } + } + + /// + /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any + /// value that is not a path. + /// + /// The escaped path as it appears in the URI format. + /// The resulting PathString + public static PathString FromUriComponent(string uriComponent) + { + // REVIEW: what is the exactly correct thing to do? + return new PathString(Uri.UnescapeDataString(uriComponent)); + } + + /// + /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported. + /// + /// The Uri object + /// The resulting PathString + public static PathString FromUriComponent(Uri uri) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + // REVIEW: what is the exactly correct thing to do? + return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)); + } + + /// + /// Determines whether the beginning of this instance matches the specified . + /// + /// The to compare. + /// true if value matches the beginning of this string; otherwise, false. + public bool StartsWithSegments(PathString other) + { + return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determines whether the beginning of this instance matches the specified when compared + /// using the specified comparison option. + /// + /// The to compare. + /// One of the enumeration values that determines how this and value are compared. + /// true if value matches the beginning of this string; otherwise, false. + public bool StartsWithSegments(PathString other, StringComparison comparisonType) + { + var value1 = Value ?? string.Empty; + var value2 = other.Value ?? string.Empty; + if (value1.StartsWith(value2, comparisonType)) + { + return value1.Length == value2.Length || value1[value2.Length] == '/'; + } + return false; + } + + /// + /// Determines whether the beginning of this instance matches the specified and returns + /// the remaining segments. + /// + /// The to compare. + /// The remaining segments after the match. + /// true if value matches the beginning of this string; otherwise, false. + public bool StartsWithSegments(PathString other, out PathString remaining) + { + return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out remaining); + } + + /// + /// Determines whether the beginning of this instance matches the specified when compared + /// using the specified comparison option and returns the remaining segments. + /// + /// The to compare. + /// One of the enumeration values that determines how this and value are compared. + /// The remaining segments after the match. + /// true if value matches the beginning of this string; otherwise, false. + public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString remaining) + { + var value1 = Value ?? string.Empty; + var value2 = other.Value ?? string.Empty; + if (value1.StartsWith(value2, comparisonType)) + { + if (value1.Length == value2.Length || value1[value2.Length] == '/') + { + remaining = new PathString(value1[value2.Length..]); + return true; + } + } + remaining = Empty; + return false; + } + + /// + /// Determines whether the beginning of this instance matches the specified and returns + /// the matched and remaining segments. + /// + /// The to compare. + /// The matched segments with the original casing in the source value. + /// The remaining segments after the match. + /// true if value matches the beginning of this string; otherwise, false. + public bool StartsWithSegments(PathString other, out PathString matched, out PathString remaining) + { + return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out matched, out remaining); + } + + /// + /// Determines whether the beginning of this instance matches the specified when compared + /// using the specified comparison option and returns the matched and remaining segments. + /// + /// The to compare. + /// One of the enumeration values that determines how this and value are compared. + /// The matched segments with the original casing in the source value. + /// The remaining segments after the match. + /// true if value matches the beginning of this string; otherwise, false. + public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString matched, out PathString remaining) + { + var value1 = Value ?? string.Empty; + var value2 = other.Value ?? string.Empty; + if (value1.StartsWith(value2, comparisonType)) + { + if (value1.Length == value2.Length || value1[value2.Length] == '/') + { + matched = new PathString(value1.Substring(0, value2.Length)); + remaining = new PathString(value1[value2.Length..]); + return true; + } + } + remaining = Empty; + matched = Empty; + return false; + } + + /// + /// Adds two PathString instances into a combined PathString value. + /// + /// The combined PathString value + public PathString Add(PathString other) + { + if (HasValue && + other.HasValue && + Value[^1] == '/') + { + // If the path string has a trailing slash and the other string has a leading slash, we need + // to trim one of them. + var combined = string.Concat(Value.AsSpan(), other.Value.AsSpan(1)); + return new PathString(combined); + } + + return new PathString(Value + other.Value); + } + + /// + /// Combines a PathString and QueryString into the joined URI formatted string value. + /// + /// The joined URI formatted string value + public string Add(QueryString other) + { + return ToUriComponent() + other.ToUriComponent(); + } + + /// + /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. + /// + /// The second PathString for comparison. + /// True if both PathString values are equal + public bool Equals(PathString other) + { + return Equals(other, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Compares this PathString value to another value using a specific StringComparison type + /// + /// The second PathString for comparison + /// The StringComparison type to use + /// True if both PathString values are equal + public bool Equals(PathString other, StringComparison comparisonType) + { + if (!HasValue && !other.HasValue) + { + return true; + } + return string.Equals(Value, other.Value, comparisonType); + } + + /// + /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase. + /// + /// The second PathString for comparison. + /// True if both PathString values are equal + public override bool Equals(object? obj) + { + if (obj is null) + { + return !HasValue; + } + return obj is PathString pathString && Equals(pathString); + } + + /// + /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation. + /// + /// The hash code + public override int GetHashCode() + { + return (HasValue ? StringComparer.OrdinalIgnoreCase.GetHashCode(Value) : 0); + } + + /// + /// Operator call through to Equals + /// + /// The left parameter + /// The right parameter + /// True if both PathString values are equal + public static bool operator ==(PathString left, PathString right) + { + return left.Equals(right); + } + + /// + /// Operator call through to Equals + /// + /// The left parameter + /// The right parameter + /// True if both PathString values are not equal + public static bool operator !=(PathString left, PathString right) + { + return !left.Equals(right); + } + + /// + /// + /// The left parameter + /// The right parameter + /// The ToString combination of both values + public static string operator +(string left, PathString right) + { + // This overload exists to prevent the implicit string<->PathString converter from + // trying to call the PathString+PathString operator for things that are not path strings. + return string.Concat(left, right.ToString()); + } + + /// + /// + /// The left parameter + /// The right parameter + /// The ToString combination of both values + public static string operator +(PathString left, string? right) + { + // This overload exists to prevent the implicit string<->PathString converter from + // trying to call the PathString+PathString operator for things that are not path strings. + return string.Concat(left.ToString(), right); + } + + /// + /// Operator call through to Add + /// + /// The left parameter + /// The right parameter + /// The PathString combination of both values + public static PathString operator +(PathString left, PathString right) + { + return left.Add(right); + } + + /// + /// Operator call through to Add + /// + /// The left parameter + /// The right parameter + /// The PathString combination of both values + public static string operator +(PathString left, QueryString right) + { + return left.Add(right); + } + + /// + /// Implicitly creates a new PathString from the given string. + /// + /// + public static implicit operator PathString(string? s) + => ConvertFromString(s); + + /// + /// Implicitly calls ToString(). + /// + /// + public static implicit operator string(PathString path) + => path.ToString(); + + internal static PathString ConvertFromString(string? s) + => string.IsNullOrEmpty(s) ? new PathString(s) : FromUriComponent(s); + } + + internal sealed class PathStringConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + => value is string @string + ? PathString.ConvertFromString(@string) + : base.ConvertFrom(context, culture, value); + + public override object ConvertTo(ITypeDescriptorContext context, + CultureInfo culture, object value, Type destinationType) + => destinationType == typeof(string) + ? value.ToString() ?? string.Empty + : base.ConvertTo(context, culture, value, destinationType); + } +} diff --git a/src/Components/WebView/WebView/src/PathStringHelper.cs b/src/Components/WebView/WebView/src/PathStringHelper.cs new file mode 100644 index 000000000000..9fecfa919103 --- /dev/null +++ b/src/Components/WebView/WebView/src/PathStringHelper.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// NOTE: This file is copied from src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs +// and made internal with a namespace change. +// It can't be referenced directly from the StaticFiles package because that would cause this package to require +// Microsoft.AspNetCore.App, thus preventing it from being used anywhere ASP.NET Core isn't supported (such as +// various platforms that .NET MAUI runs on, such as Android and iOS). + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Microsoft.AspNetCore.Components.WebView +{ + internal static class PathStringHelper + { + // uint[] bits uses 1 cache line (Array info + 16 bytes) + // bool[] would use 3 cache lines (Array info + 128 bytes) + // So we use 128 bits rather than 128 bytes/bools + private static readonly uint[] ValidPathChars = { + 0b_0000_0000__0000_0000__0000_0000__0000_0000, // 0x00 - 0x1F + 0b_0010_1111__1111_1111__1111_1111__1101_0010, // 0x20 - 0x3F + 0b_1000_0111__1111_1111__1111_1111__1111_1111, // 0x40 - 0x5F + 0b_0100_0111__1111_1111__1111_1111__1111_1110, // 0x60 - 0x7F + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsValidPathChar(char c) + { + // Use local array and uint .Length compare to elide the bounds check on array access + var validChars = ValidPathChars; + var i = (int)c; + + // Array is in chunks of 32 bits, so get offset by dividing by 32 + var offset = i >> 5; // i / 32; + // Significant bit position is the remainder of the above calc; i % 32 => i & 31 + var significantBit = 1u << (i & 31); + + // Check offset in bounds and check if significant bit set + return (uint)offset < (uint)validChars.Length && + ((validChars[offset] & significantBit) != 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPercentEncodedChar(string str, int index) + { + var len = (uint)str.Length; + if (str[index] == '%' && index < len - 2) + { + return AreFollowingTwoCharsHex(str, index); + } + + return false; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreFollowingTwoCharsHex(string str, int index) + { + Debug.Assert(index < str.Length - 2); + + var c1 = str[index + 1]; + var c2 = str[index + 2]; + return IsHexadecimalChar(c1) && IsHexadecimalChar(c2); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsHexadecimalChar(char c) + { + // Between 0 - 9 or uppercased between A - F + return (uint)(c - '0') <= 9 || (uint)((c & ~0x20) - 'A') <= ('F' - 'A'); + } + } +} diff --git a/src/Components/WebView/WebView/src/QueryString.cs b/src/Components/WebView/WebView/src/QueryString.cs new file mode 100644 index 000000000000..51252ce95f0e --- /dev/null +++ b/src/Components/WebView/WebView/src/QueryString.cs @@ -0,0 +1,304 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// NOTE: This file is copied from src/Http/Http.Abstractions/src/QueryString.cs +// and made internal with a namespace change. +// It can't be referenced directly from the StaticFiles package because that would cause this package to require +// Microsoft.AspNetCore.App, thus preventing it from being used anywhere ASP.NET Core isn't supported (such as +// various platforms that .NET MAUI runs on, such as Android and iOS). + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using System.Text.Encodings.Web; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Components.WebView +{ + /// + /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string + /// + internal readonly struct QueryString : IEquatable + { + /// + /// Represents the empty query string. This field is read-only. + /// + public static readonly QueryString Empty = new QueryString(string.Empty); + + /// + /// Initialize the query string with a given value. This value must be in escaped and delimited format with + /// a leading '?' character. + /// + /// The query string to be assigned to the Value property. + public QueryString(string? value) + { + if (!string.IsNullOrEmpty(value) && value[0] != '?') + { + throw new ArgumentException("The leading '?' must be included for a non-empty query.", nameof(value)); + } + Value = value; + } + + /// + /// The escaped query string with the leading '?' character + /// + public string? Value { get; } + + /// + /// True if the query string is not empty + /// + public bool HasValue => !string.IsNullOrEmpty(Value); + + /// + /// Provides the query string escaped in a way which is correct for combining into the URI representation. + /// A leading '?' character will be included unless the Value is null or empty. Characters which are potentially + /// dangerous are escaped. + /// + /// The query string value + public override string ToString() + { + return ToUriComponent(); + } + + /// + /// Provides the query string escaped in a way which is correct for combining into the URI representation. + /// A leading '?' character will be included unless the Value is null or empty. Characters which are potentially + /// dangerous are escaped. + /// + /// The query string value + public string ToUriComponent() + { + // Escape things properly so System.Uri doesn't mis-interpret the data. + return !string.IsNullOrEmpty(Value) ? Value!.Replace("#", "%23") : string.Empty; + } + + /// + /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any + /// value that is not a query. + /// + /// The escaped query as it appears in the URI format. + /// The resulting QueryString + public static QueryString FromUriComponent(string uriComponent) + { + if (string.IsNullOrEmpty(uriComponent)) + { + return new QueryString(string.Empty); + } + return new QueryString(uriComponent); + } + + /// + /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported. + /// + /// The Uri object + /// The resulting QueryString + public static QueryString FromUriComponent(Uri uri) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + string queryValue = uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped); + if (!string.IsNullOrEmpty(queryValue)) + { + queryValue = "?" + queryValue; + } + return new QueryString(queryValue); + } + + /// + /// Create a query string with a single given parameter name and value. + /// + /// The un-encoded parameter name + /// The un-encoded parameter value + /// The resulting QueryString + public static QueryString Create(string name, string value) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (!string.IsNullOrEmpty(value)) + { + value = UrlEncoder.Default.Encode(value); + } + return new QueryString($"?{UrlEncoder.Default.Encode(name)}={value}"); + } + + /// + /// Creates a query string composed from the given name value pairs. + /// + /// + /// The resulting QueryString + public static QueryString Create(IEnumerable> parameters) + { + var builder = new StringBuilder(); + var first = true; + foreach (var pair in parameters) + { + AppendKeyValuePair(builder, pair.Key, pair.Value, first); + first = false; + } + + return new QueryString(builder.ToString()); + } + + /// + /// Creates a query string composed from the given name value pairs. + /// + /// + /// The resulting QueryString + public static QueryString Create(IEnumerable> parameters) + { + var builder = new StringBuilder(); + var first = true; + + foreach (var pair in parameters) + { + // If nothing in this pair.Values, append null value and continue + if (StringValues.IsNullOrEmpty(pair.Value)) + { + AppendKeyValuePair(builder, pair.Key, null, first); + first = false; + continue; + } + // Otherwise, loop through values in pair.Value + foreach (var value in pair.Value) + { + AppendKeyValuePair(builder, pair.Key, value, first); + first = false; + } + } + + return new QueryString(builder.ToString()); + } + + /// + /// Concatenates to the current query string. + /// + /// The to concatenate. + /// The concatenated . + public QueryString Add(QueryString other) + { + if (!HasValue || Value!.Equals("?", StringComparison.Ordinal)) + { + return other; + } + if (!other.HasValue || other.Value!.Equals("?", StringComparison.Ordinal)) + { + return this; + } + + // ?name1=value1 Add ?name2=value2 returns ?name1=value1&name2=value2 + return new QueryString(Value + "&" + other.Value.Substring(1)); + } + + /// + /// Concatenates a query string with and + /// to the current query string. + /// + /// The name of the query string to concatenate. + /// The value of the query string to concatenate. + /// The concatenated . + public QueryString Add(string name, string value) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (!HasValue || Value!.Equals("?", StringComparison.Ordinal)) + { + return Create(name, value); + } + + var builder = new StringBuilder(Value); + AppendKeyValuePair(builder, name, value, first: false); + return new QueryString(builder.ToString()); + } + + /// + /// Evalutes if the current query string is equal to . + /// + /// The to compare. + /// if the ssquery strings are equal. + public bool Equals(QueryString other) + { + if (!HasValue && !other.HasValue) + { + return true; + } + return string.Equals(Value, other.Value, StringComparison.Ordinal); + } + + /// + /// Evaluates if the current query string is equal to an object . + /// + /// An object to compare. + /// if the query strings are equal. + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return !HasValue; + } + return obj is QueryString && Equals((QueryString)obj); + } + + /// + /// Gets a hash code for the value. + /// + /// The hash code as an . + public override int GetHashCode() + { + return (HasValue ? Value!.GetHashCode() : 0); + } + + /// + /// Evaluates if one query string is equal to another. + /// + /// A instance. + /// A instance. + /// if the query strings are equal. + public static bool operator ==(QueryString left, QueryString right) + { + return left.Equals(right); + } + + /// + /// Evaluates if one query string is not equal to another. + /// + /// A instance. + /// A instance. + /// if the query strings are not equal. + public static bool operator !=(QueryString left, QueryString right) + { + return !left.Equals(right); + } + + /// + /// Concatenates and into a single query string. + /// + /// A instance. + /// A instance. + /// The concatenated . + public static QueryString operator +(QueryString left, QueryString right) + { + return left.Add(right); + } + + private static void AppendKeyValuePair(StringBuilder builder, string key, string? value, bool first) + { + builder.Append(first ? "?" : "&"); + builder.Append(UrlEncoder.Default.Encode(key)); + builder.Append("="); + if (!string.IsNullOrEmpty(value)) + { + builder.Append(UrlEncoder.Default.Encode(value)); + } + } + } +} diff --git a/src/Components/WebView/WebView/src/Resources.resx b/src/Components/WebView/WebView/src/Resources.resx new file mode 100644 index 000000000000..98f867294e36 --- /dev/null +++ b/src/Components/WebView/WebView/src/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The path in '{0}' must start with '/'. + + \ No newline at end of file diff --git a/src/Components/WebView/WebView/src/StaticContentProvider.cs b/src/Components/WebView/WebView/src/StaticContentProvider.cs index fb4aa1db96a1..a8e700e356e0 100644 --- a/src/Components/WebView/WebView/src/StaticContentProvider.cs +++ b/src/Components/WebView/WebView/src/StaticContentProvider.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Text; -using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Components.WebView diff --git a/src/Components/WebView/WebView/src/StaticWebAssetsLoader.cs b/src/Components/WebView/WebView/src/StaticWebAssetsLoader.cs index 269a0def0030..2c38b5597f9e 100644 --- a/src/Components/WebView/WebView/src/StaticWebAssetsLoader.cs +++ b/src/Components/WebView/WebView/src/StaticWebAssetsLoader.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Reflection; using System.Xml.Linq; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives;