{ $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.