Skip to content

Commit 48c2def

Browse files
committed
Squashed 'src/SfsClient/sfs-client/' changes from be733af9..c639a506
c639a506 Adding support for a custom proxy input (microsoft#218) 258d189b Improve logging when the content type is wrong (microsoft#221) 216210ab Adding required permissions to enable uploading of CodeQL results (microsoft#214) fb953d6e Bump github/codeql-action from 2 to 3 (microsoft#215) 52af7124 Enabling CodeQL scanning (microsoft#211) e555d764 Bump clang-format from 18.1.5 to 19.1.1 (microsoft#210) ab8f0e72 Setup: improving build tools installation (microsoft#207) git-subtree-dir: src/SfsClient/sfs-client git-subtree-split: c639a506e712dbd29ca7ca0c78d5216658e78748
1 parent d5ae2a9 commit 48c2def

26 files changed

+621
-205
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
name: Initialize CodeQL
2+
3+
description: Initializes CodeQL action to be used in build workflows
4+
5+
runs:
6+
using: "composite"
7+
8+
steps:
9+
- name: Initialize CodeQL
10+
uses: github/codeql-action/init@v3
11+
with:
12+
languages: cpp

.github/workflows/main-build-ubuntu.yml

+8
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ on:
55
branches: [ "main" ]
66

77
# Permissions and environment values to be able to update the dependency graph with vcpkg information
8+
# and to enable the writing/uploading of CodeQL scan results
89
permissions:
910
contents: write
11+
security-events: write
1012

1113
env:
1214
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -19,6 +21,9 @@ jobs:
1921
steps:
2022
- uses: actions/checkout@v4
2123

24+
- name: Initialize CodeQL
25+
uses: ./.github/workflows/initialize-codeql
26+
2227
- name: Setup
2328
run: source ./scripts/setup.sh
2429

@@ -36,3 +41,6 @@ jobs:
3641
run: |
3742
./scripts/build.sh --build-type Release
3843
./scripts/test.sh --output-on-failure
44+
45+
- name: Perform CodeQL Analysis
46+
uses: github/codeql-action/analyze@v3

.github/workflows/main-build-windows.yml

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ on:
55
branches: [ "main" ]
66

77
# Permissions and environment values to be able to update the dependency graph with vcpkg information
8+
# and to enable the writing/uploading of CodeQL scan results
89
permissions:
910
contents: write
11+
security-events: write
1012

1113
env:
1214
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -19,12 +21,15 @@ jobs:
1921
steps:
2022
- uses: actions/checkout@v4
2123

24+
- name: Initialize CodeQL
25+
uses: ./.github/workflows/initialize-codeql
26+
2227
- name: Install Winget
2328
uses: ./.github/workflows/install-winget
2429

2530
- name: Setup
2631
shell: pwsh
27-
run: .\scripts\Setup.ps1 -NoBuildTools
32+
run: .\scripts\Setup.ps1
2833

2934
- name: Build and Test (no test overrides)
3035
shell: pwsh
@@ -43,3 +48,6 @@ jobs:
4348
run: |
4449
.\scripts\Build.ps1 -BuildType Release
4550
.\scripts\Test.ps1 -OutputOnFailure
51+
52+
- name: Perform CodeQL Analysis
53+
uses: github/codeql-action/analyze@v3

.github/workflows/pr.yml

+13-1
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ jobs:
2222
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
2323
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
2424
25+
- name: Initialize CodeQL
26+
uses: ./.github/workflows/initialize-codeql
27+
2528
- name: Install Winget
2629
uses: ./.github/workflows/install-winget
2730

2831
- name: Setup
2932
shell: pwsh
30-
run: .\scripts\Setup.ps1 -NoBuildTools
33+
run: .\scripts\Setup.ps1
3134

3235
- name: Check formatting
3336
shell: pwsh
@@ -45,6 +48,9 @@ jobs:
4548
.\scripts\Build.ps1 -EnableTestOverrides
4649
.\scripts\Test.ps1 -OutputOnFailure
4750
51+
- name: Perform CodeQL Analysis
52+
uses: github/codeql-action/analyze@v3
53+
4854
build-ubuntu:
4955
runs-on: ubuntu-latest
5056

@@ -58,6 +64,9 @@ jobs:
5864
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
5965
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
6066
67+
- name: Initialize CodeQL
68+
uses: ./.github/workflows/initialize-codeql
69+
6170
- name: Setup
6271
run: source ./scripts/setup.sh
6372

@@ -73,3 +82,6 @@ jobs:
7382
run: |
7483
./scripts/build.sh --enable-test-overrides
7584
./scripts/test.sh --output-on-failure
85+
86+
- name: Perform CodeQL Analysis
87+
uses: github/codeql-action/analyze@v3

client/include/sfsclient/RequestParams.h

+5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ struct RequestParams
3535
/// @note If not provided, a new CorrelationVector will be generated
3636
std::optional<std::string> baseCV;
3737

38+
/// @brief Proxy setting which can be used to establish connections with the server (optional)
39+
/// @note The string can be a hostname or dotted numerical IP address. It can be suffixed with the port number
40+
/// like :[port], and can be prefixed with [scheme]://. If not provided, no proxy will be used.
41+
std::optional<std::string> proxy;
42+
3843
/// @brief Retry for a web request after a failed attempt. If true, client will retry up to c_maxRetries times
3944
bool retryOnError{true};
4045
};

