Skip to content

Commit 4adea85

Browse files
Add a Local Nuget Server That uses Self Signed certificate (#117)
1 parent e2a8716 commit 4adea85

3 files changed

Lines changed: 291 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.10.34616.152
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "filebackedserver", "filebackedserver\filebackedserver.csproj", "{AE065439-B8C1-480C-BC50-3C4FD0F7A2CC}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{AE065439-B8C1-480C-BC50-3C4FD0F7A2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{AE065439-B8C1-480C-BC50-3C4FD0F7A2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{AE065439-B8C1-480C-BC50-3C4FD0F7A2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{AE065439-B8C1-480C-BC50-3C4FD0F7A2CC}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {AB7FAB02-6CD3-4D84-8896-246923DEE2C5}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
using System.Net;
2+
using System.Net.Sockets;
3+
using System.Net.Security;
4+
using System.Security.Cryptography.X509Certificates;
5+
using System.Text;
6+
using Newtonsoft.Json;
7+
using System.Security.Cryptography;
8+
using System.IO.Compression;
9+
10+
public class FileBackedTcpListener
11+
{
12+
private readonly X509Certificate2 _certificate;
13+
private readonly string _packageDirectory;
14+
private readonly TcpListener _tcpListener;
15+
private readonly string _uri;
16+
17+
public FileBackedTcpListener(string packageDirectory, X509Certificate2 certificate)
18+
{
19+
_packageDirectory = packageDirectory;
20+
_certificate = certificate;
21+
_tcpListener = new TcpListener(IPAddress.Loopback, 44444); // 0 for any available port
22+
_tcpListener.Start();
23+
_uri = $"https://{_tcpListener.LocalEndpoint}/";
24+
}
25+
26+
// Starts the server and waits for client requests
27+
public async Task StartServer()
28+
{
29+
Console.WriteLine($"Server started. Listening on {_tcpListener.LocalEndpoint}");
30+
31+
while (true)
32+
{
33+
Console.WriteLine("Waiting for client connection...");
34+
var client = await _tcpListener.AcceptTcpClientAsync();
35+
Console.WriteLine("Client connected.");
36+
37+
// Handling client in a new task to allow multiple client connections
38+
_ = Task.Run(() => HandleClient(client));
39+
}
40+
}
41+
42+
private async Task HandleClient(TcpClient client)
43+
{
44+
using (client)
45+
using (var sslStream = new SslStream(client.GetStream(), false))
46+
{
47+
try
48+
{
49+
await sslStream.AuthenticateAsServerAsync(_certificate, clientCertificateRequired: false, checkCertificateRevocation: true);
50+
}
51+
catch (Exception ex)
52+
{
53+
Console.WriteLine($"{ex.Message}");
54+
}
55+
using (var reader = new StreamReader(sslStream, Encoding.ASCII, false, 128))
56+
using (var writer = new StreamWriter(sslStream, Encoding.ASCII, 128, false))
57+
{
58+
try
59+
{
60+
var requestLine = await reader.ReadLineAsync();
61+
var requestParts = requestLine?.Split(' ');
62+
if (requestParts == null || requestParts.Length < 2)
63+
{
64+
throw new InvalidOperationException("Invalid HTTP request line.");
65+
}
66+
Console.WriteLine($"bout to check {requestParts[0]} and {requestParts[1]}");
67+
string method = requestParts[0];
68+
string rawUrl = requestParts[1];
69+
70+
string path = requestParts[1];
71+
var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
72+
if (path == "/v3/index.json")
73+
{
74+
SendIndexJsonResponse(writer);
75+
}
76+
else if (parts.Length > 1 && parts[0] == "v3")
77+
{
78+
if (parts[1] == "package")
79+
{
80+
if (parts.Length == 4)
81+
{
82+
ProcessPackageRequest(parts[2], writer);
83+
}
84+
else
85+
{
86+
SendPackageFile(parts[2], parts[3], parts[4], writer, sslStream);
87+
}
88+
}
89+
else
90+
{
91+
await writer.WriteLineAsync("HTTP/1.1 404 Not Found");
92+
}
93+
}
94+
else
95+
{
96+
await writer.WriteLineAsync("HTTP/1.1 404 Not Found");
97+
}
98+
}
99+
catch (Exception ex)
100+
{
101+
// Handle exception
102+
Console.WriteLine("Error processing request: " + ex.Message);
103+
}
104+
}
105+
}
106+
}
107+
108+
private void ProcessPackageRequest(string id, StreamWriter writer)
109+
{
110+
try
111+
{
112+
var versions = GetVersionsFromDirectory(id);
113+
114+
var json = JsonConvert.SerializeObject(new { versions });
115+
writer.WriteLine("HTTP/1.1 200 OK");
116+
writer.WriteLine("Content-Type: application/json");
117+
writer.WriteLine();
118+
writer.WriteLine(json);
119+
writer.Flush();
120+
}
121+
catch (Exception ex)
122+
{
123+
Console.WriteLine($"Error processing request: {ex.Message}");
124+
}
125+
}
126+
127+
private void SendPackageFile(string id, string version, string nupkg, StreamWriter writer, SslStream sslStream)
128+
{
129+
var filePath = Path.Combine(_packageDirectory, id, version, nupkg);
130+
131+
if (!File.Exists(filePath))
132+
{
133+
writer.WriteLine("HTTP/1.1 404 Not Found");
134+
return;
135+
}
136+
137+
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
138+
{
139+
writer.WriteLine("HTTP/1.1 200 OK");
140+
writer.WriteLine("Content-Type: application/octet-stream");
141+
writer.WriteLine($"Content-Disposition: attachment; filename=\"{id}.{version}.nupkg\"");
142+
writer.WriteLine($"Content-Length: {new FileInfo(filePath).Length}");
143+
writer.WriteLine();
144+
writer.Flush();
145+
146+
fileStream.CopyTo(sslStream);
147+
}
148+
}
149+
150+
private string[] GetVersionsFromDirectory(string id)
151+
{
152+
var directoryPath = Path.Combine(_packageDirectory, id);
153+
if (!Directory.Exists(directoryPath))
154+
{
155+
throw new DirectoryNotFoundException($"Directory not found: {directoryPath}");
156+
}
157+
158+
var dirInfo = new DirectoryInfo(directoryPath);
159+
return dirInfo.GetDirectories().Select(d => d.Name).ToArray();
160+
}
161+
162+
private void SendIndexJsonResponse(StreamWriter writer)
163+
{
164+
var indexResponse = new
165+
{
166+
version = "3.0.0",
167+
resources = new object[]
168+
{
169+
new Resource { Type = "SearchQueryService", Id = $"{_uri}v3/query" },
170+
new Resource { Type = "RegistrationsBaseUrl", Id = $"{_uri}v3/registration" },
171+
new Resource { Type = "PackageBaseAddress/3.0.0", Id = $"{_uri}v3/package" },
172+
new Resource { Type = "PackagePublish/2.0.0", Id = $"{_uri}v3/packagepublish" }
173+
}
174+
};
175+
176+
string jsonResponse = JsonConvert.SerializeObject(indexResponse);
177+
178+
writer.WriteLine("HTTP/1.1 200 OK");
179+
writer.WriteLine("Content-Type: application/json");
180+
writer.WriteLine();
181+
writer.WriteLine(jsonResponse);
182+
writer.Flush();
183+
}
184+
185+
public static void Main(string[] args)
186+
{
187+
X509Certificate2 certificate = GenerateSelfSignedCertificate();
188+
string packageDirectory = "packages/abcdefghijkl/1.0.0";
189+
190+
if (!Directory.Exists(packageDirectory))
191+
{
192+
Directory.CreateDirectory(packageDirectory);
193+
}
194+
195+
// Create test.nuspec file
196+
string nuspecPath = Path.Combine(packageDirectory, "abcdefghijkl.nuspec");
197+
File.WriteAllText(nuspecPath, @"
198+
<package>
199+
<metadata>
200+
<id>abcdefghijkl</id>
201+
<version>1.0.0</version>
202+
<description>Testing</description>
203+
<authors>NuGetTest</authors>
204+
<title />
205+
</metadata>
206+
</package>");
207+
208+
// Create the .nupkg file
209+
string nupkgPath = Path.Combine(packageDirectory, "abcdefghijkl.1.0.0.nupkg");
210+
if (File.Exists(nupkgPath))
211+
{
212+
File.Delete(nupkgPath);
213+
}
214+
using (var archive = ZipFile.Open(nupkgPath, ZipArchiveMode.Create))
215+
{
216+
archive.CreateEntryFromFile(nuspecPath, "abcdefghijkl.nuspec");
217+
}
218+
219+
// Delete the nuspec file after creating the .nupkg file
220+
File.Delete(nuspecPath);
221+
222+
var listener = new FileBackedTcpListener("./packages/", certificate);
223+
listener.StartServer().Wait();
224+
}
225+
226+
private static X509Certificate2 GenerateSelfSignedCertificate()
227+
{
228+
using (var rsa = RSA.Create(2048))
229+
{
230+
var request = new CertificateRequest("cn=test", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
231+
var start = DateTime.UtcNow;
232+
var end = DateTime.UtcNow.AddYears(1);
233+
var cert = request.CreateSelfSigned(start, end);
234+
var certBytes = cert.Export(X509ContentType.Pfx, "password");
235+
236+
return new X509Certificate2(certBytes, "password", X509KeyStorageFlags.Exportable);
237+
}
238+
}
239+
}
240+
241+
public class Resource
242+
{
243+
[JsonProperty("@type")]
244+
public string Type { get; set; }
245+
246+
[JsonProperty("@id")]
247+
public string Id { get; set; }
248+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="CaptureScreen" Version="1.0.0" />
12+
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.7" />
13+
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.1.1" />
14+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
15+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)