Skip to content

Commit a26ec29

Browse files
Added --allow-unsecure-downloads option for HTTP downloads (#615)
1 parent c92436b commit a26ec29

File tree

11 files changed

+154
-11
lines changed

11 files changed

+154
-11
lines changed

doc/new.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The following arguments are available:
2727
| Argument | Description |
2828
|--------------|-------------|
2929
| **-o,--out** | The output directory where the newly created manifests will be saved locally |
30+
| **--allow-unsecure-downloads** | Allow unsecure downloads (HTTP) for this operation. |
3031
| **-f,--format** | Output format of the manifest. Default is "yaml". |
3132
| **-t,--token** | GitHub personal access token used for direct submission to the Windows Package Manager repo. <br/>⚠️ _Using this argument may result in the token being logged. Consider an alternative approach https://aka.ms/winget-create-token._ |
3233
| **-?, --help** | Gets additional help on this command |

doc/update.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ The following arguments are available:
119119
| **-r, --replace** | Boolean value for replacing an existing manifest from the Windows Package Manager repo. Optionally provide a version or else the latest version will be replaced. Default is false. |
120120
| **-i, --interactive** | Boolean value for making the update command interactive. If true, the tool will prompt the user for input. Default is false. |
121121
| **-f,--format** | Output format of the manifest. Default is "yaml". |
122+
| **--allow-unsecure-downloads** | Allow unsecure downloads (HTTP) for this operation. |
122123
| **-t, --token** | GitHub personal access token used for direct submission to the Windows Package Manager repo. If no token is provided, tool will prompt for GitHub login credentials. <br/>⚠️ _Using this argument may result in the token being logged. Consider an alternative approach https://aka.ms/winget-create-token._ |
123124
| **-?, --help** | Gets additional help on this command. |
124125

src/WingetCreateCLI/Commands/BaseCommand.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,10 @@ protected static async Task<string> GitHubOAuthLoginFlow()
308308
/// <summary>
309309
/// Downloads the package file from the provided installer url.
310310
/// </summary>
311-
/// /// <param name="installerUrl"> Installer Url to be downloaded. </param>
311+
/// <param name="installerUrl">Installer Url to be downloaded. </param>
312+
/// <param name="allowHttp">The flag indicating whether to allow HTTP downloads.</param>
312313
/// <returns>Package file.</returns>
313-
protected static async Task<string> DownloadPackageFile(string installerUrl)
314+
protected static async Task<string> DownloadPackageFile(string installerUrl, bool allowHttp)
314315
{
315316
Logger.InfoLocalized(nameof(Resources.DownloadInstaller_Message), installerUrl);
316317

@@ -332,7 +333,7 @@ protected static async Task<string> DownloadPackageFile(string installerUrl)
332333

333334
try
334335
{
335-
string packageFilePath = await PackageParser.DownloadFileAsync(installerUrl);
336+
string packageFilePath = await PackageParser.DownloadFileAsync(installerUrl, allowHttp);
336337
TelemetryManager.Log.WriteEvent(new DownloadInstallerEvent { IsSuccessful = true });
337338
DownloadedInstallers.Add(installerUrl, packageFilePath);
338339
return packageFilePath;
@@ -377,6 +378,16 @@ protected static async Task<string> DownloadPackageFile(string installerUrl)
377378
Logger.ErrorLocalized(nameof(Resources.DownloadConnectionTimeout_Error));
378379
return null;
379380
}
381+
else if (e is NotSupportedException)
382+
{
383+
Logger.ErrorLocalized(nameof(Resources.DownloadProtocolNotSupported_Error));
384+
return null;
385+
}
386+
else if (e is DownloadHttpsOnlyException)
387+
{
388+
Logger.ErrorLocalized(nameof(Resources.DownloadHttpsOnly_Error));
389+
return null;
390+
}
380391
else
381392
{
382393
throw;

src/WingetCreateCLI/Commands/NewCommand.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ public static IEnumerable<Example> Examples
7474
[Option('o', "out", Required = false, HelpText = "OutputDirectory_HelpText", ResourceType = typeof(Resources))]
7575
public string OutputDir { get; set; }
7676

77+
/// <summary>
78+
/// Gets or sets a value indicating whether to allow unsecure downloads.
79+
/// </summary>
80+
[Option("allow-unsecure-downloads", Required = false, HelpText = "AllowUnsecureDownloads_HelpText", ResourceType = typeof(Resources))]
81+
public bool AllowUnsecureDownloads { get; set; }
82+
7783
/// <summary>
7884
/// Gets or sets the format of the output manifest files.
7985
/// </summary>
@@ -116,7 +122,7 @@ public override async Task<bool> Execute()
116122

117123
foreach (var installerUrl in this.InstallerUrls)
118124
{
119-
string packageFile = await DownloadPackageFile(installerUrl);
125+
string packageFile = await DownloadPackageFile(installerUrl, this.AllowUnsecureDownloads);
120126
if (string.IsNullOrEmpty(packageFile))
121127
{
122128
return false;

src/WingetCreateCLI/Commands/UpdateCommand.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ public static IEnumerable<Example> Examples
121121
[Option('f', "format", Required = false, HelpText = "ManifestFormat_HelpText", ResourceType = typeof(Resources))]
122122
public override ManifestFormat Format { get => base.Format; set => base.Format = value; }
123123

124+
/// <summary>
125+
/// Gets or sets a value indicating whether to allow unsecure downloads.
126+
/// </summary>
127+
[Option("allow-unsecure-downloads", Required = false, HelpText = "AllowUnsecureDownloads_HelpText", ResourceType = typeof(Resources))]
128+
public bool AllowUnsecureDownloads { get; set; }
129+
124130
/// <summary>
125131
/// Gets or sets the GitHub token used to submit a pull request on behalf of the user.
126132
/// </summary>
@@ -406,7 +412,7 @@ public async Task<Manifests> UpdateManifestsAutonomously(Manifests manifests)
406412

407413
foreach (var installerUpdate in installerMetadataList)
408414
{
409-
string packageFile = await DownloadPackageFile(installerUpdate.InstallerUrl);
415+
string packageFile = await DownloadPackageFile(installerUpdate.InstallerUrl, this.AllowUnsecureDownloads);
410416
if (string.IsNullOrEmpty(packageFile))
411417
{
412418
return null;
@@ -1005,7 +1011,7 @@ private async Task UpdateSingleInstallerInteractively(Installer installer)
10051011
{
10061012
string url = Prompt.Input<string>(Resources.NewInstallerUrl_Message, null, null, new[] { FieldValidation.ValidateProperty(newInstaller, nameof(Installer.InstallerUrl)) });
10071013

1008-
string packageFile = await DownloadPackageFile(url);
1014+
string packageFile = await DownloadPackageFile(url, this.AllowUnsecureDownloads);
10091015
string archivePath = null;
10101016

10111017
if (string.IsNullOrEmpty(packageFile))

src/WingetCreateCLI/Properties/Resources.Designer.cs

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/WingetCreateCLI/Properties/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,4 +1471,13 @@ Warning: Using this argument may result in the token being logged. Consider an a
14711471
<data name="MicrosoftEntraIdAuthenticationInfo_Scope_KeywordDescription" xml:space="preserve">
14721472
<value>The scope value for Microsoft Entra Id authentication</value>
14731473
</data>
1474+
<data name="DownloadProtocolNotSupported_Error" xml:space="preserve">
1475+
<value>Only HTTPS URLs are supported for downloads. Use the "--allow-unsecure-downloads" option to allow HTTP URLs.</value>
1476+
</data>
1477+
<data name="DownloadHttpsOnly_Error" xml:space="preserve">
1478+
<value>Only HTTPS URLs are supported without "--allow-unsecure-downloads".</value>
1479+
</data>
1480+
<data name="AllowUnsecureDownloads_HelpText" xml:space="preserve">
1481+
<value>Allow unsecure downloads (HTTP) for this operation.</value>
1482+
</data>
14741483
</root>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
namespace Microsoft.WingetCreateCore.Common.Exceptions
5+
{
6+
using System;
7+
8+
/// <summary>
9+
/// The exception that is thrown when the download URL is not HTTPS.
10+
/// </summary>
11+
public class DownloadHttpsOnlyException : Exception
12+
{
13+
}
14+
}

src/WingetCreateCore/Common/PackageParser.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,19 @@ public static void ParsePackages(List<InstallerMetadata> installerMetadataList,
117117
/// Download file at specified URL to temp directory, unless it's already present.
118118
/// </summary>
119119
/// <param name="url">The URL of the file to be downloaded.</param>
120+
/// <param name="allowHttp">Whether to allow HTTP downloads.</param>
120121
/// <param name="maxDownloadSize">The maximum file size in bytes to download.</param>
121122
/// <returns>Path of downloaded, or previously downloaded, file.</returns>
122-
public static async Task<string> DownloadFileAsync(string url, long? maxDownloadSize = null)
123+
public static async Task<string> DownloadFileAsync(string url, bool allowHttp, long? maxDownloadSize = null)
123124
{
125+
ValidateUrl(url, allowHttp);
124126
var response = await httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
125127

126128
int redirectCount = 0;
127129
while (response.StatusCode == System.Net.HttpStatusCode.Redirect && redirectCount < 2)
128130
{
129131
var redirectUri = response.Headers.Location;
132+
ValidateUrl(redirectUri, allowHttp);
130133
response = await httpClient.GetAsync(redirectUri, HttpCompletionOption.ResponseHeadersRead);
131134
redirectCount++;
132135
}
@@ -1116,5 +1119,28 @@ private static string RemoveInvalidCharsFromString(string value)
11161119
{
11171120
return Regex.Replace(value, InvalidCharacters, string.Empty);
11181121
}
1122+
1123+
private static void ValidateUrl(string url, bool allowHttp)
1124+
{
1125+
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri downloadUrl))
1126+
{
1127+
throw new InvalidOperationException();
1128+
}
1129+
1130+
ValidateUrl(downloadUrl, allowHttp);
1131+
}
1132+
1133+
private static void ValidateUrl(Uri url, bool allowHttp)
1134+
{
1135+
if (url.Scheme != Uri.UriSchemeHttp && url.Scheme != Uri.UriSchemeHttps)
1136+
{
1137+
throw new NotSupportedException();
1138+
}
1139+
1140+
if (!allowHttp && url.Scheme != Uri.UriSchemeHttps)
1141+
{
1142+
throw new DownloadHttpsOnlyException();
1143+
}
1144+
}
11191145
}
11201146
}

src/WingetCreateTests/WingetCreateTests/TestUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public static string MockDownloadFile(string filename)
139139
{
140140
string url = $"https://fakedomain.com/{filename}";
141141
SetMockHttpResponseContent(filename);
142-
string downloadedPath = PackageParser.DownloadFileAsync(url).Result;
142+
string downloadedPath = PackageParser.DownloadFileAsync(url, false).Result;
143143
return downloadedPath;
144144
}
145145

0 commit comments

Comments
 (0)