www.pudn.com > indyprelim.zip > IdAuthenticationDigest.pas


{ 
  $Project$ 
  $Workfile$ 
  $Revision$ 
  $DateUTC$ 
  $Id$ 
 
  This file is part of the Indy (Internet Direct) project, and is offered 
  under the dual-licensing agreement described on the Indy website. 
  (http://www.indyproject.org/) 
 
  Copyright: 
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved. 
} 
{ 
  $Log$ 
} 
{ 
  2005-04-22 BTaylor 
  Fixed AV from incorrect object being freed 
  Fixed memory leak 
  Improved parsing 
 
  Rev 1.6    1/3/05 4:48:24 PM  RLebeau 
  Removed reference to StrUtils unit, not being used. 
 
  Rev 1.5    12/1/2004 1:57:50 PM  JPMugaas 
  Updated with some code posted by: 
 
  Interpulse Systeemontwikkeling 
  Interpulse Automatisering B.V. 
  http://www.interpulse.nl 
 
  Rev 1.1    2004.11.25 06:17:00 PM  EDMeester 
 
  Rev 1.0    2002.11.12 10:30:44 PM  czhower 
} 
 
unit IdAuthenticationDigest; 
 
{ 
  Implementation of the digest authentication as specified in RFC2617 
  rev 1.1: Edwin Meester (systeemontwikkeling@interpulse.nl) 
  Author: Doychin Bondzhev (doychin@dsoft-bg.com) 
  Copyright: (c) Chad Z. Hower and The Winshoes Working Group. 
} 
 
interface 
{$i IdCompilerDefines.inc} 
 
uses 
  IdAuthentication, 
  IdException, 
  IdGlobal, 
  IdHashMessageDigest, 
  IdHeaderList, 
  IdSys, 
  IdObjs; 
 
type 
  EIdInvalidAlgorithm = class(EIdException); 
 
  TIdDigestAuthentication = class(TIdAuthentication) 
  protected 
    FRealm: String; 
    FStale: Boolean; 
    FOpaque: String; 
    FDomain: TIdStringList; 
    Fnonce: String; 
    FNoncecount: integer; 
    FAlgorithm: String; 
    FMethod, FUri: string; //needed for digest, Somebody make this nice :D 
    FPostbody: TIdStringList; //needed voor auth-int, Somebody make this nice :D 
    FQopOptions: TIdStringList; 
    FOther: TIdStringList; 
    function DoNext: TIdAuthWhatsNext; override; 
  public 
    destructor Destroy; override; 
    function Authentication: String; override; 
    property Method: String read FMethod write FMethod; 
    property Uri: String read FUri write FUri; 
    property Postbody: TIdStringList read FPostbody write FPostbody; 
  end; 
 
implementation 
 
uses 
  IdHash, IdResourceStrings, IdResourceStringsProtocols; 
 
{ TIdDigestAuthentication } 
 
destructor TIdDigestAuthentication.Destroy; 
begin 
  Sys.FreeAndNil(FDomain); 
  Sys.FreeAndNil(FQopOptions); 
  inherited Destroy; 
end; 
 
function TIdDigestAuthentication.Authentication: String; 
 
  function ResultString(s: String): String; 
  var 
    MDValue: T4x4LongWordRecord; 
    LHash : TIdBytes; 
    i: Integer; 
    S1: String; 
  begin 
    with TIdHashMessageDigest5.Create do try 
      MDValue := HashValue(S); 
    finally Free end; 
    SetLength(LHash, 16); 
    CopyTIdLongWord(MDValue[0], LHash, 0); 
    CopyTIdLongWord(MDValue[1], LHash, 4); 
    CopyTIdLongWord(MDValue[2], LHash, 8); 
    CopyTIdLongWord(MDValue[3], LHash, 12); 
    for i := 0 to 15 do begin 
      S1 := S1 + Sys.Format('%02x', [LHash[i]]); 
    end; 
    while Pos(' ', S1) > 0 do begin 
      S1[Pos(' ', S1)] := '0'; 
    end; 
    Result := IndyLowerCase(S1); //Stupid uppercase, cost me a whole day to figure this one out 
  end; 
 
var 
  LstrA1, LstrA2, LstrCNonce, LstrResponse: string; 
begin 
  Result := '';    {do not localize} 
  case FCurrentStep of 
    0: 
      begin 
        Result := 'Digest'; //Just be save with this one 
      end; 
    1: 
      begin 
        //Build request 
 
        LstrCNonce := ResultString(Sys.DateTimeToStr(Sys.Now)); 
 
        LstrA1 := ResultString(Username + ':' + FRealm + ':' + Password); 
        if TextIsSame(FAlgorithm, 'MD5-sess') then begin 
          LstrA1 := ResultString(LstrA1 + ':' + Fnonce + ':' + LstrCNonce); 
        end; 
        if FQopOptions.IndexOf('auth-int') > -1 then begin 
          LstrA2 := ResultString(FMethod + ':' + FUri + ':' + ResultString(FPostbody.CommaText)) 
        end else begin 
          LstrA2 := ResultString(FMethod + ':' + FUri); 
        end; 
        LstrResponse := LstrA1 + ':' + Fnonce + ':'; 
        if (FQopOptions.IndexOf('auth-int') > -1) or (FQopOptions.IndexOf('auth') > -1) then begin //Qop header present 
          LstrResponse := LstrResponse + Sys.IntToHex(FNoncecount, 8) + ':' + LstrCNonce + ':'; 
          if FQopOptions.IndexOf('auth-int') > -1 then begin 
            LstrResponse := LstrResponse + 'auth-int:'; 
          end else begin 
            LstrResponse := LstrResponse + 'auth:'; 
          end; 
        end; 
        LstrResponse := LstrResponse + LstrA2; 
        LstrResponse := ResultString(LStrResponse); 
 
        Result := Result + 'Digest ' + {do not localize} 
          'username="' + Username + '", ' + {do not localize} 
          'realm="' + FRealm + '", ' +  {do not localize} 
          'nonce="' + FNonce + '", ' + {do not localize} 
          'algorithm="' + FAlgorithm + '", ' + {do not localize} 
          'uri="' + Furi + '", '; 
        if (FQopOptions.IndexOf('auth-int') > -1) or (FQopOptions.IndexOf('auth') > -1) then begin //Qop header present 
          if FQopOptions.IndexOf('auth-int') > -1 then begin 
            Result := Result + 'qop="auth-int", ' 
          end else begin 
            Result := Result + 'qop="auth", '; 
          end; 
          Result := Result + 'nc=' + Sys.IntToHex(FNoncecount, 8) + ', ' + 
            'cnonce="' + LstrCNonce + '", '; 
        end; 
        Result := Result + 'response="' + LstrResponse + '", ' + 
          'opaque="' + FOpaque + '"'; 
        Inc(FNoncecount); 
        FCurrentStep := 0; 
      end; 
  end; 
end; 
 
function RemoveQuote(const aStr:string):string; 
begin 
  if (Length(aStr)>=2) and (aStr[1]='"') and (astr[Length(aStr)]='"') then begin 
    Result := Copy(aStr, 2, Length(astr)-2) 
  end else begin 
    Result := aStr; 
  end; 
end; 
 
function TIdDigestAuthentication.DoNext: TIdAuthWhatsNext; 
var 
  S, LstrTempNonce: String; 
  LParams: TIdStringList; 
  f: String; 
  i: Integer; 
begin 
  Result := wnDoRequest; 
 
  case FCurrentStep of 
    0: 
      begin 
        //gather info 
        if not Assigned(FDomain) then begin 
          FDomain := TIdStringList.Create; 
        end else begin 
          FDomain.Clear; 
        end; 
        if not Assigned(FQopOptions) then begin 
          FQopOptions := TIdStringList.Create; 
        end else begin 
          FQopOptions.Clear; 
        end; 
 
        S := ReadAuthInfo('Digest'); 
        Fetch(S); 
 
        LParams := TIdStringList.Create; 
        try 
          while Length(S) > 0 do begin 
            f := Fetch(S, ', '); 
            LParams.Add(f); 
          end; 
 
          for i := lParams.Count-1 downto 0 do 
          begin 
            f := lParams.Values[lParams.Names[i]]; 
            f := RemoveQuote(f); 
            LParams.Values[LParams.Names[i]] := f; 
          end; 
 
          FRealm := LParams.Values['realm']; 
          LStrTempnonce := LParams.Values['nonce']; 
          if FNonce <> LstrTempNonce then 
          begin 
            FnonceCount := 1; 
            FNonce := LstrTempNonce; 
          end; 
 
          S := LParams.Values['domain']; 
          while Length(S) > 0 do begin 
            FDomain.Add(Fetch(S)); 
          end; 
 
          Fopaque := LParams.Values['opaque']; 
          FStale := IndyCompareStr(LParams.Values['stale'], 'True') = 1; 
          FAlgorithm := LParams.Values['algorithm']; 
          FQopOptions.CommaText := Params.Values['qop']; 
 
          if not TextIsSame(FAlgorithm, 'MD5') then begin 
            //FAlgorithm:='MD5'; 
            raise EIdInvalidAlgorithm.Create(RSHTTPAuthInvalidHash); 
          end; 
 
        finally 
          Sys.FreeAndNil(LParams); 
        end; 
 
        FCurrentStep := 1; 
 
        if Length(Username) > 0 then begin 
          Result := wnDoRequest; 
        end else begin 
          Result := wnAskTheProgram; 
        end; 
      end; 
  end; 
end; 
 
initialization 
  RegisterAuthenticationMethod('Digest', TIdDigestAuthentication); 
end.