Skip to content

Commit ec8b9ec

Browse files
phkelleyEdward Thomson
authored and
Edward Thomson
committed
Subtransport logic.
1 parent ca6242c commit ec8b9ec

14 files changed

+1109
-1
lines changed

LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
<Compile Include="ResetHeadFixture.cs" />
103103
<Compile Include="FetchFixture.cs" />
104104
<Compile Include="ResetIndexFixture.cs" />
105+
<Compile Include="SmartSubtransportFixture.cs" />
105106
<Compile Include="StatusFixture.cs" />
106107
<Compile Include="TestHelpers\BaseFixture.cs" />
107108
<Compile Include="BlobFixture.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Net;
6+
using System.Net.Security;
7+
using LibGit2Sharp.Tests.TestHelpers;
8+
using Xunit;
9+
using Xunit.Extensions;
10+
11+
namespace LibGit2Sharp.Tests
12+
{
13+
public class SmartSubtransportFixture : BaseFixture
14+
{
15+
// Warning: this certification validation callback will accept *all*
16+
// SSL certificates without validation. This is *only* for testing.
17+
// Do *NOT* enable this in production.
18+
private readonly RemoteCertificateValidationCallback certificateValidationCallback =
19+
(sender, certificate, chain, errors) => { return true; };
20+
21+
[Theory]
22+
[InlineData("http", "http://github.com/libgit2/TestGitRepository")]
23+
[InlineData("https", "https://github.com/libgit2/TestGitRepository")]
24+
public void CustomSmartSubtransportTest(string scheme, string url)
25+
{
26+
string remoteName = "testRemote";
27+
28+
var scd = BuildSelfCleaningDirectory();
29+
var repoPath = Repository.Init(scd.RootedDirectoryPath);
30+
31+
SmartSubtransportRegistration<MockSmartSubtransport> registration = null;
32+
33+
try
34+
{
35+
// Disable server certificate validation for testing.
36+
// Do *NOT* enable this in production.
37+
ServicePointManager.ServerCertificateValidationCallback = certificateValidationCallback;
38+
39+
registration = GlobalSettings.RegisterSmartSubtransport<MockSmartSubtransport>(scheme);
40+
Assert.NotNull(registration);
41+
42+
using (var repo = new Repository(scd.DirectoryPath))
43+
{
44+
Remote remote = repo.Network.Remotes.Add(remoteName, url);
45+
46+
// Set up structures for the expected results
47+
// and verifying the RemoteUpdateTips callback.
48+
TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance;
49+
ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName);
50+
51+
// Add expected branch objects
52+
foreach (KeyValuePair<string, ObjectId> kvp in expectedResults.BranchTips)
53+
{
54+
expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value);
55+
}
56+
57+
// Add the expected tags
58+
string[] expectedTagNames = { "blob", "commit_tree" };
59+
foreach (string tagName in expectedTagNames)
60+
{
61+
TestRemoteInfo.ExpectedTagInfo expectedTagInfo = expectedResults.Tags[tagName];
62+
expectedFetchState.AddExpectedTag(tagName, ObjectId.Zero, expectedTagInfo);
63+
}
64+
65+
// Perform the actual fetch
66+
repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto });
67+
68+
// Verify the expected
69+
expectedFetchState.CheckUpdatedReferences(repo);
70+
}
71+
}
72+
finally
73+
{
74+
GlobalSettings.UnregisterSmartSubtransport(registration);
75+
76+
ServicePointManager.ServerCertificateValidationCallback -= certificateValidationCallback;
77+
}
78+
}
79+
80+
[Fact]
81+
public void CannotReregisterScheme()
82+
{
83+
SmartSubtransportRegistration<MockSmartSubtransport> httpRegistration =
84+
GlobalSettings.RegisterSmartSubtransport<MockSmartSubtransport>("http");
85+
86+
try
87+
{
88+
Assert.Throws<EntryExistsException>(() =>
89+
GlobalSettings.RegisterSmartSubtransport<MockSmartSubtransport>("http"));
90+
}
91+
finally
92+
{
93+
GlobalSettings.UnregisterSmartSubtransport(httpRegistration);
94+
}
95+
}
96+
97+
[Fact]
98+
public void CannotUnregisterTwice()
99+
{
100+
SmartSubtransportRegistration<MockSmartSubtransport> httpRegistration =
101+
GlobalSettings.RegisterSmartSubtransport<MockSmartSubtransport>("http");
102+
103+
GlobalSettings.UnregisterSmartSubtransport(httpRegistration);
104+
105+
Assert.Throws<NotFoundException>(() =>
106+
GlobalSettings.UnregisterSmartSubtransport(httpRegistration));
107+
}
108+
109+
private class MockSmartSubtransport : RpcSmartSubtransport
110+
{
111+
protected override SmartSubtransportStream Action(String url, GitSmartSubtransportAction action)
112+
{
113+
String endpointUrl, contentType = null;
114+
bool isPost = false;
115+
116+
switch (action)
117+
{
118+
case GitSmartSubtransportAction.UploadPackList:
119+
endpointUrl = String.Concat(url, "/info/refs?service=git-upload-pack");
120+
break;
121+
122+
case GitSmartSubtransportAction.UploadPack:
123+
endpointUrl = String.Concat(url, "/git-upload-pack");
124+
contentType = "application/x-git-upload-pack-request";
125+
isPost = true;
126+
break;
127+
128+
case GitSmartSubtransportAction.ReceivePackList:
129+
endpointUrl = String.Concat(url, "/info/refs?service=git-receive-pack");
130+
break;
131+
132+
case GitSmartSubtransportAction.ReceivePack:
133+
endpointUrl = String.Concat(url, "/git-receive-pack");
134+
contentType = "application/x-git-receive-pack-request";
135+
isPost = true;
136+
break;
137+
138+
default:
139+
throw new InvalidOperationException();
140+
}
141+
142+
return new MockSmartSubtransportStream(this, endpointUrl, isPost, contentType);
143+
}
144+
145+
private class MockSmartSubtransportStream : SmartSubtransportStream
146+
{
147+
private static int MAX_REDIRECTS = 5;
148+
149+
private MemoryStream postBuffer = new MemoryStream();
150+
private Stream responseStream;
151+
152+
public MockSmartSubtransportStream(MockSmartSubtransport parent, string endpointUrl, bool isPost, string contentType)
153+
: base(parent)
154+
{
155+
EndpointUrl = endpointUrl;
156+
IsPost = isPost;
157+
ContentType = contentType;
158+
}
159+
160+
private string EndpointUrl
161+
{
162+
get;
163+
set;
164+
}
165+
166+
private bool IsPost
167+
{
168+
get;
169+
set;
170+
}
171+
172+
private string ContentType
173+
{
174+
get;
175+
set;
176+
}
177+
178+
public override int Write(Stream dataStream, long length)
179+
{
180+
byte[] buffer = new byte[4096];
181+
long writeTotal = 0;
182+
183+
while (length > 0)
184+
{
185+
int readLen = dataStream.Read(buffer, 0, (int)Math.Min(buffer.Length, length));
186+
187+
if (readLen == 0)
188+
{
189+
break;
190+
}
191+
192+
postBuffer.Write(buffer, 0, readLen);
193+
length -= readLen;
194+
writeTotal += readLen;
195+
}
196+
197+
if (writeTotal < length)
198+
{
199+
throw new EndOfStreamException("Could not write buffer (short read)");
200+
}
201+
202+
return 0;
203+
}
204+
205+
private static HttpWebRequest CreateWebRequest(string endpointUrl, bool isPost, string contentType)
206+
{
207+
HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(endpointUrl);
208+
webRequest.UserAgent = "git/1.0 (libgit2 custom transport)";
209+
webRequest.ServicePoint.Expect100Continue = false;
210+
webRequest.AllowAutoRedirect = false;
211+
212+
if (isPost)
213+
{
214+
webRequest.Method = "POST";
215+
webRequest.ContentType = contentType;
216+
}
217+
218+
return webRequest;
219+
}
220+
221+
private HttpWebResponse GetResponseWithRedirects()
222+
{
223+
HttpWebRequest request = CreateWebRequest(EndpointUrl, IsPost, ContentType);
224+
HttpWebResponse response = null;
225+
226+
for (int i = 0; i < MAX_REDIRECTS; i++)
227+
{
228+
if (IsPost && postBuffer.Length > 0)
229+
{
230+
postBuffer.Seek(0, SeekOrigin.Begin);
231+
232+
using (Stream requestStream = request.GetRequestStream())
233+
{
234+
postBuffer.WriteTo(requestStream);
235+
}
236+
}
237+
238+
response = (HttpWebResponse)request.GetResponse();
239+
240+
if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect)
241+
{
242+
request = CreateWebRequest(response.Headers["Location"], IsPost, ContentType);
243+
continue;
244+
}
245+
246+
break;
247+
}
248+
249+
if (response == null)
250+
{
251+
throw new Exception("Too many redirects");
252+
}
253+
254+
return response;
255+
}
256+
257+
public override int Read(Stream dataStream, long length, out long readTotal)
258+
{
259+
byte[] buffer = new byte[4096];
260+
readTotal = 0;
261+
262+
if (responseStream == null)
263+
{
264+
HttpWebResponse response = GetResponseWithRedirects();
265+
responseStream = response.GetResponseStream();
266+
}
267+
268+
while (length > 0)
269+
{
270+
int readLen = responseStream.Read(buffer, 0, (int)Math.Min(buffer.Length, length));
271+
272+
if (readLen == 0)
273+
break;
274+
275+
dataStream.Write(buffer, 0, readLen);
276+
readTotal += readLen;
277+
length -= readLen;
278+
}
279+
280+
return 0;
281+
}
282+
283+
protected override void Free()
284+
{
285+
if (responseStream != null)
286+
{
287+
responseStream.Dispose();
288+
responseStream = null;
289+
}
290+
291+
base.Free();
292+
}
293+
}
294+
}
295+
}
296+
}
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal class GitSmartSubtransport
8+
{
9+
static GitSmartSubtransport()
10+
{
11+
GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransport), "GCHandle").ToInt32();
12+
}
13+
14+
public action_callback Action;
15+
public close_callback Close;
16+
public free_callback Free;
17+
18+
/* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */
19+
20+
public IntPtr GCHandle;
21+
22+
/* The following static fields are not part of the structure definition. */
23+
24+
public static int GCHandleOffset;
25+
26+
public delegate int action_callback(
27+
out IntPtr stream,
28+
IntPtr subtransport,
29+
IntPtr url,
30+
GitSmartSubtransportAction action);
31+
32+
public delegate int close_callback(IntPtr subtransport);
33+
34+
public delegate void free_callback(IntPtr subtransport);
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal class GitSmartSubtransportRegistration
8+
{
9+
public IntPtr SubtransportCallback;
10+
public uint Rpc;
11+
12+
public delegate int create_callback(
13+
out IntPtr subtransport,
14+
IntPtr transport);
15+
}
16+
}

0 commit comments

Comments
 (0)