IdHeaderList.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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.9 10/26/2004 10:10:58 PM JPMugaas
  18. Updated refs.
  19. Rev 1.8 3/6/2004 2:53:30 PM JPMugaas
  20. Cleaned up an if as per Bug #79.
  21. Rev 1.7 2004.02.03 5:43:42 PM czhower
  22. Name changes
  23. Rev 1.6 2004.01.27 1:39:26 AM czhower
  24. CharIsInSet bug fix
  25. Rev 1.5 1/22/2004 3:50:04 PM SPerry
  26. fixed set problems (with CharIsInSet)
  27. Rev 1.4 1/22/2004 7:10:06 AM JPMugaas
  28. Tried to fix AnsiSameText depreciation.
  29. Rev 1.3 10/5/2003 11:43:50 PM GGrieve
  30. Use IsLeadChar
  31. Rev 1.2 10/4/2003 9:15:14 PM GGrieve
  32. DotNet changes
  33. Rev 1.1 2/25/2003 12:56:20 PM JPMugaas
  34. Updated with Hadi's fix for a bug . If complete boolean expression i on, you
  35. may get an Index out of range error.
  36. Rev 1.0 11/13/2002 07:53:52 AM JPMugaas
  37. 2002-Jan-27 Don Siders
  38. - Modified FoldLine to include Comma in break character set.
  39. 2000-May-31 J. Peter Mugaas
  40. - started this class to facilitate some work on Indy so we don't have to
  41. convert '=' to ":" and vice-versa just to use the Values property.
  42. }
  43. unit IdHeaderList;
  44. {
  45. NOTE: This is a modification of Borland's TStrings definition in a
  46. TStringList descendant. I had to conceal the original Values to do
  47. this since most of low level property setting routines aren't virtual
  48. and are private.
  49. }
  50. interface
  51. {$i IdCompilerDefines.inc}
  52. uses
  53. Classes, IdGlobalProtocols;
  54. type
  55. TIdHeaderList = class(TStringList)
  56. protected
  57. FNameValueSeparator : String;
  58. FUnfoldLines : Boolean;
  59. FFoldLines : Boolean;
  60. FFoldLinesLength : Integer;
  61. FQuoteType: TIdHeaderQuotingType;
  62. //
  63. procedure AssignTo(Dest: TPersistent); override;
  64. {This deletes lines which were folded}
  65. Procedure DeleteFoldedLines(Index : Integer);
  66. {This folds one line into several lines}
  67. function FoldLine(AString : string): TStrings; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use FoldLineToList()'{$ENDIF};{$ENDIF}
  68. procedure FoldLineToList(AString : string; ALines: TStrings);
  69. {Folds lines and inserts them into a position, Index}
  70. procedure FoldAndInsert(AString : String; Index : Integer);
  71. {Name property get method}
  72. function GetName(Index: Integer): string;
  73. {Value property get method}
  74. function GetValue(const AName: string): string;
  75. {Value property get method}
  76. function GetParam(const AName, AParam: string): string;
  77. function GetAllParams(const AName: string): string;
  78. {Value property set method}
  79. procedure SetValue(const AName, AValue: string);
  80. {Value property set method}
  81. procedure SetParam(const AName, AParam, AValue: string);
  82. procedure SetAllParams(const AName, AValue: string);
  83. {Gets a value from a string}
  84. function GetValueFromLine(var VLine : Integer) : String;
  85. procedure SkipValueAtLine(var VLine : Integer);
  86. public
  87. procedure AddStrings(Strings: TStrings); override;
  88. { This method extracts "name=value" strings from the ASrc TStrings and adds
  89. them to this list using our delimiter defined in NameValueSeparator. }
  90. procedure AddStdValues(ASrc: TStrings);
  91. { This method adds a single name/value pair to this list using our delimiter
  92. defined in NameValueSeparator. }
  93. procedure AddValue(const AName, AValue: string); // allows duplicates
  94. { This method extracts all of the values from this list and puts them in the
  95. ADest TStrings as "name=value" strings.}
  96. procedure ConvertToStdValues(ADest: TStrings);
  97. constructor Create(AQuoteType: TIdHeaderQuotingType);
  98. { This method, given a name specified by AName, extracts all of the values
  99. for that name and puts them in a new string list (just the values) one
  100. per line in the ADest TIdStrings.}
  101. procedure Extract(const AName: string; ADest: TStrings);
  102. { This property works almost exactly as Borland's IndexOfName except it
  103. uses our delimiter defined in NameValueSeparator }
  104. function IndexOfName(const AName: string): Integer; reintroduce;
  105. { This property works almost exactly as Borland's Names except it uses
  106. our delimiter defined in NameValueSeparator }
  107. property Names[Index: Integer]: string read GetName;
  108. { This property works almost exactly as Borland's Values except it uses
  109. our delimiter defined in NameValueSeparator }
  110. property Values[const Name: string]: string read GetValue write SetValue;
  111. property Params[const Name, Param: string]: string read GetParam write SetParam;
  112. property AllParams[const Name: string]: string read GetAllParams write SetAllParams;
  113. { This is the separator we need to separate the name from the value }
  114. property NameValueSeparator : String read FNameValueSeparator
  115. write FNameValueSeparator;
  116. { Should we unfold lines so that continuation header data is returned as
  117. well}
  118. property UnfoldLines : Boolean read FUnfoldLines write FUnfoldLines;
  119. { Should we fold lines we the Values(x) property is set with an
  120. assignment }
  121. property FoldLines : Boolean read FFoldLines write FFoldLines;
  122. { The Wrap position for our folded lines }
  123. property FoldLength : Integer read FFoldLinesLength write FFoldLinesLength;
  124. end;
  125. implementation
  126. uses
  127. IdGlobal,
  128. SysUtils;
  129. { TIdHeaderList }
  130. procedure TIdHeaderList.AddStdValues(ASrc: TStrings);
  131. var
  132. i: integer;
  133. begin
  134. BeginUpdate;
  135. try
  136. for i := 0 to ASrc.Count - 1 do begin
  137. AddValue(ASrc.Names[i], IndyValueFromIndex(ASrc, i));
  138. end;
  139. finally
  140. EndUpdate;
  141. end;
  142. end;
  143. procedure TIdHeaderList.AddValue(const AName, AValue: string);
  144. var
  145. I: Integer;
  146. begin
  147. if (AName <> '') and (AValue <> '') then begin {Do not Localize}
  148. I := Add(''); {Do not Localize}
  149. if FFoldLines then begin
  150. FoldAndInsert(AName + FNameValueSeparator + AValue, I);
  151. end else begin
  152. Put(I, AName + FNameValueSeparator + AValue);
  153. end;
  154. end;
  155. end;
  156. procedure TIdHeaderList.AddStrings(Strings: TStrings);
  157. begin
  158. if Strings is TIdHeaderList then begin
  159. inherited AddStrings(Strings);
  160. end else begin
  161. AddStdValues(Strings);
  162. end;
  163. end;
  164. procedure TIdHeaderList.AssignTo(Dest: TPersistent);
  165. begin
  166. if (Dest is TStrings) and not (Dest is TIdHeaderList) then begin
  167. ConvertToStdValues(TStrings(Dest));
  168. end else begin
  169. inherited AssignTo(Dest);
  170. end;
  171. end;
  172. procedure TIdHeaderList.ConvertToStdValues(ADest: TStrings);
  173. var
  174. idx: Integer;
  175. LName, LValue: string;
  176. begin
  177. ADest.BeginUpdate;
  178. try
  179. idx := 0;
  180. while idx < Count do
  181. begin
  182. LName := GetName(idx);
  183. LValue := GetValueFromLine(idx);
  184. IndyAddPair(ADest, LName, LValue);
  185. end;
  186. finally
  187. ADest.EndUpdate;
  188. end;
  189. end;
  190. constructor TIdHeaderList.Create(AQuoteType: TIdHeaderQuotingType);
  191. begin
  192. inherited Create;
  193. FNameValueSeparator := ': '; {Do not Localize}
  194. FUnfoldLines := True;
  195. FFoldLines := True;
  196. { 78 was specified by a message draft available at
  197. http://www.imc.org/draft-ietf-drums-msg-fmt }
  198. // HTTP does not technically have a limitation on line lengths
  199. FFoldLinesLength := iif(AQuoteType = QuoteHTTP, MaxInt, 78);
  200. FQuoteType := AQuoteType;
  201. end;
  202. procedure TIdHeaderList.DeleteFoldedLines(Index: Integer);
  203. begin
  204. Inc(Index); {skip the current line}
  205. if Index < Count then begin
  206. while (Index < Count) and CharIsInSet(Get(Index), 1, LWS) do begin {Do not Localize}
  207. Delete(Index);
  208. end;
  209. end;
  210. end;
  211. procedure TIdHeaderList.Extract(const AName: string; ADest: TStrings);
  212. var
  213. idx : Integer;
  214. begin
  215. if Assigned(ADest) then begin
  216. ADest.BeginUpdate;
  217. try
  218. idx := 0;
  219. while idx < Count do
  220. begin
  221. if TextIsSame(AName, GetName(idx)) then begin
  222. ADest.Add(GetValueFromLine(idx));
  223. end else begin
  224. SkipValueAtLine(idx);
  225. end;
  226. end;
  227. finally
  228. ADest.EndUpdate;
  229. end;
  230. end;
  231. end;
  232. procedure TIdHeaderList.FoldAndInsert(AString : String; Index: Integer);
  233. var
  234. LStrs : TStrings;
  235. idx : Integer;
  236. begin
  237. LStrs := TStringList.Create;
  238. try
  239. FoldLineToList(AString, LStrs);
  240. idx := LStrs.Count - 1;
  241. Put(Index, LStrs[idx]);
  242. {We decrement by one because we put the last string into the HeaderList}
  243. Dec(idx);
  244. while idx > -1 do
  245. begin
  246. Insert(Index, LStrs[idx]);
  247. Dec(idx);
  248. end;
  249. finally
  250. FreeAndNil(LStrs);
  251. end; //finally
  252. end;
  253. {$I IdDeprecatedImplBugOff.inc}
  254. function TIdHeaderList.FoldLine(AString : string): TStrings;
  255. {$I IdDeprecatedImplBugOn.inc}
  256. begin
  257. Result := TStringList.Create;
  258. try
  259. FoldLineToList(AString, Result);
  260. except
  261. FreeAndNil(Result);
  262. raise;
  263. end;
  264. end;
  265. procedure TIdHeaderList.FoldLineToList(AString : string; ALines: TStrings);
  266. var
  267. s : String;
  268. begin
  269. {we specify a space so that starts a folded line}
  270. s := IndyWrapText(AString, EOL+' ', LWS+',', FFoldLinesLength); {Do not Localize}
  271. if s <> '' then begin
  272. ALines.BeginUpdate;
  273. try
  274. repeat
  275. ALines.Add(TrimRight(Fetch(s, EOL)));
  276. until s = ''; {Do not Localize};
  277. finally
  278. ALines.EndUpdate;
  279. end;
  280. end;
  281. end;
  282. function TIdHeaderList.GetName(Index: Integer): string;
  283. var
  284. I : Integer;
  285. begin
  286. Result := Get(Index);
  287. {We trim right to remove space to accomodate header errors such as
  288. Message-ID:<asdf@fdfs
  289. }
  290. I := IndyPos(TrimRight(FNameValueSeparator), Result);
  291. if I <> 0 then begin
  292. SetLength(Result, I - 1);
  293. end else begin
  294. SetLength(Result, 0);
  295. end;
  296. end;
  297. function TIdHeaderList.GetValue(const AName: string): string;
  298. var
  299. idx: Integer;
  300. begin
  301. idx := IndexOfName(AName);
  302. Result := GetValueFromLine(idx);
  303. end;
  304. function TIdHeaderList.GetValueFromLine(var VLine: Integer): String;
  305. var
  306. LLine, LSep: string;
  307. P: Integer;
  308. begin
  309. if (VLine >= 0) and (VLine < Count) then begin
  310. LLine := Get(VLine);
  311. Inc(VLine);
  312. {We trim right to remove space to accomodate header errors such as
  313. Message-ID:<asdf@fdfs
  314. }
  315. LSep := TrimRight(FNameValueSeparator);
  316. P := IndyPos(LSep, LLine);
  317. Result := TrimLeft(Copy(LLine, P + Length(LSep), MaxInt));
  318. if FUnfoldLines then begin
  319. while VLine < Count do begin
  320. LLine := Get(VLine);
  321. // s[1] is safe since header lines cannot be empty as that causes then end of the header block
  322. if not CharIsInSet(LLine, 1, LWS) then begin
  323. Break;
  324. end;
  325. Result := Trim(Result) + ' ' + Trim(LLine); {Do not Localize}
  326. Inc(VLine);
  327. end;
  328. end;
  329. // User may be fetching a folded line directly.
  330. Result := Trim(Result);
  331. end else begin
  332. Result := ''; {Do not Localize}
  333. end;
  334. end;
  335. procedure TIdHeaderList.SkipValueAtLine(var VLine: Integer);
  336. begin
  337. if (VLine >= 0) and (VLine < Count) then begin
  338. Inc(VLine);
  339. if FUnfoldLines then begin
  340. while VLine < Count do begin
  341. // s[1] is safe since header lines cannot be empty as that causes then end of the header block
  342. if not CharIsInSet(Get(VLine), 1, LWS) then begin
  343. Break;
  344. end;
  345. Inc(VLine);
  346. end;
  347. end;
  348. end;
  349. end;
  350. function TIdHeaderList.GetParam(const AName, AParam: string): string;
  351. var
  352. s: string;
  353. LQuoteType: TIdHeaderQuotingType;
  354. begin
  355. s := Values[AName];
  356. if s <> '' then begin
  357. LQuoteType := FQuoteType;
  358. case LQuoteType of
  359. QuoteRFC822: begin
  360. if PosInStrArray(AName, ['Content-Type', 'Content-Disposition'], False) <> -1 then begin {Do not Localize}
  361. LQuoteType := QuoteMIME;
  362. end;
  363. end;
  364. QuoteMIME: begin
  365. if PosInStrArray(AName, ['Content-Type', 'Content-Disposition'], False) = -1 then begin {Do not Localize}
  366. LQuoteType := QuoteRFC822;
  367. end;
  368. end;
  369. end;
  370. Result := ExtractHeaderSubItem(s, AParam, LQuoteType);
  371. end else begin
  372. Result := '';
  373. end;
  374. end;
  375. function TIdHeaderList.GetAllParams(const AName: string): string;
  376. var
  377. s: string;
  378. begin
  379. s := Values[AName];
  380. if s <> '' then begin
  381. Fetch(s, ';'); {do not localize}
  382. Result := Trim(s);
  383. end else begin
  384. Result := '';
  385. end;
  386. end;
  387. function TIdHeaderList.IndexOfName(const AName: string): Integer;
  388. var
  389. i: Integer;
  390. begin
  391. Result := -1;
  392. for i := 0 to Count - 1 do begin
  393. if TextIsSame(GetName(i), AName) then begin
  394. Result := i;
  395. Exit;
  396. end;
  397. end;
  398. end;
  399. procedure TIdHeaderList.SetValue(const AName, AValue: string);
  400. var
  401. I: Integer;
  402. begin
  403. I := IndexOfName(AName);
  404. if AValue <> '' then begin {Do not Localize}
  405. if I < 0 then begin
  406. I := Add(''); {Do not Localize}
  407. end;
  408. if FFoldLines then begin
  409. DeleteFoldedLines(I);
  410. FoldAndInsert(AName + FNameValueSeparator + AValue, I);
  411. end else begin
  412. Put(I, AName + FNameValueSeparator + AValue);
  413. end;
  414. end
  415. else if I >= 0 then begin
  416. if FFoldLines then begin
  417. DeleteFoldedLines(I);
  418. end;
  419. Delete(I);
  420. end;
  421. end;
  422. procedure TIdHeaderList.SetParam(const AName, AParam, AValue: string);
  423. var
  424. LQuoteType: TIdHeaderQuotingType;
  425. begin
  426. LQuoteType := FQuoteType;
  427. case LQuoteType of
  428. QuoteRFC822: begin
  429. if PosInStrArray(AName, ['Content-Type', 'Content-Disposition'], False) <> -1 then begin {Do not Localize}
  430. LQuoteType := QuoteMIME;
  431. end;
  432. end;
  433. QuoteMIME: begin
  434. if PosInStrArray(AName, ['Content-Type', 'Content-Disposition'], False) = -1 then begin {Do not Localize}
  435. LQuoteType := QuoteRFC822;
  436. end;
  437. end;
  438. end;
  439. Values[AName] := ReplaceHeaderSubItem(Values[AName], AParam, AValue, LQuoteType);
  440. end;
  441. procedure TIdHeaderList.SetAllParams(const AName, AValue: string);
  442. var
  443. LValue: string;
  444. begin
  445. LValue := Values[AName];
  446. if LValue <> '' then
  447. begin
  448. LValue := ExtractHeaderItem(LValue);
  449. if AValue <> '' then begin
  450. LValue := LValue + '; ' + AValue; {do not localize}
  451. end;
  452. Values[AName] := LValue;
  453. end;
  454. end;
  455. end.