Browse Source

* Added 401 authorization handling

git-svn-id: trunk@26679 -
michael 11 years ago
parent
commit
2cbb9daaa7
1 changed files with 104 additions and 33 deletions
  1. 104 33
      packages/fcl-web/src/base/fphttpclient.pp

+ 104 - 33
packages/fcl-web/src/base/fphttpclient.pp

@@ -35,13 +35,16 @@ Const
 
 Type
   TRedirectEvent = Procedure (Sender : TObject; Const ASrc : String; Var ADest: String) of object;
+  TPasswordEvent = Procedure (Sender : TObject; Var RepeatRequest : Boolean) of object;
 
   { TFPCustomHTTPClient }
   TFPCustomHTTPClient = Class(TComponent)
   private
     FAllowRedirect: Boolean;
     FMaxRedirects: Byte;
+    FOnPassword: TPasswordEvent;
     FOnRedirect: TRedirectEvent;
+    FPassword: String;
     FSentCookies,
     FCookies: TStrings;
     FHTTPVersion: String;
@@ -53,6 +56,7 @@ Type
     FServerHTTPVersion: String;
     FSocket : TInetSocket;
     FBuffer : Ansistring;
+    FUserName: String;
     function CheckContentLength: Integer;
     function CheckTransferEncoding: string;
     function GetCookies: TStrings;
@@ -68,6 +72,7 @@ Type
     function ReadString: String;
     // Check if response code is in AllowedResponseCodes. if not, an exception is raised.
     // If AllowRedirect is true, and the result is a Redirect status code, the result is also true
+    // If the OnPassword event is set, then a 401 will also result in True.
     function CheckResponseCode(ACode: Integer;  const AllowedResponseCodes: array of Integer): Boolean; virtual;
     // Read response from server, and write any document to Stream.
     Procedure ReadResponse(Stream: TStream;  const AllowedResponseCodes: array of Integer; HeadersOnly: Boolean = False); virtual;
@@ -82,12 +87,19 @@ Type
     // Run method AMethod, using request URL AURL. Write Response to Stream, and headers in ResponseHeaders.
     // If non-empty, AllowedResponseCodes contains an array of response codes considered valid responses.
     // If HandleRedirect is True, then Redirect status is accepted as a correct status, but request is not repeated.
+    // No authorization callback.
     Procedure DoMethod(Const AMethod,AURL : String; Stream : TStream; Const AllowedResponseCodes : Array of Integer); virtual;
     // Send request to server: construct request line and send headers and request body.
     Procedure SendRequest(const AMethod: String; URI: TURI); virtual;
   Public
     Constructor Create(AOwner: TComponent); override;
     Destructor Destroy; override;
+    // Add header Aheader with value AValue to HTTPHeaders, replacing exiting values
+    Class Procedure AddHeader(HTTPHeaders : TStrings; Const AHeader,AValue : String);
+    // Index of header AHeader in httpheaders.
+    Class Function IndexOfHeader(HTTPHeaders : TStrings; Const AHeader : String) : Integer;
+    // Return value of header AHeader from httpheaders. Returns empty if it doesn't exist yet.
+    Class Function GetHeader(HTTPHeaders : TStrings; Const AHeader : String) : String;
     // Request Header management
     // Return index of header, -1 if not present.
     Function IndexOfHeader(Const AHeader : String) : Integer;
@@ -95,7 +107,7 @@ Type
     Procedure AddHeader(Const AHeader,AValue : String);
     // Return header value, empty if not present.
     Function GetHeader(Const AHeader : String) : String;
-    // General-purpose call. Handles redirect.
+    // General-purpose call. Handles redirect and authorization retry (OnPassword).
     Procedure HTTPMethod(Const AMethod,AURL : String; Stream : TStream; Const AllowedResponseCodes : Array of Integer); virtual;
     // Execute GET on server, store result in Stream, File, StringList or string
     Procedure Get(Const AURL : String; Stream : TStream);
@@ -205,8 +217,17 @@ Type
     // Called On redirect. Dest URL can be edited.
     // If The DEST url is empty on return, the method is aborted (with redirect status).
     Property OnRedirect : TRedirectEvent Read FOnRedirect Write FOnRedirect;
+    // Authentication.
+    // When set, they override the credentials found in the URI.
+    // They also override any Authenticate: header in Requestheaders.
+    Property UserName : String Read FUserName Write FUserName;
+    Property Password : String Read FPassword Write FPassword;
+    // If a request returns a 401, then the OnPassword event is fired.
+    // It can modify the username/password and set RepeatRequest to true;
+    Property OnPassword : TPasswordEvent Read FOnPassword Write FOnPassword;
   end;
 
+
   TFPHTTPClient = Class(TFPCustomHTTPClient)
   Public
     Property RequestHeaders;
@@ -220,6 +241,9 @@ Type
     Property AllowRedirect;
     Property MaxRedirects;
     Property OnRedirect;
+    Property UserName;
+    Property Password;
+    Property OnPassword;
   end;
   EHTTPClient = Class(Exception);
 
@@ -328,40 +352,21 @@ begin
 end;
 
 function TFPCustomHTTPClient.IndexOfHeader(const AHeader: String): Integer;
-Var
-  L : Integer;
-  H : String;
 begin
