From 4378ff7957521268d05213e313a544e2a87d86cf Mon Sep 17 00:00:00 2001 From: arvanus Date: Wed, 7 Mar 2018 23:47:25 -0300 Subject: [PATCH 01/10] new WebSocket client implementation Creates an simple WebSocket implementation, initially only supporting text traffic, hope it helps others too As I couldn't test bigger than 65536 chars message, I blocked it, sorry! Please, send me suggestions of what to change, as this is just the very first version --- Lib/Core/IdWebSocketSimpleClient.pas.pas | 506 +++++++++++++++++++++++ 1 file changed, 506 insertions(+) create mode 100644 Lib/Core/IdWebSocketSimpleClient.pas.pas diff --git a/Lib/Core/IdWebSocketSimpleClient.pas.pas b/Lib/Core/IdWebSocketSimpleClient.pas.pas new file mode 100644 index 000000000..0f80b038b --- /dev/null +++ b/Lib/Core/IdWebSocketSimpleClient.pas.pas @@ -0,0 +1,506 @@ +{ + * Simple WebSocket client for Delphi + * http://www.websocket.org/echo.html + * Author: Lucas Rubian Schatz + * Copyright 2018, Indy Working Group. + * + * Date: 22/02/2018 + + TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support +} +{ +Sample code: +//var lSWC:TIdSimpleWebSocketClient; +... +begin + lSWC := TIdSimpleWebSocketClient.Create(self); + lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent + lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl + if not lSWC.AutoCreateHandler then + begin + if lSWC.IOHandler=nil then + lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; + end; + lSWC.Connect('wss://echo.websocket.org'); + lSWC.writeText('!!It worked!!'); +end; + +} + +unit IdWebSocketSimpleClient; + +interface + +uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, + IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; +Type + TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; + TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; + + TIdSimpleWebSocketClient = class(TIdTCPClient) + private + ExpectedSec_WebSocket_Accept: string; + FHeartBeatInterval: Cardinal; + FAutoCreateHandler: Boolean; + FURL: String; + function generateWebSocketKey():String; + protected + lInternalLock:TCriticalSection; + //get if a particular bit is 1 + function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; + //set a particular bit as 1 + function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + + function readFromSocket:boolean;virtual; + function encodeFrame(pMsg:String; pPong:Boolean=false):TIdBytes; + function encodePong:TidBytes; + function verifyHeader(pHeader:TStrings):boolean; + procedure startHeartBeat; + + published + public + onDataEvent:TSWSCDataEvent; + onConnectionDataEvent:TSWSCDataEvent; + onPing:TNotifyEvent; + onError:TSWSCErrorEvent; + onHeartBeatTimer:TNotifyEvent; + function Connected: Boolean; overload; + procedure Close; + property URL: String read FURL write FURL; + property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; + property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; + procedure writeText(pMsg:String); + constructor Create(AOwner: TComponent); + destructor Destroy; override; + + procedure Connect(pURL:String);overload; + +end; + +implementation + +{ TIdSimpleWebSocketClient } + +procedure TIdSimpleWebSocketClient.Close; +begin + self.lInternalLock.Enter; + try + if self.Connected then + begin + self.IOHandler.InputBuffer.Clear; + self.IOHandler.CloseGracefully; + self.Disconnect; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + finally + self.lInternalLock.Leave; + end +end; + +function TIdSimpleWebSocketClient.generateWebSocketKey():String; +var rand:TidBytes; + I: Integer; +begin + SetLength(rand, 16); + for I := low(rand) to High(rand) do + rand[i] := byte(random(255)); + + result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String + self.ExpectedSec_WebSocket_Accept := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 + + with TIdHashSHA1.Create do + try + + ExpectedSec_WebSocket_Accept := TIdEncoderMIME.EncodeBytes(HashString( self.ExpectedSec_WebSocket_Accept )); + finally + Free; + end; +end; + +function TIdSimpleWebSocketClient.Connected: Boolean; +begin + result := false; //for some reason, if its not connected raises an error after connection lost! + try + result := inherited; + except + end +end; + +procedure TIdSimpleWebSocketClient.Connect(pURL: String); +var URI : TIdURI; + lSecure : Boolean; +begin + try + URI := TIdURI.Create(pURL); + self.URL := pURL; + self.Host := URI.Host; + URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet + + if URI.Path='' then + URI.Path := '/'; + lSecure := uri.Protocol='https'; + + if URI.Port.IsEmpty then + begin + if lSecure then + self.Port := 443 + else + self.Port := 80; + end + else + self.Port := StrToInt(URI.Port); + + + if lSecure and (self.IOHandler=nil) then + begin + if self.AutoCreateHandler then //for simple life + begin + self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + //(self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; + end + else + raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); + end; + + if self.Connected then + raise Exception.Create('Already connected, verify'); + + + inherited Connect; + + self.Socket.WriteLn(format('GET %s HTTP/1.1', [URI.Path])); + self.Socket.WriteLn(format('Host: %s', [URI.Host])); + self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); +// self.Socket.WriteLn('Accept-Encoding: gzip, deflate'); + self.Socket.WriteLn('Connection: Upgrade'); + self.Socket.WriteLn('Upgrade: WebSocket'); + self.Socket.WriteLn('Sec-WebSocket-Version: 13'); + self.Socket.WriteLn('Sec-WebSocket-Protocol: chat'); + self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); + self.Socket.WriteLn(''); + + readFromSocket; + startHeartBeat; + finally + URI.Free; + end; +end; + +constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); +begin + inherited; + lInternalLock := TCriticalSection.Create; + Randomize; + self.AutoCreateHandler := false; + self.HeartBeatInterval := 30000; +end; + +destructor TIdSimpleWebSocketClient.Destroy; +begin + lInternalLock.Free; + if self.AutoCreateHandler and Assigned(self.IOHandler) then + self.IOHandler.Free; + inherited; +end; + +function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pPong:Boolean): TIdBytes; +var FIN, MASK: Cardinal; + MaskingKey:array[0..3] of cardinal; + EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; + buffer:Tidbytes; + I: Integer; + xor1, xor2:char ; + ExtendedPayloadLength:Integer; +begin + FIN:=0; + FIN := Set_a_bit(FIN,7); + if pPong then + begin + FIN := Set_a_bit(FIN,3);//Ping= 10001001 + FIN := Set_a_bit(FIN,1);//Pong= 10001010 + end + else + FIN := Set_a_bit(FIN,0); + + MASK := set_a_bit(0,7); + + ExtendedPayloadLength:= 0; + if pMsg.Length<=125 then + MASK := MASK+pMsg.Length + else + if pMsg.Length 0; +end; + +function TIdSimpleWebSocketClient.readFromSocket:Boolean; +var + s: string; + b:Byte; + T: ITask; + posicao:Integer; + size,sizemsg:Integer; + pingByte:Byte; + masked:boolean; + upgraded:Boolean; + forceDisconnect:Boolean; + lHeader:TStringlist; +begin + s := ''; + posicao := 0; + size := 0; + masked := false; + upgraded := false; + pingByte := Set_a_Bit(0,7); //1000100//PingByte + pingByte := Set_a_Bit(pingByte,3); + pingByte := Set_a_Bit(pingByte,0); + lHeader := TStringList.Create; + result := false; + try + while Connected and not upgraded do //First, we guarantee that this is an valid Websocket + begin + b := self.Socket.ReadByte; + + s := s+chr(b); + if (not upgraded and (b=ord(#13))) then + begin + if s=#10#13 then + begin + + //verifies header + if not verifyHeader(lHeader) then + begin + raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); + end; + + upgraded := true; + s := ''; + posicao := 0; + sizeMsg := 0; + end + else + begin + if assigned(onConnectionDataEvent) then + onConnectionDataEvent(self, s); + + lHeader.Add(s.Trim); + s := ''; + end; + end; + end; + except + on e:Exception do + begin + forceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, forceDisconnect); + if forceDisconnect then + self.Close; + exit; + end; + end; + + + if Connected then + T := TTask.Run( + procedure + begin + + try + while Connected do + begin + + b := self.Socket.ReadByte; + + + if upgraded and (posicao=0) and Get_a_Bit(b, 7) then //FIN + begin + if b=pingByte then + begin + b := self.Socket.ReadByte; + self.Socket.WriteDirect(encodePong); + if assigned(onPing) then + onPing(self); + + end + else + inc(posicao); + end + else if upgraded and (posicao=1) then + begin + masked := Get_a_Bit(b, 7); + size := b; + if masked then + size := b-set_a_bit(0,7); + sizeMsg := 0; + if size=0 then + posicao := 0 + else + if size=126 then // get size from 2 next bytes + begin + b := self.Socket.ReadByte; + size := Round(b*intpower(2,8)); + b := self.Socket.ReadByte; + size := size+Round(b*intpower(2,0)); + end + else if size=127 then + raise Exception.Create('Size block bigger than supported by this framework, fix is welcome'); + + inc(posicao); + end + else + begin + if upgraded then + begin + inc(sizeMsg); + if sizemsg=size then + posicao:=0; + end; + + s := s+chr(b); + if (upgraded and (sizemsg=size)) then + begin + posicao := 0; + sizeMsg := 0; + if upgraded and assigned(onDataEvent) then + onDataEvent(self, s); + + s := ''; + + end; + end; + end; + except + on e:Exception do + begin + forceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, forceDisconnect); + if forceDisconnect then + self.Close; + end; + end; + end); + + if not Connected or not upgraded then + raise Exception.Create('Websocket not connected or timeout'); +end; + +procedure TIdSimpleWebSocketClient.startHeartBeat; +var TimeUltimaNotif:TDateTime; + forceDisconnect:Boolean; +begin + TThread.CreateAnonymousThread(procedure begin + TimeUltimaNotif := Now; + try + while (self.Connected) and (self.HeartBeatInterval>0) do + begin + //HeartBeat: + if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then + begin + if assigned(self.onHeartBeatTimer) then + self.onHeartBeatTimer(self); + TimeUltimaNotif := Now; + end; + TThread.Sleep(500); + end; + except + on e:Exception do + begin + forceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, forceDisconnect); + if forceDisconnect then + self.Close; + end; + end; + + end).Start; +end; + +function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; +begin + pHeader.NameValueSeparator := ':'; + result := false; + if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then + begin + if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.ExpectedSec_WebSocket_Accept then + result := true + else + raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); + + end; +end; + +function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue or (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.writeText(pMsg: String); +begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(pMSG)); + finally + lInternalLock.Leave; + end; +end; + +end. From a62d15fe43717ce6f1dbf9a4d38f24d63488a667 Mon Sep 17 00:00:00 2001 From: arvanus Date: Sat, 2 Jun 2018 15:27:45 -0300 Subject: [PATCH 02/10] Fix file name IdWebSocketSimpleClient.pas --- ...dWebSocketSimpleClient.pas.pas => IdWebSocketSimpleClient.pas} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Lib/Core/{IdWebSocketSimpleClient.pas.pas => IdWebSocketSimpleClient.pas} (100%) diff --git a/Lib/Core/IdWebSocketSimpleClient.pas.pas b/Lib/Core/IdWebSocketSimpleClient.pas similarity index 100% rename from Lib/Core/IdWebSocketSimpleClient.pas.pas rename to Lib/Core/IdWebSocketSimpleClient.pas From 74f338ca094ace789a0179af9864744f2dbae438 Mon Sep 17 00:00:00 2001 From: arvanus Date: Sat, 2 Jun 2018 18:08:02 -0300 Subject: [PATCH 03/10] Fix ping/pong protocol Minor fix for memory lead, and minor code change for OpCode --- Lib/Core/IdWebSocketSimpleClient.pas | 344 +++++++++++++++++---------- 1 file changed, 224 insertions(+), 120 deletions(-) diff --git a/Lib/Core/IdWebSocketSimpleClient.pas b/Lib/Core/IdWebSocketSimpleClient.pas index 0f80b038b..7536fe12a 100644 --- a/Lib/Core/IdWebSocketSimpleClient.pas +++ b/Lib/Core/IdWebSocketSimpleClient.pas @@ -38,57 +38,101 @@ interface Type TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; - +// * %x0 denotes a continuation frame +// * %x1 denotes a text frame +// * %x2 denotes a binary frame +// * %x3-7 are reserved for further non-control frames +// * %x8 denotes a connection close +// * %x9 denotes a ping +// * %xA denotes a pong +// * %xB-F are reserved for further control frames + + TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); + Const + TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); + + Type TIdSimpleWebSocketClient = class(TIdTCPClient) private - ExpectedSec_WebSocket_Accept: string; + SecWebSocketAcceptExpectedResponse: string; FHeartBeatInterval: Cardinal; FAutoCreateHandler: Boolean; FURL: String; - function generateWebSocketKey():String; + FOnUpgrade: TnotifyEvent; + FonHeartBeatTimer: TNotifyEvent; + FonError: TSWSCErrorEvent; + FonPing: TSWSCDataEvent; + FonConnectionDataEvent: TSWSCDataEvent; + FonDataEvent: TSWSCDataEvent; + FUpgraded: Boolean; + protected + lInternalLock:TCriticalSection; + lClosingEventLocalHandshake:Boolean; + //Sync Event + lSyncFunctionEvent:TSimpleEvent; + lSyncFunctionTrigger:TFunc; + //Sync Event + //get if a particular bit is 1 function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; //set a particular bit as 1 function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + //set a particular bit as 0 + function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - function readFromSocket:boolean;virtual; - function encodeFrame(pMsg:String; pPong:Boolean=false):TIdBytes; - function encodePong:TidBytes; + procedure readFromWebSocket;virtual; + function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; function verifyHeader(pHeader:TStrings):boolean; procedure startHeartBeat; + procedure sendCloseHandshake; + function generateWebSocketKey:String; + + published - public - onDataEvent:TSWSCDataEvent; - onConnectionDataEvent:TSWSCDataEvent; - onPing:TNotifyEvent; - onError:TSWSCErrorEvent; - onHeartBeatTimer:TNotifyEvent; - function Connected: Boolean; overload; - procedure Close; - property URL: String read FURL write FURL; + property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; + property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; + property onPing: TSWSCDataEvent read FonPing write FonPing; + property onError: TSWSCErrorEvent read FonError write FonError; + property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; + property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; + property URL: String read FURL write FURL; + public + + procedure Connect(pURL:String);overload; + procedure Close; + function Connected: Boolean; overload; + property Upgraded: Boolean read FUpgraded; + procedure writeText(pMsg:String); + procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); + constructor Create(AOwner: TComponent); destructor Destroy; override; - procedure Connect(pURL:String);overload; - end; implementation { TIdSimpleWebSocketClient } +function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue and not (1 shl Bit); +end; + procedure TIdSimpleWebSocketClient.Close; begin self.lInternalLock.Enter; try if self.Connected then begin + self.sendCloseHandshake; self.IOHandler.InputBuffer.Clear; self.IOHandler.CloseGracefully; self.Disconnect; @@ -109,12 +153,11 @@ function TIdSimpleWebSocketClient.generateWebSocketKey():String; rand[i] := byte(random(255)); result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String - self.ExpectedSec_WebSocket_Accept := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 + self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 with TIdHashSHA1.Create do try - - ExpectedSec_WebSocket_Accept := TIdEncoderMIME.EncodeBytes(HashString( self.ExpectedSec_WebSocket_Accept )); + SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); finally Free; end; @@ -134,6 +177,7 @@ procedure TIdSimpleWebSocketClient.Connect(pURL: String); lSecure : Boolean; begin try + lClosingEventLocalHandshake := false; URI := TIdURI.Create(pURL); self.URL := pURL; self.Host := URI.Host; @@ -160,7 +204,7 @@ procedure TIdSimpleWebSocketClient.Connect(pURL: String); begin self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - //(self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; end else raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); @@ -171,25 +215,31 @@ procedure TIdSimpleWebSocketClient.Connect(pURL: String); inherited Connect; - + if not URI.Port.IsEmpty then + URI.Host := URI.Host+':'+URI.Port; self.Socket.WriteLn(format('GET %s HTTP/1.1', [URI.Path])); self.Socket.WriteLn(format('Host: %s', [URI.Host])); self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); -// self.Socket.WriteLn('Accept-Encoding: gzip, deflate'); - self.Socket.WriteLn('Connection: Upgrade'); + self.Socket.WriteLn('Connection: keep-alive, Upgrade'); self.Socket.WriteLn('Upgrade: WebSocket'); self.Socket.WriteLn('Sec-WebSocket-Version: 13'); - self.Socket.WriteLn('Sec-WebSocket-Protocol: chat'); self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); self.Socket.WriteLn(''); - readFromSocket; + readFromWebSocket; startHeartBeat; finally URI.Free; end; end; +procedure TIdSimpleWebSocketClient.sendCloseHandshake; +begin + self.lClosingEventLocalHandshake := true; + self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); + TThread.Sleep(200); +end; + constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); begin inherited; @@ -207,7 +257,7 @@ destructor TIdSimpleWebSocketClient.Destroy; inherited; end; -function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pPong:Boolean): TIdBytes; +function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; var FIN, MASK: Cardinal; MaskingKey:array[0..3] of cardinal; EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; @@ -217,14 +267,7 @@ function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pPong:Boolean): TIdBy ExtendedPayloadLength:Integer; begin FIN:=0; - FIN := Set_a_bit(FIN,7); - if pPong then - begin - FIN := Set_a_bit(FIN,3);//Ping= 10001001 - FIN := Set_a_bit(FIN,1);//Pong= 10001010 - end - else - FIN := Set_a_bit(FIN,0); + FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; MASK := set_a_bit(0,7); @@ -277,79 +320,85 @@ function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pPong:Boolean): TIdBy result := buffer; end; -function TIdSimpleWebSocketClient.encodePong: TidBytes; -begin - result := encodeFrame('', true); -end; - function TIdSimpleWebSocketClient.Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; begin Result := (aValue and (1 shl Bit)) <> 0; end; -function TIdSimpleWebSocketClient.readFromSocket:Boolean; +procedure TIdSimpleWebSocketClient.readFromWebSocket; var - s: string; + lSpool: string; b:Byte; T: ITask; - posicao:Integer; - size,sizemsg:Integer; - pingByte:Byte; - masked:boolean; - upgraded:Boolean; - forceDisconnect:Boolean; + lPos:Integer; + lSize:Integer; + lOpCode:Byte; + linFrame:Boolean; + lMasked:boolean; + lForceDisconnect:Boolean; lHeader:TStringlist; +// lClosingRemoteHandshake:Boolean; +// lPing:Boolean; begin - s := ''; - posicao := 0; - size := 0; - masked := false; - upgraded := false; - pingByte := Set_a_Bit(0,7); //1000100//PingByte - pingByte := Set_a_Bit(pingByte,3); - pingByte := Set_a_Bit(pingByte,0); + lSpool := ''; + lPos := 0; + lSize := 0; + lOpCode := 0; + lMasked := false; + FUpgraded := false; +// lPing := false; +// pingByte := Set_a_Bit(0,7); //1001001//PingByte +// pingByte := Set_a_Bit(pingByte,3); +// pingByte := Set_a_Bit(pingByte,0); +// closeByte := Set_a_Bit(0,7);//1001000//CloseByte +// closeByte := Set_a_Bit(closeByte,3); + lHeader := TStringList.Create; - result := false; + linFrame := false; + try - while Connected and not upgraded do //First, we guarantee that this is an valid Websocket + while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket begin b := self.Socket.ReadByte; - s := s+chr(b); - if (not upgraded and (b=ord(#13))) then + lSpool := lSpool+chr(b); + if (not FUpgraded and (b=ord(#13))) then begin - if s=#10#13 then + if lSpool=#10#13 then begin //verifies header - if not verifyHeader(lHeader) then - begin + try + if not verifyHeader(lHeader) then + begin raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); + end; + finally + lHeader.Free; end; - upgraded := true; - s := ''; - posicao := 0; - sizeMsg := 0; + FUpgraded := true; + lSpool := ''; + lPos := 0; end else begin if assigned(onConnectionDataEvent) then - onConnectionDataEvent(self, s); + onConnectionDataEvent(self, lSpool); - lHeader.Add(s.Trim); - s := ''; + lHeader.Add(lSpool.Trim); + lSpool := ''; end; end; end; except on e:Exception do begin - forceDisconnect := true; + lForceDisconnect := true; if assigned(self.onError) then - self.onError(self, e, e.Message, forceDisconnect); - if forceDisconnect then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then self.Close; exit; end; @@ -368,59 +417,83 @@ function TIdSimpleWebSocketClient.readFromSocket:Boolean; b := self.Socket.ReadByte; - if upgraded and (posicao=0) and Get_a_Bit(b, 7) then //FIN + if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN begin - if b=pingByte then - begin - b := self.Socket.ReadByte; - self.Socket.WriteDirect(encodePong); - if assigned(onPing) then - onPing(self); + linFrame := true; + lOpCode := Clear_a_Bit(b, 7); - end - else - inc(posicao); + inc(lPos); + + + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then end - else if upgraded and (posicao=1) then + else if FUpgraded and (lPos=1) then begin - masked := Get_a_Bit(b, 7); - size := b; - if masked then - size := b-set_a_bit(0,7); - sizeMsg := 0; - if size=0 then - posicao := 0 + lMasked := Get_a_Bit(b, 7); + lSize := b; + if lMasked then + lSize := b-set_a_bit(0,7); + if lSize=0 then + lPos := 0 else - if size=126 then // get size from 2 next bytes + if lSize=126 then // get size from 2 next bytes begin b := self.Socket.ReadByte; - size := Round(b*intpower(2,8)); + lSize := Round(b*intpower(2,8)); b := self.Socket.ReadByte; - size := size+Round(b*intpower(2,0)); + lSize := lSize+Round(b*intpower(2,0)); end - else if size=127 then - raise Exception.Create('Size block bigger than supported by this framework, fix is welcome'); + else if lSize=127 then + raise Exception.Create('TODO: Size block bigger than supported by this framework, fix is welcome'); - inc(posicao); + inc(lPos); end else + if linFrame then begin - if upgraded then - begin - inc(sizeMsg); - if sizemsg=size then - posicao:=0; - end; + lSpool := lSpool+chr(b); - s := s+chr(b); - if (upgraded and (sizemsg=size)) then + if (FUpgraded and (Length(lSpool)=lSize)) then begin - posicao := 0; - sizeMsg := 0; - if upgraded and assigned(onDataEvent) then - onDataEvent(self, s); + lPos := 0; + linFrame := false; + + if lOpCode=TOpCodeByte[TOpCode.TOPing] then + begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); + finally + lInternalLock.Leave; + end; - s := ''; + if assigned(onPing) then + onPing(self, lSpool); + end + else + begin + if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then + onDataEvent(self, lSpool); + if assigned(self.lSyncFunctionTrigger) then + begin + if self.lSyncFunctionTrigger(lSpool) then + begin + self.lSyncFunctionEvent.SetEvent; + end; + end; + end; + + lSpool := ''; + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + begin + if not Self.lClosingEventLocalHandshake then + begin + self.Close; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + break + end; end; end; @@ -428,22 +501,29 @@ function TIdSimpleWebSocketClient.readFromSocket:Boolean; except on e:Exception do begin - forceDisconnect := true; + lForceDisconnect := true; if assigned(self.onError) then - self.onError(self, e, e.Message, forceDisconnect); - if forceDisconnect then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then self.Close; end; end; end); - if not Connected or not upgraded then - raise Exception.Create('Websocket not connected or timeout'); + if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then + begin + + raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); + end + else + if assigned(self.OnUpgrade) then + self.OnUpgrade(self); + end; procedure TIdSimpleWebSocketClient.startHeartBeat; var TimeUltimaNotif:TDateTime; - forceDisconnect:Boolean; + lForceDisconnect:Boolean; begin TThread.CreateAnonymousThread(procedure begin TimeUltimaNotif := Now; @@ -462,10 +542,10 @@ procedure TIdSimpleWebSocketClient.startHeartBeat; except on e:Exception do begin - forceDisconnect := true; + lForceDisconnect := true; if assigned(self.onError) then - self.onError(self, e, e.Message, forceDisconnect); - if forceDisconnect then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then self.Close; end; end; @@ -477,9 +557,15 @@ function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; begin pHeader.NameValueSeparator := ':'; result := false; + if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then + raise Exception.Create(pHeader[0].Substring(9)); + if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then begin - if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.ExpectedSec_WebSocket_Accept then + if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then + result := true + else + if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then result := true else raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); @@ -503,4 +589,22 @@ procedure TIdSimpleWebSocketClient.writeText(pMsg: String); end; end; +procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; + pTriggerFunction: TFunc); +begin + self.lSyncFunctionTrigger := pTriggerFunction; + try + self.lSyncFunctionEvent := TSimpleEvent.Create(); + self.lSyncFunctionEvent.ResetEvent; + self.writeText(pMsg); + self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); + + finally + self.lSyncFunctionTrigger:= nil; + self.lSyncFunctionEvent.Free; + end; + + +end; + end. From 0a67e92a40a5ae667b3c1d2df1cd811c7e1ce5cd Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Sat, 25 May 2019 23:09:53 -0500 Subject: [PATCH 04/10] Fix warning and incorrect URI in "GET" request --- Lib/Core/IdWebSocketSimpleClient.pas | 6 +- .../__history/IdWebSocketSimpleClient.pas.~1~ | 610 ++++++++++++++++++ .../__history/IdWebSocketSimpleClient.pas.~2~ | 610 ++++++++++++++++++ 3 files changed, 1223 insertions(+), 3 deletions(-) create mode 100644 Lib/Core/__history/IdWebSocketSimpleClient.pas.~1~ create mode 100644 Lib/Core/__history/IdWebSocketSimpleClient.pas.~2~ diff --git a/Lib/Core/IdWebSocketSimpleClient.pas b/Lib/Core/IdWebSocketSimpleClient.pas index 7536fe12a..9df93bfa5 100644 --- a/Lib/Core/IdWebSocketSimpleClient.pas +++ b/Lib/Core/IdWebSocketSimpleClient.pas @@ -4,8 +4,8 @@ * Author: Lucas Rubian Schatz * Copyright 2018, Indy Working Group. * + * Date: 25/05/2019 - Jason R. Nelson (adaloveless) - Fix warning and incorrect URI in "GET" request * Date: 22/02/2018 - TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support } { @@ -26,7 +26,6 @@ lSWC.Connect('wss://echo.websocket.org'); lSWC.writeText('!!It worked!!'); end; - } unit IdWebSocketSimpleClient; @@ -176,6 +175,7 @@ procedure TIdSimpleWebSocketClient.Connect(pURL: String); var URI : TIdURI; lSecure : Boolean; begin + uri := nil; try lClosingEventLocalHandshake := false; URI := TIdURI.Create(pURL); @@ -217,7 +217,7 @@ procedure TIdSimpleWebSocketClient.Connect(pURL: String); inherited Connect; if not URI.Port.IsEmpty then URI.Host := URI.Host+':'+URI.Port; - self.Socket.WriteLn(format('GET %s HTTP/1.1', [URI.Path])); + self.Socket.WriteLn(format('GET %s HTTP/1.1', [uri.path+uri.Document])); self.Socket.WriteLn(format('Host: %s', [URI.Host])); self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); self.Socket.WriteLn('Connection: keep-alive, Upgrade'); diff --git a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~1~ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~1~ new file mode 100644 index 000000000..7536fe12a --- /dev/null +++ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~1~ @@ -0,0 +1,610 @@ +{ + * Simple WebSocket client for Delphi + * http://www.websocket.org/echo.html + * Author: Lucas Rubian Schatz + * Copyright 2018, Indy Working Group. + * + * Date: 22/02/2018 + + TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support +} +{ +Sample code: +//var lSWC:TIdSimpleWebSocketClient; +... +begin + lSWC := TIdSimpleWebSocketClient.Create(self); + lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent + lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl + if not lSWC.AutoCreateHandler then + begin + if lSWC.IOHandler=nil then + lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; + end; + lSWC.Connect('wss://echo.websocket.org'); + lSWC.writeText('!!It worked!!'); +end; + +} + +unit IdWebSocketSimpleClient; + +interface + +uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, + IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; +Type + TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; + TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; +// * %x0 denotes a continuation frame +// * %x1 denotes a text frame +// * %x2 denotes a binary frame +// * %x3-7 are reserved for further non-control frames +// * %x8 denotes a connection close +// * %x9 denotes a ping +// * %xA denotes a pong +// * %xB-F are reserved for further control frames + + TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); + Const + TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); + + Type + TIdSimpleWebSocketClient = class(TIdTCPClient) + private + SecWebSocketAcceptExpectedResponse: string; + FHeartBeatInterval: Cardinal; + FAutoCreateHandler: Boolean; + FURL: String; + FOnUpgrade: TnotifyEvent; + FonHeartBeatTimer: TNotifyEvent; + FonError: TSWSCErrorEvent; + FonPing: TSWSCDataEvent; + FonConnectionDataEvent: TSWSCDataEvent; + FonDataEvent: TSWSCDataEvent; + FUpgraded: Boolean; + + protected + + lInternalLock:TCriticalSection; + lClosingEventLocalHandshake:Boolean; + //Sync Event + lSyncFunctionEvent:TSimpleEvent; + lSyncFunctionTrigger:TFunc; + //Sync Event + + //get if a particular bit is 1 + function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; + //set a particular bit as 1 + function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + //set a particular bit as 0 + function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + + procedure readFromWebSocket;virtual; + function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; + function verifyHeader(pHeader:TStrings):boolean; + procedure startHeartBeat; + procedure sendCloseHandshake; + function generateWebSocketKey:String; + + + + published + property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; + property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; + property onPing: TSWSCDataEvent read FonPing write FonPing; + property onError: TSWSCErrorEvent read FonError write FonError; + property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; + property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; + property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; + property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; + property URL: String read FURL write FURL; + public + + procedure Connect(pURL:String);overload; + procedure Close; + function Connected: Boolean; overload; + property Upgraded: Boolean read FUpgraded; + + procedure writeText(pMsg:String); + procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); + + constructor Create(AOwner: TComponent); + destructor Destroy; override; + +end; + +implementation + +{ TIdSimpleWebSocketClient } + +function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue and not (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.Close; +begin + self.lInternalLock.Enter; + try + if self.Connected then + begin + self.sendCloseHandshake; + self.IOHandler.InputBuffer.Clear; + self.IOHandler.CloseGracefully; + self.Disconnect; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + finally + self.lInternalLock.Leave; + end +end; + +function TIdSimpleWebSocketClient.generateWebSocketKey():String; +var rand:TidBytes; + I: Integer; +begin + SetLength(rand, 16); + for I := low(rand) to High(rand) do + rand[i] := byte(random(255)); + + result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String + self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 + + with TIdHashSHA1.Create do + try + SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); + finally + Free; + end; +end; + +function TIdSimpleWebSocketClient.Connected: Boolean; +begin + result := false; //for some reason, if its not connected raises an error after connection lost! + try + result := inherited; + except + end +end; + +procedure TIdSimpleWebSocketClient.Connect(pURL: String); +var URI : TIdURI; + lSecure : Boolean; +begin + try + lClosingEventLocalHandshake := false; + URI := TIdURI.Create(pURL); + self.URL := pURL; + self.Host := URI.Host; + URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet + + if URI.Path='' then + URI.Path := '/'; + lSecure := uri.Protocol='https'; + + if URI.Port.IsEmpty then + begin + if lSecure then + self.Port := 443 + else + self.Port := 80; + end + else + self.Port := StrToInt(URI.Port); + + + if lSecure and (self.IOHandler=nil) then + begin + if self.AutoCreateHandler then //for simple life + begin + self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; + end + else + raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); + end; + + if self.Connected then + raise Exception.Create('Already connected, verify'); + + + inherited Connect; + if not URI.Port.IsEmpty then + URI.Host := URI.Host+':'+URI.Port; + self.Socket.WriteLn(format('GET %s HTTP/1.1', [URI.Path])); + self.Socket.WriteLn(format('Host: %s', [URI.Host])); + self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); + self.Socket.WriteLn('Connection: keep-alive, Upgrade'); + self.Socket.WriteLn('Upgrade: WebSocket'); + self.Socket.WriteLn('Sec-WebSocket-Version: 13'); + self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); + self.Socket.WriteLn(''); + + readFromWebSocket; + startHeartBeat; + finally + URI.Free; + end; +end; + +procedure TIdSimpleWebSocketClient.sendCloseHandshake; +begin + self.lClosingEventLocalHandshake := true; + self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); + TThread.Sleep(200); +end; + +constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); +begin + inherited; + lInternalLock := TCriticalSection.Create; + Randomize; + self.AutoCreateHandler := false; + self.HeartBeatInterval := 30000; +end; + +destructor TIdSimpleWebSocketClient.Destroy; +begin + lInternalLock.Free; + if self.AutoCreateHandler and Assigned(self.IOHandler) then + self.IOHandler.Free; + inherited; +end; + +function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; +var FIN, MASK: Cardinal; + MaskingKey:array[0..3] of cardinal; + EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; + buffer:Tidbytes; + I: Integer; + xor1, xor2:char ; + ExtendedPayloadLength:Integer; +begin + FIN:=0; + FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; + + MASK := set_a_bit(0,7); + + ExtendedPayloadLength:= 0; + if pMsg.Length<=125 then + MASK := MASK+pMsg.Length + else + if pMsg.Length 0; +end; + +procedure TIdSimpleWebSocketClient.readFromWebSocket; +var + lSpool: string; + b:Byte; + T: ITask; + lPos:Integer; + lSize:Integer; + lOpCode:Byte; + linFrame:Boolean; + lMasked:boolean; + lForceDisconnect:Boolean; + lHeader:TStringlist; +// lClosingRemoteHandshake:Boolean; +// lPing:Boolean; +begin + lSpool := ''; + lPos := 0; + lSize := 0; + lOpCode := 0; + lMasked := false; + FUpgraded := false; +// lPing := false; +// pingByte := Set_a_Bit(0,7); //1001001//PingByte +// pingByte := Set_a_Bit(pingByte,3); +// pingByte := Set_a_Bit(pingByte,0); +// closeByte := Set_a_Bit(0,7);//1001000//CloseByte +// closeByte := Set_a_Bit(closeByte,3); + + lHeader := TStringList.Create; + linFrame := false; + + try + while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket + begin + b := self.Socket.ReadByte; + + lSpool := lSpool+chr(b); + if (not FUpgraded and (b=ord(#13))) then + begin + if lSpool=#10#13 then + begin + + //verifies header + try + if not verifyHeader(lHeader) then + begin + raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); + end; + finally + lHeader.Free; + end; + + FUpgraded := true; + lSpool := ''; + lPos := 0; + end + else + begin + if assigned(onConnectionDataEvent) then + onConnectionDataEvent(self, lSpool); + + lHeader.Add(lSpool.Trim); + lSpool := ''; + end; + end; + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + exit; + end; + end; + + + if Connected then + T := TTask.Run( + procedure + begin + + try + while Connected do + begin + + b := self.Socket.ReadByte; + + + if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN + begin + linFrame := true; + lOpCode := Clear_a_Bit(b, 7); + + inc(lPos); + + + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + end + else if FUpgraded and (lPos=1) then + begin + lMasked := Get_a_Bit(b, 7); + lSize := b; + if lMasked then + lSize := b-set_a_bit(0,7); + if lSize=0 then + lPos := 0 + else + if lSize=126 then // get size from 2 next bytes + begin + b := self.Socket.ReadByte; + lSize := Round(b*intpower(2,8)); + b := self.Socket.ReadByte; + lSize := lSize+Round(b*intpower(2,0)); + end + else if lSize=127 then + raise Exception.Create('TODO: Size block bigger than supported by this framework, fix is welcome'); + + inc(lPos); + end + else + if linFrame then + begin + lSpool := lSpool+chr(b); + + if (FUpgraded and (Length(lSpool)=lSize)) then + begin + lPos := 0; + linFrame := false; + + if lOpCode=TOpCodeByte[TOpCode.TOPing] then + begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); + finally + lInternalLock.Leave; + end; + + if assigned(onPing) then + onPing(self, lSpool); + end + else + begin + if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then + onDataEvent(self, lSpool); + if assigned(self.lSyncFunctionTrigger) then + begin + if self.lSyncFunctionTrigger(lSpool) then + begin + self.lSyncFunctionEvent.SetEvent; + end; + end; + end; + + lSpool := ''; + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + begin + if not Self.lClosingEventLocalHandshake then + begin + self.Close; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + break + end; + + end; + end; + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + end; + end; + end); + + if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then + begin + + raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); + end + else + if assigned(self.OnUpgrade) then + self.OnUpgrade(self); + +end; + +procedure TIdSimpleWebSocketClient.startHeartBeat; +var TimeUltimaNotif:TDateTime; + lForceDisconnect:Boolean; +begin + TThread.CreateAnonymousThread(procedure begin + TimeUltimaNotif := Now; + try + while (self.Connected) and (self.HeartBeatInterval>0) do + begin + //HeartBeat: + if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then + begin + if assigned(self.onHeartBeatTimer) then + self.onHeartBeatTimer(self); + TimeUltimaNotif := Now; + end; + TThread.Sleep(500); + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + end; + end; + + end).Start; +end; + +function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; +begin + pHeader.NameValueSeparator := ':'; + result := false; + if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then + raise Exception.Create(pHeader[0].Substring(9)); + + if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then + begin + if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then + result := true + else + if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then + result := true + else + raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); + + end; +end; + +function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue or (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.writeText(pMsg: String); +begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(pMSG)); + finally + lInternalLock.Leave; + end; +end; + +procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; + pTriggerFunction: TFunc); +begin + self.lSyncFunctionTrigger := pTriggerFunction; + try + self.lSyncFunctionEvent := TSimpleEvent.Create(); + self.lSyncFunctionEvent.ResetEvent; + self.writeText(pMsg); + self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); + + finally + self.lSyncFunctionTrigger:= nil; + self.lSyncFunctionEvent.Free; + end; + + +end; + +end. diff --git a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~2~ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~2~ new file mode 100644 index 000000000..9df93bfa5 --- /dev/null +++ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~2~ @@ -0,0 +1,610 @@ +{ + * Simple WebSocket client for Delphi + * http://www.websocket.org/echo.html + * Author: Lucas Rubian Schatz + * Copyright 2018, Indy Working Group. + * + * Date: 25/05/2019 - Jason R. Nelson (adaloveless) - Fix warning and incorrect URI in "GET" request + * Date: 22/02/2018 + TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support +} +{ +Sample code: +//var lSWC:TIdSimpleWebSocketClient; +... +begin + lSWC := TIdSimpleWebSocketClient.Create(self); + lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent + lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl + if not lSWC.AutoCreateHandler then + begin + if lSWC.IOHandler=nil then + lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; + end; + lSWC.Connect('wss://echo.websocket.org'); + lSWC.writeText('!!It worked!!'); +end; +} + +unit IdWebSocketSimpleClient; + +interface + +uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, + IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; +Type + TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; + TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; +// * %x0 denotes a continuation frame +// * %x1 denotes a text frame +// * %x2 denotes a binary frame +// * %x3-7 are reserved for further non-control frames +// * %x8 denotes a connection close +// * %x9 denotes a ping +// * %xA denotes a pong +// * %xB-F are reserved for further control frames + + TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); + Const + TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); + + Type + TIdSimpleWebSocketClient = class(TIdTCPClient) + private + SecWebSocketAcceptExpectedResponse: string; + FHeartBeatInterval: Cardinal; + FAutoCreateHandler: Boolean; + FURL: String; + FOnUpgrade: TnotifyEvent; + FonHeartBeatTimer: TNotifyEvent; + FonError: TSWSCErrorEvent; + FonPing: TSWSCDataEvent; + FonConnectionDataEvent: TSWSCDataEvent; + FonDataEvent: TSWSCDataEvent; + FUpgraded: Boolean; + + protected + + lInternalLock:TCriticalSection; + lClosingEventLocalHandshake:Boolean; + //Sync Event + lSyncFunctionEvent:TSimpleEvent; + lSyncFunctionTrigger:TFunc; + //Sync Event + + //get if a particular bit is 1 + function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; + //set a particular bit as 1 + function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + //set a particular bit as 0 + function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + + procedure readFromWebSocket;virtual; + function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; + function verifyHeader(pHeader:TStrings):boolean; + procedure startHeartBeat; + procedure sendCloseHandshake; + function generateWebSocketKey:String; + + + + published + property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; + property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; + property onPing: TSWSCDataEvent read FonPing write FonPing; + property onError: TSWSCErrorEvent read FonError write FonError; + property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; + property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; + property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; + property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; + property URL: String read FURL write FURL; + public + + procedure Connect(pURL:String);overload; + procedure Close; + function Connected: Boolean; overload; + property Upgraded: Boolean read FUpgraded; + + procedure writeText(pMsg:String); + procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); + + constructor Create(AOwner: TComponent); + destructor Destroy; override; + +end; + +implementation + +{ TIdSimpleWebSocketClient } + +function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue and not (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.Close; +begin + self.lInternalLock.Enter; + try + if self.Connected then + begin + self.sendCloseHandshake; + self.IOHandler.InputBuffer.Clear; + self.IOHandler.CloseGracefully; + self.Disconnect; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + finally + self.lInternalLock.Leave; + end +end; + +function TIdSimpleWebSocketClient.generateWebSocketKey():String; +var rand:TidBytes; + I: Integer; +begin + SetLength(rand, 16); + for I := low(rand) to High(rand) do + rand[i] := byte(random(255)); + + result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String + self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 + + with TIdHashSHA1.Create do + try + SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); + finally + Free; + end; +end; + +function TIdSimpleWebSocketClient.Connected: Boolean; +begin + result := false; //for some reason, if its not connected raises an error after connection lost! + try + result := inherited; + except + end +end; + +procedure TIdSimpleWebSocketClient.Connect(pURL: String); +var URI : TIdURI; + lSecure : Boolean; +begin + uri := nil; + try + lClosingEventLocalHandshake := false; + URI := TIdURI.Create(pURL); + self.URL := pURL; + self.Host := URI.Host; + URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet + + if URI.Path='' then + URI.Path := '/'; + lSecure := uri.Protocol='https'; + + if URI.Port.IsEmpty then + begin + if lSecure then + self.Port := 443 + else + self.Port := 80; + end + else + self.Port := StrToInt(URI.Port); + + + if lSecure and (self.IOHandler=nil) then + begin + if self.AutoCreateHandler then //for simple life + begin + self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; + end + else + raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); + end; + + if self.Connected then + raise Exception.Create('Already connected, verify'); + + + inherited Connect; + if not URI.Port.IsEmpty then + URI.Host := URI.Host+':'+URI.Port; + self.Socket.WriteLn(format('GET %s HTTP/1.1', [uri.path+uri.Document])); + self.Socket.WriteLn(format('Host: %s', [URI.Host])); + self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); + self.Socket.WriteLn('Connection: keep-alive, Upgrade'); + self.Socket.WriteLn('Upgrade: WebSocket'); + self.Socket.WriteLn('Sec-WebSocket-Version: 13'); + self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); + self.Socket.WriteLn(''); + + readFromWebSocket; + startHeartBeat; + finally + URI.Free; + end; +end; + +procedure TIdSimpleWebSocketClient.sendCloseHandshake; +begin + self.lClosingEventLocalHandshake := true; + self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); + TThread.Sleep(200); +end; + +constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); +begin + inherited; + lInternalLock := TCriticalSection.Create; + Randomize; + self.AutoCreateHandler := false; + self.HeartBeatInterval := 30000; +end; + +destructor TIdSimpleWebSocketClient.Destroy; +begin + lInternalLock.Free; + if self.AutoCreateHandler and Assigned(self.IOHandler) then + self.IOHandler.Free; + inherited; +end; + +function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; +var FIN, MASK: Cardinal; + MaskingKey:array[0..3] of cardinal; + EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; + buffer:Tidbytes; + I: Integer; + xor1, xor2:char ; + ExtendedPayloadLength:Integer; +begin + FIN:=0; + FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; + + MASK := set_a_bit(0,7); + + ExtendedPayloadLength:= 0; + if pMsg.Length<=125 then + MASK := MASK+pMsg.Length + else + if pMsg.Length 0; +end; + +procedure TIdSimpleWebSocketClient.readFromWebSocket; +var + lSpool: string; + b:Byte; + T: ITask; + lPos:Integer; + lSize:Integer; + lOpCode:Byte; + linFrame:Boolean; + lMasked:boolean; + lForceDisconnect:Boolean; + lHeader:TStringlist; +// lClosingRemoteHandshake:Boolean; +// lPing:Boolean; +begin + lSpool := ''; + lPos := 0; + lSize := 0; + lOpCode := 0; + lMasked := false; + FUpgraded := false; +// lPing := false; +// pingByte := Set_a_Bit(0,7); //1001001//PingByte +// pingByte := Set_a_Bit(pingByte,3); +// pingByte := Set_a_Bit(pingByte,0); +// closeByte := Set_a_Bit(0,7);//1001000//CloseByte +// closeByte := Set_a_Bit(closeByte,3); + + lHeader := TStringList.Create; + linFrame := false; + + try + while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket + begin + b := self.Socket.ReadByte; + + lSpool := lSpool+chr(b); + if (not FUpgraded and (b=ord(#13))) then + begin + if lSpool=#10#13 then + begin + + //verifies header + try + if not verifyHeader(lHeader) then + begin + raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); + end; + finally + lHeader.Free; + end; + + FUpgraded := true; + lSpool := ''; + lPos := 0; + end + else + begin + if assigned(onConnectionDataEvent) then + onConnectionDataEvent(self, lSpool); + + lHeader.Add(lSpool.Trim); + lSpool := ''; + end; + end; + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + exit; + end; + end; + + + if Connected then + T := TTask.Run( + procedure + begin + + try + while Connected do + begin + + b := self.Socket.ReadByte; + + + if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN + begin + linFrame := true; + lOpCode := Clear_a_Bit(b, 7); + + inc(lPos); + + + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + end + else if FUpgraded and (lPos=1) then + begin + lMasked := Get_a_Bit(b, 7); + lSize := b; + if lMasked then + lSize := b-set_a_bit(0,7); + if lSize=0 then + lPos := 0 + else + if lSize=126 then // get size from 2 next bytes + begin + b := self.Socket.ReadByte; + lSize := Round(b*intpower(2,8)); + b := self.Socket.ReadByte; + lSize := lSize+Round(b*intpower(2,0)); + end + else if lSize=127 then + raise Exception.Create('TODO: Size block bigger than supported by this framework, fix is welcome'); + + inc(lPos); + end + else + if linFrame then + begin + lSpool := lSpool+chr(b); + + if (FUpgraded and (Length(lSpool)=lSize)) then + begin + lPos := 0; + linFrame := false; + + if lOpCode=TOpCodeByte[TOpCode.TOPing] then + begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); + finally + lInternalLock.Leave; + end; + + if assigned(onPing) then + onPing(self, lSpool); + end + else + begin + if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then + onDataEvent(self, lSpool); + if assigned(self.lSyncFunctionTrigger) then + begin + if self.lSyncFunctionTrigger(lSpool) then + begin + self.lSyncFunctionEvent.SetEvent; + end; + end; + end; + + lSpool := ''; + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + begin + if not Self.lClosingEventLocalHandshake then + begin + self.Close; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + break + end; + + end; + end; + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + end; + end; + end); + + if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then + begin + + raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); + end + else + if assigned(self.OnUpgrade) then + self.OnUpgrade(self); + +end; + +procedure TIdSimpleWebSocketClient.startHeartBeat; +var TimeUltimaNotif:TDateTime; + lForceDisconnect:Boolean; +begin + TThread.CreateAnonymousThread(procedure begin + TimeUltimaNotif := Now; + try + while (self.Connected) and (self.HeartBeatInterval>0) do + begin + //HeartBeat: + if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then + begin + if assigned(self.onHeartBeatTimer) then + self.onHeartBeatTimer(self); + TimeUltimaNotif := Now; + end; + TThread.Sleep(500); + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + end; + end; + + end).Start; +end; + +function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; +begin + pHeader.NameValueSeparator := ':'; + result := false; + if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then + raise Exception.Create(pHeader[0].Substring(9)); + + if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then + begin + if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then + result := true + else + if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then + result := true + else + raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); + + end; +end; + +function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue or (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.writeText(pMsg: String); +begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(pMSG)); + finally + lInternalLock.Leave; + end; +end; + +procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; + pTriggerFunction: TFunc); +begin + self.lSyncFunctionTrigger := pTriggerFunction; + try + self.lSyncFunctionEvent := TSimpleEvent.Create(); + self.lSyncFunctionEvent.ResetEvent; + self.writeText(pMsg); + self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); + + finally + self.lSyncFunctionTrigger:= nil; + self.lSyncFunctionEvent.Free; + end; + + +end; + +end. From a0fe64c356f4422fd8c3d2ba3e5e62e9a650ec02 Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Sat, 25 May 2019 23:53:35 -0500 Subject: [PATCH 05/10] Fix extended_payload_length decoding --- Lib/Core/IdWebSocketSimpleClient.pas | 14 +- .../__history/IdWebSocketSimpleClient.pas.~3~ | 610 +++++++++++++++++ .../__history/IdWebSocketSimpleClient.pas.~4~ | 611 ++++++++++++++++++ 3 files changed, 1228 insertions(+), 7 deletions(-) create mode 100644 Lib/Core/__history/IdWebSocketSimpleClient.pas.~3~ create mode 100644 Lib/Core/__history/IdWebSocketSimpleClient.pas.~4~ diff --git a/Lib/Core/IdWebSocketSimpleClient.pas b/Lib/Core/IdWebSocketSimpleClient.pas index 9df93bfa5..725487b9d 100644 --- a/Lib/Core/IdWebSocketSimpleClient.pas +++ b/Lib/Core/IdWebSocketSimpleClient.pas @@ -332,7 +332,7 @@ procedure TIdSimpleWebSocketClient.readFromWebSocket; b:Byte; T: ITask; lPos:Integer; - lSize:Integer; + lSize:int64; lOpCode:Byte; linFrame:Boolean; lMasked:boolean; @@ -408,6 +408,8 @@ procedure TIdSimpleWebSocketClient.readFromWebSocket; if Connected then T := TTask.Run( procedure + var + extended_payload_length: cardinal; begin try @@ -438,13 +440,11 @@ procedure TIdSimpleWebSocketClient.readFromWebSocket; else if lSize=126 then // get size from 2 next bytes begin - b := self.Socket.ReadByte; - lSize := Round(b*intpower(2,8)); - b := self.Socket.ReadByte; - lSize := lSize+Round(b*intpower(2,0)); + lsize := self.socket.ReadUInt16; end - else if lSize=127 then - raise Exception.Create('TODO: Size block bigger than supported by this framework, fix is welcome'); + else if lSize=127 then begin + lsize := self.socket.ReadUInt64; + end; inc(lPos); end diff --git a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~3~ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~3~ new file mode 100644 index 000000000..9df93bfa5 --- /dev/null +++ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~3~ @@ -0,0 +1,610 @@ +{ + * Simple WebSocket client for Delphi + * http://www.websocket.org/echo.html + * Author: Lucas Rubian Schatz + * Copyright 2018, Indy Working Group. + * + * Date: 25/05/2019 - Jason R. Nelson (adaloveless) - Fix warning and incorrect URI in "GET" request + * Date: 22/02/2018 + TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support +} +{ +Sample code: +//var lSWC:TIdSimpleWebSocketClient; +... +begin + lSWC := TIdSimpleWebSocketClient.Create(self); + lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent + lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl + if not lSWC.AutoCreateHandler then + begin + if lSWC.IOHandler=nil then + lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; + end; + lSWC.Connect('wss://echo.websocket.org'); + lSWC.writeText('!!It worked!!'); +end; +} + +unit IdWebSocketSimpleClient; + +interface + +uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, + IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; +Type + TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; + TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; +// * %x0 denotes a continuation frame +// * %x1 denotes a text frame +// * %x2 denotes a binary frame +// * %x3-7 are reserved for further non-control frames +// * %x8 denotes a connection close +// * %x9 denotes a ping +// * %xA denotes a pong +// * %xB-F are reserved for further control frames + + TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); + Const + TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); + + Type + TIdSimpleWebSocketClient = class(TIdTCPClient) + private + SecWebSocketAcceptExpectedResponse: string; + FHeartBeatInterval: Cardinal; + FAutoCreateHandler: Boolean; + FURL: String; + FOnUpgrade: TnotifyEvent; + FonHeartBeatTimer: TNotifyEvent; + FonError: TSWSCErrorEvent; + FonPing: TSWSCDataEvent; + FonConnectionDataEvent: TSWSCDataEvent; + FonDataEvent: TSWSCDataEvent; + FUpgraded: Boolean; + + protected + + lInternalLock:TCriticalSection; + lClosingEventLocalHandshake:Boolean; + //Sync Event + lSyncFunctionEvent:TSimpleEvent; + lSyncFunctionTrigger:TFunc; + //Sync Event + + //get if a particular bit is 1 + function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; + //set a particular bit as 1 + function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + //set a particular bit as 0 + function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + + procedure readFromWebSocket;virtual; + function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; + function verifyHeader(pHeader:TStrings):boolean; + procedure startHeartBeat; + procedure sendCloseHandshake; + function generateWebSocketKey:String; + + + + published + property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; + property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; + property onPing: TSWSCDataEvent read FonPing write FonPing; + property onError: TSWSCErrorEvent read FonError write FonError; + property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; + property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; + property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; + property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; + property URL: String read FURL write FURL; + public + + procedure Connect(pURL:String);overload; + procedure Close; + function Connected: Boolean; overload; + property Upgraded: Boolean read FUpgraded; + + procedure writeText(pMsg:String); + procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); + + constructor Create(AOwner: TComponent); + destructor Destroy; override; + +end; + +implementation + +{ TIdSimpleWebSocketClient } + +function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue and not (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.Close; +begin + self.lInternalLock.Enter; + try + if self.Connected then + begin + self.sendCloseHandshake; + self.IOHandler.InputBuffer.Clear; + self.IOHandler.CloseGracefully; + self.Disconnect; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + finally + self.lInternalLock.Leave; + end +end; + +function TIdSimpleWebSocketClient.generateWebSocketKey():String; +var rand:TidBytes; + I: Integer; +begin + SetLength(rand, 16); + for I := low(rand) to High(rand) do + rand[i] := byte(random(255)); + + result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String + self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 + + with TIdHashSHA1.Create do + try + SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); + finally + Free; + end; +end; + +function TIdSimpleWebSocketClient.Connected: Boolean; +begin + result := false; //for some reason, if its not connected raises an error after connection lost! + try + result := inherited; + except + end +end; + +procedure TIdSimpleWebSocketClient.Connect(pURL: String); +var URI : TIdURI; + lSecure : Boolean; +begin + uri := nil; + try + lClosingEventLocalHandshake := false; + URI := TIdURI.Create(pURL); + self.URL := pURL; + self.Host := URI.Host; + URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet + + if URI.Path='' then + URI.Path := '/'; + lSecure := uri.Protocol='https'; + + if URI.Port.IsEmpty then + begin + if lSecure then + self.Port := 443 + else + self.Port := 80; + end + else + self.Port := StrToInt(URI.Port); + + + if lSecure and (self.IOHandler=nil) then + begin + if self.AutoCreateHandler then //for simple life + begin + self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; + end + else + raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); + end; + + if self.Connected then + raise Exception.Create('Already connected, verify'); + + + inherited Connect; + if not URI.Port.IsEmpty then + URI.Host := URI.Host+':'+URI.Port; + self.Socket.WriteLn(format('GET %s HTTP/1.1', [uri.path+uri.Document])); + self.Socket.WriteLn(format('Host: %s', [URI.Host])); + self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); + self.Socket.WriteLn('Connection: keep-alive, Upgrade'); + self.Socket.WriteLn('Upgrade: WebSocket'); + self.Socket.WriteLn('Sec-WebSocket-Version: 13'); + self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); + self.Socket.WriteLn(''); + + readFromWebSocket; + startHeartBeat; + finally + URI.Free; + end; +end; + +procedure TIdSimpleWebSocketClient.sendCloseHandshake; +begin + self.lClosingEventLocalHandshake := true; + self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); + TThread.Sleep(200); +end; + +constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); +begin + inherited; + lInternalLock := TCriticalSection.Create; + Randomize; + self.AutoCreateHandler := false; + self.HeartBeatInterval := 30000; +end; + +destructor TIdSimpleWebSocketClient.Destroy; +begin + lInternalLock.Free; + if self.AutoCreateHandler and Assigned(self.IOHandler) then + self.IOHandler.Free; + inherited; +end; + +function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; +var FIN, MASK: Cardinal; + MaskingKey:array[0..3] of cardinal; + EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; + buffer:Tidbytes; + I: Integer; + xor1, xor2:char ; + ExtendedPayloadLength:Integer; +begin + FIN:=0; + FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; + + MASK := set_a_bit(0,7); + + ExtendedPayloadLength:= 0; + if pMsg.Length<=125 then + MASK := MASK+pMsg.Length + else + if pMsg.Length 0; +end; + +procedure TIdSimpleWebSocketClient.readFromWebSocket; +var + lSpool: string; + b:Byte; + T: ITask; + lPos:Integer; + lSize:Integer; + lOpCode:Byte; + linFrame:Boolean; + lMasked:boolean; + lForceDisconnect:Boolean; + lHeader:TStringlist; +// lClosingRemoteHandshake:Boolean; +// lPing:Boolean; +begin + lSpool := ''; + lPos := 0; + lSize := 0; + lOpCode := 0; + lMasked := false; + FUpgraded := false; +// lPing := false; +// pingByte := Set_a_Bit(0,7); //1001001//PingByte +// pingByte := Set_a_Bit(pingByte,3); +// pingByte := Set_a_Bit(pingByte,0); +// closeByte := Set_a_Bit(0,7);//1001000//CloseByte +// closeByte := Set_a_Bit(closeByte,3); + + lHeader := TStringList.Create; + linFrame := false; + + try + while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket + begin + b := self.Socket.ReadByte; + + lSpool := lSpool+chr(b); + if (not FUpgraded and (b=ord(#13))) then + begin + if lSpool=#10#13 then + begin + + //verifies header + try + if not verifyHeader(lHeader) then + begin + raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); + end; + finally + lHeader.Free; + end; + + FUpgraded := true; + lSpool := ''; + lPos := 0; + end + else + begin + if assigned(onConnectionDataEvent) then + onConnectionDataEvent(self, lSpool); + + lHeader.Add(lSpool.Trim); + lSpool := ''; + end; + end; + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + exit; + end; + end; + + + if Connected then + T := TTask.Run( + procedure + begin + + try + while Connected do + begin + + b := self.Socket.ReadByte; + + + if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN + begin + linFrame := true; + lOpCode := Clear_a_Bit(b, 7); + + inc(lPos); + + + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + end + else if FUpgraded and (lPos=1) then + begin + lMasked := Get_a_Bit(b, 7); + lSize := b; + if lMasked then + lSize := b-set_a_bit(0,7); + if lSize=0 then + lPos := 0 + else + if lSize=126 then // get size from 2 next bytes + begin + b := self.Socket.ReadByte; + lSize := Round(b*intpower(2,8)); + b := self.Socket.ReadByte; + lSize := lSize+Round(b*intpower(2,0)); + end + else if lSize=127 then + raise Exception.Create('TODO: Size block bigger than supported by this framework, fix is welcome'); + + inc(lPos); + end + else + if linFrame then + begin + lSpool := lSpool+chr(b); + + if (FUpgraded and (Length(lSpool)=lSize)) then + begin + lPos := 0; + linFrame := false; + + if lOpCode=TOpCodeByte[TOpCode.TOPing] then + begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); + finally + lInternalLock.Leave; + end; + + if assigned(onPing) then + onPing(self, lSpool); + end + else + begin + if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then + onDataEvent(self, lSpool); + if assigned(self.lSyncFunctionTrigger) then + begin + if self.lSyncFunctionTrigger(lSpool) then + begin + self.lSyncFunctionEvent.SetEvent; + end; + end; + end; + + lSpool := ''; + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + begin + if not Self.lClosingEventLocalHandshake then + begin + self.Close; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + break + end; + + end; + end; + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + end; + end; + end); + + if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then + begin + + raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); + end + else + if assigned(self.OnUpgrade) then + self.OnUpgrade(self); + +end; + +procedure TIdSimpleWebSocketClient.startHeartBeat; +var TimeUltimaNotif:TDateTime; + lForceDisconnect:Boolean; +begin + TThread.CreateAnonymousThread(procedure begin + TimeUltimaNotif := Now; + try + while (self.Connected) and (self.HeartBeatInterval>0) do + begin + //HeartBeat: + if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then + begin + if assigned(self.onHeartBeatTimer) then + self.onHeartBeatTimer(self); + TimeUltimaNotif := Now; + end; + TThread.Sleep(500); + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + end; + end; + + end).Start; +end; + +function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; +begin + pHeader.NameValueSeparator := ':'; + result := false; + if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then + raise Exception.Create(pHeader[0].Substring(9)); + + if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then + begin + if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then + result := true + else + if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then + result := true + else + raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); + + end; +end; + +function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue or (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.writeText(pMsg: String); +begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(pMSG)); + finally + lInternalLock.Leave; + end; +end; + +procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; + pTriggerFunction: TFunc); +begin + self.lSyncFunctionTrigger := pTriggerFunction; + try + self.lSyncFunctionEvent := TSimpleEvent.Create(); + self.lSyncFunctionEvent.ResetEvent; + self.writeText(pMsg); + self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); + + finally + self.lSyncFunctionTrigger:= nil; + self.lSyncFunctionEvent.Free; + end; + + +end; + +end. diff --git a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~4~ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~4~ new file mode 100644 index 000000000..2d7a830a4 --- /dev/null +++ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~4~ @@ -0,0 +1,611 @@ +{ + * Simple WebSocket client for Delphi + * http://www.websocket.org/echo.html + * Author: Lucas Rubian Schatz + * Copyright 2018, Indy Working Group. + * + * Date: 25/05/2019 - Jason R. Nelson (adaloveless) - Fix warning and incorrect URI in "GET" request + * Date: 22/02/2018 + TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support +} +{ +Sample code: +//var lSWC:TIdSimpleWebSocketClient; +... +begin + lSWC := TIdSimpleWebSocketClient.Create(self); + lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent + lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl + if not lSWC.AutoCreateHandler then + begin + if lSWC.IOHandler=nil then + lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; + end; + lSWC.Connect('wss://echo.websocket.org'); + lSWC.writeText('!!It worked!!'); +end; +} + +unit IdWebSocketSimpleClient; + +interface + +uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, + IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; +Type + TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; + TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; +// * %x0 denotes a continuation frame +// * %x1 denotes a text frame +// * %x2 denotes a binary frame +// * %x3-7 are reserved for further non-control frames +// * %x8 denotes a connection close +// * %x9 denotes a ping +// * %xA denotes a pong +// * %xB-F are reserved for further control frames + + TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); + Const + TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); + + Type + TIdSimpleWebSocketClient = class(TIdTCPClient) + private + SecWebSocketAcceptExpectedResponse: string; + FHeartBeatInterval: Cardinal; + FAutoCreateHandler: Boolean; + FURL: String; + FOnUpgrade: TnotifyEvent; + FonHeartBeatTimer: TNotifyEvent; + FonError: TSWSCErrorEvent; + FonPing: TSWSCDataEvent; + FonConnectionDataEvent: TSWSCDataEvent; + FonDataEvent: TSWSCDataEvent; + FUpgraded: Boolean; + + protected + + lInternalLock:TCriticalSection; + lClosingEventLocalHandshake:Boolean; + //Sync Event + lSyncFunctionEvent:TSimpleEvent; + lSyncFunctionTrigger:TFunc; + //Sync Event + + //get if a particular bit is 1 + function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; + //set a particular bit as 1 + function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + //set a particular bit as 0 + function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; + + procedure readFromWebSocket;virtual; + function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; + function verifyHeader(pHeader:TStrings):boolean; + procedure startHeartBeat; + procedure sendCloseHandshake; + function generateWebSocketKey:String; + + + + published + property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; + property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; + property onPing: TSWSCDataEvent read FonPing write FonPing; + property onError: TSWSCErrorEvent read FonError write FonError; + property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; + property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; + property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; + property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; + property URL: String read FURL write FURL; + public + + procedure Connect(pURL:String);overload; + procedure Close; + function Connected: Boolean; overload; + property Upgraded: Boolean read FUpgraded; + + procedure writeText(pMsg:String); + procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); + + constructor Create(AOwner: TComponent); + destructor Destroy; override; + +end; + +implementation + +{ TIdSimpleWebSocketClient } + +function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue and not (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.Close; +begin + self.lInternalLock.Enter; + try + if self.Connected then + begin + self.sendCloseHandshake; + self.IOHandler.InputBuffer.Clear; + self.IOHandler.CloseGracefully; + self.Disconnect; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + finally + self.lInternalLock.Leave; + end +end; + +function TIdSimpleWebSocketClient.generateWebSocketKey():String; +var rand:TidBytes; + I: Integer; +begin + SetLength(rand, 16); + for I := low(rand) to High(rand) do + rand[i] := byte(random(255)); + + result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String + self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 + + with TIdHashSHA1.Create do + try + SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); + finally + Free; + end; +end; + +function TIdSimpleWebSocketClient.Connected: Boolean; +begin + result := false; //for some reason, if its not connected raises an error after connection lost! + try + result := inherited; + except + end +end; + +procedure TIdSimpleWebSocketClient.Connect(pURL: String); +var URI : TIdURI; + lSecure : Boolean; +begin + uri := nil; + try + lClosingEventLocalHandshake := false; + URI := TIdURI.Create(pURL); + self.URL := pURL; + self.Host := URI.Host; + URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet + + if URI.Path='' then + URI.Path := '/'; + lSecure := uri.Protocol='https'; + + if URI.Port.IsEmpty then + begin + if lSecure then + self.Port := 443 + else + self.Port := 80; + end + else + self.Port := StrToInt(URI.Port); + + + if lSecure and (self.IOHandler=nil) then + begin + if self.AutoCreateHandler then //for simple life + begin + self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; + (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; + end + else + raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); + end; + + if self.Connected then + raise Exception.Create('Already connected, verify'); + + + inherited Connect; + if not URI.Port.IsEmpty then + URI.Host := URI.Host+':'+URI.Port; + var s := uri.path+uri.Document; + self.Socket.WriteLn(format('GET %s HTTP/1.1', [s])); + self.Socket.WriteLn(format('Host: %s', [URI.Host])); + self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); + self.Socket.WriteLn('Connection: keep-alive, Upgrade'); + self.Socket.WriteLn('Upgrade: WebSocket'); + self.Socket.WriteLn('Sec-WebSocket-Version: 13'); + self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); + self.Socket.WriteLn(''); + + readFromWebSocket; + startHeartBeat; + finally + URI.Free; + end; +end; + +procedure TIdSimpleWebSocketClient.sendCloseHandshake; +begin + self.lClosingEventLocalHandshake := true; + self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); + TThread.Sleep(200); +end; + +constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); +begin + inherited; + lInternalLock := TCriticalSection.Create; + Randomize; + self.AutoCreateHandler := false; + self.HeartBeatInterval := 30000; +end; + +destructor TIdSimpleWebSocketClient.Destroy; +begin + lInternalLock.Free; + if self.AutoCreateHandler and Assigned(self.IOHandler) then + self.IOHandler.Free; + inherited; +end; + +function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; +var FIN, MASK: Cardinal; + MaskingKey:array[0..3] of cardinal; + EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; + buffer:Tidbytes; + I: Integer; + xor1, xor2:char ; + ExtendedPayloadLength:Integer; +begin + FIN:=0; + FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; + + MASK := set_a_bit(0,7); + + ExtendedPayloadLength:= 0; + if pMsg.Length<=125 then + MASK := MASK+pMsg.Length + else + if pMsg.Length 0; +end; + +procedure TIdSimpleWebSocketClient.readFromWebSocket; +var + lSpool: string; + b:Byte; + T: ITask; + lPos:Integer; + lSize:int64; + lOpCode:Byte; + linFrame:Boolean; + lMasked:boolean; + lForceDisconnect:Boolean; + lHeader:TStringlist; +// lClosingRemoteHandshake:Boolean; +// lPing:Boolean; +begin + lSpool := ''; + lPos := 0; + lSize := 0; + lOpCode := 0; + lMasked := false; + FUpgraded := false; +// lPing := false; +// pingByte := Set_a_Bit(0,7); //1001001//PingByte +// pingByte := Set_a_Bit(pingByte,3); +// pingByte := Set_a_Bit(pingByte,0); +// closeByte := Set_a_Bit(0,7);//1001000//CloseByte +// closeByte := Set_a_Bit(closeByte,3); + + lHeader := TStringList.Create; + linFrame := false; + + try + while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket + begin + b := self.Socket.ReadByte; + + lSpool := lSpool+chr(b); + if (not FUpgraded and (b=ord(#13))) then + begin + if lSpool=#10#13 then + begin + + //verifies header + try + if not verifyHeader(lHeader) then + begin + raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); + end; + finally + lHeader.Free; + end; + + FUpgraded := true; + lSpool := ''; + lPos := 0; + end + else + begin + if assigned(onConnectionDataEvent) then + onConnectionDataEvent(self, lSpool); + + lHeader.Add(lSpool.Trim); + lSpool := ''; + end; + end; + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + exit; + end; + end; + + + if Connected then + T := TTask.Run( + procedure + var + extended_payload_length: cardinal; + begin + + try + while Connected do + begin + + b := self.Socket.ReadByte; + + + if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN + begin + linFrame := true; + lOpCode := Clear_a_Bit(b, 7); + + inc(lPos); + + + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + end + else if FUpgraded and (lPos=1) then + begin + lMasked := Get_a_Bit(b, 7); + lSize := b; + if lMasked then + lSize := b-set_a_bit(0,7); + if lSize=0 then + lPos := 0 + else + if lSize=126 then // get size from 2 next bytes + begin + lsize := self.socket.ReadUInt16; + end + else if lSize=127 then begin + lsize := self.socket.ReadUInt64; + end; + + inc(lPos); + end + else + if linFrame then + begin + lSpool := lSpool+chr(b); + + if (FUpgraded and (Length(lSpool)=lSize)) then + begin + lPos := 0; + linFrame := false; + + if lOpCode=TOpCodeByte[TOpCode.TOPing] then + begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); + finally + lInternalLock.Leave; + end; + + if assigned(onPing) then + onPing(self, lSpool); + end + else + begin + if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then + onDataEvent(self, lSpool); + if assigned(self.lSyncFunctionTrigger) then + begin + if self.lSyncFunctionTrigger(lSpool) then + begin + self.lSyncFunctionEvent.SetEvent; + end; + end; + end; + + lSpool := ''; + if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then + begin + if not Self.lClosingEventLocalHandshake then + begin + self.Close; + if assigned(self.OnDisconnected) then + self.OnDisconnected(self); + end; + break + end; + + end; + end; + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + end; + end; + end); + + if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then + begin + + raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); + end + else + if assigned(self.OnUpgrade) then + self.OnUpgrade(self); + +end; + +procedure TIdSimpleWebSocketClient.startHeartBeat; +var TimeUltimaNotif:TDateTime; + lForceDisconnect:Boolean; +begin + TThread.CreateAnonymousThread(procedure begin + TimeUltimaNotif := Now; + try + while (self.Connected) and (self.HeartBeatInterval>0) do + begin + //HeartBeat: + if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then + begin + if assigned(self.onHeartBeatTimer) then + self.onHeartBeatTimer(self); + TimeUltimaNotif := Now; + end; + TThread.Sleep(500); + end; + except + on e:Exception do + begin + lForceDisconnect := true; + if assigned(self.onError) then + self.onError(self, e, e.Message, lForceDisconnect); + if lForceDisconnect then + self.Close; + end; + end; + + end).Start; +end; + +function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; +begin + pHeader.NameValueSeparator := ':'; + result := false; + if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then + raise Exception.Create(pHeader[0].Substring(9)); + + if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then + begin + if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then + result := true + else + if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then + result := true + else + raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); + + end; +end; + +function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; + const Bit: Byte): Cardinal; +begin + Result := aValue or (1 shl Bit); +end; + +procedure TIdSimpleWebSocketClient.writeText(pMsg: String); +begin + try + lInternalLock.Enter; + self.Socket.Write(encodeFrame(pMSG)); + finally + lInternalLock.Leave; + end; +end; + +procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; + pTriggerFunction: TFunc); +begin + self.lSyncFunctionTrigger := pTriggerFunction; + try + self.lSyncFunctionEvent := TSimpleEvent.Create(); + self.lSyncFunctionEvent.ResetEvent; + self.writeText(pMsg); + self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); + + finally + self.lSyncFunctionTrigger:= nil; + self.lSyncFunctionEvent.Free; + end; + + +end; + +end. From ce870028e6f0f9944824c51b6502ced7a5aca379 Mon Sep 17 00:00:00 2001 From: arvanus Date: Tue, 10 Mar 2020 09:47:50 -0300 Subject: [PATCH 06/10] Delete IdWebSocketSimpleClient.pas.~1~ --- .../__history/IdWebSocketSimpleClient.pas.~1~ | 610 ------------------ 1 file changed, 610 deletions(-) delete mode 100644 Lib/Core/__history/IdWebSocketSimpleClient.pas.~1~ diff --git a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~1~ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~1~ deleted file mode 100644 index 7536fe12a..000000000 --- a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~1~ +++ /dev/null @@ -1,610 +0,0 @@ -{ - * Simple WebSocket client for Delphi - * http://www.websocket.org/echo.html - * Author: Lucas Rubian Schatz - * Copyright 2018, Indy Working Group. - * - * Date: 22/02/2018 - - TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support -} -{ -Sample code: -//var lSWC:TIdSimpleWebSocketClient; -... -begin - lSWC := TIdSimpleWebSocketClient.Create(self); - lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent - lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl - if not lSWC.AutoCreateHandler then - begin - if lSWC.IOHandler=nil then - lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); - (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; - end; - lSWC.Connect('wss://echo.websocket.org'); - lSWC.writeText('!!It worked!!'); -end; - -} - -unit IdWebSocketSimpleClient; - -interface - -uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, - IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; -Type - TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; - TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; -// * %x0 denotes a continuation frame -// * %x1 denotes a text frame -// * %x2 denotes a binary frame -// * %x3-7 are reserved for further non-control frames -// * %x8 denotes a connection close -// * %x9 denotes a ping -// * %xA denotes a pong -// * %xB-F are reserved for further control frames - - TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); - Const - TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); - - Type - TIdSimpleWebSocketClient = class(TIdTCPClient) - private - SecWebSocketAcceptExpectedResponse: string; - FHeartBeatInterval: Cardinal; - FAutoCreateHandler: Boolean; - FURL: String; - FOnUpgrade: TnotifyEvent; - FonHeartBeatTimer: TNotifyEvent; - FonError: TSWSCErrorEvent; - FonPing: TSWSCDataEvent; - FonConnectionDataEvent: TSWSCDataEvent; - FonDataEvent: TSWSCDataEvent; - FUpgraded: Boolean; - - protected - - lInternalLock:TCriticalSection; - lClosingEventLocalHandshake:Boolean; - //Sync Event - lSyncFunctionEvent:TSimpleEvent; - lSyncFunctionTrigger:TFunc; - //Sync Event - - //get if a particular bit is 1 - function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; - //set a particular bit as 1 - function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - //set a particular bit as 0 - function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - - procedure readFromWebSocket;virtual; - function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; - function verifyHeader(pHeader:TStrings):boolean; - procedure startHeartBeat; - procedure sendCloseHandshake; - function generateWebSocketKey:String; - - - - published - property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; - property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; - property onPing: TSWSCDataEvent read FonPing write FonPing; - property onError: TSWSCErrorEvent read FonError write FonError; - property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; - property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; - property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; - property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; - property URL: String read FURL write FURL; - public - - procedure Connect(pURL:String);overload; - procedure Close; - function Connected: Boolean; overload; - property Upgraded: Boolean read FUpgraded; - - procedure writeText(pMsg:String); - procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); - - constructor Create(AOwner: TComponent); - destructor Destroy; override; - -end; - -implementation - -{ TIdSimpleWebSocketClient } - -function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; - const Bit: Byte): Cardinal; -begin - Result := aValue and not (1 shl Bit); -end; - -procedure TIdSimpleWebSocketClient.Close; -begin - self.lInternalLock.Enter; - try - if self.Connected then - begin - self.sendCloseHandshake; - self.IOHandler.InputBuffer.Clear; - self.IOHandler.CloseGracefully; - self.Disconnect; - if assigned(self.OnDisconnected) then - self.OnDisconnected(self); - end; - finally - self.lInternalLock.Leave; - end -end; - -function TIdSimpleWebSocketClient.generateWebSocketKey():String; -var rand:TidBytes; - I: Integer; -begin - SetLength(rand, 16); - for I := low(rand) to High(rand) do - rand[i] := byte(random(255)); - - result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String - self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 - - with TIdHashSHA1.Create do - try - SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); - finally - Free; - end; -end; - -function TIdSimpleWebSocketClient.Connected: Boolean; -begin - result := false; //for some reason, if its not connected raises an error after connection lost! - try - result := inherited; - except - end -end; - -procedure TIdSimpleWebSocketClient.Connect(pURL: String); -var URI : TIdURI; - lSecure : Boolean; -begin - try - lClosingEventLocalHandshake := false; - URI := TIdURI.Create(pURL); - self.URL := pURL; - self.Host := URI.Host; - URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet - - if URI.Path='' then - URI.Path := '/'; - lSecure := uri.Protocol='https'; - - if URI.Port.IsEmpty then - begin - if lSecure then - self.Port := 443 - else - self.Port := 80; - end - else - self.Port := StrToInt(URI.Port); - - - if lSecure and (self.IOHandler=nil) then - begin - if self.AutoCreateHandler then //for simple life - begin - self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); - (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; - end - else - raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); - end; - - if self.Connected then - raise Exception.Create('Already connected, verify'); - - - inherited Connect; - if not URI.Port.IsEmpty then - URI.Host := URI.Host+':'+URI.Port; - self.Socket.WriteLn(format('GET %s HTTP/1.1', [URI.Path])); - self.Socket.WriteLn(format('Host: %s', [URI.Host])); - self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); - self.Socket.WriteLn('Connection: keep-alive, Upgrade'); - self.Socket.WriteLn('Upgrade: WebSocket'); - self.Socket.WriteLn('Sec-WebSocket-Version: 13'); - self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); - self.Socket.WriteLn(''); - - readFromWebSocket; - startHeartBeat; - finally - URI.Free; - end; -end; - -procedure TIdSimpleWebSocketClient.sendCloseHandshake; -begin - self.lClosingEventLocalHandshake := true; - self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); - TThread.Sleep(200); -end; - -constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); -begin - inherited; - lInternalLock := TCriticalSection.Create; - Randomize; - self.AutoCreateHandler := false; - self.HeartBeatInterval := 30000; -end; - -destructor TIdSimpleWebSocketClient.Destroy; -begin - lInternalLock.Free; - if self.AutoCreateHandler and Assigned(self.IOHandler) then - self.IOHandler.Free; - inherited; -end; - -function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; -var FIN, MASK: Cardinal; - MaskingKey:array[0..3] of cardinal; - EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; - buffer:Tidbytes; - I: Integer; - xor1, xor2:char ; - ExtendedPayloadLength:Integer; -begin - FIN:=0; - FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; - - MASK := set_a_bit(0,7); - - ExtendedPayloadLength:= 0; - if pMsg.Length<=125 then - MASK := MASK+pMsg.Length - else - if pMsg.Length 0; -end; - -procedure TIdSimpleWebSocketClient.readFromWebSocket; -var - lSpool: string; - b:Byte; - T: ITask; - lPos:Integer; - lSize:Integer; - lOpCode:Byte; - linFrame:Boolean; - lMasked:boolean; - lForceDisconnect:Boolean; - lHeader:TStringlist; -// lClosingRemoteHandshake:Boolean; -// lPing:Boolean; -begin - lSpool := ''; - lPos := 0; - lSize := 0; - lOpCode := 0; - lMasked := false; - FUpgraded := false; -// lPing := false; -// pingByte := Set_a_Bit(0,7); //1001001//PingByte -// pingByte := Set_a_Bit(pingByte,3); -// pingByte := Set_a_Bit(pingByte,0); -// closeByte := Set_a_Bit(0,7);//1001000//CloseByte -// closeByte := Set_a_Bit(closeByte,3); - - lHeader := TStringList.Create; - linFrame := false; - - try - while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket - begin - b := self.Socket.ReadByte; - - lSpool := lSpool+chr(b); - if (not FUpgraded and (b=ord(#13))) then - begin - if lSpool=#10#13 then - begin - - //verifies header - try - if not verifyHeader(lHeader) then - begin - raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); - end; - finally - lHeader.Free; - end; - - FUpgraded := true; - lSpool := ''; - lPos := 0; - end - else - begin - if assigned(onConnectionDataEvent) then - onConnectionDataEvent(self, lSpool); - - lHeader.Add(lSpool.Trim); - lSpool := ''; - end; - end; - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - exit; - end; - end; - - - if Connected then - T := TTask.Run( - procedure - begin - - try - while Connected do - begin - - b := self.Socket.ReadByte; - - - if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN - begin - linFrame := true; - lOpCode := Clear_a_Bit(b, 7); - - inc(lPos); - - - if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then - end - else if FUpgraded and (lPos=1) then - begin - lMasked := Get_a_Bit(b, 7); - lSize := b; - if lMasked then - lSize := b-set_a_bit(0,7); - if lSize=0 then - lPos := 0 - else - if lSize=126 then // get size from 2 next bytes - begin - b := self.Socket.ReadByte; - lSize := Round(b*intpower(2,8)); - b := self.Socket.ReadByte; - lSize := lSize+Round(b*intpower(2,0)); - end - else if lSize=127 then - raise Exception.Create('TODO: Size block bigger than supported by this framework, fix is welcome'); - - inc(lPos); - end - else - if linFrame then - begin - lSpool := lSpool+chr(b); - - if (FUpgraded and (Length(lSpool)=lSize)) then - begin - lPos := 0; - linFrame := false; - - if lOpCode=TOpCodeByte[TOpCode.TOPing] then - begin - try - lInternalLock.Enter; - self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); - finally - lInternalLock.Leave; - end; - - if assigned(onPing) then - onPing(self, lSpool); - end - else - begin - if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then - onDataEvent(self, lSpool); - if assigned(self.lSyncFunctionTrigger) then - begin - if self.lSyncFunctionTrigger(lSpool) then - begin - self.lSyncFunctionEvent.SetEvent; - end; - end; - end; - - lSpool := ''; - if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then - begin - if not Self.lClosingEventLocalHandshake then - begin - self.Close; - if assigned(self.OnDisconnected) then - self.OnDisconnected(self); - end; - break - end; - - end; - end; - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - end; - end; - end); - - if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then - begin - - raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); - end - else - if assigned(self.OnUpgrade) then - self.OnUpgrade(self); - -end; - -procedure TIdSimpleWebSocketClient.startHeartBeat; -var TimeUltimaNotif:TDateTime; - lForceDisconnect:Boolean; -begin - TThread.CreateAnonymousThread(procedure begin - TimeUltimaNotif := Now; - try - while (self.Connected) and (self.HeartBeatInterval>0) do - begin - //HeartBeat: - if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then - begin - if assigned(self.onHeartBeatTimer) then - self.onHeartBeatTimer(self); - TimeUltimaNotif := Now; - end; - TThread.Sleep(500); - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - end; - end; - - end).Start; -end; - -function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; -begin - pHeader.NameValueSeparator := ':'; - result := false; - if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then - raise Exception.Create(pHeader[0].Substring(9)); - - if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then - begin - if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then - result := true - else - if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then - result := true - else - raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); - - end; -end; - -function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; - const Bit: Byte): Cardinal; -begin - Result := aValue or (1 shl Bit); -end; - -procedure TIdSimpleWebSocketClient.writeText(pMsg: String); -begin - try - lInternalLock.Enter; - self.Socket.Write(encodeFrame(pMSG)); - finally - lInternalLock.Leave; - end; -end; - -procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; - pTriggerFunction: TFunc); -begin - self.lSyncFunctionTrigger := pTriggerFunction; - try - self.lSyncFunctionEvent := TSimpleEvent.Create(); - self.lSyncFunctionEvent.ResetEvent; - self.writeText(pMsg); - self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); - - finally - self.lSyncFunctionTrigger:= nil; - self.lSyncFunctionEvent.Free; - end; - - -end; - -end. From 1d15a6a9eae19a8796280bb49a389d03943ae832 Mon Sep 17 00:00:00 2001 From: arvanus Date: Tue, 10 Mar 2020 09:48:32 -0300 Subject: [PATCH 07/10] Delete IdWebSocketSimpleClient.pas.~4~ --- .../__history/IdWebSocketSimpleClient.pas.~4~ | 611 ------------------ 1 file changed, 611 deletions(-) delete mode 100644 Lib/Core/__history/IdWebSocketSimpleClient.pas.~4~ diff --git a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~4~ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~4~ deleted file mode 100644 index 2d7a830a4..000000000 --- a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~4~ +++ /dev/null @@ -1,611 +0,0 @@ -{ - * Simple WebSocket client for Delphi - * http://www.websocket.org/echo.html - * Author: Lucas Rubian Schatz - * Copyright 2018, Indy Working Group. - * - * Date: 25/05/2019 - Jason R. Nelson (adaloveless) - Fix warning and incorrect URI in "GET" request - * Date: 22/02/2018 - TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support -} -{ -Sample code: -//var lSWC:TIdSimpleWebSocketClient; -... -begin - lSWC := TIdSimpleWebSocketClient.Create(self); - lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent - lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl - if not lSWC.AutoCreateHandler then - begin - if lSWC.IOHandler=nil then - lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); - (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; - end; - lSWC.Connect('wss://echo.websocket.org'); - lSWC.writeText('!!It worked!!'); -end; -} - -unit IdWebSocketSimpleClient; - -interface - -uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, - IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; -Type - TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; - TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; -// * %x0 denotes a continuation frame -// * %x1 denotes a text frame -// * %x2 denotes a binary frame -// * %x3-7 are reserved for further non-control frames -// * %x8 denotes a connection close -// * %x9 denotes a ping -// * %xA denotes a pong -// * %xB-F are reserved for further control frames - - TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); - Const - TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); - - Type - TIdSimpleWebSocketClient = class(TIdTCPClient) - private - SecWebSocketAcceptExpectedResponse: string; - FHeartBeatInterval: Cardinal; - FAutoCreateHandler: Boolean; - FURL: String; - FOnUpgrade: TnotifyEvent; - FonHeartBeatTimer: TNotifyEvent; - FonError: TSWSCErrorEvent; - FonPing: TSWSCDataEvent; - FonConnectionDataEvent: TSWSCDataEvent; - FonDataEvent: TSWSCDataEvent; - FUpgraded: Boolean; - - protected - - lInternalLock:TCriticalSection; - lClosingEventLocalHandshake:Boolean; - //Sync Event - lSyncFunctionEvent:TSimpleEvent; - lSyncFunctionTrigger:TFunc; - //Sync Event - - //get if a particular bit is 1 - function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; - //set a particular bit as 1 - function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - //set a particular bit as 0 - function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - - procedure readFromWebSocket;virtual; - function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; - function verifyHeader(pHeader:TStrings):boolean; - procedure startHeartBeat; - procedure sendCloseHandshake; - function generateWebSocketKey:String; - - - - published - property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; - property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; - property onPing: TSWSCDataEvent read FonPing write FonPing; - property onError: TSWSCErrorEvent read FonError write FonError; - property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; - property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; - property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; - property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; - property URL: String read FURL write FURL; - public - - procedure Connect(pURL:String);overload; - procedure Close; - function Connected: Boolean; overload; - property Upgraded: Boolean read FUpgraded; - - procedure writeText(pMsg:String); - procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); - - constructor Create(AOwner: TComponent); - destructor Destroy; override; - -end; - -implementation - -{ TIdSimpleWebSocketClient } - -function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; - const Bit: Byte): Cardinal; -begin - Result := aValue and not (1 shl Bit); -end; - -procedure TIdSimpleWebSocketClient.Close; -begin - self.lInternalLock.Enter; - try - if self.Connected then - begin - self.sendCloseHandshake; - self.IOHandler.InputBuffer.Clear; - self.IOHandler.CloseGracefully; - self.Disconnect; - if assigned(self.OnDisconnected) then - self.OnDisconnected(self); - end; - finally - self.lInternalLock.Leave; - end -end; - -function TIdSimpleWebSocketClient.generateWebSocketKey():String; -var rand:TidBytes; - I: Integer; -begin - SetLength(rand, 16); - for I := low(rand) to High(rand) do - rand[i] := byte(random(255)); - - result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String - self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 - - with TIdHashSHA1.Create do - try - SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); - finally - Free; - end; -end; - -function TIdSimpleWebSocketClient.Connected: Boolean; -begin - result := false; //for some reason, if its not connected raises an error after connection lost! - try - result := inherited; - except - end -end; - -procedure TIdSimpleWebSocketClient.Connect(pURL: String); -var URI : TIdURI; - lSecure : Boolean; -begin - uri := nil; - try - lClosingEventLocalHandshake := false; - URI := TIdURI.Create(pURL); - self.URL := pURL; - self.Host := URI.Host; - URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet - - if URI.Path='' then - URI.Path := '/'; - lSecure := uri.Protocol='https'; - - if URI.Port.IsEmpty then - begin - if lSecure then - self.Port := 443 - else - self.Port := 80; - end - else - self.Port := StrToInt(URI.Port); - - - if lSecure and (self.IOHandler=nil) then - begin - if self.AutoCreateHandler then //for simple life - begin - self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); - (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; - end - else - raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); - end; - - if self.Connected then - raise Exception.Create('Already connected, verify'); - - - inherited Connect; - if not URI.Port.IsEmpty then - URI.Host := URI.Host+':'+URI.Port; - var s := uri.path+uri.Document; - self.Socket.WriteLn(format('GET %s HTTP/1.1', [s])); - self.Socket.WriteLn(format('Host: %s', [URI.Host])); - self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); - self.Socket.WriteLn('Connection: keep-alive, Upgrade'); - self.Socket.WriteLn('Upgrade: WebSocket'); - self.Socket.WriteLn('Sec-WebSocket-Version: 13'); - self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); - self.Socket.WriteLn(''); - - readFromWebSocket; - startHeartBeat; - finally - URI.Free; - end; -end; - -procedure TIdSimpleWebSocketClient.sendCloseHandshake; -begin - self.lClosingEventLocalHandshake := true; - self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); - TThread.Sleep(200); -end; - -constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); -begin - inherited; - lInternalLock := TCriticalSection.Create; - Randomize; - self.AutoCreateHandler := false; - self.HeartBeatInterval := 30000; -end; - -destructor TIdSimpleWebSocketClient.Destroy; -begin - lInternalLock.Free; - if self.AutoCreateHandler and Assigned(self.IOHandler) then - self.IOHandler.Free; - inherited; -end; - -function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; -var FIN, MASK: Cardinal; - MaskingKey:array[0..3] of cardinal; - EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; - buffer:Tidbytes; - I: Integer; - xor1, xor2:char ; - ExtendedPayloadLength:Integer; -begin - FIN:=0; - FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; - - MASK := set_a_bit(0,7); - - ExtendedPayloadLength:= 0; - if pMsg.Length<=125 then - MASK := MASK+pMsg.Length - else - if pMsg.Length 0; -end; - -procedure TIdSimpleWebSocketClient.readFromWebSocket; -var - lSpool: string; - b:Byte; - T: ITask; - lPos:Integer; - lSize:int64; - lOpCode:Byte; - linFrame:Boolean; - lMasked:boolean; - lForceDisconnect:Boolean; - lHeader:TStringlist; -// lClosingRemoteHandshake:Boolean; -// lPing:Boolean; -begin - lSpool := ''; - lPos := 0; - lSize := 0; - lOpCode := 0; - lMasked := false; - FUpgraded := false; -// lPing := false; -// pingByte := Set_a_Bit(0,7); //1001001//PingByte -// pingByte := Set_a_Bit(pingByte,3); -// pingByte := Set_a_Bit(pingByte,0); -// closeByte := Set_a_Bit(0,7);//1001000//CloseByte -// closeByte := Set_a_Bit(closeByte,3); - - lHeader := TStringList.Create; - linFrame := false; - - try - while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket - begin - b := self.Socket.ReadByte; - - lSpool := lSpool+chr(b); - if (not FUpgraded and (b=ord(#13))) then - begin - if lSpool=#10#13 then - begin - - //verifies header - try - if not verifyHeader(lHeader) then - begin - raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); - end; - finally - lHeader.Free; - end; - - FUpgraded := true; - lSpool := ''; - lPos := 0; - end - else - begin - if assigned(onConnectionDataEvent) then - onConnectionDataEvent(self, lSpool); - - lHeader.Add(lSpool.Trim); - lSpool := ''; - end; - end; - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - exit; - end; - end; - - - if Connected then - T := TTask.Run( - procedure - var - extended_payload_length: cardinal; - begin - - try - while Connected do - begin - - b := self.Socket.ReadByte; - - - if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN - begin - linFrame := true; - lOpCode := Clear_a_Bit(b, 7); - - inc(lPos); - - - if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then - end - else if FUpgraded and (lPos=1) then - begin - lMasked := Get_a_Bit(b, 7); - lSize := b; - if lMasked then - lSize := b-set_a_bit(0,7); - if lSize=0 then - lPos := 0 - else - if lSize=126 then // get size from 2 next bytes - begin - lsize := self.socket.ReadUInt16; - end - else if lSize=127 then begin - lsize := self.socket.ReadUInt64; - end; - - inc(lPos); - end - else - if linFrame then - begin - lSpool := lSpool+chr(b); - - if (FUpgraded and (Length(lSpool)=lSize)) then - begin - lPos := 0; - linFrame := false; - - if lOpCode=TOpCodeByte[TOpCode.TOPing] then - begin - try - lInternalLock.Enter; - self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); - finally - lInternalLock.Leave; - end; - - if assigned(onPing) then - onPing(self, lSpool); - end - else - begin - if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then - onDataEvent(self, lSpool); - if assigned(self.lSyncFunctionTrigger) then - begin - if self.lSyncFunctionTrigger(lSpool) then - begin - self.lSyncFunctionEvent.SetEvent; - end; - end; - end; - - lSpool := ''; - if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then - begin - if not Self.lClosingEventLocalHandshake then - begin - self.Close; - if assigned(self.OnDisconnected) then - self.OnDisconnected(self); - end; - break - end; - - end; - end; - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - end; - end; - end); - - if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then - begin - - raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); - end - else - if assigned(self.OnUpgrade) then - self.OnUpgrade(self); - -end; - -procedure TIdSimpleWebSocketClient.startHeartBeat; -var TimeUltimaNotif:TDateTime; - lForceDisconnect:Boolean; -begin - TThread.CreateAnonymousThread(procedure begin - TimeUltimaNotif := Now; - try - while (self.Connected) and (self.HeartBeatInterval>0) do - begin - //HeartBeat: - if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then - begin - if assigned(self.onHeartBeatTimer) then - self.onHeartBeatTimer(self); - TimeUltimaNotif := Now; - end; - TThread.Sleep(500); - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - end; - end; - - end).Start; -end; - -function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; -begin - pHeader.NameValueSeparator := ':'; - result := false; - if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then - raise Exception.Create(pHeader[0].Substring(9)); - - if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then - begin - if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then - result := true - else - if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then - result := true - else - raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); - - end; -end; - -function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; - const Bit: Byte): Cardinal; -begin - Result := aValue or (1 shl Bit); -end; - -procedure TIdSimpleWebSocketClient.writeText(pMsg: String); -begin - try - lInternalLock.Enter; - self.Socket.Write(encodeFrame(pMSG)); - finally - lInternalLock.Leave; - end; -end; - -procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; - pTriggerFunction: TFunc); -begin - self.lSyncFunctionTrigger := pTriggerFunction; - try - self.lSyncFunctionEvent := TSimpleEvent.Create(); - self.lSyncFunctionEvent.ResetEvent; - self.writeText(pMsg); - self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); - - finally - self.lSyncFunctionTrigger:= nil; - self.lSyncFunctionEvent.Free; - end; - - -end; - -end. From 5a6a3895dc9a730c5ca8bdcc78066c040d104fbe Mon Sep 17 00:00:00 2001 From: arvanus Date: Tue, 10 Mar 2020 09:48:46 -0300 Subject: [PATCH 08/10] Delete IdWebSocketSimpleClient.pas.~3~ --- .../__history/IdWebSocketSimpleClient.pas.~3~ | 610 ------------------ 1 file changed, 610 deletions(-) delete mode 100644 Lib/Core/__history/IdWebSocketSimpleClient.pas.~3~ diff --git a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~3~ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~3~ deleted file mode 100644 index 9df93bfa5..000000000 --- a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~3~ +++ /dev/null @@ -1,610 +0,0 @@ -{ - * Simple WebSocket client for Delphi - * http://www.websocket.org/echo.html - * Author: Lucas Rubian Schatz - * Copyright 2018, Indy Working Group. - * - * Date: 25/05/2019 - Jason R. Nelson (adaloveless) - Fix warning and incorrect URI in "GET" request - * Date: 22/02/2018 - TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support -} -{ -Sample code: -//var lSWC:TIdSimpleWebSocketClient; -... -begin - lSWC := TIdSimpleWebSocketClient.Create(self); - lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent - lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl - if not lSWC.AutoCreateHandler then - begin - if lSWC.IOHandler=nil then - lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); - (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; - end; - lSWC.Connect('wss://echo.websocket.org'); - lSWC.writeText('!!It worked!!'); -end; -} - -unit IdWebSocketSimpleClient; - -interface - -uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, - IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; -Type - TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; - TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; -// * %x0 denotes a continuation frame -// * %x1 denotes a text frame -// * %x2 denotes a binary frame -// * %x3-7 are reserved for further non-control frames -// * %x8 denotes a connection close -// * %x9 denotes a ping -// * %xA denotes a pong -// * %xB-F are reserved for further control frames - - TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); - Const - TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); - - Type - TIdSimpleWebSocketClient = class(TIdTCPClient) - private - SecWebSocketAcceptExpectedResponse: string; - FHeartBeatInterval: Cardinal; - FAutoCreateHandler: Boolean; - FURL: String; - FOnUpgrade: TnotifyEvent; - FonHeartBeatTimer: TNotifyEvent; - FonError: TSWSCErrorEvent; - FonPing: TSWSCDataEvent; - FonConnectionDataEvent: TSWSCDataEvent; - FonDataEvent: TSWSCDataEvent; - FUpgraded: Boolean; - - protected - - lInternalLock:TCriticalSection; - lClosingEventLocalHandshake:Boolean; - //Sync Event - lSyncFunctionEvent:TSimpleEvent; - lSyncFunctionTrigger:TFunc; - //Sync Event - - //get if a particular bit is 1 - function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; - //set a particular bit as 1 - function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - //set a particular bit as 0 - function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - - procedure readFromWebSocket;virtual; - function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; - function verifyHeader(pHeader:TStrings):boolean; - procedure startHeartBeat; - procedure sendCloseHandshake; - function generateWebSocketKey:String; - - - - published - property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; - property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; - property onPing: TSWSCDataEvent read FonPing write FonPing; - property onError: TSWSCErrorEvent read FonError write FonError; - property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; - property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; - property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; - property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; - property URL: String read FURL write FURL; - public - - procedure Connect(pURL:String);overload; - procedure Close; - function Connected: Boolean; overload; - property Upgraded: Boolean read FUpgraded; - - procedure writeText(pMsg:String); - procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); - - constructor Create(AOwner: TComponent); - destructor Destroy; override; - -end; - -implementation - -{ TIdSimpleWebSocketClient } - -function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; - const Bit: Byte): Cardinal; -begin - Result := aValue and not (1 shl Bit); -end; - -procedure TIdSimpleWebSocketClient.Close; -begin - self.lInternalLock.Enter; - try - if self.Connected then - begin - self.sendCloseHandshake; - self.IOHandler.InputBuffer.Clear; - self.IOHandler.CloseGracefully; - self.Disconnect; - if assigned(self.OnDisconnected) then - self.OnDisconnected(self); - end; - finally - self.lInternalLock.Leave; - end -end; - -function TIdSimpleWebSocketClient.generateWebSocketKey():String; -var rand:TidBytes; - I: Integer; -begin - SetLength(rand, 16); - for I := low(rand) to High(rand) do - rand[i] := byte(random(255)); - - result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String - self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 - - with TIdHashSHA1.Create do - try - SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); - finally - Free; - end; -end; - -function TIdSimpleWebSocketClient.Connected: Boolean; -begin - result := false; //for some reason, if its not connected raises an error after connection lost! - try - result := inherited; - except - end -end; - -procedure TIdSimpleWebSocketClient.Connect(pURL: String); -var URI : TIdURI; - lSecure : Boolean; -begin - uri := nil; - try - lClosingEventLocalHandshake := false; - URI := TIdURI.Create(pURL); - self.URL := pURL; - self.Host := URI.Host; - URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet - - if URI.Path='' then - URI.Path := '/'; - lSecure := uri.Protocol='https'; - - if URI.Port.IsEmpty then - begin - if lSecure then - self.Port := 443 - else - self.Port := 80; - end - else - self.Port := StrToInt(URI.Port); - - - if lSecure and (self.IOHandler=nil) then - begin - if self.AutoCreateHandler then //for simple life - begin - self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); - (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; - end - else - raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); - end; - - if self.Connected then - raise Exception.Create('Already connected, verify'); - - - inherited Connect; - if not URI.Port.IsEmpty then - URI.Host := URI.Host+':'+URI.Port; - self.Socket.WriteLn(format('GET %s HTTP/1.1', [uri.path+uri.Document])); - self.Socket.WriteLn(format('Host: %s', [URI.Host])); - self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); - self.Socket.WriteLn('Connection: keep-alive, Upgrade'); - self.Socket.WriteLn('Upgrade: WebSocket'); - self.Socket.WriteLn('Sec-WebSocket-Version: 13'); - self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); - self.Socket.WriteLn(''); - - readFromWebSocket; - startHeartBeat; - finally - URI.Free; - end; -end; - -procedure TIdSimpleWebSocketClient.sendCloseHandshake; -begin - self.lClosingEventLocalHandshake := true; - self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); - TThread.Sleep(200); -end; - -constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); -begin - inherited; - lInternalLock := TCriticalSection.Create; - Randomize; - self.AutoCreateHandler := false; - self.HeartBeatInterval := 30000; -end; - -destructor TIdSimpleWebSocketClient.Destroy; -begin - lInternalLock.Free; - if self.AutoCreateHandler and Assigned(self.IOHandler) then - self.IOHandler.Free; - inherited; -end; - -function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; -var FIN, MASK: Cardinal; - MaskingKey:array[0..3] of cardinal; - EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; - buffer:Tidbytes; - I: Integer; - xor1, xor2:char ; - ExtendedPayloadLength:Integer; -begin - FIN:=0; - FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; - - MASK := set_a_bit(0,7); - - ExtendedPayloadLength:= 0; - if pMsg.Length<=125 then - MASK := MASK+pMsg.Length - else - if pMsg.Length 0; -end; - -procedure TIdSimpleWebSocketClient.readFromWebSocket; -var - lSpool: string; - b:Byte; - T: ITask; - lPos:Integer; - lSize:Integer; - lOpCode:Byte; - linFrame:Boolean; - lMasked:boolean; - lForceDisconnect:Boolean; - lHeader:TStringlist; -// lClosingRemoteHandshake:Boolean; -// lPing:Boolean; -begin - lSpool := ''; - lPos := 0; - lSize := 0; - lOpCode := 0; - lMasked := false; - FUpgraded := false; -// lPing := false; -// pingByte := Set_a_Bit(0,7); //1001001//PingByte -// pingByte := Set_a_Bit(pingByte,3); -// pingByte := Set_a_Bit(pingByte,0); -// closeByte := Set_a_Bit(0,7);//1001000//CloseByte -// closeByte := Set_a_Bit(closeByte,3); - - lHeader := TStringList.Create; - linFrame := false; - - try - while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket - begin - b := self.Socket.ReadByte; - - lSpool := lSpool+chr(b); - if (not FUpgraded and (b=ord(#13))) then - begin - if lSpool=#10#13 then - begin - - //verifies header - try - if not verifyHeader(lHeader) then - begin - raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); - end; - finally - lHeader.Free; - end; - - FUpgraded := true; - lSpool := ''; - lPos := 0; - end - else - begin - if assigned(onConnectionDataEvent) then - onConnectionDataEvent(self, lSpool); - - lHeader.Add(lSpool.Trim); - lSpool := ''; - end; - end; - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - exit; - end; - end; - - - if Connected then - T := TTask.Run( - procedure - begin - - try - while Connected do - begin - - b := self.Socket.ReadByte; - - - if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN - begin - linFrame := true; - lOpCode := Clear_a_Bit(b, 7); - - inc(lPos); - - - if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then - end - else if FUpgraded and (lPos=1) then - begin - lMasked := Get_a_Bit(b, 7); - lSize := b; - if lMasked then - lSize := b-set_a_bit(0,7); - if lSize=0 then - lPos := 0 - else - if lSize=126 then // get size from 2 next bytes - begin - b := self.Socket.ReadByte; - lSize := Round(b*intpower(2,8)); - b := self.Socket.ReadByte; - lSize := lSize+Round(b*intpower(2,0)); - end - else if lSize=127 then - raise Exception.Create('TODO: Size block bigger than supported by this framework, fix is welcome'); - - inc(lPos); - end - else - if linFrame then - begin - lSpool := lSpool+chr(b); - - if (FUpgraded and (Length(lSpool)=lSize)) then - begin - lPos := 0; - linFrame := false; - - if lOpCode=TOpCodeByte[TOpCode.TOPing] then - begin - try - lInternalLock.Enter; - self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); - finally - lInternalLock.Leave; - end; - - if assigned(onPing) then - onPing(self, lSpool); - end - else - begin - if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then - onDataEvent(self, lSpool); - if assigned(self.lSyncFunctionTrigger) then - begin - if self.lSyncFunctionTrigger(lSpool) then - begin - self.lSyncFunctionEvent.SetEvent; - end; - end; - end; - - lSpool := ''; - if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then - begin - if not Self.lClosingEventLocalHandshake then - begin - self.Close; - if assigned(self.OnDisconnected) then - self.OnDisconnected(self); - end; - break - end; - - end; - end; - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - end; - end; - end); - - if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then - begin - - raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); - end - else - if assigned(self.OnUpgrade) then - self.OnUpgrade(self); - -end; - -procedure TIdSimpleWebSocketClient.startHeartBeat; -var TimeUltimaNotif:TDateTime; - lForceDisconnect:Boolean; -begin - TThread.CreateAnonymousThread(procedure begin - TimeUltimaNotif := Now; - try - while (self.Connected) and (self.HeartBeatInterval>0) do - begin - //HeartBeat: - if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then - begin - if assigned(self.onHeartBeatTimer) then - self.onHeartBeatTimer(self); - TimeUltimaNotif := Now; - end; - TThread.Sleep(500); - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - end; - end; - - end).Start; -end; - -function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; -begin - pHeader.NameValueSeparator := ':'; - result := false; - if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then - raise Exception.Create(pHeader[0].Substring(9)); - - if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then - begin - if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then - result := true - else - if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then - result := true - else - raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); - - end; -end; - -function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; - const Bit: Byte): Cardinal; -begin - Result := aValue or (1 shl Bit); -end; - -procedure TIdSimpleWebSocketClient.writeText(pMsg: String); -begin - try - lInternalLock.Enter; - self.Socket.Write(encodeFrame(pMSG)); - finally - lInternalLock.Leave; - end; -end; - -procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; - pTriggerFunction: TFunc); -begin - self.lSyncFunctionTrigger := pTriggerFunction; - try - self.lSyncFunctionEvent := TSimpleEvent.Create(); - self.lSyncFunctionEvent.ResetEvent; - self.writeText(pMsg); - self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); - - finally - self.lSyncFunctionTrigger:= nil; - self.lSyncFunctionEvent.Free; - end; - - -end; - -end. From 2994943e84cb3ecd20cb4ab96d9df0e3d1f06aea Mon Sep 17 00:00:00 2001 From: arvanus Date: Tue, 10 Mar 2020 09:48:59 -0300 Subject: [PATCH 09/10] Delete IdWebSocketSimpleClient.pas.~2~ --- .../__history/IdWebSocketSimpleClient.pas.~2~ | 610 ------------------ 1 file changed, 610 deletions(-) delete mode 100644 Lib/Core/__history/IdWebSocketSimpleClient.pas.~2~ diff --git a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~2~ b/Lib/Core/__history/IdWebSocketSimpleClient.pas.~2~ deleted file mode 100644 index 9df93bfa5..000000000 --- a/Lib/Core/__history/IdWebSocketSimpleClient.pas.~2~ +++ /dev/null @@ -1,610 +0,0 @@ -{ - * Simple WebSocket client for Delphi - * http://www.websocket.org/echo.html - * Author: Lucas Rubian Schatz - * Copyright 2018, Indy Working Group. - * - * Date: 25/05/2019 - Jason R. Nelson (adaloveless) - Fix warning and incorrect URI in "GET" request - * Date: 22/02/2018 - TODO: implement methods for sending and receiving binary data, and support for bigger than 65536 bytes support -} -{ -Sample code: -//var lSWC:TIdSimpleWebSocketClient; -... -begin - lSWC := TIdSimpleWebSocketClient.Create(self); - lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent - lSWC.AutoCreateHandler := false; //you can set this as true in the majority of Websockets with ssl - if not lSWC.AutoCreateHandler then - begin - if lSWC.IOHandler=nil then - lSWC.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(lSWC); - (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - (lSWC.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; - end; - lSWC.Connect('wss://echo.websocket.org'); - lSWC.writeText('!!It worked!!'); -end; -} - -unit IdWebSocketSimpleClient; - -interface - -uses Classes, System.SysUtils, IdSSLOpenSSL, IdTCPClient, IdGlobal, IdCoderMIME, - IdHash, IdHashSHA, math, System.threading, DateUtils, System.SyncObjs, IdURI; -Type - TSWSCDataEvent = procedure(Sender: TObject; const Text: string) of object; - TSWSCErrorEvent = procedure(Sender: TObject; exception:Exception;const Text: string; var forceDisconnect) of object; -// * %x0 denotes a continuation frame -// * %x1 denotes a text frame -// * %x2 denotes a binary frame -// * %x3-7 are reserved for further non-control frames -// * %x8 denotes a connection close -// * %x9 denotes a ping -// * %xA denotes a pong -// * %xB-F are reserved for further control frames - - TOpCode = (TOContinuation, TOTextFrame, TOBinaryFrame, TOConnectionClose, TOPing, TOPong); - Const - TOpCodeByte: array[TopCode] of Byte = ($0, $1, $2, $8, $9, $A); - - Type - TIdSimpleWebSocketClient = class(TIdTCPClient) - private - SecWebSocketAcceptExpectedResponse: string; - FHeartBeatInterval: Cardinal; - FAutoCreateHandler: Boolean; - FURL: String; - FOnUpgrade: TnotifyEvent; - FonHeartBeatTimer: TNotifyEvent; - FonError: TSWSCErrorEvent; - FonPing: TSWSCDataEvent; - FonConnectionDataEvent: TSWSCDataEvent; - FonDataEvent: TSWSCDataEvent; - FUpgraded: Boolean; - - protected - - lInternalLock:TCriticalSection; - lClosingEventLocalHandshake:Boolean; - //Sync Event - lSyncFunctionEvent:TSimpleEvent; - lSyncFunctionTrigger:TFunc; - //Sync Event - - //get if a particular bit is 1 - function Get_a_Bit(const aValue: Cardinal; const Bit: Byte): Boolean; - //set a particular bit as 1 - function Set_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - //set a particular bit as 0 - function Clear_a_Bit(const aValue: Cardinal; const Bit: Byte): Cardinal; - - procedure readFromWebSocket;virtual; - function encodeFrame(pMsg:String; pOpCode:TOpCode=TOpCode.TOTextFrame):TIdBytes; - function verifyHeader(pHeader:TStrings):boolean; - procedure startHeartBeat; - procedure sendCloseHandshake; - function generateWebSocketKey:String; - - - - published - property onDataEvent: TSWSCDataEvent read FonDataEvent write FonDataEvent; - property onConnectionDataEvent: TSWSCDataEvent read FonConnectionDataEvent write FonConnectionDataEvent; - property onPing: TSWSCDataEvent read FonPing write FonPing; - property onError: TSWSCErrorEvent read FonError write FonError; - property onHeartBeatTimer: TNotifyEvent read FonHeartBeatTimer write FonHeartBeatTimer; - property OnUpgrade: TnotifyEvent read FOnUpgrade write FOnUpgrade; - property HeartBeatInterval: Cardinal read FHeartBeatInterval write FHeartBeatInterval; - property AutoCreateHandler: Boolean read FAutoCreateHandler write FAutoCreateHandler; - property URL: String read FURL write FURL; - public - - procedure Connect(pURL:String);overload; - procedure Close; - function Connected: Boolean; overload; - property Upgraded: Boolean read FUpgraded; - - procedure writeText(pMsg:String); - procedure writeTextSync(pMsg:String;pTriggerFunction:TFunc); - - constructor Create(AOwner: TComponent); - destructor Destroy; override; - -end; - -implementation - -{ TIdSimpleWebSocketClient } - -function TIdSimpleWebSocketClient.Clear_a_Bit(const aValue: Cardinal; - const Bit: Byte): Cardinal; -begin - Result := aValue and not (1 shl Bit); -end; - -procedure TIdSimpleWebSocketClient.Close; -begin - self.lInternalLock.Enter; - try - if self.Connected then - begin - self.sendCloseHandshake; - self.IOHandler.InputBuffer.Clear; - self.IOHandler.CloseGracefully; - self.Disconnect; - if assigned(self.OnDisconnected) then - self.OnDisconnected(self); - end; - finally - self.lInternalLock.Leave; - end -end; - -function TIdSimpleWebSocketClient.generateWebSocketKey():String; -var rand:TidBytes; - I: Integer; -begin - SetLength(rand, 16); - for I := low(rand) to High(rand) do - rand[i] := byte(random(255)); - - result := TIdEncoderMIME.EncodeBytes(rand); //generates a random Base64String - self.SecWebSocketAcceptExpectedResponse := Result + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; //fixed string, see: https://tools.ietf.org/html/rfc6455#section-1.3 - - with TIdHashSHA1.Create do - try - SecWebSocketAcceptExpectedResponse := TIdEncoderMIME.EncodeBytes(HashString( self.SecWebSocketAcceptExpectedResponse )); - finally - Free; - end; -end; - -function TIdSimpleWebSocketClient.Connected: Boolean; -begin - result := false; //for some reason, if its not connected raises an error after connection lost! - try - result := inherited; - except - end -end; - -procedure TIdSimpleWebSocketClient.Connect(pURL: String); -var URI : TIdURI; - lSecure : Boolean; -begin - uri := nil; - try - lClosingEventLocalHandshake := false; - URI := TIdURI.Create(pURL); - self.URL := pURL; - self.Host := URI.Host; - URI.Protocol := ReplaceOnlyFirst(URI.Protocol.ToLower, 'ws', 'http'); //replaces wss to https too, as apparently indy does not support ws(s) yet - - if URI.Path='' then - URI.Path := '/'; - lSecure := uri.Protocol='https'; - - if URI.Port.IsEmpty then - begin - if lSecure then - self.Port := 443 - else - self.Port := 80; - end - else - self.Port := StrToInt(URI.Port); - - - if lSecure and (self.IOHandler=nil) then - begin - if self.AutoCreateHandler then //for simple life - begin - self.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self); - (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.Mode := TIdSSLMode.sslmClient; - (self.IOHandler as TIdSSLIOHandlerSocketOpenSSL).SSLOptions.SSLVersions := [TIdSSLVersion.sslvTLSv1, TIdSSLVersion.sslvTLSv1_1, TIdSSLVersion.sslvTLSv1_2]; //depending on your server, change this at your code; - end - else - raise Exception.Create('Please, inform a TIdSSLIOHandlerSocketOpenSSL descendant'); - end; - - if self.Connected then - raise Exception.Create('Already connected, verify'); - - - inherited Connect; - if not URI.Port.IsEmpty then - URI.Host := URI.Host+':'+URI.Port; - self.Socket.WriteLn(format('GET %s HTTP/1.1', [uri.path+uri.Document])); - self.Socket.WriteLn(format('Host: %s', [URI.Host])); - self.Socket.WriteLn('User-Agent: Delphi WebSocket Simple Client'); - self.Socket.WriteLn('Connection: keep-alive, Upgrade'); - self.Socket.WriteLn('Upgrade: WebSocket'); - self.Socket.WriteLn('Sec-WebSocket-Version: 13'); - self.Socket.WriteLn(format('Sec-WebSocket-Key: %s', [generateWebSocketKey()])); - self.Socket.WriteLn(''); - - readFromWebSocket; - startHeartBeat; - finally - URI.Free; - end; -end; - -procedure TIdSimpleWebSocketClient.sendCloseHandshake; -begin - self.lClosingEventLocalHandshake := true; - self.Socket.Write(self.encodeFrame('', TOpCode.TOConnectionClose)); - TThread.Sleep(200); -end; - -constructor TIdSimpleWebSocketClient.Create(AOwner: TComponent); -begin - inherited; - lInternalLock := TCriticalSection.Create; - Randomize; - self.AutoCreateHandler := false; - self.HeartBeatInterval := 30000; -end; - -destructor TIdSimpleWebSocketClient.Destroy; -begin - lInternalLock.Free; - if self.AutoCreateHandler and Assigned(self.IOHandler) then - self.IOHandler.Free; - inherited; -end; - -function TIdSimpleWebSocketClient.encodeFrame(pMsg:String; pOpCode:TOpCode): TIdBytes; -var FIN, MASK: Cardinal; - MaskingKey:array[0..3] of cardinal; - EXTENDED_PAYLOAD_LEN:array[0..3] of Cardinal; - buffer:Tidbytes; - I: Integer; - xor1, xor2:char ; - ExtendedPayloadLength:Integer; -begin - FIN:=0; - FIN := Set_a_bit(FIN,7) or TOpCodeByte[pOpCode]; - - MASK := set_a_bit(0,7); - - ExtendedPayloadLength:= 0; - if pMsg.Length<=125 then - MASK := MASK+pMsg.Length - else - if pMsg.Length 0; -end; - -procedure TIdSimpleWebSocketClient.readFromWebSocket; -var - lSpool: string; - b:Byte; - T: ITask; - lPos:Integer; - lSize:Integer; - lOpCode:Byte; - linFrame:Boolean; - lMasked:boolean; - lForceDisconnect:Boolean; - lHeader:TStringlist; -// lClosingRemoteHandshake:Boolean; -// lPing:Boolean; -begin - lSpool := ''; - lPos := 0; - lSize := 0; - lOpCode := 0; - lMasked := false; - FUpgraded := false; -// lPing := false; -// pingByte := Set_a_Bit(0,7); //1001001//PingByte -// pingByte := Set_a_Bit(pingByte,3); -// pingByte := Set_a_Bit(pingByte,0); -// closeByte := Set_a_Bit(0,7);//1001000//CloseByte -// closeByte := Set_a_Bit(closeByte,3); - - lHeader := TStringList.Create; - linFrame := false; - - try - while Connected and not FUpgraded do //First, we guarantee that this is an valid Websocket - begin - b := self.Socket.ReadByte; - - lSpool := lSpool+chr(b); - if (not FUpgraded and (b=ord(#13))) then - begin - if lSpool=#10#13 then - begin - - //verifies header - try - if not verifyHeader(lHeader) then - begin - raise Exception.Create('URL is not from an valid websocket server, not a valid response header found'); - end; - finally - lHeader.Free; - end; - - FUpgraded := true; - lSpool := ''; - lPos := 0; - end - else - begin - if assigned(onConnectionDataEvent) then - onConnectionDataEvent(self, lSpool); - - lHeader.Add(lSpool.Trim); - lSpool := ''; - end; - end; - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - exit; - end; - end; - - - if Connected then - T := TTask.Run( - procedure - begin - - try - while Connected do - begin - - b := self.Socket.ReadByte; - - - if FUpgraded and (lPos=0) and Get_a_Bit(b, 7) then //FIN - begin - linFrame := true; - lOpCode := Clear_a_Bit(b, 7); - - inc(lPos); - - - if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then - end - else if FUpgraded and (lPos=1) then - begin - lMasked := Get_a_Bit(b, 7); - lSize := b; - if lMasked then - lSize := b-set_a_bit(0,7); - if lSize=0 then - lPos := 0 - else - if lSize=126 then // get size from 2 next bytes - begin - b := self.Socket.ReadByte; - lSize := Round(b*intpower(2,8)); - b := self.Socket.ReadByte; - lSize := lSize+Round(b*intpower(2,0)); - end - else if lSize=127 then - raise Exception.Create('TODO: Size block bigger than supported by this framework, fix is welcome'); - - inc(lPos); - end - else - if linFrame then - begin - lSpool := lSpool+chr(b); - - if (FUpgraded and (Length(lSpool)=lSize)) then - begin - lPos := 0; - linFrame := false; - - if lOpCode=TOpCodeByte[TOpCode.TOPing] then - begin - try - lInternalLock.Enter; - self.Socket.Write(encodeFrame(lSpool, TOpCode.TOPong)); - finally - lInternalLock.Leave; - end; - - if assigned(onPing) then - onPing(self, lSpool); - end - else - begin - if FUpgraded and assigned(FonDataEvent) and (not (lOpCode=TOpCodeByte[TOpCode.TOConnectionClose])) then - onDataEvent(self, lSpool); - if assigned(self.lSyncFunctionTrigger) then - begin - if self.lSyncFunctionTrigger(lSpool) then - begin - self.lSyncFunctionEvent.SetEvent; - end; - end; - end; - - lSpool := ''; - if lOpCode=TOpCodeByte[TOpCode.TOConnectionClose] then - begin - if not Self.lClosingEventLocalHandshake then - begin - self.Close; - if assigned(self.OnDisconnected) then - self.OnDisconnected(self); - end; - break - end; - - end; - end; - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - end; - end; - end); - - if ((not Connected) or (not FUpgraded))and (not (( lOpCode=TOpCodeByte[TOpCode.TOConnectionClose]) or lClosingEventLocalHandshake))then - begin - - raise Exception.Create('Websocket not connected or timeout'+QuotedStr(lSpool)); - end - else - if assigned(self.OnUpgrade) then - self.OnUpgrade(self); - -end; - -procedure TIdSimpleWebSocketClient.startHeartBeat; -var TimeUltimaNotif:TDateTime; - lForceDisconnect:Boolean; -begin - TThread.CreateAnonymousThread(procedure begin - TimeUltimaNotif := Now; - try - while (self.Connected) and (self.HeartBeatInterval>0) do - begin - //HeartBeat: - if (MilliSecondsBetween(TimeUltimaNotif, Now) >= Floor(self.HeartBeatInterval)) then - begin - if assigned(self.onHeartBeatTimer) then - self.onHeartBeatTimer(self); - TimeUltimaNotif := Now; - end; - TThread.Sleep(500); - end; - except - on e:Exception do - begin - lForceDisconnect := true; - if assigned(self.onError) then - self.onError(self, e, e.Message, lForceDisconnect); - if lForceDisconnect then - self.Close; - end; - end; - - end).Start; -end; - -function TIdSimpleWebSocketClient.verifyHeader(pHeader: TStrings): boolean; -begin - pHeader.NameValueSeparator := ':'; - result := false; - if (pos('HTTP/1.1 101', pHeader[0])=0) and (pos('HTTP/1.1', pHeader[0])>0) then - raise Exception.Create(pHeader[0].Substring(9)); - - if (pHeader.Values['Connection'].Trim.ToLower='upgrade') and (pHeader.Values['Upgrade'].Trim.ToLower='websocket') then - begin - if pHeader.Values['Sec-WebSocket-Accept'].Trim=self.SecWebSocketAcceptExpectedResponse then - result := true - else - if pHeader.Values['Sec-WebSocket-Accept'].trim.IsEmpty then - result := true - else - raise Exception.Create('Unexpected return key on Sec-WebSocket-Accept in handshake'); - - end; -end; - -function TIdSimpleWebSocketClient.Set_a_Bit(const aValue: Cardinal; - const Bit: Byte): Cardinal; -begin - Result := aValue or (1 shl Bit); -end; - -procedure TIdSimpleWebSocketClient.writeText(pMsg: String); -begin - try - lInternalLock.Enter; - self.Socket.Write(encodeFrame(pMSG)); - finally - lInternalLock.Leave; - end; -end; - -procedure TIdSimpleWebSocketClient.writeTextSync(pMsg: String; - pTriggerFunction: TFunc); -begin - self.lSyncFunctionTrigger := pTriggerFunction; - try - self.lSyncFunctionEvent := TSimpleEvent.Create(); - self.lSyncFunctionEvent.ResetEvent; - self.writeText(pMsg); - self.lSyncFunctionEvent.WaitFor(self.ReadTimeout); - - finally - self.lSyncFunctionTrigger:= nil; - self.lSyncFunctionEvent.Free; - end; - - -end; - -end. From f6815293496d643ad3397bbd7d8f957a4bbe897a Mon Sep 17 00:00:00 2001 From: bintesztelo <102803512+bintesztelo@users.noreply.github.com> Date: Sun, 17 Apr 2022 23:05:20 +0200 Subject: [PATCH 10/10] Update IdWebSocketSimpleClient.pas To connect Binance and to SUBSCRIBE one crypto. --- Lib/Core/IdWebSocketSimpleClient.pas | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Lib/Core/IdWebSocketSimpleClient.pas b/Lib/Core/IdWebSocketSimpleClient.pas index 725487b9d..e68cde7e8 100644 --- a/Lib/Core/IdWebSocketSimpleClient.pas +++ b/Lib/Core/IdWebSocketSimpleClient.pas @@ -10,8 +10,15 @@ } { Sample code: + +procedure Tform1.lSWC1DataEvent(Sender: TObject; const Text: string); +begin + smemo1.Lines.Add(text); +end; + //var lSWC:TIdSimpleWebSocketClient; ... + begin lSWC := TIdSimpleWebSocketClient.Create(self); lSWC.onDataEvent := self.lSWC1DataEvent; //TSWSCDataEvent @@ -25,6 +32,9 @@ end; lSWC.Connect('wss://echo.websocket.org'); lSWC.writeText('!!It worked!!'); + //OR: + //lSWC.Connect('wss://stream.binance.com:9443/ws'); + //lSWC.writeText('{"method": "SUBSCRIBE","params": ["btcusdt@kline_15m"],"id": 1}'); end; }