TcpIp.pas 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. unit TcpIp;
  2. interface
  3. {$WARN SYMBOL_DEPRECATED OFF}
  4. { Based on TCP/IP component V0.2 }
  5. { Copyright 1997 Andreas Hörstemeier Version 0.22 2001-07-09 }
  6. { http://www.hoerstemeier.com/tcpip.htm }
  7. {$x+}
  8. uses
  9. Sysutils,
  10. Classes,
  11. Winsock,
  12. Windows;
  13. type
  14. TSocketState = (invalid, valid, connected, state_unknown);
  15. TTraceLevel = (tt_proto_sent, tt_proto_get, tt_socket);
  16. ETcpIpError = class(Exception);
  17. ESocketError = class(ETcpIpError)
  18. ErrorNumber: Cardinal;
  19. constructor Create(Number: Cardinal);
  20. end;
  21. EProtocolError = class(ETcpIpError)
  22. ErrorNumber: Word;
  23. Protocol: string;
  24. constructor Create(const Proto, Msg: string; number: Word);
  25. end;
  26. TTraceProc = procedure(const S: string; Level: TTraceLevel) of object;
  27. TTcpIp = class(TComponent)
  28. protected
  29. FSocket: TSocket;
  30. FHostname: string;
  31. FTracer: TTraceProc;
  32. FSocketNumber: SmallInt;
  33. IpAddress: LongInt; // Network order!
  34. FEof: Boolean;
  35. FStream: TStream;
  36. FBuffer: Pointer;
  37. FLoggedIn: Boolean;
  38. function CreateSocket: TSocket;
  39. procedure ConnectSocket(var Socket: TSocket; SocketNumber: SmallInt;
  40. IpAddress: LongInt);
  41. procedure BindSocket(var Socket: TSocket; OutPortMin, OutPortMax: Word);
  42. procedure OpenSocketOut(var Socket: TSocket; SocketNumber: SmallInt;
  43. IpAddress: LongInt); virtual;
  44. procedure OpenSocketIn(var Socket: TSocket; var SocketNumber: SmallInt;
  45. IpAddress: LongInt);
  46. procedure CloseSocket(var Socket: TSocket);
  47. function AcceptSocketIn(Socket: TSocket; var SockInfo: TSockAddr): TSocket;
  48. function SocketState(Socket: TSocket): TSocketState;
  49. function SocketByName(const Service: string): SmallInt;
  50. function ReadLine(Socket: TSocket): string;
  51. procedure ReadVar(Socket: TSocket; var Buf; Size: Integer; var Ok: Integer);
  52. procedure WriteBuf(Socket: TSocket; const Buf; var Size: Integer);
  53. procedure WriteStr(Socket: TSocket; const S: string);
  54. procedure SetStream(Value: TStream);
  55. procedure Action; virtual;
  56. procedure SendCommand(const S: string); virtual;
  57. public
  58. procedure Login; virtual;
  59. procedure Logout; virtual;
  60. property OnTrace: TTraceProc read FTracer write FTracer;
  61. constructor Create(AOwner: TComponent); override;
  62. destructor Destroy; override;
  63. function Eof(Socket: TSocket): Boolean; virtual;
  64. end;
  65. THttp = class(TTcpIp)
  66. protected
  67. FUrl: string;
  68. FPath: string;
  69. FProxy: string;
  70. FSender: string;
  71. FReference: string;
  72. FAgent: string;
  73. FNoCache: boolean;
  74. FStatusNr: Integer;
  75. FStatusTxt: string;
  76. FSize: Integer;
  77. FType: string;
  78. FDoAuthor: TStringList;
  79. FContentPost: string;
  80. FRedirect: Boolean;
  81. procedure GetBody(Post: Boolean);
  82. procedure SendRequest(const Method, Version: string);
  83. procedure GetAnswer;
  84. procedure ReportStatusError;
  85. public
  86. property Stream: TStream read FStream write SetStream;
  87. property ContentSize: Integer read FSize;
  88. property ContentType: string read FType;
  89. property StatusNumber: Integer read FStatusNr;
  90. property StatusText: string read FStatusTxt;
  91. procedure Action; override;
  92. procedure Post;
  93. constructor Create(AOwner: TComponent); override;
  94. destructor Destroy; override;
  95. property AuthorizationRequest: TStringList read FDoAuthor;
  96. published
  97. property URL: string read FUrl write FUrl;
  98. property Proxy: string read FProxy write FProxy;
  99. property Sender: string read FSender write FSender;
  100. property Agent: string read FAgent write FAgent;
  101. property Reference: string read FReference write FReference;
  102. property NoCache: Boolean read FNoCache write FNoCache;
  103. property ContentTypePost: string read FContentPost write FContentPost;
  104. property OnTrace;
  105. end;
  106. procedure Register;
  107. resourcestring
  108. SSocketError2 = 'Socket error (%s)';
  109. STimeout = 'Timeout';
  110. SUnknownSockError = 'Unknown error';
  111. SHttpError = 'Received response %d %s from %s';
  112. SRedirectLimitError = 'Exceeded maximal redirect limie %d';
  113. implementation
  114. const
  115. BackLog = 2; // possible values 1..5
  116. BufSize = $7F00; // size of the internal standard buffer
  117. MaxRedirects = 4;
  118. INVALID_IP_ADDRESS= -1; // only invalid as a host ip, maybe OK for broadcast ($FFFFFFFF as longint)
  119. function LookupHostname(const Hostname: string): LongInt;
  120. var
  121. RemoteHost: PHostEnt; // no, don't free it!
  122. IpAddress: LongInt;
  123. begin
  124. IpAddress:=INVALID_IP_ADDRESS;
  125. try
  126. if Hostname='' then
  127. begin
  128. Result := IpAddress;
  129. Exit;
  130. end
  131. else
  132. begin
  133. // try a xxx.xxx.xxx.xx first
  134. IpAddress := Winsock.Inet_Addr(PAnsiChar(AnsiString(Hostname)));
  135. if IpAddress = SOCKET_ERROR then
  136. begin
  137. RemoteHost := Winsock.GetHostByName(PAnsiChar(AnsiString(Hostname)));
  138. if (RemoteHost = nil) or (RemoteHost^.h_length <= 0) then
  139. begin
  140. Result := IpAddress;
  141. Exit; // host not found
  142. end
  143. else
  144. begin
  145. IpAddress := LongInt(Pointer(RemoteHost^.h_addr_list^)^);
  146. end;
  147. // use the first address given
  148. end;
  149. end;
  150. except
  151. IpAddress := INVALID_IP_ADDRESS;
  152. end;
  153. Result := IpAddress;
  154. end;
  155. function Ip2String(IpAddress: LongInt): string;
  156. begin
  157. IpAddress := winsock.ntohl(IpAddress);
  158. Result :=
  159. IntToStr(IpAddress shr 24)+'.'+
  160. IntToStr((IpAddress shr 16) and $FF)+'.'+
  161. IntToStr((IpAddress shr 8) and $FF)+'.'+
  162. IntToStr(IpAddress and $FF);
  163. end;
  164. // find the count'th occurence of the substring,
  165. // if count<0 then look from the back
  166. function PosN(const s, t: string; Count: Integer): Integer;
  167. var
  168. i, h, last: Integer;
  169. u: string;
  170. begin
  171. u := t;
  172. if Count > 0 then
  173. begin
  174. Result := length(t);
  175. for i := 1 to Count do
  176. begin
  177. h := Pos(s, u);
  178. if h > 0 then
  179. u := Copy(u, Pos(s, u) + 1, length(u))
  180. else
  181. begin
  182. u := '';
  183. Inc(Result);
  184. end;
  185. end;
  186. Result := Result - Length(u);
  187. end
  188. else
  189. if Count < 0 then
  190. begin
  191. last := 0;
  192. for i := Length(t) downto 1 do
  193. begin
  194. u := Copy(t, i, Length(t));
  195. h := Pos(s, u);
  196. if (h <> 0) and (h + i <> last) then
  197. begin
  198. last := h + i - 1;
  199. Inc(Count);
  200. if Count = 0 then Break;
  201. end;
  202. end;
  203. if Count = 0 then Result := last
  204. else Result := 0;
  205. end
  206. else
  207. Result := 0;
  208. end;
  209. constructor EProtocolError.Create(const Proto, Msg: string; Number: Word);
  210. begin
  211. inherited Create(Msg);
  212. Protocol := Proto;
  213. ErrorNumber := Number;
  214. end;
  215. constructor ESocketError.Create(Number: Cardinal);
  216. const
  217. UnknownSuccessError = $1BD0000;
  218. var
  219. SysError: string;
  220. begin
  221. if Number = UnknownSuccessError then SysError := SUnknownSockError
  222. else SysError := SysErrorMessage(Number);
  223. inherited Create(Format(SSocketError2, [SysError]));
  224. ErrorNumber := Number;
  225. end;
  226. // standard syntax of an URL:
  227. // protocol://[user[:password]@]server[:port]/path
  228. procedure ParseUrl(const Url: string; var Proto, User, Pass, Host, Port, Path: string);
  229. var
  230. p, q: Integer;
  231. s: string;
  232. begin
  233. Proto := '';
  234. User := '';
  235. Pass := '';
  236. Host := '';
  237. Port := '';
  238. Path := '';
  239. p := Pos('://', Url);
  240. if p = 0 then
  241. begin
  242. if LowerCase(Copy(Url, 1, 7)) = 'mailto:' then
  243. begin
  244. // mailto:// not common
  245. Proto := 'mailto';
  246. p := Pos(':', Url);
  247. end;
  248. end
  249. else
  250. begin
  251. Proto := Copy(Url, 1, p-1);
  252. Inc(p,2);
  253. end;
  254. s := Copy(Url, p + 1, Length(Url));
  255. p := Pos('/',s);
  256. if p = 0 then p := Length(s) + 1;
  257. Path := Copy(s, p, Length(s));
  258. s := Copy(s, 1, p-1);
  259. p := PosN(':', s, -1);
  260. if p > Length(s) then p:=0;
  261. q := PosN('@', s, -1);
  262. if q > Length(s) then q := 0;
  263. if (p = 0) and (q = 0) then
  264. begin
  265. // no user, password or port
  266. Host := s;
  267. Exit;
  268. end
  269. else
  270. if q < p then
  271. begin
  272. // a port given
  273. Port := Copy(s, p + 1, Length(s));
  274. Host := Copy(s, q + 1, p - q - 1);
  275. if q = 0 then exit; // no user, password
  276. s := Copy(s, 1, q - 1);
  277. end
  278. else
  279. begin
  280. Host := Copy(s, q + 1, Length(s));
  281. s := Copy(s, 1, q - 1);
  282. end;
  283. p := Pos(':', s);
  284. if p = 0 then User := s
  285. else
  286. begin
  287. User := Copy(s, 1, p - 1);
  288. Pass := Copy(s, p + 1, Length(s));
  289. end;
  290. end;
  291. { TTcpIp }
  292. constructor TTcpIp.Create(AOwner: TComponent);
  293. begin
  294. inherited;
  295. GetMem(FBuffer, BufSize);
  296. FStream := TMemorystream.Create;
  297. FSocket := INVALID_SOCKET;
  298. IpAddress := INVALID_IP_ADDRESS;
  299. FLoggedIn := False;
  300. end;
  301. destructor TTcpIp.Destroy;
  302. begin
  303. FTracer := nil;
  304. if FBuffer <> nil then
  305. FreeMem(FBuffer, BufSize);
  306. FStream.Free;
  307. if FSocket <> INVALID_SOCKET then
  308. Logout;
  309. inherited;
  310. end;
  311. function TTcpIp.CreateSocket: TSocket;
  312. begin
  313. Result := Winsock.Socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
  314. if Result = INVALID_SOCKET then
  315. begin
  316. raise ESocketError.Create(WSAGetLastError);
  317. end;
  318. end;
  319. procedure TTcpIp.BindSocket(var Socket: TSocket; OutPortMin, OutPortMax: Word);
  320. var
  321. LocalAddress: TSockAddr;
  322. i: Word;
  323. begin
  324. with LocalAddress do
  325. begin
  326. Sin_Family := PF_INET;
  327. Sin_addr.S_addr := INADDR_ANY;
  328. end;
  329. i := OutPortMin;
  330. while i <= OutPortMax do
  331. begin
  332. LocalAddress.Sin_Port := Winsock.htons(i);
  333. if Winsock.bind(Socket, LocalAddress, SizeOf(LocalAddress)) <>
  334. SOCKET_ERROR then Break;
  335. Inc(i);
  336. end;
  337. end;
  338. procedure TTcpIp.ConnectSocket(var Socket: TSocket; SocketNumber: SmallInt;
  339. IpAddress: LongInt);
  340. var
  341. RemoteAddress: TSockAddr;
  342. Error: Integer;
  343. begin
  344. with RemoteAddress do
  345. begin
  346. Sin_Family := PF_INET;
  347. Sin_Port := Winsock.htons(SocketNumber);
  348. Sin_addr := TInAddr(IpAddress);
  349. end;
  350. if Winsock.Connect(Socket,RemoteAddress,
  351. SizeOf(RemoteAddress)) = SOCKET_ERROR then
  352. begin
  353. Error := Winsock.WSAGetLastError;
  354. if Error <> WSAEWouldBlock then
  355. begin
  356. CloseSocket(Socket);
  357. if Assigned(FTracer) then
  358. begin
  359. FTracer('Failed to open output socket '+IntToStr(SocketNumber)+' to host '+
  360. Ip2String(IpAddress), tt_socket);
  361. end;
  362. raise ESocketError.Create(Error);
  363. end
  364. end
  365. else
  366. if Assigned(FTracer) then
  367. begin
  368. FTracer('Opened output socket '+IntToStr(SocketNumber)+' to host '+
  369. Ip2String(IpAddress)+' successfully; ID '+IntToStr(Socket), tt_socket);
  370. end;
  371. end;
  372. procedure TTcpIp.OpenSocketOut(var Socket: TSocket; SocketNumber:SmallInt;
  373. IpAddress: LongInt);
  374. begin
  375. CloseSocket(Socket);
  376. Socket := CreateSocket;
  377. ConnectSocket(Socket, SocketNumber, IpAddress);
  378. end;
  379. procedure TTcpIp.OpenSocketIn(var Socket: TSocket; var SocketNumber: SmallInt;
  380. IpAddress: LongInt);
  381. var
  382. LocalAddress: TSockAddr;
  383. l: Integer;
  384. begin
  385. CloseSocket(Socket);
  386. Socket := CreateSocket;
  387. // open the socket and let it listen
  388. with LocalAddress do
  389. begin
  390. Sin_Family := PF_INET;
  391. Sin_Port := Winsock.htons(SocketNumber);
  392. Sin_addr := TInAddr(IpAddress);
  393. end;
  394. if Winsock.Bind(Socket, LocalAddress, SizeOf(LocalAddress)) = SOCKET_ERROR then
  395. begin
  396. if Assigned(FTracer) then
  397. begin
  398. FTracer('Failed to bind socket '+IntToStr(SocketNumber)+' for local ip '+
  399. Ip2String(IpAddress), tt_socket);
  400. end;
  401. CloseSocket(Socket);
  402. Exit;
  403. end
  404. else
  405. if Assigned(FTracer) then
  406. begin
  407. FTracer('Bound to socket '+IntToStr(SocketNumber)+' for local ip '+
  408. Ip2String(IpAddress), tt_socket);
  409. end;
  410. l := SizeOf(LocalAddress);
  411. if Winsock.GetSockName(Socket, LocalAddress, l) <> SOCKET_ERROR then
  412. SocketNumber := Winsock.ntohs(LocalAddress.Sin_Port);
  413. if Winsock.Listen(Socket, BackLog) = SOCKET_ERROR then
  414. begin
  415. CloseSocket(Socket);
  416. if Assigned(FTracer) then
  417. begin
  418. FTracer('Failed to set input socket ' + IntToStr(SocketNumber) +
  419. ' to listening mode', tt_socket);
  420. end
  421. end
  422. else
  423. if Assigned(FTracer) then
  424. begin
  425. FTracer('Set input socket ' + IntToStr(SocketNumber) +
  426. ' to listening mode sucessfully; ID ' + IntToStr(Socket), tt_socket);
  427. end;
  428. end;
  429. function TTcpIp.AcceptSocketIn(Socket: TSocket; var SockInfo: TSockAddr): TSocket;
  430. var
  431. x: u_int;
  432. LocalAddress: TSockAddr;
  433. TempSocket: TSocket;
  434. begin
  435. x := SizeOf(LocalAddress);
  436. {$ifdef ver80}
  437. TempSocket := Winsock.Accept(Socket, LocalAddress, x);
  438. {$else}
  439. {$ifdef ver90}
  440. TempSocket := Winsock.Accept(Socket, LocalAddress, x);
  441. {$else} { Delphi 3 and higher }
  442. TempSocket := Winsock.Accept(Socket, @LocalAddress, @x);
  443. {$endif}
  444. {$endif}
  445. if TempSocket = SOCKET_ERROR then
  446. begin
  447. // no incoming call available
  448. TempSocket := INVALID_SOCKET;
  449. if Assigned(FTracer) then
  450. begin
  451. FTracer('No incoming connection found on socket ID '+IntToStr(Socket),
  452. tt_socket);
  453. end;
  454. end
  455. else
  456. if Assigned(FTracer) then
  457. begin
  458. FTracer('Incoming connection found on socket ID '+IntToStr(Socket)+
  459. '; generated socket ID '+IntToStr(TempSocket), tt_socket);
  460. end;
  461. AcceptSocketIn := TempSocket;
  462. SockInfo := LocalAddress;
  463. end;
  464. function TTcpIp.SocketState(Socket: TSocket): TSocketState;
  465. var
  466. PeerAdr: TSockAddr;
  467. x: u_int;
  468. begin
  469. if Socket = INVALID_SOCKET then Result := invalid
  470. else
  471. begin
  472. x := SizeOf(TSockAddr);
  473. if Winsock.GetPeerName(Socket, PeerAdr, x) = 0 then
  474. Result := connected
  475. else
  476. begin
  477. if Winsock.WSAGetLastError <> WSAENOTCONN then
  478. Result := state_unknown
  479. else
  480. Result := valid
  481. end;
  482. end;
  483. end;
  484. procedure TTcpIp.CloseSocket(var Socket: TSocket);
  485. begin
  486. if Socket <> INVALID_SOCKET then
  487. begin
  488. Winsock.CloseSocket(Socket);
  489. if Assigned(FTracer) then
  490. FTracer('Closed socket ID '+IntToStr(socket), tt_socket);
  491. Socket := INVALID_SOCKET;
  492. end;
  493. end;
  494. function TTcpIp.SocketByName(const Service: string): SmallInt;
  495. var
  496. ServiceEntry: PServEnt;
  497. s: string;
  498. begin
  499. s := service + #0;
  500. ServiceEntry := Winsock.GetServByName(pansichar(AnsiString(s)), 'tcp');
  501. if ServiceEntry = nil then
  502. Result := 0
  503. else
  504. Result := Winsock.htons(ServiceEntry^.s_port);
  505. end;
  506. procedure TTcpIp.Login;
  507. begin
  508. if FLoggedIn then Logout;
  509. IpAddress := LookupHostname(FHostname);
  510. if IpAddress = INVALID_IP_ADDRESS then
  511. raise ETcpIpError.Create('Couldn''t resolve hostname ' + FHostname);
  512. OpenSocketOut(FSocket, FSocketNumber, IpAddress);
  513. FEof := False;
  514. FLoggedIn := True;
  515. end;
  516. procedure TTcpIp.LogOut;
  517. begin
  518. CloseSocket(FSocket);
  519. FSocket := invalid_socket;
  520. FLoggedIn := False;
  521. end;
  522. procedure TTcpIp.SendCommand(const S: string);
  523. begin
  524. WriteStr(FSocket, S + #13#10);
  525. if Assigned(FTracer) then
  526. FTracer(S, tt_proto_sent);
  527. end;
  528. function TTcpIp.Eof(Socket: TSocket): Boolean;
  529. begin
  530. Result := FEof or (SocketState(Socket) <> connected);
  531. end;
  532. procedure TTcpIp.ReadVar(Socket: TSocket; var Buf; Size: Integer;
  533. var Ok: Integer);
  534. var
  535. TempBuf: Pointer;
  536. Error: Integer;
  537. ReadSet: TFDSet;
  538. Timeout: TTimeVal;
  539. begin
  540. TempBuf := nil;
  541. try
  542. if @Buf = nil then
  543. GetMem(TempBuf, Size) // alloc for the -> /dev/null
  544. else
  545. TempBuf := @Buf;
  546. repeat
  547. FD_ZERO(ReadSet);
  548. FD_SET(Socket, ReadSet);
  549. Timeout.tv_sec := 5;
  550. Timeout.tv_usec := 0;
  551. Ok := Winsock.Select(1, @ReadSet, nil, nil, @Timeout);
  552. if Ok = 0 then
  553. raise ETcpIpError.Create(Format(SSocketError2, [STimeout]))
  554. else if Ok = SOCKET_ERROR then
  555. raise ESocketError.Create(WSAGetLastError);
  556. Ok := Winsock.Recv(Socket, TempBuf^, Size, 0);
  557. if Ok <= 0 then
  558. begin
  559. Error := Winsock.WSAGetLastError;
  560. FEof := (Error <> WSAEWouldBlock);
  561. end
  562. else
  563. begin
  564. if Assigned(FTracer) then
  565. FTracer('Received ' + IntToStr(Ok) + ' bytes on socket ID ' +
  566. IntToStr(FSocket), tt_socket);
  567. end;
  568. until FEof or (Ok > 0);
  569. finally
  570. if @Buf = nil then
  571. FreeMem(TempBuf, Size)
  572. end;
  573. end;
  574. function TTcpIp.ReadLine(Socket: TSocket): string;
  575. var
  576. x: AnsiChar;
  577. Ok: Integer;
  578. s: UTF8String;
  579. begin
  580. s := '';
  581. repeat
  582. ReadVar(Socket, x, 1, Ok);
  583. if Ok <> 1 then
  584. begin
  585. Break;
  586. end
  587. else
  588. if x = #13 then // at least NCSA 1.3 does send a #10 only
  589. else
  590. if x = #10 then
  591. begin
  592. Break;
  593. end
  594. else
  595. begin
  596. s := s + UTF8String(x);
  597. end;
  598. until Eof(Socket);
  599. Result := string(s);
  600. end;
  601. procedure TTcpIp.WriteBuf(Socket: TSocket; const Buf; var Size: Integer);
  602. begin
  603. Size := Winsock.Send(Socket, Pointer(@Buf)^, Size, 0);
  604. if Assigned(FTracer) then
  605. FTracer('Sent ' + IntToStr(Size) + ' bytes on socket ID ' +
  606. IntToStr(FSocket), tt_socket);
  607. end;
  608. procedure TTcpIp.WriteStr(Socket: TSocket; const s: string);
  609. var
  610. u: UTF8String;
  611. l: Integer;
  612. begin
  613. u := UTF8String(s);
  614. l := Length(u);
  615. {$ifdef ver80}
  616. WriteBuf(Socket, PAnsiChar(@u[1])^, l);
  617. {$else}
  618. {$ifopt h-}
  619. WriteBuf(Socket, PAnsiChar(@u[1])^, l);
  620. {$else}
  621. WriteBuf(Socket, PAnsiChar(u)^, l);
  622. {$endif}
  623. {$endif}
  624. end;
  625. procedure TTcpIp.SetStream(Value: TStream);
  626. begin
  627. TMemoryStream(FStream).LoadFromStream(value);
  628. end;
  629. procedure TTcpIp.Action;
  630. var
  631. p: Pointer;
  632. ok, ok2: Integer;
  633. begin
  634. Login;
  635. TMemoryStream(FStream).Clear;
  636. while not Eof(FSocket) do
  637. begin
  638. ReadVar(FSocket, FBuffer^, BufSize, ok);
  639. p := FBuffer;
  640. while ok > 0 do
  641. begin
  642. // just to be sure everything goes into the stream
  643. ok2 := FStream.write(p^, ok);
  644. Dec(ok,ok2);
  645. p := Pointer(LongInt(p) + ok2);
  646. end;
  647. end;
  648. FStream.Seek(0, 0);
  649. end;
  650. { THttp }
  651. constructor THttp.Create(AOwner: TComponent);
  652. begin
  653. inherited;
  654. FContentPost := 'application/x-www-form-urlencoded';
  655. FDoAuthor := TStringlist.Create;
  656. FRedirect := False;
  657. end;
  658. destructor THttp.Destroy;
  659. begin
  660. FDoAuthor.Free;
  661. inherited;
  662. end;
  663. procedure THttp.SendRequest(const Method, Version: string);
  664. begin
  665. SendCommand(Method + ' ' + FPath + ' HTTP/' + Version);
  666. SendCommand('Host: ' + FHostname);
  667. SendCommand('Connection: close');
  668. if FSender <> '' then
  669. SendCommand('From: ' + FSender);
  670. if FReference <> '' then
  671. SendCommand('Referer: ' + FReference);
  672. if FAgent <> '' then
  673. SendCommand('User-Agent: ' + FAgent);
  674. if FNoCache then
  675. SendCommand('Pragma: no-cache');
  676. if Method = 'POST' then
  677. begin
  678. SendCommand('Content-Length: ' + IntToStr(Stream.Size));
  679. if FContentPost <> '' then
  680. SendCommand('Content-Type: ' + FContentPost);
  681. end;
  682. WriteStr(FSocket, #13#10); // finalize the request
  683. end;
  684. procedure THttp.ReportStatusError;
  685. begin
  686. raise EProtocolError.Create('HTTP',
  687. Format(SHttpError, [FStatusNr, FStatusTxt, FHostName]), FStatusNr);
  688. end;
  689. procedure THttp.GetAnswer;
  690. var
  691. s: string;
  692. Proto, User, Pass, Port: string;
  693. Field, Data: string;
  694. begin
  695. FDoAuthor.Clear;
  696. FType := '';
  697. FSize := 0;
  698. FRedirect := False;
  699. repeat
  700. s := ReadLine(FSocket);
  701. if s <> '' then
  702. if Assigned(FTracer) then
  703. FTracer(s, tt_proto_get);
  704. // many servers (including ours) obviously return 1.1 response to 1.0 request
  705. if (Copy(s, 1, 8) = 'HTTP/1.0') or
  706. (Copy(s, 1, 8) = 'HTTP/1.1') then
  707. begin
  708. FStatusNr := StrToInt(Copy(s, 10, 3));
  709. FStatusTxt := Copy(s, 14, Length(s));
  710. if FStatusNr >= 400 then ReportStatusError;
  711. end
  712. else
  713. if Pos(':', s) > 0 then
  714. begin
  715. Field := LowerCase(Copy(s, 1, Pos(':', s) - 1));
  716. Data := Copy(s, Pos(':', s) + 2, Length(s));
  717. if Field = 'location' then
  718. begin
  719. if Proxy <> '' then
  720. FPath := Data // it goes via a proxy, so just change the uri
  721. else
  722. begin
  723. ParseUrl(Data, Proto, User, Pass, FHostname, Port, FPath);
  724. if Port <> '' then FSocketNumber := StrToInt(Port);
  725. end;
  726. FRedirect := True;
  727. end
  728. else
  729. if Field = 'content-length' then
  730. FSize := StrToInt(Data)
  731. else
  732. if Field = 'content-type' then
  733. FType := Data
  734. else
  735. if Field = 'www-authenticate' then
  736. FDoAuthor.Add(Data);
  737. end
  738. until s = '';
  739. end;
  740. procedure THttp.Action;
  741. begin
  742. GetBody(False);
  743. end;
  744. procedure THttp.GetBody(Post: Boolean);
  745. var
  746. Proto, User, Pass, Host, Port, Path: string;
  747. Method: string;
  748. Redirects: Integer;
  749. p: Pointer;
  750. ok, ok2: Integer;
  751. begin
  752. // parse url and proxy to FHostname, FPath and FSocketNumber
  753. if FProxy <> '' then
  754. begin
  755. ParseUrl(FUrl, Proto, User, Pass, Host, Port, Path);
  756. FPath := FUrl;
  757. if Proto = '' then
  758. FPath := 'http://' + FPath;
  759. ParseUrl(FProxy, Proto, User, Pass, Host, Port, Path);
  760. if Port = '' then Port := '8080';
  761. end
  762. else
  763. begin
  764. ParseUrl(FUrl, Proto, User, Pass, Host, Port, FPath);
  765. if Port = '' then Port := '80';
  766. end;
  767. if Proto = '' then Proto := 'http';
  768. if FPath = '' then FPath := '/';
  769. FHostname := Host;
  770. FSocketNumber := StrToInt(Port);
  771. Redirects := 0;
  772. // loop until we get answer without Location header
  773. repeat
  774. Inc(Redirects);
  775. if Redirects > MaxRedirects then
  776. raise ETcpIpError.Create(Format(SRedirectLimitError, [MaxRedirects]));
  777. // do directly GET/POST, instead of HEAD first.
  778. // currently we use this for updates only and the potentional overhead
  779. // of GET/POST on redirect answer is smaller then two requests per each check
  780. // (especially for the server itself)
  781. Login;
  782. if Post then Method := 'POST'
  783. else Method := 'GET';
  784. SendRequest(Method, '1.1');
  785. if Post then
  786. begin
  787. // Send the data
  788. TMemoryStream(FStream).Seek(0, 0);
  789. ok := 1;
  790. while ok > 0 do
  791. begin
  792. ok := FStream.Read(FBuffer^, BufSize);
  793. WriteBuf(FSocket, FBuffer^, ok);
  794. end;
  795. end;
  796. GetAnswer;
  797. // read the data
  798. if not FRedirect then
  799. begin
  800. TMemoryStream(FStream).Clear;
  801. while not Eof(FSocket) do
  802. begin
  803. ReadVar(FSocket, FBuffer^, BufSize, Ok);
  804. p := FBuffer;
  805. while ok > 0 do
  806. begin
  807. // just to be sure everything goes into the stream
  808. ok2 := FStream.Write(p^, ok);
  809. Dec(ok, ok2);
  810. p := Pointer(LongInt(p) + ok2);
  811. end;
  812. end;
  813. FStream.Seek(0,0); // set the stream back to start
  814. end;
  815. Logout;
  816. until not FRedirect;
  817. if FStatusNr <> 200 then ReportStatusError;
  818. end;
  819. procedure THttp.Post;
  820. begin
  821. GetBody(True);
  822. end;
  823. procedure Register;
  824. begin
  825. RegisterComponents('Martin', [THttp]);
  826. end;
  827. initialization
  828. end.