3060 lines
105 KiB
Plaintext
3060 lines
105 KiB
Plaintext
|
{
|
|||
|
$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.
|
|||
|
|