Introduce downloads.h/cpp

This commit is contained in:
Alexander Karatarakis 2018-06-19 16:59:27 -07:00
parent 84d65840ab
commit 3e76baa163
5 changed files with 169 additions and 135 deletions

View File

@ -0,0 +1,16 @@
#pragma once
#include <vcpkg/base/files.h>
namespace vcpkg::Downloads
{
void verify_downloaded_file_hash(const Files::Filesystem& fs,
const std::string& url,
const fs::path& path,
const std::string& sha512);
void download_file(vcpkg::Files::Filesystem& fs,
const std::string& url,
const fs::path& download_path,
const std::string& sha512);
}

View File

@ -0,0 +1,142 @@
#include "pch.h"
#include <vcpkg/base/downloads.h>
#include <vcpkg/base/util.h>
#include <vcpkg/commands.h>
namespace vcpkg::Downloads
{
#if defined(_WIN32)
static void winhttp_download_file(Files::Filesystem& fs,
CStringView target_file_path,
CStringView hostname,
CStringView url_path)
{
// Make sure the directories are present, otherwise fopen_s fails
const auto dir = fs::path(target_file_path.c_str()).parent_path();
std::error_code ec;
fs.create_directories(dir, ec);
Checks::check_exit(VCPKG_LINE_INFO, !ec, "Could not create directories %s", dir.u8string());
FILE* f = nullptr;
const errno_t err = fopen_s(&f, target_file_path.c_str(), "wb");
Checks::check_exit(VCPKG_LINE_INFO,
!err,
"Could not download https://%s%s. Failed to open file %s. Error code was %s",
hostname,
url_path,
target_file_path,
std::to_string(err));
auto hSession = WinHttpOpen(
L"vcpkg/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
Checks::check_exit(VCPKG_LINE_INFO, hSession, "WinHttpOpen() failed: %d", GetLastError());
// Use Windows 10 defaults on Windows 7
DWORD secure_protocols(WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2);
WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols));
// Specify an HTTP server.
auto hConnect = WinHttpConnect(hSession, Strings::to_utf16(hostname).c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0);
Checks::check_exit(VCPKG_LINE_INFO, hConnect, "WinHttpConnect() failed: %d", GetLastError());
// Create an HTTP request handle.
auto hRequest = WinHttpOpenRequest(hConnect,
L"GET",
Strings::to_utf16(url_path).c_str(),
nullptr,
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE);
Checks::check_exit(VCPKG_LINE_INFO, hRequest, "WinHttpOpenRequest() failed: %d", GetLastError());
// Send a request.
auto bResults =
WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpSendRequest() failed: %d", GetLastError());
// End the request.
bResults = WinHttpReceiveResponse(hRequest, NULL);
Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpReceiveResponse() failed: %d", GetLastError());
std::vector<char> buf;
size_t total_downloaded_size = 0;
DWORD dwSize = 0;
do
{
DWORD downloaded_size = 0;
bResults = WinHttpQueryDataAvailable(hRequest, &dwSize);
Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpQueryDataAvailable() failed: %d", GetLastError());
if (buf.size() < dwSize) buf.resize(dwSize * 2);
bResults = WinHttpReadData(hRequest, (LPVOID)buf.data(), dwSize, &downloaded_size);
Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpReadData() failed: %d", GetLastError());
fwrite(buf.data(), 1, downloaded_size, f);
total_downloaded_size += downloaded_size;
} while (dwSize > 0);
WinHttpCloseHandle(hSession);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
fflush(f);
fclose(f);
}
#endif
void verify_downloaded_file_hash(const Files::Filesystem& fs,
const std::string& url,
const fs::path& path,
const std::string& sha512)
{
const std::string actual_hash = Commands::Hash::get_file_hash(fs, path, "SHA512");
Checks::check_exit(VCPKG_LINE_INFO,
sha512 == actual_hash,
"File does not have the expected hash:\n"
" url : [ %s ]\n"
" File path : [ %s ]\n"
" Expected hash : [ %s ]\n"
" Actual hash : [ %s ]\n",
url,
path.u8string(),
sha512,
actual_hash);
}
void download_file(vcpkg::Files::Filesystem& fs,
const std::string& url,
const fs::path& download_path,
const std::string& sha512)
{
const std::string download_path_part = download_path.u8string() + ".part";
std::error_code ec;
fs.remove(download_path, ec);
fs.remove(download_path_part, ec);
#if defined(_WIN32)
auto url_no_proto = url.substr(8); // drop https://
auto path_begin = Util::find(url_no_proto, '/');
std::string hostname(url_no_proto.begin(), path_begin);
std::string path(path_begin, url_no_proto.end());
winhttp_download_file(fs, download_path_part.c_str(), hostname, path);
#else
const auto code = System::cmd_execute(
Strings::format(R"(curl -L '%s' --create-dirs --output '%s')", url, download_path_part));
Checks::check_exit(VCPKG_LINE_INFO, code == 0, "Could not download %s", url);
#endif
verify_downloaded_file_hash(fs, url, download_path_part, sha512);
fs.rename(download_path_part, download_path, ec);
Checks::check_exit(VCPKG_LINE_INFO,
!ec,
"Failed to do post-download rename-in-place.\n"
"fs.rename(%s, %s, %s)",
download_path_part,
download_path.u8string(),
ec.message());
}
}

