{
  $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$
}
{
  Rev 1.7    6/26/2004 12:11:16 AM  BGooijen
  updates for D8

  Rev 1.6    4/8/2004 4:00:40 PM  BGooijen
  Fix for D8

  Rev 1.5    07/04/2004 20:44:06  HHariri
  Updates

  Rev 1.4    07/04/2004 20:07:50  HHariri
  Updates for .NET

    Rev 1.3    10/19/2003 4:50:10 PM  DSiders
  Added localization comments.

  Rev 1.2    10/12/2003 1:49:48 PM  BGooijen
  Changed comment of last checkin

  Rev 1.1    10/12/2003 1:43:32 PM  BGooijen
  Changed IdCompilerDefines.inc to Core\IdCompilerDefines.inc

  Rev 1.0    11/13/2002 07:54:34 AM  JPMugaas
}

unit IdHTTPWebBrokerBridge;

{
Original Author: Dave Nottage.
Modified by: Grahame Grieve
Modified by: Chad Z. Hower (Kudzu)
}

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  HTTPApp,
  IdContext, IdCustomHTTPServer, IdException, IdTCPServer, IdIOHandlerSocket,
  {$IFDEF CLR}System.Text,{$ENDIF}
  WebBroker, WebReq;

type
  EWBBException = class(EIdException);
  EWBBInvalidIdxGetDateVariable = class(EWBBException);
  EWBBInvalidIdxSetDateVariable = class(EWBBException );
  EWBBInvalidIdxGetIntVariable = class(EWBBException );
  EWBBInvalidIdxSetIntVariable = class(EWBBException );
  EWBBInvalidIdxGetStrVariable = class(EWBBException);
  EWBBInvalidIdxSetStringVar = class(EWBBException);
  EWBBInvalidStringVar = class(EWBBException);

  TIdHTTPAppRequest = class(TWebRequest)
  protected
    FRequestInfo   : TIdHTTPRequestInfo;
    FResponseInfo  : TIdHTTPResponseInfo;
    FThread        : TIdContext;
    FContentStream : TStream;
    FFreeContentStream : Boolean;
    //
    function GetDateVariable(Index: Integer): TDateTime; override;
    function GetIntegerVariable(Index: Integer): Integer; override;
    function GetStringVariable(Index: Integer): AnsiString; override;
    {$IFDEF VCL_XE_OR_ABOVE}
    function GetRemoteIP: string; override;
    function GetRawPathInfo: AnsiString; override;
    {$ENDIF}
  public
    constructor Create(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
     AResponseInfo: TIdHTTPResponseInfo);
    destructor Destroy; override;
    function GetFieldByName(const Name: AnsiString): AnsiString; override;
    function ReadClient(var Buffer{$IFDEF CLR}: TBytes{$ENDIF}; Count: Integer): Integer; override;
    function ReadString(Count: Integer): AnsiString; override;
    {function ReadUnicodeString(Count: Integer): string;}
    function TranslateURI(const URI: string): string; override;
    function WriteClient(var ABuffer; ACount: Integer): Integer; override;

    {$IFDEF VCL_6_OR_ABOVE}
      {$DEFINE VCL_6_OR_ABOVE_OR_CLR}
    {$ENDIF}
    {$IFDEF CLR}
      {$DEFINE VCL_6_OR_ABOVE_OR_CLR}
    {$ENDIF}
    {$IFDEF VCL_6_OR_ABOVE_OR_CLR}
    function WriteHeaders(StatusCode: Integer; const ReasonString, Headers: AnsiString): Boolean; override;
    {$ENDIF}
    function WriteString(const AString: AnsiString): Boolean; override;
  end;

  TIdHTTPAppResponse = class(TWebResponse)
  protected
    FContent: string;
    FRequestInfo: TIdHTTPRequestInfo;
    FResponseInfo: TIdHTTPResponseInfo;
    FSent: Boolean;
    FThread: TIdContext;
    FContentType: AnsiString; // Workaround to preserve value of ContentType property
    //
    function GetContent: AnsiString; override;
    function GetDateVariable(Index: Integer): TDateTime; override;
    function GetStatusCode: Integer; override;
    function GetIntegerVariable(Index: Integer): Integer; override;
    function GetLogMessage: string; override;
    function GetStringVariable(Index: Integer): AnsiString; override;
    procedure SetContent(const AValue: AnsiString); override;
    procedure SetContentStream(AValue: TStream); override;
    procedure SetStatusCode(AValue: Integer); override;
    procedure SetStringVariable(Index: Integer; const Value: AnsiString); override;
    procedure SetDateVariable(Index: Integer; const Value: TDateTime); override;
    procedure SetIntegerVariable(Index: Integer; Value: Integer); override;
    procedure SetLogMessage(const Value: string); override;
    procedure MoveCookiesAndCustomHeaders;
  public
    constructor Create(AHTTPRequest: TWebRequest; AThread: TIdContext;
     ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    procedure SendRedirect(const URI: AnsiString); override;
    procedure SendResponse; override;
    procedure SendStream(AStream: TStream); override;
    function Sent: Boolean; override;
  end;

  TIdHTTPWebBrokerBridge = class(TIdCustomHTTPServer)
  private
    procedure RunWebModuleClass(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
     AResponseInfo: TIdHTTPResponseInfo);
  protected
    FWebModuleClass: TComponentClass;
    //
    procedure DoCommandGet(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
     AResponseInfo: TIdHTTPResponseInfo); override;
    procedure DoCommandOther(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
     AResponseInfo: TIdHTTPResponseInfo); override;
    procedure InitComponent; override;
  public
    procedure RegisterWebModuleClass(AClass: TComponentClass);
  end;

implementation

uses
  IdResourceStringsProtocols,
  IdBuffer, IdHTTPHeaderInfo, IdGlobal, IdGlobalProtocols, IdCookie, IdStream,
  {$IFDEF STRING_IS_UNICODE}IdCharsets,{$ENDIF}
  SysUtils, Math
  {$IFDEF HAS_TNetEncoding}
  , System.NetEncoding
  {$ENDIF}
  ;

type
  // Make HandleRequest accessible
  TWebDispatcherAccess = class(TCustomWebDispatcher);

const
  INDEX_RESP_Version = 0;
  INDEX_RESP_ReasonString = 1;
  INDEX_RESP_Server = 2;
  INDEX_RESP_WWWAuthenticate = 3;
  INDEX_RESP_Realm = 4;
  INDEX_RESP_Allow = 5;
  INDEX_RESP_Location = 6;
  INDEX_RESP_ContentEncoding = 7;
  INDEX_RESP_ContentType = 8;
  INDEX_RESP_ContentVersion = 9;
  INDEX_RESP_DerivedFrom = 10;
  INDEX_RESP_Title = 11;
  //
  INDEX_RESP_ContentLength = 0;
  //
  INDEX_RESP_Date = 0;
  INDEX_RESP_Expires = 1;
  INDEX_RESP_LastModified = 2;
  //
  //Borland coder didn't define constants in HTTPApp
  INDEX_Method           = 0;
  INDEX_ProtocolVersion  = 1;
  INDEX_URL              = 2;
  INDEX_Query            = 3;
  INDEX_PathInfo         = 4;
  INDEX_PathTranslated   = 5;
  INDEX_CacheControl     = 6;
  INDEX_Date             = 7;
  INDEX_Accept           = 8;
  INDEX_From             = 9;
  INDEX_Host             = 10;
  INDEX_IfModifiedSince  = 11;
  INDEX_Referer          = 12;
  INDEX_UserAgent        = 13;
  INDEX_ContentEncoding  = 14;
  INDEX_ContentType      = 15;
  INDEX_ContentLength    = 16;
  INDEX_ContentVersion   = 17;
  INDEX_DerivedFrom      = 18;
  INDEX_Expires          = 19;
  INDEX_Title            = 20;
  INDEX_RemoteAddr       = 21;
  INDEX_RemoteHost       = 22;
  INDEX_ScriptName       = 23;
  INDEX_ServerPort       = 24;
  INDEX_Content          = 25;
  INDEX_Connection       = 26;
  INDEX_Cookie           = 27;
  INDEX_Authorization    = 28;

{ TIdHTTPAppRequest }

constructor TIdHTTPAppRequest.Create(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  i: Integer;
begin
  FThread := AThread;
  FRequestInfo := ARequestInfo;
  FResponseInfo := AResponseInfo;
  inherited Create;
  for i := 0 to ARequestInfo.Cookies.Count - 1 do begin
    CookieFields.Add(ARequestInfo.Cookies[i].ClientCookie);
  end;
  if Assigned(FRequestInfo.PostStream) then
  begin
    FContentStream := FRequestInfo.PostStream;
    FFreeContentStream := False;
  end else
  begin
    if FRequestInfo.FormParams <> '' then begin {do not localize}
      // an input form that was submitted as "application/www-url-encoded"...
      FContentStream := TStringStream.Create(FRequestInfo.FormParams);
    end else
    begin
      // anything else for now...
      FContentStream := TStringStream.Create(FRequestInfo.UnparsedParams);
    end;
    FFreeContentStream := True;
  end;
end;

destructor TIdHTTPAppRequest.Destroy;
begin
  if FFreeContentStream then begin
    FreeAndNil(FContentStream);
  end;
  inherited;
end;

function TIdHTTPAppRequest.GetDateVariable(Index: Integer): TDateTime;
var
  LValue: string;
begin
  LValue := string(GetStringVariable(Index));
  if Length(LValue) > 0 then begin
    Result := ParseDate(LValue);
  end else begin
    Result := -1;
  end;
end;

function TIdHTTPAppRequest.GetIntegerVariable(Index: Integer): Integer;
begin
  Result := StrToIntDef(string(GetStringVariable(Index)), -1)
end;

{$IFDEF VCL_XE_OR_ABOVE}
function TIdHTTPAppRequest.GetRawPathInfo: AnsiString;
begin
  Result := AnsiString(FRequestInfo.URI);
end;

function TIdHTTPAppRequest.GetRemoteIP: string;
begin
  Result := String(FRequestInfo.RemoteIP);
end;
{$ENDIF}

function TIdHTTPAppRequest.GetStringVariable(Index: Integer): AnsiString;
var
  s: string;
  LPos: TIdStreamSize;
  LBytes: TIdBytes;
begin
  LBytes := nil;
  case Index of
    INDEX_Method          : Result := AnsiString(FRequestInfo.Command);
    INDEX_ProtocolVersion : Result := AnsiString(FRequestInfo.Version);
    //INDEX_URL             : Result := AnsiString(FRequestInfo.Document);
    INDEX_URL             : Result := AnsiString(''); // Root - consistent with ISAPI which return path to root
    INDEX_Query           : Result := AnsiString(FRequestInfo.QueryParams);
    INDEX_PathInfo        : Result := AnsiString(FRequestInfo.Document);
    INDEX_PathTranslated  : Result := AnsiString(FRequestInfo.Document);             // it's not clear quite what should be done here - we can't translate to a path
    INDEX_CacheControl    : Result := GetFieldByName('Cache-Control');   {do not localize}
    INDEX_Date            : Result := GetFieldByName('Date');            {do not localize}
    INDEX_Accept          : Result := AnsiString(FRequestInfo.Accept);
    INDEX_From            : Result := AnsiString(FRequestInfo.From);
    INDEX_Host: begin
      s := FRequestInfo.Host;
      Result := AnsiString(Fetch(s, ':'));
    end;
    INDEX_IfModifiedSince : Result := GetFieldByName('If-Modified-Since'); {do not localize}
    INDEX_Referer         : Result := AnsiString(FRequestInfo.Referer);
    INDEX_UserAgent       : Result := AnsiString(FRequestInfo.UserAgent);
    INDEX_ContentEncoding : Result := AnsiString(FRequestInfo.ContentEncoding);
    INDEX_ContentType     : Result := AnsiString(FRequestInfo.ContentType);
    INDEX_ContentLength   : Result := AnsiString(IntToStr(FContentStream.Size));
    INDEX_ContentVersion  : Result := GetFieldByName('CONTENT_VERSION'); {do not localize}
    INDEX_DerivedFrom     : Result := GetFieldByName('Derived-From');    {do not localize}
    INDEX_Expires         : Result := GetFieldByName('Expires');         {do not localize}
    INDEX_Title           : Result := GetFieldByName('Title');           {do not localize}
    INDEX_RemoteAddr      : Result := AnsiString(FRequestInfo.RemoteIP);
    INDEX_RemoteHost      : Result := GetFieldByName('REMOTE_HOST');     {do not localize}
    INDEX_ScriptName      : Result := '';
    INDEX_ServerPort: begin
      s := FRequestInfo.Host;
      Fetch(s, ':');
      if Length(s) = 0 then begin
        s := IntToStr(FThread.Connection.Socket.Binding.Port);
        // Result := '80';
      end;
      Result := AnsiString(s);
    end;
    INDEX_Content: begin
      if FFreeContentStream then
      begin
        Result := AnsiString(TStringStream(FContentStream).DataString);
      end else
      begin
        LPos := FContentStream.Position;
        FContentStream.Position := 0;
        try
          // RLebeau 2/21/2009: not using ReadStringAsCharSet() anymore.  Since
          // this method returns an AnsiString, the stream data should not be
          // decoded to Unicode and then converted to Ansi.  That can lose
          // characters.  Also, for D2009+, the AnsiString payload should have
          // the proper codepage assigned to it as well so it can be converted
          // correctly if assigned to other string variables later on...

          // Result := ReadStringAsCharSet(FContentStream, FRequestInfo.CharSet);
          TIdStreamHelper.ReadBytes(FContentStream, LBytes);
          {$IFDEF DOTNET}
          // RLebeau: how to handle this correctly in .NET?
          Result := AnsiString(BytesToStringRaw(LBytes));
          {$ELSE}
          SetString(Result, PAnsiChar(LBytes), Length(LBytes));
            {$IFDEF VCL_2009_OR_ABOVE}
          SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FRequestInfo.CharSet), False);
            {$ENDIF}
          {$ENDIF}
        finally
          FContentStream.Position := LPos;
        end;
      end;
    end;
    INDEX_Connection      : Result := GetFieldByName('Connection');      {do not localize}
    INDEX_Cookie          : Result := '';  // not available at present. FRequestInfo.Cookies....;
    INDEX_Authorization   : Result := GetFieldByName('Authorization');   {do not localize}
  else
    Result := '';
  end;
