Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add basic proxy support #1776

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ SRL
srs
standalone
startswith
stl
streambuf
strtoull
subdir
Expand Down
15 changes: 14 additions & 1 deletion doc/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ The `downloader` setting controls which code is used when downloading packages.
`wininet` uses the [WinINet](https://docs.microsoft.com/windows/win32/wininet/about-wininet) APIs, while `do` uses the
[Delivery Optimization](https://support.microsoft.com/windows/delivery-optimization-in-windows-10-0656e53c-15f2-90de-a87a-a2172c94cf6d) service.

The `doProgressTimeoutInSeconds` setting updates the number of seconds to wait without progress before fallback. The default number of seconds is 60, minimum is 1 and the maximum is 600.
The `doProgressTimeoutInSeconds` setting updates the number of seconds to wait without progress before fallback. The default number of seconds is 60, minimum is 1 and the maximum is 600.

```json
"network": {
Expand All @@ -125,6 +125,19 @@ The `doProgressTimeoutInSeconds` setting updates the number of seconds to wait w
}
```

### Proxy

The `proxy` setting specifies the proxy server used for download packages. If not specified, winget will retrieve current proxy settings from the registry.

The `proxyOverride` setting specifies a list of host names or IP addresses, or both, where no proxy should be used. Local hosts are always bypassed as if the list is extended by `["localhost", "loopback", "127.0.0.1", "[::1]"]`.

```json
"network": {
"proxy": "http://127.0.0.1:7890",
"proxyOverride": ["192.168.*", "github.com"]
}
```

## Experimental Features

To allow work to be done and distributed to early adopters for feedback, settings can be used to enable "experimental" features.
Expand Down
11 changes: 11 additions & 0 deletions schemas/JSON/settings/settings.schema.0.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@
"default": 60,
"minimum": 1,
"maximum": 600
},
"proxy": {
"description": "Proxy server used for download packages",
"type": "string"
},
"proxyOverride": {
"description": "List of hosts where no proxy should be used",
"type": "array",
"items": {
"type": "string"
}
}
}
},
Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ namespace AppInstaller::CLI
return Argument{ "file", 'f', Args::Type::HashFile, Resource::String::FileArgumentDescription, ArgumentType::Positional, true };
case Args::Type::Msix:
return Argument{ "msix", 'm', Args::Type::Msix, Resource::String::MsixArgumentDescription, ArgumentType::Flag };
case Args::Type::NetworkProxy:
return Argument{ "proxy", NoAlias, Args::Type::NetworkProxy, Resource::String::NetworkProxyArgumentDescription, ArgumentType::Standard, Visibility::Help };
case Args::Type::NetworkProxyOverride:
return Argument{ "no-proxy", NoAlias, Args::Type::NetworkProxyOverride, Resource::String::NetworkProxyOverrideArgumentDescription, ArgumentType::Standard, Visibility::Help };
case Args::Type::ListVersions:
return Argument{ "versions", NoAlias, Args::Type::ListVersions, Resource::String::VersionsArgumentDescription, ArgumentType::Flag };
case Args::Type::Help:
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Commands/InstallCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ namespace AppInstaller::CLI
Argument::ForType(Args::Type::HashOverride),
Argument::ForType(Args::Type::DependencySource),
Argument::ForType(Args::Type::AcceptPackageAgreements),
Argument::ForType(Args::Type::NetworkProxy),
Argument::ForType(Args::Type::NetworkProxyOverride),
Argument::ForType(Args::Type::CustomHeader),
Argument::ForType(Args::Type::AcceptSourceAgreements),
};
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Commands/UpgradeCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ namespace AppInstaller::CLI
Argument::ForType(Args::Type::InstallLocation),
Argument::ForType(Args::Type::HashOverride),
Argument::ForType(Args::Type::AcceptPackageAgreements),
Argument::ForType(Args::Type::NetworkProxy),
Argument::ForType(Args::Type::NetworkProxyOverride),
Argument::ForType(Args::Type::AcceptSourceAgreements),
Argument::ForType(Execution::Args::Type::CustomHeader),
Argument{ "all", Argument::NoAlias, Args::Type::All, Resource::String::UpdateAllArgumentDescription, ArgumentType::Flag },
Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCLICore/ExecutionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ namespace AppInstaller::CLI::Execution
AdminSettingEnable,
AdminSettingDisable,

