{
    This file is part of the Free Pascal run time library.
    Copyright (c) 1999-2009 by the Free Pascal development team

    TFCgiApplication class.

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}
{ $define CGIDEBUG}
{$mode objfpc}
{$H+}

{ Disable rangechecks. 
  Buffers of unknown size are received and handled with a dummy array type }

{$RANGECHECKS OFF}

unit custfcgi;

Interface

{$if defined(win32) or defined(win64)}
{$define windowspipe}
{$ifend}

uses
  Classes,SysUtils, httpdefs, 
{$ifdef unix}
  BaseUnix,
{$else}
  winsock2, windows,
{$endif}
  Sockets, custweb, cgiprotocol, httpprotocol, custcgi, fastcgi;

Type
  { TFCGIRequest }
  TCustomFCgiApplication = Class;
  TFCGIRequest = Class;
  TFCGIResponse = Class;

  TProtocolOption = (poNoPadding,poStripContentLength, poFailonUnknownRecord,
                     poReuseAddress, poUseSelect );
  TProtocolOptions = Set of TProtocolOption;

  TUnknownRecordEvent = Procedure (ARequest : TFCGIRequest; AFCGIRecord: PFCGI_Header) Of Object;
  TFastCGIReadEvent = Function (AHandle : THandle; Var ABuf; ACount : Integer) : Integer of Object;
  TFastCGIWriteEvent = Function (AHandle : THandle; Const ABuf; ACount : Integer; Out ExtendedErrorCode : Integer) : Integer of Object;

  TFCGIRequest = Class(TCGIRequest)
  Private
    FHandle: THandle;
    FKeepConnectionAfterRequest: boolean;
    FPO: TProtoColOptions;
    FRequestID : Word;
    FCGIParams : TSTrings;
    FUR: TUnknownRecordEvent;
    FLog : TLogEvent;
    FSTDin : String;
  Protected
    function DoGetCGIVar(AVarName: String): String; override;
    procedure GetNameValuePairsFromContentRecord(const ARecord : PFCGI_ContentRecord; NameValueList : TStrings); virtual;
    Procedure Log(EventType : TEventType; Const Msg : String);
  Public
    destructor Destroy; override;
    function ProcessFCGIRecord(AFCGIRecord : PFCGI_Header) : boolean; virtual;
    property RequestID : word read FRequestID write FRequestID;
    property Handle : THandle read FHandle write FHandle;
    property KeepConnectionAfterRequest : boolean read FKeepConnectionAfterRequest;
    Property ProtocolOptions : TProtoColOptions read FPO Write FPO;
    Property OnUnknownRecord : TUnknownRecordEvent Read FUR Write FUR;
  end;
  TFCGIRequestClass = Class of TFCGIRequest;

  { TFCGIResponse }

  TFCGIResponse = Class(TCGIResponse)
  private
    FPO: TProtoColOptions;
    FOnWrite : TFastCGIWriteEvent;
  Protected
    procedure Write_FCGIRecord(ARecord : PFCGI_Header); virtual;
    Procedure DoSendHeaders(Headers : TStrings); override;
    Procedure DoSendContent; override;
    Property ProtocolOptions : TProtoColOptions Read FPO Write FPO;
  end;
  TFCGIResponseClass = Class of TFCGIResponse;

  TReqResp = record
             Request : TFCgiRequest;
             Response : TFCgiResponse;
             end;

  { TFCgiHandler }

  TFCgiHandler = class(TWebHandler)
  Private
    FLingerTimeOut: integer;
    FOnUnknownRecord: TUnknownRecordEvent;
    FPO: TProtoColOptions;
    FRequestsArray : Array of TReqResp;
    FRequestsAvail : integer;
    FHandle : THandle;
    Socket: longint;
    FIAddress      : TInetSockAddr;
    FAddressLength : tsocklen;
    FAddress: string;
    FTimeOut,
    FPort: integer;
{$ifdef windowspipe}
    FIsWinPipe: Boolean;
{$endif}
{$IFDEF WINDOWS}
    FShutdownThread : TThread;
    Procedure CheckShutDownEvent;
    Procedure HandleShutDownEvent(Sender : TObject);
{$ENDIF}
    function AcceptConnection: Integer;
    procedure CloseConnection;
    function Read_FCGIRecord : PFCGI_Header;
    function DataAvailable : Boolean;
  protected
    function CreateRequest : TFCGIRequest; virtual;
    function CreateResponse(ARequest: TFCGIRequest) : TFCGIResponse; virtual;
    Function DoFastCGIRead(AHandle : THandle; Var ABuf; ACount : Integer) : Integer; virtual;
    Function DoFastCGIWrite(AHandle : THandle; Const ABuf; ACount : Integer; Out ExtendedErrorCode : Integer) : Integer; virtual;
    function  ProcessRecord(AFCGI_Record: PFCGI_Header; out ARequest: TRequest;  out AResponse: TResponse): boolean; virtual;
    procedure SetupSocket(var IAddress: TInetSockAddr;  var AddressLength: tsocklen); virtual;
    function  WaitForRequest(out ARequest : TRequest; out AResponse : TResponse) : boolean; override;
    procedure EndRequest(ARequest : TRequest;AResponse : TResponse); override;
  Public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Port: integer read FPort write FPort;
    property LingerTimeOut : integer read FLingerTimeOut write FLingerTimeOut;
    property Address: string read FAddress write FAddress;
    Property ProtocolOptions : TProtoColOptions Read FPO Write FPO;
    Property OnUnknownRecord : TUnknownRecordEvent Read FOnUnknownRecord Write FOnUnknownRecord;
    Property TimeOut : Integer Read FTimeOut Write FTimeOut;
  end;
  TFCgiHandlerClass = Class of TFCgiHandler;

  { TCustomFCgiApplication }

  TCustomFCgiApplication = Class(TCustomWebApplication)
  private
    function GetAddress: string;
    function GetFPO: TProtoColOptions;
    function GetLingerTimeOut: integer;
    function GetOnUnknownRecord: TUnknownRecordEvent;
    function GetPort: integer;
    procedure SetAddress(const AValue: string);
    procedure SetLingerTimeOut(const AValue: integer);
    procedure SetOnUnknownRecord(const AValue: TUnknownRecordEvent);
    procedure SetPort(const AValue: integer);
    procedure SetPO(const AValue: TProtoColOptions);
  protected
    function InitializeWebHandler: TWebHandler; override;
  Public
    property Port: integer read GetPort write SetPort;
    property LingerTimeOut : integer read GetLingerTimeOut write SetLingerTimeOut;
    property Address: string read GetAddress write SetAddress;
    Property ProtocolOptions : TProtoColOptions Read GetFPO Write SetPO;
    Property OnUnknownRecord : TUnknownRecordEvent Read GetOnUnknownRecord Write SetOnUnknownRecord;
  end;

