Skip to content

Commit e419ea8

Browse files
authored
Merge pull request #1542 from ramshenai/fix/support-custom-hdrs-for-fetch
Wireup http custom headers in fetch options
2 parents 57b6079 + 7c48ef0 commit e419ea8

File tree

8 files changed

+184
-12
lines changed

8 files changed

+184
-12
lines changed

LibGit2Sharp.Tests/CloneFixture.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,5 +557,54 @@ public void CanCancelRecursiveClone()
557557

558558
}
559559
}
560+
561+
[Fact]
562+
public void CannotCloneWithForbiddenCustomHeaders()
563+
{
564+
var scd = BuildSelfCleaningDirectory();
565+
566+
const string url = "https://github.com/libgit2/TestGitRepository";
567+
568+
const string knownHeader = "User-Agent: mygit-201";
569+
var cloneOptions = new CloneOptions()
570+
{
571+
FetchOptions = new FetchOptions { CustomHeaders = new String[] { knownHeader } }
572+
};
573+
574+
Assert.Throws<LibGit2SharpException>(() => Repository.Clone(url, scd.DirectoryPath, cloneOptions));
575+
}
576+
577+
[Fact]
578+
public void CannotCloneWithMalformedCustomHeaders()
579+
{
580+
var scd = BuildSelfCleaningDirectory();
581+
582+
const string url = "https://github.com/libgit2/TestGitRepository";
583+
584+
const string knownHeader = "hello world";
585+
var cloneOptions = new CloneOptions()
586+
{
587+
FetchOptions = new FetchOptions { CustomHeaders = new String[] { knownHeader } }
588+
};
589+
590+
Assert.Throws<LibGit2SharpException>(() => Repository.Clone(url, scd.DirectoryPath, cloneOptions));
591+
}
592+
593+
[Fact]
594+
public void CanCloneWithCustomHeaders()
595+
{
596+
var scd = BuildSelfCleaningDirectory();
597+
598+
const string url = "https://github.com/libgit2/TestGitRepository";
599+
600+
const string knownHeader = "X-Hello: world";
601+
var cloneOptions = new CloneOptions()
602+
{
603+
FetchOptions = new FetchOptions { CustomHeaders = new String[] { knownHeader } }
604+
};
605+
606+
var clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, cloneOptions);
607+
Assert.True(Directory.Exists(clonedRepoPath));
608+
}
560609
}
561610
}

LibGit2Sharp.Tests/FetchFixture.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,5 +239,57 @@ public void FetchHonorsTheFetchPruneConfigurationEntry()
239239
Assert.Equal(4, clonedRepo.Branches.Count(b => b.IsRemote));
240240
}
241241
}
242+
243+
[Fact]
244+
public void CannotFetchWithForbiddenCustomHeaders()
245+
{
246+
var scd = BuildSelfCleaningDirectory();
247+
248+
const string url = "https://github.com/libgit2/TestGitRepository";
249+
250+
string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath);
251+
252+
const string knownHeader = "User-Agent: mygit-201";
253+
var options = new FetchOptions { CustomHeaders = new String[] { knownHeader } };
254+
using (var repo = new Repository(clonedRepoPath))
255+
{
256+
Assert.Throws<LibGit2SharpException>(() => Commands.Fetch(repo, "origin", new string[0], options, null));
257+
}
258+
}
259+
260+
[Fact]
261+
public void CanFetchWithCustomHeaders()
262+
{
263+
var scd = BuildSelfCleaningDirectory();
264+
265+
const string url = "https://github.com/libgit2/TestGitRepository";
266+
267+
string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath);
268+
269+
const string knownHeader = "X-Hello: mygit-201";
270+
var options = new FetchOptions { CustomHeaders = new String[] { knownHeader } };
271+
using (var repo = new Repository(clonedRepoPath))
272+
{
273+
Commands.Fetch(repo, "origin", new string[0], options, null);
274+
}
275+
}
276+
277+
[Fact]
278+
public void CannotFetchWithMalformedCustomHeaders()
279+
{
280+
var scd = BuildSelfCleaningDirectory();
281+
282+
const string url = "https://github.com/libgit2/TestGitRepository";
283+
284+
string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath);
285+
286+
const string knownHeader = "Hello world";
287+
var options = new FetchOptions { CustomHeaders = new String[] { knownHeader } };
288+
using (var repo = new Repository(clonedRepoPath))
289+
{
290+
Assert.Throws<LibGit2SharpException>(() => Commands.Fetch(repo, "origin", new string[0], options, null));
291+
}
292+
}
293+
242294
}
243295
}

LibGit2Sharp/CloneOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ public CloneOptions()
4343
/// </summary>
4444
public CheckoutProgressHandler OnCheckoutProgress { get; set; }
4545

46+
/// <summary>
47+
/// Gets or sets the fetch options.
48+
/// </summary>
49+
public FetchOptions FetchOptions { get; set; }
50+
4651
#region IConvertableToGitCheckoutOpts
4752

4853
CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks()

