diff --git a/src/lib-csharp/Sources/GitBase.cs b/src/lib-csharp/Sources/GitBase.cs
index 5641dc61..6f701422 100644
--- a/src/lib-csharp/Sources/GitBase.cs
+++ b/src/lib-csharp/Sources/GitBase.cs
@@ -35,12 +35,14 @@ namespace Velopack.Sources
protected virtual string? AccessToken { get; }
///
- /// The Bearer token used in the request.
+ /// The Bearer or other type of Authorization header used to authenticate against the Api.
///
- protected virtual string? Authorization => string.IsNullOrWhiteSpace(AccessToken) ? null : "Bearer " + AccessToken;
+ protected abstract (string Name, string Value) Authorization { get; }
- ///
- public GitBase(string repoUrl, string? accessToken, bool prerelease, IFileDownloader? downloader = null)
+ ///
+ /// Base constructor.
+ ///
+ protected GitBase(string repoUrl, string? accessToken, bool prerelease, IFileDownloader? downloader = null)
{
RepoUri = new Uri(repoUrl.TrimEnd('/'));
AccessToken = accessToken;
@@ -55,7 +57,12 @@ namespace Velopack.Sources
// this might be a browser url or an api url (depending on whether we have a AccessToken or not)
// https://docs.github.com/en/rest/reference/releases#get-a-release-asset
var assetUrl = GetAssetUrlFromName(githubEntry.Release, releaseEntry.FileName);
- return Downloader.DownloadFile(assetUrl, localFile, progress, Authorization, "application/octet-stream", cancelToken: cancelToken);
+ return Downloader.DownloadFile(assetUrl, localFile, progress,
+ new Dictionary {
+ [Authorization.Name] = Authorization.Value,
+ ["Accept"] = "application/octet-stream"
+ },
+ cancelToken: cancelToken);
}
throw new ArgumentException($"Expected releaseEntry to be {nameof(GitBaseAsset)} but got {releaseEntry.GetType().Name}.");
@@ -84,7 +91,12 @@ namespace Velopack.Sources
logger.Trace(ex.ToString());
continue;
}
- var releaseBytes = await Downloader.DownloadBytes(assetUrl, Authorization, "application/octet-stream").ConfigureAwait(false);
+ var releaseBytes = await Downloader.DownloadBytes(assetUrl,
+ new Dictionary {
+ [Authorization.Name] = Authorization.Value,
+ ["Accept"] = "application/octet-stream"
+ }
+ ).ConfigureAwait(false);
var txt = CoreUtil.RemoveByteOrderMarkerIfPresent(releaseBytes);
var feed = VelopackAssetFeed.FromJson(txt);
foreach (var f in feed.Assets) {
diff --git a/src/lib-csharp/Sources/GiteaSource.cs b/src/lib-csharp/Sources/GiteaSource.cs
index 720ea1f9..c519864d 100644
--- a/src/lib-csharp/Sources/GiteaSource.cs
+++ b/src/lib-csharp/Sources/GiteaSource.cs
@@ -81,11 +81,10 @@ namespace Velopack.Sources
: base(repoUrl, accessToken, prerelease, downloader)
{
}
- ///
- /// The authorization token used in the request.
- /// Overwrite it to token
- ///
- protected override string? Authorization => string.IsNullOrWhiteSpace(AccessToken) ? null : "token " + AccessToken;
+
+ ///
+ protected override (string Name, string Value) Authorization => ("Authorization", $"token {AccessToken}");
+
///
protected override async Task GetReleases(bool includePrereleases)
{
@@ -97,7 +96,12 @@ namespace Velopack.Sources
var releasesPath = $"repos{RepoUri.AbsolutePath}/releases?limit={perPage}&page={page}&draft=false";
var baseUri = GetApiBaseUrl(RepoUri);
var getReleasesUri = new Uri(baseUri, releasesPath);
- var response = await Downloader.DownloadString(getReleasesUri.ToString(), Authorization, "application/json").ConfigureAwait(false);
+ var response = await Downloader.DownloadString(getReleasesUri.ToString(),
+ new Dictionary {
+ [Authorization.Name] = Authorization.Value,
+ ["Accept"] = "application/json"
+ }
+ ).ConfigureAwait(false);
var releases = CompiledJson.DeserializeGiteaReleaseList(response);
if (releases == null) return new GiteaRelease[0];
return releases.OrderByDescending(d => d.PublishedAt).Where(x => includePrereleases || !x.Prerelease).ToArray();
diff --git a/src/lib-csharp/Sources/GithubSource.cs b/src/lib-csharp/Sources/GithubSource.cs
index 611d9063..1cb5942b 100644
--- a/src/lib-csharp/Sources/GithubSource.cs
+++ b/src/lib-csharp/Sources/GithubSource.cs
@@ -87,6 +87,9 @@ namespace Velopack.Sources
{
}
+ ///
+ protected override (string Name, string Value) Authorization => ("Authorization", $"Bearer {AccessToken}");
+
///
protected override async Task GetReleases(bool includePrereleases)
{
@@ -96,7 +99,12 @@ namespace Velopack.Sources
var releasesPath = $"repos{RepoUri.AbsolutePath}/releases?per_page={perPage}&page={page}";
var baseUri = GetApiBaseUrl(RepoUri);
var getReleasesUri = new Uri(baseUri, releasesPath);
- var response = await Downloader.DownloadString(getReleasesUri.ToString(), Authorization, "application/vnd.github.v3+json").ConfigureAwait(false);
+ var response = await Downloader.DownloadString(getReleasesUri.ToString(),
+ new Dictionary {
+ [Authorization.Name] = Authorization.Value,
+ ["Accept"] = "application/vnd.github.v3+json"
+ }
+ ).ConfigureAwait(false);
var releases = CompiledJson.DeserializeGithubReleaseList(response);
if (releases == null) return Array.Empty();
return releases.OrderByDescending(d => d.PublishedAt).Where(x => includePrereleases || !x.Prerelease).ToArray();
diff --git a/src/lib-csharp/Sources/GitlabSource.cs b/src/lib-csharp/Sources/GitlabSource.cs
index a51b469a..6c70fea5 100644
--- a/src/lib-csharp/Sources/GitlabSource.cs
+++ b/src/lib-csharp/Sources/GitlabSource.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Velopack.Util;
@@ -98,6 +99,9 @@ namespace Velopack.Sources
///
public class GitlabSource : GitBase
{
+ ///
+ protected override (string Name, string Value) Authorization => ("PRIVATE-TOKEN", AccessToken ?? string.Empty);
+
///
///
/// The URL of the GitLab repository to download releases from
@@ -156,7 +160,11 @@ namespace Velopack.Sources
var releasesPath = $"{RepoUri.AbsolutePath}/releases?per_page={perPage}&page={page}";
var baseUri = new Uri("https://gitlab.com");
var getReleasesUri = new Uri(baseUri, releasesPath);
- var response = await Downloader.DownloadString(getReleasesUri.ToString(), Authorization).ConfigureAwait(false);
+ var response = await Downloader.DownloadString(getReleasesUri.ToString(),
+ new Dictionary {
+ [Authorization.Name] = Authorization.Value,
+ ["Accept"] = "application/json"
+ }).ConfigureAwait(false);
var releases = CompiledJson.DeserializeGitlabReleaseList(response);
if (releases == null) return new GitlabRelease[0];
return releases.OrderByDescending(d => d.ReleasedAt).Where(x => includePrereleases || !x.UpcomingRelease).ToArray();
diff --git a/src/lib-csharp/Sources/HttpClientFileDownloader.cs b/src/lib-csharp/Sources/HttpClientFileDownloader.cs
index c1e1dd30..183b66cd 100644
--- a/src/lib-csharp/Sources/HttpClientFileDownloader.cs
+++ b/src/lib-csharp/Sources/HttpClientFileDownloader.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
@@ -18,9 +19,9 @@ namespace Velopack.Sources
public static ProductInfoHeaderValue UserAgent => new("Velopack", VelopackRuntimeInfo.VelopackNugetVersion.ToFullString());
///
- public virtual async Task DownloadFile(string url, string targetFile, Action progress, string? authorization, string? accept, double timeout, CancellationToken cancelToken = default)
+ public virtual async Task DownloadFile(string url, string targetFile, Action progress, IDictionary? headers, double timeout, CancellationToken cancelToken = default)
{
- using var client = CreateHttpClient(authorization, accept, timeout);
+ using var client = CreateHttpClient(headers, timeout);
try {
using (var fs = File.Open(targetFile, FileMode.Create)) {
@@ -36,9 +37,9 @@ namespace Velopack.Sources
}
///
- public virtual async Task DownloadBytes(string url, string? authorization, string? accept, double timeout)
+ public virtual async Task DownloadBytes(string url, IDictionary? headers, double timeout)
{
- using var client = CreateHttpClient(authorization, accept, timeout);
+ using var client = CreateHttpClient(headers, timeout);
try {
return await client.GetByteArrayAsync(url).ConfigureAwait(false);
@@ -50,9 +51,9 @@ namespace Velopack.Sources
}
///
- public virtual async Task DownloadString(string url, string? authorization, string? accept, double timeout)
+ public virtual async Task DownloadString(string url, IDictionary? headers, double timeout)
{
- using var client = CreateHttpClient(authorization, accept, timeout);
+ using var client = CreateHttpClient(headers, timeout);
try {
return await client.GetStringAsync(url).ConfigureAwait(false);
@@ -123,16 +124,15 @@ namespace Velopack.Sources
///
/// Creates a new for every request.
///
- protected virtual HttpClient CreateHttpClient(string? authorization, string? accept, double timeout = 30)
+ protected virtual HttpClient CreateHttpClient(IDictionary? headers, double timeout)
{
var client = new HttpClient(CreateHttpClientHandler());
client.DefaultRequestHeaders.UserAgent.Add(UserAgent);
- if (authorization != null)
- client.DefaultRequestHeaders.Add("Authorization", authorization);
-
- if (accept != null)
- client.DefaultRequestHeaders.Add("Accept", accept);
+ foreach (var header in headers ?? new Dictionary())
+ {
+ client.DefaultRequestHeaders.Add(header.Key, header.Value);
+ }
client.Timeout = TimeSpan.FromMinutes(timeout);
return client;
diff --git a/src/lib-csharp/Sources/IFileDownloader.cs b/src/lib-csharp/Sources/IFileDownloader.cs
index e03ca440..67343c46 100644
--- a/src/lib-csharp/Sources/IFileDownloader.cs
+++ b/src/lib-csharp/Sources/IFileDownloader.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -14,31 +15,26 @@ namespace Velopack.Sources
///
/// The url which will be downloaded.
///
- /// The local path where the file will be stored
- /// If a file exists at this path, it will be overwritten.
+ /// The local path where the file will be stored
+ /// If a file exists at this path, it will be overwritten.
///
- /// A delegate for reporting download progress, with expected values from 0-100.
- ///
- ///
- /// Text to be sent in the 'Authorization' header of the request.
- ///
- ///
- /// Text to be sent in the 'Accept' header of the request.
+ /// A delegate for reporting download progress, with expected values from 0-100.
///
+ /// Headers that can be passed to Http Downloader, e.g. Accept or Authorization.
///
- /// The maximum time in minutes to wait for the download to complete.
+ /// The maximum time in minutes to wait for the download to complete.
///
/// Optional token to cancel the request.
- Task DownloadFile(string url, string targetFile, Action progress, string? authorization = null, string? accept = null, double timeout = 30, CancellationToken cancelToken = default);
+ Task DownloadFile(string url, string targetFile, Action progress, IDictionary? headers = null, double timeout = 30, CancellationToken cancelToken = default);
///
/// Returns a byte array containing the contents of the file at the specified url
///
- Task DownloadBytes(string url, string? authorization = null, string? accept = null, double timeout = 30);
+ Task DownloadBytes(string url, IDictionary? headers = null, double timeout = 30);
///
/// Returns a string containing the contents of the specified url
///
- Task DownloadString(string url, string? authorization = null, string? accept = null, double timeout = 30);
+ Task DownloadString(string url, IDictionary? headers = null, double timeout = 30);
}
}
diff --git a/test/Velopack.Tests/TestHelpers/FakeDownloader.cs b/test/Velopack.Tests/TestHelpers/FakeDownloader.cs
index fc089845..c929d8f6 100644
--- a/test/Velopack.Tests/TestHelpers/FakeDownloader.cs
+++ b/test/Velopack.Tests/TestHelpers/FakeDownloader.cs
@@ -7,23 +7,21 @@ public class FakeDownloader : IFileDownloader
{
public string LastUrl { get; private set; }
public string LastLocalFile { get; private set; }
- public string LastAuthHeader { get; private set; }
- public string LastAcceptHeader { get; private set; }
+ public IDictionary? LastHeaders { get; private set; }
public byte[] MockedResponseBytes { get; set; } = [];
public bool WriteMockLocalFile { get; set; } = false;
- public Task DownloadBytes(string url, string auth, string acc, double timeout = 30)
+ public Task DownloadBytes(string url, IDictionary headers, double timeout = 30)
{
LastUrl = url;
- LastAuthHeader = auth;
- LastAcceptHeader = acc;
+ LastHeaders = headers;
return Task.FromResult(MockedResponseBytes);
}
- public async Task DownloadFile(string url, string targetFile, Action progress, string auth, string acc, double timeout, CancellationToken token)
+ public async Task DownloadFile(string url, string targetFile, Action progress, IDictionary headers, double timeout = 30, CancellationToken token = default)
{
LastLocalFile = targetFile;
- var resp = await DownloadBytes(url, auth, acc);
+ var resp = await DownloadBytes(url, headers);
progress?.Invoke(25);
progress?.Invoke(50);
progress?.Invoke(75);
@@ -32,8 +30,8 @@ public class FakeDownloader : IFileDownloader
File.WriteAllBytes(targetFile, resp);
}
- public async Task DownloadString(string url, string auth, string acc, double timeout = 30)
+ public async Task DownloadString(string url, IDictionary headers, double timeout = 30)
{
- return Encoding.UTF8.GetString(await DownloadBytes(url, auth, acc));
+ return Encoding.UTF8.GetString(await DownloadBytes(url, headers));
}
}
diff --git a/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs b/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs
index 786a3f80..8577bf6c 100644
--- a/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs
+++ b/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs
@@ -57,7 +57,7 @@ internal class FakeFixtureRepository : IFileDownloader
_releases = releases;
}
- public Task DownloadBytes(string url, string authorization = null, string accept = null, double timeout = 30)
+ public Task DownloadBytes(string url, IDictionary headers = null, double timeout = 30)
{
if (url.Contains($"/{_releasesName}?")) {
MemoryStream ms = new MemoryStream();
@@ -82,7 +82,7 @@ internal class FakeFixtureRepository : IFileDownloader
return Task.FromResult(File.ReadAllBytes(filePath));
}
- public Task DownloadFile(string url, string targetFile, Action progress, string authorization = null, string accept = null, double timeout = 30,
+ public Task DownloadFile(string url, string targetFile, Action progress, IDictionary headers = null, double timeout = 30,
CancellationToken token = default)
{
var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
@@ -99,7 +99,7 @@ internal class FakeFixtureRepository : IFileDownloader
return Task.CompletedTask;
}
- public Task DownloadString(string url, string authorization = null, string accept = null, double timeout = 30)
+ public Task DownloadString(string url, IDictionary headers = null, double timeout = 30)
{
if (url.Contains($"/{_releasesName}?")) {
MemoryStream ms = new MemoryStream();