IdMultipartFormData.pas 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  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. Prior revision history:
  16. Rev 1.17 2/8/05 6:07:16 PM RLebeau
  17. Removed AddToInternalBuffer() method, using new AppendString() function
  18. from IdGlobal instead
  19. Rev 1.16 10/26/2004 10:29:30 PM JPMugaas
  20. Updated refs.
  21. Rev 1.15 7/16/04 12:02:16 PM RLebeau
  22. Reverted FileName fields to not strip off folder paths anymore.
  23. Rev 1.14 7/5/04 1:19:06 PM RLebeau
  24. Updated IdRead() to check the calculated byte count before copying data
  25. into the caller's buffer.
  26. Rev 1.13 5/31/04 9:28:58 PM RLebeau
  27. Updated FileName fields to strip off folder paths.
  28. Added "Content-Transfer-Encoding" header to file fields
  29. Updated "Content-Type" headers to be the appropriate media types when
  30. applicable
  31. Rev 1.12 5/30/04 7:39:02 PM RLebeau
  32. Moved FormatField() method from TIdMultiPartFormDataStream to
  33. TIdFormDataField instead
  34. Misc. tweaks and bug fixes
  35. Rev 1.11 2004.05.20 11:37:02 AM czhower
  36. IdStreamVCL
  37. Rev 1.10 3/1/04 8:57:34 PM RLebeau
  38. Format() fixes for TIdMultiPartFormDataStream.FormatField() and
  39. TIdFormDataField.GetFieldSize().
  40. Rev 1.9 2004.02.03 5:44:08 PM czhower
  41. Name changes
  42. Rev 1.8 2004.02.03 2:12:16 PM czhower
  43. $I path change
  44. Rev 1.7 25/01/2004 21:56:42 CCostelloe
  45. Updated IdSeek to use new IdFromBeginning
  46. Rev 1.6 24/01/2004 19:26:56 CCostelloe
  47. Cleaned up warnings
  48. Rev 1.5 22/11/2003 12:05:26 AM GGrieve
  49. Get working on both win32 and DotNet after other DotNet changes
  50. Rev 1.4 11/10/2003 8:03:54 PM BGooijen
  51. Did all todo's ( TStream to TIdStream mainly )
  52. Rev 1.3 2003.10.24 10:43:12 AM czhower
  53. TIdSTream to dos
  54. Rev 1.2 10/17/2003 12:49:52 AM DSiders
  55. Added localization comments.
  56. Added resource string for unsupported operation exception.
  57. Rev 1.1 10/7/2003 10:07:06 PM GGrieve
  58. Get HTTP compiling for DotNet
  59. Rev 1.0 11/13/2002 07:57:42 AM JPMugaas
  60. Initial version control checkin.
  61. 2001-Nov-23
  62. changed spelling error from XxxDataFiled to XxxDataField
  63. 2001-Nov Doychin Bondzhev
  64. Now it descends from TStream and does not do buffering.
  65. Changes in the way the form parts are added to the stream.
  66. }
  67. unit IdMultipartFormData;
  68. {
  69. Implementation of the Multipart Form data
  70. Based on Internet standards outlined in:
  71. RFC 1867 - Form-based File Upload in HTML
  72. RFC 2388 - Returning Values from Forms: multipart/form-data
  73. Author: Shiv Kumar
  74. }
  75. interface
  76. {$I IdCompilerDefines.inc}
  77. uses
  78. Classes,
  79. IdGlobal,
  80. IdException,
  81. IdCharsets,
  82. IdCoderHeader,
  83. IdResourceStringsProtocols;
  84. const
  85. sContentTypeFormData = 'multipart/form-data; boundary='; {do not localize}
  86. sContentTypeOctetStream = 'application/octet-stream'; {do not localize}
  87. sContentTypeTextPlain = 'text/plain'; {do not localize}
  88. CRLF = #13#10;
  89. sContentDispositionPlaceHolder = 'Content-Disposition: form-data; name="%s"'; {do not localize}
  90. sFileNamePlaceHolder = '; filename="%s"'; {do not localize}
  91. sContentTypePlaceHolder = 'Content-Type: %s'; {do not localize}
  92. sCharsetPlaceHolder = '; charset="%s"'; {do not localize}
  93. sContentTransferPlaceHolder = 'Content-Transfer-Encoding: %s'; {do not localize}
  94. sContentTransferQuotedPrintable = 'quoted-printable'; {do not localize}
  95. sContentTransferBinary = 'binary'; {do not localize}
  96. type
  97. TIdMultiPartFormDataStream = class;
  98. TIdFormDataField = class(TCollectionItem)
  99. protected
  100. FFileName: string;
  101. FCharset: string;
  102. FContentType: string;
  103. FContentTransfer: string;
  104. FFieldName: string;
  105. FFieldStream: TStream;
  106. FFieldValue: String;
  107. FCanFreeFieldStream: Boolean;
  108. FHeaderCharSet: string;
  109. FHeaderEncoding: Char;
  110. function FormatHeader: string;
  111. function PrepareDataStream(var VCanFree: Boolean): TStream;
  112. function GetFieldSize: Int64;
  113. function GetFieldStream: TStream;
  114. function GetFieldValue: string;
  115. procedure SetCharset(const Value: string);
  116. procedure SetContentType(const Value: string);
  117. procedure SetContentTransfer(const Value: string);
  118. procedure SetFieldName(const Value: string);
  119. procedure SetFieldStream(const Value: TStream);
  120. procedure SetFieldValue(const Value: string);
  121. procedure SetFileName(const Value: string);
  122. procedure SetHeaderCharSet(const Value: string);
  123. procedure SetHeaderEncoding(const Value: Char);
  124. public
  125. constructor Create(Collection: TCollection); override;
  126. destructor Destroy; override;
  127. // procedure Assign(Source: TPersistent); override;
  128. property ContentTransfer: string read FContentTransfer write SetContentTransfer;
  129. property ContentType: string read FContentType write SetContentType;
  130. property Charset: string read FCharset write SetCharset;
  131. property FieldName: string read FFieldName write SetFieldName;
  132. property FieldStream: TStream read GetFieldStream write SetFieldStream;
  133. property FileName: string read FFileName write SetFileName;
  134. property FieldValue: string read GetFieldValue write SetFieldValue;
  135. property FieldSize: Int64 read GetFieldSize;
  136. property HeaderCharSet: string read FHeaderCharSet write SetHeaderCharSet;
  137. property HeaderEncoding: Char read FHeaderEncoding write SetHeaderEncoding;
  138. end;
  139. TIdFormDataFields = class(TCollection)
  140. protected
  141. FParentStream: TIdMultiPartFormDataStream;
  142. function GetFormDataField(AIndex: Integer): TIdFormDataField;
  143. public
  144. constructor Create(AMPStream: TIdMultiPartFormDataStream);
  145. function Add: TIdFormDataField;
  146. property MultipartFormDataStream: TIdMultiPartFormDataStream read FParentStream;
  147. property Items[AIndex: Integer]: TIdFormDataField read GetFormDataField;
  148. end;
  149. TIdMultiPartFormDataStream = class(TIdBaseStream)
  150. protected
  151. FInputStream: TStream;
  152. FFreeInputStream: Boolean;
  153. FBoundary: string;
  154. FRequestContentType: string;
  155. FCurrentItem: integer;
  156. FInitialized: Boolean;
  157. FInternalBuffer: TIdBytes;
  158. FPosition: Int64;
  159. FSize: Int64;
  160. FFields: TIdFormDataFields;
  161. function GenerateUniqueBoundary: string;
  162. procedure CalculateSize;
  163. function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
  164. function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
  165. function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override;
  166. procedure IdSetSize(ASize : Int64); override;
  167. public
  168. constructor Create;
  169. destructor Destroy; override;
  170. function AddFormField(const AFieldName, AFieldValue: string; const ACharset: string = ''; const AContentType: string = ''; const AFileName: string = ''): TIdFormDataField; overload;
  171. function AddFormField(const AFieldName, AContentType, ACharset: string; AFieldValue: TStream; const AFileName: string = ''): TIdFormDataField; overload;
  172. function AddObject(const AFieldName, AContentType, ACharset: string; AFileData: TObject; const AFileName: string = ''): TIdFormDataField; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use overloaded version of AddFormField()'{$ENDIF};{$ENDIF}
  173. function AddFile(const AFieldName, AFileName: String; const AContentType: string = ''): TIdFormDataField;
  174. procedure Clear;
  175. property Boundary: string read FBoundary;
  176. property RequestContentType: string read FRequestContentType;
  177. end;
  178. EIdInvalidObjectType = class(EIdException);
  179. EIdUnsupportedOperation = class(EIdException);
  180. EIdUnsupportedTransfer = class(EIdException);
  181. EIdUnsupportedEncoding = class(EIdException);
  182. implementation
  183. uses
  184. SysUtils,
  185. IdCoderQuotedPrintable,
  186. IdCoderMIME,
  187. IdStream,
  188. IdGlobalProtocols;
  189. const
  190. cAllowedContentTransfers: array[0..4] of String = (
  191. '7bit', '8bit', 'binary', 'quoted-printable', 'base64' {do not localize}
  192. );
  193. cAllowedHeaderEncodings: array[0..2] of String = (
  194. 'Q', 'B', '8' {do not localize}
  195. );
  196. { TIdMultiPartFormDataStream }
  197. constructor TIdMultiPartFormDataStream.Create;
  198. begin
  199. inherited Create;
  200. FSize := 0;
  201. FInitialized := False;
  202. FBoundary := GenerateUniqueBoundary;
  203. FRequestContentType := sContentTypeFormData + FBoundary;
  204. FFields := TIdFormDataFields.Create(Self);
  205. end;
  206. destructor TIdMultiPartFormDataStream.Destroy;
  207. begin
  208. FreeAndNil(FFields);
  209. inherited Destroy;
  210. end;
  211. {$I IdDeprecatedImplBugOff.inc}
  212. function TIdMultiPartFormDataStream.AddObject(const AFieldName,
  213. AContentType, ACharset: string; AFileData: TObject;
  214. const AFileName: string = ''): TIdFormDataField;
  215. {$I IdDeprecatedImplBugOn.inc}
  216. begin
  217. if not (AFileData is TStream) then begin
  218. raise EIdInvalidObjectType.Create(RSMFDInvalidObjectType);
  219. end;
  220. Result := AddFormField(AFieldName, AContentType, ACharset, TStream(AFileData), AFileName);
  221. end;
  222. function TIdMultiPartFormDataStream.AddFile(const AFieldName, AFileName: String;
  223. const AContentType: string = ''): TIdFormDataField;
  224. var
  225. LStream: TIdReadFileExclusiveStream;
  226. LItem: TIdFormDataField;
  227. begin
  228. LStream := TIdReadFileExclusiveStream.Create(AFileName);
  229. try
  230. LItem := FFields.Add;
  231. except
  232. FreeAndNil(LStream);
  233. raise;
  234. end;
  235. LItem.FFieldName := AFieldName;
  236. LItem.FFileName := ExtractFileName(AFileName);
  237. LItem.FFieldStream := LStream;
  238. LItem.FCanFreeFieldStream := True;
  239. if AContentType <> '' then begin
  240. LItem.ContentType := AContentType;
  241. end else begin
  242. LItem.FContentType := GetMIMETypeFromFile(AFileName);
  243. end;
  244. LItem.FContentTransfer := sContentTransferBinary;
  245. Result := LItem;
  246. end;
  247. function TIdMultiPartFormDataStream.AddFormField(const AFieldName, AFieldValue: string;
  248. const ACharset: string = ''; const AContentType: string = ''; const AFileName: string = ''): TIdFormDataField;
  249. var
  250. LItem: TIdFormDataField;
  251. begin
  252. LItem := FFields.Add;
  253. LItem.FFieldName := AFieldName;
  254. LItem.FFileName := ExtractFileName(AFileName);
  255. LItem.FFieldValue := AFieldValue;
  256. if AContentType <> '' then begin
  257. LItem.ContentType := AContentType;
  258. end else begin
  259. LItem.FContentType := sContentTypeTextPlain;
  260. end;
  261. if ACharset <> '' then begin
  262. LItem.FCharset := ACharset;
  263. end;
  264. LItem.FContentTransfer := sContentTransferQuotedPrintable;
  265. Result := LItem;
  266. end;
  267. function TIdMultiPartFormDataStream.AddFormField(const AFieldName, AContentType, ACharset: string;
  268. AFieldValue: TStream; const AFileName: string = ''): TIdFormDataField;
  269. var
  270. LItem: TIdFormDataField;
  271. begin
  272. if not Assigned(AFieldValue) then begin
  273. raise EIdInvalidObjectType.Create(RSMFDInvalidObjectType);
  274. end;
  275. LItem := FFields.Add;
  276. LItem.FFieldName := AFieldName;
  277. LItem.FFileName := ExtractFileName(AFileName);
  278. LItem.FFieldStream := AFieldValue;
  279. if AContentType <> '' then begin
  280. LItem.ContentType := AContentType;
  281. end else begin
  282. LItem.FContentType := GetMIMETypeFromFile(AFileName);
  283. end;
  284. if ACharset <> '' then begin
  285. LItem.FCharSet := ACharset;
  286. end;
  287. LItem.FContentTransfer := sContentTransferBinary;
  288. Result := LItem;
  289. end;
  290. procedure TIdMultiPartFormDataStream.Clear;
  291. begin
  292. FInitialized := False;
  293. FFields.Clear;
  294. if FFreeInputStream then begin
  295. FInputStream.Free;
  296. end;
  297. FInputStream := nil;
  298. FFreeInputStream := False;
  299. FCurrentItem := 0;
  300. FPosition := 0;
  301. FSize := 0;
  302. SetLength(FInternalBuffer, 0);
  303. end;
  304. function TIdMultiPartFormDataStream.GenerateUniqueBoundary: string;
  305. begin
  306. // TODO: add a way for a user-defined prefix to be placed in between
  307. // the dashes and the random data, such as 'WebKitFormBoundary'...
  308. Result := '--------' + FormatDateTime('mmddyyhhnnsszzz', Now); {do not localize}
  309. end;
  310. procedure TIdMultiPartFormDataStream.CalculateSize;
  311. var
  312. I: Integer;
  313. begin
  314. FSize := 0;
  315. if FFields.Count > 0 then begin
  316. for I := 0 to FFields.Count-1 do begin
  317. FSize := FSize + FFields.Items[I].FieldSize;
  318. end;
  319. FSize := FSize + 2{'--'} + Length(Boundary) + 4{'--'+CRLF};
  320. end;
  321. end;
  322. // RLebeau - IdRead() should wrap multiple files of the same field name
  323. // using a single "multipart/mixed" MIME part, as recommended by RFC 1867
  324. function TIdMultiPartFormDataStream.IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint;
  325. var
  326. LTotalRead, LCount, LBufferCount, LRemaining : Integer;
  327. LItem: TIdFormDataField;
  328. LEncoding: IIdTextEncoding;
  329. begin
  330. if not FInitialized then begin
  331. FInitialized := True;
  332. FCurrentItem := 0;
  333. SetLength(FInternalBuffer, 0);
  334. end;
  335. LTotalRead := 0;
  336. LBufferCount := 0;
  337. while (LTotalRead < ACount) and ((Length(FInternalBuffer) > 0) or Assigned(FInputStream) or (FCurrentItem < FFields.Count)) do
  338. begin
  339. if (Length(FInternalBuffer) = 0) and (not Assigned(FInputStream)) then
  340. begin
  341. LItem := FFields.Items[FCurrentItem];
  342. EnsureEncoding(LEncoding, enc8Bit);
  343. AppendString(FInternalBuffer, LItem.FormatHeader, -1, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
  344. FInputStream := LItem.PrepareDataStream(FFreeInputStream);
  345. if not Assigned(FInputStream) then begin
  346. AppendString(FInternalBuffer, CRLF);
  347. Inc(FCurrentItem);
  348. end;
  349. end;
  350. if Length(FInternalBuffer) > 0 then begin
  351. LCount := IndyMin(ACount - LBufferCount, Length(FInternalBuffer));
  352. if LCount > 0 then begin
  353. LRemaining := Length(FInternalBuffer) - LCount;
  354. CopyTIdBytes(FInternalBuffer, 0, VBuffer, LBufferCount, LCount);
  355. if LRemaining > 0 then begin
  356. CopyTIdBytes(FInternalBuffer, LCount, FInternalBuffer, 0, LRemaining);
  357. end;
  358. SetLength(FInternalBuffer, LRemaining);
  359. LBufferCount := LBufferCount + LCount;
  360. FPosition := FPosition + LCount;
  361. LTotalRead := LTotalRead + LCount;
  362. end;
  363. end;
  364. if (LTotalRead < ACount) and (Length(FInternalBuffer) = 0) and Assigned(FInputStream) then begin
  365. LCount := TIdStreamHelper.ReadBytes(FInputStream, VBuffer, ACount - LTotalRead, LBufferCount);
  366. if LCount > 0 then begin
  367. LBufferCount := LBufferCount + LCount;
  368. LTotalRead := LTotalRead + LCount;
  369. FPosition := FPosition + LCount;
  370. end
  371. else begin
  372. SetLength(FInternalBuffer, 0);
  373. if FFreeInputStream then begin
  374. FInputStream.Free;
  375. end else begin
  376. FInputStream.Position := 0;
  377. AppendString(FInternalBuffer, CRLF);
  378. end;
  379. FInputStream := nil;
  380. FFreeInputStream := False;
  381. Inc(FCurrentItem);
  382. end;
  383. end;
  384. if (Length(FInternalBuffer) = 0) and (not Assigned(FInputStream)) and (FCurrentItem = FFields.Count) then begin
  385. AppendString(FInternalBuffer, '--' + Boundary + '--' + CRLF); {do not localize}
  386. Inc(FCurrentItem);
  387. end;
  388. end;
  389. Result := LTotalRead;
  390. end;
  391. function TIdMultiPartFormDataStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64;
  392. begin
  393. Result := 0;
  394. case AOrigin of
  395. soBeginning: begin
  396. if (AOffset = 0) then begin
  397. FInitialized := False;
  398. FPosition := 0;
  399. Result := 0;
  400. end else begin
  401. Result := FPosition;
  402. end;
  403. end;
  404. soCurrent: begin
  405. Result := FPosition;
  406. end;
  407. soEnd: begin
  408. if (AOffset = 0) then begin
  409. CalculateSize;
  410. Result := FSize;
  411. end else begin
  412. Result := FPosition;
  413. end;
  414. end;
  415. end;
  416. end;
  417. function TIdMultiPartFormDataStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint;
  418. begin
  419. raise EIdUnsupportedOperation.Create(RSUnsupportedOperation);
  420. end;
  421. procedure TIdMultiPartFormDataStream.IdSetSize(ASize: Int64);
  422. begin
  423. raise EIdUnsupportedOperation.Create(RSUnsupportedOperation);
  424. end;
  425. { TIdFormDataFields }
  426. function TIdFormDataFields.Add: TIdFormDataField;
  427. begin
  428. Result := TIdFormDataField(inherited Add);
  429. end;
  430. constructor TIdFormDataFields.Create(AMPStream: TIdMultiPartFormDataStream);
  431. begin
  432. inherited Create(TIdFormDataField);
  433. FParentStream := AMPStream;
  434. end;
  435. function TIdFormDataFields.GetFormDataField(AIndex: Integer): TIdFormDataField;
  436. begin
  437. Result := TIdFormDataField(inherited Items[AIndex]);
  438. end;
  439. { TIdFormDataField }
  440. constructor TIdFormDataField.Create(Collection: TCollection);
  441. var
  442. LDefCharset: TIdCharSet;
  443. begin
  444. inherited Create(Collection);
  445. FFieldStream := nil;
  446. FFileName := '';
  447. FFieldName := '';
  448. FContentType := '';
  449. FCanFreeFieldStream := False;
  450. // it's not clear when FHeaderEncoding should be Q not B.
  451. // Comments welcome on atozedsoftware.indy.general
  452. LDefCharset := IdGetDefaultCharSet;
  453. case LDefCharset of
  454. idcs_ISO_8859_1:
  455. begin
  456. FHeaderEncoding := 'Q'; { quoted-printable } {Do not Localize}
  457. FHeaderCharSet := IdCharsetNames[LDefCharset];
  458. end;
  459. idcs_UNICODE_1_1:
  460. begin
  461. FHeaderEncoding := 'B'; { base64 } {Do not Localize}
  462. FHeaderCharSet := IdCharsetNames[idcs_UTF_8];
  463. end;
  464. else
  465. begin
  466. FHeaderEncoding := 'B'; { base64 } {Do not Localize}
  467. FHeaderCharSet := IdCharsetNames[LDefCharset];
  468. end;
  469. end;
  470. end;
  471. destructor TIdFormDataField.Destroy;
  472. begin
  473. if Assigned(FFieldStream) then begin
  474. if FCanFreeFieldStream then begin
  475. FFieldStream.Free;
  476. end;
  477. end;
  478. inherited Destroy;
  479. end;
  480. function TIdFormDataField.FormatHeader: string;
  481. var
  482. LBoundary: string;
  483. begin
  484. LBoundary := '--' + TIdFormDataFields(Collection).MultipartFormDataStream.Boundary; {do not localize}
  485. // TODO: when STRING_IS_ANSI is defined, provide a way for the user to specify the AnsiString encoding for header values...
  486. Result := IndyFormat('%s' + CRLF + sContentDispositionPlaceHolder,
  487. [LBoundary, EncodeHeader(FieldName, '', FHeaderEncoding, FHeaderCharSet)]); {do not localize}
  488. if Length(FileName) > 0 then begin
  489. Result := Result + IndyFormat(sFileNamePlaceHolder,
  490. [EncodeHeader(FileName, '', FHeaderEncoding, FHeaderCharSet)]); {do not localize}
  491. end;
  492. Result := Result + CRLF;
  493. if Length(ContentType) > 0 then begin
  494. Result := Result + IndyFormat(sContentTypePlaceHolder, [ContentType]); {do not localize}
  495. if Length(CharSet) > 0 then begin
  496. Result := Result + IndyFormat(sCharsetPlaceHolder, [Charset]); {do not localize}
  497. end;
  498. Result := Result + CRLF;
  499. end;
  500. if Length(FContentTransfer) > 0 then begin
  501. Result := Result + IndyFormat(sContentTransferPlaceHolder + CRLF, [FContentTransfer]);
  502. end;
  503. Result := Result + CRLF;
  504. end;
  505. function TIdFormDataField.GetFieldSize: Int64;
  506. var
  507. LStream: TStream;
  508. LOldPos: TIdStreamSize;
  509. {$IFDEF STRING_IS_ANSI}
  510. LBytes: TIdBytes;
  511. {$ENDIF}
  512. I: Integer;
  513. begin
  514. {$IFDEF STRING_IS_ANSI}
  515. LBytes := nil; // keep the compiler happy
  516. {$ENDIF}
  517. Result := Length(FormatHeader);
  518. if Assigned(FFieldStream) then begin
  519. I := PosInStrArray(ContentTransfer, cAllowedContentTransfers, False);
  520. if I <= 2 then begin
  521. // need to include an explicit CRLF at the end of the data
  522. Result := Result + FFieldStream.Size + 2{CRLF};
  523. end else
  524. begin
  525. LStream := TIdCalculateSizeStream.Create;
  526. try
  527. LOldPos := FFieldStream.Position;
  528. try
  529. if I = 3 then begin
  530. TIdEncoderQuotedPrintable.EncodeStream(FFieldStream, LStream);
  531. // the encoded text always includes a CRLF at the end...
  532. Result := Result + LStream.Size {+2};
  533. end else begin
  534. TIdEncoderMime.EncodeStream(FFieldStream, LStream);
  535. // the encoded text does not include a CRLF at the end...
  536. Result := Result + LStream.Size + 2;
  537. end;
  538. finally
  539. FFieldStream.Position := LOldPos;
  540. end;
  541. finally
  542. LStream.Free;
  543. end;
  544. end;
  545. end
  546. else if Length(FFieldValue) > 0 then begin
  547. I := PosInStrArray(FContentTransfer, cAllowedContentTransfers, False);
  548. if I <= 0 then begin
  549. // 7bit
  550. {$IFDEF STRING_IS_UNICODE}
  551. I := IndyTextEncoding_ASCII.GetByteCount(FFieldValue);
  552. {$ELSE}
  553. // the methods useful for calculating a length without actually
  554. // encoding are protected, so have to actually encode the
  555. // string to find out the final length...
  556. LBytes := RawToBytes(FFieldValue[1], Length(FFieldValue));
  557. CheckByteEncoding(LBytes, CharsetToEncoding(FCharset), IndyTextEncoding_ASCII);
  558. I := Length(LBytes);
  559. {$ENDIF}
  560. // need to include an explicit CRLF at the end of the data
  561. Result := Result + I + 2{CRLF};
  562. end
  563. else if (I = 1) or (I = 2) then begin
  564. // 8bit/binary
  565. {$IFDEF STRING_IS_UNICODE}
  566. I := CharsetToEncoding(FCharset).GetByteCount(FFieldValue);
  567. {$ELSE}
  568. I := Length(FFieldValue);
  569. {$ENDIF}
  570. // need to include an explicit CRLF at the end of the data
  571. Result := Result + I + 2{CRLF};
  572. end else
  573. begin
  574. LStream := TIdCalculateSizeStream.Create;
  575. try
  576. {$IFNDEF STRING_IS_UNICODE}
  577. LBytes := RawToBytes(FFieldValue[1], Length(FFieldValue));
  578. {$ENDIF}
  579. if I = 3 then begin
  580. // quoted-printable
  581. {$IFDEF STRING_IS_UNICODE}
  582. TIdEncoderQuotedPrintable.EncodeString(FFieldValue, LStream, CharsetToEncoding(FCharset));
  583. {$ELSE}
  584. TIdEncoderQuotedPrintable.EncodeBytes(LBytes, LStream);
  585. {$ENDIF}
  586. // the encoded text always includes a CRLF at the end...
  587. Result := Result + LStream.Size {+2};
  588. end else begin
  589. // base64
  590. {$IFDEF STRING_IS_UNICODE}
  591. TIdEncoderMIME.EncodeString(FFieldValue, LStream, CharsetToEncoding(FCharset){$IFDEF STRING_IS_ANSI}, IndyTextEncoding_OSDefault{$ENDIF});
  592. {$ELSE}
  593. TIdEncoderMIME.EncodeBytes(LBytes, LStream);
  594. {$ENDIF}
  595. // the encoded text does not include a CRLF at the end...
  596. Result := Result + LStream.Size + 2;
  597. end;
  598. finally
  599. LStream.Free;
  600. end;
  601. end;
  602. end else begin
  603. // need to include an explicit CRLF at the end of blank text
  604. Result := Result + 2{CRLF};
  605. end;
  606. end;
  607. function TIdFormDataField.PrepareDataStream(var VCanFree: Boolean): TStream;
  608. var
  609. I: Integer;
  610. {$IFDEF STRING_IS_ANSI}
  611. LBytes: TIdBytes;
  612. {$ENDIF}
  613. begin
  614. {$IFDEF STRING_IS_ANSI}
  615. LBytes := nil; // keep the compiler happy
  616. {$ENDIF}
  617. Result := nil;
  618. VCanFree := False;
  619. if Assigned(FFieldStream) then begin
  620. FFieldStream.Position := 0;
  621. I := PosInStrArray(FContentTransfer, cAllowedContentTransfers, False);
  622. if I <= 2 then begin
  623. Result := FFieldStream;
  624. end else begin
  625. Result := TMemoryStream.Create;
  626. try
  627. if I = 3 then begin
  628. TIdEncoderQuotedPrintable.EncodeStream(FFieldStream, Result);
  629. // the encoded text always includes a CRLF at the end...
  630. end else begin
  631. TIdEncoderMime.EncodeStream(FFieldStream, Result);
  632. // the encoded text does not include a CRLF at the end...
  633. WriteStringToStream(Result, CRLF);
  634. end;
  635. Result.Position := 0;
  636. except
  637. FreeAndNil(Result);
  638. raise;
  639. end;
  640. VCanFree := True;
  641. end;
  642. end
  643. else if Length(FFieldValue) > 0 then begin
  644. Result := TMemoryStream.Create;
  645. try
  646. {$IFDEF STRING_IS_ANSI}
  647. LBytes := RawToBytes(FFieldValue[1], Length(FFieldValue));
  648. {$ENDIF}
  649. I := PosInStrArray(FContentTransfer, cAllowedContentTransfers, False);
  650. if I <= 0 then begin
  651. // 7bit
  652. {$IFDEF STRING_IS_UNICODE}
  653. WriteStringToStream(Result, FFieldValue, IndyTextEncoding_ASCII);
  654. {$ELSE}
  655. CheckByteEncoding(LBytes, CharsetToEncoding(FCharset), IndyTextEncoding_ASCII);
  656. WriteTIdBytesToStream(Result, LBytes);
  657. {$ENDIF}
  658. // need to include an explicit CRLF at the end of the data
  659. WriteStringToStream(Result, CRLF);
  660. end
  661. else if (I = 1) or (I = 2) then begin
  662. // 8bit/binary
  663. {$IFDEF STRING_IS_UNICODE}
  664. WriteStringToStream(Result, FFieldValue, CharsetToEncoding(FCharset));
  665. {$ELSE}
  666. WriteTIdBytesToStream(Result, LBytes);
  667. {$ENDIF}
  668. // need to include an explicit CRLF at the end of the data
  669. WriteStringToStream(Result, CRLF);
  670. end else
  671. begin
  672. if I = 3 then begin
  673. // quoted-printable
  674. {$IFDEF STRING_IS_UNICODE}
  675. TIdEncoderQuotedPrintable.EncodeString(FFieldValue, Result, CharsetToEncoding(FCharset));
  676. {$ELSE}
  677. TIdEncoderQuotedPrintable.EncodeBytes(LBytes, Result);
  678. {$ENDIF}
  679. // the encoded text always includes a CRLF at the end...
  680. end else begin
  681. // base64
  682. {$IFDEF STRING_IS_UNICODE}
  683. TIdEncoderMIME.EncodeString(FFieldValue, Result, CharsetToEncoding(FCharset));
  684. {$ELSE}
  685. TIdEncoderMIME.EncodeBytes(LBytes, Result);
  686. {$ENDIF}
  687. // the encoded text does not include a CRLF at the end...
  688. WriteStringToStream(Result, CRLF);
  689. end;
  690. end;
  691. except
  692. FreeAndNil(Result);
  693. raise;
  694. end;
  695. Result.Position := 0;
  696. VCanFree := True;
  697. end;
  698. end;
  699. function TIdFormDataField.GetFieldStream: TStream;
  700. begin
  701. if not Assigned(FFieldStream) then begin
  702. raise EIdInvalidObjectType.Create(RSMFDInvalidObjectType);
  703. end;
  704. Result := FFieldStream;
  705. end;
  706. function TIdFormDataField.GetFieldValue: string;
  707. begin
  708. if Assigned(FFieldStream) then begin
  709. raise EIdInvalidObjectType.Create(RSMFDInvalidObjectType);
  710. end;
  711. Result := FFieldValue;
  712. end;
  713. procedure TIdFormDataField.SetCharset(const Value: string);
  714. begin
  715. FCharset := Value;
  716. end;
  717. procedure TIdFormDataField.SetContentTransfer(const Value: string);
  718. begin
  719. if Length(Value) > 0 then begin
  720. if PosInStrArray(Value, cAllowedContentTransfers, False) = -1 then begin
  721. raise EIdUnsupportedTransfer.Create(RSMFDInvalidTransfer);
  722. end;
  723. end;
  724. FContentTransfer := Value;
  725. end;
  726. procedure TIdFormDataField.SetContentType(const Value: string);
  727. var
  728. LContentType, LCharSet: string;
  729. begin
  730. if Length(Value) > 0 then begin
  731. LContentType := Value;
  732. end
  733. else if Length(FFileName) > 0 then begin
  734. LContentType := GetMIMETypeFromFile(FFileName);
  735. end
  736. else begin
  737. LContentType := sContentTypeOctetStream;
  738. end;
  739. FContentType := RemoveHeaderEntry(LContentType, 'charset', LCharSet, QuoteMIME); {do not localize}
  740. // RLebeau: per RFC 2045 Section 5.2:
  741. //
  742. // Default RFC 822 messages without a MIME Content-Type header are taken
  743. // by this protocol to be plain text in the US-ASCII character set,
  744. // which can be explicitly specified as:
  745. //
  746. // Content-type: text/plain; charset=us-ascii
  747. //
  748. // This default is assumed if no Content-Type header field is specified.
  749. // It is also recommend that this default be assumed when a
  750. // syntactically invalid Content-Type header field is encountered. In
  751. // the presence of a MIME-Version header field and the absence of any
  752. // Content-Type header field, a receiving User Agent can also assume
  753. // that plain US-ASCII text was the sender's intent. Plain US-ASCII
  754. // text may still be assumed in the absence of a MIME-Version or the
  755. // presence of an syntactically invalid Content-Type header field, but
  756. // the sender's intent might have been otherwise.
  757. if (LCharSet = '') and (FCharSet = '') and IsHeaderMediaType(FContentType, 'text') then begin {do not localize}
  758. LCharSet := 'us-ascii'; {do not localize}
  759. end;
  760. {RLebeau: override the current CharSet only if the header specifies a new value}
  761. if LCharSet <> '' then begin
  762. FCharSet := LCharSet;
  763. end;
  764. end;
  765. procedure TIdFormDataField.SetFieldName(const Value: string);
  766. begin
  767. FFieldName := Value;
  768. end;
  769. procedure TIdFormDataField.SetFieldStream(const Value: TStream);
  770. begin
  771. if not Assigned(Value) then begin
  772. raise EIdInvalidObjectType.Create(RSMFDInvalidObjectType);
  773. end;
  774. if Assigned(FFieldStream) and FCanFreeFieldStream then begin
  775. FFieldStream.Free;
  776. end;
  777. FFieldValue := '';
  778. FFieldStream := Value;
  779. FCanFreeFieldStream := False;
  780. end;
  781. procedure TIdFormDataField.SetFieldValue(const Value: string);
  782. begin
  783. if Assigned(FFieldStream) then begin
  784. if FCanFreeFieldStream then begin
  785. FFieldStream.Free;
  786. end;
  787. FFieldStream := nil;
  788. FCanFreeFieldStream := False;
  789. end;
  790. FFieldValue := Value;
  791. end;
  792. procedure TIdFormDataField.SetFileName(const Value: string);
  793. begin
  794. FFileName := ExtractFileName(Value);
  795. end;
  796. procedure TIdFormDataField.SetHeaderCharSet(const Value: string);
  797. begin
  798. FHeaderCharset := Value;
  799. end;
  800. procedure TIdFormDataField.SetHeaderEncoding(const Value: Char);
  801. begin
  802. if FHeaderEncoding <> Value then begin
  803. if PosInStrArray(Value, cAllowedHeaderEncodings, False) = -1 then begin
  804. raise EIdUnsupportedEncoding.Create(RSMFDInvalidEncoding);
  805. end;
  806. FHeaderEncoding := Value;
  807. end;
  808. end;
  809. end.