500 lines
16 KiB
Plaintext
500 lines
16 KiB
Plaintext
{
|
|
$Project$
|
|
$Workfile$
|
|
$Revision$
|
|
$DateUTC$
|
|
$Id$
|
|
|
|
This file is part of the Indy (Internet Direct) project, and is offered
|
|
under the dual-licensing agreement described on the Indy website.
|
|
(http://www.indyproject.org/)
|
|
|
|
Copyright:
|
|
(c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
|
|
}
|
|
{
|
|
$Log$
|
|
}
|
|
{
|
|
Rev 1.24 10/14/2004 1:45:32 PM BGooijen
|
|
Beauty fixes ;)
|
|
|
|
Rev 1.23 10/14/2004 1:05:48 PM BGooijen
|
|
set PerformReply to false, else "200 OK" was added behind the document body
|
|
|
|
Rev 1.22 09.08.2004 09:30:00 OMonien
|
|
changed disconnect handling. Previous implementation failed when exceptions
|
|
ocured in command handler.
|
|
|
|
Rev 1.21 08.08.2004 10:35:56 OMonien
|
|
Greeting removed
|
|
|
|
Rev 1.20 6/11/2004 9:36:28 AM DSiders
|
|
Added "Do not Localize" comments.
|
|
|
|
Rev 1.19 2004.05.20 1:39:24 PM czhower
|
|
Last of the IdStream updates
|
|
|
|
Rev 1.18 2004.05.20 11:37:20 AM czhower
|
|
IdStreamVCL
|
|
|
|
Rev 1.17 4/19/2004 7:07:38 PM BGooijen
|
|
the remote headers are now passed to the OnHTTPDocument event
|
|
|
|
Rev 1.16 4/18/2004 11:31:26 PM BGooijen
|
|
Fixed POST
|
|
Build CONNECT
|
|
fixed some bugs where chars were replaced when that was not needed ( thus
|
|
causing corrupt data )
|
|
|
|
Rev 1.15 2004.04.13 10:24:24 PM czhower
|
|
Bug fix for when user changes stream.
|
|
|
|
Rev 1.14 2004.02.03 5:45:12 PM czhower
|
|
Name changes
|
|
|
|
Rev 1.13 1/21/2004 2:42:52 PM JPMugaas
|
|
InitComponent
|
|
|
|
Rev 1.12 10/25/2003 06:52:12 AM JPMugaas
|
|
Updated for new API changes and tried to restore some functionality.
|
|
|
|
Rev 1.11 2003.10.24 10:43:10 AM czhower
|
|
TIdSTream to dos
|
|
|
|
Rev 1.10 10/17/2003 12:10:08 AM DSiders
|
|
Added localization comments.
|
|
|
|
Rev 1.9 2003.10.12 3:50:44 PM czhower
|
|
Compile todos
|
|
|
|
Rev 1.8 7/13/2003 7:57:38 PM SPerry
|
|
fixed problem with commandhandlers
|
|
|
|
Rev 1.6 5/25/2003 03:54:42 AM JPMugaas
|
|
|
|
Rev 1.5 2/24/2003 08:56:50 PM JPMugaas
|
|
|
|
Rev 1.4 1/20/2003 1:15:44 PM BGooijen
|
|
Changed to TIdTCPServer / TIdCmdTCPServer classes
|
|
|
|
Rev 1.3 1-14-2003 19:19:22 BGooijen
|
|
The first line of the header was sent to the server twice, fixed that.
|
|
|
|
Rev 1.2 1-1-2003 21:52:06 BGooijen
|
|
Changed for TIdContext
|
|
|
|
Rev 1.1 12-29-2002 13:00:02 BGooijen
|
|
- Works on Indy 10 now
|
|
- Cleaned up some code
|
|
|
|
Rev 1.0 2002.11.22 8:37:50 PM czhower
|
|
|
|
Rev 1.0 2002.11.22 8:37:16 PM czhower
|
|
|
|
10-May-2002: Created Unit.
|
|
}
|
|
|
|
unit IdHTTPProxyServer;
|
|
|
|
interface
|
|
|
|
{$i IdCompilerDefines.inc}
|
|
|
|
{
|
|
Indy HTTP proxy Server
|
|
|
|
Original Programmer: Bas Gooijen (bas_gooijen@yahoo.com)
|
|
Current Maintainer: Bas Gooijen
|
|
Code is given to the Indy Pit Crew.
|
|
|
|
Modifications by Chad Z. Hower (Kudzu)
|
|
}
|
|
|
|
uses
|
|
Classes,
|
|
IdAssignedNumbers,
|
|
IdGlobal,
|
|
IdHeaderList,
|
|
IdTCPConnection,
|
|
IdCustomTCPServer, //for TIdServerContext
|
|
IdCmdTCPServer,
|
|
IdCommandHandlers,
|
|
IdContext,
|
|
IdYarn;
|
|
|
|
const
|
|
IdPORT_HTTPProxy = 8080;
|
|
|
|
type
|
|
TIdHTTPProxyTransferMode = ( tmFullDocument, tmStreaming );
|
|
TIdHTTPProxyTransferSource = ( tsClient, tsServer );
|
|
|
|
TIdHTTPProxyServerContext = class(TIdServerContext)
|
|
protected
|
|
FHeaders: TIdHeaderList;
|
|
FCommand: String;
|
|
FDocument: String;
|
|
FOutboundClient: TIdTCPConnection;
|
|
FTarget: String;
|
|
FTransferMode: TIdHTTPProxyTransferMode;
|
|
FTransferSource: TIdHTTPProxyTransferSource;
|
|
public
|
|
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
|
|
destructor Destroy; override;
|
|
property Headers: TIdHeaderList read FHeaders;
|
|
property Command: String read FCommand;
|
|
property Document: String read FDocument;
|
|
property OutboundClient: TIdTCPConnection read FOutboundClient;
|
|
property Target: String read FTarget;
|
|
property TransferMode: TIdHTTPProxyTransferMode read FTransferMode write FTransferMode;
|
|
property TransferSource: TIdHTTPProxyTransferSource read FTransferSource;
|
|
end;
|
|
|
|
TIdHTTPProxyServer = class;
|
|
|
|
TOnHTTPContextEvent = procedure(AContext: TIdHTTPProxyServerContext) of object;
|
|
TOnHTTPDocument = procedure(AContext: TIdHTTPProxyServerContext; var VStream: TStream) of object;
|
|
|
|
TIdHTTPProxyServer = class(TIdCmdTCPServer)
|
|
protected
|
|
FDefTransferMode: TIdHTTPProxyTransferMode;
|
|
FOnHTTPBeforeCommand: TOnHTTPContextEvent;
|
|
FOnHTTPResponse: TOnHTTPContextEvent;
|
|
FOnHTTPDocument: TOnHTTPDocument;
|
|
// CommandHandlers
|
|
procedure CommandPassThrough(ASender: TIdCommand);
|
|
procedure CommandCONNECT(ASender: TIdCommand); // for ssl
|
|
procedure DoHTTPBeforeCommand(AContext: TIdHTTPProxyServerContext);
|
|
procedure DoHTTPDocument(AContext: TIdHTTPProxyServerContext; var VStream: TStream);
|
|
procedure DoHTTPResponse(AContext: TIdHTTPProxyServerContext);
|
|
procedure InitializeCommandHandlers; override;
|
|
procedure TransferData(AContext: TIdHTTPProxyServerContext; ASrc, ADest: TIdTCPConnection);
|
|
procedure InitComponent; override;
|
|
published
|
|
property DefaultPort default IdPORT_HTTPProxy;
|
|
property DefaultTransferMode: TIdHTTPProxyTransferMode read FDefTransferMode write FDefTransferMode default tmFullDocument;
|
|
property OnHTTPBeforeCommand: TOnHTTPContextEvent read FOnHTTPBeforeCommand write FOnHTTPBeforeCommand;
|
|
property OnHTTPResponse: TOnHTTPContextEvent read FOnHTTPResponse write FOnHTTPResponse;
|
|
property OnHTTPDocument: TOnHTTPDocument read FOnHTTPDocument write FOnHTTPDocument;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
IdResourceStrings, IdResourceStringsProtocols, IdReplyRFC, IdTCPClient, IdURI,
|
|
IdGlobalProtocols, IdStack, IdTCPStream, IdException, SysUtils;
|
|
|
|
constructor TIdHTTPProxyServerContext.Create(AConnection: TIdTCPConnection;
|
|
AYarn: TIdYarn; AList: TIdContextThreadList = nil);
|
|
begin
|
|
inherited Create(AConnection, AYarn, AList);
|
|
FHeaders := TIdHeaderList.Create(QuoteHTTP);
|
|
end;
|
|
|
|
destructor TIdHTTPProxyServerContext.Destroy;
|
|
begin
|
|
FreeAndNil(FHeaders);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{ TIdHTTPProxyServer }
|
|
|
|
procedure TIdHTTPProxyServer.InitializeCommandHandlers;
|
|
var
|
|
LCommandHandler: TIdCommandHandler;
|
|
begin
|
|
inherited;
|
|
LCommandHandler := CommandHandlers.Add;
|
|
LCommandHandler.Command := 'GET'; {do not localize}
|
|
LCommandHandler.OnCommand := CommandPassThrough;
|
|
LCommandHandler.ParseParams := True;
|
|
LCommandHandler.Disconnect := True;
|
|
|
|
LCommandHandler := CommandHandlers.Add;
|
|
LCommandHandler.Command := 'POST'; {do not localize}
|
|
LCommandHandler.OnCommand := CommandPassThrough;
|
|
LCommandHandler.ParseParams := True;
|
|
LCommandHandler.Disconnect := True;
|
|
|
|
LCommandHandler := CommandHandlers.Add;
|
|
LCommandHandler.Command := 'HEAD'; {do not localize}
|
|
LCommandHandler.OnCommand := CommandPassThrough;
|
|
LCommandHandler.ParseParams := True;
|
|
LCommandHandler.Disconnect := True;
|
|
|
|
LCommandHandler := CommandHandlers.Add;
|
|
LCommandHandler.Command := 'CONNECT'; {do not localize}
|
|
LCommandHandler.OnCommand := CommandCONNECT;
|
|
LCommandHandler.ParseParams := True;
|
|
LCommandHandler.Disconnect := True;
|
|
|
|
//HTTP Servers/Proxies do not send a greeting
|
|
Greeting.Clear;
|
|
end;
|
|
|
|
procedure TIdHTTPProxyServer.TransferData(AContext: TIdHTTPProxyServerContext;
|
|
ASrc, ADest: TIdTCPConnection);
|
|
var
|
|
LStream: TStream;
|
|
LSize: TIdStreamSize;
|
|
S: String;
|
|
begin
|
|
// RLebeau: TODO - support chunked, gzip, and deflate transfers.
|
|
|
|
// RLebeau: determine how many bytes to read
|
|
S := AContext.Headers.Values['Content-Length']; {Do not Localize}
|
|
if S <> '' then
|
|
begin
|
|
LSize := IndyStrToStreamSize(S, -1) ; {Do not Localize}
|
|
if LSize < 0 then begin
|
|
// Write HTTP error status response
|
|
if AContext.TransferSource = tsClient then begin
|
|
ASrc.IOHandler.WriteLn('HTTP/1.0 400 Bad Request'); {Do not Localize}
|
|
end else begin
|
|
ASrc.IOHandler.WriteLn('HTTP/1.0 502 Bad Gateway'); {Do not Localize}
|
|
end;
|
|
ASrc.IOHandler.WriteLn;
|
|
Exit;
|
|
end;
|
|
end else begin
|
|
LSize := -1;
|
|
end;
|
|
|
|
if AContext.TransferSource = tsClient then begin
|
|
ADest.IOHandler.WriteLn(AContext.Command + ' ' + AContext.Document + ' HTTP/1.0'); {Do not Localize}
|
|
end;
|
|
|
|
if (AContext.TransferSource = tsServer) or (LSize > 0) then
|
|
begin
|
|
LStream := nil;
|
|
try
|
|
if AContext.TransferMode = tmFullDocument then
|
|
begin
|
|
//TODO: Have an event to let the user perform stream creation
|
|
LStream := TMemoryStream.Create;
|
|
// RLebeau: do not write the source headers until the OnHTTPDocument
|
|
// event has had a chance to update them if it alters the document data...
|
|
ASrc.IOHandler.ReadStream(LStream, LSize, LSize < 0);
|
|
LStream.Position := 0;
|
|
DoHTTPDocument(AContext, LStream);
|
|
ADest.IOHandler.Write(AContext.Headers);
|
|
ADest.IOHandler.WriteLn;
|
|
ADest.IOHandler.Write(LStream);
|
|
end else
|
|
begin
|
|
// RLebeau: direct pass-through, send everything as-is...
|
|
LStream := TIdTCPStream.Create(ADest);
|
|
ADest.IOHandler.Write(AContext.Headers);
|
|
ADest.IOHandler.WriteLn;
|
|
ASrc.IOHandler.ReadStream(LStream, LSize, LSize < 0);
|
|
end;
|
|
finally
|
|
FreeAndNil(LStream);
|
|
end;
|
|
end else
|
|
begin
|
|
// RLebeau: the client sent a document with no data in it, so just pass
|
|
// along the headers by themselves ...
|
|
ADest.IOHandler.Write(AContext.Headers);
|
|
ADest.IOHandler.WriteLn;
|
|
end;
|
|
end;
|
|
|
|
procedure TIdHTTPProxyServer.CommandPassThrough(ASender: TIdCommand);
|
|
var
|
|
LURI: TIdURI;
|
|
LContext: TIdHTTPProxyServerContext;
|
|
begin
|
|
ASender.PerformReply := False;
|
|
|
|
LContext := TIdHTTPProxyServerContext(ASender.Context);
|
|
LContext.FCommand := ASender.CommandHandler.Command;
|
|
LContext.FTarget := ASender.Params.Strings[0];
|
|
|
|
LContext.FOutboundClient := TIdTCPClient.Create(nil);
|
|
try
|
|
LURI := TIdURI.Create(LContext.Target);
|
|
try
|
|
TIdTCPClient(LContext.FOutboundClient).Host := LURI.Host;
|
|
|
|
if LURI.Port <> '' then begin
|
|
TIdTCPClient(LContext.FOutboundClient).Port := IndyStrToInt(LURI.Port, 80);
|
|
end
|
|
else if TextIsSame(LURI.Protocol, 'http') then begin {do not localize}
|
|
TIdTCPClient(LContext.FOutboundClient).Port := IdPORT_HTTP;
|
|
end
|
|
else if TextIsSame(LURI.Protocol, 'https') then begin {do not localize}
|
|
TIdTCPClient(LContext.FOutboundClient).Port := IdPORT_https;
|
|
end else begin
|
|
raise EIdException.Create(RSHTTPUnknownProtocol);
|
|
end;
|
|
|
|
//We have to remove the host and port from the request
|
|
LContext.FDocument := LURI.GetPathAndParams;
|
|
finally
|
|
FreeAndNil(LURI);
|
|
end;
|
|
|
|
LContext.Headers.Clear;
|
|
LContext.Connection.IOHandler.Capture(LContext.Headers, '', False);
|
|
LContext.FTransferMode := FDefTransferMode;
|
|
LContext.FTransferSource := tsClient;
|
|
DoHTTPBeforeCommand(LContext);
|
|
|
|
TIdTCPClient(LContext.FOutboundClient).Connect;
|
|
try
|
|
TransferData(LContext, LContext.Connection, LContext.FOutboundClient);
|
|
|
|
LContext.Headers.Clear;
|
|
LContext.FOutboundClient.IOHandler.Capture(LContext.Headers, '', False);
|
|
LContext.FTransferMode := FDefTransferMode;
|
|
LContext.FTransferSource := tsServer;
|
|
DoHTTPResponse(LContext);
|
|
TransferData(LContext, LContext.FOutboundClient, LContext.Connection);
|
|
finally
|
|
LContext.FOutboundClient.Disconnect;
|
|
end;
|
|
finally
|
|
FreeAndNil(LContext.FOutboundClient);
|
|
end;
|
|
end;
|
|
|
|
procedure TIdHTTPProxyServer.CommandCONNECT(ASender: TIdCommand);
|
|
var
|
|
LRemoteHost: string;
|
|
LContext: TIdHTTPProxyServerContext;
|
|
LReadList, LDataAvailList: TIdSocketList;
|
|
LClientToServerStream, LServerToClientStream: TStream;
|
|
begin
|
|
// RLebeau 7/31/09: we can't make any assumptions about the contents of
|
|
// the data being exchanged after the connection has been established.
|
|
// It may not (and likely will not) be HTTP data at all. We must pass
|
|
// it along as-is in both directions, in as near-realtime as we can...
|
|
|
|
ASender.PerformReply := False;
|
|
|
|
LContext := TIdHTTPProxyServerContext(ASender.Context);
|
|
LContext.FCommand := ASender.CommandHandler.Command;
|
|
LContext.FTarget := ASender.Params.Strings[0];
|
|
|
|
LContext.FOutboundClient := TIdTCPClient.Create(nil);
|
|
try
|
|
LClientToServerStream := nil;
|
|
LServerToClientStream := nil;
|
|
try
|
|
LClientToServerStream := TIdTCPStream.Create(LContext.FOutboundClient);
|
|
LServerToClientStream := TIdTCPStream.Create(LContext.Connection);
|
|
|
|
LRemoteHost := LContext.Target;
|
|
TIdTCPClient(LContext.FOutboundClient).Host := Fetch(LRemoteHost, ':', True);
|
|
TIdTCPClient(LContext.FOutboundClient).Port := IndyStrToInt(LRemoteHost, 443);
|
|
|
|
LContext.Headers.Clear;
|
|
LContext.Connection.IOHandler.Capture(LContext.Headers, '', False);
|
|
LContext.FTransferMode := FDefTransferMode;
|
|
LContext.FTransferSource := tsClient;
|
|
DoHTTPBeforeCommand(LContext);
|
|
|
|
LReadList := nil;
|
|
LDataAvailList := nil;
|
|
try
|
|
LReadList := TIdSocketList.CreateSocketList;
|
|
LDataAvailList := TIdSocketList.CreateSocketList;
|
|
|
|
TIdTCPClient(LContext.FOutboundClient).Connect;
|
|
try
|
|
LReadList.Add(LContext.Connection.Socket.Binding.Handle);
|
|
LReadList.Add(LContext.FOutboundClient.Socket.Binding.Handle);
|
|
|
|
LContext.Connection.IOHandler.WriteLn('HTTP/1.0 200 Connection established'); {do not localize}
|
|
LContext.Connection.IOHandler.WriteLn('Proxy-agent: Indy-Proxy/1.1'); {do not localize}
|
|
LContext.Connection.IOHandler.WriteLn;
|
|
|
|
LContext.Connection.IOHandler.ReadTimeout := 100;
|
|
LContext.FOutboundClient.IOHandler.ReadTimeout := 100;
|
|
|
|
while LContext.Connection.Connected and LContext.FOutboundClient.Connected do
|
|
begin
|
|
if LReadList.SelectReadList(LDataAvailList, IdTimeoutInfinite) then
|
|
begin
|
|
if LDataAvailList.ContainsSocket(LContext.Connection.Socket.Binding.Handle) then
|
|
begin
|
|
LContext.Connection.IOHandler.CheckForDataOnSource(0);
|
|
end;
|
|
if LDataAvailList.ContainsSocket(LContext.FOutboundClient.Socket.Binding.Handle) then
|
|
begin
|
|
LContext.FOutboundClient.IOHandler.CheckForDataOnSource(0);
|
|
end;
|
|
|
|
if not LContext.Connection.IOHandler.InputBufferIsEmpty then
|
|
begin
|
|
LContext.Connection.IOHandler.InputBuffer.ExtractToStream(LClientToServerStream);
|
|
end;
|
|
if not LContext.FOutboundClient.IOHandler.InputBufferIsEmpty then
|
|
begin
|
|
LContext.FOutboundClient.IOHandler.InputBuffer.ExtractToStream(LServerToClientStream);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if LContext.FOutboundClient.Connected and (not LContext.Connection.IOHandler.InputBufferIsEmpty) then
|
|
begin
|
|
LContext.Connection.IOHandler.InputBuffer.ExtractToStream(LClientToServerStream);
|
|
end;
|
|
if LContext.Connection.Connected and (not LContext.FOutboundClient.IOHandler.InputBufferIsEmpty) then
|
|
begin
|
|
LContext.FOutboundClient.IOHandler.InputBuffer.ExtractToStream(LServerToClientStream);
|
|
end;
|
|
finally
|
|
LContext.FOutboundClient.Disconnect;
|
|
end;
|
|
finally
|
|
FreeAndNil(LDataAvailList);
|
|
FreeAndNil(LReadList);
|
|
end;
|
|
finally
|
|
FreeAndNil(LClientToServerStream);
|
|
FreeAndNil(LServerToClientStream);
|
|
end;
|
|
finally
|
|
FreeAndNil(LContext.FOutboundClient);
|
|
end;
|
|
end;
|
|
|
|
procedure TIdHTTPProxyServer.InitComponent;
|
|
begin
|
|
inherited InitComponent;
|
|
ContextClass := TIdHTTPProxyServerContext;
|
|
DefaultPort := IdPORT_HTTPProxy;
|
|
FDefTransferMode := tmFullDocument;
|
|
Greeting.Text.Text := ''; // RS
|
|
ReplyUnknownCommand.Text.Text := ''; // RS
|
|
end;
|
|
|
|
procedure TIdHTTPProxyServer.DoHTTPBeforeCommand(AContext: TIdHTTPProxyServerContext);
|
|
begin
|
|
if Assigned(OnHTTPBeforeCommand) then begin
|
|
OnHTTPBeforeCommand(AContext);
|
|
end;
|
|
end;
|
|
|
|
procedure TIdHTTPProxyServer.DoHTTPDocument(AContext: TIdHTTPProxyServerContext;
|
|
var VStream: TStream);
|
|
begin
|
|
if Assigned(OnHTTPDocument) then begin
|
|
OnHTTPDocument(AContext, VStream);
|
|
end;
|
|
end;
|
|
|
|
procedure TIdHTTPProxyServer.DoHTTPResponse(AContext: TIdHTTPProxyServerContext);
|
|
begin
|
|
if Assigned(OnHTTPResponse) then begin
|
|
OnHTTPResponse(AContext);
|
|
end;
|
|
end;
|
|
|
|
end.
|
|
|
|
|