restemplate/indy/Protocols/IdHTTP.pas

3060 lines
105 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.65 3/5/2005 3:33:52 PM JPMugaas
Fix for some compiler warnings having to do with TStream.Read being platform
specific. This was fixed by changing the Compressor API to use TIdStreamVCL
instead of TStream. I also made appropriate adjustments to other units for
this.
Rev 1.64 2/13/2005 3:09:20 PM DSiders
Modified TIdCustomHTTP.PrepareRequest to free the local URI instance if an
exception occurs in the method. (try...finally)
Rev 1.63 2/11/05 11:29:34 AM RLebeau
Removed compiler warning
Rev 1.62 2/9/05 2:12:08 AM RLebeau
Fixes for Compiler errors
Rev 1.61 2/8/05 6:43:42 PM RLebeau
Added OnHeaderAvailable event
Rev 1.60 1/11/05 1:25:08 AM RLebeau
More changes to SetHostAndPort()
Rev 1.59 1/6/05 2:28:52 PM RLebeau
Fix for SetHostAndPort() not using its local variables properly
Rev 1.58 06/01/2005 22:23:04 CCostelloe
Bug fix (typo, gizp instead of gzip)
Rev 1.57 05/12/2004 23:10:58 CCostelloe
Recoded fix to suit Delphi < 7
Rev 1.56 30/11/2004 23:46:12 CCostelloe
Bug fix for SSL connections giving a "Connection closed gracefully" exception
and requested page not getting returned (IOHandler.Response is empty)
Rev 1.55 25/11/2004 21:28:06 CCostelloe
Bug fix for POSTing fields that have the same name
Rev 1.54 10/26/2004 10:13:24 PM JPMugaas
Updated refs.
Rev 1.53 7/16/04 1:19:20 AM RLebeau
Fix for compiler error
Rev 1.52 7/15/04 8:19:30 PM RLebeau
Updated TIdHTTPProtocol.ProcessResponse() to treat 302 redirects like 303.
Updated TIdHTTPProtocol.BuildAndSendRequest() to use a try...except block
Rev 1.51 6/17/2004 8:30:04 AM DSiders
TIdCustomHTTP modified:
- Fixed error in AuthRetries property reading wrong member var.
- Added AuthProxyRetries and MaxAuthRetries properties to public interface.
TIdHTTP modified to publish AuthRetries, AuthProxyRetries, and MaxAuthRetries.
TIdHTTPProtocol.ProcessResponse modified to use public properties
AuthRetries, AuthProxyRetries, and MaxAutrhRetries.
Rev 1.50 2004.05.20 11:36:46 AM czhower
IdStreamVCL
Rev 1.49 4/28/04 1:45:26 PM RLebeau
Updated TIdCustomHTTP.SetRequestParams() to strip off the trailing CRLF
before encoding rather than afterwards
Rev 1.48 2004.04.07 11:18:08 PM czhower
Bug and naming fix.
Rev 1.47 7/4/2004 6:00:02 PM SGrobety
Reformatted to match project guidelines
Rev 1.46 7/4/2004 4:58:24 PM SGrobety
Reformatted to match project guidelines
Rev 1.45 6/4/2004 5:16:40 PM SGrobety
Added AMaxHeaderCount: integer parameter to TIdHTTPProtocol.RetrieveHeaders
and MaxHeaderLines property to TIdCustomHTTP (default to 255)
Rev 1.44 2004.03.06 10:39:52 PM czhower
Removed duplicate code
Rev 1.43 2004.03.06 8:56:30 PM czhower
-Change to disconnect
-Addition of DisconnectNotifyPeer
-WriteHeader now write bufers
Rev 1.42 3/3/2004 5:58:00 AM JPMugaas
Some IFDEF excluses were removed because the functionality is now in DotNET.
Rev 1.41 2004.02.23 9:33:12 PM czhower
Now can optionally ignore response codes for exceptions.
Rev 1.40 2/15/2004 6:34:02 AM JPMugaas
Fix for where I broke the HTTP client with a parameter change in the GZip
decompress method.
Rev 1.39 2004.02.03 5:43:44 PM czhower
Name changes
Rev 1.38 2004.02.03 2:12:10 PM czhower
$I path change
Rev 1.37 2004.01.27 11:41:18 PM czhower
Removed const arguments
Rev 1.35 24/01/2004 19:22:34 CCostelloe
Cleaned up warnings
Rev 1.34 2004.01.22 5:29:02 PM czhower
TextIsSame
Rev 1.33 2004.01.21 1:04:50 PM czhower
InitComponenet
Rev 1.32 1/2/2004 11:41:48 AM BGooijen
Enabled IPv6 support
Rev 1.31 22/11/2003 12:04:28 AM GGrieve
Add support for HTTP status code 303
Rev 1.30 10/25/2003 06:51:58 AM JPMugaas
Updated for new API changes and tried to restore some functionality.
Rev 1.29 2003.10.24 10:43:08 AM czhower
TIdSTream to dos
Rev 1.28 24/10/2003 10:58:40 AM SGrobety
Made authentication work even if no OnAnthenticate envent handler present
Rev 1.27 10/18/2003 1:53:10 PM BGooijen
Added include
Rev 1.26 10/17/2003 12:08:48 AM DSiders
Added localization comments.
Rev 1.25 2003.10.14 1:27:52 PM czhower
DotNet
Rev 1.24 10/7/2003 11:33:54 PM GGrieve
Get works under DotNet
Rev 1.23 10/7/2003 10:07:04 PM GGrieve
Get HTTP compiling for DotNet
Rev 1.22 10/4/2003 9:15:58 PM GGrieve
fix to compile
Rev 1.21 9/26/2003 01:41:48 PM JPMugaas
Fix for problem wihere "identity" was being added more than once to the
accepted encoding contents.
Rev 1.20 9/14/2003 07:54:20 PM JPMugaas
Published the Compressor property.
Rev 1.19 7/30/2003 05:34:22 AM JPMugaas
Fix for bug where decompression was not done if the Content Length was
specified. I found that at http://www.news.com.
Added Identity to the content encoding to be consistant with Opera. Identity
is the default Accept-Encoding (RFC 2616).
Rev 1.18 7/13/2003 10:57:28 PM BGooijen
Fixed GZip and Deflate decoding
Rev 1.17 7/13/2003 11:29:06 AM JPMugaas
Made sure some GZIP decompression stub code is in IdHTTP.
Rev 1.15 10.7.2003 <20>. 21:03:02 DBondzhev
Fixed NTML proxy authorization
Rev 1.14 6/19/2003 02:36:56 PM JPMugaas
Removed a connected check and it seems to work better that way.
Rev 1.13 6/5/2003 04:53:54 AM JPMugaas
Reworkings and minor changes for new Reply exception framework.
Rev 1.12 4/30/2003 01:47:24 PM JPMugaas
Added TODO concerning a ConnectTimeout.
Rev 1.11 4/2/2003 3:18:30 PM BGooijen
fixed av when retrieving an url when no iohandler was assigned
Rev 1.10 3/26/2003 5:13:40 PM BGooijen
TIdSSLIOHandlerSocketBase.URIToCheck is now set
Rev 1.9 3/13/2003 11:05:26 AM JPMugaas
Now should work with 3rd party vendor SSL IOHandlers.
Rev 1.8 3/11/2003 10:14:52 PM BGooijen
Undid the stripping of the CR
Rev 1.7 2/27/2003 2:04:26 PM BGooijen
If any call to iohandler.readln returns a CR at the end, it is removed now.
Rev 1.6 2/26/2003 11:50:08 AM BGooijen
things were messed up in TIdHTTPProtocol.RetrieveHeaders, because the call to
readln doesn't strip the CR at the end (terminator=LF), therefore the end of
the header was not found.
Rev 1.5 2/26/2003 11:42:46 AM BGooijen
changed ReadLn (IOerror 6) to IOHandler.ReadLn
Rev 1.4 2/4/2003 6:30:44 PM BGooijen
Re-enabled SSL-support
Rev 1.3 1/17/2003 04:14:42 PM JPMugaas
Fixed warnings.
Rev 1.2 12/7/2002 05:32:16 PM JPMugaas
Now compiles with destination removed.
Rev 1.1 12/6/2002 05:29:52 PM JPMugaas
Now decend from TIdTCPClientCustom instead of TIdTCPClient.
Rev 1.0 11/13/2002 07:54:12 AM JPMugaas
2001-Nov Nick Panteleeff
- Authentication and POST parameter extentsions
2001-Sept Doychin Bondzhev
- New internal design and new Authentication procedures.
- Bug fixes and new features in few other supporting components
2001-Jul-7 Doychin Bondzhev
- new property AllowCookie
- There is no more ExtraHeders property in Request/Response. Raw headers is used for that purpose.
2001-Jul-1 Doychin Bondzhev
- SSL support is up again - Thanks to Gregor
2001-Jun-17 Doychin Bondzhev
- New unit IdHTTPHeaderInfo.pas that contains the
TIdHeaderInfo(TIdEntytiHeaderInfo, TIdRequestHeaderInfo and TIdResponseHeaderInfo)
- Still in development and not verry well tested
By default when there is no authorization object associated with HTTP compoenet and there is user name and password
HTTP component creates and instance of TIdBasicAuthentication class. This behaivor is for both web server and proxy server
authorizations
2001-Apr-17 Doychin Bondzhev
- Added OnProxyAuthorization event. This event is called on 407 response from the HTTP Proxy.
- Added 2 new properties in TIdHeaderInfo
property AuthenticationScheme: TIdAuthenticationScheme - this property contains information for authentication scheme
requested by the web server
property ProxyAuthenticationScheme: TIdAuthenticationScheme - this property contains information for authentication scheme
requested by the proxy server
- Now the component authomaticly reconginizes the requested authorization scheme and it supports Basic like before and has been
extend to support Digest authorization
2001-Mar-31 Doychin Bondzhev
- If there is no CookieManager it does not support cookies.
2001-Feb-18 Doychin Bondzhev
- Added OnAuthorization event. This event is called on 401 response from the HTTP server.
This can be used to ask the user program to supply user name and password in order to acces
the requested resource
2001-Feb-02 Doychin Bondzhev
- Added Cookie support and relative paths on redirect
2000-Jul-25 Hadi Hariri
- Overloaded POst and moved clearing to disconect.
2000-June-22 Hadi Hariri
- Added Proxy support.
2000-June-10 Hadi Hariri
- Added Chunk-Encoding support and HTTP version number. Some additional
improvements.
2000-May-23 J. Peter Mugaas
-added redirect capability and supporting properties. Redirect is optional
and is set with HandleRedirects. Redirection is limited to RedirectMaximum
to prevent stack overflow due to recursion and to prevent redirects between
two places which would cause this to go on to infinity.
2000-May-22 J. Peter Mugaas
-adjusted code for servers which returned LF instead of EOL
-Headers are now retreived before an exception is raised. This
also facilitates server redirection where the server tells the client to
get a document from another location.
2000-May-01 Hadi Hariri
-Converted to Mercury
2000-May-01 Hadi Hariri
-Added PostFromStream and some clean up
2000-Apr-10 Hadi Hariri
-Re-done quite a few things and fixed GET bugs and finished POST method.
2000-Jan-13 MTL
-Moved to the New Palette Scheme
2000-Jan-08 MTL
-Cleaned up a few compiler hints during 7.038 build
1999-Dec-10 Hadi Hariri
-Started.
}
unit IdHTTP;
{
Implementation of the HTTP protcol as specified in RFC 2616, 2109, 2965.
(See NOTE below for details of what is exactly implemented)
Author: Hadi Hariri (hadi@urusoft.com)
Copyright: (c) Chad Z. Hower and The Winshoes Working Group.
Initials: Hadi Hariri - HH
}
{
TODO: Figure out what to do with ConnectTimeout.
Ideally, that should be in the core and is not the same as a read Timeout.
}
interface
{$I IdCompilerDefines.inc}
uses
Classes,
IdException, IdExceptionCore, IdAssignedNumbers, IdHeaderList, IdHTTPHeaderInfo, IdReplyRFC,
IdSSL, IdZLibCompressorBase,
IdTCPClient, IdURI, IdCookie, IdCookieManager, IdAuthentication, IdAuthenticationManager,
IdMultipartFormData, IdGlobal, IdBaseComponent, IdUriUtils;
type
// TO DOCUMENTATION TEAM
// ------------------------
// For internal use. No need of documentation
// hmConnect - Used to connect trought CERN proxy to SSL enabled sites.
TIdHTTPMethod = string;
const
Id_HTTPMethodHead = 'HEAD';
Id_HTTPMethodGet = 'GET';
Id_HTTPMethodPost = 'POST';
Id_HTTPMethodOptions = 'OPTIONS';
Id_HTTPMethodTrace = 'TRACE';
Id_HTTPMethodPut = 'PUT';
Id_HTTPMethodDelete = 'DELETE';
Id_HTTPMethodConnect = 'CONNECT';
Id_HTTPMethodPatch = 'PATCH';
//(hmHead, hmGet, hmPost, hmOptions, hmTrace, hmPut, hmDelete, hmConnect, hmPatch);
type
TIdHTTPWhatsNext = (wnGoToURL, wnJustExit, wnDontKnow, wnReadAndGo, wnAuthRequest);
TIdHTTPConnectionType = (ctNormal, ctSSL, ctProxy, ctSSLProxy);
// Protocol options
TIdHTTPOption = (hoInProcessAuth, hoKeepOrigProtocol, hoForceEncodeParams,
hoNonSSLProxyUseConnectVerb, hoNoParseMetaHTTPEquiv, hoWaitForUnexpectedData,
hoTreat302Like303, hoNoProtocolErrorException, hoNoReadMultipartMIME);
TIdHTTPOptions = set of TIdHTTPOption;
// Must be documented
TIdHTTPProtocolVersion = (pv1_0, pv1_1);
TIdHTTPOnRedirectEvent = procedure(Sender: TObject; var dest: string; var NumRedirect: Integer; var Handled: boolean; var VMethod: TIdHTTPMethod) of object;
TIdHTTPOnHeadersAvailable = procedure(Sender: TObject; AHeaders: TIdHeaderList; var VContinue: Boolean) of object;
TIdOnSelectAuthorization = procedure(Sender: TObject; var AuthenticationClass: TIdAuthenticationClass; AuthInfo: TIdHeaderList) of object;
TIdOnAuthorization = procedure(Sender: TObject; Authentication: TIdAuthentication; var Handled: Boolean) of object;
// TIdProxyOnAuthorization = procedure(Sender: TObject; Authentication: TIdAuthentication; var Handled: boolean) of object;
const
Id_TIdHTTP_ProtocolVersion = pv1_1;
Id_TIdHTTP_RedirectMax = 15;
Id_TIdHTTP_MaxHeaderLines = 255;
Id_TIdHTTP_HandleRedirects = False;
Id_TIdHTTP_MaxAuthRetries = 3;
type
TIdCustomHTTP = class;
// TO DOCUMENTATION TEAM
// ------------------------
// The following classes are used internally and no need of documentation
// Only TIdHTTP must be documented
//
TIdHTTPResponse = class(TIdResponseHeaderInfo)
protected
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FHTTP: TIdCustomHTTP;
FResponseCode: Integer;
FResponseText: string;
FKeepAlive: Boolean;
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FContentStream: TStream;
FResponseVersion: TIdHTTPProtocolVersion;
FMetaHTTPEquiv : TIdMetaHTTPEquiv;
//
function GetKeepAlive: Boolean;
function GetResponseCode: Integer;
procedure SetResponseText(const AValue: String);
procedure ProcessMetaHTTPEquiv;
public
constructor Create(AHTTP: TIdCustomHTTP); reintroduce; virtual;
destructor Destroy; override;
procedure Clear; override;
property KeepAlive: Boolean read GetKeepAlive write FKeepAlive;
property MetaHTTPEquiv: TIdMetaHTTPEquiv read FMetaHTTPEquiv;
property ResponseText: string read FResponseText write SetResponseText;
property ResponseCode: Integer read GetResponseCode write FResponseCode;
property ResponseVersion: TIdHTTPProtocolVersion read FResponseVersion write FResponseVersion;
property ContentStream: TStream read FContentStream write FContentStream;
end;
TIdHTTPRequest = class(TIdRequestHeaderInfo)
protected
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FHTTP: TIdCustomHTTP;
FURL: string;
FMethod: TIdHTTPMethod;
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FSourceStream: TStream;
FUseProxy: TIdHTTPConnectionType;
FIPVersion: TIdIPVersion;
FDestination: string;
public
constructor Create(AHTTP: TIdCustomHTTP); reintroduce; virtual;
property URL: string read FURL write FURL;
property Method: TIdHTTPMethod read FMethod write FMethod;
property Source: TStream read FSourceStream write FSourceStream;
property UseProxy: TIdHTTPConnectionType read FUseProxy;
property IPVersion: TIdIPversion read FIPVersion write FIPVersion;
property Destination: string read FDestination write FDestination;
end;
TIdHTTPProtocol = class(TObject)
protected
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FHTTP: TIdCustomHTTP;
FRequest: TIdHTTPRequest;
FResponse: TIdHTTPResponse;
public
constructor Create(AConnection: TIdCustomHTTP);
destructor Destroy; override;
function ProcessResponse(AIgnoreReplies: array of Int16): TIdHTTPWhatsNext;
procedure BuildAndSendRequest(AURI: TIdURI);
procedure RetrieveHeaders(AMaxHeaderCount: integer);
//
property Request: TIdHTTPRequest read FRequest;
property Response: TIdHTTPResponse read FResponse;
end;
TIdCustomHTTP = class(TIdTCPClientCustom)
protected
{Retries counter for WWW authorization}
FAuthRetries: Integer;
{Retries counter for proxy authorization}
FAuthProxyRetries: Integer;
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FCookieManager: TIdCookieManager;
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FCompressor : TIdZLibCompressorBase;
FImplicitCookieManager: Boolean;
{Max retries for authorization}
FMaxAuthRetries: Integer;
FMaxHeaderLines: integer;
FAllowCookies: Boolean;
{$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FAuthenticationManager: TIdAuthenticationManager;
FProtocolVersion: TIdHTTPProtocolVersion;
{this is an internal counter for redirects}
FRedirectCount: Integer;
FRedirectMax: Integer;
FHandleRedirects: Boolean;
FOptions: TIdHTTPOptions;
FURI: TIdURI;
FHTTPProto: TIdHTTPProtocol;
FProxyParameters: TIdProxyConnectionInfo;
//
FOnHeadersAvailable: TIdHTTPOnHeadersAvailable;
FOnRedirect: TIdHTTPOnRedirectEvent;
FOnSelectAuthorization: TIdOnSelectAuthorization;
FOnSelectProxyAuthorization: TIdOnSelectAuthorization;
FOnAuthorization: TIdOnAuthorization;
FOnProxyAuthorization: TIdOnAuthorization;
//
{
procedure SetHost(const Value: string); override;
procedure SetPort(const Value: integer); override;
}
procedure DoRequest(const AMethod: TIdHTTPMethod; AURL: string;
ASource, AResponseContent: TStream; AIgnoreReplies: array of Int16); virtual;
function CreateProtocol: TIdHTTPProtocol; virtual;
procedure InitComponent; override;
function InternalReadLn: String;
procedure SetAuthenticationManager(Value: TIdAuthenticationManager);
procedure SetCookieManager(ACookieManager: TIdCookieManager);
procedure SetAllowCookies(AValue: Boolean);
function GetResponseCode: Integer;
function GetResponseText: string;
function DoOnAuthorization(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse): Boolean; virtual;
function DoOnProxyAuthorization(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse): Boolean; virtual;
function DoOnRedirect(var Location: string; var VMethod: TIdHTTPMethod; RedirectCount: integer): boolean; virtual;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure ProcessCookies(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse);
function SetHostAndPort: TIdHTTPConnectionType;
procedure SetCookies(AURL: TIdURI; ARequest: TIdHTTPRequest);
procedure ReadResult(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse);
procedure PrepareRequest(ARequest: TIdHTTPRequest);
procedure ConnectToHost(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse);
function GetResponse: TIdHTTPResponse;
function GetRequest: TIdHTTPRequest;
function GetMetaHTTPEquiv: TIdMetaHTTPEquiv;
procedure SetRequest(Value: TIdHTTPRequest);
procedure SetProxyParams(AValue: TIdProxyConnectionInfo);
function SetRequestParams(ASource: TStrings; AByteEncoding: IIdTextEncoding
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding{$ENDIF}
): string;
procedure CheckAndConnect(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse);
procedure DoOnDisconnected; override;
//misc internal stuff
function ResponseCharset: String;
public
destructor Destroy; override;
procedure Delete(AURL: string; AResponseContent: TStream); overload;
function Delete(AURL: string
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
procedure Options(AURL: string; AResponseContent: TStream); overload;
function Options(AURL: string
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
procedure Get(AURL: string; AResponseContent: TStream); overload;
procedure Get(AURL: string; AResponseContent: TStream; AIgnoreReplies: array of Int16); overload;
function Get(AURL: string
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
function Get(AURL: string; AIgnoreReplies: array of Int16
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
procedure Trace(AURL: string; AResponseContent: TStream); overload;
function Trace(AURL: string
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
procedure Head(AURL: string);
function Post(AURL: string; const ASourceFile: String
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
function Post(AURL: string; ASource: TStrings; AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil; ADestEncoding: IIdTextEncoding = nil{$ENDIF}): string; overload;
function Post(AURL: string; ASource: TStream
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
function Post(AURL: string; ASource: TIdMultiPartFormDataStream
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
procedure Post(AURL: string; const ASourceFile: String; AResponseContent: TStream); overload;
procedure Post(AURL: string; ASource: TStrings; AResponseContent: TStream; AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF}); overload;
procedure Post(AURL: string; ASource, AResponseContent: TStream); overload;
procedure Post(AURL: string; ASource: TIdMultiPartFormDataStream; AResponseContent: TStream); overload;
function Put(AURL: string; ASource: TStream
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
procedure Put(AURL: string; ASource, AResponseContent: TStream); overload;
procedure Patch(AURL: string; ASource, AResponseContent: TStream); overload;
function Patch(AURL: string; ASource: TStream
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string; overload;
{This is an object that can compress and decompress HTTP Deflate encoding}
property Compressor : TIdZLibCompressorBase read FCompressor write FCompressor;
{This is the response code number such as 404 for File not Found}
property ResponseCode: Integer read GetResponseCode;
{This is the text of the message such as "404 File Not Found here Sorry"}
property ResponseText: string read GetResponseText;
property Response: TIdHTTPResponse read GetResponse;
property MetaHTTPEquiv: TIdMetaHTTPEquiv read GetMetaHTTPEquiv;
{ This is the last processed URL }
property URL: TIdURI read FURI;
// number of retry attempts for Authentication
property AuthRetries: Integer read FAuthRetries;
property AuthProxyRetries: Integer read FAuthProxyRetries;
// maximum number of Authentication retries permitted
property MaxAuthRetries: Integer read FMaxAuthRetries write FMaxAuthRetries default Id_TIdHTTP_MaxAuthRetries;
property AllowCookies: Boolean read FAllowCookies write SetAllowCookies;
{Do we handle redirect requests or simply raise an exception and let the
developer deal with it}
property HandleRedirects: Boolean read FHandleRedirects write FHandleRedirects default Id_TIdHTTP_HandleRedirects;
property ProtocolVersion: TIdHTTPProtocolVersion read FProtocolVersion write FProtocolVersion default Id_TIdHTTP_ProtocolVersion;
//how many redirects were made in the last request
property RedirectCount: Integer read FRedirectCount;
{This is the maximum number of redirects we wish to handle, we limit this
to prevent stack overflow due to recursion. Recursion is safe ONLY if
prevented for continuing to infinity}
property RedirectMaximum: Integer read FRedirectMax write FRedirectMax default Id_TIdHTTP_RedirectMax;
// S.G. 6/4/2004: This is to prevent the server from responding with too many header lines
property MaxHeaderLines: integer read FMaxHeaderLines write FMaxHeaderLines default Id_TIdHTTP_MaxHeaderLines;
property ProxyParams: TIdProxyConnectionInfo read FProxyParameters write SetProxyParams;
property Request: TIdHTTPRequest read GetRequest write SetRequest;
property HTTPOptions: TIdHTTPOptions read FOptions write FOptions;
//
property OnHeadersAvailable: TIdHTTPOnHeadersAvailable read FOnHeadersAvailable write FOnHeadersAvailable;
// Fired when a rediretion is requested.
property OnRedirect: TIdHTTPOnRedirectEvent read FOnRedirect write FOnRedirect;
property OnSelectAuthorization: TIdOnSelectAuthorization read FOnSelectAuthorization write FOnSelectAuthorization;
property OnSelectProxyAuthorization: TIdOnSelectAuthorization read FOnSelectProxyAuthorization write FOnSelectProxyAuthorization;
property OnAuthorization: TIdOnAuthorization read FOnAuthorization write FOnAuthorization;
property OnProxyAuthorization: TIdOnAuthorization read FOnProxyAuthorization write FOnProxyAuthorization;
// Cookie stuff
property CookieManager: TIdCookieManager read FCookieManager write SetCookieManager;
//
property AuthenticationManager: TIdAuthenticationManager read FAuthenticationManager write SetAuthenticationManager;
end;
TIdHTTP = class(TIdCustomHTTP)
published
// number of Authentication retries permitted
property MaxAuthRetries;
property AllowCookies;
{ Do we handle redirect requests or simply raise an exception and let the
developer deal with it }
property HandleRedirects;
property ProtocolVersion;
{ This is the maximum number of redirects we wish to handle, we limit this
to prevent stack overflow due to recursion. Recursion is safe ONLY if
prevented for continuing to infinity }
property RedirectMaximum;
property ProxyParams;
property Request;
property HTTPOptions;
//
property OnHeadersAvailable;
// Fired when a rediretion is requested.
property OnRedirect;
property OnSelectAuthorization;
property OnSelectProxyAuthorization;
property OnAuthorization;
property OnProxyAuthorization;
// property Host;
// property Port default IdPORT_HTTP;
// Cookie stuff
property CookieManager;
// property AuthenticationManager: TIdAuthenticationManager read FAuthenticationManager write SetAuthenticationManager;
// ZLib compression library object for use with deflate and gzip encoding
property Compressor;
end;
EIdUnknownProtocol = class(EIdException);
EIdHTTPProtocolException = class( EIdReplyRFCError )
protected
FErrorMessage: string;
public
constructor CreateError(const anErrCode: Integer; const asReplyMessage: string;
const asErrorMessage: string); reintroduce; virtual;
property ErrorMessage: string read FErrorMessage;
end;
implementation
uses
SysUtils,
IdAllAuthentications, IdComponent, IdCoderMIME, IdTCPConnection,
IdResourceStringsCore, IdResourceStringsProtocols, IdGlobalProtocols,
IdIOHandler, IdIOHandlerSocket;
const
ProtocolVersionString: array[TIdHTTPProtocolVersion] of string = ('1.0', '1.1'); {do not localize}
{ EIdHTTPProtocolException }
constructor EIdHTTPProtocolException.CreateError(const anErrCode: Integer;
const asReplyMessage: string; const asErrorMessage: string);
begin
inherited CreateError(anErrCode, asReplyMessage);
FErrorMessage := asErrorMessage;
end;
{ TIdHTTP }
function IsContentTypeHtml(AInfo: TIdEntityHeaderInfo) : Boolean;
begin
Result := IsHeaderMediaTypes(AInfo.ContentType, ['text/html', 'text/html-sandboxed','application/xhtml+xml']); {do not localize}
end;
function IsContentTypeAppXml(AInfo: TIdEntityHeaderInfo) : Boolean;
begin
Result := IsHeaderMediaTypes(AInfo.ContentType,
['application/xml', 'application/xml-external-parsed-entity', 'application/xml-dtd'] {do not localize}
);
if not Result then
begin
Result := not IsHeaderMediaType(AInfo.ContentType, 'text'); {do not localize}
if Result then begin
Result := TextEndsWith(ExtractHeaderMediaSubType(AInfo.ContentType), '+xml') {do not localize}
end;
end;
end;
destructor TIdCustomHTTP.Destroy;
begin
FreeAndNil(FHTTPProto);
FreeAndNil(FURI);
FreeAndNil(FProxyParameters);
SetCookieManager(nil);
inherited Destroy;
end;
procedure TIdCustomHTTP.Delete(AURL: string; AResponseContent: TStream);
begin
DoRequest(Id_HTTPMethodDelete, AURL, nil, AResponseContent, []);
end;
function TIdCustomHTTP.Delete(AURL: string
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LStream: TMemoryStream;
begin
LStream := TMemoryStream.Create;
try
DoRequest(Id_HTTPMethodDelete, AURL, nil, LStream, []);
LStream.Position := 0;
Result := ReadStringAsCharset(LStream, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
// TODO: if the data is XML, add/update the declared encoding to 'UTF-16LE'...
finally
FreeAndNil(LStream);
end;
end;
procedure TIdCustomHTTP.Options(AURL: string; AResponseContent: TStream);
begin
DoRequest(Id_HTTPMethodOptions, AURL, nil, AResponseContent, []);
end;
function TIdCustomHTTP.Options(AURL: string
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LStream: TMemoryStream;
begin
LStream := TMemoryStream.Create;
try
DoRequest(Id_HTTPMethodOptions, AURL, nil, LStream, []);
LStream.Position := 0;
Result := ReadStringAsCharset(LStream, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
// TODO: if the data is XML, add/update the declared encoding to 'UTF-16LE'...
finally
FreeAndNil(LStream);
end;
end;
procedure TIdCustomHTTP.Get(AURL: string; AResponseContent: TStream);
begin
Get(AURL, AResponseContent, []);
end;
procedure TIdCustomHTTP.Trace(AURL: string; AResponseContent: TStream);
begin
DoRequest(Id_HTTPMethodTrace, AURL, nil, AResponseContent, []);
end;
procedure TIdCustomHTTP.Head(AURL: string);
begin
DoRequest(Id_HTTPMethodHead, AURL, nil, nil, []);
end;
procedure TIdCustomHTTP.Post(AURL: string; ASource, AResponseContent: TStream);
var
OldProtocol: TIdHTTPProtocolVersion;
begin
// PLEASE READ CAREFULLY
// Currently when issuing a POST, IdHTTP will automatically set the protocol
// to version 1.0 independently of the value it had initially. This is because
// there are some servers that don't respect the RFC to the full extent. In
// particular, they don't respect sending/not sending the Expect: 100-Continue
// header. Until we find an optimum solution that does NOT break the RFC, we
// will restrict POSTS to version 1.0.
OldProtocol := FProtocolVersion;
try
// If hoKeepOrigProtocol is SET, is possible to assume that the developer
// is sure in operations of the server
if not (hoKeepOrigProtocol in FOptions) then begin
if Connected then begin
Disconnect;
end;
FProtocolVersion := pv1_0;
end;
DoRequest(Id_HTTPMethodPost, AURL, ASource, AResponseContent, []);
finally
FProtocolVersion := OldProtocol;
end;
end;
// RLebeau 12/21/2010: this is based on W3's HTML standards:
//
// HTML 4.01
// http://www.w3.org/TR/html401/
//
// HTML 5
// http://www.w3.org/TR/html5/
function WWWFormUrlEncode(const ASrc: string; AByteEncoding: IIdTextEncoding
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding{$ENDIF}
): string;
const
// HTML 4.01 Section 17.13.4 ("Form content types") says:
//
// application/x-www-form-urlencoded
//
// Control names and values are escaped. Space characters are replaced by `+',
// and then reserved characters are escaped as described in [RFC1738], section
// 2.2: Non-alphanumeric characters are replaced by `%HH', a percent sign and
// two hexadecimal digits representing the ASCII code of the character. Line
// breaks are represented as "CR LF" pairs (i.e., `%0D%0A').
//
// On the other hand, HTML 5 Section 4.10.16.4 ("URL-encoded form data") says:
//
// If the character isn't in the range U+0020, U+002A, U+002D, U+002E,
// U+0030 .. U+0039, U+0041 .. U+005A, U+005F, U+0061 .. U+007A then replace
// the character with a string formed as follows: Start with the empty string,
// and then, taking each byte of the character when expressed in the selected
// character encoding in turn, append to the string a U+0025 PERCENT SIGN
// character (%) followed by two characters in the ranges U+0030 DIGIT ZERO (0)
// to U+0039 DIGIT NINE (9) and U+0041 LATIN CAPITAL LETTER A to
// U+005A LATIN CAPITAL LETTER Z representing the hexadecimal value of the
// byte zero-padded if necessary).
//
// If the character is a U+0020 SPACE character, replace it with a single
// U+002B PLUS SIGN character (+).
//
// So, lets err on the side of caution and use the HTML 5.x definition, as it
// encodes some of the characters that HTML 4.01 allows unencoded...
//
SafeChars: TIdUnicodeString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-._'; {do not localize}
var
I, J, CharLen, ByteLen: Integer;
Buf: TIdBytes;
{$IFDEF STRING_IS_ANSI}
LChars: TIdWideChars;
{$ENDIF}
LChar: WideChar;
Encoded: Boolean;
begin
Result := ''; {Do not Localize}
// keep the compiler happy
Buf := nil;
{$IFDEF STRING_IS_ANSI}
LChars := nil;
{$ENDIF}
if ASrc = '' then begin
Exit;
end;
EnsureEncoding(AByteEncoding, encUTF8);
{$IFDEF STRING_IS_ANSI}
EnsureEncoding(ASrcEncoding, encOSDefault);
LChars := ASrcEncoding.GetChars(RawToBytes(ASrc[1], Length(ASrc)));
{$ENDIF}
// 2 Chars to handle UTF-16 surrogates
SetLength(Buf, AByteEncoding.GetMaxByteCount(2));
I := 0;
while I < Length({$IFDEF STRING_IS_UNICODE}ASrc{$ELSE}LChars{$ENDIF}) do
begin
LChar := {$IFDEF STRING_IS_UNICODE}ASrc[I+1]{$ELSE}LChars[I]{$ENDIF};
// RLebeau 1/7/09: using Ord() for #128-#255 because in D2009 and later, the
// compiler may change characters >= #128 from their Ansi codepage value to
// their true Unicode codepoint value, depending on the codepage used for
// the source code. For instance, #128 may become #$20AC...
if Ord(LChar) = 32 then {do not localize}
begin
Result := Result + '+'; {do not localize}
Inc(I);
end
else if WideCharIsInSet(SafeChars, LChar) then
begin
Result := Result + Char(LChar);
Inc(I);
end else
begin
// HTML 5 Section 4.10.16.4 says:
//
// For each character ... that cannot be expressed using the selected character
// encoding, replace the character by a string consisting of a U+0026 AMPERSAND
// character (&), a U+0023 NUMBER SIGN character (#), one or more characters in
// the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9) representing the
// Unicode code point of the character in base ten, and finally a U+003B
// SEMICOLON character (;).
//
CharLen := CalcUTF16CharLength(
{$IFDEF STRING_IS_UNICODE}ASrc, I+1{$ELSE}LChars, I{$ENDIF}
); // calculate length including surrogates
ByteLen := AByteEncoding.GetBytes(
{$IFDEF STRING_IS_UNICODE}ASrc, I+1{$ELSE}LChars, I{$ENDIF},
CharLen, Buf, 0); // explicit Unicode->Ansi conversion
Encoded := (ByteLen > 0);
if Encoded and (LChar <> '?') then begin {do not localize}
for J := 0 to ByteLen-1 do begin
if Buf[J] = Ord('?') then begin {do not localize}
Encoded := False;
Break;
end;
end;
end;
if Encoded then begin
for J := 0 to ByteLen-1 do begin
Result := Result + '%' + IntToHex(Ord(Buf[J]), 2); {do not localize}
end;
end else begin
J := GetUTF16Codepoint(
{$IFDEF STRING_IS_UNICODE}ASrc, I+1{$ELSE}LChars, I{$ENDIF});
Result := Result + '&#' + IntToStr(J) + ';'; {do not localize}
end;
Inc(I, CharLen);
end;
end;
end;
function TIdCustomHTTP.SetRequestParams(ASource: TStrings; AByteEncoding: IIdTextEncoding
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding{$ENDIF}
): string;
var
i: Integer;
LPos: integer;
LStr: string;
LTemp: TStringList;
function EncodeLineBreaks(AStrings: TStrings): String;
begin
if AStrings.Count > 1 then begin
// break trailing CR&LF
Result := ReplaceAll(Trim(AStrings.Text), sLineBreak, '&'); {do not localize}
end else begin
Result := Trim(AStrings.Text);
end;
end;
begin
if Assigned(ASource) then begin
if hoForceEncodeParams in FOptions then begin
// make a copy of ASource so the caller's TStrings object is not modified
LTemp := TStringList.Create;
try
LTemp.Assign(ASource);
for i := 0 to LTemp.Count - 1 do begin
LStr := LTemp[i];
// TODO: use LTemp.NameValueSeparator on platforms that support it
LPos := IndyPos('=', LStr); {do not localize}
if LPos > 0 then begin
LTemp[i] := WWWFormUrlEncode(LTemp.Names[i], AByteEncoding{$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF})
+ '=' {do not localize}
+ WWWFormUrlEncode(IndyValueFromIndex(LTemp, i), AByteEncoding{$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF});
end else begin
LTemp[i] := WWWFormUrlEncode(LStr, AByteEncoding{$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF});
end;
end;
Result := EncodeLineBreaks(LTemp);
finally
LTemp.Free;
end;
end else begin
Result := EncodeLineBreaks(ASource);
end;
end else begin
Result := '';
end;
end;
function TIdCustomHTTP.Post(AURL: string; const ASourceFile: String
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LSource: TIdReadFileExclusiveStream;
begin
LSource := TIdReadFileExclusiveStream.Create(ASourceFile);
try
Result := Post(AURL, LSource{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
finally
FreeAndNil(LSource);
end;
end;
procedure TIdCustomHTTP.Post(AURL: string; const ASourceFile: String; AResponseContent: TStream);
var
LSource: TStream;
begin
LSource := TIdReadFileExclusiveStream.Create(ASourceFile);
try
Post(AURL, LSource, AResponseContent);
finally
FreeAndNil(LSource);
end;
end;
procedure TIdCustomHTTP.Post(AURL: string; ASource: TStrings; AResponseContent: TStream;
AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil{$ENDIF}
);
var
LParams: TMemoryStream;
begin
// Usual posting request have default ContentType is application/x-www-form-urlencoded
if (Request.ContentType = '') or IsContentTypeHtml(Request) then begin
Request.ContentType := 'application/x-www-form-urlencoded'; {do not localize}
end;
if ASource <> nil then
begin
LParams := TMemoryStream.Create;
try
WriteStringToStream(LParams, SetRequestParams(ASource, AByteEncoding{$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF}));
LParams.Position := 0;
Post(AURL, LParams, AResponseContent);
finally
FreeAndNil(LParams);
end;
end else begin
Post(AURL, TStream(nil), AResponseContent);
end;
end;
function TIdCustomHTTP.Post(AURL: string; ASource: TStrings; AByteEncoding: IIdTextEncoding = nil
{$IFDEF STRING_IS_ANSI}; ASrcEncoding: IIdTextEncoding = nil; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LResponse: TMemoryStream;
begin
LResponse := TMemoryStream.Create;
try
Post(AURL, ASource, LResponse, AByteEncoding{$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF});
LResponse.Position := 0;
Result := ReadStringAsCharset(LResponse, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
// TODO: if the data is XML, add/update the declared encoding to 'UTF-16LE'...
finally
FreeAndNil(LResponse);
end;
end;
function TIdCustomHTTP.Post(AURL: string; ASource: TStream
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LResponse: TMemoryStream;
begin
LResponse := TMemoryStream.Create;
try
Post(AURL, ASource, LResponse);
LResponse.Position := 0;
Result := ReadStringAsCharset(LResponse, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
// TODO: if the data is XML, add/update the declared encoding to 'UTF-16LE'...
finally
FreeAndNil(LResponse);
end;
end;
procedure TIdCustomHTTP.Put(AURL: string; ASource, AResponseContent: TStream);
begin
DoRequest(Id_HTTPMethodPut, AURL, ASource, AResponseContent, []);
end;
function TIdCustomHTTP.Put(AURL: string; ASource: TStream
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LResponse: TMemoryStream;
begin
LResponse := TMemoryStream.Create;
try
Put(AURL, ASource, LResponse);
LResponse.Position := 0;
Result := ReadStringAsCharset(LResponse, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
// TODO: if the data is XML, add/update the declared encoding to 'UTF-16LE'...
finally
FreeAndNil(LResponse);
end;
end;
function TIdCustomHTTP.Get(AURL: string
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
begin
Result := Get(AURL, []{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
end;
function TIdCustomHTTP.Trace(AURL: string
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LResponse: TMemoryStream;
begin
LResponse := TMemoryStream.Create;
try
Trace(AURL, LResponse);
LResponse.Position := 0;
Result := ReadStringAsCharset(LResponse, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
// TODO: if the data is XML, add/update the declared encoding to 'UTF-16LE'...
finally
FreeAndNil(LResponse);
end;
end;
function TIdCustomHTTP.DoOnRedirect(var Location: string; var VMethod: TIdHTTPMethod; RedirectCount: integer): boolean;
begin
// TODO: convert relative URLs to full URLs here...
Result := HandleRedirects;
if Assigned(FOnRedirect) then begin
FOnRedirect(Self, Location, RedirectCount, Result, VMethod);
end;
end;
procedure TIdCustomHTTP.SetCookies(AURL: TIdURI; ARequest: TIdHTTPRequest);
var
// under ARC, convert a weak reference to a strong reference before working with it
LCookieManager: TIdCookieManager;
begin
LCookieManager := FCookieManager;
if Assigned(LCookieManager) and AllowCookies then
begin
// Send secure cookies only if we have Secured connection
LCookieManager.GenerateClientCookies(
AURL,
TextIsSame(AURL.Protocol, 'HTTPS'), {do not localize}
ARequest.RawHeaders);
end;
end;
// This function sets the Host and Port and returns a boolean depending on
// whether a PROXY is being used or not.
function TIdCustomHTTP.SetHostAndPort: TIdHTTPConnectionType;
var
LHost: string;
LPort: Integer;
begin
// First check to see if a Proxy has been specified.
if Length(ProxyParams.ProxyServer) > 0 then begin
if (not TextIsSame(FHost, ProxyParams.ProxyServer)) or (FPort <> ProxyParams.ProxyPort) then begin
if Connected then begin
Disconnect;
end;
end;
LHost := ProxyParams.ProxyServer;
LPort := ProxyParams.ProxyPort;
if TextIsSame(URL.Protocol, 'HTTPS') then begin {do not localize}
Result := ctSSLProxy;
end else begin
Result := ctProxy;
end;
end else begin
if Assigned(Socket) then begin
if Assigned(Socket.Binding) then begin
if URL.IPVersion <> Socket.Binding.IPVersion then begin
if Connected then begin
Disconnect; // get rid of current socket handle
end;
end;
end;
end;
LHost := URL.Host;
LPort := IndyStrToInt(URL.Port, IdPORT_HTTP);
if (not TextIsSame(FHost, LHost)) or (LPort <> FPort) then begin
if Connected then begin
Disconnect;
end;
end;
if TextIsSame(URL.Protocol, 'HTTPS') then begin {do not localize}
Result := ctSSL;
end else begin
Result := ctNormal;
end;
end;
Host := LHost;
Port := LPort;
end;
procedure TIdCustomHTTP.ReadResult(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse);
var
LS: TStream;
LOrigStream : TStream;
LParseHTML : Boolean;
LCreateTmpContent : Boolean;
LDecMeth : Integer;
//0 - no compression was used or we can't support that feature
//1 - deflate
//2 - gzip
// under ARC, convert a weak reference to a strong reference before working with it
LCompressor: TIdZLibCompressorBase;
function CheckForPendingData(ATimeout: Integer): Boolean;
begin
if IOHandler.InputBufferIsEmpty then begin
IOHandler.CheckForDataOnSource(ATimeout);
end;
Result := not IOHandler.InputBufferIsEmpty;
end;
function ShouldRead: Boolean;
var
CanRead: Boolean;
begin
Result := False;
if IndyPos('chunked', LowerCase(AResponse.TransferEncoding)) > 0 then begin {do not localize}
CanRead := True;
end
else if AResponse.HasContentLength then begin
CanRead := AResponse.ContentLength > 0; // If chunked then this is also 0
end
else if IsHeaderMediaType(AResponse.ContentType, 'multipart') then begin {do not localize}
CanRead := not (hoNoReadMultipartMIME in FOptions);
end
else begin
CanRead := True;
end;
if CanRead then
begin
// DO NOT READ IF THE REQUEST IS HEAD!!!
// The server is supposed to send a 'Content-Length' header without sending
// the actual data. 1xx, 204, and 304 replies are not supposed to contain
// entity bodies, either...
if TextIsSame(ARequest.Method, Id_HTTPMethodHead) or
({TextIsSame(ARequest.Method, Id_HTTPMethodPost) and} TextIsSame(ARequest.MethodOverride, Id_HTTPMethodHead)) or
// TODO: check for 'X-HTTP-Method' and 'X-METHOD-OVERRIDE' request headers as well...
((AResponse.ResponseCode div 100) = 1) or
(AResponse.ResponseCode = 204) or
(AResponse.ResponseCode = 304) then
begin
// Have noticed one case where a non-conforming server did send an
// entity body in response to a HEAD request. If requested, ignore
// anything the server may send by accident
if not (hoWaitForUnexpectedData in FOptions) then begin
Exit;
end;
Result := CheckForPendingData(100);
end
else if (AResponse.ResponseCode div 100) = 3 then
begin
// This is a workaround for buggy HTTP 1.1 servers which
// does not return any body with 302 response code
Result := CheckForPendingData(5000);
end else begin
Result := True;
end;
end;
end;
function ChunkSize: integer;
var
j: Integer;
s: string;
begin
s := InternalReadLn;
j := IndyPos(';', s); {do not localize}
if j > 0 then begin
s := Copy(s, 1, j - 1);
end;
Result := IndyStrToInt('$' + Trim(s), 0); {do not localize}
end;
procedure ReadChunked;
var
LSize: Integer;
LTrailHeader: String;
begin
DoStatus(hsStatusText, [RSHTTPChunkStarted]);
BeginWork(wmRead);
try
LSize := ChunkSize;
while LSize <> 0 do begin
if Assigned(LS) then begin
IOHandler.ReadStream(LS, LSize);
end else begin
IOHandler.Discard(LSize);
end;
InternalReadLn; // CRLF at end of chunk data
LSize := ChunkSize;
end;
// read trailer headers
LTrailHeader := InternalReadLn;
while LTrailHeader <> '' do begin
AResponse.RawHeaders.Add(LTrailHeader);
LTrailHeader := InternalReadLn;
end;
finally
EndWork(wmRead);
end;
end;
procedure ReadMIME;
var
LMIMEBoundary: TIdBytes;
LIndex: Integer;
LSize: Integer;
begin
LMIMEBoundary := ToBytes('--' + ExtractHeaderSubItem(AResponse.ContentType, 'boundary', QuoteHTTP) + '--');
BeginWork(wmRead);
try
try
repeat
LIndex := IOHandler.InputBuffer.IndexOf(LMIMEBoundary);
if LIndex <> -1 then
begin
LSize := LIndex + Length(LMIMEBoundary);
if Assigned(LS) then begin
IOHandler.ReadStream(LS, LSize);
end else begin
IOHandler.Discard(LSize);
end;
InternalReadLn; // CRLF at end of boundary
Break;
end;
LSize := IOHandler.InputBuffer.Size - (Length(LMIMEBoundary)-1);
if LSize > 0 then begin
if Assigned(LS) then begin
IOHandler.ReadStream(LS, LSize);
end else begin
IOHandler.Discard(LSize);
end;
end;
IOHandler.CheckForDataOnSource;
IOHandler.CheckForDisconnect(True, True);
until False;
except
on E: EIdConnClosedGracefully do begin
if Assigned(LS) then begin
IOHandler.InputBuffer.ExtractToStream(LS);
end else begin
IOHandler.InputBuffer.Clear;
end;
end;
end;
finally
EndWork(wmRead);
end;
end;
begin
if not ShouldRead then begin
Exit;
end;
LDecMeth := 0;
LParseHTML := Assigned(AResponse.ContentStream) and IsContentTypeHtml(AResponse) and not (hoNoParseMetaHTTPEquiv in FOptions);
LCreateTmpContent := LParseHTML and not (AResponse.ContentStream is TCustomMemoryStream);
LOrigStream := AResponse.ContentStream;
if LCreateTmpContent then begin
AResponse.ContentStream := TMemoryStream.Create;
end;
LCompressor := Compressor;
try
// we need to determine what type of decompression may need to be used
// before we read from the IOHandler. If there is compression, then we
// use a local stream to download the compressed data and decompress it.
// If no compression is used, ContentStream will be used directly
if Assigned(AResponse.ContentStream) then begin
if Assigned(LCompressor) and LCompressor.IsReady then begin
LDecMeth := PosInStrArray(AResponse.ContentEncoding, ['deflate', 'gzip'], False) + 1; {do not localize}
end;
if LDecMeth > 0 then begin
LS := TMemoryStream.Create;
end else begin
LS := AResponse.ContentStream;
end;
end else
begin
LS := nil;
end;
try
if IndyPos('chunked', LowerCase(AResponse.TransferEncoding)) > 0 then begin {do not localize}
ReadChunked;
end
else if AResponse.HasContentLength then begin
if AResponse.ContentLength > 0 then begin// If chunked then this is also 0
try
if Assigned(LS) then begin
IOHandler.ReadStream(LS, AResponse.ContentLength);
end else begin
IOHandler.Discard(AResponse.ContentLength);
end;
except
// should this be caught here? We are being told the size, so a
// premature disconnect should be an error, right?
on E: EIdConnClosedGracefully do begin end;
end;
end;
end
else if IsHeaderMediaType(AResponse.ContentType, 'multipart') then begin {do not localize}
ReadMIME;
end else begin
if Assigned(LS) then begin
IOHandler.ReadStream(LS, -1, True);
end else begin
IOHandler.DiscardAll;
end;
end;
if LDecMeth > 0 then begin
LS.Position := 0;
case LDecMeth of
1 : LCompressor.DecompressDeflateStream(LS, AResponse.ContentStream);
2 : LCompressor.DecompressGZipStream(LS, AResponse.ContentStream);
end;
end;
finally
if LDecMeth > 0 then begin
FreeAndNil(LS);
end;
end;
if LParseHTML then begin
AResponse.ProcessMetaHTTPEquiv;
end;
finally
if LCreateTmpContent then
begin
try
LOrigStream.CopyFrom(AResponse.ContentStream, 0);
finally
{$IFNDEF USE_OBJECT_ARC}
AResponse.ContentStream.Free;
{$ENDIF}
AResponse.ContentStream := LOrigStream;
end;
end;
end;
end;
// TODO: move the XML charset detector below to the IdGlobalProtocols unit so
// it can be used in other components, like TIdMessageClient and TIdIMAP4...
type
XmlEncoding = (xmlUCS4BE, xmlUCS4BEOdd, xmlUCS4LE, xmlUCS4LEOdd,
xmlUTF16BE, xmlUTF16LE, xmlUTF8, xmlEBCDIC, xmlUnknown
);
XmlBomInfo = record
Charset: String;
BOMLen: Integer;
BOM: UInt32;
BOMMask: UInt32;
end;
XmlNonBomInfo = record
CharLen: Integer;
FirstChar: UInt32;
LastChar: UInt32;
CharMask: UInt32;
end;
const
XmlBOMs: array[xmlUCS4BE..xmlUTF8] of XmlBomInfo = (
(Charset: 'UCS-4BE'; BOMLen: 4; BOM: $0000FEFF; BOMMask: $FFFFFFFF), {do not localize}
(Charset: ''; {UCS-4} BOMLen: 4; BOM: $0000FFFE; BOMMask: $FFFFFFFF),
(Charset: 'UCS-4LE'; BOMLen: 4; BOM: $FFFE0000; BOMMask: $FFFFFFFF), {do not localize}
(Charset: ''; {UCS-4} BOMLen: 4; BOM: $FEFF0000; BOMMask: $FFFFFFFF),
(Charset: 'UTF-16BE'; BOMLen: 2; BOM: $FEFF0000; BOMMask: $FFFF0000), {do not localize}
(Charset: 'UTF-16LE'; BOMLen: 2; BOM: $FFFE0000; BOMMask: $FFFF0000), {do not localize}
(Charset: 'UTF-8'; BOMLen: 3; BOM: $EFBBBF00; BOMMask: $FFFFFF00) {do not localize}
);
XmlNonBOMs: array[xmlUCS4BE..xmlEBCDIC] of XmlNonBomInfo = (
(CharLen: 4; FirstChar: $0000003C; LastChar: $0000003E; CharMask: $FFFFFFFF),
(CharLen: 4; FirstChar: $00003C00; LastChar: $00003E00; CharMask: $FFFFFFFF),
(CharLen: 4; FirstChar: $3C000000; LastChar: $3E000000; CharMask: $FFFFFFFF),
(CharLen: 4; FirstChar: $003C0000; LastChar: $003E0000; CharMask: $FFFFFFFF),
(CharLen: 2; FirstChar: $003C003F; LastChar: $003E0000; CharMask: $FFFF0000),
(CharLen: 2; FirstChar: $3C003F00; LastChar: $3E000000; CharMask: $FFFF0000),
(CharLen: 1; FirstChar: $3C3F786D; LastChar: $3E000000; CharMask: $FF000000),
(CharLen: 1; FirstChar: $4C6FA794; LastChar: $6E000000; CharMask: $FF000000)
);
XmlUCS4AsciiIndex: array[xmlUCS4BE..xmlUCS4LEOdd] of Integer = (3, 2, 0, 1);
// RLebeau: only interested in EBCDIC ASCII characters that are allowed in
// an XML declaration, we'll treat everything else as #01 for now...
XmlEBCDICTable: array[Byte] of Char = (
{ -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -A -B -C -D -E -F }
{0-} #01, #01, #01, #01, #01, #09, #01, #01, #01, #01, #01, #01, #01, #13, #01, #01, {do not localize}
{1-} #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, {do not localize}
{2-} #01, #01, #01, #01, #01, #10, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, {do not localize}
{3-} #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, {do not localize}
{4-} ' ', #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, '.', '<', #01, #01, #01, {do not localize}
{5-} #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, {do not localize}
{6-} '-', #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, '_', '>', '?', {do not localize}
{7-} #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #27, '=', '"', {do not localize}
{8-} #01, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', #01, #01, #01, #01, #01, #01, {do not localize}
{9-} #01, 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', #01, #01, #01, #01, #01, #01, {do not localize}
{A-} #01, #01, 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', #01, #01, #01, #01, #01, #01, {do not localize}
{B-} #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, #01, {do not localize}
{C-} #01, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', #01, #01, #01, #01, #01, #01, {do not localize}
{D-} #01, 'J', 'K', 'L', 'N', 'N', 'O', 'P', 'Q', 'R', #01, #01, #01, #01, #01, #01, {do not localize}
{E-} #01, #01, 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', #01, #01, #01, #01, #01, #01, {do not localize}
{F-} '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', #01, #01, #01, #01, #01, #01 {do not localize}
);
function DetectXmlCharset(AStream: TStream): String;
var
Buffer: TIdBytes;
InBuf, StreamPos, CurPos: TIdStreamSize;
XmlDec, XmlEnc: String;
{$IFDEF STRING_IS_IMMUTABLE}
LSB: TIdStringBuilder;
{$ENDIF}
I, Len: Integer;
Enc: XmlEncoding;
Signature: UInt32;
function BufferToUInt32: UInt32;
begin
Result := (UInt32(Buffer[0]) shl 24) or
(UInt32(Buffer[1]) shl 16) or
(UInt32(Buffer[2]) shl 8) or
UInt32(Buffer[3]);
end;
begin
// XML's default encoding is UTF-8 unless specified otherwise, either
// by a BOM or an explicit "encoding" in the XML's prolog...
Result := 'UTF-8'; {do not localize}
StreamPos := AStream.Position;
try
AStream.Position := 0;
SetLength(Buffer, 4);
FillBytes(Buffer, 4, $00);
InBuf := ReadTIdBytesFromStream(AStream, Buffer, 4);
if InBuf < 3 then begin
Exit;
end;
Signature := BufferToUInt32;
// check for known BOMs first...
for Enc := Low(XmlBOMs) to High(XmlBOMs) do begin
if (Signature and XmlBOMs[Enc].BOMMask) = XmlBOMs[Enc].BOM then begin
Inc(StreamPos, XmlBOMs[Enc].BOMLen);
Result := XmlBOMs[Enc].Charset;
Exit;
end;
end;
// check for non-BOM'ed encodings now...
if InBuf <> 4 then begin
Exit;
end;
XmlDec := '';
for Enc := Low(XmlNonBOMs) to High(XmlNonBOMs) do begin
if Signature = XmlNonBOMs[Enc].FirstChar then begin
FillBytes(Buffer, 4, $00);
while (AStream.Size - AStream.Position) >= XmlNonBOMs[Enc].CharLen do
begin
ReadTIdBytesFromStream(AStream, Buffer, XmlNonBOMs[Enc].CharLen);
Signature := BufferToUInt32;
if (Signature and XmlNonBOMs[Enc].CharMask) = XmlNonBOMs[Enc].LastChar then
begin
CurPos := AStream.Position;
AStream.Position := 0;
case Enc of
xmlUCS4BE, xmlUCS4LE, xmlUCS4BEOdd, xmlUCS4LEOdd: begin
// TODO: create UCS-4 IIdTextEncoding implementations...
Len := CurPos div XmlNonBOMs[Enc].CharLen;
{$IFDEF STRING_IS_IMMUTABLE}
LSB := TIdStringBuilder.Create(Len);
{$ELSE}
SetLength(XmlDec, Len);
{$ENDIF}
for I := 1 to Len do begin
ReadTIdBytesFromStream(AStream, Buffer, XmlNonBOMs[Enc].CharLen);
{$IFDEF STRING_IS_IMMUTABLE}
LSB.Append(Char(Buffer[XmlUCS4AsciiIndex[Enc]]));
{$ELSE}
XmlDec[I] := Char(Buffer[XmlUCS4AsciiIndex[Enc]]);
{$ENDIF}
end;
{$IFDEF STRING_IS_IMMUTABLE}
XmlDec := LSB.ToString;
LSB := nil;
{$ENDIF}
end;
xmlUTF16BE: begin
XmlDec := ReadStringFromStream(AStream, CurPos, IndyTextEncoding_UTF16BE);
end;
xmlUTF16LE: begin
XmlDec := ReadStringFromStream(AStream, CurPos, IndyTextEncoding_UTF16LE);
end;
xmlUTF8: begin
XmlDec := ReadStringFromStream(AStream, CurPos, IndyTextEncoding_UTF8);
end;
xmlEBCDIC: begin
// TODO: create an EBCDIC IIdTextEncoding implementation...
{$IFDEF STRING_IS_IMMUTABLE}
Len := ReadTIdBytesFromStream(AStream, Buffer, CurPos);
LSB := TStringBuilder.Create(Len);
for I := 0 to Len-1 do begin
LSB.Append(XmlEBCDICTable[Buffer[I]]);
end;
XmlDec := LSB.ToString;
{$ELSE}
XmlDec := ReadStringFromStream(AStream, CurPos, IndyTextEncoding_8Bit);
for I := 1 to Length(XmlDec) do begin
XmlDec[I] := XmlEBCDICTable[Byte(XmlDec[I])];
end;
{$ENDIF}
end;
end;
Break;
end;
end;
Break;
end;
end;
if XmlDec = '' then begin
Exit;
end;
I := Pos('encoding', XmlDec); {do not localize}
if I = 0 then begin
Exit;
end;
XmlDec := TrimLeft(Copy(XmlDec, I+8, MaxInt));
if not CharEquals(XmlDec, 1, '=') then begin {do not localize}
Exit;
end;
XmlDec := TrimLeft(Copy(XmlDec, 2, MaxInt));
if XmlDec = '' then begin
Exit;
end;
if XmlDec[1] = #$27 then begin
XmlDec := Copy(XmlDec, 2, MaxInt);
XmlEnc := Fetch(XmlDec, #$27);
end
else if XmlDec[1] = '"' then begin
XmlDec := Copy(XmlDec, 2, MaxInt);
XmlEnc := Fetch(XmlDec, '"');
end;
XmlEnc := Trim(XmlEnc);
if XmlEnc = '' then begin
Exit;
end;
Result := XmlEnc;
finally
AStream.Position := StreamPos;
end;
end;
function TIdCustomHTTP.ResponseCharset: String;
begin
if IsContentTypeAppXml(Response) then begin
// the media type is not a 'text/...' based XML type, so ignore the
// charset from the headers, if present, and parse the XML itself...
Result := DetectXmlCharset(Response.ContentStream);
end
else begin
// RLebeau 1/30/2012: Response.CharSet is now updated at the time
// when HTML content is parsed for <meta> tags ...
// TODO: if the Charset is not specified, return an appropriate value
// that is registered with IANA for the reported ContentType...
Result := Response.CharSet;
end;
end;
const
Requires_HTTP_1_1: array[0..4] of String = (Id_HTTPMethodTrace, Id_HTTPMethodPut, Id_HTTPMethodOptions, Id_HTTPMethodDelete, Id_HTTPMethodPatch);
procedure TIdCustomHTTP.PrepareRequest(ARequest: TIdHTTPRequest);
var
LURI: TIdURI;
LHost: string;
begin
LURI := TIdURI.Create(ARequest.URL);
try
if Length(LURI.Username) > 0 then begin
ARequest.Username := LURI.Username;
ARequest.Password := LURI.Password;
end;
FURI.Username := ARequest.Username;
FURI.Password := ARequest.Password;
FURI.Path := ProcessPath(FURI.Path, LURI.Path);
FURI.Document := LURI.Document;
FURI.Params := LURI.Params;
if Length(LURI.Host) > 0 then begin
FURI.Host := LURI.Host;
end;
if Length(LURI.Protocol) > 0 then begin
FURI.Protocol := LURI.Protocol;
end
// non elegant solution - to be recoded, only for pointing the bug / GREGOR
else if TextIsSame(FURI.Protocol, 'https') then begin {do not localize}
FURI.Protocol := 'https'; {do not localize}
end
else begin
FURI.Protocol := 'http'; {do not localize}
end;
if Length(LURI.Port) > 0 then begin
FURI.Port := LURI.Port;
end
else if TextIsSame(LURI.Protocol, 'http') then begin {do not localize}
FURI.Port := IntToStr(IdPORT_HTTP);
end
else if TextIsSame(LURI.Protocol, 'https') then begin {do not localize}
FURI.Port := IntToStr(IdPORT_https);
end
else if Length(FURI.Port) = 0 then begin
raise EIdUnknownProtocol.Create(RSHTTPUnknownProtocol);
end;
if (TextIsSame(ARequest.Method, Id_HTTPMethodOptions) or TextIsSame(ARequest.MethodOverride, Id_HTTPMethodOptions))
and TextIsSame(LURI.Document, '*') then {do not localize}
begin
ARequest.URL := LURI.Document;
end else begin
// The URL part is not URL encoded at this place
ARequest.URL := URL.GetPathAndParams;
end;
ARequest.IPVersion := LURI.IPVersion;
FURI.IPVersion := ARequest.IPVersion;
// Check for valid HTTP request methods
if (PosInStrArray(ARequest.Method, Requires_HTTP_1_1, False) > -1) or
(PosInStrArray(ARequest.MethodOverride, Requires_HTTP_1_1, False) > -1) then
begin
if ProtocolVersion <> pv1_1 then begin
raise EIdException.Create(RSHTTPMethodRequiresVersion);
end;
end;
if Assigned(ARequest.Source) then begin
ARequest.ContentLength := ARequest.Source.Size;
end else begin
ARequest.ContentLength := -1;
end;
// RLebeau: wrap an IPv6 address in brackets, per RFC 2732, and RFC 3986 section 3.2.2...
if (FURI.IPVersion = Id_IPv6) and (MakeCanonicalIPv6Address(FURI.Host) <> '') then begin
LHost := '[' + FURI.Host + ']'; {do not localize}
end else begin
LHost := FURI.Host;
end;
if (TextIsSame(FURI.Protocol, 'http') and (FURI.Port = IntToStr(IdPORT_HTTP))) or {do not localize}
(TextIsSame(FURI.Protocol, 'https') and (FURI.Port = IntToStr(IdPORT_https))) then {do not localize}
begin
ARequest.Host := LHost;
end else begin
ARequest.Host := LHost + ':' + FURI.Port; {do not localize}
end;
finally
FreeAndNil(LURI); // Free URI Object
end;
end;
procedure TIdCustomHTTP.CheckAndConnect(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse);
begin
if not AResponse.KeepAlive then begin
Disconnect;
end;
if Assigned(IOHandler) then begin
IOHandler.InputBuffer.Clear;
end;
CheckForGracefulDisconnect(False);
if not Connected then try
IPVersion := FURI.IPVersion;
case ARequest.UseProxy of
ctNormal, ctProxy:
begin
if (IOHandler is TIdSSLIOHandlerSocketBase) then begin
TIdSSLIOHandlerSocketBase(IOHandler).PassThrough := True;
TIdSSLIOHandlerSocketBase(IOHandler).URIToCheck := FURI.URI;
end;
end;
ctSSL, ctSSLProxy:
begin
// if an IOHandler has not been assigned yet, try to create a default SSL IOHandler object
//
// TODO: if an IOHandler has been assigned, but is not an SSL IOHandler,
// release it and try to create a default SSL IOHandler object?
//
if IOHandler = nil then begin
IOHandler := TIdIOHandler.TryMakeIOHandler(TIdSSLIOHandlerSocketBase, Self);
if IOHandler = nil then begin
raise EIdIOHandlerPropInvalid.Create(RSIOHandlerPropInvalid);
end;
IOHandler.OnStatus := OnStatus;
ManagedIOHandler := True;
end
else if not (IOHandler is TIdSSLIOHandlerSocketBase) then begin
raise EIdIOHandlerPropInvalid.Create(RSIOHandlerPropInvalid);
end;
TIdSSLIOHandlerSocketBase(IOHandler).URIToCheck := FURI.URI;
TIdSSLIOHandlerSocketBase(IOHandler).PassThrough := (ARequest.UseProxy = ctSSLProxy);
end;
end;
Connect;
except
on E: EIdSSLProtocolReplyError do begin
Disconnect;
raise;
end;
end;
end;
procedure TIdCustomHTTP.ConnectToHost(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse);
var
LLocalHTTP: TIdHTTPProtocol;
LUseConnectVerb: Boolean;
// under ARC, convert a weak reference to a strong reference before working with it
LCompressor: TIdZLibCompressorBase;
LOldProxy: TIdHTTPConnectionType;
LNewDest: string;
begin
LNewDest := URL.Host + ':' + URL.Port;
LOldProxy := ARequest.FUseProxy;
ARequest.FUseProxy := SetHostAndPort;
if ARequest.UseProxy <> LOldProxy then begin
if Connected then begin
Disconnect;
end;
end
else if (ARequest.UseProxy = ctSSLProxy) and (not TextIsSame(ARequest.Destination, LNewDest)) then begin
if Connected then begin
Disconnect;
end;
end;
ARequest.Destination := LNewDest;
LUseConnectVerb := False;
case ARequest.UseProxy of
ctNormal:
begin
if (ProtocolVersion = pv1_0) and (Length(ARequest.Connection) = 0) then
begin
ARequest.Connection := 'keep-alive'; {do not localize}
end;
end;
ctSSL, ctSSLProxy:
begin
ARequest.Connection := '';
if ARequest.UseProxy = ctSSLProxy then begin
// if already connected to an SSL proxy, DO NOT send another
// CONNECT request, as it will be sent directly to the target
// HTTP server and not to the proxy!
LUseConnectVerb := not Connected;
end;
end;
ctProxy:
begin
ARequest.URL := FURI.URI;
if (ProtocolVersion = pv1_0) and (Length(ARequest.Connection) = 0) then
begin
ARequest.ProxyConnection := 'keep-alive'; {do not localize}
end;
if hoNonSSLProxyUseConnectVerb in FOptions then begin
// if already connected to a proxy, DO NOT send another CONNECT
// request, as it will be sent directly to the target HTTP server
// and not to the proxy!
LUseConnectVerb := not Connected;
end;
end;
end;
LCompressor := FCompressor;
if Assigned(LCompressor) and LCompressor.IsReady then begin
if IndyPos('deflate', ARequest.AcceptEncoding) = 0 then {do not localize}
begin
if ARequest.AcceptEncoding <> '' then begin {do not localize}
ARequest.AcceptEncoding := ARequest.AcceptEncoding + ', deflate'; {do not localize}
end else begin
ARequest.AcceptEncoding := 'deflate'; {do not localize}
end;
end;
if IndyPos('gzip', ARequest.AcceptEncoding) = 0 then {do not localize}
begin
if ARequest.AcceptEncoding <> '' then begin {do not localize}
ARequest.AcceptEncoding := ARequest.AcceptEncoding + ', gzip'; {do not localize}
end else begin
ARequest.AcceptEncoding := 'gzip'; {do not localize}
end;
end;
end;
{$IFDEF USE_OBJECT_ARC}LCompressor := nil;{$ENDIF}
// TODO: if AcceptEncoding is blank, DON'T set it to 'identity'! Oddly,
// some faulty servers do not understand 'identity' when explicitly
// stated. It is the default behavior when no "Accept-Encoding" header
// is sent, so just let the server fallback to it normally...
if IndyPos('identity', ARequest.AcceptEncoding) = 0 then begin {do not localize}
if ARequest.AcceptEncoding <> '' then begin
ARequest.AcceptEncoding := ARequest.AcceptEncoding + ', identity'; {do not localize}
end else begin
ARequest.AcceptEncoding := 'identity'; {do not localize}
end;
end;
if LUseConnectVerb then begin
LLocalHTTP := CreateProtocol;
try
LLocalHTTP.Request.UserAgent := ARequest.UserAgent;
LLocalHTTP.Request.Host := ARequest.Host;
LLocalHTTP.Request.Pragma := 'no-cache'; {do not localize}
LLocalHTTP.Request.URL := ARequest.Destination;
LLocalHTTP.Request.Method := Id_HTTPMethodConnect;
LLocalHTTP.Request.ProxyConnection := 'keep-alive'; {do not localize}
LLocalHTTP.Request.FUseProxy := ARequest.UseProxy;
// leaving LLocalHTTP.Response.ContentStream set to nil so response data is discarded without wasting memory
try
repeat
CheckAndConnect(LLocalHTTP.Request, LLocalHTTP.Response);
LLocalHTTP.BuildAndSendRequest(nil);
LLocalHTTP.Response.ResponseText := InternalReadLn;
if Length(LLocalHTTP.Response.ResponseText) = 0 then begin
// Support for HTTP responses without status line and headers
LLocalHTTP.Response.ResponseText := 'HTTP/1.0 200 OK'; {do not localize}
LLocalHTTP.Response.Connection := 'close'; {do not localize}
end else begin
LLocalHTTP.RetrieveHeaders(MaxHeaderLines);
ProcessCookies(LLocalHTTP.Request, LLocalHTTP.Response);
end;
if (LLocalHTTP.Response.ResponseCode div 100) = 2 then begin
// Connection established
if (ARequest.UseProxy = ctSSLProxy) and (IOHandler is TIdSSLIOHandlerSocketBase) then begin
TIdSSLIOHandlerSocketBase(IOHandler).PassThrough := False;
end;
Break;
end else begin
LLocalHTTP.ProcessResponse([]);
end;
until False;
except
raise;
// TODO: Add property that will contain the error messages.
end;
finally
FreeAndNil(LLocalHTTP);
end;
end else begin
CheckAndConnect(ARequest, AResponse);
end;
FHTTPProto.BuildAndSendRequest(URL);
// RLebeau 1/31/2008: in order for TIdWebDAV to post data correctly, don't
// restrict which HTTP methods can post (except logically for GET and HEAD),
// especially since TIdCustomHTTP.PrepareRequest() does not differentiate when
// setting up the 'Content-Length' header ...
// TODO: when sending an HTTP 1.1 request with an 'Expect: 100-continue' header,
// do not send the Source data until the server replies with a 100 response code,
// or until a timeout occurs if the server does not send a 100...
if ARequest.Source <> nil then begin
IOHandler.Write(ARequest.Source, 0, False);
end;
end;
procedure TIdCustomHTTP.SetAllowCookies(AValue: Boolean);
begin
FAllowCookies := AValue;
end;
procedure TIdCustomHTTP.ProcessCookies(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse);
var
LCookies: TStringList;
// under ARC, convert a weak reference to a strong reference before working with it
LCookieManager: TIdCookieManager;
begin
LCookieManager := FCookieManager;
if (not Assigned(LCookieManager)) and AllowCookies then begin
LCookieManager := TIdCookieManager.Create(Self);
SetCookieManager(LCookieManager);
FImplicitCookieManager := True;
end;
if Assigned(LCookieManager) and AllowCookies then begin
LCookies := TStringList.Create;
try
AResponse.RawHeaders.Extract('Set-Cookie', LCookies); {do not localize}
AResponse.MetaHTTPEquiv.RawHeaders.Extract('Set-Cookie', LCookies); {do not localize}
LCookieManager.AddServerCookies(LCookies, FURI);
finally
FreeAndNil(LCookies);
end;
end;
end;
// under ARC, all weak references to a freed object get nil'ed automatically
// so this is mostly redundant
procedure TIdCustomHTTP.Notification(AComponent: TComponent; Operation: TOperation);
begin
if Operation = opRemove then begin
if (AComponent = FCookieManager) then begin
FCookieManager := nil;
FImplicitCookieManager := False;
end
{$IFNDEF USE_OBJECT_ARC}
else if (AComponent = FAuthenticationManager) then begin
FAuthenticationManager := nil;
end else if (AComponent = FCompressor) then begin
FCompressor := nil;
end
{$ENDIF}
;
end;
inherited Notification(AComponent, Operation);
end;
procedure TIdCustomHTTP.SetCookieManager(ACookieManager: TIdCookieManager);
var
// under ARC, convert a weak reference to a strong reference before working with it
LCookieManager: TIdCookieManager;
begin
LCookieManager := FCookieManager;
if LCookieManager <> ACookieManager then begin
// under ARC, all weak references to a freed object get nil'ed automatically
if Assigned(LCookieManager) then begin
if FImplicitCookieManager then begin
FCookieManager := nil;
FImplicitCookieManager := False;
IdDisposeAndNil(LCookieManager);
end else begin
{$IFNDEF USE_OBJECT_ARC}
LCookieManager.RemoveFreeNotification(Self);
{$ENDIF}
end;
end;
FCookieManager := ACookieManager;
FImplicitCookieManager := False;
{$IFNDEF USE_OBJECT_ARC}
if Assigned(ACookieManager) then begin
ACookieManager.FreeNotification(Self);
end;
{$ENDIF}
end;
end;
function TIdCustomHTTP.DoOnAuthorization(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse): Boolean;
var
i: Integer;
S: string;
LAuthCls: TIdAuthenticationClass;
LAuth: TIdAuthentication;
begin
Inc(FAuthRetries);
if not Assigned(ARequest.Authentication) then begin
// Find wich Authentication method is supported from us.
LAuthCls := nil;
for i := 0 to AResponse.WWWAuthenticate.Count - 1 do begin
S := AResponse.WWWAuthenticate[i];
LAuthCls := FindAuthClass(Fetch(S));
if Assigned(LAuthCls) then begin
Break;
end;
end;
// let the user override us, if desired.
if Assigned(FOnSelectAuthorization) then begin
OnSelectAuthorization(Self, LAuthCls, AResponse.WWWAuthenticate);
end;
if not Assigned(LAuthCls) then begin
Result := False;
Exit;
end;
ARequest.Authentication := LAuthCls.Create;
end;
{
this is commented out as it breaks SSPI and NTLM authentication. it is
normal and expected to get multiple 407 responses during negotiation.
// Clear password and reset autorization if previous failed
if (AResponse.FResponseCode = 401) then begin
ARequest.Password := '';
ARequest.Authentication.Reset;
end;
}
// S.G. 20/10/2003: Added part about the password. Not testing user name as some
// S.G. 20/10/2003: web sites do not require user name, only password.
//
// RLebeau 11/18/2014: what about SSPI? It does not require an explicit
// username/password as it can use the identity of the user token associated
// with the calling thread!
//
Result := Assigned(FOnAuthorization) or (Trim(ARequest.Password) <> '');
if not Result then begin
Exit;
end;
LAuth := ARequest.Authentication;
LAuth.Username := ARequest.Username;
LAuth.Password := ARequest.Password;
// S.G. 20/10/2003: ToDo: We need to have a marker here to prevent the code to test with the same username/password combo
// S.G. 20/10/2003: if they are picked up from properties.
LAuth.Params.Values['Authorization'] := ARequest.Authentication.Authentication; {do not localize}
LAuth.AuthParams := AResponse.WWWAuthenticate;
Result := False;
repeat
case LAuth.Next of
wnAskTheProgram:
begin // Ask the user porgram to supply us with authorization information
if Assigned(FOnAuthorization) then
begin
LAuth.UserName := ARequest.Username;
LAuth.Password := ARequest.Password;
OnAuthorization(Self, LAuth, Result);
if Result then begin
ARequest.BasicAuthentication := True;
ARequest.Username := LAuth.UserName;
ARequest.Password := LAuth.Password;
end else begin
Break;
end;
end;
end;
wnDoRequest:
begin
Result := True;
Break;
end;
wnFail:
begin
Result := False;
Break;
end;
end;
until False;
end;
function TIdCustomHTTP.DoOnProxyAuthorization(ARequest: TIdHTTPRequest; AResponse: TIdHTTPResponse): Boolean;
var
i: Integer;
S: string;
LAuthCls: TIdAuthenticationClass;
LAuth: TIdAuthentication;
begin
Inc(FAuthProxyRetries);
if not Assigned(ProxyParams.Authentication) then begin
// Find which Authentication method is supported from us.
LAuthCls := nil;
for i := 0 to AResponse.ProxyAuthenticate.Count-1 do begin
S := AResponse.ProxyAuthenticate[i];
LAuthCls := FindAuthClass(Fetch(S));
if Assigned(LAuthCls) then begin
Break;
end;
end;
// let the user override us, if desired.
if Assigned(FOnSelectProxyAuthorization) then begin
OnSelectProxyAuthorization(Self, LAuthCls, AResponse.ProxyAuthenticate);
end;
if not Assigned(LAuthCls) then begin
Result := False;
Exit;
end;
ProxyParams.Authentication := LAuthCls.Create;
end;
{
this is commented out as it breaks SSPI and NTLM authentication. it is
normal and expected to get multiple 407 responses during negotiation.
// Clear password and reset authorization if previous failed
if (AResponse.FResponseCode = 407) then begin
ProxyParams.ProxyPassword := '';
ProxyParams.Authentication.Reset;
end;
}
// RLebeau 11/18/2014: Added part about the password. Not testing user name
// as some proxies do not require user name, only password.
//
// RLebeau 11/18/2014: what about SSPI? It does not require an explicit
// username/password as it can use the identity of the user token associated
// with the calling thread!
//
Result := Assigned(OnProxyAuthorization) or (Trim(ProxyParams.ProxyPassword) <> '');
if not Result then begin
Exit;
end;
LAuth := ProxyParams.Authentication;
LAuth.Username := ProxyParams.ProxyUsername;
LAuth.Password := ProxyParams.ProxyPassword;
// TODO: do we need to set this, like DoOnAuthorization does?
//LAuth.Params.Values['Authorization'] := ProxyParams.Authentication; {do not localize}
LAuth.AuthParams := AResponse.ProxyAuthenticate;
Result := False;
repeat
case LAuth.Next of
wnAskTheProgram: // Ask the user porgram to supply us with authorization information
begin
if Assigned(OnProxyAuthorization) then
begin
LAuth.Username := ProxyParams.ProxyUsername;
LAuth.Password := ProxyParams.ProxyPassword;
OnProxyAuthorization(Self, LAuth, Result);
if Result then begin
// TODO: do we need to set this, like DoOnAuthorization does?
//ProxyParams.BasicAuthentication := True;
ProxyParams.ProxyUsername := LAuth.Username;
ProxyParams.ProxyPassword := LAuth.Password;
end else begin
Break;
end;
end;
end;
wnDoRequest:
begin
Result := True;
Break;
end;
wnFail:
begin
Result := False;
Break;
end;
end;
until False;
end;
function TIdCustomHTTP.GetResponseCode: Integer;
begin
Result := Response.ResponseCode;
end;
function TIdCustomHTTP.GetResponseText: string;
begin
Result := Response.ResponseText;
end;
function TIdCustomHTTP.GetResponse: TIdHTTPResponse;
begin
Result := FHTTPProto.Response;
end;
function TIdCustomHTTP.GetRequest: TIdHTTPRequest;
begin
Result := FHTTPProto.Request;
end;
function TIdCustomHTTP.GetMetaHTTPEquiv: TIdMetaHTTPEquiv;
begin
Result := Response.MetaHTTPEquiv;
end;
procedure TIdCustomHTTP.DoOnDisconnected;
var
// under ARC, convert a weak reference to a strong reference before working with it
LAuthManager: TIdAuthenticationManager;
begin
inherited DoOnDisconnected;
if Assigned(Request.Authentication) and
(Request.Authentication.CurrentStep = Request.Authentication.Steps) then
begin
LAuthManager := AuthenticationManager;
if Assigned(LAuthManager) then begin
LAuthManager.AddAuthentication(Request.Authentication, URL);
end;
{$IFNDEF USE_OBJECT_ARC}
Request.Authentication.Free;
{$ENDIF}
Request.Authentication := nil;
end;
if Assigned(ProxyParams.Authentication) and
(ProxyParams.Authentication.CurrentStep = ProxyParams.Authentication.Steps) then begin
ProxyParams.Authentication.Reset;
end;
end;
procedure TIdCustomHTTP.SetAuthenticationManager(Value: TIdAuthenticationManager);
begin
{$IFDEF USE_OBJECT_ARC}
// under ARC, all weak references to a freed object get nil'ed automatically
FAuthenticationManager := Value;
{$ELSE}
if FAuthenticationManager <> Value then begin
if Assigned(FAuthenticationManager) then begin
FAuthenticationManager.RemoveFreeNotification(self);
end;
FAuthenticationManager := Value;
if Assigned(FAuthenticationManager) then begin
FAuthenticationManager.FreeNotification(Self);
end;
end;
{$ENDIF}
end;
{
procedure TIdCustomHTTP.SetHost(const Value: string);
begin
inherited SetHost(Value);
URL.Host := Value;
end;
procedure TIdCustomHTTP.SetPort(const Value: integer);
begin
inherited SetPort(Value);
URL.Port := IntToStr(Value);
end;
}
procedure TIdCustomHTTP.SetRequest(Value: TIdHTTPRequest);
begin
FHTTPProto.Request.Assign(Value);
end;
procedure TIdCustomHTTP.SetProxyParams(AValue: TIdProxyConnectionInfo);
begin
FProxyParameters.Assign(AValue);
end;
procedure TIdCustomHTTP.Post(AURL: string; ASource: TIdMultiPartFormDataStream;
AResponseContent: TStream);
begin
Assert(ASource<>nil);
Request.ContentType := ASource.RequestContentType;
// TODO: Request.CharSet := ASource.RequestCharSet;
Post(AURL, TStream(ASource), AResponseContent);
end;
function TIdCustomHTTP.Post(AURL: string; ASource: TIdMultiPartFormDataStream
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
begin
Assert(ASource<>nil);
Request.ContentType := ASource.RequestContentType;
// TODO: Request.CharSet := ASource.RequestCharSet;
Result := Post(AURL, TStream(ASource){$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
end;
{ TIdHTTPResponse }
constructor TIdHTTPResponse.Create(AHTTP: TIdCustomHTTP);
begin
inherited Create(AHTTP);
FHTTP := AHTTP;
FResponseCode := -1;
FMetaHTTPEquiv := TIdMetaHTTPEquiv.Create(AHTTP);
end;
destructor TIdHTTPResponse.Destroy;
begin
FreeAndNil(FMetaHTTPEquiv);
inherited Destroy;
end;
procedure TIdHTTPResponse.Clear;
begin
inherited Clear;
FMetaHTTPEquiv.Clear;
end;
procedure TIdHTTPResponse.ProcessMetaHTTPEquiv;
var
StdValues: TStringList;
I: Integer;
Name: String;
begin
FMetaHTTPEquiv.ProcessMetaHTTPEquiv(ContentStream);
if FMetaHTTPEquiv.RawHeaders.Count > 0 then begin
// TODO: optimize this
StdValues := TStringList.Create;
try
FMetaHTTPEquiv.RawHeaders.ConvertToStdValues(StdValues);
for I := 0 to StdValues.Count-1 do begin
Name := StdValues.Names[I];
if Name <> '' then begin
RawHeaders.Values[Name] := IndyValueFromIndex(StdValues, I);
end;
end;
finally
StdValues.Free;
end;
ProcessHeaders;
end;
if FMetaHTTPEquiv.CharSet <> '' then begin
FCharSet := FMetaHTTPEquiv.CharSet;
end;
end;
function TIdHTTPResponse.GetKeepAlive: Boolean;
begin
if FHTTP.Connected then begin
FHTTP.IOHandler.CheckForDisconnect(False);
end;
FKeepAlive := FHTTP.Connected;
if FKeepAlive then
begin
case FHTTP.ProtocolVersion of // TODO: use ResponseVersion instead?
pv1_1:
{ By default we assume that keep-alive is by default and will close
the connection only there is "close" }
begin
FKeepAlive := not (
TextIsSame(Trim(Connection), 'CLOSE') or {do not localize}
TextIsSame(Trim(ProxyConnection), 'CLOSE') {do not localize}
);
end;
pv1_0:
{ By default we assume that keep-alive is not by default and will keep
the connection only if there is "keep-alive" }
begin
FKeepAlive :=
TextIsSame(Trim(Connection), 'KEEP-ALIVE') or {do not localize}
TextIsSame(Trim(ProxyConnection), 'KEEP-ALIVE') {do not localize}
{ or ((ResponseVersion = pv1_1) and
(Length(Trim(Connection)) = 0) and
(Length(Trim(ProxyConnection)) = 0)) };
end;
end;
end;
Result := FKeepAlive;
end;
function TIdHTTPResponse.GetResponseCode: Integer;
var
S, Tmp: string;
begin
if FResponseCode = -1 then
begin
S := FResponseText;
Fetch(S);
S := Trim(S);
// RLebeau: IIS supports status codes with decimals in them, but it is not supposed to
// transmit them to clients, which is a violation of RFC 2616. But have seen it happen,
// so check for it...
Tmp := Fetch(S, ' ', False); {do not localize}
S := Fetch(Tmp, '.', False); {do not localize}
FResponseCode := IndyStrToInt(S, -1);
end;
Result := FResponseCode;
end;
procedure TIdHTTPResponse.SetResponseText(const AValue: String);
var
S: String;
i: TIdHTTPProtocolVersion;
begin
FResponseText := AValue;
FResponseCode := -1; // re-parse the next time it is accessed
ResponseVersion := pv1_0; // default until determined otherwise...
S := Copy(FResponseText, 6, 3);
for i := Low(TIdHTTPProtocolVersion) to High(TIdHTTPProtocolVersion) do begin
if TextIsSame(ProtocolVersionString[i], S) then begin
ResponseVersion := i;
Exit;
end;
end;
end;
{ TIdHTTPRequest }
constructor TIdHTTPRequest.Create(AHTTP: TIdCustomHTTP);
begin
inherited Create(AHTTP);
FHTTP := AHTTP;
FUseProxy := ctNormal;
end;
{ TIdHTTPProtocol }
constructor TIdHTTPProtocol.Create(AConnection: TIdCustomHTTP);
begin
inherited Create;
FHTTP := AConnection;
// Create the headers
FRequest := TIdHTTPRequest.Create(FHTTP);
FResponse := TIdHTTPResponse.Create(FHTTP);
end;
destructor TIdHTTPProtocol.Destroy;
begin
FreeAndNil(FRequest);
FreeAndNil(FResponse);
inherited Destroy;
end;
procedure TIdHTTPProtocol.BuildAndSendRequest(AURI: TIdURI);
var
i: Integer;
LBufferingStarted: Boolean;
begin
// needed for Digest authentication, but maybe others as well...
if Assigned(Request.Authentication) then begin
// TODO: include entity body for Digest "auth-int" qop...
Request.Authentication.SetRequest(Request.Method, Request.URL);
end;
// TODO: disable header folding for HTTP 1.0 requests
Request.SetHeaders;
FHTTP.ProxyParams.SetHeaders(Request.RawHeaders);
if Assigned(AURI) then begin
FHTTP.SetCookies(AURI, Request);
end;
// This is a workaround for some HTTP servers which do not implement
// the HTTP protocol properly
LBufferingStarted := not FHTTP.IOHandler.WriteBufferingActive;
if LBufferingStarted then begin
FHTTP.IOHandler.WriteBufferOpen;
end;
try
FHTTP.IOHandler.WriteLn(Request.Method + ' ' + Request.URL + ' HTTP/' + ProtocolVersionString[FHTTP.ProtocolVersion]); {do not localize}
// write the headers
for i := 0 to Request.RawHeaders.Count - 1 do begin
if Length(Request.RawHeaders.Strings[i]) > 0 then begin
FHTTP.IOHandler.WriteLn(Request.RawHeaders.Strings[i]);
end;
end;
FHTTP.IOHandler.WriteLn(''); {do not localize}
if LBufferingStarted then begin
FHTTP.IOHandler.WriteBufferClose;
end;
except
if LBufferingStarted then begin
FHTTP.IOHandler.WriteBufferCancel;
end;
raise;
end;
end;
procedure TIdHTTPProtocol.RetrieveHeaders(AMaxHeaderCount: integer);
var
s: string;
LHeaderCount: Integer;
begin
// Set the response headers
// Clear headers
// Don't use Capture.
// S.G. 6/4/2004: Added AmaxHeaderCount parameter to prevent the "header bombing" of the server
Response.Clear;
s := FHTTP.InternalReadLn;
try
LHeaderCount := 0;
while (s <> '') and ( (AMaxHeaderCount > 0) or (LHeaderCount < AMaxHeaderCount) ) do
begin
Response.RawHeaders.Add(S);
s := FHTTP.InternalReadLn;
Inc(LHeaderCount);
end;
except
on E: EIdConnClosedGracefully do begin
FHTTP.Disconnect;
end else begin
raise;
end;
end;
Response.ProcessHeaders;
end;
function TIdHTTPProtocol.ProcessResponse(AIgnoreReplies: array of Int16): TIdHTTPWhatsNext;
var
LResponseCode, LResponseDigit: Integer;
procedure CheckException;
var
i: Integer;
LTempResponse: TMemoryStream;
LTempStream: TStream;
begin
LTempResponse := TMemoryStream.Create;
try
LTempStream := Response.ContentStream;
Response.ContentStream := LTempResponse;
try
try
FHTTP.ReadResult(Request, Response);
except
on E: EIdConnClosedGracefully do begin
FHTTP.Disconnect;
end;
end;
if hoNoProtocolErrorException in FHTTP.HTTPOptions then begin
Exit;
end;
if High(AIgnoreReplies) > -1 then begin
for i := Low(AIgnoreReplies) to High(AIgnoreReplies) do begin
if LResponseCode = AIgnoreReplies[i] then begin
Exit;
end;
end;
end;
LTempResponse.Position := 0;
raise EIdHTTPProtocolException.CreateError(LResponseCode, FHTTP.ResponseText,
ReadStringAsCharset(LTempResponse, FHTTP.ResponseCharSet));
finally
Response.ContentStream := LTempStream;
end;
finally
FreeAndNil(LTempResponse);
end;
end;
procedure DiscardContent;
var
LOrigStream: TStream;
begin
LOrigStream := Response.ContentStream;
Response.ContentStream := nil;
try
try
FHTTP.ReadResult(Request, Response);
except
on E: EIdConnClosedGracefully do begin
FHTTP.Disconnect;
end;
end;
finally
Response.ContentStream := LOrigStream;
end;
end;
function HeadersCanContinue: Boolean;
begin
Result := True;
if Assigned(FHTTP.OnHeadersAvailable) then begin
FHTTP.OnHeadersAvailable(FHTTP, Response.RawHeaders, Result);
end;
end;
var
LLocation: string;
LMethod: TIdHTTPMethod;
LNeedAuth: Boolean;
//LTemp: Integer;
begin
// provide the user with the headers and let the user decide
// whether the response processing should continue...
if not HeadersCanContinue then begin
Response.KeepAlive := False; // TODO: provide the user an option whether to force DoRequest() to disconnect the connection or not
Result := wnJustExit;
Exit;
end;
// Cache this as ResponseCode calls GetResponseCode which parses it out
LResponseCode := Response.ResponseCode;
LResponseDigit := LResponseCode div 100;
LNeedAuth := False;
// Handle Redirects
// RLebeau: All 3xx replies other than 304 are redirects. Reply 201 has a
// Location header but is NOT a redirect!
// RLebeau 4/21/2011: Amazon S3 includes a Location header in its 200 reply
// to some PUT requests. Not sure if this is a bug or intentional, but we
// should NOT perform a redirect for any replies other than 3xx. Amazon S3
// does NOT include a Location header in its 301 reply, though! This is
// intentional, per Amazon's documentation, as a way for developers to
// detect when URLs are addressed incorrectly...
if (LResponseDigit = 3) and (LResponseCode <> 304) then
begin
if Response.Location = '' then begin
CheckException;
Result := wnJustExit;
Exit;
end;
Inc(FHTTP.FRedirectCount);
// LLocation := TIdURI.URLDecode(Response.Location);
LLocation := Response.Location;
LMethod := Request.Method;
// fire the event
if not FHTTP.DoOnRedirect(LLocation, LMethod, FHTTP.FRedirectCount) then begin
CheckException;
Result := wnJustExit;
Exit;
end;
if (FHTTP.FHandleRedirects) and (FHTTP.FRedirectCount < FHTTP.FRedirectMax) then begin
Result := wnGoToURL;
Request.URL := LLocation;
// GDG 21/11/2003. If it's a 303, we should do a get this time
// RLebeau 7/15/2004 - do a GET on 302 as well, as mentioned in RFC 2616
// RLebeau 1/11/2008 - turns out both situations are WRONG! RFCs 2068 and
// 2616 specifically state that changing the method to GET in response
// to 302 and 303 is errorneous. Indy 9 did it right by reusing the
// original method and source again and only changing the URL, so lets
// revert back to that same behavior!
// RLebeau 12/28/2012 - one more time. RFCs 2068 and 2616 actually say that
// changing the method in response to 302 is erroneous, but changing the
// method to GET in response to 303 is intentional and why 303 was introduced
// in the first place. Erroneous clients treat 302 as 303, though. Now
// encountering servers that actually expect this 303 behavior, so we have
// to enable it again! Adding an optional HTTPOption flag so clients can
// enable the erroneous 302 behavior if they really need it.
if ((LResponseCode = 302) and (hoTreat302Like303 in FHTTP.HTTPOptions)) or
(LResponseCode = 303) then
begin
Request.Source := nil;
Request.Method := Id_HTTPMethodGet;
end else begin
Request.Method := LMethod;
end;
Request.MethodOverride := '';
end else begin
Result := wnJustExit;
Response.Location := LLocation;
end;
if FHTTP.Connected then begin
// This is a workaround for buggy HTTP 1.1 servers which
// does not return any body with 302 response code
DiscardContent; // may wait a few seconds for any kind of content
end;
end else begin
//Ciaran, 30th Nov 2004: I commented out the following code. When a https server
//sends a disconnect immediately after sending the requested page in an SSL
//session (which they sometimes do to indicate a "session" is finished), the code
//below causes a "Connection closed gracefully" exception BUT the returned page
//is lost (IOHandler.Request is empty). If the code below is re-enabled by
//someone for whatever reason, they MUST test for this case.
// GREGOR Workaround
// if we get an error we disconnect if we use SSLIOHandler
//if Assigned(FHTTP.IOHandler) then
//begin
// Response.KeepAlive := not (FHTTP.Connected and (FHTTP.IOHandler is TIdSSLIOHandlerSocketBase) and Response.KeepAlive);
//end;
// RLebeau 2/15/2006: RFC 1945 states the following:
//
// For response messages, whether or not an entity body is included with
// a message is dependent on both the request method and the response
// code. All responses to the HEAD request method must not include a
// body, even though the presence of entity header fields may lead one
// to believe they do. All 1xx (informational), 204 (no content), and
// 304 (not modified) responses must not include a body. All other
// responses must include an entity body or a Content-Length header
// field defined with a value of zero (0).
if LResponseDigit <> 2 then begin
case LResponseCode of
401:
begin // HTTP Server authorization required
if (FHTTP.AuthRetries >= FHTTP.MaxAuthRetries) or
(not FHTTP.DoOnAuthorization(Request, Response)) then begin
if Assigned(Request.Authentication) then begin
Request.Authentication.Reset;
end;
CheckException;
Result := wnJustExit;
Exit;
end else begin
LNeedAuth := hoInProcessAuth in FHTTP.HTTPOptions;
end;
end;
407:
begin // Proxy Server authorization requered
if (FHTTP.AuthProxyRetries >= FHTTP.MaxAuthRetries) or
(not FHTTP.DoOnProxyAuthorization(Request, Response)) then
begin
if Assigned(FHTTP.ProxyParams.Authentication) then begin
FHTTP.ProxyParams.Authentication.Reset;
end;
CheckException;
Result := wnJustExit;
Exit;
end else begin
LNeedAuth := hoInProcessAuth in FHTTP.HTTPOptions;
end;
end;
else begin
CheckException;
Result := wnJustExit;
Exit;
end;
end;
end;
if LNeedAuth then begin
// discard the content of Error message
DiscardContent;
Result := wnAuthRequest;
end else
begin
// RLebeau 6/30/2006: DO NOT READ IF THE REQUEST IS HEAD!!!
// The server is supposed to send a 'Content-Length' header
// without sending the actual data...
if TextIsSame(Request.Method, Id_HTTPMethodHead) or
TextIsSame(Request.MethodOverride, Id_HTTPMethodHead) or
(LResponseCode = 204) then
begin
// Have noticed one case where a non-conforming server did send an
// entity body in response to a HEAD request. If requested, ignore
// anything the server may send by accident
DiscardContent;
end else begin
FHTTP.ReadResult(Request, Response);
end;
Result := wnJustExit;
end;
end;
end;
function TIdCustomHTTP.CreateProtocol: TIdHTTPProtocol;
begin
Result := TIdHTTPProtocol.Create(Self);
end;
procedure TIdCustomHTTP.InitComponent;
begin
inherited;
FURI := TIdURI.Create('');
FAuthRetries := 0;
FAuthProxyRetries := 0;
AllowCookies := True;
FImplicitCookieManager := False;
FOptions := [hoForceEncodeParams];
FRedirectMax := Id_TIdHTTP_RedirectMax;
FHandleRedirects := Id_TIdHTTP_HandleRedirects;
//
FProtocolVersion := Id_TIdHTTP_ProtocolVersion;
FHTTPProto := CreateProtocol;
FProxyParameters := TIdProxyConnectionInfo.Create;
FProxyParameters.Clear;
FMaxAuthRetries := Id_TIdHTTP_MaxAuthRetries;
FMaxHeaderLines := Id_TIdHTTP_MaxHeaderLines;
end;
function TIdCustomHTTP.InternalReadLn: String;
begin
Result := IOHandler.ReadLn;
if IOHandler.ReadLnTimedout then begin
raise EIdReadTimeout.Create(RSReadTimeout);
end;
end;
function TIdCustomHTTP.Get(AURL: string; AIgnoreReplies: array of Int16
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LStream: TMemoryStream;
begin
LStream := TMemoryStream.Create;
try
Get(AURL, LStream, AIgnoreReplies);
LStream.Position := 0;
Result := ReadStringAsCharset(LStream, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
// TODO: if the data is XML, add/update the declared encoding to 'UTF-16LE'...
finally
FreeAndNil(LStream);
end;
end;
procedure TIdCustomHTTP.Get(AURL: string; AResponseContent: TStream;
AIgnoreReplies: array of Int16);
begin
DoRequest(Id_HTTPMethodGet, AURL, nil, AResponseContent, AIgnoreReplies);
end;
procedure TIdCustomHTTP.DoRequest(const AMethod: TIdHTTPMethod;
AURL: string; ASource, AResponseContent: TStream;
AIgnoreReplies: array of Int16);
var
LResponseLocation: TIdStreamSize;
begin
//reset any counters
FRedirectCount := 0;
FAuthRetries := 0;
FAuthProxyRetries := 0;
if Assigned(AResponseContent) then begin
LResponseLocation := AResponseContent.Position;
end else begin
LResponseLocation := 0; // Just to avoid the warning message
end;
Request.URL := AURL;
Request.Method := AMethod;
Request.Source := ASource;
Response.ContentStream := AResponseContent;
try
repeat
PrepareRequest(Request);
if IOHandler is TIdSSLIOHandlerSocketBase then begin
TIdSSLIOHandlerSocketBase(IOHandler).URIToCheck := FURI.URI;
end;
ConnectToHost(Request, Response);
// Workaround for servers wich respond with 100 Continue on GET and HEAD
// This workaround is just for temporary use until we have final HTTP 1.1
// realisation. HTTP 1.1 is ongoing because of all the buggy and conflicting servers.
repeat
Response.ResponseText := InternalReadLn;
FHTTPProto.RetrieveHeaders(MaxHeaderLines);
ProcessCookies(Request, Response);
until Response.ResponseCode <> 100;
case FHTTPProto.ProcessResponse(AIgnoreReplies) of
wnAuthRequest:
begin
Request.URL := AURL;
end;
wnReadAndGo:
begin
ReadResult(Request, Response);
if Assigned(AResponseContent) then begin
AResponseContent.Position := LResponseLocation;
AResponseContent.Size := LResponseLocation;
end;
FAuthRetries := 0;
FAuthProxyRetries := 0;
end;
wnGoToURL:
begin
if Assigned(AResponseContent) then begin
AResponseContent.Position := LResponseLocation;
AResponseContent.Size := LResponseLocation;
end;
FAuthRetries := 0;
FAuthProxyRetries := 0;
end;
wnJustExit:
begin
Break;
end;
wnDontKnow:
begin
raise EIdException.Create(RSHTTPNotAcceptable);
end;
end;
until False;
finally
if not Response.KeepAlive then begin
// TODO: do not disconnect if hoNoReadMultipartMIME is in effect
Disconnect;
end;
end;
end;
procedure TIdCustomHTTP.Patch(AURL: string; ASource, AResponseContent: TStream);
begin
DoRequest(Id_HTTPMethodPatch, AURL, ASource, AResponseContent, []);
end;
function TIdCustomHTTP.Patch(AURL: string; ASource: TStream
{$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
): string;
var
LResponse: TMemoryStream;
begin
LResponse := TMemoryStream.Create;
try
Patch(AURL, ASource, LResponse);
LResponse.Position := 0;
Result := ReadStringAsCharset(LResponse, ResponseCharset{$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF});
// TODO: if the data is XML, add/update the declared encoding to 'UTF-16LE'...
finally
FreeAndNil(LResponse);
end;
end;
end.