end;

function TIdHTTPAppRequest.GetFieldByName(const Name: AnsiString): AnsiString;
begin
  Result := AnsiString(FRequestInfo.RawHeaders.Values[string(Name)]);
end;

function TIdHTTPAppRequest.ReadClient(var Buffer{$IFDEF CLR}: TBytes{$ENDIF};
  Count: Integer): Integer;
begin
  {$IFDEF CLR}
  Result := TIdStreamHelper.ReadBytes(FContentStream, Buffer, Count);
  {$ELSE}
  Result := FContentStream.Read(Buffer, Count);
  {$ENDIF}
  // well, it shouldn't be less than 0. but let's not take chances
  if Result < 0 then begin
    Result := 0;
  end;
end;

function TIdHTTPAppRequest.ReadString(Count: Integer): AnsiString;
var
  LBytes: TIdBytes;
begin
  // RLebeau 2/21/2009: not using ReadStringAsCharSet() anymore.  Since
  // this method returns an AnsiString, the stream data should not be
  // decoded to Unicode and then converted to Ansi.  That can lose
  // characters.

  // Result := AnsiString(ReadStringFromStream(FContentStream, Count));
  LBytes := nil;
  TIdStreamHelper.ReadBytes(FContentStream, LBytes, Count);
  {$IFDEF DOTNET}
  // RLebeau: how to handle this correctly in .NET?
  Result := AnsiString(BytesToStringRaw(LBytes));
  {$ELSE}
  SetString(Result, PAnsiChar(LBytes), Length(LBytes));
    {$IFDEF VCL_2009_OR_ABOVE}
  SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FRequestInfo.CharSet), False);
    {$ENDIF}
  {$ENDIF}
