From 6752bb0ae2576e9c84720e79e48a05da7267bb1a Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Tue, 20 May 2025 13:04:09 +0300 Subject: [PATCH 01/12] Add schema version validation and log a warning, also copying dup of CompareSemVer --- dev-proxy-abstractions/ProxyUtils.cs | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/dev-proxy-abstractions/ProxyUtils.cs b/dev-proxy-abstractions/ProxyUtils.cs index 5ce6b3bb..322ffe93 100644 --- a/dev-proxy-abstractions/ProxyUtils.cs +++ b/dev-proxy-abstractions/ProxyUtils.cs @@ -510,4 +510,107 @@ public static string ReplaceVariables(string s, Dictionary varia return s1; } + + public static void ValidateSchemaVersion(string schemaUrl, ILogger logger) + { + if (string.IsNullOrWhiteSpace(schemaUrl)) + { + logger.LogDebug("Schema is empty, skipping schema version validation."); + return; + } + try + { + var uri = new Uri(schemaUrl); + if (uri.Segments.Length > 2) + { + var schemaVersion = uri.Segments[^2] + .TrimStart('v') + .TrimEnd('/'); + if (CompareSemVer(ProductVersion, schemaVersion) != 0) + { + var currentSchemaUrl = uri.ToString().Replace($"/v{schemaVersion}/", $"/v{ProductVersion}/"); + logger.LogWarning("The version of schema does not match the installed Dev Proxy version, the expected schema is {schema}", currentSchemaUrl); + } + } + else + { + logger.LogDebug("Invalid schema {schemaUrl}, skipping schema version validation.", schemaUrl); + } + } + catch(Exception ex) + { + logger.LogWarning("Invalid schema {schemaUrl}, skipping schema version validation. Error: {error}", schemaUrl, ex.Message); + } + } + + /// + /// Compares two semantic versions strings. + /// + /// ver1 + /// ver2 + /// Returns 0 if the versions are equal, -1 if a is less than b, and 1 if a is greater than b. + private static int CompareSemVer(string? a, string? b) + { + if (a == null && b == null) + { + return 0; + } + else if (a == null) + { + return -1; + } + else if (b == null) + { + return 1; + } + + if (a.StartsWith('v')) + { + a = a[1..]; + } + if (b.StartsWith('v')) + { + b = b[1..]; + } + + var aParts = a.Split('-'); + var bParts = b.Split('-'); + + var aVersion = new Version(aParts[0]); + var bVersion = new Version(bParts[0]); + + var compare = aVersion.CompareTo(bVersion); + if (compare != 0) + { + // if the versions are different, return the comparison result + return compare; + } + + // if the versions are the same, compare the prerelease tags + if (aParts.Length == 1 && bParts.Length == 1) + { + // if both versions are stable, they are equal + return 0; + } + else if (aParts.Length == 1) + { + // if a is stable and b is not, a is greater + return 1; + } + else if (bParts.Length == 1) + { + // if b is stable and a is not, b is greater + return -1; + } + else if (aParts[1] == bParts[1]) + { + // if both versions are prerelease and the tags are the same, they are equal + return 0; + } + else + { + // if both versions are prerelease, b is greater + return -1; + } + } } From 118613aed1822516c75e8ba3c8c9bec16f266d5d Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Tue, 20 May 2025 13:18:46 +0300 Subject: [PATCH 02/12] Add schema version validation for devproxy config --- dev-proxy/PluginLoader.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dev-proxy/PluginLoader.cs b/dev-proxy/PluginLoader.cs index 75c545e9..6f608192 100644 --- a/dev-proxy/PluginLoader.cs +++ b/dev-proxy/PluginLoader.cs @@ -134,6 +134,16 @@ private PluginConfig PluginConfig { if (_pluginConfig == null) { + var schemaUrl = Configuration.GetValue("$schema"); + if (string.IsNullOrWhiteSpace(schemaUrl)) + { + _logger.LogDebug("No schema URL found in configuration file, skipping schema version validation"); + } + else + { + ProxyUtils.ValidateSchemaVersion(schemaUrl, _logger); + } + _pluginConfig = new PluginConfig(); if (isDiscover) From a9665f7d583b8c15b4cc061f1cda6d9e02e4c2aa Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Tue, 20 May 2025 13:25:12 +0300 Subject: [PATCH 03/12] Add schema version validation for plugin section config --- dev-proxy-abstractions/BaseProxyPlugin.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-proxy-abstractions/BaseProxyPlugin.cs b/dev-proxy-abstractions/BaseProxyPlugin.cs index 802c5df6..7711d7a0 100644 --- a/dev-proxy-abstractions/BaseProxyPlugin.cs +++ b/dev-proxy-abstractions/BaseProxyPlugin.cs @@ -83,6 +83,7 @@ public virtual async Task RegisterAsync() return (false, [string.Format(CultureInfo.InvariantCulture, "Configuration section {0} not found in configuration file", configSectionName)]); } + ProxyUtils.ValidateSchemaVersion(schemaUrl, Logger); return await ProxyUtils.ValidateJson(configSection.GetRawText(), schemaUrl, Logger); } catch (Exception ex) From 1e58d30f1505a3b63e9cd77a1e52a4b986c9e604 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Tue, 20 May 2025 13:27:52 +0300 Subject: [PATCH 04/12] Add schema version validation for plugin config --- dev-proxy-abstractions/BaseLoader.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dev-proxy-abstractions/BaseLoader.cs b/dev-proxy-abstractions/BaseLoader.cs index 3d73e284..d64b8591 100644 --- a/dev-proxy-abstractions/BaseLoader.cs +++ b/dev-proxy-abstractions/BaseLoader.cs @@ -24,13 +24,14 @@ private async Task ValidateFileContents(string fileContents) using var document = JsonDocument.Parse(fileContents); var root = document.RootElement; - if (!root.TryGetProperty("$schema", out var schemaUrl)) + if (!root.TryGetProperty("$schema", out var schemaUrlElement)) { _logger.LogDebug("Schema reference not found in file {File}. Skipping schema validation", FilePath); return true; } - - var (IsValid, ValidationErrors) = await ProxyUtils.ValidateJson(fileContents, schemaUrl.GetString(), _logger); + var schemaUrl = schemaUrlElement.GetString() ?? ""; + ProxyUtils.ValidateSchemaVersion(schemaUrl, _logger); + var (IsValid, ValidationErrors) = await ProxyUtils.ValidateJson(fileContents, schemaUrl, _logger); if (!IsValid) { _logger.LogError("Schema validation failed for {File} with the following errors: {Errors}", FilePath, string.Join(", ", ValidationErrors)); From a663ef85bb6eb4b33e8d231e66f81f835c784817 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Tue, 20 May 2025 13:55:46 +0300 Subject: [PATCH 05/12] Remove CompareSemVer dup and refactor a bit --- dev-proxy-abstractions/ProxyUtils.cs | 41 +++++++++------- dev-proxy/UpdateNotification.cs | 72 +--------------------------- 2 files changed, 26 insertions(+), 87 deletions(-) diff --git a/dev-proxy-abstractions/ProxyUtils.cs b/dev-proxy-abstractions/ProxyUtils.cs index 322ffe93..d229527d 100644 --- a/dev-proxy-abstractions/ProxyUtils.cs +++ b/dev-proxy-abstractions/ProxyUtils.cs @@ -548,38 +548,47 @@ public static void ValidateSchemaVersion(string schemaUrl, ILogger logger) /// /// ver1 /// ver2 - /// Returns 0 if the versions are equal, -1 if a is less than b, and 1 if a is greater than b. - private static int CompareSemVer(string? a, string? b) + /// + /// Returns 0 if the versions are equal, -1 if a is less than b, and 1 if a is greater than b. + /// An invalid argument is "rounded" to a minimal version. + /// + public static int CompareSemVer(string? a, string? b) { - if (a == null && b == null) + if (string.IsNullOrWhiteSpace(a) && string.IsNullOrWhiteSpace(b)) { return 0; } - else if (a == null) + else if (string.IsNullOrWhiteSpace(a)) { return -1; } - else if (b == null) + else if (string.IsNullOrWhiteSpace(b)) { return 1; } - if (a.StartsWith('v')) - { - a = a[1..]; - } - if (b.StartsWith('v')) - { - b = b[1..]; - } + a = a.TrimStart('v'); + b = b.TrimStart('v'); var aParts = a.Split('-'); var bParts = b.Split('-'); - var aVersion = new Version(aParts[0]); - var bVersion = new Version(bParts[0]); + var aParsed = Version.TryParse(aParts[0], out var aVersion); + var bParsed = Version.TryParse(bParts[0], out var bVersion); + if (!aParsed && !bParsed) + { + return 0; + } + else if (!aParsed) + { + return -1; + } + else if (!bParsed) + { + return 1; + } - var compare = aVersion.CompareTo(bVersion); + var compare = aVersion!.CompareTo(bVersion); if (compare != 0) { // if the versions are different, return the comparison result diff --git a/dev-proxy/UpdateNotification.cs b/dev-proxy/UpdateNotification.cs index 71dcd074..b647bc78 100644 --- a/dev-proxy/UpdateNotification.cs +++ b/dev-proxy/UpdateNotification.cs @@ -45,7 +45,7 @@ internal static class UpdateNotification // -1 = latest release is greater // 0 = versions are equal // 1 = current version is greater - if (CompareSemVer(currentVersion, latestReleaseVersion) < 0) + if (ProxyUtils.CompareSemVer(currentVersion, latestReleaseVersion) < 0) { return latestRelease; } @@ -60,76 +60,6 @@ internal static class UpdateNotification } } - /// - /// Compares two semantic versions strings. - /// - /// ver1 - /// ver2 - /// Returns 0 if the versions are equal, -1 if a is less than b, and 1 if a is greater than b. - private static int CompareSemVer(string? a, string? b) - { - if (a == null && b == null) - { - return 0; - } - else if (a == null) - { - return -1; - } - else if (b == null) - { - return 1; - } - - if (a.StartsWith('v')) - { - a = a[1..]; - } - if (b.StartsWith('v')) - { - b = b[1..]; - } - - var aParts = a.Split('-'); - var bParts = b.Split('-'); - - var aVersion = new Version(aParts[0]); - var bVersion = new Version(bParts[0]); - - var compare = aVersion.CompareTo(bVersion); - if (compare != 0) - { - // if the versions are different, return the comparison result - return compare; - } - - // if the versions are the same, compare the prerelease tags - if (aParts.Length == 1 && bParts.Length == 1) - { - // if both versions are stable, they are equal - return 0; - } - else if (aParts.Length == 1) - { - // if a is stable and b is not, a is greater - return 1; - } - else if (bParts.Length == 1) - { - // if b is stable and a is not, b is greater - return -1; - } - else if (aParts[1] == bParts[1]) - { - // if both versions are prerelease and the tags are the same, they are equal - return 0; - } - else { - // if both versions are prerelease, b is greater - return -1; - } - } - private static async Task GetLatestReleaseAsync(ReleaseType releaseType) { var http = new HttpClient(); From ff27598e5acbc86cbbb48d8216023cc26213951b Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Thu, 22 May 2025 11:11:56 +0300 Subject: [PATCH 06/12] Fix build and adjust format --- dev-proxy-abstractions/ProxyUtils.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dev-proxy-abstractions/ProxyUtils.cs b/dev-proxy-abstractions/ProxyUtils.cs index f2060f25..ed4f16e5 100644 --- a/dev-proxy-abstractions/ProxyUtils.cs +++ b/dev-proxy-abstractions/ProxyUtils.cs @@ -355,7 +355,7 @@ public static void MergeHeaders(IList allHeaders, IList jsonSerializerOptions; - + public static JsonDocumentOptions JsonDocumentOptions { get; } = new() { AllowTrailingCommas = true, @@ -425,7 +425,8 @@ public static List GetWildcardPatterns(List urls) } // For multiple URLs, find the common prefix - var paths = group.Select(url => { + var paths = group.Select(url => + { if (url.Contains('*')) { return url; @@ -544,7 +545,7 @@ public static void ValidateSchemaVersion(string schemaUrl, ILogger logger) logger.LogDebug("Invalid schema {schemaUrl}, skipping schema version validation.", schemaUrl); } } - catch(Exception ex) + catch (Exception ex) { logger.LogWarning("Invalid schema {schemaUrl}, skipping schema version validation. Error: {error}", schemaUrl, ex.Message); } @@ -628,6 +629,7 @@ public static int CompareSemVer(string? a, string? b) // if both versions are prerelease, b is greater return -1; } + } public static string GetVersionString(string productVersion) { From e61958b3bb484feb776bfdd074f4e9c87a9d6571 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Thu, 22 May 2025 11:59:40 +0300 Subject: [PATCH 07/12] Remove pre-release suffix from scheme version if any --- dev-proxy-abstractions/ProxyUtils.cs | 3 ++- dev-proxy/UpdateNotification.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dev-proxy-abstractions/ProxyUtils.cs b/dev-proxy-abstractions/ProxyUtils.cs index ed4f16e5..37e5716a 100644 --- a/dev-proxy-abstractions/ProxyUtils.cs +++ b/dev-proxy-abstractions/ProxyUtils.cs @@ -536,7 +536,8 @@ public static void ValidateSchemaVersion(string schemaUrl, ILogger logger) .TrimEnd('/'); if (CompareSemVer(ProductVersion, schemaVersion) != 0) { - var currentSchemaUrl = uri.ToString().Replace($"/v{schemaVersion}/", $"/v{ProductVersion}/"); + var currentVersion = GetVersionString(ProductVersion); + var currentSchemaUrl = uri.ToString().Replace($"/v{schemaVersion}/", $"/v{currentVersion}/"); logger.LogWarning("The version of schema does not match the installed Dev Proxy version, the expected schema is {schema}", currentSchemaUrl); } } diff --git a/dev-proxy/UpdateNotification.cs b/dev-proxy/UpdateNotification.cs index b647bc78..740911d2 100644 --- a/dev-proxy/UpdateNotification.cs +++ b/dev-proxy/UpdateNotification.cs @@ -40,7 +40,7 @@ internal static class UpdateNotification } var latestReleaseVersion = latestRelease.Version; - var currentVersion = ProxyUtils.ProductVersion; + var currentVersion = ProxyUtils.GetVersionString(ProxyUtils.ProductVersion); // -1 = latest release is greater // 0 = versions are equal From 4b873964a31a4625f87e1f65adcad16644cd6591 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Thu, 22 May 2025 12:28:39 +0300 Subject: [PATCH 08/12] Refactor GetVersionString(), rename to NormalizeVersion --- dev-proxy-abstractions/ProxyUtils.cs | 13 ++++++++----- .../CommandHandlers/ConfigNewCommandHandler.cs | 2 +- dev-proxy/UpdateNotification.cs | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/dev-proxy-abstractions/ProxyUtils.cs b/dev-proxy-abstractions/ProxyUtils.cs index 37e5716a..9b813d76 100644 --- a/dev-proxy-abstractions/ProxyUtils.cs +++ b/dev-proxy-abstractions/ProxyUtils.cs @@ -536,7 +536,7 @@ public static void ValidateSchemaVersion(string schemaUrl, ILogger logger) .TrimEnd('/'); if (CompareSemVer(ProductVersion, schemaVersion) != 0) { - var currentVersion = GetVersionString(ProductVersion); + var currentVersion = NormalizeVersion(ProductVersion); var currentSchemaUrl = uri.ToString().Replace($"/v{schemaVersion}/", $"/v{currentVersion}/"); logger.LogWarning("The version of schema does not match the installed Dev Proxy version, the expected schema is {schema}", currentSchemaUrl); } @@ -632,10 +632,13 @@ public static int CompareSemVer(string? a, string? b) } } - public static string GetVersionString(string productVersion) + /// + /// Produces major.minor.patch version dropping a pre-release suffix. + /// + /// A version looks like "0.28.1", "0.28.1-alpha", "0.28.10-beta.1", "0.28.10-rc.1", or "0.28.0-preview-1", etc. + public static string NormalizeVersion(string version) { - return productVersion.Contains("-beta") - ? productVersion.Split("-")[0] - : productVersion; + version ??= string.Empty; + return version.Split('-', StringSplitOptions.None)[0]; } } diff --git a/dev-proxy/CommandHandlers/ConfigNewCommandHandler.cs b/dev-proxy/CommandHandlers/ConfigNewCommandHandler.cs index fe24cf14..1b633a9b 100644 --- a/dev-proxy/CommandHandlers/ConfigNewCommandHandler.cs +++ b/dev-proxy/CommandHandlers/ConfigNewCommandHandler.cs @@ -17,7 +17,7 @@ public class VisualStudioCodeSnippet public static class ConfigNewCommandHandler { - private static readonly string snippetsFileUrl = $"https://aka.ms/devproxy/snippets/v{ProxyUtils.GetVersionString(ProxyUtils.ProductVersion)}"; + private static readonly string snippetsFileUrl = $"https://aka.ms/devproxy/snippets/v{ProxyUtils.NormalizeVersion(ProxyUtils.ProductVersion)}"; private static readonly string configFileSnippetName = "ConfigFile"; public static async Task CreateConfigFileAsync(string name, ILogger logger) diff --git a/dev-proxy/UpdateNotification.cs b/dev-proxy/UpdateNotification.cs index 740911d2..aad9aab2 100644 --- a/dev-proxy/UpdateNotification.cs +++ b/dev-proxy/UpdateNotification.cs @@ -40,7 +40,7 @@ internal static class UpdateNotification } var latestReleaseVersion = latestRelease.Version; - var currentVersion = ProxyUtils.GetVersionString(ProxyUtils.ProductVersion); + var currentVersion = ProxyUtils.NormalizeVersion(ProxyUtils.ProductVersion); // -1 = latest release is greater // 0 = versions are equal From 071c7f1a4d587f29532baf7350c9a38556341a9c Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Tue, 27 May 2025 12:51:23 +0300 Subject: [PATCH 09/12] Remove redundant null-coalescing assignment --- dev-proxy-abstractions/ProxyUtils.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/dev-proxy-abstractions/ProxyUtils.cs b/dev-proxy-abstractions/ProxyUtils.cs index 9b813d76..16faed64 100644 --- a/dev-proxy-abstractions/ProxyUtils.cs +++ b/dev-proxy-abstractions/ProxyUtils.cs @@ -320,7 +320,6 @@ public static string ProductVersion } } - return _productVersion; } } @@ -638,7 +637,6 @@ public static int CompareSemVer(string? a, string? b) /// A version looks like "0.28.1", "0.28.1-alpha", "0.28.10-beta.1", "0.28.10-rc.1", or "0.28.0-preview-1", etc. public static string NormalizeVersion(string version) { - version ??= string.Empty; return version.Split('-', StringSplitOptions.None)[0]; } } From 0adebab2d9b1f84ab49fe4b3640cc6a6ffea677a Mon Sep 17 00:00:00 2001 From: waldekmastykarz Date: Fri, 30 May 2025 11:59:47 +0200 Subject: [PATCH 10/12] Layout --- dev-proxy-abstractions/BaseLoader.cs | 2 ++ dev-proxy-abstractions/ProxyUtils.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/dev-proxy-abstractions/BaseLoader.cs b/dev-proxy-abstractions/BaseLoader.cs index 3297280e..f16ead4a 100644 --- a/dev-proxy-abstractions/BaseLoader.cs +++ b/dev-proxy-abstractions/BaseLoader.cs @@ -29,9 +29,11 @@ private async Task ValidateFileContents(string fileContents) _logger.LogDebug("Schema reference not found in file {File}. Skipping schema validation", FilePath); return true; } + var schemaUrl = schemaUrlElement.GetString() ?? ""; ProxyUtils.ValidateSchemaVersion(schemaUrl, _logger); var (IsValid, ValidationErrors) = await ProxyUtils.ValidateJson(fileContents, schemaUrl, _logger); + if (!IsValid) { _logger.LogError("Schema validation failed for {File} with the following errors: {Errors}", FilePath, string.Join(", ", ValidationErrors)); diff --git a/dev-proxy-abstractions/ProxyUtils.cs b/dev-proxy-abstractions/ProxyUtils.cs index 16faed64..8a2884ef 100644 --- a/dev-proxy-abstractions/ProxyUtils.cs +++ b/dev-proxy-abstractions/ProxyUtils.cs @@ -525,6 +525,7 @@ public static void ValidateSchemaVersion(string schemaUrl, ILogger logger) logger.LogDebug("Schema is empty, skipping schema version validation."); return; } + try { var uri = new Uri(schemaUrl); From 5628a97e654353feeb90fa890acb321c93908874 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Sat, 31 May 2025 17:06:41 +0300 Subject: [PATCH 11/12] Fix regression, notify to update a beta version if there is a released one --- dev-proxy/UpdateNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-proxy/UpdateNotification.cs b/dev-proxy/UpdateNotification.cs index aad9aab2..b647bc78 100644 --- a/dev-proxy/UpdateNotification.cs +++ b/dev-proxy/UpdateNotification.cs @@ -40,7 +40,7 @@ internal static class UpdateNotification } var latestReleaseVersion = latestRelease.Version; - var currentVersion = ProxyUtils.NormalizeVersion(ProxyUtils.ProductVersion); + var currentVersion = ProxyUtils.ProductVersion; // -1 = latest release is greater // 0 = versions are equal From a04a7a866dea09df6acf8ca28714842e70235d27 Mon Sep 17 00:00:00 2001 From: Artem Azaraev Date: Sat, 31 May 2025 17:13:22 +0300 Subject: [PATCH 12/12] Fix to compare normalized scheme versions and avoid false-positive scheme warning if current is beta version --- dev-proxy-abstractions/ProxyUtils.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-proxy-abstractions/ProxyUtils.cs b/dev-proxy-abstractions/ProxyUtils.cs index 8a2884ef..177b78cb 100644 --- a/dev-proxy-abstractions/ProxyUtils.cs +++ b/dev-proxy-abstractions/ProxyUtils.cs @@ -525,7 +525,7 @@ public static void ValidateSchemaVersion(string schemaUrl, ILogger logger) logger.LogDebug("Schema is empty, skipping schema version validation."); return; } - + try { var uri = new Uri(schemaUrl); @@ -534,9 +534,9 @@ public static void ValidateSchemaVersion(string schemaUrl, ILogger logger) var schemaVersion = uri.Segments[^2] .TrimStart('v') .TrimEnd('/'); - if (CompareSemVer(ProductVersion, schemaVersion) != 0) + var currentVersion = NormalizeVersion(ProductVersion); + if (CompareSemVer(currentVersion, schemaVersion) != 0) { - var currentVersion = NormalizeVersion(ProductVersion); var currentSchemaUrl = uri.ToString().Replace($"/v{schemaVersion}/", $"/v{currentVersion}/"); logger.LogWarning("The version of schema does not match the installed Dev Proxy version, the expected schema is {schema}", currentSchemaUrl); }