unit uROWinInetHttpChannel; {----------------------------------------------------------------------------} { RemObjects SDK Library - Core Library } { } { compiler: Delphi 5 and up } { platform: Win32 } { } { (c)opyright RemObjects Software. all rights reserved. } { } { Using this code requires a valid license of the RemObjects SDK } { which can be obtained at http://www.remobjects.com. } {----------------------------------------------------------------------------} {$I RemObjects.inc} interface uses {$IFDEF REMOBJECTS_TRIAL}uROTrial,{$ENDIF} Classes, WinInet, uRORes, uROClient, uROClientIntf, Windows; const DEFAULT_TIMEOUT = 1000 * 60 * 10; // default value = 10 Minutes type { TLogin } TROLogin = class(TPersistent) private fUsername, fPassword: string; public procedure Assign(Source: TPersistent); override; published property Username:string read fUsername write fUsername; property Password:string read fPassword write fPassword; end; TAfterAttemptConnectEvent = procedure (Sender: TObject; var aAttemptConnectResult: DWORD) of object; { TROWinINetHttpChannel } TROWinInetHTTPChannel = class(TROTransportChannel, IROTransport, IROTCPTransport, IROHTTPTransport) private fUserAgent:string; fTargetUrl:string; fStoreConnected:boolean; fKeepConnection:boolean; fHeaders:string; fInetConnect: HINTERNET; fInetRoot: HINTERNET; fUrlScheme: Integer; fUrlHost: string; fUrlSite: string; fUrlPort: Integer; fLogin: TROLogin; fAfterConnect: TNotifyEvent; fTrustInvalidCA: boolean; // True means disable check for valid SSL/PCT-based certificates. fTimeOut: integer; // Request TimeOut value. fAttemptToConnect: Boolean; fInitializeAutoProxy: Boolean; fOnAfterAttemptConnect: TAfterAttemptConnectEvent; procedure SetConnected(iValue:boolean); function GetConnected:boolean; procedure SetLogin(const Value: TROLogin); function SendData(iData:TStream):hInternet; procedure Check(Error: Boolean); procedure ReceiveData(iRequest:hInternet; ioData:TStream); function AllowInvalidCA(InetConnect: HINTERNET; dwInetStatus: DWord): DWord; function CheckInetError(InetConnect: HINTERNET; dwInetStatus: DWord): DWord; protected procedure IntDispatch(aRequest, aResponse : TStream); override; procedure IntSetServerLocator(aServerLocator : TROServerLocator); override; { IROTransport } function GetTransportObject : TObject; override; { IROTCPTransport } function GetClientAddress : string; { IROHTTPTransport } procedure SetHeaders(const aName, aValue : string); function GetHeaders(const aName : string) : string; function GetContentType : string; procedure SetContentType(const aValue : string); function GetUserAgent : string; procedure SetUserAgent(const aValue : string); procedure SetTargetURL(const Value: string); function GetTargetURL:string; function GetPathInfo : string; function GetLocation : string; function GetQueryString: String; procedure SetPathInfo(const aValue: String); procedure TriggerAfterConnect; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure CheckProperties; override; procedure Assign(aSource : TPersistent); override; published property UserAgent:string read GetUserAgent write SetUserAgent; property TargetURL : string read fTargetURL write SetTargetURL; property Login:TROLogin read fLogin write SetLogin; property StoreConnected:boolean read fStoreConnected write fStoreConnected default false; property Connected:boolean read GetConnected write SetConnected stored fStoreConnected default false; property KeepConnection:boolean read fKeepConnection write fKeepConnection default false; property Timeout: Integer read fTimeout write fTimeout default DEFAULT_TIMEOUT; property AfterConnect:TNotifyEvent read fAfterConnect Write fAfterConnect; property AttemptToConnect: Boolean read fAttemptToConnect write fAttemptToConnect default true; property OnProgress; property OnAfterAttemptConnect: TAfterAttemptConnectEvent read FOnAfterAttemptConnect write FOnAfterAttemptConnect; property InitializeAutoProxy: Boolean read fInitializeAutoProxy write fInitializeAutoProxy default false; published property SynchronizedProbing; property OnSendStream; property OnReceiveStream; property ServerLocators; property DispatchOptions; property OnServerLocatorAssignment; property ProbeServers; property ProbeFrequency; property OnBeforeProbingServers; property OnAfterProbingServers; property OnBeforeProbingServer; property OnAfterProbingServer; property OnLoginNeeded; end; resourcestring sxInvalidURLformat = 'Invalid URL format'; implementation uses SysUtils, uROHTTPTools, uROClasses; const INTERNET_ERROR_MASK_COMBINED_SEC_CERT = $2; const_WinInetErrorCode_MIN = INTERNET_ERROR_BASE + 1; const_WinInetErrorCode_MAX = ERROR_INTERNET_FAILED_DUETOSECURITYCHECK; ERROR_INTERNET_FORTEZZA_LOGIN_NEEDED = (INTERNET_ERROR_BASE + 54); ERROR_INTERNET_SEC_CERT_ERRORS = (INTERNET_ERROR_BASE + 55); ERROR_INTERNET_SEC_CERT_NO_REV = (INTERNET_ERROR_BASE + 56); ERROR_INTERNET_SEC_CERT_REV_FAILED = (INTERNET_ERROR_BASE + 57); { TROWinINetHTTPChannel } procedure TROWinINetHTTPChannel.Check(Error: Boolean); var ErrCode:integer; S:string; begin ErrCode := getlasterror; if Error and (ErrCode <> 0) then begin SetLength(S, 256); if FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, Pointer(GetModuleHandle('wininet.dll')), ErrCode, 0, PChar(S), Length(S), nil) > 0 then begin; SetLength(S, StrLen(PChar(S))); while (Length(S) > 0) and (S[Length(S)] in [#10, #13]) do SetLength(S, Length(S) - 1); raise EROException.Create(S); end else begin raise EROException.CreateFmt(err_UnexpectedWinINetProblem,[ErrCode]); end; end; end; constructor TROWinInetHTTPChannel.Create(AOwner: TComponent); begin inherited; fTimeOut := DEFAULT_TIMEOUT; UserAgent := str_ProductName; fLogin := TROLogin.Create(); fAttemptToConnect := true; end; destructor TROWinInetHTTPChannel.Destroy; begin Connected := false; FreeAndNil(fLogin); inherited; end; function TROWinInetHTTPChannel.GetConnected: boolean; begin result := Assigned(fInetConnect); end; function TROWinINetHTTPChannel.GetContentType: string; begin result := GetHeaders(id_ContentType); end; procedure TROWinINetHTTPChannel.SetContentType(const aValue: string); begin SetHeaders(id_ContentType,aValue); end; function TROWinINetHTTPChannel.GetHeaders(const aName: string): string; begin end; procedure TROWinINetHTTPChannel.SetHeaders(const aName, aValue: string); begin if aValue = '' then exit; if fHeaders <> '' then fHeaders := fHeaders+#13#10; fHeaders := fHeaders+aName+': '+aValue; end; function TROWinINetHTTPChannel.GetClientAddress: string; begin result := ''; end; function TROWinINetHTTPChannel.GetLocation: string; begin result := ''; end; function TROWinINetHTTPChannel.GetPathInfo: string; begin result := ''; end; function TROWinINetHTTPChannel.GetTargetURL: string; begin result := fTargetUrl; end; function TROWinINetHTTPChannel.GetUserAgent: string; begin result := fUserAgent; end; procedure TROWinINetHTTPChannel.SetUserAgent(const aValue: string); begin if UserAgent <> aValue then fUserAgent := aValue; end; procedure TROWinInetHTTPChannel.SetLogin(const Value: TROLogin); begin fLogin.Assign(Value); end; procedure TROWinINetHTTPChannel.IntDispatch(aRequest, aResponse: TStream); var lRequest:hInternet; begin CheckProperties; Connected := true; try lRequest := SendData(aRequest); try ReceiveData(lRequest,aResponse); finally InternetCloseHandle(lRequest); end; finally if not KeepConnection then Connected := false; end; end; function TROWinINetHTTPChannel.SendData(iData: TStream):hInternet; var lHeaders: string; RetVal, Flags: DWord; AcceptTypes: array of PChar; lDataStream: TMemoryStream; Mask: DWORD; bRes: Boolean; begin SetLength(AcceptTypes, 2); TriggerProgress(ptStart, pdSending, 0, iData.Size); AcceptTypes[0] := PChar('application/octet-stream'); AcceptTypes[1] := nil; Flags := INTERNET_FLAG_NO_CACHE_WRITE; if KeepConnection then Flags := Flags or INTERNET_FLAG_KEEP_CONNECTION; if fURLScheme = INTERNET_SCHEME_HTTPS then begin Flags := Flags or INTERNET_FLAG_SECURE; if fTrustInvalidCA then begin Flags := Flags or INTERNET_FLAG_IGNORE_CERT_CN_INVALID or INTERNET_FLAG_IGNORE_CERT_DATE_INVALID; end; end; result := HttpOpenRequest(FInetConnect, 'POST', PChar(fURLSite), nil, nil, Pointer(AcceptTypes), Flags, Integer(Self)); Check(not Assigned(result)); { Time Out } if fTimeOut > 0 then begin InternetSetOption(Result, INTERNET_OPTION_RECEIVE_TIMEOUT, @fTimeOut, SizeOf(fTimeOut)); InternetSetOption(FInetConnect, INTERNET_OPTION_RECEIVE_TIMEOUT, @fTimeOut, SizeOf(fTimeOut)); InternetSetOption(Result, INTERNET_OPTION_SEND_TIMEOUT, @fTimeOut, SizeOf(fTimeOut)); InternetSetOption(FInetConnect, INTERNET_OPTION_SEND_TIMEOUT, @fTimeOut, SizeOf(fTimeOut)); end; { Sertificates Errors } Mask := INTERNET_ERROR_MASK_COMBINED_SEC_CERT; InternetSetOption(FInetConnect, INTERNET_OPTION_ERROR_MASK, @Mask, SizeOf(Mask)); if iData is TMemoryStream then begin lDataStream := iData as TMemoryStream end else begin lDataStream := TMemoryStream.Create(); lDataStream.LoadFromStream(iData); end; try while true do begin lHeaders := fHeaders; fHeaders := ''; bRes := HttpSendRequest(Result, pChar(lHeaders), Length(lHeaders), lDataStream.Memory, lDataStream.Size); if not bRes then begin RetVal := GetLastError; if (RetVal > const_WinInetErrorCode_MIN) and (RetVal < const_WinInetErrorCode_MAX) then begin if (RetVal = ERROR_INTERNET_SEC_CERT_ERRORS) or (RetVal = ERROR_INTERNET_INVALID_CA) or (RetVal = ERROR_INTERNET_SEC_CERT_CN_INVALID) or (RetVal = ERROR_INTERNET_SEC_CERT_DATE_INVALID) or (RetVal = ERROR_INTERNET_SEC_CERT_REV_FAILED) then begin RetVal := AllowInvalidCA(Result, RetVal); bRes := True; end else if (RetVal = ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED) then begin { you are accessing Secure Socket Layer (SSL)-protected resource on a Web server that requires a valid client certificate. } RetVal := CheckInetError(Result, RetVal); bRes := True; end else if (RetVal = HTTP_STATUS_DENIED) or (RetVal = HTTP_STATUS_PAYMENT_REQ) then begin RetVal := CheckInetError(Result, RetVal); bRes := True; end; end; end else begin RetVal := CheckInetError(Result, GetLastError); end; Check(not bRes); case RetVal of ERROR_SUCCESS: begin SetLastError(ERROR_SUCCESS); Break; end; ERROR_CANCELLED: SysUtils.Abort; ERROR_INTERNET_FORCE_RETRY: {Retry the operation}; end; end; finally if lDataStream <> iData then lDataStream.Free; end; TriggerProgress(ptDone, pdSending, 0, iData.Size); end; procedure TROWinInetHTTPChannel.ReceiveData(iRequest:hInternet; ioData:TStream); const MaxStatusText : Integer = 4096; var Size, Status, Len, Index: DWord; S:string; lpszData: PChar; // buffer for the data dwSize: DWORD; // size of the data available dwDownloaded: DWORD; // size of the downloaded data lTotalSize,lReceivedSize:dword; begin Len := SizeOf(Status); Index := 0; { Get Status Code } if not HttpQueryInfo(iRequest, HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER, @Status, Len, Index) then RaiseLastOSError(); { Throw Exception is StatusCode >= 300 BUT not 500. SOAP faulty envelopes comes with that set } if (Status >= 300) and (Status <> 500) then begin Index := 0; Size := MaxStatusText; SetLength(S, Size); if HttpQueryInfo(iRequest, HTTP_QUERY_STATUS_TEXT, @S[1], Size, Index) then begin SetLength(S, Size); raise EROException.CreateFmt('%s (%d)', [S, Status]); end; end; Index := 0; { get total size } if not HttpQueryInfo(iRequest, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER, @lTotalSize, Len, Index) then begin { do not raise an exception when there is no "Content-Length" value in result header } if GetLastError <> ERROR_HTTP_HEADER_NOT_FOUND then begin RaiseLastOSError(); //TotalSize = 0;// end else begin SetLastError(0); lTotalSize := 0; end; end; TriggerProgress(ptStart, pdReceiving, 0, lTotalSize); lReceivedSize := 0; dwSize := 0; dwDownloaded := 0; while (true) do begin // The call to InternetQueryDataAvailable determines the amount of data available to download. while (not InternetQueryDataAvailable(iRequest, dwSize, 0, 0)) and (GetLastError = ERROR_IO_PENDING) do begin Sleep(1); end; // Allocates a buffer of the size returned by InternetQueryDataAvailable GetMem(lpszData, dwSize + 1); try // Reads the data from the HINTERNET handle. while (not InternetReadFile(iRequest, lpszData, dwSize, dwDownloaded)) and (GetLastError = ERROR_IO_PENDING) do begin Sleep(1); end; if (GetLastError >= const_WinInetErrorCode_MIN) and (GetLastError <= const_WinInetErrorCode_MAX) then begin Check(True); end; if GetLastError <> 0 then begin // Adds a null terminator to the end of the data buffer lpszData[dwDownloaded] := #0; end; ioData.Write(PChar(lpszData)^, dwDownloaded); Inc(lReceivedSize, dwDownloaded); finally FreeMem(lpszData); end; TriggerProgress(ptInProgress, pdReceiving, lReceivedSize, lTotalSize); // Check the size of the remaining data. If it is zero, break. if (dwDownloaded = 0) then begin break; end; end; ioData.seek(0, soFromBeginning); TriggerProgress(ptDone, pdReceiving, 0,0); end; function TROWinInetHTTPChannel.AllowInvalidCA(InetConnect: HINTERNET; dwInetStatus: DWord): DWord; var dwError: integer; lppvData: pointer; SECURITY_FLAGS: integer; lpszDataSize: cardinal; CertRevocation: boolean; BRes: Boolean; begin Result := ERROR_CANCELLED; if (dwInetStatus = ERROR_INTERNET_SEC_CERT_ERRORS) or (dwInetStatus = ERROR_INTERNET_INVALID_CA) or (dwInetStatus = ERROR_INTERNET_SEC_CERT_CN_INVALID) or (dwInetStatus = ERROR_INTERNET_SEC_CERT_DATE_INVALID) or (dwInetStatus = ERROR_INTERNET_SEC_CERT_REV_FAILED) then begin CertRevocation := (dwInetStatus = ERROR_INTERNET_SEC_CERT_REV_FAILED); if CertRevocation or not fTrustInvalidCA then begin dwError := InternetErrorDlg(GetDesktopWindow(), InetConnect, dwInetStatus, FLAGS_ERROR_UI_FILTER_FOR_ERRORS or FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS or FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, lppvData); bRes := (dwError = ERROR_INTERNET_FORCE_RETRY) or (dwError = ERROR_SUCCESS); end else begin bRes := True; end; if bRes then begin try InternetQueryOption(InetConnect, INTERNET_OPTION_SECURITY_FLAGS, @SECURITY_FLAGS, lpszDataSize); if CertRevocation then begin SECURITY_FLAGS := SECURITY_FLAGS or SECURITY_FLAG_IGNORE_REVOCATION; end else begin SECURITY_FLAGS := SECURITY_FLAGS or SECURITY_FLAG_IGNORE_CERT_CN_INVALID or SECURITY_FLAG_IGNORE_CERT_DATE_INVALID or SECURITY_FLAG_IGNORE_UNKNOWN_CA; end; InternetSetOption(InetConnect, INTERNET_OPTION_SECURITY_FLAGS, @SECURITY_FLAGS, lpszDataSize); except dwError := InternetErrorDlg(GetDesktopWindow(), InetConnect, dwInetStatus, FLAGS_ERROR_UI_FILTER_FOR_ERRORS or FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS or FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, lppvData); bRes := (dwError = ERROR_INTERNET_FORCE_RETRY) or (dwError = ERROR_SUCCESS); end; end; if not CertRevocation then begin FTrustInvalidCA := bRes; end; if bRes then begin Result := ERROR_INTERNET_FORCE_RETRY; end; end; end; function TROWinInetHTTPChannel.CheckInetError(InetConnect: HINTERNET; dwInetStatus: DWord): DWord; var lppvData: pointer; begin Result := InternetErrorDlg(GetDesktopWindow(), InetConnect, dwInetStatus, FLAGS_ERROR_UI_FILTER_FOR_ERRORS or FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS or FLAGS_ERROR_UI_FLAGS_GENERATE_DATA, lppvData); end; procedure TROWinInetHTTPChannel.TriggerAfterConnect; begin if Assigned(AfterConnect) then AfterConnect(self); end; function InternetInitializeAutoProxyDll(dwReserved: DWord): LongBool; stdcall; external 'WinInet.dll'; var AutoProxyCalled: Boolean = False; procedure TROWinInetHTTPChannel.SetConnected(iValue: boolean); var lAccessType:Integer; lAttemptConnect: DWORD; begin if iValue and not GetConnected then begin {if Length(Proxy) > 0 then AccessType := INTERNET_OPEN_TYPE_PROXY else} lAccessType := INTERNET_OPEN_TYPE_PRECONFIG; if fInitializeAutoProxy then begin if not AutoProxyCalled then begin InternetInitializeAutoProxyDll(0); AutoProxyCalled := true; end; end; fInetRoot := InternetOpen(PChar(UserAgent), lAccessType, nil, nil, 0);//PChar(Proxy)}, PChar(ProxyByPass), 0); if fAttemptToConnect then begin lAttemptConnect := InternetAttemptConnect(0); if assigned(FOnAfterAttemptConnect) then FOnAfterAttemptConnect(self, lAttemptConnect); if lAttemptConnect <> ERROR_SUCCESS then SysUtils.Abort; end; Check(not Assigned(FInetRoot)); try FInetConnect := InternetConnect(FInetRoot, PChar(FURLHost), FURLPort, PChar(Login.UserName), PChar(Login.Password), INTERNET_SERVICE_HTTP, 0, Cardinal(Self)); Check(not Assigned(FInetConnect)); TriggerAfterConnect(); except InternetCloseHandle(FInetRoot); FInetRoot := nil; raise; end; end else if not iValue then begin if Assigned(FInetConnect) then InternetCloseHandle(FInetConnect); FInetConnect := nil; if Assigned(FInetRoot) then InternetCloseHandle(FInetRoot); FInetRoot := nil; end; end; procedure TROWinINetHTTPChannel.SetTargetURL(const Value: string); var URLComp:TURLComponents; p:PChar; begin if Targeturl <> Value then begin fTargetUrl := Value; if fTargetUrl <> '' then begin FillChar(URLComp, SizeOf(URLComp), 0); URLComp.dwStructSize := SizeOf(URLComp); URLComp.dwSchemeLength := 1; URLComp.dwHostNameLength := 1; URLComp.dwURLPathLength := 1; InternetCrackUrl(PChar(fTargetUrl), 0, 0, URLComp); if not (URLComp.nScheme in [INTERNET_SCHEME_HTTP, INTERNET_SCHEME_HTTPS]) then raise EROException.Create(sxInvalidURLformat); FURLScheme := URLComp.nScheme; FURLPort := URLComp.nPort; p := PChar(fTargetUrl); FURLHost := Copy(fTargetUrl, URLComp.lpszHostName - p + 1, URLComp.dwHostNameLength); FURLSite := Copy(fTargetUrl, URLComp.lpszUrlPath - p + 1, URLComp.dwURLPathLength); end else begin fURLPort := 0; fURLHost := ''; fURLSite := ''; fURLScheme := 0; end; end; end; function TROWinInetHTTPChannel.GetTransportObject: TObject; begin result := self; end; procedure TROWinInetHTTPChannel.IntSetServerLocator(aServerLocator: TROServerLocator); begin TargetURL := aServerLocator.Host; end; function TROWinInetHTTPChannel.GetQueryString: String; begin result := '' end; procedure TROWinInetHTTPChannel.SetPathInfo(const aValue: String); begin // do nothing end; procedure TROWinInetHTTPChannel.CheckProperties; begin uROClasses.Check(TargetURL = '', Name + '.TargetURL must be set.'); inherited; end; procedure TROWinInetHTTPChannel.Assign(aSource: TPersistent); var lSource: TROWinInetHTTPChannel; begin inherited; if (aSource is TROWinInetHTTPChannel) then begin lSource := TROWinInetHTTPChannel(aSource); UserAgent := lSource.UserAgent; TargetURL := lSource.TargetURL; Login.Assign(lSource.Login); StoreConnected := lSource.StoreConnected; Connected := lSource.Connected; KeepConnection := lSource.KeepConnection; AfterConnect := lSource.AfterConnect; OnProgress := lSource.OnProgress; end; end; { TROLogin } procedure TROLogin.Assign(Source: TPersistent); begin if (Source is TROLogin) then begin Username := TROLogin(Source).Username; Password := TROLogin(Source).Password; end else inherited; end; initialization RegisterTransportChannelClass(TROWinInetHTTPChannel); finalization UnRegisterTransportChannelClass(TROWinInetHTTPChannel); end.