IdMultipartFormData.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. { $HDR$}
  2. {**********************************************************************}
  3. { Unit archived using Team Coherence }
  4. { Team Coherence is Copyright 2002 by Quality Software Components }
  5. { }
  6. { For further information / comments, visit our WEB site at }
  7. { http://www.TeamCoherence.com }
  8. {**********************************************************************}
  9. {}
  10. { $Log: 10267: IdMultipartFormData.pas
  11. {
  12. { Rev 1.7 7/16/04 12:02:54 PM RLebeau
  13. { Reverted FileName fields to not strip off folder paths anymore.
  14. }
  15. {
  16. { Rev 1.6 7/5/04 1:18:18 PM RLebeau
  17. { Updated Read() to check the calculated byte count before copying data into
  18. { the caller's buffer.
  19. }
  20. {
  21. { Rev 1.5 5/31/04 9:29:30 PM RLebeau
  22. { Updated FileName fields to strip off folder paths.
  23. {
  24. { Added "Content-Transfer-Encoding" header to file fields
  25. {
  26. { Updated "Content-Type" headers to be the appropriate media types when
  27. { applicable
  28. }
  29. {
  30. { Rev 1.3 3/1/04 8:53:46 PM RLebeau
  31. { Format() fixes for TIdMultiPartFormDataStream.FormatField() and
  32. { TIdFormDataField.GetFieldSize().
  33. }
  34. {
  35. { Rev 1.2 9/10/2003 02:58:38 AM JPMugaas
  36. { Format() fixes for TIdMultiPartFormDataStream.FormatField() and
  37. { TIdFormDataField.GetFieldSize(). Checked in on behalf of Remy Lebeau
  38. }
  39. {
  40. { Rev 1.1 01.2.2003 ã. 12:00:00 DBondzhev
  41. }
  42. {
  43. { Rev 1.0 2002.11.12 10:46:56 PM czhower
  44. }
  45. unit IdMultipartFormData;
  46. {
  47. Implementation of the Multipart From data
  48. Author: Shiv Kumar
  49. Copyright: (c) Chad Z. Hower and The Winshoes Working Group.
  50. Details of implementation
  51. -------------------------
  52. 2001-Nov Doychin Bondzhev
  53. - Now it descends from TStream and does not do buffering.
  54. - Changes in the way the form parts are added to the stream.
  55. 2001-Nov-23
  56. - changed spelling error from XxxDataFiled to XxxDataField
  57. }
  58. interface
  59. uses
  60. SysUtils, Classes, IdGlobal, IdException, IdResourceStrings;
  61. const
  62. sContentTypeFormData = 'multipart/form-data; boundary=';
  63. sContentTypeOctetStream = 'application/octet-stream';
  64. crlf = #13#10;
  65. sContentDisposition = 'Content-Disposition: form-data; name="%s"';
  66. sFileNamePlaceHolder = '; filename="%s"';
  67. sContentTypePlaceHolder = 'Content-Type: %s';
  68. type
  69. TIdMultiPartFormDataStream = class;
  70. TIdFormDataField = class(TCollectionItem)
  71. protected
  72. FFieldValue: string;
  73. FFileName: string;
  74. FContentType: string;
  75. FFieldName: string;
  76. FFieldObject: TObject;
  77. FCanFreeFieldObject: Boolean;
  78. function GetFieldSize: LongInt;
  79. function GetFieldStream: TStream;
  80. function GetFieldStrings: TStrings;
  81. procedure SetContentType(const Value: string);
  82. procedure SetFieldName(const Value: string);
  83. procedure SetFieldStream(const Value: TStream);
  84. procedure SetFieldStrings(const Value: TStrings);
  85. procedure SetFieldValue(const Value: string);
  86. procedure SetFieldObject(const Value: TObject);
  87. procedure SetFileName(const Value: string);
  88. public
  89. constructor Create(Collection: TCollection); override;
  90. destructor Destroy; override;
  91. // procedure Assign(Source: TPersistent); override;
  92. function FormatField: string;
  93. property ContentType: string read FContentType write SetContentType;
  94. property FieldName: string read FFieldName write SetFieldName;
  95. property FieldStream: TStream read GetFieldStream write SetFieldStream;
  96. property FieldStrings: TStrings read GetFieldStrings write SetFieldStrings;
  97. property FieldObject: TObject read FFieldObject write SetFieldObject;
  98. property FileName: string read FFileName write SetFileName;
  99. property FieldValue: string read FFieldValue write SetFieldValue;
  100. property FieldSize: LongInt read GetFieldSize;
  101. end;
  102. TIdFormDataFields = class(TCollection)
  103. protected
  104. FParentStream: TIdMultiPartFormDataStream;
  105. function GetFormDataField(AIndex: Integer): TIdFormDataField;
  106. public
  107. constructor Create(AMPStream: TIdMultiPartFormDataStream);
  108. function Add: TIdFormDataField;
  109. property MultipartFormDataStream: TIdMultiPartFormDataStream read FParentStream;
  110. property Items[AIndex: Integer]: TIdFormDataField read GetFormDataField;
  111. end;
  112. TIdMultiPartFormDataStream = class(TStream)
  113. protected
  114. FInputStream: TStream;
  115. FBoundary: string;
  116. FRequestContentType: string;
  117. FCurrentItem: integer;
  118. FInitialized: Boolean;
  119. FInternalBuffer: string;
  120. FPosition: Int64;
  121. FSize: Int64;
  122. FFields: TIdFormDataFields;
  123. function GenerateUniqueBoundary: string;
  124. function PrepareStreamForDispatch: string;
  125. public
  126. constructor Create;
  127. destructor Destroy; override;
  128. function Read(var Buffer; Count: Longint): Longint; override;
  129. function Write(const Buffer; Count: Longint): Longint; override;
  130. function Seek(Offset: Longint; Origin: Word): Longint; overload; override;
  131. procedure AddFormField(const AFieldName, AFieldValue: string);
  132. procedure AddObject(const AFieldName, AContentType: string; AFileData: TObject; const AFileName: string = '');
  133. procedure AddFile(const AFieldName, AFileName, AContentType: string);
  134. property Boundary: string read FBoundary;
  135. property RequestContentType: string read FRequestContentType;
  136. end;
  137. EIdInvalidObjectType = class(EIdException);
  138. implementation
  139. { TIdMultiPartFormDataStream }
  140. constructor TIdMultiPartFormDataStream.Create;
  141. begin
  142. inherited Create;
  143. FSize := 0;
  144. FInitialized := False;
  145. FBoundary := GenerateUniqueBoundary;
  146. FRequestContentType := sContentTypeFormData + FBoundary;
  147. FFields := TIdFormDataFields.Create(Self);
  148. end;
  149. destructor TIdMultiPartFormDataStream.Destroy;
  150. begin
  151. FreeAndNil(FFields);
  152. inherited Destroy;
  153. end;
  154. procedure TIdMultiPartFormDataStream.AddObject(const AFieldName,
  155. AContentType: string; AFileData: TObject; const AFileName: string = '');
  156. var
  157. LItem: TIdFormDataField;
  158. begin
  159. LItem := FFields.Add;
  160. with LItem do begin
  161. FFieldName := AFieldName;
  162. FFileName := AFileName;
  163. FFieldObject := AFileData;
  164. if Length(AContentType) > 0 then begin
  165. FContentType := AContentType;
  166. end else begin
  167. if Length(FFileName) > 0 then begin
  168. FContentType := GetMIMETypeFromFile(FFileName);
  169. end else begin
  170. FContentType := sContentTypeOctetStream;
  171. end;
  172. end;
  173. end;
  174. FSize := FSize + LItem.FieldSize;
  175. end;
  176. procedure TIdMultiPartFormDataStream.AddFile(const AFieldName, AFileName,
  177. AContentType: string);
  178. var
  179. LStream: TFileStream;
  180. LItem: TIdFormDataField;
  181. begin
  182. LStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
  183. try
  184. LItem := FFields.Add;
  185. except
  186. FreeAndNil(LStream);
  187. raise;
  188. end;
  189. with LItem do begin
  190. FFieldName := AFieldName;
  191. FFileName := AFileName;
  192. FFieldObject := LStream;
  193. FCanFreeFieldObject := True;
  194. if Length(AContentType) > 0 then begin
  195. FContentType := AContentType;
  196. end else begin
  197. FContentType := GetMIMETypeFromFile(AFileName);
  198. end;
  199. end;
  200. FSize := FSize + LItem.FieldSize;
  201. end;
  202. procedure TIdMultiPartFormDataStream.AddFormField(const AFieldName,
  203. AFieldValue: string);
  204. var
  205. LItem: TIdFormDataField;
  206. begin
  207. LItem := FFields.Add;
  208. with LItem do begin
  209. FFieldName := AFieldName;
  210. FFieldValue := AFieldValue;
  211. end;
  212. FSize := FSize + LItem.FieldSize;
  213. end;
  214. function TIdMultiPartFormDataStream.GenerateUniqueBoundary: string;
  215. begin
  216. Result := '--------' + FormatDateTime('mmddyyhhnnsszzz', Now);
  217. end;
  218. function TIdMultiPartFormDataStream.PrepareStreamForDispatch: string;
  219. begin
  220. Result := {crlf +} '--' + Boundary + '--' + crlf;
  221. end;
  222. // RLebeau - Read() should wrap multiple files using a single
  223. // "multipart/mixed" MIME part, as recommended by RFC 1867
  224. function TIdMultiPartFormDataStream.Read(var Buffer;
  225. Count: Integer): Longint;
  226. type
  227. PByteArray = ^TByteArray;
  228. TByteArray = array[0..High(Integer) - 1] of Byte; // 2GB size
  229. var
  230. LTotalRead: Integer;
  231. LCount: Integer;
  232. LBufferCount: Integer;
  233. LItem: TIdFormDataField;
  234. begin
  235. if not FInitialized then begin
  236. FInitialized := True;
  237. FCurrentItem := 0;
  238. SetLength(FInternalBuffer, 0);
  239. end;
  240. LTotalRead := 0;
  241. LBufferCount := 0;
  242. while (LTotalRead < Count) and ((FCurrentItem < FFields.Count) or (Length(FInternalBuffer) > 0)) do begin
  243. if (Length(FInternalBuffer) = 0) and not Assigned(FInputStream) then begin
  244. LItem := FFields.Items[FCurrentItem];
  245. FInternalBuffer := LItem.FormatField;
  246. if Assigned(LItem.FieldObject) then begin
  247. if (LItem.FieldObject is TStream) then begin
  248. FInputStream := TStream(LItem.FieldObject);
  249. FInputStream.Seek(0, soFromBeginning);
  250. end else begin
  251. if (LItem.FieldObject is TStrings) then begin
  252. FInternalBuffer := FInternalBuffer + TStrings(LItem.FieldObject).Text;
  253. Inc(FCurrentItem);
  254. end;
  255. end;
  256. end else begin
  257. Inc(FCurrentItem);
  258. end;
  259. end;
  260. if Length(FInternalBuffer) > 0 then begin
  261. if Length(FInternalBuffer) > (Count - LBufferCount) then begin
  262. LCount := Count - LBufferCount;
  263. end else begin
  264. LCount := Length(FInternalBuffer);
  265. end;
  266. if LCount > 0 then begin
  267. Move(FInternalBuffer[1], TByteArray(Buffer)[LBufferCount], LCount);
  268. Delete(FInternalBuffer, 1, LCount);
  269. LBufferCount := LBufferCount + LCount;
  270. FPosition := FPosition + LCount;
  271. LTotalRead := LTotalRead + LCount;
  272. end;
  273. end;
  274. if Assigned(FInputStream) and (LTotalRead < Count) then begin
  275. LCount := FInputStream.Read(TByteArray(Buffer)[LBufferCount], Count - LTotalRead);
  276. if LCount < (Count - LTotalRead) then begin
  277. FInputStream.Seek(0, soFromBeginning);
  278. FInputStream := nil;
  279. Inc(FCurrentItem);
  280. FInternalBuffer := #13#10;
  281. end;
  282. LBufferCount := LBufferCount + LCount;
  283. LTotalRead := LTotalRead + LCount;
  284. FPosition := FPosition + LCount;
  285. end;
  286. if FCurrentItem = FFields.Count then begin
  287. FInternalBuffer := FInternalBuffer + PrepareStreamForDispatch;
  288. Inc(FCurrentItem);
  289. end;
  290. end;
  291. Result := LTotalRead;
  292. end;
  293. function TIdMultiPartFormDataStream.Seek(Offset: Integer;
  294. Origin: Word): Longint;
  295. begin
  296. Result := 0;
  297. case Origin of
  298. soFromBeginning: begin
  299. if (Offset = 0) then begin
  300. FInitialized := False;
  301. FPosition := 0;
  302. Result := 0;
  303. end else begin
  304. Result := FPosition;
  305. end;
  306. end;
  307. soFromCurrent: begin
  308. Result := FPosition;
  309. end;
  310. soFromEnd: begin
  311. Result := FSize + Length(PrepareStreamForDispatch);
  312. end;
  313. end;
  314. end;
  315. function TIdMultiPartFormDataStream.Write(const Buffer;
  316. Count: Integer): Longint;
  317. begin
  318. raise Exception.Create('Unsupported operation.');
  319. end;
  320. { TIdFormDataFields }
  321. function TIdFormDataFields.Add: TIdFormDataField;
  322. begin
  323. Result := TIdFormDataField(inherited Add);
  324. end;
  325. constructor TIdFormDataFields.Create(AMPStream: TIdMultiPartFormDataStream);
  326. begin
  327. inherited Create(TIdFormDataField);
  328. FParentStream := AMPStream;
  329. end;
  330. function TIdFormDataFields.GetFormDataField(
  331. AIndex: Integer): TIdFormDataField;
  332. begin
  333. Result := TIdFormDataField(inherited Items[AIndex]);
  334. end;
  335. { TIdFormDataField }
  336. constructor TIdFormDataField.Create(Collection: TCollection);
  337. begin
  338. inherited Create(Collection);
  339. FFieldObject := nil;
  340. FFileName := '';
  341. FFieldName := '';
  342. FContentType := '';
  343. FCanFreeFieldObject := False;
  344. end;
  345. destructor TIdFormDataField.Destroy;
  346. begin
  347. if Assigned(FFieldObject) then begin
  348. if FCanFreeFieldObject then begin
  349. FreeAndNil(FFieldObject);
  350. end;
  351. end;
  352. inherited Destroy;
  353. end;
  354. function TIdFormDataField.FormatField: string;
  355. var
  356. LBoundary: string;
  357. begin
  358. LBoundary := TIdFormDataFields(Collection).MultipartFormDataStream.Boundary;
  359. if Assigned(FieldObject) then begin
  360. if Length(FileName) > 0 then begin
  361. Result := Format('--%s' + crlf + sContentDisposition +
  362. sFileNamePlaceHolder + crlf + sContentTypePlaceHolder +
  363. crlf + crlf, [LBoundary, FieldName, FileName, ContentType]);
  364. Exit;
  365. end;
  366. end;
  367. Result := Format('--%s' + crlf + sContentDisposition + crlf + crlf +
  368. '%s' + crlf, [LBoundary, FieldName, FieldValue]);
  369. end;
  370. function TIdFormDataField.GetFieldSize: LongInt;
  371. begin
  372. Result := Length(FormatField);
  373. if Assigned(FFieldObject) then begin
  374. if FieldObject is TStrings then begin
  375. Result := Result + Length(TStrings(FieldObject).Text) + 2;
  376. end else begin
  377. if FieldObject is TStream then begin
  378. Result := Result + TStream(FieldObject).Size + 2;
  379. end;
  380. end;
  381. end;
  382. end;
  383. function TIdFormDataField.GetFieldStream: TStream;
  384. begin
  385. Result := nil;
  386. if Assigned(FFieldObject) then begin
  387. if (FFieldObject is TStream) then begin
  388. Result := TStream(FFieldObject);
  389. end else begin
  390. raise EIdInvalidObjectType.Create(RSMFDIvalidObjectType);
  391. end;
  392. end;
  393. end;
  394. function TIdFormDataField.GetFieldStrings: TStrings;
  395. begin
  396. Result := nil;
  397. if Assigned(FFieldObject) then begin
  398. if (FFieldObject is TStrings) then begin
  399. Result := TStrings(FFieldObject);
  400. end else begin
  401. raise EIdInvalidObjectType.Create(RSMFDIvalidObjectType);
  402. end;
  403. end;
  404. end;
  405. procedure TIdFormDataField.SetContentType(const Value: string);
  406. begin
  407. if Length(Value) > 0 then begin
  408. FContentType := Value;
  409. end else begin
  410. if Length(FFileName) > 0 then begin
  411. FContentType := GetMIMETypeFromFile(FFileName);
  412. end else begin;
  413. FContentType := sContentTypeOctetStream;
  414. end;
  415. end;
  416. GetFieldSize;
  417. end;
  418. procedure TIdFormDataField.SetFieldName(const Value: string);
  419. begin
  420. FFieldName := Value;
  421. GetFieldSize;
  422. end;
  423. procedure TIdFormDataField.SetFieldObject(const Value: TObject);
  424. begin
  425. if Assigned(Value) then begin
  426. if not ((Value is TStream) or (Value is TStrings)) then begin
  427. raise EIdInvalidObjectType.Create(RSMFDIvalidObjectType);
  428. end;
  429. end;
  430. if Assigned(FFieldObject) then begin
  431. if FCanFreeFieldObject then begin
  432. FreeAndNil(FFieldObject);
  433. end;
  434. end;
  435. FFieldObject := Value;
  436. FCanFreeFieldObject := False;
  437. GetFieldSize;
  438. end;
  439. procedure TIdFormDataField.SetFieldStream(const Value: TStream);
  440. begin
  441. FieldObject := Value;
  442. end;
  443. procedure TIdFormDataField.SetFieldStrings(const Value: TStrings);
  444. begin
  445. FieldObject := Value;
  446. end;
  447. procedure TIdFormDataField.SetFieldValue(const Value: string);
  448. begin
  449. FFieldValue := Value;
  450. GetFieldSize;
  451. end;
  452. procedure TIdFormDataField.SetFileName(const Value: string);
  453. begin
  454. FFileName := Value;
  455. GetFieldSize;
  456. end;
  457. end.