// Network Behavior
NetworkProxy,
NetworkProxyOverride,

// Other
All, // Used in Update command to update all installed packages to latest
ListVersions, // Used in Show command to list all available versions of an app
Expand Down
8 changes: 8 additions & 0 deletions src/AppInstallerCLICore/ExecutionContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "CompletionData.h"
#include "PackageCollection.h"
#include "Workflows/WorkflowBase.h"
#include "AppInstallerDownloader.h"

#include <filesystem>
#include <map>
Expand Down Expand Up @@ -50,6 +51,7 @@ namespace AppInstaller::CLI::Execution
Dependencies,
DependencySource,
AllowedArchitectures,
NetworkProxyInfo,
Max
};

Expand Down Expand Up @@ -207,5 +209,11 @@ namespace AppInstaller::CLI::Execution
{
using value_t = std::vector<Utility::Architecture>;
};

template <>
struct DataMapping<Data::NetworkProxyInfo>
{
using value_t = Utility::ProxyInfo;
};
}
}
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(MultipleInstalledPackagesFound);
WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFound);
WINGET_DEFINE_RESOURCE_STRINGID(NameArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(NetworkProxyArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(NetworkProxyOverrideArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers);
WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage);
WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledPackageFound);
Expand Down
13 changes: 12 additions & 1 deletion src/AppInstallerCLICore/Workflows/DownloadFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@ namespace AppInstaller::CLI::Workflow
}
}

void GetProxyInfo(Execution::Context& context)
{
auto proxy = context.Args.GetArg(Execution::Args::Type::NetworkProxy);
auto proxyOverride = context.Args.GetArg(Execution::Args::Type::NetworkProxyOverride);
if (!proxy.empty() || !proxyOverride.empty())
context.Add<Execution::Data::NetworkProxyInfo>({std::string{proxy}, std::string{proxyOverride}});
else
context.Add<Execution::Data::NetworkProxyInfo>(Utility::GetProxyInfo());
}

void DownloadInstaller(Execution::Context& context)
{
// Check if file was already downloaded.
Expand Down Expand Up @@ -308,7 +318,8 @@ namespace AppInstaller::CLI::Workflow
Utility::DownloadType::Installer,
std::placeholders::_1,
true,
downloadInfo));
downloadInfo,
context.Get<Execution::Data::NetworkProxyInfo>()));

success = true;
}
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/Workflows/DownloadFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