end;

function TIdHTTPAppRequest.TranslateURI(const URI: string): string;
begin
  // we don't have the concept of a path translation. It's not quite clear
  // what to do about this. Comments welcome (grahame@kestral.com.au)
  Result := URI;
end;

{$IFDEF VCL_6_OR_ABOVE_OR_CLR}
function TIdHTTPAppRequest.WriteHeaders(StatusCode: Integer; const ReasonString, Headers: AnsiString): Boolean;
begin
  FResponseInfo.ResponseNo := StatusCode;
  FResponseInfo.ResponseText := string(ReasonString);
  FResponseInfo.CustomHeaders.Add(string(Headers));
  FResponseInfo.WriteHeader;
  Result := True;
end;
{$ENDIF}

function TIdHTTPAppRequest.WriteString(const AString: AnsiString): Boolean;
begin
  FThread.Connection.IOHandler.Write(string(AString));
  Result := True;
end;

function TIdHTTPAppRequest.WriteClient(var ABuffer; ACount: Integer): Integer;
var
  LBuffer: TIdBytes;
begin
  SetLength(LBuffer, ACount);
{$IFNDEF CLR}
  Move(ABuffer, LBuffer[0], ACount);
{$ELSE}
  // RLebeau: this can't be right?  It is interpretting the source as a
  // null-terminated character string, which is likely not the case...
  CopyTIdBytes(ToBytes(string(ABuffer)), 0, LBuffer, 0, ACount);
{$ENDIF}
  FThread.Connection.IOHandler.Write(LBuffer);
  Result := ACount;
