restemplate/indy/Protocols/IdCustomHTTPServer.pas

2411 lines
83 KiB
Plaintext
Raw Permalink Blame History

{
$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.42 3/14/05 11:45:50 AM RLebeau
Buf fix for DoExecute() not not filling in the TIdHTTPRequestInfo.FormParams
correctly.
Removed LImplicitPostStream variable from DoExecute(), no longer used.
TIdHTTPRequestInfo takes ownership of the PostStream anyway, so no need to
free it early. This also allows the PostStream to always be available in the
OnCommand... event handlers.
Rev 1.41 2/9/05 2:11:02 AM RLebeau
Removed compiler hint
Rev 1.40 2/9/05 1:19:26 AM RLebeau
Fixes for Compiler errors
Rev 1.39 2/8/05 6:47:46 PM RLebeau
updated OnCommandOther to have ARequestInfo and AResponseInfo parameters
Rev 1.38 12/16/04 2:15:20 AM RLebeau
Another DoExecute() update
Rev 1.37 12/15/04 9:03:50 PM RLebeau
Renamed TIdHTTPRequestInfo.DecodeCommand() to DecodeHTTPCommand() and made it
into a standalone function.
Rev 1.36 12/15/04 4:17:42 PM RLebeau
Updated DoExecute() to call LRequestInfo.DecodeCommand()
Rev 1.35 12/2/2004 4:23:48 PM JPMugaas
Adjusted for changes in Core.
Rev 1.34 10/26/2004 8:59:32 PM JPMugaas
Updated with new TStrings references for more portability.
Rev 1.33 2004.05.20 11:37:12 AM czhower
IdStreamVCL
Rev 1.32 5/6/04 3:19:00 PM RLebeau
Added extra comments
Rev 1.31 2004.04.18 12:52:06 AM czhower
Big bug fix with server disconnect and several other bug fixed that I found
along the way.
Rev 1.30 2004.04.08 1:46:32 AM czhower
Small Optimizations
Rev 1.29 7/4/2004 4:10:44 PM SGrobety
Small fix to keep it synched with the IOHandler properties
Rev 1.28 6/4/2004 5:15:02 PM SGrobety
Implemented MaximumHeaderLineCount property (default to 1024)
Rev 1.27 2004.02.03 5:45:02 PM czhower
Name changes
Rev 1.26 1/27/2004 3:58:52 PM SPerry
StringStream ->IdStringStream
Rev 1.25 2004.01.22 5:58:58 PM czhower
IdCriticalSection
Rev 1.24 1/22/2004 8:26:28 AM JPMugaas
Ansi* calls changed.
Rev 1.23 1/21/2004 1:57:30 PM JPMugaas
InitComponent
Rev 1.22 21.1.2004 <20>. 13:22:18 DBondzhev
Fix for Dccil bug
Rev 1.21 10/25/2003 06:51:44 AM JPMugaas
Updated for new API changes and tried to restore some functionality.
Rev 1.20 2003.10.24 10:43:02 AM czhower
TIdSTream to dos
Rev 1.19 10/19/2003 11:49:40 AM DSiders
Added localization comments.
Rev 1.18 10/17/2003 12:05:40 AM DSiders
Corrected spelling error in resource string.
Rev 1.17 10/15/2003 11:10:16 PM GGrieve
DotNet changes
Rev 1.16 2003.10.12 3:37:58 PM czhower
Now compiles again.
Rev 1.15 6/24/2003 11:38:50 AM BGooijen
Fixed ssl support
Rev 1.14 6/18/2003 11:44:04 PM BGooijen
Moved ServeFile and SmartServeFile to TIdHTTPResponseInfo.
Added TIdHTTPResponseInfo.HTTPServer field
Rev 1.13 05.6.2003 <20>. 11:11:12 DBondzhev
Socket exceptions should not be stopped after DoCommandGet.
Rev 1.12 4/9/2003 9:38:40 PM BGooijen
fixed av on FSessionList.PurgeStaleSessions(Terminated);
Rev 1.11 20/3/2003 19:49:24 GGrieve
Define SmartServeFile
Rev 1.10 3/13/2003 10:21:14 AM BGooijen
Changed result of function .execute
Rev 1.9 2/25/2003 10:43:36 AM BGooijen
removed unneeded assignment
Rev 1.8 2/25/2003 10:38:46 AM BGooijen
The Serversoftware wasn't send to the client, because of duplicate properties
(.Server and .ServerSoftware).
Rev 1.7 2/24/2003 08:20:50 PM JPMugaas
Now should compile with new code.
Rev 1.6 11.2.2003 13:36:14 TPrami
- Fixed URL get paremeter handling (SeeRFC 1866 section 8.2.1.)
Rev 1.5 1/17/2003 05:35:20 PM JPMugaas
Now compiles with new design.
Rev 1.4 1-1-2003 20:12:44 BGooijen
Changed to support the new TIdContext class
Rev 1.3 12-15-2002 13:08:38 BGooijen
simplified TimeStampInterval
Rev 1.2 6/12/2002 10:59:34 AM SGrobety Version: 1.1
Made to work with Indy 10
Rev 1.0 21/11/2002 12:41:04 PM SGrobety Version: Indy 10
Rev 1.0 11/14/2002 02:16:32 PM JPMugaas
}
unit IdCustomHTTPServer;
interface
{$i IdCompilerDefines.inc}
uses
Classes,
{$IFDEF HAS_UNIT_Generics_Collections}
System.Generics.Collections,
{$ENDIF}
IdAssignedNumbers,
IdContext, IdException,
IdGlobal, IdStack,
IdExceptionCore, IdGlobalProtocols, IdHeaderList, IdCustomTCPServer,
IdTCPConnection, IdThread, IdCookie, IdHTTPHeaderInfo, IdStackConsts,
IdBaseComponent, IdThreadSafe,
SysUtils;
type
// Enums
THTTPCommandType = (hcUnknown, hcHEAD, hcGET, hcPOST, hcDELETE, hcPUT, hcTRACE, hcOPTION);
const
Id_TId_HTTPServer_KeepAlive = false;
Id_TId_HTTPServer_ParseParams = True;
Id_TId_HTTPServer_SessionState = False;
Id_TId_HTTPSessionTimeOut = 0;
Id_TId_HTTPAutoStartSession = False;
Id_TId_HTTPMaximumHeaderLineCount = 1024;
GResponseNo = 200;
GFContentLength = -1;
GServerSoftware = gsIdProductName + '/' + gsIdVersion; {Do not Localize}
GContentType = 'text/html'; {Do not Localize}
GSessionIDCookie = 'IDHTTPSESSIONID'; {Do not Localize}
HTTPRequestStrings: array[0..Ord(High(THTTPCommandType))] of string = ('UNKNOWN', 'HEAD','GET','POST','DELETE','PUT','TRACE', 'OPTIONS'); {do not localize}
type
// Forwards
TIdHTTPSession = class;
TIdHTTPCustomSessionList = class;
TIdHTTPRequestInfo = class;
TIdHTTPResponseInfo = class;
TIdCustomHTTPServer = class;
//events
TIdHTTPSessionEndEvent = procedure(Sender: TIdHTTPSession) of object;
TIdHTTPSessionStartEvent = procedure(Sender: TIdHTTPSession) of object;
TIdHTTPCreateSession = procedure(ASender:TIdContext;
var VHTTPSession: TIdHTTPSession) of object;
TIdHTTPCreatePostStream = procedure(AContext: TIdContext; AHeaders: TIdHeaderList; var VPostStream: TStream) of object;
TIdHTTPDoneWithPostStream = procedure(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; var VCanFree: Boolean) of object;
TIdHTTPParseAuthenticationEvent = procedure(AContext: TIdContext; const AAuthType, AAuthData: String; var VUsername, VPassword: String; var VHandled: Boolean) of object;
TIdHTTPCommandEvent = procedure(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo) of object;
TIdHTTPCommandError = procedure(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
AException: Exception) of object;
TIdHTTPInvalidSessionEvent = procedure(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
var VContinueProcessing: Boolean; const AInvalidSessionID: String) of object;
TIdHTTPHeadersAvailableEvent = procedure(AContext: TIdContext; const AUri: string; AHeaders: TIdHeaderList; var VContinueProcessing: Boolean) of object;
TIdHTTPHeadersBlockedEvent = procedure(AContext: TIdContext; AHeaders: TIdHeaderList; var VResponseNo: Integer; var VResponseText, VContentText: String) of object;
TIdHTTPHeaderExpectationsEvent = procedure(AContext: TIdContext; const AExpectations: String; var VContinueProcessing: Boolean) of object;
TIdHTTPQuerySSLPortEvent = procedure(APort: TIdPort; var VUseSSL: Boolean) of object;
//objects
EIdHTTPServerError = class(EIdException);
EIdHTTPHeaderAlreadyWritten = class(EIdHTTPServerError);
EIdHTTPErrorParsingCommand = class(EIdHTTPServerError);
EIdHTTPUnsupportedAuthorisationScheme = class(EIdHTTPServerError);
EIdHTTPCannotSwitchSessionStateWhenActive = class(EIdHTTPServerError);
EIdHTTPCannotSwitchSessionIDCookieNameWhenActive = class(EIdHTTPServerError);
TIdHTTPRequestInfo = class(TIdRequestHeaderInfo)
protected
FAuthExists: Boolean;
FCookies: TIdCookies;
FParams: TStrings;
FPostStream: TStream;
FRawHTTPCommand: string;
FRemoteIP: string;
FSession: TIdHTTPSession;
FDocument: string;
FURI: string;
FCommand: string;
FVersion: string;
FVersionMajor: Integer;
FVersionMinor: Integer;
FAuthUsername: string;
FAuthPassword: string;
FUnparsedParams: string;
FQueryParams: string;
FFormParams: string;
FCommandType: THTTPCommandType;
//
procedure DecodeAndSetParams(const AValue: String); virtual;
public
constructor Create(AOwner: TPersistent); override;
destructor Destroy; override;
//
function IsVersionAtLeast(const AMajor, AMinor: Integer): Boolean;
property Session: TIdHTTPSession read FSession;
//
property AuthExists: Boolean read FAuthExists;
property AuthPassword: string read FAuthPassword;
property AuthUsername: string read FAuthUsername;
property Command: string read FCommand;
property CommandType: THTTPCommandType read FCommandType;
property Cookies: TIdCookies read FCookies;
property Document: string read FDocument write FDocument; // writable for isapi compatibility. Use with care
property URI: string read FURI;
property Params: TStrings read FParams;
property PostStream: TStream read FPostStream write FPostStream;
property RawHTTPCommand: string read FRawHTTPCommand;
property RemoteIP: String read FRemoteIP;
property UnparsedParams: string read FUnparsedParams write FUnparsedParams; // writable for isapi compatibility. Use with care
property FormParams: string read FFormParams write FFormParams; // writable for isapi compatibility. Use with care
property QueryParams: string read FQueryParams write FQueryParams; // writable for isapi compatibility. Use with care
property Version: string read FVersion;
property VersionMajor: Integer read FVersionMajor;
property VersionMinor: Integer read FVersionMinor;
end;
TIdHTTPResponseInfo = class(TIdResponseHeaderInfo)
protected
FAuthRealm: string;
FConnection: TIdTCPConnection;
FResponseNo: Integer;
FCookies: TIdCookies;
FContentStream: TStream;
FContentText: string;
FCloseConnection: Boolean;
FFreeContentStream: Boolean;
FHeaderHasBeenWritten: Boolean;
FResponseText: string;
FHTTPServer: TIdCustomHTTPServer;
FSession: TIdHTTPSession;
FRequestInfo: TIdHTTPRequestInfo;
//
procedure ReleaseContentStream;
procedure SetCookies(const AValue: TIdCookies);
procedure SetHeaders; override;
procedure SetResponseNo(const AValue: Integer);
procedure SetCloseConnection(const Value: Boolean);
public
function GetServer: string;
procedure SetServer(const Value: string);
public
procedure CloseSession;
constructor Create(AServer: TIdCustomHTTPServer; ARequestInfo: TIdHTTPRequestInfo; AConnection: TIdTCPConnection); reintroduce;
destructor Destroy; override;
procedure Redirect(const AURL: string);
procedure WriteHeader;
procedure WriteContent;
//
function ServeFile(AContext: TIdContext; const AFile: String): Int64; virtual;
function SmartServeFile(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; const AFile: String): Int64;
//
property AuthRealm: string read FAuthRealm write FAuthRealm;
property CloseConnection: Boolean read FCloseConnection write SetCloseConnection;
property ContentStream: TStream read FContentStream write FContentStream;
property ContentText: string read FContentText write FContentText;
property Cookies: TIdCookies read FCookies write SetCookies;
property FreeContentStream: Boolean read FFreeContentStream write FFreeContentStream;
// writable for isapi compatibility. Use with care
property HeaderHasBeenWritten: Boolean read FHeaderHasBeenWritten write FHeaderHasBeenWritten;
property ResponseNo: Integer read FResponseNo write SetResponseNo;
property ResponseText: String read FResponseText write FResponseText;
property HTTPServer: TIdCustomHTTPServer read FHTTPServer;
property ServerSoftware: string read GetServer write SetServer;
property Session: TIdHTTPSession read FSession;
end;
TIdHTTPSession = Class(TObject)
protected
FContent: TStrings;
FLastTimeStamp: TDateTime;
FLock: TIdCriticalSection;
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FOwner: TIdHTTPCustomSessionList;
FSessionID: string;
FRemoteHost: string;
//
procedure SetContent(const Value: TStrings);
function IsSessionStale: boolean; virtual;
procedure DoSessionEnd; virtual;
public
constructor Create(AOwner: TIdHTTPCustomSessionList); virtual;
constructor CreateInitialized(AOwner: TIdHTTPCustomSessionList; const SessionID,
RemoteIP: string); virtual;
destructor Destroy; override;
procedure Lock;
procedure Unlock;
//
property Content: TStrings read FContent write SetContent;
property LastTimeStamp: TDateTime read FLastTimeStamp;
property RemoteHost: string read FRemoteHost;
property SessionID: String read FSessionID;
end;
{$IFDEF HAS_GENERICS_TThreadList}
TIdHTTPSessionThreadList = TThreadList<TIdHTTPSession>;
TIdHTTPSessionList = TList<TIdHTTPSession>;
{$ELSE}
// TODO: flesh out to match TThreadList<TIdHTTPSession> and TList<TIdHTTPSession> for non-Generics compilers
TIdHTTPSessionThreadList = TThreadList;
TIdHTTPSessionList = TList;
{$ENDIF}
TIdHTTPCustomSessionList = class(TIdBaseComponent)
private
FSessionTimeout: Integer;
FOnSessionEnd: TIdHTTPSessionEndEvent;
FOnSessionStart: TIdHTTPSessionStartEvent;
protected
// remove a session from the session list. Called by the session on "Free"
procedure RemoveSession(Session: TIdHTTPSession); virtual; abstract;
public
procedure Clear; virtual; abstract;
procedure PurgeStaleSessions(PurgeAll: Boolean = false); virtual; abstract;
function CreateUniqueSession(const RemoteIP: String): TIdHTTPSession; virtual; abstract;
function CreateSession(const RemoteIP, SessionID: String): TIdHTTPSession; virtual; abstract;
function GetSession(const SessionID, RemoteIP: string): TIdHTTPSession; virtual; abstract;
procedure Add(ASession: TIdHTTPSession); virtual; Abstract;
published
property SessionTimeout: Integer read FSessionTimeout write FSessionTimeout;
property OnSessionEnd: TIdHTTPSessionEndEvent read FOnSessionEnd write FOnSessionEnd;
property OnSessionStart: TIdHTTPSessionStartEvent read FOnSessionStart write FOnSessionStart;
end;
TIdThreadSafeMimeTable = class(TIdThreadSafe)
protected
FTable: TIdMimeTable;
function GetLoadTypesFromOS: Boolean;
procedure SetLoadTypesFromOS(AValue: Boolean);
function GetOnBuildCache: TNotifyEvent;
procedure SetOnBuildCache(AValue: TNotifyEvent);
public
constructor Create(const AutoFill: Boolean = True); reintroduce;
destructor Destroy; override;
procedure BuildCache;
procedure AddMimeType(const Ext, MIMEType: string; const ARaiseOnError: Boolean = True);
function GetFileMIMEType(const AFileName: string): string;
function GetDefaultFileExt(const MIMEType: string): string;
procedure LoadFromStrings(const AStrings: TStrings; const MimeSeparator: Char = '='); {Do not Localize}
procedure SaveToStrings(const AStrings: TStrings; const MimeSeparator: Char = '='); {Do not Localize}
function Lock: TIdMimeTable; reintroduce;
procedure Unlock; reintroduce;
//
property LoadTypesFromOS: Boolean read GetLoadTypesFromOS write SetLoadTypesFromOS;
property OnBuildCache: TNotifyEvent read GetOnBuildCache write SetOnBuildCache;
end;
TIdCustomHTTPServer = class(TIdCustomTCPServer)
protected
FAutoStartSession: Boolean;
FKeepAlive: Boolean;
FParseParams: Boolean;
FServerSoftware: string;
FMIMETable: TIdThreadSafeMimeTable;
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FSessionList: TIdHTTPCustomSessionList;
FImplicitSessionList: Boolean;
FSessionState: Boolean;
FSessionTimeOut: Integer;
//
FOnCreatePostStream: TIdHTTPCreatePostStream;
FOnDoneWithPostStream: TIdHTTPDoneWithPostStream;
FOnCreateSession: TIdHTTPCreateSession;
FOnInvalidSession: TIdHTTPInvalidSessionEvent;
FOnParseAuthentication: TIdHTTPParseAuthenticationEvent;
FOnSessionEnd: TIdHTTPSessionEndEvent;
FOnSessionStart: TIdHTTPSessionStartEvent;
FOnCommandGet: TIdHTTPCommandEvent;
FOnCommandOther: TIdHTTPCommandEvent;
FOnCommandError: TIdHTTPCommandError;
FOnHeadersAvailable: TIdHTTPHeadersAvailableEvent;
FOnHeadersBlocked: TIdHTTPHeadersBlockedEvent;
FOnHeaderExpectations: TIdHTTPHeaderExpectationsEvent;
FOnQuerySSLPort: TIdHTTPQuerySSLPortEvent;
//
FSessionCleanupThread: TIdThread;
FMaximumHeaderLineCount: Integer;
FSessionIDCookieName: string;
//
procedure CreatePostStream(ASender: TIdContext; AHeaders: TIdHeaderList; var VPostStream: TStream); virtual;
procedure DoneWithPostStream(ASender: TIdContext; ARequestInfo: TIdHTTPRequestInfo); virtual;
procedure DoOnCreateSession(AContext: TIdContext; var VNewSession: TIdHTTPSession); virtual;
procedure DoInvalidSession(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
var VContinueProcessing: Boolean; const AInvalidSessionID: String); virtual;
procedure DoCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo); virtual;
procedure DoCommandOther(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo); virtual;
procedure DoCommandError(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo; AException: Exception); virtual;
procedure DoConnect(AContext: TIdContext); override;
function DoHeadersAvailable(ASender: TIdContext; const AUri: String; AHeaders: TIdHeaderList): Boolean; virtual;
procedure DoHeadersBlocked(ASender: TIdContext; AHeaders: TIdHeaderList; var VResponseNo: Integer; var VResponseText, VContentText: String); virtual;
function DoHeaderExpectations(ASender: TIdContext; const AExpectations: String): Boolean; virtual;
function DoParseAuthentication(ASender: TIdContext; const AAuthType, AAuthData: String; var VUsername, VPassword: String): Boolean;
function DoQuerySSLPort(APort: TIdPort): Boolean; virtual;
procedure DoSessionEnd(Sender: TIdHTTPSession); virtual;
procedure DoSessionStart(Sender: TIdHTTPSession); virtual;
//
function DoExecute(AContext:TIdContext): Boolean; override;
//
procedure Startup; override;
procedure Shutdown; override;
procedure SetSessionList(const AValue: TIdHTTPCustomSessionList);
procedure SetSessionState(const Value: Boolean);
procedure SetSessionIDCookieName(const AValue: string);
function IsSessionIDCookieNameStored: Boolean;
function GetSessionFromCookie(AContext:TIdContext;
AHTTPrequest: TIdHTTPRequestInfo; AHTTPResponse: TIdHTTPResponseInfo;
var VContinueProcessing: Boolean): TIdHTTPSession;
procedure InitComponent; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
{ to be published in TIdHTTPServer}
property OnCreatePostStream: TIdHTTPCreatePostStream read FOnCreatePostStream write FOnCreatePostStream;
property OnDoneWithPostStream: TIdHTTPDoneWithPostStream read FOnDoneWithPostStream write FOnDoneWithPostStream;
property OnCommandGet: TIdHTTPCommandEvent read FOnCommandGet write FOnCommandGet;
public
function CreateSession(AContext:TIdContext;
HTTPResponse: TIdHTTPResponseInfo;
HTTPRequest: TIdHTTPRequestInfo): TIdHTTPSession;
destructor Destroy; override;
function EndSession(const SessionName: String; const RemoteIP: String = ''): Boolean;
//
property MIMETable: TIdThreadSafeMimeTable read FMIMETable;
property SessionList: TIdHTTPCustomSessionList read FSessionList write SetSessionList;
published
property AutoStartSession: boolean read FAutoStartSession write FAutoStartSession default Id_TId_HTTPAutoStartSession;
property DefaultPort default IdPORT_HTTP;
property KeepAlive: Boolean read FKeepAlive write FKeepAlive default Id_TId_HTTPServer_KeepAlive;
property MaximumHeaderLineCount: Integer read FMaximumHeaderLineCount write FMaximumHeaderLineCount default Id_TId_HTTPMaximumHeaderLineCount;
property ParseParams: boolean read FParseParams write FParseParams default Id_TId_HTTPServer_ParseParams;
property ServerSoftware: string read FServerSoftware write FServerSoftware;
property SessionState: Boolean read FSessionState write SetSessionState default Id_TId_HTTPServer_SessionState;
property SessionTimeOut: Integer read FSessionTimeOut write FSessionTimeOut default Id_TId_HTTPSessionTimeOut;
property SessionIDCookieName: string read FSessionIDCookieName write SetSessionIDCookieName stored IsSessionIDCookieNameStored;
//
property OnCommandError: TIdHTTPCommandError read FOnCommandError write FOnCommandError;
property OnCommandOther: TIdHTTPCommandEvent read FOnCommandOther write FOnCommandOther;
property OnCreateSession: TIdHTTPCreateSession read FOnCreateSession write FOnCreateSession;
property OnInvalidSession: TIdHTTPInvalidSessionEvent read FOnInvalidSession write FOnInvalidSession;
property OnHeadersAvailable: TIdHTTPHeadersAvailableEvent read FOnHeadersAvailable write FOnHeadersAvailable;
property OnHeadersBlocked: TIdHTTPHeadersBlockedEvent read FOnHeadersBlocked write FOnHeadersBlocked;
property OnHeaderExpectations: TIdHTTPHeaderExpectationsEvent read FOnHeaderExpectations write FOnHeaderExpectations;
property OnParseAuthentication: TIdHTTPParseAuthenticationEvent read FOnParseAuthentication write FOnParseAuthentication;
property OnQuerySSLPort: TIdHTTPQuerySSLPortEvent read FOnQuerySSLPort write FOnQuerySSLPort;
property OnSessionStart: TIdHTTPSessionStartEvent read FOnSessionStart write FOnSessionStart;
property OnSessionEnd: TIdHTTPSessionEndEvent read FOnSessionEnd write FOnSessionEnd;
end;
TIdHTTPDefaultSessionList = Class(TIdHTTPCustomSessionList)
protected
FSessionList: TIdHTTPSessionThreadList;
procedure RemoveSession(Session: TIdHTTPSession); override;
// remove a session surgically when list already locked down (prevent deadlock)
procedure RemoveSessionFromLockedList(AIndex: Integer; ALockedSessionList: TIdHTTPSessionList);
protected
procedure InitComponent; override;
public
destructor Destroy; override;
property SessionList: TIdHTTPSessionThreadList read FSessionList;
procedure Clear; override;
procedure Add(ASession: TIdHTTPSession); override;
procedure PurgeStaleSessions(PurgeAll: Boolean = false); override;
function CreateUniqueSession(const RemoteIP: String): TIdHTTPSession; override;
function CreateSession(const RemoteIP, SessionID: String): TIdHTTPSession; override;
function GetSession(const SessionID, RemoteIP: string): TIdHTTPSession; override;
end;
TIdHTTPRangeStream = class(TIdBaseStream)
private
FSourceStream: TStream;
FOwnsSource: Boolean;
FRangeStart, FRangeEnd: Int64;
FResponseCode: Integer;
protected
function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override;
function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override;
procedure IdSetSize(ASize: Int64); override;
public
constructor Create(ASource: TStream; ARangeStart, ARangeEnd: Int64; AOwnsSource: Boolean = True);
destructor Destroy; override;
property ResponseCode: Integer read FResponseCode;
property RangeStart: Int64 read FRangeStart;
property RangeEnd: Int64 read FRangeEnd;
property SourceStream: TStream read FSourceStream;
end;
implementation
uses
{$IFDEF VCL_XE3_OR_ABOVE}
System.SyncObjs,
{$ENDIF}
{$IFDEF KYLIXCOMPAT}
Libc,
{$ENDIF}
{$IFDEF USE_VCL_POSIX}
Posix.SysSelect,
Posix.SysTime,
{$ENDIF}
{$IFDEF DOTNET}
{$IFDEF USE_INLINE}
System.IO,
System.Threading,
{$ENDIF}
{$IFDEF WINDOWS}
Windows,
{$ENDIF}
{$ENDIF}
{$IFDEF VCL_2010_OR_ABOVE}
{$IFDEF WINDOWS}
Windows,
{$ENDIF}
{$ENDIF}
IdCoderMIME, IdResourceStringsProtocols, IdURI, IdIOHandler, IdIOHandlerSocket,
IdSSL, IdResourceStringsCore, IdStream;
const
SessionCapacity = 128;
ContentTypeFormUrlencoded = 'application/x-www-form-urlencoded'; {Do not Localize}
// Calculate the number of MS between two TimeStamps
function TimeStampInterval(const AStartStamp, AEndStamp: TDateTime): integer;
begin
Result := Trunc((AEndStamp - AStartStamp) * MSecsPerDay);
end;
{ //(Bas Gooijen) was:
function TimeStampInterval(StartStamp, EndStamp: TDateTime): integer;
var
days: Integer;
hour, min, s, ms: Word;
begin
days := Trunc(EndStamp - StartStamp); // whole days
DecodeTime(EndStamp - StartStamp, hour, min, s, ms);
Result := (((days * 24 + hour) * 60 + min) * 60 + s) * 1000 + ms;
end;
}
function GetRandomString(NumChar: UInt32): string;
const
CharMap = 'qwertzuiopasdfghjklyxcvbnmQWERTZUIOPASDFGHJKLYXCVBNM1234567890'; {Do not Localize}
MaxChar: UInt32 = Length(CharMap) - 1;
var
i: integer;
{$IFDEF STRING_IS_IMMUTABLE}
LSB: TIdStringBuilder;
{$ENDIF}
begin
randomize;
{$IFDEF STRING_IS_IMMUTABLE}
LSB := TIdStringBuilder.Create(NumChar);
{$ELSE}
SetLength(Result, NumChar);
{$ENDIF}
for i := 1 to NumChar do
begin
// Add one because CharMap is 1-based
{$IFDEF STRING_IS_IMMUTABLE}
LSB.Append(CharMap[Random(MaxChar) + 1]);
{$ELSE}
Result[i] := CharMap[Random(MaxChar) + 1];
{$ENDIF}
end;
{$IFDEF STRING_IS_IMMUTABLE}
Result := LSB.ToString;
{$ENDIF}
end;
function DecodeHTTPCommand(const ACmd: string): THTTPCommandType;
var
I: Integer;
begin
Result := hcUnknown;
for I := Low(HTTPRequestStrings) to High(HTTPRequestStrings) do begin
if TextIsSame(ACmd, HTTPRequestStrings[i]) then begin
Result := THTTPCommandType(i);
Exit;
end;
end; // for
end;
type
TIdHTTPSessionCleanerThread = Class(TIdThread)
protected
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FSessionList: TIdHTTPCustomSessionList;
public
constructor Create(SessionList: TIdHTTPCustomSessionList); reintroduce;
procedure AfterRun; override;
procedure Run; override;
end; // class
function InternalReadLn(AIOHandler: TIdIOHandler): String;
begin
Result := AIOHandler.ReadLn;
if AIOHandler.ReadLnTimedout then begin
raise EIdReadTimeout.Create(RSReadTimeout);
end;
end;
{ TIdThreadSafeMimeTable }
constructor TIdThreadSafeMimeTable.Create(const AutoFill: Boolean = True);
begin
inherited Create;
FTable := TIdMimeTable.Create(AutoFill);
end;
destructor TIdThreadSafeMimeTable.Destroy;
begin
inherited Lock;
try
FreeAndNil(FTable);
finally
inherited Unlock;
end;
inherited Destroy;
end;
function TIdThreadSafeMimeTable.GetLoadTypesFromOS: Boolean;
begin
Lock;
try
Result := FTable.LoadTypesFromOS;
finally
Unlock;
end;
end;
procedure TIdThreadSafeMimeTable.SetLoadTypesFromOS(AValue: Boolean);
begin
Lock;
try
FTable.LoadTypesFromOS := AValue;
finally
Unlock;
end;
end;
function TIdThreadSafeMimeTable.GetOnBuildCache: TNotifyEvent;
begin
Lock;
try
Result := FTable.OnBuildCache;
finally
Unlock;
end;
end;
procedure TIdThreadSafeMimeTable.SetOnBuildCache(AValue: TNotifyEvent);
begin
Lock;
try
FTable.OnBuildCache := AValue;
finally
Unlock;
end;
end;
procedure TIdThreadSafeMimeTable.BuildCache;
begin
Lock;
try
FTable.BuildCache;
finally
Unlock;
end;
end;
procedure TIdThreadSafeMimeTable.AddMimeType(const Ext, MIMEType: string; const ARaiseOnError: Boolean = True);
begin
Lock;
try
FTable.AddMimeType(Ext, MIMEType, ARaiseOnError);
finally
Unlock;
end;
end;
function TIdThreadSafeMimeTable.GetFileMIMEType(const AFileName: string): string;
begin
Lock;
try
Result := FTable.GetFileMIMEType(AFileName);
finally
Unlock;
end;
end;
function TIdThreadSafeMimeTable.GetDefaultFileExt(const MIMEType: string): string;
begin
Lock;
try
Result := FTable.GetDefaultFileExt(MIMEType);
finally
Unlock;
end;
end;
procedure TIdThreadSafeMimeTable.LoadFromStrings(const AStrings: TStrings; const MimeSeparator: Char = '='); {Do not Localize}
begin
Lock;
try
FTable.LoadFromStrings(AStrings, MimeSeparator);
finally
Unlock;
end;
end;
procedure TIdThreadSafeMimeTable.SaveToStrings(const AStrings: TStrings; const MimeSeparator: Char = '='); {Do not Localize}
begin
Lock;
try
FTable.SaveToStrings(AStrings, MimeSeparator);
finally
Unlock;
end;
end;
function TIdThreadSafeMimeTable.Lock: TIdMimeTable;
begin
inherited Lock;
Result := FTable;
end;
procedure TIdThreadSafeMimeTable.Unlock;
begin
inherited Unlock;
end;
{ TIdHTTPRangeStream }
constructor TIdHTTPRangeStream.Create(ASource: TStream; ARangeStart, ARangeEnd: Int64;
AOwnsSource: Boolean = True);
var
LSize: Int64;
begin
inherited Create;
FSourceStream := ASource;
FOwnsSource := AOwnsSource;
FResponseCode := 200;
if (ARangeStart > -1) or (ARangeEnd > -1) then begin
LSize := ASource.Size;
if ARangeStart > -1 then begin
// requesting prefix range from BOF
if ARangeStart >= LSize then begin
// range unsatisfiable
FResponseCode := 416;
Exit;
end;
if ARangeEnd > -1 then begin
if ARangeEnd < ARangeStart then begin
// invalid syntax
Exit;
end;
ARangeEnd := IndyMin(ARangeEnd, LSize-1);
end else begin
ARangeEnd := LSize-1;
end;
end else begin
// requesting suffix range from EOF
if ARangeEnd = 0 then begin
// range unsatisfiable
FResponseCode := 416;
Exit;
end;
ARangeStart := IndyMax(LSize - ARangeEnd, 0);
ARangeEnd := LSize-1;
end;
FResponseCode := 206;
FRangeStart := ARangeStart;
FRangeEnd := ARangeEnd;
end;
end;
destructor TIdHTTPRangeStream.Destroy;
begin
if FOwnsSource then begin
FreeAndNil(FSourceStream);
end;
inherited Destroy;
end;
function TIdHTTPRangeStream.IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
if FResponseCode = 206 then begin
ACount := Longint(IndyMin(Int64(ACount), (FRangeEnd+1) - FSourceStream.Position));
end;
Result := TIdStreamHelper.ReadBytes(FSourceStream, VBuffer, ACount, AOffset);
end;
function TIdHTTPRangeStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64;
var
LOffset: Int64;
begin
if FResponseCode = 206 then begin
case AOrigin of
soBeginning: LOffset := FRangeStart + AOffset;
soCurrent: LOffset := FSourceStream.Position + AOffset;
soEnd: LOffset := (FRangeEnd+1) + AOffset;
else
// TODO: move this into IdResourceStringsProtocols.pas
raise EIdException.Create('Unknown Seek Origin'); {do not localize}
end;
LOffset := IndyMax(LOffset, FRangeStart);
LOffset := IndyMin(LOffset, FRangeEnd+1);
Result := TIdStreamHelper.Seek(FSourceStream, LOffset, soBeginning) - FRangeStart;
end else begin
Result := TIdStreamHelper.Seek(FSourceStream, AOffset, AOrigin);
end;
end;
function TIdHTTPRangeStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint;
begin
Result := 0;
end;
procedure TIdHTTPRangeStream.IdSetSize(ASize: Int64);
begin
end;
{ TIdCustomHTTPServer }
procedure TIdCustomHTTPServer.InitComponent;
begin
inherited InitComponent;
FSessionState := Id_TId_HTTPServer_SessionState;
DefaultPort := IdPORT_HTTP;
ParseParams := Id_TId_HTTPServer_ParseParams;
FMIMETable := TIdThreadSafeMimeTable.Create(False);
FSessionTimeOut := Id_TId_HTTPSessionTimeOut;
FAutoStartSession := Id_TId_HTTPAutoStartSession;
FKeepAlive := Id_TId_HTTPServer_KeepAlive;
FMaximumHeaderLineCount := Id_TId_HTTPMaximumHeaderLineCount;
FSessionIDCookieName := GSessionIDCookie;
end;
// under ARC, all weak references to a freed object get nil'ed automatically
// so this is mostly redundant
procedure TIdCustomHTTPServer.Notification(AComponent: TComponent; Operation: TOperation);
begin
if (Operation = opRemove) and (AComponent = FSessionList) then begin
FSessionList := nil;
FImplicitSessionList := False;
end;
inherited Notification(AComponent, Operation);
end;
function TIdCustomHTTPServer.DoParseAuthentication(ASender: TIdContext;
const AAuthType, AAuthData: String; var VUsername, VPassword: String): Boolean;
var
s: String;
LDecoder: TIdDecoderMIME;
begin
Result := False;
if Assigned(FOnParseAuthentication) then begin
FOnParseAuthentication(ASender, AAuthType, AAuthData, VUsername, VPassword, Result);
end;
if (not Result) and TextIsSame(AAuthType, 'Basic') then begin {Do not Localize}
LDecoder := TIdDecoderMIME.Create;
try
s := LDecoder.DecodeString(AAuthData);
finally
LDecoder.Free;
end;
VUsername := Fetch(s, ':'); {Do not Localize}
VPassword := s;
Result := True;
end;
end;
procedure TIdCustomHTTPServer.DoOnCreateSession(AContext: TIdContext; Var VNewSession: TIdHTTPSession);
begin
VNewSession := nil;
if Assigned(FOnCreateSession) then
begin
OnCreateSession(AContext, VNewSession);
end;
end;
function TIdCustomHTTPServer.CreateSession(AContext: TIdContext; HTTPResponse: TIdHTTPResponseInfo;
HTTPRequest: TIdHTTPRequestInfo): TIdHTTPSession;
var
LCookie: TIdCookie;
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
Result := nil;
if SessionState then begin
LSessionList := FSessionList;
if Assigned(LSessionList) then begin
DoOnCreateSession(AContext, Result);
if not Assigned(Result) then begin
Result := LSessionList.CreateUniqueSession(HTTPRequest.RemoteIP);
end else begin
LSessionList.Add(Result);
end;
LCookie := HTTPResponse.Cookies.Add;
LCookie.CookieName := SessionIDCookieName;
LCookie.Value := Result.SessionID;
LCookie.Path := '/'; {Do not Localize}
// By default the cookie will be valid until the user has closed his browser window.
// MaxAge := SessionTimeOut div 1000;
HTTPResponse.FSession := Result;
HTTPRequest.FSession := Result;
end;
end;
end;
destructor TIdCustomHTTPServer.Destroy;
var
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
Active := False; // Set Active to false in order to close all active sessions.
FreeAndNil(FMIMETable);
LSessionList := FSessionList;
if Assigned(LSessionList) and FImplicitSessionList then begin
FSessionList := nil;
FImplicitSessionList := False;
IdDisposeAndNil(LSessionList);
end;
inherited Destroy;
end;
procedure TIdCustomHTTPServer.DoCommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if Assigned(FOnCommandGet) then begin
FOnCommandGet(AContext, ARequestInfo, AResponseInfo);
end;
end;
procedure TIdCustomHTTPServer.DoCommandOther(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
if Assigned(FOnCommandOther) then begin
FOnCommandOther(AContext, ARequestInfo, AResponseInfo);
end;
end;
procedure TIdCustomHTTPServer.DoCommandError(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
AException: Exception);
begin
if Assigned(FOnCommandError) then begin
FOnCommandError(AContext, ARequestInfo, AResponseInfo, AException);
end;
end;
procedure TIdCustomHTTPServer.DoConnect(AContext: TIdContext);
begin
// RLebeau 6/17/08: let the user decide whether to enable SSL in their
// own event handler. Indy should not be making any assumptions about
// whether to implicitally force SSL on any given connection. This
// prevents a single server from handling both SSL and non-SSL connections
// together. The whole point of the PassThrough property is to allow
// per-connection SSL handling.
//
// TODO: move this new logic into TIdCustomTCPServer directly somehow
if AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase then begin
TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough :=
not DoQuerySSLPort(AContext.Connection.Socket.Binding.Port);
end;
inherited DoConnect(AContext);
end;
function TIdCustomHTTPServer.DoQuerySSLPort(APort: TIdPort): Boolean;
begin
Result := not Assigned(FOnQuerySSLPort);
if not Result then begin
FOnQuerySSLPort(APort, Result);
end;
end;
function TIdCustomHTTPServer.DoHeadersAvailable(ASender: TIdContext; const AUri: String;
AHeaders: TIdHeaderList): Boolean;
begin
Result := True;
if Assigned(OnHeadersAvailable) then begin
OnHeadersAvailable(ASender, AUri, AHeaders, Result);
end;
end;
procedure TIdCustomHTTPServer.DoHeadersBlocked(ASender: TIdContext; AHeaders: TIdHeaderList;
var VResponseNo: Integer; var VResponseText, VContentText: String);
begin
VResponseNo := 403;
VResponseText := '';
VContentText := '';
if Assigned(OnHeadersBlocked) then begin
OnHeadersBlocked(ASender, AHeaders, VResponseNo, VResponseText, VContentText);
end;
end;
function TIdCustomHTTPServer.DoHeaderExpectations(ASender: TIdContext; const AExpectations: String): Boolean;
begin
Result := TextIsSame(AExpectations, '100-continue'); {Do not Localize}
if Assigned(OnHeaderExpectations) then begin
OnHeaderExpectations(ASender, AExpectations, Result);
end;
end;
function TIdCustomHTTPServer.DoExecute(AContext:TIdContext): boolean;
var
LRequestInfo: TIdHTTPRequestInfo;
LResponseInfo: TIdHTTPResponseInfo;
procedure ReadCookiesFromRequestHeader;
var
LRawCookies: TStringList;
begin
LRawCookies := TStringList.Create;
try
LRequestInfo.RawHeaders.Extract('Cookie', LRawCookies); {Do not Localize}
LRequestInfo.Cookies.AddClientCookies(LRawCookies);
finally
FreeAndNil(LRawCookies);
end;
end;
function GetRemoteIP(ASocket: TIdIOHandlerSocket): String;
begin
Result := '';
if ASocket <> nil then begin
if ASocket.Binding <> nil then begin
Result := ASocket.Binding.PeerIP;
end;
end;
end;
function HeadersCanContinue: Boolean;
var
LResponseNo: Integer;
LResponseText, LContentText, S: String;
begin
// let the user decide if the request headers are acceptable
Result := DoHeadersAvailable(AContext, LRequestInfo.URI, LRequestInfo.RawHeaders);
if not Result then begin
DoHeadersBlocked(AContext, LRequestInfo.RawHeaders, LResponseNo, LResponseText, LContentText);
LResponseInfo.ResponseNo := LResponseNo;
if Length(LResponseText) > 0 then begin
LResponseInfo.ResponseText := LResponseText;
end;
LResponseInfo.ContentText := LContentText;
LResponseInfo.CloseConnection := True;
LResponseInfo.WriteHeader;
if Length(LContentText) > 0 then begin
LResponseInfo.WriteContent;
end;
Exit;
end;
// check for HTTP v1.1 'Host' and 'Expect' headers...
if not LRequestInfo.IsVersionAtLeast(1, 1) then begin
Exit;
end;
// MUST report a 400 (Bad Request) error if an HTTP/1.1
// request does not include a 'Host' header
S := LRequestInfo.RawHeaders.Values['Host'];
if Length(S) = 0 then begin
LResponseInfo.ResponseNo := 400;
LResponseInfo.CloseConnection := True;
LResponseInfo.WriteHeader;
Exit;
end;
// if the client has already sent some or all of the request
// body then don't bother checking for a v1.1 'Expect' header
if not AContext.Connection.IOHandler.InputBufferIsEmpty then begin
Exit;
end;
S := LRequestInfo.RawHeaders.Values['Expect'];
if Length(S) = 0 then begin
Exit;
end;
// check if the client expectations can be satisfied...
Result := DoHeaderExpectations(AContext, S);
if not Result then begin
LResponseInfo.ResponseNo := 417;
LResponseInfo.CloseConnection := True;
LResponseInfo.WriteHeader;
Exit;
end;
if Pos('100-continue', LowerCase(S)) > 0 then begin {Do not Localize}
// the client requested a '100-continue' expectation so send
// a '100 Continue' reply now before the request body can be read
AContext.Connection.IOHandler.WriteLn(LRequestInfo.Version + ' 100 ' + RSHTTPContinue + EOL); {Do not Localize}
end;
end;
function PreparePostStream: Boolean;
var
I, Size: Integer;
S: String;
LIOHandler: TIdIOHandler;
begin
Result := False;
LIOHandler := AContext.Connection.IOHandler;
// RLebeau 1/6/2009: don't create the PostStream unless there is
// actually something to read. This should make it easier for the
// request handler to know when to use the PostStream and when to
// use the (Unparsed)Params instead...
if (LRequestInfo.TransferEncoding <> '') and
(not TextIsSame(LRequestInfo.TransferEncoding, 'identity')) then {do not localize}
begin
if IndyPos('chunked', LowerCase(LRequestInfo.TransferEncoding)) = 0 then begin {do not localize}
LResponseInfo.ResponseNo := 400; // bad request
LResponseInfo.CloseConnection := True;
LResponseInfo.WriteHeader;
Exit;
end;
CreatePostStream(AContext, LRequestInfo.RawHeaders, LRequestInfo.FPostStream);
if LRequestInfo.FPostStream = nil then begin
LRequestInfo.FPostStream := TMemoryStream.Create;
end;
LRequestInfo.PostStream.Position := 0;
repeat
S := InternalReadLn(LIOHandler);
I := IndyPos(';', S); {do not localize}
if I > 0 then begin
S := Copy(S, 1, I - 1);
end;
Size := IndyStrToInt('$' + Trim(S), 0); {do not localize}
if Size = 0 then begin
Break;
end;
LIOHandler.ReadStream(LRequestInfo.PostStream, Size);
InternalReadLn(LIOHandler); // CRLF at end of chunk data
until False;
// skip trailer headers
repeat until InternalReadLn(LIOHandler) = '';
LRequestInfo.PostStream.Position := 0;
end
else if LRequestInfo.HasContentLength then
begin
CreatePostStream(AContext, LRequestInfo.RawHeaders, LRequestInfo.FPostStream);
if LRequestInfo.FPostStream = nil then begin
LRequestInfo.FPostStream := TMemoryStream.Create;
end;
LRequestInfo.PostStream.Position := 0;
if LRequestInfo.ContentLength > 0 then begin
LIOHandler.ReadStream(LRequestInfo.PostStream, LRequestInfo.ContentLength);
LRequestInfo.PostStream.Position := 0;
end;
end
// If HTTP Pipelining is used by the client, bytes may exist that belong to
// the NEXT request! We need to look at the CURRENT request and only check
// for misreported body data if a body is actually expected. GET and HEAD
// requests do not have bodies...
else if LRequestInfo.CommandType in [hcPOST, hcPUT] then
begin
if LIOHandler.InputBufferIsEmpty then begin
LIOHandler.CheckForDataOnSource(1);
end;
if not LIOHandler.InputBufferIsEmpty then begin
LResponseInfo.ResponseNo := 411; // length required
LResponseInfo.CloseConnection := True;
LResponseInfo.WriteHeader;
Exit;
end;
end;
Result := True;
end;
var
i: integer;
s, LInputLine, LRawHTTPCommand, LCmd, LContentType, LAuthType: String;
LURI: TIdURI;
LContinueProcessing, LCloseConnection: Boolean;
LConn: TIdTCPConnection;
LEncoding: IIdTextEncoding;
begin
LContinueProcessing := True;
Result := False;
LCloseConnection := not KeepAlive;
try
try
LConn := AContext.Connection;
repeat
LInputLine := InternalReadLn(LConn.IOHandler);
i := RPos(' ', LInputLine, -1); {Do not Localize}
if i = 0 then begin
raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
end;
LRequestInfo := TIdHTTPRequestInfo.Create(Self);
try
LResponseInfo := TIdHTTPResponseInfo.Create(Self, LRequestInfo, LConn);
try
// SG 05.07.99
// Set the ServerSoftware string to what it's supposed to be. {Do not Localize}
LResponseInfo.ServerSoftware := Trim(ServerSoftware);
// S.G. 6/4/2004: Set the maximum number of lines that will be catured
// S.G. 6/4/2004: to prevent a remote resource starvation DOS
LConn.IOHandler.MaxCapturedLines := MaximumHeaderLineCount;
// Retrieve the HTTP version
LRawHTTPCommand := LInputLine;
LRequestInfo.FVersion := Copy(LInputLine, i + 1, MaxInt);
s := LRequestInfo.Version;
Fetch(s, '/'); {Do not localize}
LRequestInfo.FVersionMajor := IndyStrToInt(Fetch(s, '.'), -1); {Do not Localize}
LRequestInfo.FVersionMinor := IndyStrToInt(S, -1);
SetLength(LInputLine, i - 1);
// Retrieve the HTTP header
LRequestInfo.RawHeaders.Clear;
LConn.IOHandler.Capture(LRequestInfo.RawHeaders, '', False); {Do not Localize}
// TODO: call HeadersCanContinue() here before the headers are parsed,
// in case the user needs to overwrite any values...
LRequestInfo.ProcessHeaders;
// HTTP 1.1 connections are keep-alive by default
if not FKeepAlive then begin
LResponseInfo.CloseConnection := True;
end
else if LRequestInfo.IsVersionAtLeast(1, 1) then begin
LResponseInfo.CloseConnection := TextIsSame(LRequestInfo.Connection, 'close'); {Do not Localize}
end else begin
LResponseInfo.CloseConnection := not TextIsSame(LRequestInfo.Connection, 'keep-alive'); {Do not Localize}
end;
{TODO Check for 1.0 only at this point}
LCmd := UpperCase(Fetch(LInputLine, ' ')); {Do not Localize}
// check for overrides when LCmd is 'POST'...
if TextIsSame(LCmd, 'POST') then begin
s := LRequestInfo.MethodOverride; // Google/GData
if s = '' then begin
// TODO: make RequestInfo properties for these
s := LRequestInfo.RawHeaders.Values['X-HTTP-Method']; // Microsoft {do not localize}
if s = '' then begin
s := LRequestInfo.RawHeaders.Values['X-METHOD-OVERRIDE']; // IBM {do not localize}
end;
end;
if s <> '' then begin
LCmd := UpperCase(s);
end;
end;
LRequestInfo.FRawHTTPCommand := LRawHTTPCommand;
LRequestInfo.FRemoteIP := GetRemoteIP(LConn.Socket);
LRequestInfo.FCommand := LCmd;
LRequestInfo.FCommandType := DecodeHTTPCommand(LCmd);
// GET data - may exist with POSTs also
LRequestInfo.QueryParams := LInputLine;
LInputLine := Fetch(LRequestInfo.FQueryParams, '?'); {Do not Localize}
// Host
// the next line is done in TIdHTTPRequestInfo.ProcessHeaders()...
// LRequestInfo.FHost := LRequestInfo.Headers.Values['host']; {Do not Localize}
LRequestInfo.FURI := LInputLine;
// Parse the document input line
if LInputLine = '*' then begin {Do not Localize}
LRequestInfo.FDocument := '*'; {Do not Localize}
end else begin
LURI := TIdURI.Create(LInputLine);
try
// SG 29/11/01: Per request of Doychin
// Try to fill the "host" parameter
LRequestInfo.FDocument := TIdURI.URLDecode(LURI.Path) + TIdURI.URLDecode(LURI.Document);
if (Length(LURI.Host) > 0) and (Length(LRequestInfo.FHost) = 0) then begin
LRequestInfo.FHost := LURI.Host;
end;
finally
FreeAndNil(LURI);
end;
end;
// RLebeau 12/14/2005: provide the user with the headers and let the
// user decide whether the response processing should continue...
if not HeadersCanContinue then begin
Break;
end;
// retreive the base ContentType with attributes omitted
LContentType := ExtractHeaderItem(LRequestInfo.ContentType);
// Grab Params so we can parse them
// POSTed data - may exist with GETs also. With GETs, the action
// params from the form element will be posted
// TODO: Rune this is the area that needs fixed. Ive hacked it for now
// Get data can exists with POSTs, but can POST data exist with GETs?
// If only the first, the solution is easy. If both - need more
// investigation.
if not PreparePostStream then begin
Break;
end;
if LRequestInfo.PostStream <> nil then begin
if TextIsSame(LContentType, ContentTypeFormUrlencoded) then
begin
// decoding percent-encoded octets and applying the CharSet is handled by DecodeAndSetParams() further below...
EnsureEncoding(LEncoding, enc8Bit);
LRequestInfo.FormParams := ReadStringFromStream(LRequestInfo.PostStream, -1, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
DoneWithPostStream(AContext, LRequestInfo); // don't need the PostStream anymore
end;
end;
// glue together parameters passed in the URL and those
//
// RLebeau: should we really be doing this? For a GET, it might
// makes sense to do, but for a POST the FormParams is the content
// and the QueryParams belongs to the URL only, not the content.
// We should be keeping everything separate for accuracy...
LRequestInfo.UnparsedParams := LRequestInfo.FormParams;
if Length(LRequestInfo.QueryParams) > 0 then begin
if Length(LRequestInfo.UnparsedParams) = 0 then begin
LRequestInfo.FUnparsedParams := LRequestInfo.QueryParams;
end else begin
LRequestInfo.FUnparsedParams := LRequestInfo.UnparsedParams + '&' {Do not Localize}
+ LRequestInfo.QueryParams;
end;
end;
// Parse Params
if ParseParams then begin
if TextIsSame(LContentType, ContentTypeFormUrlencoded) then begin
LRequestInfo.DecodeAndSetParams(LRequestInfo.UnparsedParams);
end else begin
// Parse only query params when content type is not 'application/x-www-form-urlencoded' {Do not Localize}
LRequestInfo.DecodeAndSetParams(LRequestInfo.QueryParams);
end;
end;
// Cookies
ReadCookiesFromRequestHeader;
// Authentication
s := LRequestInfo.RawHeaders.Values['Authorization']; {Do not Localize}
if Length(s) > 0 then begin
LAuthType := Fetch(s, ' ');
LRequestInfo.FAuthExists := DoParseAuthentication(AContext, LAuthType, s, LRequestInfo.FAuthUsername, LRequestInfo.FAuthPassword);
if not LRequestInfo.FAuthExists then begin
raise EIdHTTPUnsupportedAuthorisationScheme.Create(
RSHTTPUnsupportedAuthorisationScheme);
end;
end;
// Session management
GetSessionFromCookie(AContext, LRequestInfo, LResponseInfo, LContinueProcessing);
if LContinueProcessing then begin
try
// These essentially all "retrieve" so they are all "Get"s
if LRequestInfo.CommandType in [hcGET, hcPOST, hcHEAD] then begin
DoCommandGet(AContext, LRequestInfo, LResponseInfo);
end else begin
DoCommandOther(AContext, LRequestInfo, LResponseInfo);
end;
except
on E: EIdSocketError do begin // don't stop socket exceptions
raise;
end;
on E: Exception do begin
LResponseInfo.ResponseNo := 500;
LResponseInfo.ContentText := E.Message;
DoCommandError(AContext, LRequestInfo, LResponseInfo, E);
end;
end;
end;
// Write even though WriteContent will, may be a redirect or other
if not LResponseInfo.HeaderHasBeenWritten then begin
LResponseInfo.WriteHeader;
end;
// Always check ContentText first
if (Length(LResponseInfo.ContentText) > 0)
or Assigned(LResponseInfo.ContentStream) then begin
LResponseInfo.WriteContent;
end;
finally
LCloseConnection := LResponseInfo.CloseConnection;
FreeAndNil(LResponseInfo);
end;
finally
FreeAndNil(LRequestInfo);
end;
until LCloseConnection;
except
on E: EIdSocketError do begin
if not ((E.LastError = Id_WSAESHUTDOWN) or (E.LastError = Id_WSAECONNABORTED) or (E.LastError = Id_WSAECONNRESET)) then begin
raise;
end;
end;
on E: EIdClosedSocket do begin
AContext.Connection.Disconnect;
end;
end;
finally
AContext.Connection.Disconnect(False);
end;
end;
procedure TIdCustomHTTPServer.DoInvalidSession(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo;
var VContinueProcessing: Boolean; const AInvalidSessionID: String);
begin
if Assigned(FOnInvalidSession) then begin
FOnInvalidSession(AContext, ARequestInfo, AResponseInfo, VContinueProcessing, AInvalidSessionID)
end;
end;
function TIdCustomHTTPServer.EndSession(const SessionName: String; const RemoteIP: String = ''): Boolean;
var
LSession: TIdHTTPSession;
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
Result := False;
LSessionList := SessionList;
if Assigned(LSessionList) then begin
LSession := SessionList.GetSession(SessionName, RemoteIP); {Do not Localize}
if Assigned(LSession) then begin
LSessionList.RemoveSession(LSession);
LSession.DoSessionEnd;
// must set the owner to nil or the session will try to fire the OnSessionEnd
// event again, and also remove itself from the session list and deadlock
LSession.FOwner := nil;
FreeAndNil(LSession);
Result := True;
end;
end;
end;
procedure TIdCustomHTTPServer.DoSessionEnd(Sender: TIdHTTPSession);
begin
if Assigned(FOnSessionEnd) then begin
FOnSessionEnd(Sender);
end;
end;
procedure TIdCustomHTTPServer.DoSessionStart(Sender: TIdHTTPSession);
begin
if Assigned(FOnSessionStart) then begin
FOnSessionStart(Sender);
end;
end;
function TIdCustomHTTPServer.GetSessionFromCookie(AContext: TIdContext;
AHTTPRequest: TIdHTTPRequestInfo; AHTTPResponse: TIdHTTPResponseInfo;
var VContinueProcessing: Boolean): TIdHTTPSession;
var
LIndex: Integer;
LSessionID: String;
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
Result := nil;
VContinueProcessing := True;
if SessionState then
begin
LSessionList := FSessionList;
LIndex := AHTTPRequest.Cookies.GetCookieIndex(SessionIDCookieName);
while LIndex >= 0 do
begin
LSessionID := AHTTPRequest.Cookies[LIndex].Value;
if Assigned(LSessionList) then begin
Result := LSessionList.GetSession(LSessionID, AHTTPRequest.RemoteIP);
if Assigned(Result) then begin
Break;
end;
end;
DoInvalidSession(AContext, AHTTPRequest, AHTTPResponse, VContinueProcessing, LSessionID);
if not VContinueProcessing then begin
Break;
end;
LIndex := AHTTPRequest.Cookies.GetCookieIndex(SessionIDCookieName, LIndex+1);
end; { while }
// check if a session was returned. If not and if AutoStartSession is set to
// true, Create a new session
if (Result = nil) and VContinueProcessing and FAutoStartSession then begin
Result := CreateSession(AContext, AHTTPResponse, AHTTPrequest);
end;
end;
AHTTPRequest.FSession := Result;
AHTTPResponse.FSession := Result;
end;
procedure TIdCustomHTTPServer.Startup;
var
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
inherited Startup;
// set the session timeout and options
LSessionList := FSessionList;
if not Assigned(LSessionList) then begin
LSessionList := TIdHTTPDefaultSessionList.Create(Self);
FSessionList := LSessionList;
FImplicitSessionList := True;
end;
if FSessionTimeOut <> 0 then begin
LSessionList.FSessionTimeout := FSessionTimeOut;
end else begin
FSessionState := False;
end;
// Session events
LSessionList.OnSessionStart := DoSessionStart;
LSessionList.OnSessionEnd := DoSessionEnd;
// If session handling is enabled, create the housekeeper thread
if SessionState then begin
FSessionCleanupThread := TIdHTTPSessionCleanerThread.Create(LSessionList);
end;
end;
procedure TIdCustomHTTPServer.Shutdown;
var
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
// Boost the clear thread priority to give it a good chance to terminate
if Assigned(FSessionCleanupThread) then begin
IndySetThreadPriority(FSessionCleanupThread, tpNormal);
FSessionCleanupThread.TerminateAndWaitFor;
FreeAndNil(FSessionCleanupThread);
end;
// RLebeau: FSessionList might not be assignd yet if Shutdown() is being
// called due to an exception raised in Startup()...
LSessionList := FSessionList;
if Assigned(LSessionList) then begin
if FImplicitSessionList then begin
SetSessionList(nil);
end else begin
LSessionList.Clear;
end;
{$IFDEF USE_OBJECT_ARC}LSessionList := nil;{$ENDIF}
end;
inherited Shutdown;
end;
procedure TIdCustomHTTPServer.SetSessionList(const AValue: TIdHTTPCustomSessionList);
var
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
LSessionList := FSessionList;
if LSessionList <> AValue then
begin
// RLebeau - is this really needed? What should happen if this
// gets called by Notification() if the sessionList is freed while
// the server is still Active?
if Active then begin
raise EIdException.Create(RSHTTPCannotSwitchSessionListWhenActive);
end;
// under ARC, all weak references to a freed object get nil'ed automatically
// If implicit one already exists free it
// Free the default SessionList
if FImplicitSessionList then begin
// Under D8 notification gets called after .Free of FreeAndNil, but before
// its set to nil with a side effect of IDisposable. To counteract this we
// set it to nil first.
// -Kudzu
FSessionList := nil;
FImplicitSessionList := False;
IdDisposeAndNil(LSessionList);
end;
{$IFNDEF USE_OBJECT_ARC}
// Ensure we will no longer be notified when the component is freed
if LSessionList <> nil then begin
LSessionList.RemoveFreeNotification(Self);
end;
{$ENDIF}
FSessionList := AValue;
{$IFNDEF USE_OBJECT_ARC}
// Ensure we will be notified when the component is freed, even is it's on
// another form
if AValue <> nil then begin
AValue.FreeNotification(Self);
end;
{$ENDIF}
end;
end;
procedure TIdCustomHTTPServer.SetSessionState(const Value: Boolean);
begin
// ToDo: Add thread multiwrite protection here
if (not (IsDesignTime or IsLoading)) and Active then begin
raise EIdHTTPCannotSwitchSessionStateWhenActive.Create(RSHTTPCannotSwitchSessionStateWhenActive);
end;
FSessionState := Value;
end;
procedure TIdCustomHTTPServer.SetSessionIDCookieName(const AValue: string);
var
LCookieName: string;
begin
// ToDo: Add thread multiwrite protection here
if (not (IsDesignTime or IsLoading)) and Active then begin
raise EIdHTTPCannotSwitchSessionIDCookieNameWhenActive.Create(RSHTTPCannotSwitchSessionIDCookieNameWhenActive);
end;
LCookieName := Trim(AValue);
if LCookieName = '' then begin
// TODO: move this into IdResourceStringsProtocols.pas
raise EIdException.Create('Invalid cookie name'); {do not localize}
end;
FSessionIDCookieName := AValue;
end;
function TIdCustomHTTPServer.IsSessionIDCookieNameStored: Boolean;
begin
Result := not TextIsSame(SessionIDCookieName, GSessionIDCookie);
end;
procedure TIdCustomHTTPServer.CreatePostStream(ASender: TIdContext;
AHeaders: TIdHeaderList; var VPostStream: TStream);
begin
if Assigned(OnCreatePostStream) then begin
OnCreatePostStream(ASender, AHeaders, VPostStream);
end;
end;
procedure TIdCustomHTTPServer.DoneWithPostStream(ASender: TIdContext;
ARequestInfo: TIdHTTPRequestInfo);
var
LCanFree: Boolean;
begin
LCanFree := True;
if Assigned(FOnDoneWithPostStream) then begin
FOnDoneWithPostStream(ASender, ARequestInfo, LCanFree);
end;
if LCanFree then begin
FreeAndNil(ARequestInfo.FPostStream);
end;
end;
{ TIdHTTPSession }
constructor TIdHTTPSession.Create(AOwner: TIdHTTPCustomSessionList);
begin
inherited Create;
FLock := TIdCriticalSection.Create;
FContent := TStringList.Create;
FOwner := AOwner;
if Assigned(AOwner) then
begin
if Assigned(AOwner.OnSessionStart) then begin
AOwner.OnSessionStart(Self);
end;
end;
end;
constructor TIdHTTPSession.CreateInitialized(AOwner: TIdHTTPCustomSessionList; const SessionID, RemoteIP: string);
begin
inherited Create;
FSessionID := SessionID;
FRemoteHost := RemoteIP;
FLastTimeStamp := Now;
FLock := TIdCriticalSection.Create;
FContent := TStringList.Create;
FOwner := AOwner;
if Assigned(AOwner) then
begin
if Assigned(AOwner.OnSessionStart) then begin
AOwner.OnSessionStart(Self);
end;
end;
end;
destructor TIdHTTPSession.Destroy;
begin
// code added here should also be reflected in
// the TIdHTTPDefaultSessionList.RemoveSessionFromLockedList method
// Why? It calls this function and this code gets executed?
DoSessionEnd;
FreeAndNil(FContent);
FreeAndNil(FLock);
if Assigned(FOwner) then begin
FOwner.RemoveSession(Self);
end;
inherited Destroy;
end;
procedure TIdHTTPSession.DoSessionEnd;
begin
if Assigned(FOwner) and Assigned(FOwner.FOnSessionEnd) then begin
FOwner.FOnSessionEnd(Self);
end;
end;
function TIdHTTPSession.IsSessionStale: boolean;
var
// under ARC, convert a weak reference to a strong reference before working with it
LOwner: TIdHTTPCustomSessionList;
begin
LOwner := FOwner;
if Assigned(LOwner) then begin
Result := TimeStampInterval(FLastTimeStamp, Now) > Integer(LOwner.SessionTimeout);
end else begin
Result := True;
end;
end;
procedure TIdHTTPSession.Lock;
begin
// ToDo: Add session locking code here
FLock.Enter;
end;
procedure TIdHTTPSession.SetContent(const Value: TStrings);
begin
FContent.Assign(Value);
end;
procedure TIdHTTPSession.Unlock;
begin
// ToDo: Add session unlocking code here
FLock.Leave;
end;
{ TIdHTTPRequestInfo }
constructor TIdHTTPRequestInfo.Create(AOwner: TPersistent);
begin
inherited Create(AOwner);
FCommandType := hcUnknown;
FCookies := TIdCookies.Create(Self);
FParams := TStringList.Create;
ContentLength := -1;
end;
procedure TIdHTTPRequestInfo.DecodeAndSetParams(const AValue: String);
var
i, j : Integer;
s: string;
LEncoding: IIdTextEncoding;
begin
// Convert special characters
// ampersand '&' separates values {Do not Localize}
Params.BeginUpdate;
try
Params.Clear;
// TODO: provide an event or property that lets the user specify
// which charset to use for decoding query string parameters. We
// should not be using the 'Content-Type' charset for that. For
// 'application/x-www-form-urlencoded' forms, we should be, though...
LEncoding := CharsetToEncoding(CharSet);
i := 1;
while i <= Length(AValue) do
begin
j := i;
while (j <= Length(AValue)) and (AValue[j] <> '&') do {do not localize}
begin
Inc(j);
end;
s := Copy(AValue, i, j-i);
// See RFC 1866 section 8.2.1. TP
s := ReplaceAll(s, '+', ' '); {do not localize}
Params.Add(TIdURI.URLDecode(s, LEncoding));
i := j + 1;
end;
finally
Params.EndUpdate;
end;
end;
destructor TIdHTTPRequestInfo.Destroy;
begin
FreeAndNil(FCookies);
FreeAndNil(FParams);
FreeAndNil(FPostStream);
inherited Destroy;
end;
function TIdHTTPRequestInfo.IsVersionAtLeast(const AMajor, AMinor: Integer): Boolean;
begin
Result := (FVersionMajor > AMajor) or
((FVersionMajor = AMajor) and (FVersionMinor >= AMinor));
end;
{ TIdHTTPResponseInfo }
procedure TIdHTTPResponseInfo.CloseSession;
var
i: Integer;
LCookie: TIdCookie;
begin
i := Cookies.GetCookieIndex(HTTPServer.SessionIDCookieName);
while i > -1 do begin
Cookies.Delete(i);
i := Cookies.GetCookieIndex(HTTPServer.SessionIDCookieName, i);
end;
LCookie := Cookies.Add;
LCookie.CookieName := HTTPServer.SessionIDCookieName;
LCookie.Expires := Date-7;
FreeAndNil(FSession);
end;
constructor TIdHTTPResponseInfo.Create(AServer: TIdCustomHTTPServer;
ARequestInfo: TIdHTTPRequestInfo; AConnection: TIdTCPConnection);
begin
inherited Create(AServer);
FRequestInfo := ARequestInfo;
FConnection := AConnection;
FHttpServer := AServer;
FFreeContentStream := True;
ResponseNo := GResponseNo;
ContentType := ''; //GContentType;
ContentLength := GFContentLength;
{Some clients may not support folded lines}
RawHeaders.FoldLines := False;
FCookies := TIdCookies.Create(Self);
{TODO Specify version - add a class method dummy that calls version}
ServerSoftware := GServerSoftware;
end;
destructor TIdHTTPResponseInfo.Destroy;
begin
FreeAndNil(FCookies);
ReleaseContentStream;
inherited Destroy;
end;
procedure TIdHTTPResponseInfo.Redirect(const AURL: string);
begin
ResponseNo := 302;
Location := AURL;
end;
procedure TIdHTTPResponseInfo.ReleaseContentStream;
begin
if FreeContentStream then begin
FreeAndNil(FContentStream);
end else begin
FContentStream := nil;
end;
end;
procedure TIdHTTPResponseInfo.SetCloseConnection(const Value: Boolean);
begin
Connection := iif(Value, 'close', 'keep-alive'); {Do not Localize}
// TODO: include a 'Keep-Alive' header to specify a timeout value
FCloseConnection := Value;
end;
procedure TIdHTTPResponseInfo.SetCookies(const AValue: TIdCookies);
begin
FCookies.Assign(AValue);
end;
procedure TIdHTTPResponseInfo.SetHeaders;
begin
inherited SetHeaders;
if Server <> '' then begin
FRawHeaders.Values['Server'] := Server; {Do not Localize}
end;
if Location <> '' then begin
FRawHeaders.Values['Location'] := Location; {Do not Localize}
end;
if FLastModified > 0 then begin
FRawHeaders.Values['Last-Modified'] := LocalDateTimeToHttpStr(FLastModified); {do not localize}
end;
if AuthRealm <> '' then begin
FRawHeaders.Values['WWW-Authenticate'] := 'Basic realm="' + AuthRealm + '"'; {Do not Localize}
end;
end;
procedure TIdHTTPResponseInfo.SetResponseNo(const AValue: Integer);
begin
FResponseNo := AValue;
case FResponseNo of
100: ResponseText := RSHTTPContinue;
// 2XX: Success
200: ResponseText := RSHTTPOK;
201: ResponseText := RSHTTPCreated;
202: ResponseText := RSHTTPAccepted;
203: ResponseText := RSHTTPNonAuthoritativeInformation;
204: ResponseText := RSHTTPNoContent;
205: ResponseText := RSHTTPResetContent;
206: ResponseText := RSHTTPPartialContent;
// 3XX: Redirections
301: ResponseText := RSHTTPMovedPermanently;
302: ResponseText := RSHTTPMovedTemporarily;
303: ResponseText := RSHTTPSeeOther;
304: ResponseText := RSHTTPNotModified;
305: ResponseText := RSHTTPUseProxy;
// 4XX Client Errors
400: ResponseText := RSHTTPBadRequest;
401: ResponseText := RSHTTPUnauthorized;
403: ResponseText := RSHTTPForbidden;
404: begin
ResponseText := RSHTTPNotFound;
// Close connection
CloseConnection := True;
end;
405: ResponseText := RSHTTPMethodNotAllowed;
406: ResponseText := RSHTTPNotAcceptable;
407: ResponseText := RSHTTPProxyAuthenticationRequired;
408: ResponseText := RSHTTPRequestTimeout;
409: ResponseText := RSHTTPConflict;
410: ResponseText := RSHTTPGone;
411: ResponseText := RSHTTPLengthRequired;
412: ResponseText := RSHTTPPreconditionFailed;
413: ResponseText := RSHTTPRequestEntityTooLong;
414: ResponseText := RSHTTPRequestURITooLong;
415: ResponseText := RSHTTPUnsupportedMediaType;
417: ResponseText := RSHTTPExpectationFailed;
428: ResponseText := RSHTTPPreconditionRequired;
429: ResponseText := RSHTTPTooManyRequests;
431: ResponseText := RSHTTPRequestHeaderFieldsTooLarge;
// 5XX Server errors
500: ResponseText := RSHTTPInternalServerError;
501: ResponseText := RSHTTPNotImplemented;
502: ResponseText := RSHTTPBadGateway;
503: ResponseText := RSHTTPServiceUnavailable;
504: ResponseText := RSHTTPGatewayTimeout;
505: ResponseText := RSHTTPHTTPVersionNotSupported;
511: ResponseText := RSHTTPNetworkAuthenticationRequired;
else
ResponseText := RSHTTPUnknownResponseCode;
end;
{if ResponseNo >= 400 then
// Force COnnection closing when there is error during the request processing
CloseConnection := true;
end;}
end;
function TIdHTTPResponseInfo.ServeFile(AContext: TIdContext; const AFile: String): Int64;
var
EnableTransferFile: Boolean;
begin
if Length(ContentType) = 0 then begin
ContentType := HTTPServer.MIMETable.GetFileMIMEType(AFile);
end;
ContentLength := FileSizeByName(AFile);
if Length(ContentDisposition) = 0 then begin
// TODO: use EncodeHeader() here...
ContentDisposition := IndyFormat('attachment; filename="%s";', [ExtractFileName(AFile)]);
end;
WriteHeader;
EnableTransferFile := not (AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase);
Result := AContext.Connection.IOHandler.WriteFile(AFile, EnableTransferFile);
end;
function TIdHTTPResponseInfo.SmartServeFile(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; const AFile: String): Int64;
var
LFileDate : TDateTime;
LReqDate : TDateTime;
begin
LFileDate := IndyFileAge(AFile);
if (LFileDate = 0.0) and (not FileExists(AFile)) then
begin
ResponseNo := 404;
Result := 0;
end else
begin
LReqDate := GMTToLocalDateTime(ARequestInfo.RawHeaders.Values['If-Modified-Since']); {do not localize}
// if the file date in the If-Modified-Since header is within 2 seconds of the
// actual file, then we will send a 304. We don't use the ETag - offers nothing
// over the file date for static files on windows. Linux: consider using iNode
// RLebeau 2/21/2011: TODO - make use of ETag. It is supposed to be updated
// whenever the file contents change, regardless of the file's timestamps.
if (LReqDate <> 0) and (abs(LReqDate - LFileDate) < 2 * (1 / (24 * 60 * 60))) then
begin
ResponseNo := 304;
Result := 0;
end else
begin
Date := Now;
LastModified := LFileDate;
Result := ServeFile(AContext, AFile);
end;
end;
end;
procedure TIdHTTPResponseInfo.WriteContent;
begin
if not HeaderHasBeenWritten then begin
WriteHeader;
end;
// RLebeau 11/23/2014: Per RFC 2616 Section 4.3:
//
// For response messages, whether or not a message-body is included with
// a message is dependent on both the request method and the response
// status code (section 6.1.1). All responses to the HEAD request method
// MUST NOT include a message-body, even though the presence of entity-
// header fields might lead one to believe they do. All 1xx
// (informational), 204 (no content), and 304 (not modified) responses
// MUST NOT include a message-body. All other responses do include a
// message-body, although it MAY be of zero length.
if not (
(FRequestInfo.CommandType = hcHEAD) or
((ResponseNo div 100) = 1) or
(ResponseNo = 204) or
(ResponseNo = 304)
) then
begin
// Always check ContentText first
if ContentText <> '' then begin
FConnection.IOHandler.Write(ContentText, CharsetToEncoding(CharSet));
end
else if Assigned(ContentStream) then begin
ContentStream.Position := 0;
FConnection.IOHandler.Write(ContentStream);
end
else begin
FConnection.IOHandler.WriteLn('<HTML><BODY><B>' + IntToStr(ResponseNo) + ' ' + ResponseText {Do not Localize}
+ '</B></BODY></HTML>', CharsetToEncoding(CharSet)); {Do not Localize}
end;
end;
// Clear All - This signifies that WriteConent has been called.
ContentText := ''; {Do not Localize}
ReleaseContentStream;
end;
procedure TIdHTTPResponseInfo.WriteHeader;
var
i: Integer;
LBufferingStarted: Boolean;
begin
if HeaderHasBeenWritten then begin
raise EIdHTTPHeaderAlreadyWritten.Create(RSHTTPHeaderAlreadyWritten);
end;
FHeaderHasBeenWritten := True;
if AuthRealm <> '' then
begin
ResponseNo := 401;
if (Length(ContentText) = 0) and (not Assigned(ContentStream)) then
begin
ContentType := 'text/html; charset=utf-8'; {Do not Localize}
ContentText := '<HTML><BODY><B>' + IntToStr(ResponseNo) + ' ' + ResponseText + '</B></BODY></HTML>'; {Do not Localize}
ContentLength := -1; // calculated below
end;
end;
// RLebeau 5/15/2012: for backwards compatibility. We really should
// make the user set this every time instead...
if ContentType = '' then begin
if (ContentText <> '') or (Assigned(ContentStream)) then begin
ContentType := 'text/html; charset=ISO-8859-1'; {Do not Localize}
end;
end;
// RLebeau: according to RFC 2616 Section 4.4:
//
// If a Content-Length header field (section 14.13) is present, its
// decimal value in OCTETs represents both the entity-length and the
// transfer-length. The Content-Length header field MUST NOT be sent
// if these two lengths are different (i.e., if a Transfer-Encoding
// header field is present). If a message is received with both a
// Transfer-Encoding header field and a Content-Length header field,
// the latter MUST be ignored.
// ...
// Messages MUST NOT include both a Content-Length header field and a
// non-identity transfer-coding. If the message does include a non-
// identity transfer-coding, the Content-Length MUST be ignored.
if (ContentLength = -1) and
((TransferEncoding = '') or TextIsSame(TransferEncoding, 'identity')) then {do not localize}
begin
// Always check ContentText first
if ContentText <> '' then begin
ContentLength := CharsetToEncoding(CharSet).GetByteCount(ContentText);
end
else if Assigned(ContentStream) then begin
ContentLength := ContentStream.Size;
end else begin
ContentLength := 0;
end;
end;
if Date <= 0 then begin
Date := Now;
end;
SetHeaders;
LBufferingStarted := not FConnection.IOHandler.WriteBufferingActive;
if LBufferingStarted then begin
FConnection.IOHandler.WriteBufferOpen;
end;
try
// Write HTTP status response
FConnection.IOHandler.WriteLn('HTTP/1.1 ' + IntToStr(ResponseNo) + ' ' + ResponseText); {Do not Localize}
// Write headers
FConnection.IOHandler.Write(RawHeaders);
// Write cookies
for i := 0 to Cookies.Count - 1 do begin
FConnection.IOHandler.WriteLn('Set-Cookie: ' + Cookies[i].ServerCookie); {Do not Localize}
end;
// HTTP headers end with a double CR+LF
FConnection.IOHandler.WriteLn;
if LBufferingStarted then begin
FConnection.IOHandler.WriteBufferClose;
end;
except
if LBufferingStarted then begin
FConnection.IOHandler.WriteBufferCancel;
end;
raise;
end;
end;
function TIdHTTPResponseInfo.GetServer: string;
begin
Result := Server;
end;
procedure TIdHTTPResponseInfo.SetServer(const Value: string);
begin
Server := Value;
end;
{ TIdHTTPDefaultSessionList }
procedure TIdHTTPDefaultSessionList.Add(ASession: TIdHTTPSession);
begin
SessionList.Add(ASession);
end;
procedure TIdHTTPDefaultSessionList.Clear;
var
LSessionList: TIdHTTPSessionList;
LSession: TIdHTTPSession;
i: Integer;
begin
LSessionList := SessionList.LockList;
try
for i := LSessionList.Count - 1 DownTo 0 do
begin
LSession := {$IFDEF HAS_GENERICS_TList}LSessionList[i]{$ELSE}TIdHTTPSession(LSessionList[i]){$ENDIF};
if LSession <> nil then
begin
LSession.DoSessionEnd;
// must set the owner to nil or the session will try to fire the
// OnSessionEnd event again, and also remove itself from the session
// list and deadlock
LSession.FOwner := nil;
FreeAndNil(LSession);
end;
end;
LSessionList.Clear;
LSessionList.Capacity := SessionCapacity;
finally
SessionList.UnlockList;
end;
end;
function TIdHTTPDefaultSessionList.CreateSession(const RemoteIP, SessionID: String): TIdHTTPSession;
begin
Result := TIdHTTPSession.CreateInitialized(Self, SessionID, RemoteIP);
SessionList.Add(Result);
end;
function TIdHTTPDefaultSessionList.CreateUniqueSession(
const RemoteIP: String): TIdHTTPSession;
var
SessionID: String;
begin
SessionID := GetRandomString(15);
while GetSession(SessionID, RemoteIP) <> nil do
begin
SessionID := GetRandomString(15);
end; // while
Result := CreateSession(RemoteIP, SessionID);
end;
destructor TIdHTTPDefaultSessionList.Destroy;
begin
Clear;
FreeAndNil(FSessionList);
inherited destroy;
end;
function TIdHTTPDefaultSessionList.GetSession(const SessionID, RemoteIP: string): TIdHTTPSession;
var
LSessionList: TIdHTTPSessionList;
LSession: TIdHTTPSession;
i: Integer;
begin
Result := nil;
LSessionList := SessionList.LockList;
try
// get current time stamp
for i := 0 to LSessionList.Count - 1 do
begin
LSession := TIdHTTPSession(LSessionList[i]);
// the stale sessions check has been removed... the cleanup thread should suffice plenty
if Assigned(LSession) and TextIsSame(LSession.FSessionID, SessionID) and ((Length(RemoteIP) = 0) or TextIsSame(LSession.RemoteHost, RemoteIP)) then
begin
// Session found
LSession.FLastTimeStamp := Now;
Result := LSession;
Break;
end;
end;
finally
SessionList.UnlockList;
end;
end;
procedure TIdHTTPDefaultSessionList.InitComponent;
var
LList: TIdHTTPSessionList;
begin
inherited InitComponent;
FSessionList := TIdHTTPSessionThreadList.Create;
LList := FSessionList.LockList;
try
LList.Capacity := SessionCapacity;
finally
FSessionList.UnlockList;
end;
end;
procedure TIdHTTPDefaultSessionList.PurgeStaleSessions(PurgeAll: Boolean = false);
var
LSessionList: TIdHTTPSessionList;
LSession: TIdHTTPSession;
i: Integer;
begin
// S.G. 24/11/00: Added a way to force a session purge (Used when thread is terminated)
// Get necessary data
Assert(SessionList<>nil);
LSessionList := SessionList.LockList;
try
// Loop though the sessions.
for i := LSessionList.Count - 1 downto 0 do
begin
// Identify the stale sessions
LSession := {$IFDEF HAS_GENERICS_TList}LSessionList[i]{$ELSE}TIdHTTPSession(LSessionList[i]){$ENDIF};
if Assigned(LSession) and (PurgeAll or LSession.IsSessionStale) then
begin
RemoveSessionFromLockedList(i, LSessionList);
end;
end;
finally
SessionList.UnlockList;
end;
end;
procedure TIdHTTPDefaultSessionList.RemoveSession(Session: TIdHTTPSession);
var
LSessionList: TIdHTTPSessionList;
Index: integer;
begin
LSessionList := SessionList.LockList;
try
Index := LSessionList.IndexOf(Session);
if index > -1 then
begin
LSessionList.Delete(index);
end;
finally
SessionList.UnlockList;
end;
end;
procedure TIdHTTPDefaultSessionList.RemoveSessionFromLockedList(AIndex: Integer;
ALockedSessionList: TIdHTTPSessionList);
var
LSession: TIdHTTPSession;
begin
LSession := {$IFDEF HAS_GENERICS_TList}ALockedSessionList[AIndex]{$ELSE}TIdHTTPSession(ALockedSessionList[AIndex]){$ENDIF};
LSession.DoSessionEnd;
// must set the owner to nil or the session will try to fire the OnSessionEnd
// event again, and also remove itself from the session list and deadlock
LSession.FOwner := nil;
FreeAndNil(LSession);
ALockedSessionList.Delete(AIndex);
end;
{ TIdHTTPSessionClearThread }
procedure TIdHTTPSessionCleanerThread.AfterRun;
var
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
LSessionList := FSessionList;
if Assigned(LSessionList) then begin
LSessionList.PurgeStaleSessions(True);
end;
inherited AfterRun;
end;
constructor TIdHTTPSessionCleanerThread.Create(SessionList: TIdHTTPCustomSessionList);
begin
inherited Create(false);
// thread priority used to be set to tpIdle but this is not supported
// under DotNet. How low do you want to go?
IndySetThreadPriority(Self, tpLowest);
FSessionList := SessionList;
end;
procedure TIdHTTPSessionCleanerThread.Run;
var
// under ARC, convert a weak reference to a strong reference before working with it
LSessionList: TIdHTTPCustomSessionList;
begin
IndySleep(1000);
LSessionList := FSessionList;
if Assigned(LSessionList) then begin
LSessionList.PurgeStaleSessions(Terminated);
end;
end;
end.