IdHTTPProxyServer.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.24 10/14/2004 1:45:32 PM BGooijen
  18. Beauty fixes ;)
  19. Rev 1.23 10/14/2004 1:05:48 PM BGooijen
  20. set PerformReply to false, else "200 OK" was added behind the document body
  21. Rev 1.22 09.08.2004 09:30:00 OMonien
  22. changed disconnect handling. Previous implementation failed when exceptions
  23. ocured in command handler.
  24. Rev 1.21 08.08.2004 10:35:56 OMonien
  25. Greeting removed
  26. Rev 1.20 6/11/2004 9:36:28 AM DSiders
  27. Added "Do not Localize" comments.
  28. Rev 1.19 2004.05.20 1:39:24 PM czhower
  29. Last of the IdStream updates
  30. Rev 1.18 2004.05.20 11:37:20 AM czhower
  31. IdStreamVCL
  32. Rev 1.17 4/19/2004 7:07:38 PM BGooijen
  33. the remote headers are now passed to the OnHTTPDocument event
  34. Rev 1.16 4/18/2004 11:31:26 PM BGooijen
  35. Fixed POST
  36. Build CONNECT
  37. fixed some bugs where chars were replaced when that was not needed ( thus
  38. causing corrupt data )
  39. Rev 1.15 2004.04.13 10:24:24 PM czhower
  40. Bug fix for when user changes stream.
  41. Rev 1.14 2004.02.03 5:45:12 PM czhower
  42. Name changes
  43. Rev 1.13 1/21/2004 2:42:52 PM JPMugaas
  44. InitComponent
  45. Rev 1.12 10/25/2003 06:52:12 AM JPMugaas
  46. Updated for new API changes and tried to restore some functionality.
  47. Rev 1.11 2003.10.24 10:43:10 AM czhower
  48. TIdSTream to dos
  49. Rev 1.10 10/17/2003 12:10:08 AM DSiders
  50. Added localization comments.
  51. Rev 1.9 2003.10.12 3:50:44 PM czhower
  52. Compile todos
  53. Rev 1.8 7/13/2003 7:57:38 PM SPerry
  54. fixed problem with commandhandlers
  55. Rev 1.6 5/25/2003 03:54:42 AM JPMugaas
  56. Rev 1.5 2/24/2003 08:56:50 PM JPMugaas
  57. Rev 1.4 1/20/2003 1:15:44 PM BGooijen
  58. Changed to TIdTCPServer / TIdCmdTCPServer classes
  59. Rev 1.3 1-14-2003 19:19:22 BGooijen
  60. The first line of the header was sent to the server twice, fixed that.
  61. Rev 1.2 1-1-2003 21:52:06 BGooijen
  62. Changed for TIdContext
  63. Rev 1.1 12-29-2002 13:00:02 BGooijen
  64. - Works on Indy 10 now
  65. - Cleaned up some code
  66. Rev 1.0 2002.11.22 8:37:50 PM czhower
  67. Rev 1.0 2002.11.22 8:37:16 PM czhower
  68. 10-May-2002: Created Unit.
  69. }
  70. unit IdHTTPProxyServer;
  71. interface
  72. {$i IdCompilerDefines.inc}
  73. {
  74. Indy HTTP proxy Server
  75. Original Programmer: Bas Gooijen ([email protected])
  76. Current Maintainer: Bas Gooijen
  77. Code is given to the Indy Pit Crew.
  78. Modifications by Chad Z. Hower (Kudzu)
  79. }
  80. uses
  81. Classes,
  82. IdAssignedNumbers,
  83. IdGlobal,
  84. IdHeaderList,
  85. IdTCPConnection,
  86. IdCustomTCPServer, //for TIdServerContext
  87. IdCmdTCPServer,
  88. IdCommandHandlers,
  89. IdContext,
  90. IdYarn;
  91. const
  92. IdPORT_HTTPProxy = 8080;
  93. type
  94. TIdHTTPProxyTransferMode = ( tmFullDocument, tmStreaming );
  95. TIdHTTPProxyTransferSource = ( tsClient, tsServer );
  96. TIdHTTPProxyServerContext = class(TIdServerContext)
  97. protected
  98. FHeaders: TIdHeaderList;
  99. FCommand: String;
  100. FDocument: String;
  101. FOutboundClient: TIdTCPConnection;
  102. FTarget: String;
  103. FTransferMode: TIdHTTPProxyTransferMode;
  104. FTransferSource: TIdHTTPProxyTransferSource;
  105. public
  106. constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
  107. destructor Destroy; override;
  108. property Headers: TIdHeaderList read FHeaders;
  109. property Command: String read FCommand;
  110. property Document: String read FDocument;
  111. property OutboundClient: TIdTCPConnection read FOutboundClient;
  112. property Target: String read FTarget;
  113. property TransferMode: TIdHTTPProxyTransferMode read FTransferMode write FTransferMode;
  114. property TransferSource: TIdHTTPProxyTransferSource read FTransferSource;
  115. end;
  116. TIdHTTPProxyServer = class;
  117. TOnHTTPContextEvent = procedure(AContext: TIdHTTPProxyServerContext) of object;
  118. TOnHTTPDocument = procedure(AContext: TIdHTTPProxyServerContext; var VStream: TStream) of object;
  119. TIdHTTPProxyServer = class(TIdCmdTCPServer)
  120. protected
  121. FDefTransferMode: TIdHTTPProxyTransferMode;
  122. FOnHTTPBeforeCommand: TOnHTTPContextEvent;
  123. FOnHTTPResponse: TOnHTTPContextEvent;
  124. FOnHTTPDocument: TOnHTTPDocument;
  125. // CommandHandlers
  126. procedure CommandPassThrough(ASender: TIdCommand);
  127. procedure CommandCONNECT(ASender: TIdCommand); // for ssl
  128. procedure DoHTTPBeforeCommand(AContext: TIdHTTPProxyServerContext);
  129. procedure DoHTTPDocument(AContext: TIdHTTPProxyServerContext; var VStream: TStream);
  130. procedure DoHTTPResponse(AContext: TIdHTTPProxyServerContext);
  131. procedure InitializeCommandHandlers; override;
  132. procedure TransferData(AContext: TIdHTTPProxyServerContext; ASrc, ADest: TIdTCPConnection);
  133. procedure InitComponent; override;
  134. published
  135. property DefaultPort default IdPORT_HTTPProxy;
  136. property DefaultTransferMode: TIdHTTPProxyTransferMode read FDefTransferMode write FDefTransferMode default tmFullDocument;
  137. property OnHTTPBeforeCommand: TOnHTTPContextEvent read FOnHTTPBeforeCommand write FOnHTTPBeforeCommand;
  138. property OnHTTPResponse: TOnHTTPContextEvent read FOnHTTPResponse write FOnHTTPResponse;
  139. property OnHTTPDocument: TOnHTTPDocument read FOnHTTPDocument write FOnHTTPDocument;
  140. end;
  141. implementation
  142. uses
  143. IdResourceStringsProtocols, IdIOHandler, IdTCPClient,
  144. IdURI, IdGlobalProtocols, IdStack, IdStackConsts, IdTCPStream, IdException, SysUtils;
  145. constructor TIdHTTPProxyServerContext.Create(AConnection: TIdTCPConnection;
  146. AYarn: TIdYarn; AList: TIdContextThreadList = nil);
  147. begin
  148. inherited Create(AConnection, AYarn, AList);
  149. FHeaders := TIdHeaderList.Create(QuoteHTTP);
  150. end;
  151. destructor TIdHTTPProxyServerContext.Destroy;
  152. begin
  153. FreeAndNil(FHeaders);
  154. inherited Destroy;
  155. end;
  156. { TIdHTTPProxyServer }
  157. procedure TIdHTTPProxyServer.InitializeCommandHandlers;
  158. var
  159. LCommandHandler: TIdCommandHandler;
  160. begin
  161. inherited;
  162. LCommandHandler := CommandHandlers.Add;
  163. LCommandHandler.Command := 'GET'; {do not localize}
  164. LCommandHandler.OnCommand := CommandPassThrough;
  165. LCommandHandler.ParseParams := True;
  166. LCommandHandler.Disconnect := True;
  167. LCommandHandler := CommandHandlers.Add;
  168. LCommandHandler.Command := 'POST'; {do not localize}
  169. LCommandHandler.OnCommand := CommandPassThrough;
  170. LCommandHandler.ParseParams := True;
  171. LCommandHandler.Disconnect := True;
  172. LCommandHandler := CommandHandlers.Add;
  173. LCommandHandler.Command := 'HEAD'; {do not localize}
  174. LCommandHandler.OnCommand := CommandPassThrough;
  175. LCommandHandler.ParseParams := True;
  176. LCommandHandler.Disconnect := True;
  177. LCommandHandler := CommandHandlers.Add;
  178. LCommandHandler.Command := 'CONNECT'; {do not localize}
  179. LCommandHandler.OnCommand := CommandCONNECT;
  180. LCommandHandler.ParseParams := True;
  181. LCommandHandler.Disconnect := True;
  182. //HTTP Servers/Proxies do not send a greeting
  183. Greeting.Clear;
  184. end;
  185. procedure TIdHTTPProxyServer.TransferData(AContext: TIdHTTPProxyServerContext;
  186. ASrc, ADest: TIdTCPConnection);
  187. var
  188. LStream: TStream;
  189. LSize: TIdStreamSize;
  190. S: String;
  191. begin
  192. // RLebeau: TODO - support chunked, gzip, and deflate transfers.
  193. // RLebeau: determine how many bytes to read
  194. S := AContext.Headers.Values['Content-Length']; {Do not Localize}
  195. if S <> '' then
  196. begin
  197. LSize := IndyStrToStreamSize(S, -1) ; {Do not Localize}
  198. if LSize < 0 then begin
  199. // Write HTTP error status response
  200. if AContext.TransferSource = tsClient then begin
  201. ASrc.IOHandler.WriteLn('HTTP/1.0 400 Bad Request'); {Do not Localize}
  202. end else begin
  203. ASrc.IOHandler.WriteLn('HTTP/1.0 502 Bad Gateway'); {Do not Localize}
  204. end;
  205. ASrc.IOHandler.WriteLn;
  206. Exit;
  207. end;
  208. end else begin
  209. LSize := -1;
  210. end;
  211. if AContext.TransferSource = tsClient then begin
  212. ADest.IOHandler.WriteLn(AContext.Command + ' ' + AContext.Document + ' HTTP/1.0'); {Do not Localize}
  213. end;
  214. if (AContext.TransferSource = tsServer) or (LSize > 0) then
  215. begin
  216. LStream := nil;
  217. try
  218. if AContext.TransferMode = tmFullDocument then
  219. begin
  220. //TODO: Have an event to let the user perform stream creation
  221. LStream := TMemoryStream.Create;
  222. // RLebeau: do not write the source headers until the OnHTTPDocument
  223. // event has had a chance to update them if it alters the document data...
  224. ASrc.IOHandler.ReadStream(LStream, LSize, LSize < 0);
  225. LStream.Position := 0;
  226. DoHTTPDocument(AContext, LStream);
  227. ADest.IOHandler.Write(AContext.Headers);
  228. ADest.IOHandler.WriteLn;
  229. ADest.IOHandler.Write(LStream);
  230. end else
  231. begin
  232. // RLebeau: direct pass-through, send everything as-is...
  233. LStream := TIdTCPStream.Create(ADest);
  234. ADest.IOHandler.Write(AContext.Headers);
  235. ADest.IOHandler.WriteLn;
  236. ASrc.IOHandler.ReadStream(LStream, LSize, LSize < 0);
  237. end;
  238. finally
  239. FreeAndNil(LStream);
  240. end;
  241. end else
  242. begin
  243. // RLebeau: the client sent a document with no data in it, so just pass
  244. // along the headers by themselves ...
  245. ADest.IOHandler.Write(AContext.Headers);
  246. ADest.IOHandler.WriteLn;
  247. end;
  248. end;
  249. procedure TIdHTTPProxyServer.CommandPassThrough(ASender: TIdCommand);
  250. var
  251. LURI: TIdURI;
  252. LContext: TIdHTTPProxyServerContext;
  253. LConnection: string;
  254. function IsVersionAtLeast11(const AVersionStr: string): Boolean;
  255. var
  256. s: string;
  257. LMajor, LMinor: Integer;
  258. begin
  259. s := AVersionStr;
  260. Fetch(s, '/'); {Do not localize}
  261. LMajor := IndyStrToInt(Fetch(s, '.'), -1); {Do not Localize}
  262. LMinor := IndyStrToInt(S, -1);
  263. Result := (LMajor > 1) or ((LMajor = 1) and (LMinor >= 1));
  264. end;
  265. begin
  266. ASender.PerformReply := False;
  267. LContext := TIdHTTPProxyServerContext(ASender.Context);
  268. LContext.FCommand := ASender.CommandHandler.Command;
  269. LContext.FTarget := ASender.Params.Strings[0];
  270. LContext.FOutboundClient := TIdTCPClient.Create(nil);
  271. try
  272. LURI := TIdURI.Create(LContext.Target);
  273. try
  274. TIdTCPClient(LContext.FOutboundClient).Host := LURI.Host;
  275. if LURI.Port <> '' then begin
  276. TIdTCPClient(LContext.FOutboundClient).Port := IndyStrToInt(LURI.Port, 80);
  277. end
  278. else if TextIsSame(LURI.Protocol, 'http') then begin {do not localize}
  279. TIdTCPClient(LContext.FOutboundClient).Port := IdPORT_HTTP;
  280. end
  281. else if TextIsSame(LURI.Protocol, 'https') then begin {do not localize}
  282. TIdTCPClient(LContext.FOutboundClient).Port := IdPORT_https;
  283. end else begin
  284. raise EIdException.Create(RSHTTPUnknownProtocol); // TODO: create a new Exception class for this
  285. end;
  286. //We have to remove the host and port from the request
  287. LContext.FDocument := LURI.GetPathAndParams;
  288. finally
  289. FreeAndNil(LURI);
  290. end;
  291. LContext.Headers.Clear;
  292. LContext.Connection.IOHandler.Capture(LContext.Headers, '', False);
  293. LContext.FTransferMode := FDefTransferMode;
  294. LContext.FTransferSource := tsClient;
  295. DoHTTPBeforeCommand(LContext);
  296. LConnection := LContext.Headers.Values['Proxy-Connection']; {do not localize}
  297. if LConnection <> '' then begin
  298. ASender.Disconnect := TextIsSame(LConnection, 'close'); {do not localize}
  299. end else begin
  300. LConnection := LContext.Headers.Values['Connection']; {do not localize}
  301. if IsVersionAtLeast11(ASender.Params.Strings[1]) then begin
  302. ASender.Disconnect := TextIsSame(LConnection, 'close'); {do not localize}
  303. end else begin
  304. ASender.Disconnect := not TextIsSame(LConnection, 'keep-alive'); {do not localize}
  305. end;
  306. end;
  307. // TODO: If the client requests a keep-alive with the target server, don't disconnect the
  308. // TIdTCPClient below, so it can be reused for subsequent requests. Disconnect it only
  309. // when the requesting client disconnects, the keep-alive times out, or a different
  310. // host/port is requested...
  311. TIdTCPClient(LContext.FOutboundClient).Connect;
  312. try
  313. // TODO: if FDefTransferMode is tmStreaming, send the request and receive the response
  314. // in parallel, similar to how CommandCONNECT() does. This would also facilitate the
  315. // server being able to send back an error reponse while the client is still sending
  316. // its request...
  317. LContext.Headers.Values['Connection'] := 'close'; {do not localize}
  318. TransferData(LContext, LContext.Connection, LContext.FOutboundClient);
  319. LContext.Headers.Clear;
  320. LContext.FOutboundClient.IOHandler.Capture(LContext.Headers, '', False);
  321. LContext.FTransferMode := FDefTransferMode;
  322. LContext.FTransferSource := tsServer;
  323. DoHTTPResponse(LContext);
  324. LContext.Headers.Values['Proxy-Connection'] := iif(ASender.Disconnect, 'close', 'keep-alive'); {do not localize}
  325. TransferData(LContext, LContext.FOutboundClient, LContext.Connection);
  326. finally
  327. LContext.FOutboundClient.Disconnect;
  328. end;
  329. finally
  330. FreeAndNil(LContext.FOutboundClient);
  331. end;
  332. end;
  333. procedure TIdHTTPProxyServer.CommandCONNECT(ASender: TIdCommand);
  334. var
  335. LRemoteHost: string;
  336. LContext: TIdHTTPProxyServerContext;
  337. LReadList, LDataAvailList: TIdSocketList;
  338. LClientToServerStream, LServerToClientStream: TStream;
  339. LConnectionHandle, LOutBoundHandle: TIdStackSocketHandle;
  340. LConnectionIO, LOutboundIO: TIdIOHandler;
  341. procedure CheckForData(DoRead: Boolean);
  342. begin
  343. if DoRead and LConnectionIO.InputBufferIsEmpty and LOutboundIO.InputBufferIsEmpty then
  344. begin
  345. if LReadList.SelectReadList(LDataAvailList, IdTimeoutInfinite) then
  346. begin
  347. if LDataAvailList.ContainsSocket(LConnectionHandle) then
  348. begin
  349. LConnectionIO.CheckForDataOnSource(0);
  350. end;
  351. if LDataAvailList.ContainsSocket(LOutBoundHandle) then
  352. begin
  353. LOutboundIO.CheckForDataOnSource(0);
  354. end;
  355. end;
  356. end;
  357. if not LConnectionIO.InputBufferIsEmpty then
  358. begin
  359. LConnectionIO.InputBuffer.ExtractToStream(LClientToServerStream);
  360. end;
  361. if not LOutboundIO.InputBufferIsEmpty then
  362. begin
  363. LOutboundIO.InputBuffer.ExtractToStream(LServerToClientStream);
  364. end;
  365. LConnectionIO.CheckForDisconnect;
  366. LOutboundIO.CheckForDisconnect;
  367. end;
  368. begin
  369. // RLebeau 7/31/09: we can't make any assumptions about the contents of
  370. // the data being exchanged after the connection has been established.
  371. // It may not (and likely will not) be HTTP data at all. We must pass
  372. // it along as-is in both directions, in as near-realtime as we can...
  373. ASender.PerformReply := False;
  374. LContext := TIdHTTPProxyServerContext(ASender.Context);
  375. LContext.FCommand := ASender.CommandHandler.Command;
  376. LContext.FTarget := ASender.Params.Strings[0];
  377. LContext.FOutboundClient := TIdTCPClient.Create(nil);
  378. try
  379. LClientToServerStream := TIdTCPStream.Create(LContext.FOutboundClient);
  380. try
  381. LServerToClientStream := TIdTCPStream.Create(LContext.Connection);
  382. try
  383. LRemoteHost := LContext.Target;
  384. TIdTCPClient(LContext.FOutboundClient).Host := Fetch(LRemoteHost, ':', True);
  385. TIdTCPClient(LContext.FOutboundClient).Port := IndyStrToInt(LRemoteHost, 443);
  386. LConnectionIO := LContext.Connection.IOHandler;
  387. LContext.Headers.Clear;
  388. LConnectionIO.Capture(LContext.Headers, '', False);
  389. LContext.FTransferMode := FDefTransferMode; // TODO: should this be forced to tmStreaming instead?
  390. LContext.FTransferSource := tsClient;
  391. DoHTTPBeforeCommand(LContext);
  392. TIdTCPClient(LContext.FOutboundClient).Connect;
  393. try
  394. LOutboundIO := LContext.FOutboundClient.IOHandler;
  395. LConnectionHandle := LContext.Binding.Handle;
  396. LOutBoundHandle := LContext.FOutboundClient.Socket.Binding.Handle;
  397. LReadList := TIdSocketList.CreateSocketList;
  398. try
  399. LReadList.Add(LConnectionHandle);
  400. LReadList.Add(LOutBoundHandle);
  401. LDataAvailList := TIdSocketList.CreateSocketList;
  402. try
  403. LConnectionIO.WriteLn('HTTP/1.0 200 Connection established'); {do not localize}
  404. LConnectionIO.WriteLn('Proxy-agent: Indy-Proxy/1.1'); {do not localize}
  405. LConnectionIO.WriteLn;
  406. CheckForData(False);
  407. while LContext.Connection.Connected and LContext.FOutboundClient.Connected do
  408. begin
  409. CheckForData(True);
  410. end;
  411. if LContext.FOutboundClient.Connected and (not LConnectionIO.InputBufferIsEmpty) then
  412. begin
  413. LConnectionIO.InputBuffer.ExtractToStream(LClientToServerStream);
  414. end;
  415. if LContext.Connection.Connected and (not LOutboundIO.InputBufferIsEmpty) then
  416. begin
  417. LOutboundIO.InputBuffer.ExtractToStream(LServerToClientStream);
  418. end;
  419. finally
  420. FreeAndNil(LDataAvailList);
  421. end;
  422. finally
  423. FreeAndNil(LReadList);
  424. end;
  425. finally
  426. LContext.FOutboundClient.Disconnect;
  427. LOutboundIO := nil;
  428. end;
  429. finally
  430. FreeAndNil(LServerToClientStream);
  431. LConnectionIO := nil;
  432. end;
  433. finally
  434. FreeAndNil(LClientToServerStream);
  435. end;
  436. finally
  437. FreeAndNil(LContext.FOutboundClient);
  438. end;
  439. end;
  440. procedure TIdHTTPProxyServer.InitComponent;
  441. begin
  442. inherited InitComponent;
  443. ContextClass := TIdHTTPProxyServerContext;
  444. DefaultPort := IdPORT_HTTPProxy;
  445. FDefTransferMode := tmFullDocument;
  446. Greeting.Text.Text := ''; // RS
  447. ReplyUnknownCommand.Text.Text := ''; // RS
  448. end;
  449. procedure TIdHTTPProxyServer.DoHTTPBeforeCommand(AContext: TIdHTTPProxyServerContext);
  450. begin
  451. if Assigned(OnHTTPBeforeCommand) then begin
  452. OnHTTPBeforeCommand(AContext);
  453. end;
  454. end;
  455. procedure TIdHTTPProxyServer.DoHTTPDocument(AContext: TIdHTTPProxyServerContext;
  456. var VStream: TStream);
  457. begin
  458. if Assigned(OnHTTPDocument) then begin
  459. OnHTTPDocument(AContext, VStream);
  460. end;
  461. end;
  462. procedure TIdHTTPProxyServer.DoHTTPResponse(AContext: TIdHTTPProxyServerContext);
  463. begin
  464. if Assigned(OnHTTPResponse) then begin
  465. OnHTTPResponse(AContext);
  466. end;
  467. end;
  468. end.