Var
  FCGIRequestClass : TFCGIRequestClass = TFCGIRequest;
  FCGIResponseClass : TFCGIResponseClass = TFCGIResponse;
  FCGIWebHandlerClass : TFCgiHandlerClass = TFCgiHandler;

ResourceString
  SNoInputHandle    = 'Failed to open input-handle passed from server. Socket Error: %d';
  SNoSocket         = 'Failed to open socket. Socket Error: %d';
  SBindFailed       = 'Failed to bind to port %d. Socket Error: %d';
  SListenFailed     = 'Failed to listen to port %d. Socket Error: %d';
  SErrReadingSocket = 'Failed to read data from socket. Error: %d';
  SErrReadingHeader = 'Failed to read FastCGI header. Read only %d bytes';
  SErrWritingSocket = 'Failed to write data to socket. Error: %d';
  SErrNoRequest     = 'Internal error: No request available when writing data';
  
Implementation

{$ifdef CGIDEBUG}
uses
  dbugintf;
{$endif}
{$undef nosignal}

{$if defined(FreeBSD) or defined(Linux)}
  {$define nosignal}
{$ifend}

{$IFDEF WINDOWS}
Type

  { TShutdownThread }
  TShutdownEvent = Procedure (Sender : TObject) Of Object;
  TShutdownThread = Class(TThread)
  Private
    FEvent : THandle;
    FOnShutDown : TShutdownEvent;
  Public
    Constructor CreateWithEvent(AEvent : THandle; AOnShutDown : TShutdownEvent);
    Procedure Execute; override;
  end;
{$ENDIF}

Const 
   NoSignalAttr =  {$ifdef nosignal} MSG_NOSIGNAL{$else}0{$endif};

{$IFDEF WINDOWS}
{ TShutdownThread }

constructor TShutdownThread.CreateWithEvent(AEvent: THandle; AOnShutDown : TShutdownEvent);
begin
  Inherited Create(False);
  FEvent:=AEvent;
  FOnShutDown:=AOnShutDown;
  OnTerminate:=AOnShutDown;
end;

procedure TShutdownThread.Execute;
begin
  WaitForSingleObject(FEvent,INFINITE);
  If Assigned(FOnShutDown) then
    FOnShutDown(Self);
  // This is very ugly, but there is no other way to stop the named pipe
  // from accepting new connections.
  // Using Halt(0) is not enough.
  ExitProcess(0);
end;
{$ENDIF WINDOWS}

