1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258 |
- { Web server component, built on the HTTP server component
- Copyright (C) 2006-2008 Micha Nelissen
- This library is Free software; you can redistribute it and/or modify it
- under the terms of the GNU Library General Public License as published by
- the Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- 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. See the GNU Library General Public License
- for more details.
- You should have received a Copy of the GNU Library General Public License
- along with This library; if not, Write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- This license has been modified. See file LICENSE.ADDON for more information.
- Should you find these sources without a LICENSE File, please contact
- me at [email protected]
- }
- unit lwebserver;
- {$mode objfpc}{$h+}
- {$inline on}
- interface
- uses
- sysutils, classes, lhttp, lhttputil, lmimetypes, levents,
- lprocess, process, lfastcgi, fastcgi_base;
- type
- TLMultipartParameter = (mpContentType, mpContentDisposition, mpContentTransferEncoding,
- mpContentID, mpContentDescription);
- TLMultipartState = (msStart, msBodypartHeader, msBodypartData);
- const
- URIParamSepChar: char = '&';
- CookieSepChar: char = ';';
- FormURLContentType: pchar = 'application/x-www-form-urlencoded';
- MultipartContentType: pchar = 'multipart/form-data';
- MPParameterStrings: array[TLMultipartParameter] of string =
- ('Content-Type', 'Content-Disposition', 'Content-Transfer-Encoding',
- 'Content-ID', 'Content-Discription');
- type
- TDocumentHandler = class;
- TFileHandler = class;
- TFileOutput = class(TBufferOutput)
- protected
- FFile: file;
- function GetSize: integer;
- function FillBuffer: TWriteBlockStatus; override;
- public
- constructor Create(ASocket: TLHTTPSocket);
- destructor Destroy; override;
- function Open(const AFileName: string): boolean;
- property Size: integer read GetSize;
- end;
- TCGIOutput = class(TBufferOutput)
- protected
- FParsePos: pchar;
- FReadPos: integer;
- FParsingHeaders: boolean;
-
- procedure AddEnvironment(const AName, AValue: string); virtual; abstract;
- procedure AddHTTPParam(const AName: string; AParam: TLHTTPParameter);
- function ParseHeaders: boolean;
- procedure CGIOutputError; virtual; abstract;
- procedure WriteCGIBlock;
- function WriteCGIData: TWriteBlockStatus; virtual; abstract;
- public
- FDocumentRoot: string;
- FExtraPath: string;
- FEnvPath: string;
- FScriptFileName: string;
- FScriptName: string;
- constructor Create(ASocket: TLHTTPSocket);
- destructor Destroy; override;
- function FillBuffer: TWriteBlockStatus; override;
- procedure StartRequest; virtual;
- end;
- TSimpleCGIOutput = class(TCGIOutput)
- protected
- FProcess: TLProcess;
- procedure AddEnvironment(const AName, AValue: string); override;
- procedure CGIProcNeedInput(AHandle: TLHandle);
- procedure CGIProcHasOutput(AHandle: TLHandle);
- procedure CGIProcHasStderr(AHandle: TLHandle);
- procedure DoneInput; override;
- function HandleInput(ABuffer: pchar; ASize: integer): integer; override;
- procedure CGIOutputError; override;
- function WriteCGIData: TWriteBlockStatus; override;
- public
- constructor Create(ASocket: TLHTTPSocket);
- destructor Destroy; override;
- procedure StartRequest; override;
- property Process: TLProcess read FProcess;
- end;
- TFastCGIOutput = class(TCGIOutput)
- protected
- FRequest: TLFastCGIRequest;
- procedure AddEnvironment(const AName, AValue: string); override;
- procedure CGIOutputError; override;
- procedure DoneInput; override;
- procedure RequestEnd(ARequest: TLFastCGIRequest);
- procedure RequestNeedInput(ARequest: TLFastCGIRequest);
- procedure RequestHasOutput(ARequest: TLFastCGIRequest);
- procedure RequestHasStderr(ARequest: TLFastCGIRequest);
- function HandleInput(ABuffer: pchar; ASize: integer): integer; override;
- function WriteCGIData: TWriteBlockStatus; override;
- function WriteBlock: TWriteBlockStatus; override;
- public
- constructor Create(ASocket: TLHTTPSocket);
- destructor Destroy; override;
- procedure StartRequest; override;
- property Request: TLFastCGIRequest read FRequest write FRequest;
- end;
- TCGIHandler = class(TURIHandler)
- protected
- function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override;
- public
- FCGIRoot: string;
- FEnvPath: string;
- FDocumentRoot: string;
- FScriptPathPrefix: string;
- end;
- TDocumentRequest = record
- Socket: TLHTTPServerSocket;
- Document: string;
- URIPath: string;
- ExtraPath: string;
- Info: TSearchRec;
- InfoValid: boolean;
- end;
- TDocumentHandler = class(TObject)
- private
- FNext: TDocumentHandler;
- protected
- FFileHandler: TFileHandler;
- procedure RegisterWithEventer(AEventer: TLEventer); virtual;
- public
- function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; virtual; abstract;
- property FileHandler: TFileHandler read FFileHandler;
- end;
- { TFileHandler }
- TFileHandler = class(TURIHandler)
- protected
- FDocHandlerList: TDocumentHandler;
- FDirIndexList: TStrings;
- FMimeTypeFile: string;
- procedure SetMimeTypeFile(const AValue: string);
- function HandleFile(const ARequest: TDocumentRequest): TOutputItem;
- function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override;
- procedure RegisterWithEventer(AEventer: TLEventer); override;
- public
- DocumentRoot: string;
- constructor Create;
- destructor Destroy; override;
-
- procedure RegisterHandler(AHandler: TDocumentHandler);
- property DirIndexList: TStrings read FDirIndexList;
- property MimeTypeFile: string read FMimeTypeFile write SetMimeTypeFile;
- end;
- TPHPCGIHandler = class(TDocumentHandler)
- protected
- FAppName: string;
- FEnvPath: string;
- public
- function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; override;
- property AppName: string read FAppName write FAppName;
- property EnvPath: string read FEnvPath write FEnvPath;
- end;
- TPHPFastCGIHandler = class(TDocumentHandler)
- protected
- FPool: TLFastCGIPool;
- FEnvPath: string;
- function GetAppEnv: string;
- function GetAppName: string;
- function GetHost: string;
- function GetPort: integer;
- procedure RegisterWithEventer(AEventer: TLEventer); override;
- procedure SetAppEnv(NewEnv: string);
- procedure SetAppName(NewName: string);
- procedure SetHost(NewHost: string);
- procedure SetPort(NewPort: integer);
- public
- constructor Create;
- destructor Destroy; override;
- function HandleDocument(const ARequest: TDocumentRequest): TOutputItem; override;
- property AppEnv: string read GetAppEnv write SetAppEnv;
- property AppName: string read GetAppName write SetAppName;
- property EnvPath: string read FEnvPath write FEnvPath;
- property Host: string read GetHost write SetHost;
- property Pool: TLFastCGIPool read FPool;
- property Port: integer read GetPort write SetPort;
- end;
- { Forms }
- TFormOutput = class;
- TFillBufferEvent = procedure(AFormOutput: TFormOutput; var AStatus: TWriteBlockStatus);
- THandleInputMethod = function(ABuffer: pchar; ASize: integer): integer of object;
- TFormOutput = class(TBufferOutput)
- protected
- FBoundary: pchar;
- FRequestVars: TStrings;
- FMPParameters: array[TLMultipartParameter] of pchar;
- FMPState: TLMultipartState;
- FOnExtraHeaders: TNotifyEvent;
- FOnFillBuffer: TFillBufferEvent;
- FHandleInput: THandleInputMethod;
- procedure DoneInput; override;
- function FillBuffer: TWriteBlockStatus; override;
- function FindBoundary(ABuffer: pchar): pchar;
- function HandleInput(ABuffer: pchar; ASize: integer): integer; override;
- function HandleInputDiscard(ABuffer: pchar; ASize: integer): integer;
- function HandleInputFormURL(ABuffer: pchar; ASize: integer): integer;
- function HandleInputMultipart(ABuffer: pchar; ASize: integer): integer;
- procedure ParseMultipartHeader(ABuffer, ALineEnd: pchar);
- public
- constructor Create(ASocket: TLHTTPSocket);
- destructor Destroy; override;
- function AddVariables(Variables: pchar; ASize: integer; SepChar: char): integer;
- procedure DeleteCookie(const AName: string; const APath: string = '/';
- const ADomain: string = '');
- procedure SetCookie(const AName, AValue: string; const AExpires: TDateTime;
- const APath: string = '/'; const ADomain: string = '');
- property OnExtraHeaders: TNotifyEvent read FOnExtraHeaders write FOnExtraHeaders;
- property OnFillBuffer: TFillBufferEvent read FOnFillBuffer write FOnFillBuffer;
- end;
- THandleURIEvent = function(ASocket: TLHTTPServerSocket): TFormOutput;
- TFormHandler = class(TURIHandler)
- protected
- FOnHandleURI: THandleURIEvent;
- function HandleURI(ASocket: TLHTTPServerSocket): TOutputItem; override;
- procedure SelectMultipart(AFormOutput: TFormOutput; AContentType: pchar);
- public
- property OnHandleURI: THandleURIEvent read FOnHandleURI write FOnHandleURI;
- end;
- var
- EnableWriteln: Boolean = True;
- implementation
- uses
- lstrbuffer;
- { Example handlers }
- const
- InputBufferEmptyToWriteStatus: array[boolean] of TWriteBlockStatus =
- (wsPendingData, wsWaitingData);
-
- procedure InternalWrite(const s: string);
- begin
- if EnableWriteln then
- Writeln(s);
- end;
- procedure TDocumentHandler.RegisterWithEventer(AEventer: TLEventer);
- begin
- end;
- function TCGIHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem;
- var
- lOutput: TSimpleCGIOutput;
- lExecPath: string;
- begin
- if StrLComp(ASocket.FRequestInfo.Argument, PChar(FScriptPathPrefix),
- Length(FScriptPathPrefix)) = 0 then
- begin
- lOutput := TSimpleCGIOutput.Create(ASocket);
- lOutput.FDocumentRoot := FDocumentRoot;
- lOutput.FEnvPath := FEnvPath;
- lOutput.Process.CurrentDirectory := FCGIRoot;
- lExecPath := ASocket.FRequestInfo.Argument+Length(FScriptPathPrefix);
- DoDirSeparators(lExecPath);
- lExecPath := FCGIRoot+lExecPath;
- if SeparatePath(lExecPath, lOutput.FExtraPath, faAnyFile and not faDirectory) then
- begin
- lOutput.Process.CommandLine := lExecPath;
- lOutput.FScriptFileName := lExecPath;
- lOutput.FScriptName := Copy(lExecPath, Length(FCGIRoot),
- Length(lExecPath)-Length(FCGIRoot)+1);
- lOutput.StartRequest;
- end else
- ASocket.FResponseInfo.Status := hsNotFound;
- Result := lOutput;
- end else
- Result := nil;
- end;
- constructor TFileHandler.Create;
- begin
- inherited;
- FDirIndexList := TStringList.Create;
- end;
- destructor TFileHandler.Destroy;
- begin
- FreeAndNil(FDirIndexList);
- inherited;
- end;
- procedure TFileHandler.RegisterWithEventer(AEventer: TLEventer);
- var
- lHandler: TDocumentHandler;
- begin
- lHandler := FDocHandlerList;
- while lHandler <> nil do
- begin
- lHandler.RegisterWithEventer(AEventer);
- lHandler := lHandler.FNext;
- end;
- end;
- procedure TFileHandler.SetMimeTypeFile(const AValue: string);
- begin
- FMimeTypeFile:=AValue;
- InitMimeList(aValue);
- end;
- function TFileHandler.HandleFile(const ARequest: TDocumentRequest): TOutputItem;
- var
- lFileOutput: TFileOutput;
- lReqInfo: PRequestInfo;
- lRespInfo: PResponseInfo;
- lHeaderOut: PHeaderOutInfo;
- lIndex: integer;
- begin
- Result := nil;
- if ARequest.InfoValid then
- begin
- lReqInfo := @ARequest.Socket.FRequestInfo;
- lRespInfo := @ARequest.Socket.FResponseInfo;
- lHeaderOut := @ARequest.Socket.FHeaderOut;
- if not (lReqInfo^.RequestType in [hmHead, hmGet]) then
- begin
- lRespInfo^.Status := hsNotAllowed;
- end else begin
- lFileOutput := TFileOutput.Create(ARequest.Socket);
- if lFileOutput.Open(ARequest.Document) then
- begin
- lRespInfo^.Status := hsOK;
- lHeaderOut^.ContentLength := ARequest.Info.Size;
- lRespInfo^.LastModified := LocalTimeToGMT(FileDateToDateTime(ARequest.Info.Time));
- lIndex := MimeList.IndexOf(ExtractFileExt(ARequest.Document));
- if lIndex >= 0 then
- lRespInfo^.ContentType := TStringObject(MimeList.Objects[lIndex]).Str;
- Result := lFileOutput;
- ARequest.Socket.StartResponse(lFileOutput);
- end else
- lFileOutput.Free;
- end;
- end;
- end;
- function TFileHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem;
- var
- lDocRequest: TDocumentRequest;
- lHandler: TDocumentHandler;
- lTempDoc: string;
- lDirIndexFound: boolean;
- I: integer;
- begin
- Result := nil;
- lDocRequest.Socket := ASocket;
- lDocRequest.URIPath := ASocket.FRequestInfo.Argument;
- lDocRequest.Document := lDocRequest.URIPath;
- DoDirSeparators(LDocRequest.Document);
- lDocRequest.Document := IncludeTrailingPathDelimiter(DocumentRoot)
- + lDocRequest.Document;
- lDocRequest.InfoValid := SeparatePath(lDocRequest.Document,lDocRequest.ExtraPath,
- faAnyFile, @lDocRequest.Info);
- if not lDocRequest.InfoValid then
- exit;
- if (lDocRequest.Info.Attr and faDirectory) <> 0 then
- begin
- lDirIndexFound := false;
- { if non-trivial ExtraPath, then it's not a pure directory request, so do
- not show default directory document }
- if lDocRequest.ExtraPath = PathDelim then
- begin
- lDocRequest.Document := IncludeTrailingPathDelimiter(lDocRequest.Document);
- for I := 0 to FDirIndexList.Count - 1 do
- begin
- lTempDoc := lDocRequest.Document + FDirIndexList.Strings[I];
- lDocRequest.InfoValid := FindFirst(lTempDoc,
- faAnyFile and not faDirectory, lDocRequest.Info) = 0;
- FindClose(lDocRequest.Info);
- if lDocRequest.InfoValid and ((lDocRequest.Info.Attr and faDirectory) = 0) then
- begin
- lDocRequest.Document := lTempDoc;
- lDirIndexFound := true;
- break;
- end;
- end;
- end;
- { requested a directory, but no source to show }
- if not lDirIndexFound then exit;
- end;
- lHandler := FDocHandlerList;
- while lHandler <> nil do
- begin
- Result := lHandler.HandleDocument(lDocRequest);
- if Result <> nil then exit;
- if ASocket.FResponseInfo.Status <> hsOK then exit;
- lHandler := lHandler.FNext;
- end;
- { no dynamic handler, see if it's a plain file }
- Result := HandleFile(lDocRequest);
- end;
- procedure TFileHandler.RegisterHandler(AHandler: TDocumentHandler);
- begin
- if AHandler = nil then exit;
- AHandler.FFileHandler := Self;
- AHandler.FNext := FDocHandlerList;
- FDocHandlerList := AHandler;
- end;
- function TPHPCGIHandler.HandleDocument(const ARequest: TDocumentRequest): TOutputItem;
- var
- lOutput: TSimpleCGIOutput;
- begin
- if ExtractFileExt(ARequest.Document) = '.php' then
- begin
- lOutput := TSimpleCGIOutput.Create(ARequest.Socket);
- lOutput.FDocumentRoot := FFileHandler.DocumentRoot;
- lOutput.Process.CommandLine := FAppName;
- lOutput.FScriptName := ARequest.URIPath;
- lOutput.FScriptFileName := ARequest.Document;
- lOutput.FExtraPath := ARequest.ExtraPath;
- lOutput.FEnvPath := FEnvPath;
- lOutput.StartRequest;
- Result := lOutput;
- end else
- Result := nil;
- end;
- constructor TPHPFastCGIHandler.Create;
- begin
- inherited;
- FPool := TLFastCGIPool.Create;
- end;
- destructor TPHPFastCGIHandler.Destroy;
- begin
- inherited;
- FPool.Free;
- end;
- function TPHPFastCGIHandler.GetAppEnv: string;
- begin
- Result := FPool.AppEnv;
- end;
- function TPHPFastCGIHandler.GetAppName: string;
- begin
- Result := FPool.AppName;
- end;
- function TPHPFastCGIHandler.GetHost: string;
- begin
- Result := FPool.Host;
- end;
- function TPHPFastCGIHandler.GetPort: integer;
- begin
- Result := FPool.Port;
- end;
- procedure TPHPFastCGIHandler.SetAppEnv(NewEnv: string);
- begin
- FPool.AppEnv := NewEnv;
- end;
- procedure TPHPFastCGIHandler.SetAppName(NewName: string);
- begin
- FPool.AppName := NewName;
- end;
- procedure TPHPFastCGIHandler.SetHost(NewHost: string);
- begin
- FPool.Host := NewHost;
- end;
- procedure TPHPFastCGIHandler.SetPort(NewPort: integer);
- begin
- FPool.Port := NewPort;
- end;
- procedure TPHPFastCGIHandler.RegisterWithEventer(AEventer: TLEventer);
- begin
- FPool.Eventer := AEventer;
- end;
- function TPHPFastCGIHandler.HandleDocument(const ARequest: TDocumentRequest): TOutputItem;
- var
- lOutput: TFastCGIOutput;
- fcgiRequest: TLFastCGIRequest;
- begin
- if ExtractFileExt(ARequest.Document) = '.php' then
- begin
- fcgiRequest := FPool.BeginRequest(FCGI_RESPONDER);
- if fcgiRequest <> nil then
- begin
- lOutput := TFastCGIOutput.Create(ARequest.Socket);
- lOutput.FDocumentRoot := FFileHandler.DocumentRoot;
- lOutput.FScriptName := ARequest.URIPath;
- lOutput.FScriptFileName := ARequest.Document;
- lOutput.FExtraPath := ARequest.ExtraPath;
- lOutput.FEnvPath := FEnvPath;
- lOutput.Request := fcgiRequest;
- ARequest.Socket.SetupEncoding(lOutput);
- lOutput.StartRequest;
- Result := lOutput;
- end else begin
- ARequest.Socket.FResponseInfo.Status := hsInternalError;
- ARequest.Socket.StartResponse(nil);
- Result := nil;
- end;
- end else
- Result := nil;
- end;
- { Output Items }
- constructor TFileOutput.Create(ASocket: TLHTTPSocket);
- begin
- inherited;
- FEof := true;
- end;
- destructor TFileOutput.Destroy;
- begin
- inherited;
-
- if not FEof then
- Close(FFile);
- end;
- function TFileOutput.Open(const AFileName: string): boolean;
- begin
- {$I-}
- FileMode := 0;
- Assign(FFile, AFileName);
- Reset(FFile,1);
- {$I+}
- Result := IOResult = 0;
- FEof := false;
- end;
- function TFileOutput.GetSize: integer; inline;
- begin
- Result := FileSize(FFile);
- end;
- function TFileOutput.FillBuffer: TWriteBlockStatus;
- var
- lRead: integer;
- begin
- if FEof then
- exit(wsDone);
- BlockRead(FFile, FBuffer[FBufferPos], FBufferSize-FBufferPos, lRead);
- Inc(FBufferPos, lRead);
- if lRead = 0 then
- begin
- { EOF reached }
- Close(FFile);
- exit(wsDone);
- end;
- Result := wsPendingData;
- end;
- constructor TCGIOutput.Create(ASocket: TLHTTPSocket);
- begin
- inherited;
- end;
- destructor TCGIOutput.Destroy;
- begin
- inherited;
- end;
- procedure TCGIOutput.AddHTTPParam(const AName: string; AParam: TLHTTPParameter);
- var
- lValue: pchar;
- begin
- lValue := FSocket.Parameters[AParam];
- if lValue = nil then exit;
- AddEnvironment(AName, lValue);
- end;
- procedure TCGIOutput.StartRequest;
- var
- lServerSocket: TLHTTPServerSocket;
- tempStr: string;
- begin
- lServerSocket := TLHTTPServerSocket(FSocket);
- {
- FProcess.Environment.Add('SERVER_ADDR=');
- FProcess.Environment.Add('SERVER_ADMIN=');
- FProcess.Environment.Add('SERVER_NAME=');
- FProcess.Environment.Add('SERVER_PORT=');
- }
- Self := nil;
- tempStr := TLHTTPServer(lServerSocket.Creator).ServerSoftware;
- if Length(tempStr) > 0 then
- AddEnvironment('SERVER_SOFTWARE', tempStr);
- AddEnvironment('GATEWAY_INTERFACE', 'CGI/1.1');
- AddEnvironment('SERVER_PROTOCOL', lServerSocket.FRequestInfo.VersionStr);
- AddEnvironment('REQUEST_METHOD', lServerSocket.FRequestInfo.Method);
- AddEnvironment('REQUEST_URI', '/'+lServerSocket.FRequestInfo.Argument);
- if Length(FExtraPath) > 0 then
- begin
- AddEnvironment('PATH_INFO', FExtraPath);
- { do not set PATH_TRANSLATED: bug in PHP }
- // AddEnvironment('PATH_TRANSLATED', DocumentRoot+FExtraPath);
- end;
- AddEnvironment('SCRIPT_NAME', FScriptName);
- AddEnvironment('SCRIPT_FILENAME', FScriptFileName);
-
- AddEnvironment('QUERY_STRING', lServerSocket.FRequestInfo.QueryParams);
- AddHTTPParam('CONTENT_TYPE', hpContentType);
- AddHTTPParam('CONTENT_LENGTH', hpContentLength);
- AddEnvironment('REMOTE_ADDR', FSocket.PeerAddress);
- AddEnvironment('REMOTE_PORT', IntToStr(FSocket.LocalPort));
- { used when user has authenticated in some way to server }
- // AddEnvironment('AUTH_TYPE='+...);
- // AddEnvironment('REMOTE_USER='+...);
-
- AddEnvironment('DOCUMENT_ROOT', FDocumentRoot);
- AddEnvironment('REDIRECT_STATUS', '200');
- AddHTTPParam('HTTP_HOST', hpHost);
- AddHTTPParam('HTTP_COOKIE', hpCookie);
- AddHTTPParam('HTTP_CONNECTION', hpConnection);
- AddHTTPParam('HTTP_REFERER', hpReferer);
- AddHTTPParam('HTTP_USER_AGENT', hpUserAgent);
- AddHTTPParam('HTTP_ACCEPT', hpAccept);
- AddEnvironment('PATH', FEnvPath);
- FParsingHeaders := true;
- FReadPos := FBufferPos;
- FParsePos := FBuffer+FReadPos;
- end;
- function TCGIOutput.ParseHeaders: boolean;
- var
- lHttpStatus: TLHTTPStatus;
- iEnd, lCode: integer;
- lStatus, lLength: dword;
- pLineEnd, pNextLine, pValue: pchar;
- lServerSocket: TLHTTPServerSocket;
- procedure AddExtraHeader;
- begin
- AppendString(lServerSocket.FHeaderOut.ExtraHeaders,
- FParsePos + ': ' + pValue + #13#10);
- end;
- begin
- lServerSocket := TLHTTPServerSocket(FSocket);
- repeat
- iEnd := IndexByte(FParsePos^, @FBuffer[FReadPos]-FParsePos, 10);
- if iEnd = -1 then exit(false);
- pNextLine := FParsePos+iEnd+1;
- if (iEnd > 0) and (FParsePos[iEnd-1] = #13) then
- dec(iEnd);
- pLineEnd := FParsePos+iEnd;
- pLineEnd^ := #0;
- if pLineEnd = FParsePos then
- begin
- { empty line signals end of headers }
- FParsingHeaders := false;
- FBufferOffset := pNextLine-FBuffer;
- FBufferPos := FReadPos;
- FReadPos := 0;
- lServerSocket.StartResponse(Self, true);
- exit(false);
- end;
- iEnd := IndexByte(FParsePos^, iEnd, ord(':'));
- if (iEnd = -1) or (FParsePos[iEnd+1] <> ' ') then
- break;
- FParsePos[iEnd] := #0;
- pValue := FParsePos+iEnd+2;
- if StrIComp(FParsePos, 'Content-type') = 0 then
- begin
- lServerSocket.FResponseInfo.ContentType := pValue;
- end else
- if StrIComp(FParsePos, 'Location') = 0 then
- begin
- if StrLIComp(pValue, 'http://', 7) = 0 then
- begin
- lServerSocket.FResponseInfo.Status := hsMovedPermanently;
- { add location header as-is to response }
- AddExtraHeader;
- end else
- InternalWrite('WARNING: unimplemented ''Location'' response received from CGI script');
- end else
- if StrIComp(FParsePos, 'Status') = 0 then
- begin
- { sometimes we get '<status code> space <reason>' }
- iEnd := IndexByte(pValue^, pLineEnd-pValue, ord(' '));
- if iEnd <> -1 then
- pValue[iEnd] := #0;
- Val(pValue, lStatus, lCode);
- if lCode <> 0 then
- break;
- for lHttpStatus := Low(TLHTTPStatus) to High(TLHTTPStatus) do
- if HTTPStatusCodes[lHttpStatus] = lStatus then
- lServerSocket.FResponseInfo.Status := lHttpStatus;
- end else
- if StrIComp(FParsePos, 'Content-Length') = 0 then
- begin
- Val(pValue, lLength, lCode);
- if lCode <> 0 then
- break;
- lServerSocket.FHeaderOut.ContentLength := lLength;
- end else
- if StrIComp(FParsePos, 'Last-Modified') = 0 then
- begin
- if not TryHTTPDateStrToDateTime(pValue,
- lServerSocket.FResponseInfo.LastModified) then
- InternalWrite('WARNING: unable to parse last-modified string from CGI script: ' + pValue);
- end else
- AddExtraHeader;
- FParsePos := pNextLine;
- until false;
- { error happened }
- lServerSocket.FResponseInfo.Status := hsInternalError;
- exit(true);
- end;
- function TCGIOutput.FillBuffer: TWriteBlockStatus;
- begin
- if not FParsingHeaders then
- FReadPos := FBufferPos;
- Result := WriteCGIData;
- if FParsingHeaders then
- begin
- if ParseHeaders then
- begin
- { error while parsing }
- FEof := true;
- exit(wsDone);
- end;
- end else
- FBufferPos := FReadPos;
- end;
- procedure TCGIOutput.WriteCGIBlock;
- begin
- { CGI process has output pending, we can write a block to socket }
- if FParsingHeaders then
- begin
- if (FillBuffer = wsDone) and FParsingHeaders then
- begin
- { still parsing headers ? something's wrong }
- FParsingHeaders := false;
- CGIOutputError;
- TLHTTPServerSocket(FSocket).StartResponse(Self);
- end;
- end;
- if not FParsingHeaders then
- FSocket.WriteBlock;
- end;
- { TSimpleCGIOutput }
- constructor TSimpleCGIOutput.Create(ASocket: TLHTTPSocket);
- begin
- inherited;
- FProcess := TLProcess.Create(nil);
- FProcess.Options := FProcess.Options + [poUsePipes];
- FProcess.OnNeedInput := @CGIProcNeedInput;
- FProcess.OnHasOutput := @CGIProcHasOutput;
- FProcess.OnHasStderr := @CGIProcHasStderr;
- end;
- destructor TSimpleCGIOutput.Destroy;
- begin
- inherited;
- FProcess.Free;
- end;
- function TSimpleCGIOutput.WriteCGIData: TWriteBlockStatus;
- var
- lRead: integer;
- begin
- lRead := FProcess.Output.Read(FBuffer[FReadPos], FBufferSize-FReadPos);
- if lRead = 0 then exit(wsDone);
- Inc(FReadPos, lRead);
- Result := InputBufferEmptyToWriteStatus[lRead = 0];
- end;
- procedure TSimpleCGIOutput.AddEnvironment(const AName, AValue: string);
- begin
- FProcess.Environment.Add(AName+'='+AValue);
- end;
- procedure TSimpleCGIOutput.DoneInput;
- begin
- FProcess.CloseInput;
- end;
- function TSimpleCGIOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
- begin
- if ASize > 0 then
- Result := FProcess.Input.Write(ABuffer^, ASize)
- else
- Result := 0;
- FProcess.InputEvent.IgnoreWrite := ASize = 0;
- end;
- procedure TSimpleCGIOutput.StartRequest;
- begin
- inherited;
-
- FProcess.Eventer := FSocket.Eventer;
- FProcess.Execute;
- end;
- procedure TSimpleCGIOutput.CGIOutputError;
- var
- ServerSocket: TLHTTPServerSocket;
- begin
- ServerSocket := TLHTTPServerSocket(FSocket);
- if FProcess.ExitStatus = 127 then
- ServerSocket.FResponseInfo.Status := hsNotFound
- else
- ServerSocket.FResponseInfo.Status := hsInternalError;
- end;
- procedure TSimpleCGIOutput.CGIProcNeedInput(AHandle: TLHandle);
- begin
- FProcess.InputEvent.IgnoreWrite := true;
- FSocket.ParseBuffer;
- end;
- procedure TSimpleCGIOutput.CGIProcHasOutput(AHandle: TLHandle);
- begin
- WriteCGIBlock;
- end;
- procedure TSimpleCGIOutput.CGIProcHasStderr(AHandle: TLHandle);
- var
- lBuf: array[0..1023] of char;
- lRead: integer;
- begin
- lRead := FProcess.Stderr.Read(lBuf, sizeof(lBuf)-1);
- lBuf[lRead] := #0;
- write(pchar(@lBuf[0]));
- end;
- { TFastCGIOutput }
- constructor TFastCGIOutput.Create(ASocket: TLHTTPSocket);
- begin
- inherited;
- end;
- destructor TFastCGIOutput.Destroy;
- begin
- if FRequest <> nil then
- begin
- FRequest.OnInput := nil;
- FRequest.OnOutput := nil;
- FRequest.OnStderr := nil;
- FRequest.OnEndRequest := nil;
- FRequest.AbortRequest;
- end;
- inherited;
- end;
- procedure TFastCGIOutput.AddEnvironment(const AName, AValue: string);
- begin
- FRequest.SendParam(AName, AValue);
- end;
- procedure TFastCGIOutput.CGIOutputError;
- begin
- TLHTTPServerSocket(FSocket).FResponseInfo.Status := hsInternalError;
- end;
- procedure TFastCGIOutput.DoneInput;
- begin
- if FRequest <> nil then
- FRequest.DoneInput;
- end;
- procedure TFastCGIOutput.RequestEnd(ARequest: TLFastCGIRequest);
- begin
- FRequest.OnEndRequest := nil;
- FRequest.OnInput := nil;
- FRequest.OnOutput := nil;
- FRequest := nil;
- { trigger final write, to flush output to socket }
- WriteCGIBlock;
- end;
- procedure TFastCGIOutput.RequestNeedInput(ARequest: TLFastCGIRequest);
- begin
- FSocket.ParseBuffer;
- end;
- procedure TFastCGIOutput.RequestHasOutput(ARequest: TLFastCGIRequest);
- begin
- WriteCGIBlock;
- end;
- procedure TFastCGIOutput.RequestHasStderr(ARequest: TLFastCGIRequest);
- var
- lBuf: array[0..1023] of char;
- lRead: integer;
- begin
- lRead := ARequest.Get(lBuf, sizeof(lBuf)-1);
- lBuf[lRead] := #0;
- write(pchar(@lBuf[0]));
- end;
- function TFastCGIOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
- begin
- Result := FRequest.SendInput(ABuffer, ASize);
- end;
- function TFastCGIOutput.WriteCGIData: TWriteBlockStatus;
- var
- lRead: integer;
- begin
- if FRequest = nil then exit(wsDone);
- if FRequest.OutputDone then exit(wsDone);
- lRead := FRequest.Get(@FBuffer[FReadPos], FBufferSize-FReadPos);
- Inc(FReadPos, lRead);
- Result := InputBufferEmptyToWriteStatus[lRead = 0];
- end;
- function TFastCGIOutput.WriteBlock: TWriteBlockStatus;
- begin
- if (FRequest <> nil) and FRequest.OutputPending then
- begin
- FRequest.ParseClientBuffer;
- Result := wsWaitingData;
- end else
- Result := inherited;
- end;
- procedure TFastCGIOutput.StartRequest;
- begin
- FRequest.OnEndRequest := @RequestEnd;
- FRequest.OnInput := @RequestNeedInput;
- FRequest.OnOutput := @RequestHasOutput;
- FRequest.OnStderr := @RequestHasStderr;
- inherited;
- FRequest.DoneParams;
- end;
- { TFormOutput }
- constructor TFormOutput.Create(ASocket: TLHTTPSocket);
- begin
- inherited;
- FRequestVars := TStringList.Create;
- end;
- destructor TFormOutput.Destroy;
- var
- I: integer;
- tmpObj: TObject;
- begin
- for I := 0 to FRequestVars.Count - 1 do
- begin
- tmpObj := FRequestVars.Objects[I];
- Finalize(string(tmpObj));
- FRequestVars.Objects[I] := nil;
- end;
- FRequestVars.Free;
- inherited;
- end;
- function TFormOutput.AddVariables(Variables: pchar; ASize: integer; SepChar: char): integer;
- var
- varname, sep, next: pchar;
- strName, strValue: string;
- tmpObj: TObject;
- i: integer;
- begin
- if Variables = nil then
- exit(0);
- if ASize = -1 then
- ASize := StrLen(Variables);
- varname := Variables;
- repeat
- sep := varname + IndexChar(varname^, ASize, '=');
- if sep < varname then
- break;
- dec(ASize, sep-varname);
- next := sep + IndexChar(sep^, ASize, SepChar);
- if next < sep then
- begin
- next := sep + ASize;
- ASize := 0;
- end else
- dec(ASize, next+1-sep);
- if sep > varname then
- begin
- setlength(strName, sep-varname);
- move(varname[0], strName[1], sep-varname);
- setlength(strValue, next-sep-1);
- move(sep[1], strValue[1], next-sep-1);
- i := FRequestVars.Add(strName);
- tmpObj := nil;
- string(tmpObj) := strValue;
- FRequestVars.Objects[i] := tmpObj;
- end;
- varname := next+1;
- until false;
- Result := ASize;
- end;
- procedure TFormOutput.DoneInput;
- begin
- if Assigned(FOnExtraHeaders) then
- FOnExtraHeaders(Self);
- TLHTTPServerSocket(FSocket).StartResponse(Self);
- end;
- function TFormOutput.HandleInputFormURL(ABuffer: pchar; ASize: integer): integer;
- begin
- Result := ASize-AddVariables(ABuffer, ASize, URIParamSepChar)
- end;
- procedure TFormOutput.ParseMultipartHeader(ABuffer, ALineEnd: pchar);
- var
- I: TLMultipartParameter;
- len: integer;
- begin
- for I := Low(TLMultipartParameter) to High(TLMultipartParameter) do
- begin
- len := Length(MPParameterStrings[I]);
- if ABuffer+len >= ALineEnd then
- continue;
- if (ABuffer[len] = ':')
- and (StrLIComp(ABuffer, PChar(MPParameterStrings[I]), len) = 0) then
- begin
- Inc(ABuffer, len+2);
- repeat
- if ABuffer = ALineEnd then exit;
- if ABuffer^ <> ' ' then break;
- inc(ABuffer);
- until false;
- FMPParameters[I] := ABuffer;
- if I = mpContentType then
- begin
- repeat
- if ABuffer = ALineEnd then exit;
- if ABuffer = ';' then break;
- inc(ABuffer);
- until false;
- end;
- break;
- end;
- end;
- end;
- function TFormOutput.FindBoundary(ABuffer: pchar): pchar;
- begin
- {$warning TODO}
- Result := nil;
- end;
- function TFormOutput.HandleInputMultipart(ABuffer: pchar; ASize: integer): integer;
- var
- pos, next, endline: pchar;
- begin
- pos := ABuffer;
- repeat
- case FMPState of
- msStart:
- begin
- { discard until first boundary }
- next := FindBoundary(pos);
- if next = nil then
- exit(ASize);
- FMPState := msBodypartHeader;
- end;
- msBodypartHeader:
- begin
- endline := pos + IndexChar(pos, ASize, #10);
- if endline < pos then
- exit(pos-ABuffer);
- next := endline+1;
- if (endline > pos) and ((endline-1)^ = #13) then
- dec(endline);
- endline^ := #0;
- if endline > pos then
- ParseMultipartHeader(pos, endline)
- else
- FMPState := msBodypartData;
- end;
- msBodypartData:
- begin
- { decode based on content-transfer-encoding ? }
- { CRLF before boundary, belongs to boundary, not data! }
- next := FindBoundary(ABuffer);
- end;
- else
- exit(ASize);
- end;
- dec(ASize, next-pos);
- pos := next;
- until false;
- end;
- function TFormOutput.HandleInputDiscard(ABuffer: pchar; ASize: integer): integer;
- begin
- Result := ASize;
- end;
- function TFormOutput.HandleInput(ABuffer: pchar; ASize: integer): integer;
- begin
- Result := FHandleInput(ABuffer, ASize);
- end;
- function TFormOutput.FillBuffer: TWriteBlockStatus;
- begin
- Result := wsDone;
- if Assigned(FOnFillBuffer) then
- FOnFillBuffer(Self, Result);
- end;
- procedure TFormOutput.DeleteCookie(const AName: string; const APath: string = '/';
- const ADomain: string = '');
- begin
- { cookies expire when expires is in the past, duh }
- SetCookie(AName, '', Now - 7.0, APath, ADomain);
- end;
- procedure TFormOutput.SetCookie(const AName, AValue: string; const AExpires: TDateTime;
- const APath: string = '/'; const ADomain: string = '');
- var
- headers: PStringBuffer;
- begin
- headers := @TLHTTPServerSocket(FSocket).FHeaderOut.ExtraHeaders;
- AppendString(headers^, 'Set-Cookie: ' + HTTPEncode(AName) + '=' + HTTPEncode(AValue));
- AppendString(headers^, ';path=' + APath + ';expires=' + FormatDateTime(HTTPDateFormat, AExpires));
- if Length(ADomain) > 0 then
- begin
- AppendString(headers^, ';domain=');
- AppendString(headers^, ADomain);
- end;
- AppendString(headers^, #13#10);
- end;
- { TFormHandler }
- procedure TFormHandler.SelectMultipart(AFormOutput: TFormOutput; AContentType: pchar);
- var
- boundary, endquote: pchar;
- begin
- boundary := StrScan(AContentType, '=');
- if boundary <> nil then
- begin
- Inc(boundary);
- if boundary^ = '"' then
- begin
- Inc(boundary);
- endquote := StrScan(boundary, '"');
- if endquote <> nil then
- endquote^ := #0;
- end;
- end;
- AFormOutput.FBoundary := boundary;
- AFormOutput.FHandleInput := @AFormOutput.HandleInputMultipart;
- end;
- function TFormHandler.HandleURI(ASocket: TLHTTPServerSocket): TOutputItem;
- var
- newFormOutput: TFormOutput;
- contentType: pchar;
- begin
- if not Assigned(FOnHandleURI) then
- exit(nil);
- newFormOutput := FOnHandleURI(ASocket);
- if newFormOutput = nil then
- exit(nil);
- newFormOutput.AddVariables(ASocket.FRequestInfo.QueryParams, -1, URIParamSepChar);
- newFormOutput.AddVariables(ASocket.Parameters[hpCookie], -1, CookieSepChar);
- contentType := TLHTTPServerSocket(ASocket).Parameters[hpContentType];
- if StrIComp(contentType, FormURLContentType) = 0 then
- newFormOutput.FHandleInput := @newFormOutput.HandleInputFormURL
- else if StrIComp(contentType, MultipartContentType) = 0 then
- SelectMultipart(newFormOutput, contentType)
- else
- newFormOutput.FHandleInput := @newFormOutput.HandleInputDiscard;
- Result := newFormOutput;
- end;
- end.
|