IdAuthentication.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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.5 10/26/2004 10:59:30 PM JPMugaas
  18. Updated ref.
  19. Rev 1.4 2004.02.03 5:44:52 PM czhower
  20. Name changes
  21. Rev 1.3 10/5/2003 5:01:34 PM GGrieve
  22. fix to compile Under DotNet
  23. Rev 1.2 10/4/2003 9:09:28 PM GGrieve
  24. DotNet fixes
  25. Rev 1.1 10/3/2003 11:40:38 PM GGrieve
  26. move InfyGetHostName here
  27. Rev 1.0 11/14/2002 02:12:52 PM JPMugaas
  28. 2001-Sep-11 : DSiders
  29. Corrected spelling for EIdAlreadyRegisteredAuthenticationMethod
  30. }
  31. unit IdAuthentication;
  32. {
  33. Implementation of the Basic authentication as specified in RFC 2616
  34. Copyright: (c) Chad Z. Hower and The Winshoes Working Group.
  35. Author: Doychin Bondzhev ([email protected])
  36. }
  37. interface
  38. {$i IdCompilerDefines.inc}
  39. uses
  40. Classes,
  41. IdHeaderList,
  42. IdGlobal,
  43. IdException;
  44. type
  45. TIdAuthenticationSchemes = (asBasic, asDigest, asNTLM, asUnknown);
  46. TIdAuthSchemeSet = set of TIdAuthenticationSchemes;
  47. TIdAuthWhatsNext = (wnAskTheProgram, wnDoRequest, wnFail);
  48. TIdAuthentication = class(TPersistent)
  49. protected
  50. FCurrentStep: Integer;
  51. FParams: TIdHeaderList;
  52. FAuthParams: TIdHeaderList;
  53. FCharset: string;
  54. function ReadAuthInfo(AuthName: String): String;
  55. function DoNext: TIdAuthWhatsNext; virtual; abstract;
  56. procedure SetAuthParams(AValue: TIdHeaderList);
  57. function GetPassword: String;
  58. function GetUserName: String;
  59. function GetSteps: Integer; virtual;
  60. procedure SetPassword(const Value: String); virtual;
  61. procedure SetUserName(const Value: String); virtual;
  62. public
  63. constructor Create; virtual;
  64. destructor Destroy; override;
  65. procedure Reset; virtual;
  66. procedure SetRequest(const AMethod, AUri: String); virtual;
  67. function Authentication: String; virtual; abstract;
  68. function KeepAlive: Boolean; virtual;
  69. function Next: TIdAuthWhatsNext;
  70. property AuthParams: TIdHeaderList read FAuthParams write SetAuthParams;
  71. property Params: TIdHeaderList read FParams;
  72. property Username: String read GetUserName write SetUserName;
  73. property Password: String read GetPassword write SetPassword;
  74. property Steps: Integer read GetSteps;
  75. property CurrentStep: Integer read FCurrentStep;
  76. end;
  77. TIdAuthenticationClass = class of TIdAuthentication;
  78. TIdBasicAuthentication = class(TIdAuthentication)
  79. protected
  80. FRealm: String;
  81. function DoNext: TIdAuthWhatsNext; override;
  82. function GetSteps: Integer; override; // this function determines the number of steps that this
  83. // Authtentication needs take to suceed;
  84. public
  85. function Authentication: String; override;
  86. property Realm: String read FRealm write FRealm;
  87. end;
  88. EIdAlreadyRegisteredAuthenticationMethod = class(EIdException);
  89. { Support functions }
  90. procedure RegisterAuthenticationMethod(const MethodName: String; const AuthClass: TIdAuthenticationClass);
  91. procedure UnregisterAuthenticationMethod(const MethodName: String);
  92. function FindAuthClass(const AuthName: String): TIdAuthenticationClass;
  93. implementation
  94. uses
  95. IdCoderMIME, IdGlobalProtocols,
  96. {$IFDEF HAS_UNIT_Generics_Collections}
  97. System.Generics.Collections,
  98. {$ENDIF}
  99. {$IFDEF HAS_UNIT_Generics_Defaults}
  100. System.Generics.Defaults,
  101. {$ENDIF}
  102. SysUtils;
  103. var
  104. AuthList: {$IFDEF HAS_GENERICS_TDictionary}TDictionary<String, TIdAuthenticationClass>{$ELSE}TStringList{$ENDIF} = nil;
  105. procedure RegisterAuthenticationMethod(const MethodName: String; const AuthClass: TIdAuthenticationClass);
  106. {$IFNDEF HAS_GENERICS_TDictionary}
  107. var
  108. I: Integer;
  109. {$ENDIF}
  110. begin
  111. if not Assigned(AuthList) then begin
  112. {$IFDEF HAS_GENERICS_TDictionary}
  113. AuthList := TDictionary<String, TIdAuthenticationClass>.Create(TIStringComparer.Ordinal);
  114. {$ELSE}
  115. AuthList := TStringList.Create;
  116. {$ENDIF}
  117. end;
  118. {$IFDEF HAS_GENERICS_TDictionary}
  119. if not AuthList.ContainsKey(MethodName) then begin
  120. AuthList.Add(MethodName, AuthClass);
  121. end else begin
  122. //raise EIdAlreadyRegisteredAuthenticationMethod.CreateFmt(RSHTTPAuthAlreadyRegistered, [AuthClass.ClassName]);
  123. AuthList.Items[MethodName] := AuthClass;
  124. end;
  125. {$ELSE}
  126. I := AuthList.IndexOf(MethodName);
  127. if I < 0 then begin
  128. AuthList.AddObject(MethodName, TObject(AuthClass));
  129. end else begin
  130. //raise EIdAlreadyRegisteredAuthenticationMethod.CreateFmt(RSHTTPAuthAlreadyRegistered, [AuthClass.ClassName]);
  131. AuthList.Objects[I] := TObject(AuthClass);
  132. end;
  133. {$ENDIF}
  134. end;
  135. procedure UnregisterAuthenticationMethod(const MethodName: String);
  136. {$IFNDEF HAS_GENERICS_TDictionary}
  137. var
  138. I: Integer;
  139. {$ENDIF}
  140. begin
  141. if Assigned(AuthList) then begin
  142. {$IFDEF HAS_GENERICS_TDictionary}
  143. if AuthList.ContainsKey(MethodName) then begin
  144. AuthList.Remove(MethodName);
  145. end;
  146. {$ELSE}
  147. I := AuthList.IndexOf(MethodName);
  148. if I > 0 then begin
  149. AuthList.Delete(I);
  150. end;
  151. {$ENDIF}
  152. end;
  153. end;
  154. function FindAuthClass(const AuthName: String): TIdAuthenticationClass;
  155. {$IFNDEF HAS_GENERICS_TDictionary}
  156. var
  157. I: Integer;
  158. {$ENDIF}
  159. begin
  160. Result := nil;
  161. {$IFDEF HAS_GENERICS_TDictionary}
  162. if AuthList.ContainsKey(AuthName) then begin
  163. Result := AuthList.Items[AuthName];
  164. end;
  165. {$ELSE}
  166. I := AuthList.IndexOf(AuthName);
  167. if I > -1 then begin
  168. Result := TIdAuthenticationClass(AuthList.Objects[I]);
  169. end;
  170. {$ENDIF}
  171. end;
  172. { TIdAuthentication }
  173. constructor TIdAuthentication.Create;
  174. begin
  175. inherited Create;
  176. FAuthParams := TIdHeaderList.Create(QuoteHTTP);
  177. FParams := TIdHeaderList.Create(QuoteHTTP);
  178. {$IFDEF HAS_TStringList_CaseSensitive}
  179. FParams.CaseSensitive := False;
  180. {$ENDIF}
  181. FCurrentStep := 0;
  182. end;
  183. destructor TIdAuthentication.Destroy;
  184. begin
  185. FAuthParams.Free;
  186. FParams.Free;
  187. inherited Destroy;
  188. end;
  189. procedure TIdAuthentication.SetAuthParams(AValue: TIdHeaderList);
  190. begin
  191. FAuthParams.Assign(AValue);
  192. end;
  193. function TIdAuthentication.ReadAuthInfo(AuthName: String): String;
  194. Var
  195. i: Integer;
  196. begin
  197. for i := 0 to FAuthParams.Count - 1 do begin
  198. if TextStartsWith(FAuthParams[i], AuthName) then begin
  199. Result := FAuthParams[i];
  200. Exit;
  201. end;
  202. end;
  203. Result := ''; {Do not Localize}
  204. end;
  205. function TIdAuthentication.KeepAlive: Boolean;
  206. begin
  207. Result := False;
  208. end;
  209. function TIdAuthentication.Next: TIdAuthWhatsNext;
  210. begin
  211. Result := DoNext;
  212. end;
  213. procedure TIdAuthentication.Reset;
  214. begin
  215. FCurrentStep := 0;
  216. end;
  217. procedure TIdAuthentication.SetRequest(const AMethod, AUri: String);
  218. begin
  219. // empty here, descendants can override as needed...
  220. end;
  221. function TIdAuthentication.GetPassword: String;
  222. begin
  223. Result := Params.Values['Password']; {Do not Localize}
  224. end;
  225. function TIdAuthentication.GetUserName: String;
  226. begin
  227. Result := Params.Values['Username']; {Do not Localize}
  228. end;
  229. procedure TIdAuthentication.SetPassword(const Value: String);
  230. begin
  231. Params.Values['Password'] := Value; {Do not Localize}
  232. end;
  233. procedure TIdAuthentication.SetUserName(const Value: String);
  234. begin
  235. Params.Values['Username'] := Value; {Do not Localize}
  236. end;
  237. function TIdAuthentication.GetSteps: Integer;
  238. begin
  239. Result := 0;
  240. end;
  241. { TIdBasicAuthentication }
  242. function TIdBasicAuthentication.Authentication: String;
  243. var
  244. LEncoder: TIdEncoderMIME;
  245. begin
  246. LEncoder := TIdEncoderMIME.Create;
  247. try
  248. Result := 'Basic ' + LEncoder.Encode(Username + ':' + Password, CharsetToEncoding(FCharset)); {do not localize}
  249. finally
  250. LEncoder.Free;
  251. end;
  252. end;
  253. // TODO: move this into the 'interface' section, or maybe the IdGlobalProtocols unit...
  254. function Unquote(var S: String): String;
  255. var
  256. I, Len: Integer;
  257. begin
  258. Len := Length(S);
  259. I := 2; // skip first quote
  260. while I <= Len do
  261. begin
  262. if S[I] = '"' then begin
  263. Break;
  264. end;
  265. if S[I] = '\' then begin
  266. Inc(I);
  267. end;
  268. Inc(I);
  269. end;
  270. Result := Copy(S, 2, I-2);
  271. S := Copy(S, I+1, MaxInt);
  272. // TODO: use a PosEx() loop instead
  273. {
  274. I := Pos('\', Result);
  275. while I <> 0 do
  276. begin
  277. Delete(Result, I, 1);
  278. I := PosEx('\', Result, I+1);
  279. end;
  280. }
  281. Len := Length(Result);
  282. I := 1;
  283. while I <= Len do
  284. begin
  285. if Result[I] = '\' then begin
  286. Delete(Result, I, 1);
  287. end;
  288. Inc(I);
  289. end;
  290. end;
  291. function TIdBasicAuthentication.DoNext: TIdAuthWhatsNext;
  292. var
  293. S, LName, LValue: String;
  294. LParams: TStringList;
  295. begin
  296. S := ReadAuthInfo('Basic'); {Do not Localize}
  297. Fetch(S);
  298. LParams := TStringList.Create;
  299. try
  300. {$IFDEF HAS_TStringList_CaseSensitive}
  301. LParams.CaseSensitive := False;
  302. {$ENDIF}
  303. while S <> '' do begin
  304. // RLebeau: Apache sends a space after each comma, but IIS does not!
  305. LName := Trim(Fetch(S, '=')); {do not localize}
  306. S := TrimLeft(S);
  307. if TextStartsWith(S, '"') then begin {do not localize}
  308. LValue := Unquote(S); {do not localize}
  309. Fetch(S, ','); {do not localize}
  310. end else begin
  311. LValue := Trim(Fetch(S, ','));
  312. end;
  313. IndyAddPair(LParams, LName, LValue);
  314. S := TrimLeft(S);
  315. end;
  316. FRealm := LParams.Values['realm']; {Do not Localize}
  317. FCharset := LParams.Values['charset']; // RFC 7617
  318. if FCharset = '' then begin
  319. FCharset := LParams.Values['accept-charset']; // draft-reschke-basicauth-enc-05 onwards
  320. if FCharset = '' then begin
  321. FCharset := LParams.Values['encoding']; // draft-reschke-basicauth-enc-04
  322. if FCharset = '' then begin
  323. FCharset := LParams.Values['enc']; // I saw this mentioned in a Mozilla bug report, and apparently Opera supports it
  324. if FCharset = '' then begin
  325. // TODO: check the user's input and encode using ISO-8859-1 only if
  326. // the characters will actually fit, otherwise use UTF-8 instead?
  327. FCharset := 'ISO-8859-1';
  328. end;
  329. end;
  330. end;
  331. end;
  332. finally
  333. LParams.Free;
  334. end;
  335. if FCurrentStep = 0 then
  336. begin
  337. if Username <> '' then begin
  338. Result := wnDoRequest;
  339. end else begin
  340. Result := wnAskTheProgram;
  341. end;
  342. end else begin
  343. Result := wnFail;
  344. end;
  345. end;
  346. function TIdBasicAuthentication.GetSteps: Integer;
  347. begin
  348. Result := 1;
  349. end;
  350. initialization
  351. RegisterAuthenticationMethod('Basic', TIdBasicAuthentication); {Do not Localize}
  352. finalization
  353. // UnregisterAuthenticationMethod('Basic') does not need to be called
  354. // in this case because AuthList is freed.
  355. FreeAndNil(AuthList);
  356. end.