{ TFCGIHTTPRequest }

destructor TFCGIRequest.Destroy;
begin
  FCGIParams.Free;
  inherited Destroy;
end;

function TFCGIRequest.ProcessFCGIRecord(AFCGIRecord: PFCGI_Header): boolean;
var cl,rcl : Integer;
begin
  Result := False;
  case AFCGIRecord^.reqtype of
    FCGI_BEGIN_REQUEST :
         begin
         FKeepConnectionAfterRequest := (PFCGI_BeginRequestRecord(AFCGIRecord)^.body.flags and FCGI_KEEP_CONN) = FCGI_KEEP_CONN;
//         With PFCGI_BeginRequestRecord(AFCGIRecord)^.body do
//           log(etDebug,Format('Begin request body role & flags: %d %d',[Beton(Role),Flags]));
         end;
    FCGI_PARAMS :       begin
                        if AFCGIRecord^.contentLength=0 then
                          Result := False
                        else
                          begin
                          if not assigned(FCGIParams) then
                            FCGIParams := TStringList.Create;
                          GetNameValuePairsFromContentRecord(PFCGI_ContentRecord(AFCGIRecord),FCGIParams);
                          end;
                        end;
    FCGI_STDIN :        begin
                        Result:=AFCGIRecord^.contentLength=0;
                        if not Result then
                          begin
                          cl := length(FSTDin);
                          rcl := BetoN(PFCGI_ContentRecord(AFCGIRecord)^.header.contentLength);
                          SetLength(FSTDin, rcl+cl);
                          move(PFCGI_ContentRecord(AFCGIRecord)^.ContentData[0],FSTDin[cl+1],rcl);
                          InitContent(FSTDin);
                          end;
                        end;
  else
    if Assigned(FUR) then
      FUR(Self,AFCGIRecord)
    else
      if poFailonUnknownRecord in FPO then
        TFCgiHandler.DoError('Unknown FASTCGI record type: %s',[AFCGIRecord^.reqtype]);
  end;
  if Result then
    InitRequestVars;
end;

function TFCGIRequest.DoGetCGIVar(AVarName: String): String;
begin
  Result:=FCGIParams.Values[AVarName];
end;

procedure TFCGIRequest.GetNameValuePairsFromContentRecord(const ARecord: PFCGI_ContentRecord; NameValueList: TStrings);

var
  i : integer;

  function GetVarLength : Integer;
  begin
    if (ARecord^.ContentData[i] and 128) = 0 then
      Result:=ARecord^.ContentData[i]
    else
      begin
//      Result:=BEtoN(PLongint(@(ARecord^.ContentData[i]))^);
      Result:=Int64(((ARecord^.ContentData[i] and $7f) shl 24)) + (ARecord^.ContentData[i+1] shl 16)
                   + (ARecord^.ContentData[i+2] shl 8) + (ARecord^.ContentData[i+3]);
      inc(i,3);
      end;
    inc(i);
  end;

  function GetString(ALength : integer) : string;
  begin
    if (ALength<0) then
      ALength:=0;
    SetLength(Result,ALength);
    if (ALength>0) then
      move(ARecord^.ContentData[i],Result[1],ALength);
    inc(i,ALength);
  end;

var
  VarNo,NameLength, ValueLength : Integer;
  RecordLength : Integer;
  Name,Value : String;
  h : THeader;
  v : THTTPVariableType;

begin
  i := 0;
  RecordLength:=BetoN(ARecord^.Header.contentLength);
  while i < RecordLength do
    begin
    NameLength:=GetVarLength;
    ValueLength:=GetVarLength;
    Name:=GetString(NameLength);
    Value:=GetString(ValueLength);
    VarNo:=IndexOfCGIVar(Name);
    if Not DoMapCgiToHTTP(Name,H,V) then
      NameValueList.Add(Name+'='+Value)
    else if (H<>hhUnknown) then
      SetHeader(H,Value)
    else if (v<>hvUnknown) then
      begin
      if (V=hvPathInfo) and (Copy(Value,1,2)='//') then //mod_proxy_fcgi gives double slashes at the beginning for some reason
          Delete(Value,1,3);
      if (V<>hvQuery) then
        Value:=HTTPDecode(Value);
      SetHTTPVariable(v,Value);
      end
    else
      NameValueList.Add(Name+'='+Value)
    end;
  // Microsoft-IIS hack. IIS includes the script name in the PATH_INFO
  if Pos('IIS', ServerSoftware) > 0 then
    SetHTTPVariable(hvPathInfo,StringReplace(PathInfo, ScriptName, '', [rfReplaceAll, rfIgnoreCase]));
end;

