IdReplyPOP3.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  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.21 10/26/2004 10:39:54 PM JPMugaas
  18. Updated refs.
  19. Rev 1.20 5/17/04 9:50:52 AM RLebeau
  20. Changed TIdRepliesPOP3 constructor to use 'reintroduce' instead
  21. Rev 1.19 5/16/04 5:26:58 PM RLebeau
  22. Added TIdRepliesPOP3 class
  23. Rev 1.18 2004.04.15 12:49:46 PM czhower
  24. Fixed bug in TIdReplyPOP3.IsEndMarker
  25. Rev 1.17 2004.02.03 5:45:44 PM czhower
  26. Name changes
  27. Rev 1.16 2004.01.29 12:07:52 AM czhower
  28. .Net constructor problem fix.
  29. Rev 1.15 2004.01.22 5:52:54 PM czhower
  30. Visibilty fix + TextIsSame
  31. Rev 1.14 1/3/2004 8:05:50 PM JPMugaas
  32. Bug fix: Sometimes, replies will appear twice due to the way functionality
  33. was enherited.
  34. Rev 1.13 22/12/2003 00:45:58 CCostelloe
  35. .NET fixes
  36. Rev 1.12 2003.10.18 9:42:12 PM czhower
  37. Boatload of bug fixes to command handlers.
  38. Rev 1.11 2003.09.20 10:38:40 AM czhower
  39. Bug fix to allow clearing code field (Return to default value)
  40. Rev 1.10 6/8/2003 03:26:00 AM JPMugaas
  41. AssignTo added for object assignment.
  42. Rev 1.9 6/8/2003 02:59:24 AM JPMugaas
  43. RFC 2449 and RFC 3206 support.
  44. Rev 1.8 6/5/2003 04:54:22 AM JPMugaas
  45. Reworkings and minor changes for new Reply exception framework.
  46. Rev 1.7 6/4/2003 04:06:52 PM JPMugaas
  47. Started preliminary worki on RFC 3206 and RFC 2449.
  48. Removed an old GetInternetResponse override that is no longer needed and
  49. causes its own problems.
  50. Now uses string reply codes using Kudzu's new overloaded methods so mapping
  51. to integers is no longer needed. The integers used in mapping have been
  52. removed.
  53. Rev 1.6 5/30/2003 9:06:44 PM BGooijen
  54. uses CheckIfCodeIsValid now
  55. Rev 1.5 5/26/2003 04:28:28 PM JPMugaas
  56. Removed GenerateReply and ParseResponse calls because those functions are
  57. being removed.
  58. Rev 1.4 2003.05.26 10:51:42 PM czhower
  59. Removed RFC / non POP3 parsing
  60. Rev 1.3 5/26/2003 12:22:06 PM JPMugaas
  61. Rev 1.2 5/25/2003 02:40:56 AM JPMugaas
  62. Rev 1.1 5/20/2003 10:58:28 AM JPMugaas
  63. SetReplyExceptionCode now validated by TIdReplyPOP3. This way, it can only
  64. accept our integer codes for +OK, -ERR, and +.
  65. Rev 1.0 5/19/2003 04:28:10 PM JPMugaas
  66. TIdReply decendant for POP3.
  67. }
  68. unit IdReplyPOP3;
  69. interface
  70. {$i IdCompilerDefines.inc}
  71. uses
  72. Classes,
  73. IdException,
  74. IdReply;
  75. const
  76. {do not change these strings unless you know what you are doing}
  77. ST_OK = '+OK'; {Do not translate}
  78. ST_ERR = '-ERR'; {Do not translate}
  79. ST_SASLCONTINUE = '+'; {Do not translate}
  80. //note that for extended codes, we do not put the ] ending as
  81. //error code may be hierarchical in the future with a / separating levels
  82. // RFC 2449
  83. ST_ERR_IN_USE = 'IN-USE'; {Do not translate} //already in use by another program
  84. ST_ERR_LOGIN_DELAY = 'LOGIN-DELAY'; {Do not translate} //login delay time
  85. // RFC 3206
  86. ST_ERR_SYS_TEMP = 'SYS/TEMP'; {Do not translate} //system failure - temporary
  87. ST_ERR_SYS_PERM = 'SYS/PERM'; {Do not translate} //system failure - permenent
  88. ST_ERR_AUTH = 'AUTH'; {Do not translate} //authentication credential problem
  89. const
  90. VALID_ENH_CODES : array[0..4] of string = (
  91. ST_ERR_IN_USE,
  92. ST_ERR_LOGIN_DELAY,
  93. ST_ERR_SYS_PERM,
  94. ST_ERR_SYS_TEMP,
  95. ST_ERR_AUTH
  96. );
  97. type
  98. TIdReplyPOP3 = class(TIdReply)
  99. protected
  100. FEnhancedCode : String;
  101. //
  102. class function FindCodeTextDelim(const AText : String) : Integer;
  103. class function IsValidEnhancedCode(const AText : String; const AStrict : Boolean = False) : Boolean;
  104. class function ExtractTextPosArray(const AStr : String):Integer;
  105. function GetFormattedReply: TStrings; override;
  106. procedure SetFormattedReply(const AValue: TStrings); override;
  107. function CheckIfCodeIsValid(const ACode: string): Boolean; override;
  108. procedure SetEnhancedCode(const AValue : String);
  109. public
  110. constructor CreateWithReplyTexts(
  111. ACollection: TCollection = nil;
  112. AReplyTexts: TIdReplies = nil
  113. ); override;
  114. procedure Assign(ASource: TPersistent); override;
  115. procedure Clear; override;
  116. procedure RaiseReplyError; override;
  117. class function IsEndMarker(const ALine: string): Boolean; override;
  118. published
  119. property EnhancedCode : String read FEnhancedCode write SetEnhancedCode;
  120. end;
  121. TIdRepliesPOP3 = class(TIdReplies)
  122. public
  123. constructor Create(AOwner: TPersistent); reintroduce;
  124. end;
  125. //This error is for POP3 Protocol reply exceptions
  126. // SendCmd / GetResponse
  127. EIdReplyPOP3Error = class(EIdReplyError)
  128. protected
  129. FErrorCode : String;
  130. FEnhancedCode : String;
  131. public
  132. constructor CreateError(const AErrorCode: String;
  133. const AReplyMessage: string; const AEnhancedCode : String = ''); reintroduce; virtual;
  134. property ErrorCode : String read FErrorCode;
  135. property EnhancedCode : String read FEnhancedCode;
  136. end;
  137. const
  138. VALID_POP3_STR : Array [0..2] of String = (
  139. ST_OK,
  140. ST_ERR,
  141. ST_SASLCONTINUE);
  142. type
  143. EIdPOP3ReplyException = class(EIdException);
  144. EIdPOP3ReplyInvalidEnhancedCode = class(EIdPOP3ReplyException);
  145. implementation
  146. uses
  147. IdGlobal,
  148. IdGlobalProtocols,
  149. IdResourceStringsProtocols,
  150. SysUtils;
  151. { TIdReplyPOP3 }
  152. procedure TIdReplyPOP3.Assign(ASource: TPersistent);
  153. var
  154. LR: TIdReplyPOP3;
  155. begin
  156. if ASource is TIdReplyPOP3 then begin
  157. LR := TIdReplyPOP3(ASource);
  158. //set code first as it possibly clears the reply
  159. Code := LR.Code;
  160. FEnhancedCode := LR.EnhancedCode;
  161. FText.Assign(LR.Text);
  162. end else begin
  163. inherited Assign(ASource);
  164. end;
  165. end;
  166. function TIdReplyPOP3.CheckIfCodeIsValid(const ACode: string): Boolean;
  167. var
  168. LOrd: Integer;
  169. begin
  170. LOrd := PosInStrArray(ACode, VALID_POP3_STR, False);
  171. Result := (LOrd > -1) or (Trim(ACode) = '');
  172. end;
  173. procedure TIdReplyPOP3.Clear;
  174. begin
  175. inherited Clear;
  176. FEnhancedCode := '';
  177. end;
  178. constructor TIdReplyPOP3.CreateWithReplyTexts(ACollection: TCollection = nil; AReplyTexts: TIdReplies = nil);
  179. begin
  180. inherited CreateWithReplyTexts(ACollection, AReplyTexts);
  181. FCode := ST_OK;
  182. end;
  183. class function TIdReplyPOP3.ExtractTextPosArray(const AStr: String): Integer;
  184. begin
  185. Result := PosInStrArray(Copy(AStr, 1, FindCodeTextDelim(AStr) - 1), VALID_POP3_STR, False);
  186. end;
  187. class function TIdReplyPOP3.FindCodeTextDelim(const AText: String): Integer;
  188. var
  189. LMin, LSpace: Integer;
  190. LBuf: String;
  191. LAddBackFlag: Boolean; //if we deleted a begging -, we need to add it back
  192. begin
  193. LAddBackFlag := False;
  194. //we do things this way because a line can start with a minus as in
  195. //-ERR [IN-USE] Mail box in use
  196. LBuf := AText;
  197. // TODO: use PosEx() instead, then we can just skip
  198. // past the '-' without physically removing it...
  199. if TextStartsWith(LBuf, '-') then begin
  200. Delete(LBuf, 1, 1);
  201. LAddBackFlag := True;
  202. end;
  203. LMin := IndyPos(' ', LBuf);
  204. LSpace := IndyPos('-', LBuf);
  205. if LMin > 0 then begin
  206. if (LSpace <> 0) and (LMin > LSpace) then begin
  207. Result := LSpace;
  208. end else begin
  209. Result := LMin;
  210. end;
  211. end else begin
  212. if LSpace <> 0 then begin
  213. Result := LSpace;
  214. end else begin
  215. Result := Length(AText) + 1;
  216. end;
  217. end;
  218. if LAddBackFlag then begin
  219. Inc(Result);
  220. end;
  221. end;
  222. function TIdReplyPOP3.GetFormattedReply: TStrings;
  223. var
  224. i: Integer;
  225. begin
  226. Result := GetFormattedReplyStrings;
  227. if Code <> '' then begin
  228. if FText.Count > 0 then begin
  229. for i := 0 to FText.Count - 1 do begin
  230. if i < FText.Count - 1 then begin
  231. if (Code = ST_ERR) and (FEnhancedCode <> '') then begin
  232. Result.Add(Code + '-' + FEnhancedCode + ' ' + FText[i]);
  233. end else begin
  234. Result.Add(Code + '-' + FText[i]);
  235. end;
  236. end else begin
  237. if (Code = ST_ERR) and (FEnhancedCode <> '') then begin
  238. Result.Add(Code + ' ' + FEnhancedCode + ' ' + FText[i]);
  239. end else begin
  240. Result.Add(Code + ' ' + FText[i]);
  241. end;
  242. end;
  243. end;
  244. end else begin
  245. Result.Add(Code);
  246. end;
  247. end else if FText.Count > 0 then begin
  248. Result.AddStrings(FText);
  249. end;
  250. end;
  251. class function TIdReplyPOP3.IsEndMarker(const ALine: string): Boolean;
  252. var
  253. LPos: Integer;
  254. begin
  255. Result := False;
  256. LPos := FindCodeTextDelim(ALine);
  257. if LPos > 0 then begin
  258. if LPos > Length(ALine) then begin
  259. Result := True;
  260. end else begin
  261. Result := ALine[LPos] <> '-';
  262. end;
  263. end;
  264. end;
  265. class function TIdReplyPOP3.IsValidEnhancedCode(const AText : String; const AStrict : Boolean = False): Boolean;
  266. var
  267. LBuf : String;
  268. i : integer;
  269. begin
  270. Result := Trim(AText) = '';
  271. if not Result then begin
  272. LBuf := AText;
  273. if (LBuf <> '') and TextStartsWith(LBuf, '[') then begin
  274. Delete(LBuf, 1, 1);
  275. if (LBuf <> '') and TextEndsWith(LBuf, ']') then begin
  276. LBuf := Fetch(LBuf, ']');
  277. if AStrict then begin
  278. Result := PosInStrArray(LBuf, VALID_ENH_CODES) > -1;
  279. end else begin
  280. {We don't use PosInStrArray because we only want the first
  281. charactors in our string to match. This is necessary because
  282. the POP3 enhanced codes will be hierarchical as time goes on.
  283. }
  284. for i := Low(VALID_ENH_CODES) to High(VALID_ENH_CODES) do begin
  285. if TextStartsWith(LBuf, VALID_ENH_CODES[i]) then begin
  286. Result := True;
  287. Exit;
  288. end;
  289. end;
  290. end;
  291. end;
  292. end;
  293. end;
  294. end;
  295. procedure TIdReplyPOP3.RaiseReplyError;
  296. begin
  297. raise EIdReplyPOP3Error.CreateError(Code, Text.Text);
  298. end;
  299. procedure TIdReplyPOP3.SetEnhancedCode(const AValue: String);
  300. var
  301. LBuf : String;
  302. begin
  303. LBuf := AValue;
  304. if LBuf = '' then begin
  305. FEnhancedCode := '';
  306. end else begin
  307. LBuf := UpperCase(LBuf);
  308. if (LBuf[1] <> '[') then begin
  309. LBuf := '[' + LBuf;
  310. end;
  311. if (LBuf[Length(LBuf)] <> ']') then begin
  312. LBuf := LBuf + ']';
  313. end;
  314. if IsValidEnhancedCode(LBuf, True) then begin
  315. FEnhancedCode := LBuf;
  316. end else begin
  317. raise EIdPOP3ReplyInvalidEnhancedCode.Create(RSPOP3ReplyInvalidEnhancedCode + AValue);
  318. end;
  319. end;
  320. end;
  321. procedure TIdReplyPOP3.SetFormattedReply(const AValue: TStrings);
  322. var
  323. i: Integer;
  324. idx : Integer;
  325. LOrd : Integer;
  326. LBuf, LEnh : String;
  327. LText : TStringList;
  328. begin
  329. Clear;
  330. if AValue.Count > 0 then begin
  331. // RLebeau: what is the purpose of this ExtractTextPosArray() shenanigans? Why
  332. // are we allowing the status code to appear outside of the 1st line? That does
  333. // not conform to the POP3 protocol spec. Are any servers actually doing this
  334. // in practice? None of the other TIdReply classes handle this possibility...
  335. //
  336. // Note: Microsoft's Outlook365 POP3 server DOES send a greeting WITHOUT a
  337. // status code if a client connects using implicit TLS 1.0 or 1.1! That was
  338. // apparently allowed by RFC 1725, but not anymore by RFC 1939!
  339. LOrd := ExtractTextPosArray(AValue[0]);
  340. if LOrd > -1 then begin
  341. Code := VALID_POP3_STR[LOrd];
  342. end;
  343. for i := 0 to AValue.Count - 1 do begin
  344. if LOrd = -1 then begin
  345. LOrd := ExtractTextPosArray(AValue[i]);
  346. end;
  347. idx := FindCodeTextDelim(AValue[i]);
  348. LBuf := Copy(AValue[i], idx+1, MaxInt);
  349. if (Code = ST_ERR) and IsValidEnhancedCode(Fetch(LBuf,' ',False)) then begin
  350. //don't use EnhancedCode property set method because that does
  351. //a tighter validation than we should use for parsing replies
  352. //from a server.
  353. FEnhancedCode := Fetch(LBuf);
  354. end;
  355. Text.Add(LBuf);
  356. end;
  357. if LOrd = -1 then begin
  358. // RLebeau 4/30/2023: warning - TIdReply.SetCode() calls Clear(),
  359. // which will LOSE any EnhancedCode and Text values already assigned
  360. // above! We need to preserve them here. This wouldn't be an issue
  361. // anymore if we get rid of the ExtractTextPosArray() nonsense above...
  362. LText := TStringList.Create;
  363. try
  364. LText.Assign(Text);
  365. LEnh := FEnhancedCode;
  366. Code := ST_ERR;
  367. Text.Assign(LText);
  368. FEnhancedCode := LEnh;
  369. finally
  370. LText.Free;
  371. end;
  372. end;
  373. end;
  374. end;
  375. { TIdRepliesPOP3 }
  376. constructor TIdRepliesPOP3.Create(AOwner: TPersistent);
  377. begin
  378. inherited Create(AOwner, TIdReplyPOP3);
  379. end;
  380. { EIdReplyPOP3Error }
  381. constructor EIdReplyPOP3Error.CreateError(const AErrorCode, AReplyMessage: string;
  382. const AEnhancedCode : String = '');
  383. begin
  384. inherited Create(AReplyMessage);
  385. FErrorCode := AErrorCode;
  386. FEnhancedCode := AEnhancedCode;
  387. end;
  388. end.