IdHeaderList.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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. procedure FoldLineToList(AString : string; ALines: TStrings);
  68. {Folds lines and inserts them into a position, Index}
  69. procedure FoldAndInsert(AString : String; Index : Integer);
  70. {Name property get method}
  71. function GetName(Index: Integer): string;
  72. {Value property get method}
  73. function GetValue(const AName: string): string;
  74. {Value property get method}
  75. function GetParam(const AName, AParam: string): string;
  76. function GetAllParams(const AName: string): string;
  77. {Value property set method}
  78. procedure SetValue(const AName, AValue: string);
  79. {Value property set method}
  80. procedure SetParam(const AName, AParam, AValue: string);
  81. procedure SetAllParams(const AName, AValue: string);
  82. {Gets a value from a string}
  83. function GetValueFromLine(var VLine : Integer) : String;
  84. procedure SkipValueAtLine(var VLine : Integer);
  85. public
  86. procedure AddStrings(Strings: TStrings); override;
  87. { This method extracts "name=value" strings from the ASrc TStrings and adds
  88. them to this list using our delimiter defined in NameValueSeparator. }
  89. procedure AddStdValues(ASrc: TStrings);
  90. { This method adds a single name/value pair to this list using our delimiter
  91. defined in NameValueSeparator. }
  92. procedure AddValue(const AName, AValue: string); // allows duplicates
  93. { This method extracts all of the values from this list and puts them in the
  94. ADest TStrings as "name=value" strings.}
  95. procedure ConvertToStdValues(ADest: TStrings);
  96. constructor Create(AQuoteType: TIdHeaderQuotingType);
  97. { This method, given a name specified by AName, extracts all of the values
  98. for that name and puts them in a new string list (just the values) one
  99. per line in the ADest TIdStrings.}
  100. procedure Extract(const AName: string; ADest: TStrings);
  101. { This property works almost exactly as Borland's IndexOfName except it
  102. uses our delimiter defined in NameValueSeparator }
  103. function IndexOfName(const AName: string): Integer; reintroduce;
  104. { This property works almost exactly as Borland's Names except it uses
  105. our delimiter defined in NameValueSeparator }
  106. property Names[Index: Integer]: string read GetName;
  107. { This property works almost exactly as Borland's Values except it uses
  108. our delimiter defined in NameValueSeparator }
  109. property Values[const Name: string]: string read GetValue write SetValue;
  110. property Params[const Name, Param: string]: string read GetParam write SetParam;
  111. property AllParams[const Name: string]: string read GetAllParams write SetAllParams;
  112. { This is the separator we need to separate the name from the value }
  113. property NameValueSeparator : String read FNameValueSeparator
  114. write FNameValueSeparator;
  115. { Should we unfold lines so that continuation header data is returned as
  116. well}
  117. property UnfoldLines : Boolean read FUnfoldLines write FUnfoldLines;
  118. { Should we fold lines we the Values(x) property is set with an
  119. assignment }
  120. property FoldLines : Boolean read FFoldLines write FFoldLines;
  121. { The Wrap position for our folded lines }
  122. property FoldLength : Integer read FFoldLinesLength write FFoldLinesLength;
  123. end;
  124. implementation
  125. uses
  126. IdGlobal,
  127. SysUtils;
  128. { TIdHeaderList }
  129. procedure TIdHeaderList.AddStdValues(ASrc: TStrings);
  130. var
  131. i: integer;
  132. begin
  133. BeginUpdate;
  134. try
  135. for i := 0 to ASrc.Count - 1 do begin
  136. AddValue(ASrc.Names[i], IndyValueFromIndex(ASrc, i));
  137. end;
  138. finally
  139. EndUpdate;
  140. end;
  141. end;
  142. procedure TIdHeaderList.AddValue(const AName, AValue: string);
  143. var
  144. I: Integer;
  145. begin
  146. if (AName <> '') and (AValue <> '') then begin {Do not Localize}
  147. I := Add(''); {Do not Localize}
  148. if FFoldLines then begin
  149. FoldAndInsert(AName + FNameValueSeparator + AValue, I);
  150. end else begin
  151. Put(I, AName + FNameValueSeparator + AValue);
  152. end;
  153. end;
  154. end;
  155. procedure TIdHeaderList.AddStrings(Strings: TStrings);
  156. begin
  157. if Strings is TIdHeaderList then begin
  158. inherited AddStrings(Strings);
  159. end else begin
  160. AddStdValues(Strings);
  161. end;
  162. end;
  163. procedure TIdHeaderList.AssignTo(Dest: TPersistent);
  164. begin
  165. if (Dest is TStrings) and not (Dest is TIdHeaderList) then begin
  166. ConvertToStdValues(TStrings(Dest));
  167. end else begin
  168. inherited AssignTo(Dest);
  169. end;
  170. end;
  171. procedure TIdHeaderList.ConvertToStdValues(ADest: TStrings);
  172. var
  173. idx: Integer;
  174. LName, LValue: string;
  175. begin
  176. ADest.BeginUpdate;
  177. try
  178. idx := 0;
  179. while idx < Count do
  180. begin
  181. LName := GetName(idx);
  182. LValue := GetValueFromLine(idx);
  183. IndyAddPair(ADest, LName, LValue);
  184. end;
  185. finally
  186. ADest.EndUpdate;
  187. end;
  188. end;
  189. constructor TIdHeaderList.Create(AQuoteType: TIdHeaderQuotingType);
  190. begin
  191. inherited Create;
  192. FNameValueSeparator := ': '; {Do not Localize}
  193. FUnfoldLines := True;
  194. FFoldLines := True;
  195. { 78 was specified by a message draft available at
  196. http://www.imc.org/draft-ietf-drums-msg-fmt }
  197. // HTTP does not technically have a limitation on line lengths
  198. FFoldLinesLength := iif(AQuoteType = QuoteHTTP, MaxInt, 78);
  199. FQuoteType := AQuoteType;
  200. end;
  201. procedure TIdHeaderList.DeleteFoldedLines(Index: Integer);
  202. begin
  203. Inc(Index); {skip the current line}
  204. if Index < Count then begin
  205. while (Index < Count) and CharIsInSet(Get(Index), 1, LWS) do begin {Do not Localize}
  206. Delete(Index);
  207. end;
  208. end;
  209. end;
  210. procedure TIdHeaderList.Extract(const AName: string; ADest: TStrings);
  211. var
  212. idx : Integer;
  213. begin
  214. if Assigned(ADest) then begin
  215. ADest.BeginUpdate;
  216. try
  217. idx := 0;
  218. while idx < Count do
  219. begin
  220. if TextIsSame(AName, GetName(idx)) then begin
  221. ADest.Add(GetValueFromLine(idx));
  222. end else begin
  223. SkipValueAtLine(idx);
  224. end;
  225. end;
  226. finally
  227. ADest.EndUpdate;
  228. end;
  229. end;
  230. end;
  231. procedure TIdHeaderList.FoldAndInsert(AString : String; Index: Integer);
  232. var
  233. LStrs : TStrings;
  234. idx : Integer;
  235. begin
  236. LStrs := TStringList.Create;
  237. try
  238. FoldLineToList(AString, LStrs);
  239. idx := LStrs.Count - 1;
  240. Put(Index, LStrs[idx]);
  241. {We decrement by one because we put the last string into the HeaderList}
  242. Dec(idx);
  243. while idx > -1 do
  244. begin
  245. Insert(Index, LStrs[idx]);
  246. Dec(idx);
  247. end;
  248. finally
  249. LStrs.Free;
  250. end; //finally
  251. end;
  252. procedure TIdHeaderList.FoldLineToList(AString : string; ALines: TStrings);
  253. var
  254. s : String;
  255. begin
  256. {we specify a space so that starts a folded line}
  257. s := IndyWrapText(AString, EOL+' ', LWS+',', FFoldLinesLength); {Do not Localize}
  258. if s <> '' then begin
  259. ALines.BeginUpdate;
  260. try
  261. repeat
  262. ALines.Add(TrimRight(Fetch(s, EOL)));
  263. until s = ''; {Do not Localize};
  264. finally
  265. ALines.EndUpdate;
  266. end;
  267. end;
  268. end;
  269. function TIdHeaderList.GetName(Index: Integer): string;
  270. var
  271. I : Integer;
  272. begin
  273. Result := Get(Index);
  274. {We trim right to remove space to accomodate header errors such as
  275. Message-ID:<asdf@fdfs
  276. }
  277. I := IndyPos(TrimRight(FNameValueSeparator), Result);
  278. if I <> 0 then begin
  279. SetLength(Result, I - 1);
  280. end else begin
  281. SetLength(Result, 0);
  282. end;
  283. end;
  284. function TIdHeaderList.GetValue(const AName: string): string;
  285. var
  286. idx: Integer;
  287. begin
  288. idx := IndexOfName(AName);
  289. Result := GetValueFromLine(idx);
  290. end;
  291. function TIdHeaderList.GetValueFromLine(var VLine: Integer): String;
  292. var
  293. LLine, LSep: string;
  294. P: Integer;
  295. begin
  296. if (VLine >= 0) and (VLine < Count) then begin
  297. LLine := Get(VLine);
  298. Inc(VLine);
  299. {We trim right to remove space to accomodate header errors such as
  300. Message-ID:<asdf@fdfs
  301. }
  302. LSep := TrimRight(FNameValueSeparator);
  303. P := IndyPos(LSep, LLine);
  304. Result := TrimLeft(Copy(LLine, P + Length(LSep), MaxInt));
  305. if FUnfoldLines then begin
  306. while VLine < Count do begin
  307. LLine := Get(VLine);
  308. // s[1] is safe since header lines cannot be empty as that causes then end of the header block
  309. if not CharIsInSet(LLine, 1, LWS) then begin
  310. Break;
  311. end;
  312. Result := Trim(Result) + ' ' + Trim(LLine); {Do not Localize}
  313. Inc(VLine);
  314. end;
  315. end;
  316. // User may be fetching a folded line directly.
  317. Result := Trim(Result);
  318. end else begin
  319. Result := ''; {Do not Localize}
  320. end;
  321. end;
  322. procedure TIdHeaderList.SkipValueAtLine(var VLine: Integer);
  323. begin
  324. if (VLine >= 0) and (VLine < Count) then begin
  325. Inc(VLine);
  326. if FUnfoldLines then begin
  327. while VLine < Count do begin
  328. // s[1] is safe since header lines cannot be empty as that causes then end of the header block
  329. if not CharIsInSet(Get(VLine), 1, LWS) then begin
  330. Break;
  331. end;
  332. Inc(VLine);
  333. end;
  334. end;
  335. end;
  336. end;
  337. function TIdHeaderList.GetParam(const AName, AParam: string): string;
  338. var
  339. s: string;
  340. LQuoteType: TIdHeaderQuotingType;
  341. begin
  342. s := Values[AName];
  343. if s <> '' then begin
  344. LQuoteType := FQuoteType;
  345. case LQuoteType of
  346. QuoteRFC822: begin
  347. if PosInStrArray(AName, ['Content-Type', 'Content-Disposition'], False) <> -1 then begin {Do not Localize}
  348. LQuoteType := QuoteMIME;
  349. end;
  350. end;
  351. QuoteMIME: begin
  352. if PosInStrArray(AName, ['Content-Type', 'Content-Disposition'], False) = -1 then begin {Do not Localize}
  353. LQuoteType := QuoteRFC822;
  354. end;
  355. end;
  356. end;
  357. Result := ExtractHeaderSubItem(s, AParam, LQuoteType);
  358. end else begin
  359. Result := '';
  360. end;
  361. end;
  362. function TIdHeaderList.GetAllParams(const AName: string): string;
  363. var
  364. s: string;
  365. begin
  366. s := Values[AName];
  367. if s <> '' then begin
  368. Fetch(s, ';'); {do not localize}
  369. Result := Trim(s);
  370. end else begin
  371. Result := '';
  372. end;
  373. end;
  374. function TIdHeaderList.IndexOfName(const AName: string): Integer;
  375. var
  376. i: Integer;
  377. begin
  378. Result := -1;
  379. for i := 0 to Count - 1 do begin
  380. if TextIsSame(GetName(i), AName) then begin
  381. Result := i;
  382. Exit;
  383. end;
  384. end;
  385. end;
  386. procedure TIdHeaderList.SetValue(const AName, AValue: string);
  387. var
  388. I: Integer;
  389. begin
  390. I := IndexOfName(AName);
  391. if AValue <> '' then begin {Do not Localize}
  392. if I < 0 then begin
  393. I := Add(''); {Do not Localize}
  394. end;
  395. if FFoldLines then begin
  396. DeleteFoldedLines(I);
  397. FoldAndInsert(AName + FNameValueSeparator + AValue, I);
  398. end else begin
  399. Put(I, AName + FNameValueSeparator + AValue);
  400. end;
  401. end
  402. else if I >= 0 then begin
  403. if FFoldLines then begin
  404. DeleteFoldedLines(I);
  405. end;
  406. Delete(I);
  407. end;
  408. end;
  409. procedure TIdHeaderList.SetParam(const AName, AParam, AValue: string);
  410. var
  411. LQuoteType: TIdHeaderQuotingType;
  412. begin
  413. LQuoteType := FQuoteType;
  414. case LQuoteType of
  415. QuoteRFC822: begin
  416. if PosInStrArray(AName, ['Content-Type', 'Content-Disposition'], False) <> -1 then begin {Do not Localize}
  417. LQuoteType := QuoteMIME;
  418. end;
  419. end;
  420. QuoteMIME: begin
  421. if PosInStrArray(AName, ['Content-Type', 'Content-Disposition'], False) = -1 then begin {Do not Localize}
  422. LQuoteType := QuoteRFC822;
  423. end;
  424. end;
  425. end;
  426. Values[AName] := ReplaceHeaderSubItem(Values[AName], AParam, AValue, LQuoteType);
  427. end;
  428. procedure TIdHeaderList.SetAllParams(const AName, AValue: string);
  429. var
  430. LValue: string;
  431. begin
  432. LValue := Values[AName];
  433. if LValue <> '' then
  434. begin
  435. LValue := ExtractHeaderItem(LValue);
  436. if AValue <> '' then begin
  437. LValue := LValue + '; ' + AValue; {do not localize}
  438. end;
  439. Values[AName] := LValue;
  440. end;
  441. end;
  442. end.