namespace AppInstaller::CLI::Workflow
{
// Gets the proxy settings
// Required Args: None
// Inputs: None
// Outputs: None
void GetProxyInfo(Execution::Context& context);

// Composite flow that chooses what to do based on the installer type.
// Required Args: None
// Inputs: Manifest, Installer
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Workflows/InstallFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ namespace AppInstaller::CLI::Workflow
void InstallSinglePackage(Execution::Context& context)
{
context <<
GetProxyInfo <<
Workflow::DownloadSinglePackage <<
Workflow::InstallPackageInstaller;
}
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/Workflows/UpdateFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "DependenciesFlow.h"
#include "InstallFlow.h"
#include "UpdateFlow.h"
#include "DownloadFlow.h"
#include "ManifestComparator.h"

using namespace AppInstaller::Repository;
Expand Down Expand Up @@ -140,6 +141,8 @@ namespace AppInstaller::CLI::Workflow

void UpdateAllApplicable(Execution::Context& context)
{
context << GetProxyInfo;

const auto& matches = context.Get<Execution::Data::SearchResult>().Matches;
std::vector<std::unique_ptr<Execution::Context>> packagesToInstall;
bool updateAllFoundUpdate = false;
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1265,4 +1265,10 @@ Please specify one of them using the `--source` option to proceed.</value>
<value>package has a version number that cannot be determined. Use "--include-unknown" to see all results.</value>
<comment>{Locked="--include-unknown"} This string is preceded by a (integer) number of packages that do not have notated versions.</comment>
</data>
<data name="NetworkProxyArgumentDescription" xml:space="preserve">
<value>Use the specified proxy server</value>
</data>
<data name="NetworkProxyOverrideArgumentDescription" xml:space="preserve">
<value>Semicolon-separated list of hosts where proxy should not be used</value>
</data>
</root>
53 changes: 45 additions & 8 deletions src/AppInstallerCommonCore/Downloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "Public/AppInstallerTelemetry.h"
#include "Public/winget/UserSettings.h"
#include "DODownloader.h"
#include <wil/win32_helpers.h>
#include <wil/stl.h>

using namespace AppInstaller::Runtime;
using namespace AppInstaller::Settings;
Expand All @@ -21,19 +23,22 @@ namespace AppInstaller::Utility
const std::string& url,
std::ostream& dest,
IProgressCallback& progress,
bool computeHash)
bool computeHash,
ProxyInfo info)
{
// For AICLI_LOG usages with string literals.
#pragma warning(push)
#pragma warning(disable:26449)

AICLI_LOG(Core, Info, << "WinINet downloading from url: " << url);
AICLI_LOG(Core, Info, << "Proxy: " << info.Proxy);
AICLI_LOG(Core, Info, << "Proxy Bypass: " << info.ProxyOverride);

wil::unique_hinternet session(InternetOpenA(
"winget-cli",
INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
info.Proxy.empty() ? INTERNET_OPEN_TYPE_PRECONFIG : INTERNET_OPEN_TYPE_PROXY,
info.Proxy.empty() ? NULL : info.Proxy.data(),
info.ProxyOverride.empty() ? NULL : info.ProxyOverride.data(),
0));
THROW_LAST_ERROR_IF_NULL_MSG(session, "InternetOpen() failed.");

Expand Down Expand Up @@ -136,6 +141,37 @@ namespace AppInstaller::Utility
return result;
}

ProxyInfo GetProxyInfo()
{
ProxyInfo info;
if ((info.Proxy = Settings::User().Get<Settings::Setting::NetworkProxy>()).empty())
{
std::wstring allProxy;
wil::GetEnvironmentVariableW(L"ALL_PROXY", allProxy);
if ((info.Proxy = Utility::ConvertToUTF8(allProxy)).empty())
{
std::wstring httpProxy, httpsProxy, ftpProxy;
wil::GetEnvironmentVariableW(L"HTTP_PROXY", httpProxy);
wil::GetEnvironmentVariableW(L"HTTPS_PROXY", httpsProxy);
wil::GetEnvironmentVariableW(L"FTP_PROXY", ftpProxy);
if (!httpProxy.empty())
info.Proxy = "HTTP=" + Utility::ConvertToUTF8(httpProxy);
if (!httpsProxy.empty())
info.Proxy += (info.Proxy.empty() ? "HTTPS=" : " HTTPS=") + Utility::ConvertToUTF8(httpsProxy);
if (!ftpProxy.empty())
info.Proxy += (info.Proxy.empty() ? "FTP=" : " FTP=") + Utility::ConvertToUTF8(ftpProxy);
}
}
if ((info.ProxyOverride = Settings::User().Get<Settings::Setting::NetworkProxyOverride>()).empty())
{
std::wstring noProxy;
wil::GetEnvironmentVariableW(L"NO_PROXY", noProxy);
info.ProxyOverride = Utility::ConvertToUTF8(noProxy);
std::replace_if(info.ProxyOverride.begin(), info.ProxyOverride.end(), [](char ch) { return ch == ','; }, ';');
}
return info;
}

std::optional<std::vector<BYTE>> DownloadToStream(
const std::string& url,
std::ostream& dest,
Expand All @@ -145,7 +181,7 @@ namespace AppInstaller::Utility
std::optional<DownloadInfo>)
{
THROW_HR_IF(E_INVALIDARG, url.empty());
return WinINetDownloadToStream(url, dest, progress, computeHash);
return WinINetDownloadToStream(url, dest, progress, computeHash, GetProxyInfo());
}