end;

{ TIdHTTPAppResponse }

constructor TIdHTTPAppResponse.Create(AHTTPRequest: TWebRequest; AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  FThread := AThread;
  FRequestInfo := ARequestInfo;
  FResponseInfo := AResponseInfo;
  inherited Create(AHTTPRequest);
  if Length(FHTTPRequest.ProtocolVersion) = 0 then begin
    Version := '1.0'; {do not localize}
  end;
  StatusCode := 200;
  LastModified := -1;
  Expires := -1;
  Date := -1;

  // RLebeau 8/13/2015: no longer setting a default ContentType here.  Doing so
  // sets a default CharSet, which would get carried over if the user assigns a
  // new *non-text* ContentType without an explicit charset.  TAppResponse does
  // not expose access to the FResponseInfo.CharSet property.  For example, if
  // the user sets TAppResponse.ContentType to 'image/jpeg', the resulting
  // Content-Type header woud be 'image/jpeg; charset=ISO-8859-1', which can
  // cause problems for some clients.  Besides, TIdHTTPResponseInfo.WriteHeader()
  // sets the ContentType to 'text/html; charset=ISO-8859-1' if no ContentType
  // has been provided but there is ContentText/ContentStream data, so this is
  // redundant here anyway...
  //
  // ContentType := 'text/html'; {do not localize}
end;

function TIdHTTPAppResponse.GetContent: AnsiString;
{$IFDEF STRING_IS_UNICODE}
var
  LEncoding: IIdTextEncoding;
  LBytes: TIdBytes;
{$ENDIF}
begin
  {$IFDEF STRING_IS_UNICODE}
  // RLebeau 2/21/2009: encode the content using the specified charset.
  // Also, the AnsiString payload should have the proper codepage assigned
  // to it as well so it can be converted correctly if assigned to other
  // string variables later on...
  Result := '';
  LEncoding := CharsetToEncoding(FResponseInfo.CharSet);
  LBytes := LEncoding.GetBytes(FResponseInfo.ContentText);
    {$IFDEF DOTNET}
  // RLebeau: how to handle this correctly in .NET?
  Result := AnsiString(BytesToStringRaw(LBytes));
    {$ELSE}
  SetString(Result, PAnsiChar(LBytes), Length(LBytes));
      {$IFDEF VCL_2009_OR_ABOVE}
  SetCodePage(PRawByteString(@Result)^, CharsetToCodePage(FResponseInfo.CharSet), False);
      {$ENDIF}
    {$ENDIF}
  {$ELSE}
  Result := FResponseInfo.ContentText;
  {$ENDIF}
end;

function TIdHTTPAppResponse.GetLogMessage: string;
begin
  Result := '';
end;

function TIdHTTPAppResponse.GetStatusCode: Integer;
begin
  Result := FResponseInfo.ResponseNo;
end;

function TIdHTTPAppResponse.GetDateVariable(Index: Integer): TDateTime;
  // WebBroker apps are responsible for conversion to GMT, Indy HTTP server expects apps to pas local time
  function ToGMT(ADateTime: TDateTime): TDateTime;
  begin
    Result := ADateTime;
    if Result <> -1 then
      Result := Result - OffsetFromUTC;
  end;
begin
  //TODO: resource string these
  case Index of
    INDEX_RESP_Date             : Result := ToGMT(FResponseInfo.Date);
    INDEX_RESP_Expires          : Result := ToGMT(FResponseInfo.Expires);
    INDEX_RESP_LastModified     : Result := ToGMT(FResponseInfo.LastModified);
  else
    raise EWBBInvalidIdxGetDateVariable.Create( Format( RSWBBInvalidIdxGetDateVariable,[inttostr(Index)]));
  end;
end;

procedure TIdHTTPAppResponse.SetDateVariable(Index: Integer; const Value: TDateTime);
  // WebBroker apps are responsible for conversion to GMT, Indy HTTP server expects apps to pas local time
  function ToLocal(ADateTime: TDateTime): TDateTime;
  begin
    Result := ADateTime;
    if Result <> -1 then
      Result := Result + OffsetFromUTC;
  end;
begin
  //TODO: resource string these
  case Index of
    INDEX_RESP_Date             : FResponseInfo.Date := ToLocal(Value);
    INDEX_RESP_Expires          : FResponseInfo.Expires := ToLocal(Value);
    INDEX_RESP_LastModified     : FResponseInfo.LastModified := ToLocal(Value);
  else
    raise EWBBInvalidIdxSetDateVariable.Create(Format(RSWBBInvalidIdxSetDateVariable,[inttostr(Index) ]));
  end;
end;

function TIdHTTPAppResponse.GetIntegerVariable(Index: Integer): Integer;
begin
  //TODO: resource string these
  case Index of
    INDEX_RESP_ContentLength: Result := FResponseInfo.ContentLength;
  else
    raise EWBBInvalidIdxGetIntVariable.Create( Format( RSWBBInvalidIdxGetIntVariable,[inttostr(Index)]));
  end;
end;

procedure TIdHTTPAppResponse.SetIntegerVariable(Index, Value: Integer);
begin
  //TODO: resource string these
  case Index of
    INDEX_RESP_ContentLength: FResponseInfo.ContentLength := Value;
  else
    raise EWBBInvalidIdxSetIntVariable.Create( Format(RSWBBInvalidIdxSetIntVariable,[inttostr(Index)]));  {do not localize}
  end;
end;

function TIdHTTPAppResponse.GetStringVariable(Index: Integer): AnsiString;
begin
  //TODO: resource string these
  case Index of
    INDEX_RESP_Version           :Result := AnsiString(FRequestInfo.Version);
    INDEX_RESP_ReasonString      :Result := AnsiString(FResponseInfo.ResponseText);
    INDEX_RESP_Server            :Result := AnsiString(FResponseInfo.Server);
    INDEX_RESP_WWWAuthenticate   :Result := AnsiString(FResponseInfo.WWWAuthenticate.Text);
    INDEX_RESP_Realm             :Result := AnsiString(FResponseInfo.AuthRealm);
    INDEX_RESP_Allow             :Result := AnsiString(FResponseInfo.CustomHeaders.Values['Allow']);        {do not localize}
    INDEX_RESP_Location          :Result := AnsiString(FResponseInfo.Location);
    INDEX_RESP_ContentEncoding   :Result := AnsiString(FResponseInfo.ContentEncoding);
    INDEX_RESP_ContentType       :
    begin
      if FContentType <> '' then begin
        Result := FContentType;
      end else begin
        Result := AnsiString(FResponseInfo.ContentType);
      end;
    end;
    INDEX_RESP_ContentVersion    :Result := AnsiString(FResponseInfo.ContentVersion);
    INDEX_RESP_DerivedFrom       :Result := AnsiString(FResponseInfo.CustomHeaders.Values['Derived-From']); {do not localize}
    INDEX_RESP_Title             :Result := AnsiString(FResponseInfo.CustomHeaders.Values['Title']);        {do not localize}
  else
    raise EWBBInvalidIdxGetStrVariable.Create(Format(RSWBBInvalidIdxGetStrVariable,[ IntToStr(Index)]));
  end;
end;

procedure TIdHTTPAppResponse.SetStringVariable(Index: Integer; const Value: AnsiString);
begin
  //TODO: resource string these
  case Index of
    INDEX_RESP_Version           :EWBBInvalidStringVar.Create(RSWBBInvalidStringVar);
    INDEX_RESP_ReasonString      :FResponseInfo.ResponseText := string(Value);
    INDEX_RESP_Server            :FResponseInfo.Server := string(Value);
    INDEX_RESP_WWWAuthenticate   :FResponseInfo.WWWAuthenticate.Text := string(Value);
    INDEX_RESP_Realm             :FResponseInfo.AuthRealm := string(Value);
    INDEX_RESP_Allow             :FResponseInfo.CustomHeaders.Values['Allow'] := string(Value); {do not localize}
    INDEX_RESP_Location          :FResponseInfo.Location := string(Value);
    INDEX_RESP_ContentEncoding   :FResponseInfo.ContentEncoding := string(Value);
    INDEX_RESP_ContentType       :
    begin
      FResponseInfo.ContentType := string(Value);
      FContentType := Value;
    end;
    INDEX_RESP_ContentVersion    :FResponseInfo.ContentVersion := string(Value);
    INDEX_RESP_DerivedFrom       :FResponseInfo.CustomHeaders.Values['Derived-From'] := string(Value);  {do not localize}
    INDEX_RESP_Title             :FResponseInfo.CustomHeaders.Values['Title'] := string(Value); {do not localize}
  else
    raise EWBBInvalidIdxSetStringVar.Create( Format(RSWBBInvalidIdxSetStringVar,[IntToStr(Index)]));                   {do not localize}
  end;
end;

procedure TIdHTTPAppResponse.SendRedirect(const URI: AnsiString);
begin
  FSent := True;
  MoveCookiesAndCustomHeaders;
  FResponseInfo.Redirect(string(URI));
end;

procedure TIdHTTPAppResponse.SendResponse;
begin
  FSent := True;
  // Reset to -1 so Indy will auto set it
  FResponseInfo.ContentLength := -1;
  MoveCookiesAndCustomHeaders;
  FResponseInfo.WriteContent;
end;

procedure TIdHTTPAppResponse.SendStream(AStream: TStream);
begin
  FThread.Connection.IOHandler.Write(AStream);
end;

function TIdHTTPAppResponse.Sent: Boolean;
begin
  Result := FSent;
end;

procedure TIdHTTPAppResponse.SetContent(const AValue: AnsiString);
var
  LValue : string;
begin
  {$IFDEF STRING_IS_UNICODE}
  // RLebeau 3/28/2013: decode the content using the specified charset.
  if FResponseInfo.CharSet <> '' then begin
    // AValue contains Encoded bytes
    if AValue <> '' then begin
      LValue := CharsetToEncoding(FResponseInfo.CharSet).GetString(RawToBytes(PAnsiChar(AValue)^, Length(AValue)));
    end;
  end else begin
    LValue := string(AValue);
  end;
  {$ELSE}
  LValue := string(AValue);
  {$ENDIF}
  FResponseInfo.ContentText := LValue;
  FResponseInfo.ContentLength := Length(LValue);
end;

procedure TIdHTTPAppResponse.SetLogMessage(const Value: string);
begin
  // logging not supported
end;

procedure TIdHTTPAppResponse.SetStatusCode(AValue: Integer);
begin
  FResponseInfo.ResponseNo := AValue;
end;

procedure TIdHTTPAppResponse.SetContentStream(AValue: TStream);
begin
  inherited;
  FResponseInfo.ContentStream := AValue;
end;

function DoHTTPEncode(const AStr: AnsiString): String;
begin
  {$IFDEF HAS_TNetEncoding}
  Result := TNetEncoding.URL.Encode(string(AStr));
  {$ELSE}
  Result := String(HTTPEncode(AStr));
  {$ENDIF}
end;

procedure TIdHTTPAppResponse.MoveCookiesAndCustomHeaders;
var
  i: Integer;
  LSrcCookie: TCookie;
  LDestCookie: TIdCookie;
begin
  for i := 0 to Cookies.Count - 1 do begin
    LSrcCookie := Cookies[i];
    LDestCookie := FResponseInfo.Cookies.Add;
    LDestCookie.CookieName := DoHTTPEncode(LSrcCookie.Name);
    LDestCookie.Value := DoHTTPEncode(LSrcCookie.Value);
    LDestCookie.Domain := String(LSrcCookie.Domain);
    LDestCookie.Path := String(LSrcCookie.Path);
    LDestCookie.Expires := LSrcCookie.Expires;
    LDestCookie.Secure := LSrcCookie.Secure;
    // TODO: LDestCookie.HttpOnly := LSrcCookie.HttpOnly;
  end;
  FResponseInfo.CustomHeaders.Clear;
  FResponseInfo.CustomHeaders.AddStdValues(CustomHeaders);
end;

{ TIdHTTPWebBrokerBridge }

procedure TIdHTTPWebBrokerBridge.DoCommandOther(AThread: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  DoCommandGet(AThread, ARequestInfo, AResponseInfo);

end;

procedure TIdHTTPWebBrokerBridge.InitComponent;
begin
  inherited InitComponent;
 // FOkToProcessCommand := True;
end;

type
  TIdHTTPWebBrokerBridgeRequestHandler = class(TWebRequestHandler)
  {$IFDEF HAS_CLASSVARS}
  private
   class var FWebRequestHandler: TIdHTTPWebBrokerBridgeRequestHandler;
  {$ENDIF}
  public
    constructor Create(AOwner: TComponent); override;
    {$IFDEF HAS_CLASSVARS}
      {$IFDEF HAS_CLASSDESTRUCTOR}
    class destructor Destroy;
      {$ENDIF}
    {$ENDIF}
    destructor Destroy; override;
    procedure Run(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
  end;

{$IFNDEF HAS_CLASSVARS}
var
  IndyWebRequestHandler: TIdHTTPWebBrokerBridgeRequestHandler = nil;
{$ENDIF}

{ TIdHTTPWebBrokerBridgeRequestHandler }

procedure TIdHTTPWebBrokerBridgeRequestHandler.Run(AThread: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  LRequest: TIdHTTPAppRequest;
  LResponse: TIdHTTPAppResponse;
begin
  try
    LRequest := TIdHTTPAppRequest.Create(AThread, ARequestInfo, AResponseInfo);
    try
      LResponse := TIdHTTPAppResponse.Create(LRequest, AThread, ARequestInfo, AResponseInfo);
      try
        // WebBroker will free it and we cannot change this behaviour
        AResponseInfo.FreeContentStream := False;
        HandleRequest(LRequest, LResponse);
      finally
        FreeAndNil(LResponse);
      end;
    finally
      FreeAndNil(LRequest);
    end;
  except
    // Let Indy handle this exception
    raise;
  end;
end;

constructor TIdHTTPWebBrokerBridgeRequestHandler.Create(AOwner: TComponent);
begin
  inherited;
  Classes.ApplicationHandleException := HandleException;
end;

destructor TIdHTTPWebBrokerBridgeRequestHandler.Destroy;
begin
  Classes.ApplicationHandleException := nil;
  inherited;
end;

{$IFDEF HAS_CLASSVARS}
  {$IFDEF HAS_CLASSDESTRUCTOR}
class destructor TIdHTTPWebBrokerBridgeRequestHandler.Destroy;
begin
  FreeAndNil(FWebRequestHandler);
end;
  {$ENDIF}
{$ENDIF}

function IdHTTPWebBrokerBridgeRequestHandler: TWebRequestHandler;
begin
  {$IFDEF HAS_CLASSVARS}
  if not Assigned(TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler) then
    TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil);
  Result := TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler;
  {$ELSE}
  if not Assigned(IndyWebRequestHandler) then
    IndyWebRequestHandler := TIdHTTPWebBrokerBridgeRequestHandler.Create(nil);
  Result := IndyWebRequestHandler;
  {$ENDIF}
end;

procedure TIdHTTPWebBrokerBridge.DoCommandGet(AThread: TIdContext;
 ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  if FWebModuleClass <> nil then begin
    // FWebModuleClass, RegisterWebModuleClass supported for backward compatability
    RunWebModuleClass(AThread, ARequestInfo, AResponseInfo)
  end else
  begin
    {$IFDEF HAS_CLASSVARS}
    TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo);
    {$ELSE}
    IndyWebRequestHandler.Run(AThread, ARequestInfo, AResponseInfo);
    {$ENDIF}
  end;
end;

procedure TIdHTTPWebBrokerBridge.RunWebModuleClass(AThread: TIdContext;
 ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  LRequest: TIdHTTPAppRequest;
  LResponse: TIdHTTPAppResponse;
  LWebModule: TCustomWebDispatcher;
  {$IFDEF VCL_6_OR_ABOVE}
  WebRequestHandler: IWebRequestHandler;
  {$ENDIF}
  Handled: Boolean;
begin
  LRequest := TIdHTTPAppRequest.Create(AThread, ARequestInfo, AResponseInfo);
  try
    LResponse := TIdHTTPAppResponse.Create(LRequest, AThread, ARequestInfo, AResponseInfo);
    try
      // WebBroker will free it and we cannot change this behaviour
      AResponseInfo.FreeContentStream := False;
      // There are better ways in D6, but this works in D5
      LWebModule := FWebModuleClass.Create(nil) as TCustomWebDispatcher;
      try
        {$IFDEF VCL_6_OR_ABOVE}
        if Supports(LWebModule, IWebRequestHandler, WebRequestHandler) then begin
          try
            Handled := WebRequestHandler.HandleRequest(LRequest, LResponse);
          finally
            WebRequestHandler := nil;
          end;
        end else begin
          Handled := TWebDispatcherAccess(LWebModule).DispatchAction(LRequest, LResponse);
        end;
        {$ELSE}
        Handled := TWebDispatcherAccess(LWebModule).DispatchAction(LRequest, LResponse);
        {$ENDIF}
        if Handled and (not LResponse.Sent) then begin
          LResponse.SendResponse;
        end;
      finally
        FreeAndNil(LWebModule);
      end;
    finally
      FreeAndNil(LResponse);
    end;
  finally
    FreeAndNil(LRequest);
  end;
end;

// FWebModuleClass, RegisterWebModuleClass supported for backward compatability
// Instead set WebModuleClass using: WebReq.WebRequestHandler.WebModuleClass := TWebModule1;
procedure TIdHTTPWebBrokerBridge.RegisterWebModuleClass(AClass: TComponentClass);
begin
  FWebModuleClass := AClass;
end;

initialization
  WebReq.WebRequestHandlerProc := IdHTTPWebBrokerBridgeRequestHandler;
{$IFDEF HAS_CLASSVARS}
  {$IFNDEF HAS_CLASSDESTRUCTOR}
finalization
  FreeAndNil(TIdHTTPWebBrokerBridgeRequestHandler.FWebRequestHandler);
  {$ENDIF}
{$ELSE}
finalization
  FreeAndNil(IndyWebRequestHandler);
{$ENDIF}

end.