LibGit2Sharp/Commands/Fetch.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public static void Fetch(Repository repository, string remote, IEnumerable<strin
3838

3939
options = options ?? new FetchOptions();
4040
using (var remoteHandle = RemoteFromNameOrUrl(repository.Handle, remote))
41+
using (var fetchOptionsWrapper = new GitFetchOptionsWrapper())
4142
{
4243

4344
var callbacks = new RemoteCallbacks(options);
@@ -51,11 +52,9 @@ public static void Fetch(Repository repository, string remote, IEnumerable<strin
5152
//
5253
// Also, if GitRemoteCallbacks were a class instead of a struct, we would need to guard against
5354
// GC occuring in between setting the remote callbacks and actual usage in one of the functions afterwords.
54-
var fetchOptions = new GitFetchOptions
55-
{
56-
RemoteCallbacks = gitCallbacks,
57-
download_tags = Proxy.git_remote_autotag(remoteHandle),
58-
};
55+
var fetchOptions = fetchOptionsWrapper.Options;
56+
fetchOptions.RemoteCallbacks = gitCallbacks;
57+
fetchOptions.download_tags = Proxy.git_remote_autotag(remoteHandle);
5958

6059
if (options.TagFetchMode.HasValue)
6160
{
@@ -71,6 +70,11 @@ public static void Fetch(Repository repository, string remote, IEnumerable<strin
7170
fetchOptions.Prune = FetchPruneStrategy.FromConfigurationOrDefault;
7271
}
7372

73+
if (options.CustomHeaders != null && options.CustomHeaders.Length > 0)
74+
{
75+
fetchOptions.CustomHeaders = GitStrArrayManaged.BuildFrom(options.CustomHeaders);
76+
}
77+
7478
fetchOptions.ProxyOptions = new GitProxyOptions { Version = 1 };
7579

7680
Proxy.git_remote_fetch(remoteHandle, refspecs, fetchOptions, logMessage);

LibGit2Sharp/Core/GitFetchOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ internal class GitFetchOptions
1111
public bool UpdateFetchHead = true;
1212
public TagFetchMode download_tags;
1313
public GitProxyOptions ProxyOptions;
14-
public GitStrArrayManaged custom_headers;
14+
public GitStrArrayManaged CustomHeaders;
1515
}
1616
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
3+
namespace LibGit2Sharp.Core
4+
{
5+
/// <summary>
6+
/// Git fetch options wrapper. Disposable wrapper for GitFetchOptions
7+
/// </summary>
8+
internal class GitFetchOptionsWrapper : IDisposable
9+
{
10+
public GitFetchOptionsWrapper() : this(new GitFetchOptions()) { }
11+
12+
public GitFetchOptionsWrapper(GitFetchOptions fetchOptions)
13+
{
14+
this.Options = fetchOptions;
15+
}
16+
17+
public GitFetchOptions Options { get; private set; }
18+
19+
#region IDisposable
20+
private bool disposedValue = false; // To detect redundant calls
21+
protected virtual void Dispose(bool disposing)
22+
{
23+
if (disposedValue)
24+
return;
25+
26+
this.Options.CustomHeaders.Dispose();
27+
disposedValue = true;
28+
}
29+
30+
public void Dispose()
31+
{
32+
Dispose(true);
33+
}
34+
#endregion
35+
}
36+
}

LibGit2Sharp/FetchOptions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,26 @@ public sealed class FetchOptions : FetchOptionsBase
2525
/// </para>
2626
/// </summary>
2727
public bool? Prune { get; set; }
28+
29+
/// <summary>
30+
/// Get/Set the custom headers.
31+
///
32+
/// <para>
33+
/// This allows you to set custom headers (e.g. X-Forwarded-For,
34+
/// X-Request-Id, etc),
35+
/// </para>
36+
/// </summary>
37+
/// <remarks>
38+
/// Libgit2 sets some headers for HTTP requests (User-Agent, Host,
39+
/// Accept, Content-Type, Transfer-Encoding, Content-Length, Accept) that
40+
/// cannot be overriden.
41+
/// </remarks>
42+
/// <example>
43+
/// var fetchOptions - new FetchOptions() {
44+
/// CustomHeaders = new String[] {"X-Request-Id: 12345"}
45+
/// };
46+
/// </example>
47+
/// <value>The custom headers string array</value>
48+
public string[] CustomHeaders { get; set; }
2849
}
2950
}

LibGit2Sharp/Repository.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -703,21 +703,26 @@ public static string Clone(string sourceUrl, string workdirPath,
703703
throw new UserCancelledException("Clone cancelled by the user.");
704704
}
705705

706-
using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options))
706+
using (var checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options))
707+
using (var fetchOptionsWrapper = new GitFetchOptionsWrapper())
707708
{
708709
var gitCheckoutOptions = checkoutOptionsWrapper.Options;
709710

710-
var remoteCallbacks = new RemoteCallbacks(options);
711-
var gitRemoteCallbacks = remoteCallbacks.GenerateCallbacks();
712-
713-
var gitProxyOptions = new GitProxyOptions { Version = 1 };
711+
var gitFetchOptions = fetchOptionsWrapper.Options;
712+
gitFetchOptions.ProxyOptions = new GitProxyOptions { Version = 1 };
713+
gitFetchOptions.RemoteCallbacks = new RemoteCallbacks(options).GenerateCallbacks();
714+
if (options.FetchOptions != null && options.FetchOptions.CustomHeaders != null)
715+
{
716+
gitFetchOptions.CustomHeaders =
717+
GitStrArrayManaged.BuildFrom(options.FetchOptions.CustomHeaders);
718+
}
714719

715720
var cloneOpts = new GitCloneOptions
716721
{
717722
Version = 1,
718723
Bare = options.IsBare ? 1 : 0,
719724
CheckoutOpts = gitCheckoutOptions,
720-
FetchOpts = new GitFetchOptions { ProxyOptions = gitProxyOptions, RemoteCallbacks = gitRemoteCallbacks },
725+
FetchOpts = gitFetchOptions,
721726
};
722727

723728
string clonedRepoPath;

0 commit comments

Comments
 (0)