Skip to content

Commit

Permalink
Upgrades
Browse files Browse the repository at this point in the history
Adds the ability for users to fullfill HTTP requests themselves.
Adds the ability for users to supply their own HttpClient's on a per-request basis to fulfill requests with.
  • Loading branch information
TechnikEmpire committed Nov 2, 2018
1 parent 7916fee commit 0ce472e
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 13 deletions.
165 changes: 163 additions & 2 deletions CitadelCore.Windows.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

using CitadelCore.Extensions;
using CitadelCore.IO;
using CitadelCore.Logging;
using CitadelCore.Net.Http;
using CitadelCore.Net.Proxy;
using CitadelCore.Windows.Net.Proxy;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WindowsFirewallHelper;

namespace CitadelCoreTest
Expand All @@ -30,6 +34,22 @@ internal class Program
private static readonly ushort s_standardHttpPortNetworkOrder = (ushort)IPAddress.HostToNetworkOrder((short)80);
private static readonly ushort s_standardHttpsPortNetworkOrder = (ushort)IPAddress.HostToNetworkOrder((short)443);

/// <summary>
/// We pass this in to stream copy operations whenever the user has asked us to pull a
/// payload from the net into memory. We set a hard limit of ~128 megs simply to avoid being
/// vulnerable to an attack that would balloon memory consumption.
/// </summary>
private static readonly long s_maxInMemoryData = 128000000;

private static HttpClient s_client = new HttpClient(new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
UseCookies = false,
ClientCertificateOptions = ClientCertificateOption.Automatic,
AllowAutoRedirect = true,
Proxy = null
}, true);

private static FirewallResponse OnFirewallCheck(FirewallRequest request)
{
// Only filter chrome.
Expand Down Expand Up @@ -127,6 +147,30 @@ private static void ForceGoogleSafeSearch(HttpMessageInfo messageInfo)
}
}

/// <summary>
/// Checks whether the host is MSNBC.com and if so, we will tell the proxy to let us fulfill
/// the request ourselves.
/// </summary>
/// <param name="messageInfo">
/// The message info.
/// </param>
/// <returns>
/// True if we should fulfill the request ourselves, false otherwise.
/// </returns>
private static bool ManuallyFullfill(HttpMessageInfo messageInfo)
{
if (messageInfo.MessageType == MessageType.Request)
{
if (messageInfo.Url.Host.Equals("msnbc.com", StringComparison.OrdinalIgnoreCase))
{
messageInfo.ProxyNextAction = ProxyNextAction.AllowButDelegateHandler;
return true;
}
}

return false;
}

/// <summary>
/// Called whenever a new request or response message is intercepted.
/// </summary>
Expand All @@ -146,6 +190,11 @@ private static void OnNewMessage(HttpMessageInfo messageInfo)
return;
}

if (ManuallyFullfill(messageInfo))
{
return;
}