procedure TFCGIRequest.Log(EventType: TEventType; const Msg: String);
begin
  If Assigned(FLog) then
    FLog(EventType,Msg);
end;

{ TCGIResponse }

procedure TFCGIResponse.Write_FCGIRecord(ARecord : PFCGI_Header);

var ErrorCode,
    BytesToWrite ,
    BytesWritten  : Integer;
    P : PByte;
    r : TFCGIRequest;
    
begin
  if Not (Request is TFCGIRequest) then
    TFCgiHandler.DoError(SErrNorequest);
  R:=TFCGIRequest(Request);
  BytesToWrite := BEtoN(ARecord^.contentLength) + ARecord^.paddingLength+sizeof(FCGI_Header);
  P:=PByte(Arecord);
  Repeat
    BytesWritten:=FOnWrite(R.Handle, P^, BytesToWrite,ErrorCode);
    If (BytesWritten<0) then
      begin
      // TODO : Better checking on ErrorCode
      R.FKeepConnectionAfterRequest:=False;

{$ifdef windowspipe}
      case ErrorCode of
        ERROR_BROKEN_PIPE, ERROR_NO_DATA : Exit; //No error here. Server cancel pipe
      end;
{$endif}

      TFCgiHandler.DoError(SErrWritingSocket,[ErrorCode]);
      end;
    Inc(P,BytesWritten);
    Dec(BytesToWrite,BytesWritten);
  until (BytesToWrite=0) or (BytesWritten=0);
end;

procedure TFCGIResponse.DoSendHeaders(Headers : TStrings);
var
  cl : word;
  pl : byte;
  str : String;
  ARespRecord : PFCGI_ContentRecord;
  I : Integer;

begin
  For I:=Headers.Count-1 downto 0 do
    If (Headers[i]='') then
      Headers.Delete(I);
  // IndexOfName Does not work ?
  If (poStripContentLength in ProtocolOptions) then
    For I:=Headers.Count-1 downto 0 do
      If (Pos('Content-Length',Headers[i])<>0)  then
        Headers.Delete(i);
  str := Headers.Text+sLineBreak;
  cl := length(str);
  if ((cl mod 8)=0) or (poNoPadding in ProtocolOptions) then
    pl:=0
  else
    pl := 8-(cl mod 8);
  ARespRecord:=nil;
  Getmem(ARespRecord,8+cl+pl);
  try
    FillChar(ARespRecord^,8+cl+pl,0);
    ARespRecord^.header.version:=FCGI_VERSION_1;
    ARespRecord^.header.reqtype:=FCGI_STDOUT;
    ARespRecord^.header.paddingLength:=pl;
    ARespRecord^.header.contentLength:=NtoBE(cl);
    ARespRecord^.header.requestId:=NToBE(TFCGIRequest(Request).RequestID);
    move(str[1],ARespRecord^.ContentData,cl);
    Write_FCGIRecord(PFCGI_Header(ARespRecord));
  finally
    Freemem(ARespRecord);
  end;
end;

procedure TFCGIResponse.DoSendContent;

Const
  MaxBuf = $EFFF;

var
  bs,l : Integer;
  cl : word;
  pl : byte;
  str : String;
  ARespRecord : PFCGI_ContentRecord;
  EndRequest : FCGI_EndRequestRecord;

begin
  If Assigned(ContentStream) then
    begin
    setlength(str,ContentStream.Size);
    ContentStream.Position:=0;
    ContentStream.Read(str[1],ContentStream.Size);
    end
  else
    str := Contents.Text;
  L:=Length(Str);
  BS:=0;
  Repeat
    If (L-BS)>MaxBuf then
      cl := MaxBuf
    else
      cl:=L-BS ;
    if ((cl mod 8)=0) or (poNoPadding in ProtocolOptions) then
      pl:=0
    else
      pl := 8-(cl mod 8);
    ARespRecord:=Nil;
    Getmem(ARespRecord,8+cl+pl);
    try
      ARespRecord^.header.version:=FCGI_VERSION_1;
      ARespRecord^.header.reqtype:=FCGI_STDOUT;
      ARespRecord^.header.paddingLength:=pl;
      ARespRecord^.header.contentLength:=NtoBE(cl);
      ARespRecord^.header.requestId:=NToBE(TFCGIRequest(Request).RequestID);
      move(Str[BS+1],ARespRecord^.ContentData,cl);
      Write_FCGIRecord(PFCGI_Header(ARespRecord));
    finally
      Freemem(ARespRecord);
    end;
    Inc(BS,cl);
  Until (BS=L);
  EndRequest := Default(FCGI_EndRequestRecord);
  FillChar(EndRequest,SizeOf(FCGI_EndRequestRecord),0);
  EndRequest.header.version:=FCGI_VERSION_1;
  EndRequest.header.reqtype:=FCGI_END_REQUEST;
  EndRequest.header.contentLength:=NtoBE(8);
  EndRequest.header.paddingLength:=0;
  EndRequest.header.requestId:=NToBE(TFCGIRequest(Request).RequestID);
  EndRequest.body.protocolStatus:=FCGI_REQUEST_COMPLETE;
  Write_FCGIRecord(PFCGI_Header(@EndRequest));