std::optional<std::vector<BYTE>> Download(
Expand All @@ -154,7 +190,8 @@ namespace AppInstaller::Utility
DownloadType type,
IProgressCallback& progress,
bool computeHash,
std::optional<DownloadInfo> info)
std::optional<DownloadInfo> downloadInfo,
std::optional<ProxyInfo> proxyInfo)
{
THROW_HR_IF(E_INVALIDARG, url.empty());
THROW_HR_IF(E_INVALIDARG, dest.empty());
Expand All @@ -177,7 +214,7 @@ namespace AppInstaller::Utility
{
try
{
auto result = DODownload(url, dest, progress, computeHash, info);
auto result = DODownload(url, dest, progress, computeHash, downloadInfo);
// Since we cannot pre-apply to the file with DO, post-apply the MotW to the file.
// Only do so if the file exists, because cancellation will not throw here.
if (std::filesystem::exists(dest))
Expand Down Expand Up @@ -219,7 +256,7 @@ namespace AppInstaller::Utility
// Use std::ofstream::app to append to previous empty file so that it will not
// create a new file and clear motw.
std::ofstream outfile(dest, std::ofstream::binary | std::ofstream::app);
return WinINetDownloadToStream(url, outfile, progress, computeHash);
return WinINetDownloadToStream(url, outfile, progress, computeHash, proxyInfo ? *proxyInfo : GetProxyInfo());
}

using namespace std::string_view_literals;
Expand Down
19 changes: 15 additions & 4 deletions src/AppInstallerCommonCore/Public/AppInstallerDownloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,31 +31,42 @@ namespace AppInstaller::Utility
std::string ContentId;
};

struct ProxyInfo
{
std::string Proxy;
std::string ProxyOverride;
};

// Retrieve proxy info from UserSettings and environment variables.
ProxyInfo GetProxyInfo();

// Downloads a file from the given URL and places it in the given location.
// url: The url to be downloaded from. http->https redirection is allowed.
// dest: The stream to be downloaded to.
// computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading.
// downloadIdentifier: Optional. Currently only used by DO to identify the download.
// downloadInfo: Optional. Currently only used by DO to identify the download.
std::optional<std::vector<BYTE>> DownloadToStream(
const std::string& url,
std::ostream& dest,
DownloadType type,
IProgressCallback& progress,
bool computeHash = false,
std::optional<DownloadInfo> info = {});
std::optional<DownloadInfo> downloadInfo = {});

// Downloads a file from the given URL and places it in the given location.
// url: The url to be downloaded from. http->https redirection is allowed.
// dest: The path to local file to be downloaded to.
// computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading.
// downloadIdentifier: Optional. Currently only used by DO to identify the download.
// downloadInfo: Optional. Currently only used by DO to identify the download.
// proxyInfo: Optional. Currently only used by WinINet for proxy settings.
std::optional<std::vector<BYTE>> Download(
const std::string& url,
const std::filesystem::path& dest,
DownloadType type,
IProgressCallback& progress,
bool computeHash = false,
std::optional<DownloadInfo> info = {});
std::optional<DownloadInfo> downloadInfo = {},
std::optional<ProxyInfo> proxyInfo = {});

// Determines if the given url is a remote location.
bool IsUrlRemote(std::string_view url);
Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCommonCore/Public/winget/UserSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ namespace AppInstaller::Settings
InstallScopeRequirement,
NetworkDownloader,
NetworkDOProgressTimeoutInSeconds,
NetworkProxy,
NetworkProxyOverride,
InstallArchitecturePreference,
InstallArchitectureRequirement,
InstallLocalePreference,
Expand Down Expand Up @@ -127,6 +129,8 @@ namespace AppInstaller::Settings
SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopeRequirement, std::string, ScopePreference, ScopePreference::None, ".installBehavior.requirements.scope"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDownloader, std::string, InstallerDownloader, InstallerDownloader::Default, ".network.downloader"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDOProgressTimeoutInSeconds, uint32_t, std::chrono::seconds, 60s, ".network.doProgressTimeoutInSeconds"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::NetworkProxy, std::string, std::string, {}, ".network.proxy"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::NetworkProxyOverride, std::vector<std::string>, std::string, {}, ".network.proxyOverride"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocalePreference, std::vector<std::string>, std::vector<std::string>, {}, ".installBehavior.preferences.locale"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocaleRequirement, std::vector<std::string>, std::vector<std::string>, {}, ".installBehavior.requirements.locale"sv);
SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv);
Expand Down