client/src/details/UrlBuilder.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,31 @@ std::string UrlBuilder::GetUrl() const
6666
return urlPtr;
6767
}
6868

69+
std::string UrlBuilder::GetPath() const
70+
{
71+
CurlCharPtr path;
72+
char* pathPtr = path.get();
73+
THROW_IF_CURL_URL_SETUP_ERROR(curl_url_get(m_handle, CURLUPART_PATH, &pathPtr, 0 /*flags*/));
74+
return pathPtr;
75+
}
76+
77+
std::string UrlBuilder::GetQuery() const
78+
{
79+
CurlCharPtr query;
80+
char* queryPtr = query.get();
81+
const auto queryResult = curl_url_get(m_handle, CURLUPART_QUERY, &queryPtr, 0 /*flags*/);
82+
switch (queryResult)
83+
{
84+
case CURLUE_OK:
85+
return queryPtr;
86+
case CURLUE_NO_QUERY:
87+
return {};
88+
default:
89+
THROW_IF_CURL_URL_SETUP_ERROR(queryResult);
90+
}
91+
return {};
92+
}
93+
6994
UrlBuilder& UrlBuilder::SetScheme(Scheme scheme)
7095
{
7196
switch (scheme)

client/src/details/UrlBuilder.h

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class UrlBuilder
3939

4040
std::string GetUrl() const;
4141

42+
std::string GetPath() const;
43+
std::string GetQuery() const;
44+
4245
/**
4346
* @brief Set the scheme for the URL
4447
* @param scheme The scheme to set for the URL Ex: Https

client/src/details/connection/ConnectionConfig.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ using namespace SFS::details;
1111
ConnectionConfig::ConnectionConfig(const SFS::RequestParams& requestParams)
1212
: maxRetries(requestParams.retryOnError ? c_maxRetries : 0)
1313
, baseCV(requestParams.baseCV)
14+
, proxy(requestParams.proxy)
1415
{
1516
}

client/src/details/connection/ConnectionConfig.h

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ struct ConnectionConfig
2222

2323
/// @brief The correlation vector to use for requests
2424
std::optional<std::string> baseCV;
25+
26+
/// @brief Proxy setting which can be used to establish connections with the server
27+
std::optional<std::string> proxy;
2528
};
2629
} // namespace details
2730
} // namespace SFS

client/src/details/connection/CurlConnection.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,11 @@ CurlConnection::CurlConnection(const ConnectionConfig& config, const ReportingHa
284284
m_handler,
285285
"Failed to set up curl");
286286

287+
if (config.proxy)
288+
{
289+
THROW_IF_CURL_SETUP_ERROR(curl_easy_setopt(m_handle, CURLOPT_PROXY, config.proxy->c_str()));
290+
}
291+
287292
// TODO #41: Pass AAD token in the header if it is available
288293
// TODO #42: Cert pinning with service
289294
}

client/src/details/entity/ContentType.cpp

+1-20
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,9 @@
33

44
#include "ContentType.h"
55

6-
#include "../ErrorHandling.h"
7-
#include "../ReportingHandler.h"
8-
#include "Result.h"
9-
10-
using namespace SFS;
116
using namespace SFS::details;
127

13-
namespace
14-
{
15-
std::string ToString(ContentType type)
8+
std::string SFS::details::ToString(ContentType type)
169
{
1710
switch (type)
1811
{
@@ -24,15 +17,3 @@ std::string ToString(ContentType type)
2417
return "Unknown";
2518
}
2619
}
27-
} // namespace
28-
29-
void SFS::details::ValidateContentType(ContentType currentType,
30-
ContentType expectedType,
31-
const ReportingHandler& handler)
32-
{
33-
THROW_CODE_IF_LOG(Result::ServiceUnexpectedContentType,
34-
currentType != expectedType,
35-
handler,
36-
"Unexpected content type [" + ::ToString(currentType) +
37-
"] returned by the service does not match the expected [" + ::ToString(expectedType) + "]");
38-
}

client/src/details/entity/ContentType.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33

44
#pragma once
55

6+
#include <string>
7+
68
namespace SFS::details
79
{
8-
class ReportingHandler;
9-
1010
enum class ContentType
1111
{
1212
Generic,
1313
App,
1414
};
1515

16-
void ValidateContentType(ContentType currentType, ContentType expectedType, const ReportingHandler& handler);
16+
std::string ToString(ContentType type);
1717
} // namespace SFS::details

client/src/details/entity/FileEntity.cpp

+12-2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ Architecture ArchitectureFromString(const std::string& arch, const ReportingHand
6666
return Architecture::None; // Unreachable code, but the compiler doesn't know that.
6767
}
6868
}
69+
70+
void ValidateContentType(const FileEntity& entity, ContentType expectedType, const ReportingHandler& handler)
71+
{
72+
THROW_CODE_IF_LOG(Result::ServiceUnexpectedContentType,
73+
entity.GetContentType() != expectedType,
74+
handler,
75+
"The service returned file \"" + entity.fileId + "\" with content type [" +
76+
ToString(entity.GetContentType()) + "] while the expected type was [" +
77+
ToString(expectedType) + "]");
78+
}
6979
} // namespace
7080

7181
std::unique_ptr<FileEntity> FileEntity::FromJson(const nlohmann::json& file, const ReportingHandler& handler)
@@ -204,7 +214,7 @@ ContentType GenericFileEntity::GetContentType() const
204214

205215
std::unique_ptr<File> GenericFileEntity::ToFile(FileEntity&& entity, const ReportingHandler& handler)
206216
{
207-
ValidateContentType(entity.GetContentType(), ContentType::Generic, handler);
217+
ValidateContentType(entity, ContentType::Generic, handler);
208218

209219
std::unordered_map<HashType, std::string> hashes;
210220
for (auto& [hashType, hashValue] : entity.hashes)
@@ -237,7 +247,7 @@ ContentType AppFileEntity::GetContentType() const
237247

238248
std::unique_ptr<AppFile> AppFileEntity::ToAppFile(FileEntity&& entity, const ReportingHandler& handler)
239249
{
240-
ValidateContentType(entity.GetContentType(), ContentType::App, handler);
250+
ValidateContentType(entity, ContentType::App, handler);
241251

242252
auto appEntity = dynamic_cast<AppFileEntity&&>(entity);
243253

client/src/details/entity/VersionEntity.cpp

+14-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ using namespace SFS;
1616
using namespace SFS::details;
1717
using json = nlohmann::json;
1818

19+
namespace
20+
{
21+
void ValidateContentType(const VersionEntity& entity, ContentType expectedType, const ReportingHandler& handler)
22+
{
23+
THROW_CODE_IF_LOG(Result::ServiceUnexpectedContentType,
24+
entity.GetContentType() != expectedType,
25+
handler,
26+
"The service returned entity \"" + entity.contentId.name + "\" with content type [" +
27+
ToString(entity.GetContentType()) + "] while the expected type was [" +
28+
ToString(expectedType) + "]");
29+
}
30+
} // namespace
31+
1932
std::unique_ptr<VersionEntity> VersionEntity::FromJson(const nlohmann::json& data, const ReportingHandler& handler)
2033
{
2134
// Expected format for a generic version entity:
@@ -135,6 +148,6 @@ ContentType AppVersionEntity::GetContentType() const
135148
AppVersionEntity* AppVersionEntity::GetAppVersionEntityPtr(std::unique_ptr<VersionEntity>& versionEntity,
136149
const ReportingHandler& handler)
137150
{
138-
ValidateContentType(versionEntity->GetContentType(), ContentType::App, handler);
151+
ValidateContentType(*versionEntity, ContentType::App, handler);
139152
return dynamic_cast<AppVersionEntity*>(versionEntity.get());
140153
}

client/tests/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ target_sources(
1717
functional/details/SFSClientImplTests.cpp
1818
functional/SFSClientTests.cpp
1919
mock/MockWebServer.cpp
20+
mock/ProxyServer.cpp
21+
mock/ServerCommon.cpp
2022
unit/AppContentTests.cpp
2123
unit/AppFileTests.cpp
2224
unit/ApplicabilityDetailsTests.cpp

client/tests/functional/SFSClientTests.cpp

+23-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
#include "../mock/MockWebServer.h"
5+
#include "../mock/ProxyServer.h"
56
#include "../util/TestHelper.h"
67
#include "TestOverride.h"
78
#include "sfsclient/SFSClient.h"
@@ -112,6 +113,19 @@ TEST("Testing SFSClient::GetLatestDownloadInfo()")
112113
CheckMockContent(contents[0], c_version);
113114
}
114115

116+
SECTION("No attributes + proxy")
117+
{
118+
test::ProxyServer proxy;
119+
120+
params.productRequests = {{c_productName, {}}};
121+
params.proxy = proxy.GetBaseUrl();
122+
REQUIRE(sfsClient->GetLatestDownloadInfo(params, contents) == Result::Success);
123+
REQUIRE(contents.size() == 1);
124+
CheckMockContent(contents[0], c_version);
125+
126+
REQUIRE(proxy.Stop() == Result::Success);
127+
}
128+
115129
SECTION("With attributes")
116130
{
117131
const TargetingAttributes attributes{{"attr1", "value"}};
@@ -143,6 +157,8 @@ TEST("Testing SFSClient::GetLatestDownloadInfo()")
143157
CheckMockContent(contents[0], c_nextVersion);
144158
}
145159
}
160+
161+
REQUIRE(server.Stop() == Result::Success);
146162
}
147163

148164
TEST("Testing SFSClient::GetLatestAppDownloadInfo()")
@@ -240,10 +256,13 @@ TEST("Testing SFSClient::GetLatestAppDownloadInfo()")
240256
params.productRequests = {{c_productName, {}}};
241257
auto result = sfsClient->GetLatestAppDownloadInfo(params, contents);
242258
REQUIRE(result.GetCode() == Result::ServiceUnexpectedContentType);
243-
REQUIRE(result.GetMsg() ==
244-
"Unexpected content type [Generic] returned by the service does not match the expected [App]");
259+
REQUIRE(
260+
result.GetMsg() ==
261+
R"(The service returned entity "testProduct" with content type [Generic] while the expected type was [App])");
245262
REQUIRE(contents.empty());
246263
}
264+
265+
REQUIRE(server.Stop() == Result::Success);
247266
}
248267

249268
TEST("Testing SFSClient retry behavior")
@@ -437,4 +456,6 @@ TEST("Testing SFSClient retry behavior")
437456
}
438457
}
439458
}
459+
460+
REQUIRE(server.Stop() == Result::Success);
440461
}

0 commit comments

Comments
 (0)