View File

@ -1,6 +1,7 @@
#include "pch.h"
#include <vcpkg/base/checks.h>
#include <vcpkg/base/downloads.h>
#include <vcpkg/base/sortedvector.h>
#include <vcpkg/base/strings.h>
#include <vcpkg/base/system.h>
@ -335,139 +336,6 @@ namespace vcpkg::Commands::Fetch
ec.message());
}
static void verify_hash(const Files::Filesystem& fs,
const std::string& url,
const fs::path& path,
const std::string& sha512)
{
const std::string actual_hash = Hash::get_file_hash(fs, path, "SHA512");
Checks::check_exit(VCPKG_LINE_INFO,
sha512 == actual_hash,
"File does not have the expected hash:\n"
" url : [ %s ]\n"
" File path : [ %s ]\n"
" Expected hash : [ %s ]\n"
" Actual hash : [ %s ]\n",
url,
path.u8string(),
sha512,
actual_hash);
}
#if defined(_WIN32)
static void winhttp_download_file(Files::Filesystem& fs,
CStringView target_file_path,
CStringView hostname,
CStringView url_path)
{
// Make sure the directories are present, otherwise fopen_s fails
const auto dir = fs::path(target_file_path.c_str()).parent_path();
std::error_code ec;
fs.create_directories(dir, ec);
Checks::check_exit(VCPKG_LINE_INFO, !ec, "Could not create directories %s", dir.u8string());
FILE* f = nullptr;
const errno_t err = fopen_s(&f, target_file_path.c_str(), "wb");
Checks::check_exit(VCPKG_LINE_INFO,
!err,
"Could not download https://%s%s. Failed to open file %s. Error code was %s",
hostname,
url_path,
target_file_path,
std::to_string(err));
auto hSession = WinHttpOpen(
L"vcpkg/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
Checks::check_exit(VCPKG_LINE_INFO, hSession, "WinHttpOpen() failed: %d", GetLastError());
// Use Windows 10 defaults on Windows 7
DWORD secure_protocols(WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2);
WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols));
// Specify an HTTP server.
auto hConnect = WinHttpConnect(hSession, Strings::to_utf16(hostname).c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0);
Checks::check_exit(VCPKG_LINE_INFO, hConnect, "WinHttpConnect() failed: %d", GetLastError());
// Create an HTTP request handle.
auto hRequest = WinHttpOpenRequest(hConnect,
L"GET",
Strings::to_utf16(url_path).c_str(),
nullptr,
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE);
Checks::check_exit(VCPKG_LINE_INFO, hRequest, "WinHttpOpenRequest() failed: %d", GetLastError());
// Send a request.
auto bResults =
WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpSendRequest() failed: %d", GetLastError());
// End the request.
bResults = WinHttpReceiveResponse(hRequest, NULL);
Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpReceiveResponse() failed: %d", GetLastError());
std::vector<char> buf;
size_t total_downloaded_size = 0;
DWORD dwSize = 0;
do
{
DWORD downloaded_size = 0;
bResults = WinHttpQueryDataAvailable(hRequest, &dwSize);
Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpQueryDataAvailable() failed: %d", GetLastError());
if (buf.size() < dwSize) buf.resize(dwSize * 2);
bResults = WinHttpReadData(hRequest, (LPVOID)buf.data(), dwSize, &downloaded_size);
Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpReadData() failed: %d", GetLastError());
fwrite(buf.data(), 1, downloaded_size, f);
total_downloaded_size += downloaded_size;
} while (dwSize > 0);
WinHttpCloseHandle(hSession);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
fflush(f);
fclose(f);
}
#endif
static void download_file(Files::Filesystem& fs,
const std::string& url,
const fs::path& download_path,
const std::string& sha512)
{
const std::string download_path_part = download_path.u8string() + ".part";
std::error_code ec;
fs.remove(download_path, ec);
fs.remove(download_path_part, ec);
#if defined(_WIN32)
auto url_no_proto = url.substr(8); // drop https://
auto path_begin = Util::find(url_no_proto, '/');
std::string hostname(url_no_proto.begin(), path_begin);
std::string path(path_begin, url_no_proto.end());
winhttp_download_file(fs, download_path_part.c_str(), hostname, path);
#else
const auto code = System::cmd_execute(
Strings::format(R"(curl -L '%s' --create-dirs --output '%s')", url, download_path_part));
Checks::check_exit(VCPKG_LINE_INFO, code == 0, "Could not download %s", url);
#endif
verify_hash(fs, url, download_path_part, sha512);
fs.rename(download_path_part, download_path, ec);
Checks::check_exit(VCPKG_LINE_INFO,
!ec,
"Failed to do post-download rename-in-place.\n"
"fs.rename(%s, %s, %s)",
download_path_part,
download_path.u8string(),
ec.message());
}
static fs::path fetch_tool(const VcpkgPaths& paths, const std::string& tool_name, const ToolData& tool_data)
{
const std::array<int, 3>& version = tool_data.version;
@ -488,12 +356,12 @@ namespace vcpkg::Commands::Fetch
if (!fs.exists(tool_data.download_path))
{
System::println("Downloading %s...", tool_name);
download_file(fs, tool_data.url, tool_data.download_path, tool_data.sha512);
Downloads::download_file(fs, tool_data.url, tool_data.download_path, tool_data.sha512);
System::println("Downloading %s... done.", tool_name);
}
else
{
verify_hash(fs, tool_data.url, tool_data.download_path, tool_data.sha512);
Downloads::verify_downloaded_file_hash(fs, tool_data.url, tool_data.download_path, tool_data.sha512);
}
if (tool_data.is_archive)

View File

@ -141,6 +141,7 @@
<ClInclude Include="..\include\vcpkg\base\chrono.h" />
<ClInclude Include="..\include\vcpkg\base\cofffilereader.h" />
<ClInclude Include="..\include\vcpkg\base\cstringview.h" />
<ClInclude Include="..\include\vcpkg\base\downloads.h" />
<ClInclude Include="..\include\vcpkg\base\enums.h" />
<ClInclude Include="..\include\vcpkg\base\expected.h" />
<ClInclude Include="..\include\vcpkg\base\files.h" />
@ -195,6 +196,7 @@
<ClCompile Include="..\src\vcpkg\base\checks.cpp" />
<ClCompile Include="..\src\vcpkg\base\chrono.cpp" />
<ClCompile Include="..\src\vcpkg\base\cofffilereader.cpp" />
<ClCompile Include="..\src\vcpkg\base\downloads.cpp" />
<ClCompile Include="..\src\vcpkg\base\enums.cpp" />
<ClCompile Include="..\src\vcpkg\base\files.cpp" />
<ClCompile Include="..\src\vcpkg\base\lineinfo.cpp" />

View File

@ -201,6 +201,9 @@
<ClCompile Include="..\src\vcpkg\commands.fetch.cpp">
<Filter>Source Files\vcpkg</Filter>
</ClCompile>
<ClCompile Include="..\src\vcpkg\base\downloads.cpp">
<Filter>Source Files\vcpkg\base</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\include\pch.h">
@ -347,5 +350,8 @@
<ClInclude Include="..\include\vcpkg\base\stringliteral.h">
<Filter>Header Files\vcpkg\base</Filter>
</ClInclude>
<ClInclude Include="..\include\vcpkg\base\downloads.h">
<Filter>Header Files\vcpkg\base</Filter>
</ClInclude>
</ItemGroup>
</Project>