end;

{ TFCgiHandler }

constructor TFCgiHandler.Create(AOwner: TComponent);

begin
  Inherited Create(AOwner);
  FRequestsAvail:=5;
  SetLength(FRequestsArray,FRequestsAvail);
  FHandle := THandle(-1);
  FTimeOut:=50;
{$IFDEF WINDOWS}
  CheckShutdownEvent;
{$ENDIF}
end;

destructor TFCgiHandler.Destroy;
begin
{$IFDEF WINDOWS}
  IF (FShutDownThread<>Nil) then
    begin
    TShutDownThread(FShutDownThread).FOnShutDown:=Nil;
    TShutDownThread(FShutDownThread).OnTerminate:=Nil;
    end;
{$ENDIF}
  SetLength(FRequestsArray,0);
  if (Socket<>0) then
    begin
    CloseSocket(Socket);
    Socket:=0;
    end;
  inherited Destroy;
end;

{$IFDEF WINDOWS}
Procedure TFCgiHandler.CheckShutdownEvent;

Var
  H : THandle;

begin
  // This is normally only used in mod_fastcgi.
  // mod_fcgid just kills off the process...
  H:=THandle(StrToIntDef(sysutils.GetEnvironmentVariable('_FCGI_SHUTDOWN_EVENT_'),0));
  If (H<>0) then
    FShutDownThread:=TShutdownThread.CreateWithEvent(H,@HandleShutDownEvent);
end;

procedure TFCgiHandler.HandleShutDownEvent(Sender : TOBject);
begin
  TShutDownThread(Sender).FOnShutDown:=Nil;
  TShutDownThread(Sender).OnTerminate:=Nil;
  FShutDownThread:=Nil;
  Terminate;
end;

{$ENDIF}

procedure TFCgiHandler.CloseConnection;
Var
  i : Integer;
begin
{$ifdef windowspipe}
  if FIsWinPipe then
    begin
    if not FlushFileBuffers(FHandle) then
      begin
      I:=GetLastError;
      Log(etError,Format('Failed to flush file buffers: %d ',[i]));
      end;
    if not DisconnectNamedPipe(FHandle) then
      begin
      I:=GetLastError;
      Log(etError,Format('Failed to disconnect named pipe: %d ',[i]));
      end
    end
  else
{$endif}
    begin
    i:=fpshutdown(FHandle,SHUT_RDWR);
//      Log(etError,Format('Shutting down socket: %d ',[i]));
    i:=CloseSocket(FHandle);
//      Log(etError,Format('Closing socket %d',[i]));
    end;
  FHandle := THandle(-1);
end;

procedure TFCgiHandler.EndRequest(ARequest: TRequest; AResponse: TResponse);


begin
  with FRequestsArray[TFCGIRequest(ARequest).RequestID] do
    begin
    Assert(ARequest=Request);
    Assert(AResponse=Response);
    if (not TFCGIRequest(ARequest).KeepConnectionAfterRequest) then
      CloseConnection;
    Request := Nil;
    Response := Nil;
    end;
  Inherited;
end;

function TFCgiHandler.Read_FCGIRecord : PFCGI_Header;
{ $DEFINE DUMPRECORD}
{$IFDEF DUMPRECORD}
  Procedure DumpFCGIRecord (Var Header :FCGI_Header; ContentLength : word; PaddingLength : byte; ResRecord : Pointer);

  Var
    S, s1, s2 : string;
    I : Integer;

  begin
      Writeln(Format('Dumping record, Sizeof(Header)=%d, ContentLength=%d, PaddingLength=%d',[SizeOf(Header),ContentLength,PaddingLength]));
      S:=''; s1 := '';
      For I:=0 to Sizeof(Header)+ContentLength+PaddingLength-1 do
      begin
        s2 := Format('%:2X ',[PByte(ResRecord)[i]]);
        if s2[1] = ' ' then s2[1] := '0';
        s1 := s1 + s2;
        If PByte(ResRecord)[i]>32 then
          S:=S+char(PByte(ResRecord)[i])
        else
          S:=S+' ';
        if (I>0) and (((I+1) mod 16) = 0) then
        begin
           Writeln(s1 + '  ' + S);
           S:=''; s1 := '';
        end;
      end;
      if length(s1)<48 then
        repeat s1 := s1 + ' ' until length(s1)>=48;
      Writeln(s1 + '  '+S)
  end;
{$ENDIF DUMPRECORD}

  function ReadBytes(ReadBuf: Pointer; ByteAmount : Word) : Integer;

  Var
    P : PByte;
    Count : Integer;

  begin
    Result := 0;
    P:=ReadBuf;
    if (ByteAmount=0) then exit;
    Repeat
      Count:=DoFastCGIRead(FHandle,P^,ByteAmount);
      If (Count>0) then
        begin
        Dec(ByteAmount,Count);
        P:=P+Count;
        Inc(Result,Count);
        end
      else if (Count<0) then
        DoError(SErrReadingSocket,[Count]);
    until (ByteAmount=0) or (Count=0);
  end;