// Get Technikempire.com as a replay request.
// Replay requests are only available on response message types.
// This will cause us to receive a request URI on the IpV4 loopback adapter
Expand Down Expand Up @@ -250,7 +299,7 @@ private static void OnWholeBodyContentInspection(HttpMessageInfo messageInfo)
/// Whether or not to immediately terminate the connection.
/// </param>
private static void OnStreamedContentInspection(HttpMessageInfo messageInfo, StreamOperation operation, Memory<byte> buffer, out bool dropConnection)
{
{
var toFrom = operation == StreamOperation.Read ? "from" : "to";
Console.WriteLine($"Stream {operation} {buffer.Length} bytes {toFrom} {messageInfo.Url}");
dropConnection = false;
Expand All @@ -262,7 +311,7 @@ private static void OnStreamedContentInspection(HttpMessageInfo messageInfo, Str
// site, but you can't play any videos.
// This is just to demonstrate that it's possible to have complete
// control over unbuffered streams.
//dropConnection = true;
// dropConnection = true;
}
}

Expand Down Expand Up @@ -332,6 +381,117 @@ private static void OnReplayInspection(string replayUrl, HttpReplayTerminationCa
// the cancellationCallback can be used to kill the original, source video stream.
}

/// <summary>
/// Called whenever we request to fulfill a request ourselves.
/// </summary>
/// <param name="messageInfo">
/// The message info.
/// </param>
/// <param name="context">
/// The http context to read and write to and from.
/// </param>
/// <returns>
/// Completion task.
/// </returns>
private static async Task OnManualFullfillmentCallback(HttpMessageInfo messageInfo, HttpContext context)
{
// Create the message AFTER we give the user a chance to alter things.
var requestMsg = new HttpRequestMessage(messageInfo.Method, messageInfo.Url);

// Ignore failed headers. We don't really care.
var initialFailedHeaders = requestMsg.PopulateHeaders(messageInfo.Headers, messageInfo.ExemptedHeaders);

// Make sure we send the body.
if (context.Request.Body != null)
{
if (context.Request.Body != null && (context.Request.Headers.ContainsKey("Transfer-Encoding") || (context.Request.ContentLength.HasValue && context.Request.ContentLength.Value > 0)))
{
// We have a body, but the user doesn't want to inspect it. So,
// we'll just set our content to wrap the context's input stream.
requestMsg.Content = new StreamContent(context.Request.Body);
}
}

try
{
var response = await s_client.SendAsync(requestMsg, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted);

// Blow away all response headers. We wanna clone these now from our upstream request.
context.Response.ClearAllHeaders();

// Ensure our client's response status code is set to match ours.
context.Response.StatusCode = (int)response.StatusCode;

var upstreamResponseHeaders = response.ExportAllHeaders();

bool responseHasZeroContentLength = false;
bool responseIsFixedLength = false;

foreach (var kvp in upstreamResponseHeaders.ToIHeaderDictionary())
{
foreach (var value in kvp.Value)
{
if (kvp.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
{
responseIsFixedLength = true;

if (value.Length <= 0 && value.Equals("0"))
{
responseHasZeroContentLength = true;
}
}
}
}

// Copy over the upstream headers.
context.Response.PopulateHeaders(upstreamResponseHeaders, new System.Collections.Generic.HashSet<string>());

// Copy over the upstream body.
using (var responseStream = await response?.Content.ReadAsStreamAsync())
{
context.Response.StatusCode = (int)response.StatusCode;
context.Response.PopulateHeaders(response.ExportAllHeaders(), new System.Collections.Generic.HashSet<string>());

if (!responseHasZeroContentLength && responseIsFixedLength)
{
using (var ms = new MemoryStream())
{
await Microsoft.AspNetCore.Http.Extensions.StreamCopyOperation.CopyToAsync(responseStream, ms, s_maxInMemoryData, context.RequestAborted);

var responseBody = ms.ToArray();

context.Response.Headers.Remove("Content-Length");

context.Response.Headers.Add("Content-Length", responseBody.Length.ToString());

await context.Response.Body.WriteAsync(responseBody, 0, responseBody.Length);
}
}
else
{
context.Response.Headers.Remove("Content-Length");

if (responseHasZeroContentLength)
{
context.Response.Headers.Add("Content-Length", "0");
}
else
{
await Microsoft.AspNetCore.Http.Extensions.StreamCopyOperation.CopyToAsync(responseStream, context.Response.Body, null, context.RequestAborted);
}
}
}
}
catch (Exception e)
{
while (e != null)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
}
}
}

private static void Main(string[] args)
{
GrantSelfFirewallAccess();
Expand Down Expand Up @@ -374,6 +534,7 @@ private static void Main(string[] args)
NewHttpMessageHandler = OnNewMessage,
HttpMessageWholeBodyInspectionHandler = OnWholeBodyContentInspection,
HttpMessageStreamedInspectionHandler = OnStreamedContentInspection,
HttpExternalRequestHandlerCallback = OnManualFullfillmentCallback,
BlockExternalProxies = true
};

Expand Down
19 changes: 8 additions & 11 deletions CitadelCore.Windows/CitadelCore.Windows.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>3.4.1</Version>
<Version>3.6.0</Version>
<Title>CitadeCore.Windows</Title>
<Authors>Jesse Nicholson</Authors>
<Company>Technik Empire</Company>
<Description>Transparent filtering proxy engine for Windows.</Description>
<Copyright>Copyright 2017-Present Jesse Nicholson</Copyright>
<PackageLicenseUrl>https://raw.githubusercontent.com/TechnikEmpire/CitadelCore.Windows/master/LICENSE</PackageLicenseUrl>
<Copyright>Copyright 2017-Present (c) Jesse Nicholson. All rights reserved.</Copyright>
<PackageLicenseUrl>https://www.mozilla.org/en-US/MPL/2.0/</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/TechnikEmpire/CitadelCore.Windows</PackageProjectUrl>
<RepositoryUrl>https://github.com/TechnikEmpire/CitadelCore.Windows</RepositoryUrl>
<PackageReleaseNotes>Adds the ability to exclude headers from being excluded. Normally forbidden headers are stripped, but now the user can control this.
Adds the ability to configure the proxy server in regards to dropping external proxy packets (originating from another proxy on the local machine).
Adds the ability to provide a custom message handler for all proxy server upstream connections. This enables you to define an upstream proxy, etc.
Fixes an issue where the host header is not set correctly on modification.
Purges singlestons from the source code. Handlers are now instance members of proxy server classes via hidden IStartup instances.</PackageReleaseNotes>
<PackageReleaseNotes>Adds the ability for users to fullfill HTTP requests themselves.
Adds the ability for users to supply their own HttpClient's on a per-request basis to fulfill requests with.</PackageReleaseNotes>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AssemblyVersion>3.4.1.0</AssemblyVersion>
<FileVersion>3.4.1.0</FileVersion>
<AssemblyVersion>3.6.0.0</AssemblyVersion>
<FileVersion>3.6.0.0</FileVersion>
<RepositoryType>git</RepositoryType>
<PackageTags>proxy, filter, filtering, content filtering, content-filter, websocket proxy, http proxy, https proxy</PackageTags>
</PropertyGroup>
Expand All @@ -43,7 +40,7 @@ Purges singlestons from the source code. Handlers are now instance members of pr
</ItemGroup>

<ItemGroup>
<PackageReference Include="CitadelCore" Version="3.4.1" />
<PackageReference Include="CitadelCore" Version="3.6.0" />
<PackageReference Include="WinDivertSharp" Version="1.4.3.3" />
</ItemGroup>

Expand Down

0 comments on commit 0ce472e

Please sign in to comment.