-  H:=LowerCase(Aheader);
-  l:=Length(AHeader);
-  Result:=Requestheaders.Count-1;
-  While (Result>=0) and ((LowerCase(Copy(RequestHeaders[Result],1,l)))<>h) do
-    Dec(Result);
+  Result:=IndexOfHeader(RequestHeaders,AHeader);
 end;
 
 procedure TFPCustomHTTPClient.AddHeader(const AHeader, AValue: String);
 
-Var
-  J: Integer;
 begin
-  j:=IndexOfHeader(Aheader);
-  if (J<>-1) then
-    RequestHeaders.Delete(j);
-  RequestHeaders.Add(AHeader+': '+Avalue);
+  AddHeader(RequestHeaders,AHeader,AValue);
 end;
 
 function TFPCustomHTTPClient.GetHeader(const AHeader: String): String;
 
-Var
-  I : Integer;
 
 begin
-  I:=indexOfHeader(AHeader);
-  Result:=RequestHeaders[i];
-  I:=Pos(':',Result);
-  if (I=0) then
-    I:=Length(Result);
-  System.Delete(Result,1,I);
+  Result:=GetHeader(RequestHeaders,AHeader);
 end;
 
 function TFPCustomHTTPClient.GetServerURL(URI: TURI): String;
@@ -406,13 +411,25 @@ end;
 procedure TFPCustomHTTPClient.SendRequest(const AMethod: String; URI: TURI);
 
 Var
-  S,L : String;
+  UN,PW,S,L : String;
   I : Integer;
 
 begin
   S:=Uppercase(AMethod)+' '+GetServerURL(URI)+' '+'HTTP/'+FHTTPVersion+CRLF;
-  If (URI.Username<>'') then
-    S:=S+'Authorization: Basic ' + EncodeStringBase64(URI.UserName+ ':' + URI.Password)+CRLF;
+  UN:=URI.Username;
+  PW:=URI.Password;
+  if (UserName<>'') then
+    begin
+    UN:=UserName;
+    PW:=Password;
+    end;
+  If (UN<>'') then
+    begin
+    S:=S+'Authorization: Basic ' + EncodeStringBase64(UN+':'+PW)+CRLF;
+    I:=IndexOfHeader('Authorization');
+    If I<>-1 then
+      RequestHeaders.Delete(i);
+    end;
   S:=S+'Host: '+URI.Host;
   If (URI.Port<>0) then
     S:=S+':'+IntToStr(URI.Port);
@@ -605,8 +622,13 @@ begin
       Inc(I);
       end
     end;
-  If (Not Result) and AllowRedirect then
-    Result:=IsRedirect(ACode);
+  If (Not Result) then
+    begin
+    if AllowRedirect then
+      Result:=IsRedirect(ACode);
+    If (ACode=401) then
+      Result:=Assigned(FOnPassword);
+    end;
 end;
 
 function TFPCustomHTTPClient.CheckContentLength: Integer;
@@ -863,6 +885,51 @@ begin
   inherited Destroy;
 end;
 
+class procedure TFPCustomHTTPClient.AddHeader(HTTPHeaders: TStrings;
+  const AHeader, AValue: String);
+Var
+J: Integer;
+begin
+  j:=IndexOfHeader(HTTPHeaders,Aheader);
+  if (J<>-1) then
+    HTTPHeaders.Delete(j);
+  HTTPHeaders.Add(AHeader+': '+Avalue);
+end;
+
+
+class function TFPCustomHTTPClient.IndexOfHeader(HTTPHeaders: TStrings;
+  const AHeader: String): Integer;
+
+Var
+  L : Integer;
+  H : String;
+begin
+  H:=LowerCase(Aheader);
+  l:=Length(AHeader);
+  Result:=HTTPHeaders.Count-1;
+  While (Result>=0) and ((LowerCase(Copy(HTTPHeaders[Result],1,l)))<>h) do
+    Dec(Result);
+end;
+
+class function TFPCustomHTTPClient.GetHeader(HTTPHeaders: TStrings;
+  const AHeader: String): String;
+Var
+  I : Integer;
+begin
+  I:=IndexOfHeader(HTTPHeaders,AHeader);
+  if (I=-1) then
+    Result:=''
+  else
+    begin
+    Result:=HTTPHeaders[i];
+    I:=Pos(':',Result);
+    if (I=0) then
+      I:=Length(Result);
+    System.Delete(Result,1,I);
+    Result:=TrimLeft(Result);
+    end;
+end;
+
 procedure TFPCustomHTTPClient.ResetResponse;
 
 begin
@@ -899,10 +966,7 @@ begin
         Inc(RC);
         if (RC>MaxRedirects) then
           Raise EHTTPClient.CreateFmt(SErrMaxRedirectsReached,[RC]);
-        C:=FResponseHeaders.NameValueSeparator;
-        FResponseHeaders.NameValueSeparator:=':';
-        NL:=TrimLeft(FResponseHeaders.Values['Location']);
-        FResponseHeaders.NameValueSeparator:=C;
+        NL:=GetHeader(FResponseHeaders,'Location');
         if Not Assigned(FOnRedirect) then
           L:=NL
         else
@@ -916,7 +980,14 @@ begin
         FSentCookies:=Nil;
         end;
       end;
-    RR:=AllowRedirect and IsRedirect(FResponseStatusCode) and (L<>'')
+    if (FResponseStatusCode=401) then
+      begin
+      RR:=False;
+      if Assigned(FOnPassword) then
+        FOnPassword(Self,RR);
+      end
+    else
+      RR:=AllowRedirect and IsRedirect(FResponseStatusCode) and (L<>'')
   until not RR;
 end;