var Header : FCGI_Header;
    BytesRead : integer;
    ContentLength : word;
    PaddingLength : byte;
    ResRecord : pointer;
    ReadBuf : pointer;


begin
  Result := Nil;
  ResRecord:=Nil;
  ReadBuf:=@Header;
  BytesRead:=ReadBytes(ReadBuf,Sizeof(Header));
  If (BytesRead=0) then
    Exit // Connection closed gracefully.
    // TODO : if connection closed gracefully, the request should no longer be handled.
    // Need to discard request/response
  else If (BytesRead<>Sizeof(Header)) then
    DoError(SErrReadingHeader,[BytesRead]);
  ContentLength:=BetoN(Header.contentLength);
  PaddingLength:=Header.paddingLength;
  Getmem(ResRecord,BytesRead+ContentLength+PaddingLength);
  try
    PFCGI_Header(ResRecord)^:=Header;
    ReadBuf:=ResRecord+BytesRead;
    BytesRead:=ReadBytes(ReadBuf,ContentLength);
    If (BytesRead=0) and (ContentLength>0) then
      begin
      FreeMem(resRecord);
      Exit // Connection closed gracefully.
      // TODO : properly handle connection close
      end;
    ReadBuf:=ReadBuf+BytesRead;
    BytesRead:=ReadBytes(ReadBuf,PaddingLength);
    If (BytesRead=0) and (PaddingLength>0) then
      begin
      FreeMem(resRecord);
      Exit // Connection closed gracefully.
      // TODO : properly handle connection close
      end;
    Result := ResRecord;
  except
    FreeMem(resRecord);
    Raise;
  end;
end;

procedure TFCgiHandler.SetupSocket(var IAddress : TInetSockAddr; Var AddressLength : tsocklen);

Var
  L : Linger;
  ll,lr : integer;

begin
  AddressLength:=Sizeof(IAddress);
  Socket := fpsocket(AF_INET,SOCK_STREAM,0);
  if Socket=-1 then
    DoError(SNoSocket,[socketerror]);
  IAddress.sin_family:=AF_INET;
  IAddress.sin_port:=htons(Port);
  if FAddress<>'' then
    Iaddress.sin_addr := StrToHostAddr(FAddress)
  else
    IAddress.sin_addr.s_addr:=0;
    {$IFDEF Unix}
    // remedy socket port locking on Posix platforms
    If (poReuseAddress in ProtocolOptions) then
      fpSetSockOpt(Socket, SOL_SOCKET, SO_REUSEADDR, @IAddress, SizeOf(IAddress));
    {$ENDIF}
  if fpbind(Socket,@IAddress,AddressLength)=-1 then
    begin
    CloseSocket(socket);
    Socket:=0;
    Terminate;
    DoError(SBindFailed,[port,socketerror]);
    end;
  if (FLingerTimeout>0) then
    begin
    ll:=SizeOf(l);
    if fpgetsockopt(Socket,SOL_SOCKET,SO_LINGER,@l,@ll)=0 then
      begin
//      Log(etDebug,Format('Socket linger : %d, %d',[L.l_linger,L.l_onoff]));
      if (L.l_onoff=0) then
        begin
        l.l_onoff:=1;
        l.l_linger:=1;
        lr:=fpsetsockopt(Socket,SOL_SOCKET,SO_LINGER,@l,ll);
        if (lr<>0) then
          Log(etError,Format('Set socket linger failed : %d',[lr]));
        end;
      end;
    end;
  if fplisten(Socket,10)=-1 then
    begin
    CloseSocket(socket);
    Socket:=0;
    Terminate;
    DoError(SListenFailed,[port,socketerror]);
    end;
end;

{$ifdef unix}
function TFCgiHandler.DataAvailable: Boolean;

var
  FDS: TFDSet;
  TimeV: TTimeVal;

begin
  FDS := Default(TFDSet);
  fpFD_Zero(FDS);
  fpFD_Set(FHandle, FDS);
  TimeV.tv_usec := (Timeout mod 1000) * 1000;
  TimeV.tv_sec := Timeout div 1000;
  Result := fpSelect(FHandle + 1, @FDS, @FDS, @FDS, @TimeV) > 0;
end;
{$else}
function TFCgiHandler.DataAvailable: Boolean;

var
  FDS: TFDSet;
  TimeV: TTimeVal;

begin
  FDS := Default(TFDSet);
  FD_Zero(FDS);
  FD_Set(FHandle, FDS);
  TimeV.tv_usec := (Timeout mod 1000) * 1000;
  TimeV.tv_sec := Timeout div 1000;
  Result := Select(FHandle + 1, @FDS, @FDS, @FDS, @TimeV) <> 0;
end;
{$endif}

function TFCgiHandler.CreateRequest: TFCGIRequest;

Var
  C : TFCGIRequestClass;

begin
  C:=FCGIRequestClass;
  if (C=Nil) then
    C:=TFCGIRequest;
  Result:=C.Create;
end;

function TFCgiHandler.CreateResponse(ARequest: TFCGIRequest): TFCGIResponse;
Var
  C : TFCGIResponseClass;

begin
  C:=FCGIResponseClass;
  if (C=Nil) then
    C:=TFCGIResponse;
  Result := C.Create(ARequest);
end;

function TFCgiHandler.DoFastCGIRead(AHandle: THandle; var ABuf; ACount: Integer): Integer;
begin
{$ifdef windowspipe}
  if FIsWinPipe then
    Result:=FileRead(AHandle,ABuf,ACount)
  else
{$endif}
    Result:=sockets.fpRecv(AHandle, @Abuf, ACount, NoSignalAttr);
end;

function TFCgiHandler.DoFastCGIWrite(AHandle: THandle; const ABuf;
  ACount: Integer; Out ExtendedErrorCode : Integer): Integer;
begin
  {$ifdef windowspipe}
  if FIsWinPipe then
    begin
    ExtendedErrorCode:=0;
    Result := FileWrite(AHandle, ABuf, ACount);
    if (Result<0) then
      ExtendedErrorCode:=GetLastOSError;
    end
  else
  {$endif windows}
    begin
    Repeat
      ExtendedErrorCode:=0;
      Result:=sockets.fpsend(AHandle, @ABuf, ACount, NoSignalAttr);
      if (Result<0) then
        ExtendedErrorCode:=sockets.socketerror;
    until (Result>=0) {$ifdef unix} or (ExtendedErrorCode<>ESysEINTR);{$endif}
    end;
end;

function TFCgiHandler.ProcessRecord(AFCGI_Record  : PFCGI_Header; out ARequest: TRequest; out AResponse: TResponse): boolean;

var
  ARequestID    : word;
  ATempRequest  : TFCGIRequest;
begin
  Result:=False;
  ARequestID:=BEtoN(AFCGI_Record^.requestID);
  if AFCGI_Record^.reqtype = FCGI_BEGIN_REQUEST then
    begin
    if ARequestID>FRequestsAvail then
      begin
      inc(FRequestsAvail,10);
      SetLength(FRequestsArray,FRequestsAvail);
      end;
    assert(not assigned(FRequestsArray[ARequestID].Request));
    assert(not assigned(FRequestsArray[ARequestID].Response));
    ATempRequest:=CreateRequest;
    InitRequest(ATempRequest);
    ATempRequest.RequestID:=ARequestID;
    ATempRequest.Handle:=FHandle;
    ATempRequest.ProtocolOptions:=Self.Protocoloptions;
    ATempRequest.OnUnknownRecord:=Self.OnUnknownRecord;
    ATempRequest.FLog:=@Log;
    FRequestsArray[ARequestID].Request := ATempRequest;
    end;
  if (ARequestID>FRequestsAvail) then
    begin
    // TODO : ARequestID can be invalid. What to do ?
    // in each case not try to access the array with requests.
    end
  else if FRequestsArray[ARequestID].Request.ProcessFCGIRecord(AFCGI_Record) then
    begin
    ARequest:=FRequestsArray[ARequestID].Request;
    FRequestsArray[ARequestID].Response := CreateResponse(TFCGIRequest(ARequest));
    InitResponse(FRequestsArray[ARequestID].Response);
    FRequestsArray[ARequestID].Response.ProtocolOptions:=Self.ProtocolOptions;
    FRequestsArray[ARequestID].Response.FOnWrite:=@DoFastCGIWrite;
    AResponse:=FRequestsArray[ARequestID].Response;
    Result := True;
    end;
end;

function TFCgiHandler.AcceptConnection : Integer;

{$ifdef windows}
Var
  B : BOOL;
  pipeMode : DWORD = PIPE_READMODE_BYTE or PIPE_WAIT;
  i : integer;
{$endif}

begin
{$ifndef windows}
  repeat
    Result:=fpaccept(Socket,nil,nil);
  until ((result<>-1) or (SocketError<>ESysEINTR)) and not Terminated;
{$else}
{$ifndef windowspipe}
  Result:=fpaccept(Socket,Nil,nil);
{$else windowspipe}
  if Not fIsWinPipe then
    Result:=fpaccept(Socket,Nil,Nil);
  If FIsWinPipe or ((Result<0) and ((socketerror=10038) or (socketerror = 10022))) then
    begin
    Result:=-1;
    B:=ConnectNamedPipe(Socket,Nil);
    if B or (GetLastError=ERROR_PIPE_CONNECTED) then
       begin
       Result:=Socket;
       if Not FIsWinPipe then // First time, set handle state
         if not SetNamedPipeHandleState(Result,@PipeMode,Nil,Nil) then
           begin
           I:=GetLastError;
           Log(etError,'Setting named pipe handle state failed : '+intToStr(i));
           end;
       FIsWinPipe:=True;
       end;
    end;
{$endif}
{$endif}
end;

function TFCgiHandler.WaitForRequest(out ARequest: TRequest; out AResponse: TResponse): boolean;

var
  AFCGI_Record  : PFCGI_Header;


begin
  Result := False;
  AResponse:=Nil;
  ARequest:=Nil;
  if Socket=0 then
    if Port<>0 then
      SetupSocket(FIAddress,FAddressLength)
    else
      Socket:=StdInputHandle;
  Repeat
    if FHandle=THandle(-1) then
      FHandle:=AcceptConnection;
    if FHandle=THandle(-1) then
      begin
      if not terminated then
        begin
        Terminate;
        DoError(SNoInputHandle,[socketerror]);
        end
      end;
    repeat
      If (poUseSelect in ProtocolOptions) then
        begin
        While Not DataAvailable do
          If (OnIdle<>Nil) then
            OnIdle(Self);
        end;
      AFCGI_Record:=Read_FCGIRecord;
      // If connection closed gracefully, we have nil.
      if Not Assigned(AFCGI_Record) then
        CloseConnection
      else
        try
        Result:=ProcessRecord(AFCGI_Record,ARequest,AResponse);
        Finally
          FreeMem(AFCGI_Record);
          AFCGI_Record:=Nil;
        end;
    until Result or (FHandle=THandle(-1));
  Until Result;
end;

{ TCustomFCgiApplication }

function TCustomFCgiApplication.GetAddress: string;
begin
  result := TFCgiHandler(WebHandler).Address;
end;

function TCustomFCgiApplication.GetFPO: TProtoColOptions;
begin
  result := TFCgiHandler(WebHandler).ProtocolOptions;
end;

function TCustomFCgiApplication.GetLingerTimeOut: integer;
begin
  Result:=TFCgiHandler(WebHandler).LingerTimeOut;
end;

function TCustomFCgiApplication.GetOnUnknownRecord: TUnknownRecordEvent;
begin
  result := TFCgiHandler(WebHandler).OnUnknownRecord;
end;

function TCustomFCgiApplication.GetPort: integer;
begin
  result := TFCgiHandler(WebHandler).Port;
end;

procedure TCustomFCgiApplication.SetAddress(const AValue: string);
begin
  TFCgiHandler(WebHandler).Address := AValue;
end;

procedure TCustomFCgiApplication.SetLingerTimeOut(const AValue: integer);
begin
  TFCgiHandler(WebHandler).LingerTimeOut:=AValue;
end;

procedure TCustomFCgiApplication.SetOnUnknownRecord(const AValue: TUnknownRecordEvent);
begin
  TFCgiHandler(WebHandler).OnUnknownRecord := AValue;
end;

procedure TCustomFCgiApplication.SetPort(const AValue: integer);
begin
  TFCgiHandler(WebHandler).Port := AValue;
end;

procedure TCustomFCgiApplication.SetPO(const AValue: TProtoColOptions);
begin
  TFCgiHandler(WebHandler).ProtocolOptions := AValue;
end;

function TCustomFCgiApplication.InitializeWebHandler: TWebHandler;

Var
  C : TFCGIHandlerClass;

begin
  C:=FCGIWebHandlerClass;
  If C=Nil then
    C:=TFCgiHandler;
  Result:=C.Create(self);
end;

end.
