1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648 |
- {
- $Id: header,v 1.1 2000/07/13 06:33:45 michael Exp $
- This file is part of the Free Component Library (FCL)
- Copyright (c) 2021 - by the Free Pascal development team
- Abstract websocket protocol implementation - objects only
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- unit fpwebsocket;
- {$mode objfpc}
- {$h+}
- {$modeswitch advancedrecords}
- {$modeswitch typehelpers}
- interface
- uses
- Classes, SysUtils, sockets, ssockets;
- Const
- SSecWebSocketGUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
- SSecWebsocketProtocol = 'Sec-WebSocket-Protocol';
- SSecWebsocketVersion = 'Sec-WebSocket-Version';
- SSecWebsocketExtensions = 'Sec-WebSocket-Extensions';
- SSecWebsocketKey = 'Sec-WebSocket-Key';
- SSecWebsocketAccept = 'Sec-WebSocket-Accept';
- MinFrameSize = 4;
- DefaultWebSocketVersion = 13;
- // Opcodes
- FlagReserved = $F;
- FlagContinuation = $0;
- FlagText = $1;
- FlagBinary = $2;
- FlagClose = $8;
- FlagPing = $9;
- FlagPong = $A;
- // For SVR etc.
- FlagTwoBytes = 126;
- FlagEightBytes = 127;
- FlagFinalFrame : Byte = $80;
- FlagMasked : Byte = $80;
- FlagLengthMask : Byte = $7F;
- FlagRES1 = $40;
- FlagRES2 = $20;
- FlagRES3 = $10;
- CLOSE_NORMAL_CLOSURE = 1000;
- CLOSE_GOING_AWAY = 1001;
- CLOSE_PROTOCOL_ERROR = 1002;
- CLOSE_UNSUPORTED_DATA = 1003;
- CLOSE_RESERVER = 1004;
- CLOSE_NO_STATUS_RCVD = 1005;
- CLOSE_ABNORMAL_CLOSURE = 1006;
- CLOSE_INVALID_FRAME_PAYLOAD_DATA = 1007;
- CLOSE_POLICY_VIOLATION = 1008;
- CLOSE_MESSAGE_TOO_BIG = 1009;
- CLOSE_MANDRATORY_EXT = 1010;
- CLOSE_INTERNAL_SERVER_ERROR = 1011;
- CLOSE_TLS_HANDSHAKE = 1015;
- type
- EWebSocket = Class(Exception);
- EWSHandShake = class(EWebSocket);
- TFrameType = (ftContinuation,ftText,ftBinary,ftClose,ftPing,ftPong,ftFutureOpcodes);
- TFrameTypes = Set of TFrameType;
- TFrameSequence = (fsFirst,fsContinuation,fsLast);
- TFrameSequences = Set of TFrameSequence;
- TIncomingResult = (irNone, // No data waiting
- irWaiting, // Data waiting
- irOK, // Data was waiting and handled
- irClose // Data was waiting, handled, and we must disconnect (CloseState=csClosed)
- );
- { TFrameTypeHelper }
- TFrameTypeHelper = Type helper for TFrametype
- private
- function GetAsFlag: Byte;
- procedure SetAsFlag(AValue: Byte);
- Public
- Property asFlag : Byte Read GetAsFlag Write SetAsFlag;
- end;
- { TWSHeaders }
- TWSHeaders = class
- private
- FRawHeaders: TStrings;
- FResource: String;
- Protected
- Function GetS(aIdx : Integer) : String;
- procedure SetS(AIndex: Integer; const AValue: string);
- Function GetH(const aName : string) : String;
- procedure SetH(const aName, aValue: string);
- Public
- Const
- WSHeaderNames : Array[0..8] of string
- = ('Host','Origin','Connection','Upgrade',SSecWebSocketProtocol,
- SSecWebSocketVersion,SSecWebSocketExtensions,SSecWebSocketKey,
- SSecWebSocketAccept);
- public
- constructor Create(const aResource : String; const AHeaderList: TStrings); virtual;
- Destructor Destroy; override;
- Property RawHeaders : TStrings Read FRawHeaders;
- property Resource : String Read FResource Write FResource;
- property Host: string Index 0 read GetS Write SetS;
- property Origin: string Index 1 read GetS Write SetS;
- property Connection: string Index 2 read GetS Write SetS;
- property Upgrade: string Index 3 read GetS Write SetS;
- property Protocol: string Index 4 read GetS Write SetS;
- property Version: string Index 5 read GetS Write SetS;
- property Extensions : String Index 6 read GetS Write SetS;
- property Key: string Index 7 read GetS Write SetS;
- end;
- { TWSHandShakeRequest }
- TWSHandShakeRequest = Class(TWSHeaders)
- private
- FPort: Word;
- Public
- Constructor Create(const aResource : string; const aExtraHeaders : TStrings); override;
- class function GenerateKey: String; static;
- Procedure ToStrings(aHeaders : TStrings);
- Property Port : Word Read FPort Write FPort;
- End;
- { TWSHandShakeResponse }
- TWSHandShakeResponse = Class (TWSHeaders)
- private
- FHTTPVersion: String;
- FStatusCode: Integer;
- FStatusText: String;
- Public
- Constructor Create(const aResource : string; const aExtraHeaders : TStrings); override;
- Procedure ToStrings(aHandShake : TWSHandshakeRequest; aResponse : TStrings; AddStatusLine : Boolean);
- Property HTTPVersion : String Read FHTTPVersion Write FHTTPVersion;
- Property StatusCode : Integer Read FStatusCode Write FStatusCode;
- Property StatusText : String Read FStatusText Write FSTatusText;
- property Accept : String Index 8 read GetS Write SetS;
- End;
- {$INTERFACES CORBA}
- { TWSTransport }
- { IWSTransport }
- IWSTransport = Interface
- // Check if transport can read data
- Function CanRead(aTimeOut: Integer) : Boolean;
- // Read length of buffer bytes. Raise exception if no data read
- Procedure ReadBuffer (aBytes : TBytes);
- // Read at most aCount bytes into buffer. Return number of bytes actually read, set length of buffer to actually read
- function ReadBytes (var aBytes : TBytes; aCount : Integer) : Integer;
- // Write at most aCount bytes.
- function WriteBytes (aBytes : TBytes; aCount : Integer) : Integer;
- // Write complete buffer. Raise exception if not all bytes were written.
- Procedure WriteBuffer (aBytes : TBytes);
- function ReadLn : String;
- function PeerIP: string;
- end;
- { TWSSocketHelper }
- TWSSocketHelper = Class (TObject,IWSTransport)
- Private
- FSocket : TSocketStream;
- Public
- Constructor Create (aSocket : TSocketStream);
- Function CanRead(aTimeOut: Integer) : Boolean;
- function PeerIP: string; virtual;
- function ReadLn : String; virtual;
- function ReadBytes (var aBytes : TBytes; aCount : Integer) : Integer; virtual;
- Procedure ReadBuffer (aBytes : TBytes); virtual;
- function WriteBytes (aBytes : TBytes; aCount : Integer) : Integer; virtual;
- Procedure WriteBuffer (aBytes : TBytes);
- Property Socket : TSocketStream Read FSocket;
- end;
- TWSTransport = class(TObject, IWSTransport)
- Private
- FHelper : TWSSocketHelper;
- FStream : TSocketStream;
- function GetSocket: TSocketStream;
- Public
- Constructor Create(aStream : TSocketStream);
- Destructor Destroy; override;
- Procedure CloseSocket;
- Property Helper : TWSSocketHelper Read FHelper Implements IWSTransport;
- Property Socket : TSocketStream Read GetSocket;
- end;
- { TWSFramePayload }
- TWSFramePayload = record
- DataLength: QWord;
- // Data is unmasked
- Data: TBytes;
- MaskKey: dword;
- Masked: Boolean;
- Procedure ReadData(var Content : TBytes; aTransport : IWSTransport);
- Procedure Read(buffer: TBytes; aTransport : IWSTransport);
- class procedure DoMask(var aData: TBytes; Key: DWORD); static;
- class procedure CopyMasked(SrcData: TBytes; var DestData: TBytes; Key: DWORD; aOffset: Integer); static;
- class function CopyMasked(SrcData: TBytes; Key: DWORD) : TBytes; static;
- end;
- { TWSFrame }
- TWSFrame = Class
- private
- FFrameType: TFrameType;
- FFinalFrame: Boolean;
- FRSV: Byte;
- FPayload : TWSFramePayload;
- protected
- function Read(aTransport: IWSTransport): boolean;
- function GetAsBytes : TBytes; virtual;
- Public
- // Read a message from transport. Returns Nil if the connection was closed when reading.
- class function CreateFromStream(aTransport : IWSTransport): TWSFrame;
- public
- constructor Create(aType: TFrameType; aIsFinal: Boolean; APayload: TBytes; aMask : Integer = 0); overload; virtual;
- constructor Create(Const aMessage : UTF8String; aMask : Integer = 0); overload; virtual;
- constructor Create(aType: TFrameType; aIsFinal: Boolean = True; aMask: Integer = 0); overload; virtual;
- property Reserved : Byte read FRSV write FRSV;
- property FinalFrame: Boolean read FFinalFrame write FFinalFrame;
- property Payload : TWSFramePayload Read FPayload Write FPayLoad;
- property FrameType: TFrameType read FFrameType;
- Property AsBytes : TBytes Read GetAsBytes;
- end;
- TWSFrameClass = Class of TWSFrame;
- { TWSMessage }
- TWSMessage = record
- private
- function GetAsString: UTF8String;
- function GetAsUnicodeString: UnicodeString;
- Public
- PayLoad : TBytes;
- Sequences : TFrameSequences;
- IsText : Boolean;
- Property AsString : UTF8String Read GetAsString;
- Property AsUTF8String : UTF8String Read GetAsString;
- Property AsUnicodeString : UnicodeString Read GetAsUnicodeString;
- end;
- TWSMessageEvent = procedure(Sender: TObject; const aMessage : TWSMessage) of object;
- TWSControlEvent = procedure(Sender: TObject; aType : TFrameType; const aData: TBytes) of object;
- TCloseState = (csNone,csSent,csReceived,csClosed);
- TCloseStates = Set of TCloseState;
- TWSOption = (woPongExplicit, // Send Pong explicitly, not implicitly.
- woCloseExplicit, // SeDo Close explicitly, not implicitly.
- woIndividualFrames, // Send frames one by one, do not concatenate.
- woSkipUpgradeCheck, // Skip handshake "Upgrade:" HTTP header cheack.
- woSkipVersionCheck // Skip handshake "Sec-WebSocket-Version' HTTP header check.
- );
- TWSOptions = set of TWSOption;
- { TWSConnection }
- TWSConnection = class
- Private
- class var _ConnectionCount : {$IFDEF CPU64}QWord{$ELSE}Cardinal{$ENDIF};
- private
- FAutoDisconnect: Boolean;
- FConnectionID: String;
- FFreeUserData: Boolean;
- FOnDisconnect: TNotifyEvent;
- FOutgoingFrameMask: Integer;
- FOwner: TComponent;
- FUserData: TObject;
- FWebSocketVersion: Integer;
- FInitialOpcode : TFrameType;
- FMessageContent : TBytes;
- FHandshakeRequest: TWSHandShakeRequest;
- FOnMessageReceived: TWSMessageEvent;
- FOnControl: TWSControlEvent;
- FCloseState : TCloseState;
- FOptions: TWSOptions;
- Function GetPeerIP : String;
- protected
- procedure AllocateConnectionID; virtual;
- Procedure SetCloseState(aValue : TCloseState); virtual;
- Procedure DoDisconnect; virtual; abstract;
- // Read message from connection. Return False if connection was closed.
- function DoReadMessage: Boolean;
- procedure DispatchEvent(aInitialType : TFrameType; aFrame: TWSFrame);
- Procedure SetHandShakeRequest(aRequest : TWSHandShakeRequest);
- Function HandleIncoming(aFrame: TWSFrame) : Boolean; virtual;
- function GetHandshakeCompleted: Boolean; virtual; abstract;
- Function GetTransport : IWSTransport; virtual; abstract;
- property Owner : TComponent Read FOwner;
- Public
- Type
- TConnectionIDAllocator = Procedure(out aID : String) of object;
- class var IDAllocator : TConnectionIDAllocator;
- Public
- Constructor Create(aOwner : TComponent; aOptions : TWSOptions); virtual;
- destructor Destroy; override;
- // Extract close data
- Class Function GetCloseData(aBytes : TBytes; Out aReason : String) : Word;
- // Send close with message data
- procedure Close(aData : TBytes = Nil); overload;
- procedure Close(aMessage : UTF8String); overload;
- procedure Close(aMessage : UTF8String; aReason: word); overload;
- // Check incoming message
- function CheckIncoming(aTimeout: Integer; DoRead : Boolean = True): TIncomingResult;
- // read & process incoming message. Return nil if connection was close.
- function ReadMessage: Boolean;
- // Disconnect
- Procedure Disconnect;
- // Descendents can override this to provide custom frames
- Function FrameClass : TWSFrameClass; virtual;
- // Send raw frame. No checking is done !
- procedure Send(aFrame : TWSFrame); virtual;
- // Send message
- procedure Send(const AMessage: UTF8string);
- // Send binary data
- procedure Send(const ABytes: TBytes);
- // Send control frame. ftPing,ftPong,ftClose
- procedure Send(aFrameType: TFrameType; aData : TBytes = Nil);
- // Disconnect when status is set to csClosed;
- Property AutoDisconnect : Boolean Read FAutoDisconnect Write FAutoDisconnect;
- // Close frame handling
- Property CloseState : TCloseState Read FCloseState;
- // Connection ID, allocated during create
- Property ConnectionID : String Read FConnectionID;
- // If set to true, the owner data is freed when the connection is freed.
- Property FreeUserData : Boolean Read FFreeUserData Write FFreeUserData;
- // Request headers during handshake
- property HandshakeRequest: TWSHandShakeRequest read FHandshakeRequest;
- // Has handshake been completed ?
- property HandshakeCompleted: Boolean read GetHandshakeCompleted;
- // Options passed by server
- Property Options : TWSOptions Read FOptions;
- // Mask to use when sending frames. Set to nonzero value to send masked frames.
- Property OutgoingFrameMask : Integer Read FOutgoingFrameMask Write FOutgoingFrameMask;
- // Peer IP address
- property PeerIP: string read GetPeerIP;
- // Transport in use by this connection
- property Transport: IWSTransport read GetTransport;
- // User data to associate with this connection.
- Property UserData : TObject Read FUserData Write FUserData;
- // Socket version to check for
- Property WebSocketVersion : Integer Read FWebSocketVersion Write FWebSocketVersion;
- // Called when text/binary data was received
- property OnMessageReceived: TWSMessageEvent read FOnMessageReceived write FOnMessageReceived;
- // Called when Ping, Pong, Close control messages come in.
- property OnControl: TWSControlEvent read FOnControl write FOnControl;
- // Called when disconnect is called.
- property OnDisconnect: TNotifyEvent read FOnDisconnect write FOnDisconnect;
- end;
- { TWSClientTransport }
- TWSClientTransport = Class(TWSTransport)
- end;
- { TWSClientConnection }
- TWSClientConnection = Class(TWSConnection)
- private
- FTransport : TWSClientTransport;
- FHandshakeResponse: TWSHandShakeResponse;
- Protected
- function GetTransport : IWSTransport ; override;
- public
- Constructor Create(aOwner: TComponent; aTransport : TWSClientTransport; aOptions : TWSOptions); reintroduce; overload;
- Destructor Destroy; override;
- //
- function GetHandshakeCompleted: Boolean; override;
- // Owned by connection
- Property ClientTransport : TWSClientTransport Read FTransport;
- //
- Property HandShakeResponse : TWSHandShakeResponse Read FHandshakeResponse Write FHandshakeResponse;
- End;
- { TWSServerTransport }
- TWSServerTransport = class(TWSTransport)
- end;
- { TWSServerConnection }
- TWSConnectionHandshakeEvent = procedure (aRequest : TWSHandShakeRequest; aResponse : TWSHandShakeResponse) of object;
- TWSServerConnection = Class(TWSConnection)
- Private
- FExtraHeaders: TStrings;
- FHandshakeResponseSent: Boolean;
- FOnHandShake: TWSConnectionHandshakeEvent;
- FTransport : TWSServerTransport;
- Protected
- Procedure DoDisconnect; override;
- function GetTransport: IWSTransport; override;
- procedure DoPrepareHandshakeResponse(aRequest : TWSHandShakeRequest; aResponse : TWSHandShakeResponse); virtual;
- function GetHandshakeCompleted: Boolean; override;
- public
- // Transport is owned by connection
- constructor Create(aOwner : TComponent; aTransport : TWSServerTransport; aOptions : TWSOptions); overload;
- // disconnect
- destructor Destroy; override;
- // Do full circle.
- Procedure PerformHandshake; virtual;
- // Given a request, send response
- function DoHandshake(const aRequest : TWSHandShakeRequest): Boolean;
- // Has handshake been exchanged?
- property HandshakeResponseSent: Boolean read FHandshakeResponseSent;
- // Extra handshake headers
- Property ExtraHeaders : TStrings Read FExtraHeaders;
- // Owned by connection
- property ServerTransport : TWSServerTransport Read FTransport;
- // Called when exchanging handshake
- Property OnHandshake : TWSConnectionHandshakeEvent Read FOnHandShake write FOnHandshake;
- end;
- Type
- { TBytesHelper }
- TBytesHelper = Type helper for TBytes
- // No swapping of bytes
- Function ToDword(aOffset : Integer = 0) : DWORD;
- Function ToInt32(aOffset : Integer = 0) : LongInt;
- Function ToWord(aOffset : Integer = 0) : Word;
- Function ToQWord(aOffset : Integer = 0) : QWord;
- Procedure FromDword(const aData : DWORD; aOffset : Integer = 0);
- Procedure FromInt32(const aData : Longint; aOffset : Integer = 0);
- Procedure FromWord(const aData : Word; aOffset : Integer = 0);
- Procedure FromQWord(const aData : QWord; aOffset : Integer = 0);
- procedure Reverse(var Dest: TBytes; Offset: Integer; Size: Integer);
- Function Reverse(Offset: Integer; Size: Integer) : TBytes;
- Procedure Append(aData : TBytes);
- end;
- Resourcestring
- SErrNotSimpleOperation = 'Frame type %d is not a simple operation.';
- SErrCloseAlreadySent = 'Close message already sent, cannot send more data.';
- SErrHandshakeInComplete = 'Operation cannot be performed while the handshake is not completed';
- SErrConnectionActive = 'Operation cannot be performed while the websocket connection is active';
- SErrConnectionInActive = 'Operation cannot be performed while the websocket connection is not active';
- SErrServerActive = 'Operation cannot be performed while the websocket connection is active';
- SErrInvalidSizeFlag = 'Invalid size flag: %d';
- SErrInvalidFrameType = 'Invalid frame type flag: %d';
- function DecodeBytesBase64(const s: string; Strict: boolean = false) : TBytes;
- function EncodeBytesBase64(const aBytes : TBytes) : String;
- implementation
- uses strutils, sha1,base64;
- { TFrameTypeHelper }
- function TFrameTypeHelper.GetAsFlag: Byte;
- Const
- Flags : Array[TFrameType] of byte = (FlagContinuation,FlagText,FlagBinary,FlagClose,FlagPing,FlagPong,FlagReserved);
- begin
- Result:=Flags[Self];
- end;
- procedure TFrameTypeHelper.SetAsFlag(AValue: Byte);
- begin
- case aValue of
- FlagContinuation : Self:=ftContinuation;
- FlagText : Self:=ftText;
- FlagBinary : Self:=ftBinary;
- FlagClose : Self:=ftClose;
- FlagPing : Self:=ftPing;
- FlagPong : Self:=ftPong;
- else
- Self:=ftFutureOpcodes;
- //Raise EConvertError.CreateFmt(SErrInvalidFrameType,[aValue]);
- end;
- end;
- { TWSServerTransport }
- { TWSHandShakeResponse }
- constructor TWSHandShakeResponse.Create(const aResource: string; const aExtraHeaders: TStrings);
- begin
- inherited Create(aResource, aExtraHeaders);
- HTTPVersion:='1.1';
- StatusCode:=101;
- StatusText:='Switching Protocols';
- end;
- procedure TWSHandShakeResponse.ToStrings(aHandShake: TWSHandshakeRequest; aResponse: TStrings; AddStatusLine: Boolean);
- Function CalcKey : String;
- Var
- B : TBytes;
- hash : TSHA1Digest;
- K : string;
- begin
- // respond key
- b:=[];
- k:= Trim(aHandshake.Key) + SSecWebSocketGUID;
- hash:=sha1.SHA1String(k);
- SetLength(B,SizeOf(hash));
- Move(Hash,B[0],Length(B));
- Result:=EncodeBytesBase64(B);
- end;
- begin
- // Fill needed headers
- Upgrade:='websocket';
- Connection:='Upgrade';
- // Chrome doesn't like it if you send an empty protocol header.
- if (Protocol='') and (aHandshake.Protocol<>'') then
- Protocol:=aHandshake.Protocol;
- if Version='' then
- Version:=IntToStr(DefaultWebSocketVersion);
- if Accept='' then
- Accept:=CalcKey;
- if AddStatusLine then
- aResponse.Add('HTTP/%s %d %s',[HTTPVersion,StatusCode,StatusText]);
- aResponse.AddStrings(RawHeaders);
- end;
- { TWSTransport }
- function TWSTransport.GetSocket: TSocketStream;
- begin
- Result:=FHelper.Socket
- end;
- constructor TWSTransport.Create(aStream : TSocketStream);
- begin
- FStream:=aStream;
- FHelper:=TWSSocketHelper.Create(FStream);
- end;
- destructor TWSTransport.Destroy;
- begin
- FreeAndNil(FHelper);
- FreeAndNil(FStream);
- inherited Destroy;
- end;
- procedure TWSTransport.CloseSocket;
- begin
- sockets.CloseSocket(FStream.Handle);
- end;
- { TWSTransport }
- constructor TWSSocketHelper.Create(aSocket: TSocketStream);
- begin
- FSocket:=aSocket;
- {$if defined(FreeBSD) or defined(Linux)}
- FSocket.ReadFlags:=MSG_NOSIGNAL;
- FSocket.WriteFlags:=MSG_NOSIGNAL;
- {$endif}
- end;
- function TWSSocketHelper.CanRead(aTimeOut: Integer): Boolean;
- begin
- Result:=FSocket.CanRead(aTimeout);
- end;
- function TWSSocketHelper.PeerIP: string;
- Function SocketAddrToString(ASocketAddr: TSockAddr): String;
- begin
- if ASocketAddr.sa_family = AF_INET then
- Result := NetAddrToStr(ASocketAddr.sin_addr)
- else // no ipv6 support yet
- Result := '';
- end;
- begin
- Result:= SocketAddrToString(FSocket.RemoteAddress);
- end;
- function TWSSocketHelper.ReadLn: String;
- Var
- C : Byte;
- aSize : integer;
- begin
- // Preset
- Result:='';
- SetLength(Result,255);
- aSize:=0;
- C:=0;
- While (FSocket.Read(C,1)=1) and (C<>10) do
- begin
- Inc(aSize);
- if aSize>Length(Result) then
- SetLength(Result,Length(Result)+255);
- Result[aSize]:=AnsiChar(C);
- end;
- if (aSize>0) and (Result[aSize]=#13) then
- Dec(aSize);
- SetLength(Result,aSize);
- end;
- function TWSSocketHelper.ReadBytes(var aBytes: TBytes; aCount: Integer): Integer;
- var
- buf: TBytes;
- aPos, toRead: QWord;
- begin
- aPos := 0;
- SetLength(aBytes, aCount);
- repeat
- SetLength(buf, aCount);
- Result := FSocket.ReadData(buf, aCount - aPos);
- if Result = -1 then
- break;
- SetLength(buf, Result);
- Move(buf[0], aBytes[aPos], Result);
- Inc(aPos, Result);
- ToRead := aCount - aPos;
- Result := aCount;
- until toRead <= 0;
- end;
- procedure TWSSocketHelper.ReadBuffer(aBytes: TBytes);
- begin
- FSocket.ReadBuffer(aBytes,Length(ABytes));
- end;
- function TWSSocketHelper.WriteBytes(aBytes: TBytes; aCount: Integer): Integer;
- begin
- Result:=FSocket.WriteData(aBytes,aCount);
- end;
- procedure TWSSocketHelper.WriteBuffer(aBytes: TBytes);
- begin
- FSocket.WriteBuffer(aBytes,0,Length(aBytes));
- end;
- { TWSMessage }
- function TWSMessage.GetAsString: UTF8String;
- begin
- Result:=TEncoding.UTF8.GetAnsiString(Payload);
- end;
- function TWSMessage.GetAsUnicodeString: UnicodeString;
- begin
- Result:=UTF8Decode(asUTF8String);
- end;
- { TBytesHelper }
- function TBytesHelper.Reverse(Offset: Integer; Size: Integer): TBytes;
- begin
- Result:=[];
- Reverse(Result,Offset,Size);
- end;
- procedure TBytesHelper.Append(aData: TBytes);
- Var
- sLen,dLen : SizeInt;
- begin
- sLen:=Length(Self);
- dLen:=Length(aData);
- if dLen>0 then
- begin
- SetLength(Self,dLen+sLen);
- Move(aData[0],Self[sLen],dLen);
- end;
- end;
- procedure TBytesHelper.Reverse(var Dest: TBytes; Offset: Integer; Size: Integer);
- var
- I: Integer;
- begin
- SetLength(dest, Size);
- for I := 0 to Size - 1 do
- Dest[Size-1-I]:=Self[Offset+I];
- end;
- function TBytesHelper.ToInt32(aOffset: Integer = 0): LongInt;
- begin
- Result:=0;
- Move(Self[aOffSet],Result,SizeOf(LongInt));
- end;
- function TBytesHelper.ToDword(aOffset: Integer): DWORD;
- begin
- Result:=0;
- Move(Self[aOffSet],Result,SizeOf(DWORD));
- end;
- function TBytesHelper.ToWord(aOffset: Integer): Word;
- begin
- Result:=0;
- Move(Self[aOffSet],Result,SizeOf(Word));
- end;
- function TBytesHelper.ToQWord(aOffset: Integer): QWord;
- begin
- Result:=0;
- Move(Self[aOffSet],Result,SizeOf(QWord));
- end;
- procedure TBytesHelper.FromDword(const aData: DWORD; aOffset: Integer);
- begin
- Move(aData, Self[aOffSet],SizeOf(DWORD));
- end;
- procedure TBytesHelper.FromInt32(const aData: Longint; aOffset: Integer);
- begin
- Move(aData, Self[aOffSet],SizeOf(Longint));
- end;
- procedure TBytesHelper.FromWord(const aData: Word; aOffset: Integer = 0);
- begin
- Move(aData, Self[aOffSet],SizeOf(Word));
- end;
- procedure TBytesHelper.FromQWord(const aData: QWord; aOffset: Integer);
- begin
- Move(aData, Self[aOffSet],SizeOf(QWord));
- end;
- Function HToNx(Host: QWord) : QWord;
- begin
- {$ifdef FPC_BIG_ENDIAN}
- htonx:=host;
- {$else}
- htonx:=SwapEndian(host);
- {$endif}
- end;
- Function NToHx(Net: QWord) : QWord;
- begin
- {$ifdef FPC_BIG_ENDIAN}
- ntohx:=Net;
- {$else}
- ntohx:=SwapEndian(Net);
- {$endif}
- end;
- function EncodeBytesBase64(const aBytes : TBytes) : String;
- var
- OutStream : TStringStream;
- Encoder : TBase64EncodingStream;
- begin
- if Length(aBytes)=0 then
- Exit('');
- Encoder:=Nil;
- OutStream:=TStringStream.Create('');
- try
- Encoder:=TBase64EncodingStream.create(OutStream);
- Encoder.WriteBuffer(aBytes,0,Length(aBytes));
- Encoder.Flush;
- Result:=OutStream.DataString;
- finally
- Encoder.Free;
- OutStream.free;
- end;
- end;
- function DecodeBytesBase64(const s: string; Strict: boolean = false) : TBytes;
- Const
- StrictModes : Array[Boolean] of TBase64DecodingMode = (bdmMime,bdmStrict);
- var
- missing : Integer;
- SD : String;
- Instream,
- Outstream : TBytesStream;
- Decoder : TBase64DecodingStream;
- begin
- Result:=[];
- if Length(s)=0 then
- Exit;
- SD:=S;
- Missing:=Length(Sd) mod 4;
- if Missing>0 then
- SD:=SD+StringOfChar('=',Missing);
- Outstream:=Nil;
- Decoder:=Nil;
- Instream:=TStringStream.Create(SD);
- try
- Outstream:=TBytesStream.Create(Nil);
- Decoder:=TBase64DecodingStream.Create(Instream,StrictModes[Strict]);
- Outstream.CopyFrom(Decoder,Decoder.Size);
- Result:=Outstream.Bytes;
- finally
- Decoder.Free;
- Outstream.Free;
- Instream.Free;
- end;
- end;
- { TWSFramePayload }
- procedure TWSFramePayload.ReadData(var Content: TBytes; aTransport: IWSTransport);
- Const
- MaxBufSize = 32*1024;
- Var
- Buf : TBytes;
- aPos,toRead : QWord;
- aCount : Longint;
- begin
- Buf:=[];
- ToRead:=DataLength;
- aPos:=0;
- Repeat
- aCount:=ToRead;
- if aCount>MaxBufSize then
- aCount:=MaxBufSize;
- SetLength(Buf,aCount);
- aCount := aTransport.ReadBytes(Buf,aCount);
- Move(Buf[0],Content[aPos],aCount);
- Inc(aPos,aCount);
- ToRead:=DataLength-aPos;
- Until (ToRead<=0);
- end;
- procedure TWSFramePayload.Read(buffer: TBytes; aTransport: IWSTransport);
- Var
- LenFlag : Byte;
- paylen16 : Word;
- content: TBytes;
- begin
- content:=[];
- Masked := ((buffer[1] and FlagMasked) <> 0);
- LenFlag := buffer[1] and FlagLengthMask;
- Case LenFlag of
- FlagTwoBytes:
- begin
- aTransport.ReadBytes(Buffer,2);
- Paylen16:=Buffer.ToWord(0);
- DataLength := ntohs(PayLen16);
- end;
- FlagEightBytes:
- begin
- aTransport.ReadBytes(Buffer,8);
- DataLength:=Buffer.ToQWord(0);
- DataLength := ntohx(DataLength); // MVC : Needs fixing
- end
- else
- DataLength:=lenFlag;
- end;
- if Masked then
- begin
- // In some times, not 4 bytes are returned
- aTransport.ReadBytes(Buffer,4);
- MaskKey:=buffer.ToDword(0);
- end;
- SetLength(content, DataLength);
- if (DataLength>0) then
- begin
- ReadData(Content,aTransport);
- if Masked then
- DoMask(Content, MaskKey);
- Data := content;
- end;
- end;
- { TWSFrame }
- constructor TWSFrame.Create(aType: TFrameType; aIsFinal: Boolean; APayload: TBytes; aMask: Integer=0);
- begin
- Create(aType,aIsFinal,aMask);
- FPayload.Data := APayload;
- if Assigned(aPayload) then
- FPayload.DataLength := Cardinal(Length(aPayload));
- end;
- constructor TWSFrame.Create(aType: TFrameType; aIsFinal : Boolean = True; aMask: Integer=0);
- begin
- FPayload:=Default(TWSFramePayload);
- FPayload.MaskKey:=aMask;
- FPayload.Masked:=aMask<>0;
- FFrameType := aType;
- FFinalFrame := AIsFinal;
- end;
- constructor TWSFrame.Create(const aMessage: UTF8String; aMask: Integer=0);
- Var
- Data : TBytes;
- begin
- Data:=TEncoding.UTF8.GetAnsiBytes(AMessage);
- Create(ftText,True,Data,aMask);
- end;
- class function TWSFrame.CreateFromStream(aTransport : IWSTransport): TWSFrame;
- begin
- Result:=TWSFrame.Create;
- try
- if not Result.Read(aTransport) then
- FreeAndNil(Result);
- except
- FreeAndNil(Result);
- Raise;
- end;
- end;
- function TWSFrame.Read(aTransport: IWSTransport): boolean;
- Var
- Buffer : Tbytes;
- B1 : Byte;
- begin
- Result:=False;
- Buffer:=Default(TBytes);
- SetLength(Buffer,2);
- if aTransport.ReadBytes(Buffer,2)=0 then
- Exit;
- if Length(Buffer)<2 then
- Raise EWebSocket.Create('Could not read frame header');
- B1:=buffer[0];
- FFinalFrame:=(B1 and FlagFinalFrame) = FlagFinalFrame;
- FRSV:=(B1 and %01110000) shr 4;
- FFrameType.AsFlag:=(B1 and $F);
- FPayload.Read(Buffer,aTransport);
- Result:=True;
- end;
- function TWSFrame.GetAsBytes: TBytes;
- var
- LenByte,firstByte: Byte;
- buffer, LengthBytes: TBytes;
- aOffSet, I : Integer;
- pLen16 : Word;
- pLen64 : QWord;
- begin
- Result:=Nil;
- firstByte := FrameType.AsFlag;
- if FinalFrame then
- firstByte := firstByte or FlagFinalFrame;
- if FPayload.DataLength < FlagTwoBytes then
- begin
- aOffSet:=2;
- LenByte := Byte(FPayload.DataLength);
- LengthBytes:=[];
- end
- else if Payload.DataLength < (1 shl 16) then
- begin
- aOffset:=4;
- LenByte := FlagTwoBytes;
- plen16:=Payload.DataLength;
- SetLength(LengthBytes, SizeOf(Word));
- LengthBytes.FromWord(HToNs(pLen16));
- end
- else
- begin
- aOffset:=10;
- LenByte:=FlagEightBytes;
- plen64:=Payload.DataLength;
- SetLength(LengthBytes, Sizeof(UInt64));
- LengthBytes.FromQWord(HToNx(Plen64));
- end;
- Buffer:=[];
- if FPayload.Masked then
- begin
- lenByte:=Lenbyte or FlagMasked;
- aoffSet:=aOffSet+4;
- end;
- SetLength(buffer,aOffset+Int64(FPayload.DataLength));
- buffer[0] := firstByte;
- buffer[1] := LenByte;
- for I := 0 to Length(LengthBytes)-1 do
- buffer[2 + I] := LengthBytes[I];
- if Payload.Masked then
- begin
- Buffer.FromInt32(Payload.MaskKey,aOffSet-4);
- TWSFramePayload.CopyMasked(Payload.Data,Buffer,Payload.MaskKey,aOffset);
- end
- else
- if Payload.DataLength > 0 then
- move(Payload.Data[0], buffer[aOffset], Payload.DataLength);
- Result := Buffer;
- end;
- class procedure TWSFramePayload.DoMask(var aData: TBytes; Key: DWORD);
- begin
- CopyMasked(aData,aData,Key,0)
- end;
- class procedure TWSFramePayload.CopyMasked(SrcData: TBytes; var DestData: TBytes; Key: DWORD; aOffset: Integer);
- var
- currentMaskIndex: Longint;
- byteKeys: TBytes;
- I: Longint;
- begin
- CurrentMaskIndex := 0;
- byteKeys:=[];
- SetLength(byteKeys, SizeOf(Key));
- ByteKeys.FromDword(Key);
- for I := 0 to Length(SrcData) - 1 do
- begin
- DestData[I+aOffset] := SrcData[I] XOR byteKeys[currentMaskIndex];
- currentMaskIndex := (currentMaskIndex + 1) mod 4;
- end;
- end;
- class function TWSFramePayload.CopyMasked(SrcData: TBytes; Key: DWORD): TBytes;
- begin
- Result:=[];
- SetLength(Result,Length(SrcData));
- CopyMasked(SrcData,Result,Key,0)
- end;
- { TWSRequest }
- function TWSHeaders.GetS(aIdx: Integer): String;
- begin
- Result:=GetH(WSHeaderNames[aIdx]);
- end;
- procedure TWSHeaders.SetS(AIndex: Integer; const AValue: string);
- begin
- SetH(WSHeaderNames[aIndex],aValue);
- end;
- function TWSHeaders.GetH(const aName: string): String;
- begin
- Result:=Trim(FRawHeaders.Values[aName]);
- end;
- Procedure TWSHeaders.SetH(const aName,aValue: string);
- begin
- FRawHeaders.Values[aName]:=' '+aValue;
- end;
- constructor TWSHeaders.Create(Const aResource : String; const AHeaderList: TStrings);
- var
- I : Integer;
- N,V : String;
- begin
- FResource:=aResource;
- FRawHeaders:=TStringList.Create;
- FRawHeaders.NameValueSeparator:=':';
- if Assigned(aHeaderList) then
- for I:=0 to aHeaderList.Count-1 do
- begin
- aHeaderList.GetNameValue(I,N,V);
- if (N<>'') and (V<>'') then
- FRawHeaders.Add(N+': '+Trim(V));
- end;
- end;
- destructor TWSHeaders.Destroy;
- begin
- FreeAndNil(FRawHeaders);
- inherited;
- end;
- { TWSConnection }
- procedure TWSConnection.Send(aFrameType : TFrameType; aData : TBytes = Nil);
- Var
- aFrame : TWSFrame;
- begin
- if not (aFrameType in [ftClose,ftPing,ftPong]) then
- Raise EWebSocket.CreateFmt(SErrNotSimpleOperation,[Ord(aFrameType)]);
- aFrame:=FrameClass.Create(aFrameType,True,aData);
- try
- Send(aFrame);
- finally
- aFrame.Free;
- end;
- end;
- procedure TWSConnection.SetHandShakeRequest(aRequest: TWSHandshakeRequest);
- begin
- FreeAndNil(FHandshakeRequest);
- FHandShakeRequest:=aRequest;
- end;
- constructor TWSConnection.Create(aOwner : TComponent; aOptions: TWSOptions);
- begin
- FOwner:=aOwner;
- Foptions:=aOptions;
- FWebSocketVersion:=WebSocketVersion;
- AllocateConnectionID;
- end;
- destructor TWSConnection.Destroy;
- begin
- FreeAndNil(FHandshakeRequest);
- If FreeUserData then
- FreeAndNil(FUserData);
- inherited;
- end;
- class function TWSConnection.GetCloseData(aBytes: TBytes; out aReason: String): Word;
- begin
- Result:=0;
- aReason:='';
- if Length(aBytes)>1 then
- Result:=NToHs(aBytes.ToWord(0));
- if Length(aBytes)>2 then
- aReason:=TEncoding.UTF8.GetAnsiString(aBytes,2,Length(aBytes)-2);
- end;
- function TWSConnection.GetPeerIP: String;
- Var
- S : IWSTransport;
- begin
- S:=Transport;
- if Assigned(S) then
- Result:=S.PeerIP
- else
- Result:=''
- end;
- procedure TWSConnection.AllocateConnectionID;
- begin
- if Assigned(IDAllocator) then
- IDAllocator(FConnectionID);
- if FConnectionID='' then
- {$IFDEF CPU64}
- FConnectionID:=IntToStr(InterlockedIncrement64(_ConnectionCount));
- {$ELSE}
- FConnectionID:=IntToStr(InterlockedIncrement(_ConnectionCount));
- {$ENDIF}
- end;
- procedure TWSConnection.SetCloseState(aValue: TCloseState);
- begin
- FCloseState:=aValue;
- if (FCloseState=csClosed) and autoDisconnect then
- Disconnect;
- end;
- function TWSConnection.ReadMessage: Boolean;
- begin
- Result:=DoReadMessage;
- end;
- procedure TWSConnection.DispatchEvent(aInitialType : TFrameType; aFrame : TWSFrame);
- Var
- msg: TWSMessage;
- begin
- Case aInitialType of
- ftPing,
- ftPong,
- ftClose :
- begin
- If Assigned(FOnControl) then
- FOnControl(Self,aInitialType,FMessageContent);
- FMessageContent:=[];
- end;
- ftBinary,
- ftText :
- begin
- if Assigned(FOnMessageReceived) then
- begin
- Msg:=Default(TWSMessage);
- Msg.IsText:=(aInitialType=ftText);
- if aFrame.FrameType=ftBinary then
- Msg.Sequences:=[fsFirst]
- else
- Msg.Sequences:=[fsContinuation];
- if aFrame.FinalFrame then
- Msg.Sequences:=Msg.Sequences+[fsLast];
- Msg.PayLoad:=FMessageContent;
- FOnMessageReceived(Self, Msg);
- end;
- FMessageContent:=[];
- end;
- ftContinuation: ; // Cannot happen normally
- end;
- end;
- Function TWSConnection.HandleIncoming(aFrame : TWSFrame) : Boolean;
- Procedure UpdateCloseState;
- begin
- if (FCloseState=csNone) then
- FCloseState:=csReceived
- else if (FCloseState=csSent) then
- FCloseState:=csClosed;
- end;
- begin
- Result:=True;
- // check Reserved bits
- if aFrame.Reserved<>0 then
- begin
- Close('', CLOSE_PROTOCOL_ERROR);
- UpdateCloseState;
- Result:=false;
- Exit;
- end;
- // check Reserved opcode
- if aFrame.FrameType = ftFutureOpcodes then
- begin
- Close('', CLOSE_PROTOCOL_ERROR);
- UpdateCloseState;
- Result:=false;
- Exit;
- end;
- { If control frame it must be complete }
- if ((aFrame.FrameType=ftPing) or
- (aFrame.FrameType=ftPong) or
- (aFrame.FrameType=ftClose) or
- (aFrame.FrameType=ftContinuation))
- and (not aFrame.FinalFrame) then
- begin
- Close('', CLOSE_PROTOCOL_ERROR);
- UpdateCloseState;
- Result:=false;
- Exit;
- end;
- // here we handle payload.
- if aFrame.FrameType<>ftContinuation then
- FInitialOpcode:=aFrame.FrameType;
- if aFrame.FrameType in [ftPong,ftBinary,ftText,ftPing] then
- FMessageContent:=aFrame.Payload.Data;
- // Special handling
- Case aFrame.FrameType of
- ftContinuation:
- FMessageContent.Append(aFrame.Payload.Data);
- ftPing:
- begin
- if aFrame.Payload.DataLength > 125 then
- Close('', CLOSE_PROTOCOL_ERROR)
- else
- if not (woPongExplicit in Options) then
- Send(ftPong,aFrame.Payload.Data);
- end;
- ftClose:
- begin
- // If our side sent the initial close, this is the reply, and we must disconnect (Result=false).
- Result:=FCloseState=csNone;
- if Result then
- begin
- FMessageContent:=aFrame.Payload.Data;
- if not (woCloseExplicit in Options) then
- begin
- Close('', CLOSE_NORMAL_CLOSURE); // Will update state
- Result:=False; // We can disconnect.
- end
- else
- UpdateCloseState
- end
- else
- UpdateCloseState;
- end;
- else
- ; // avoid Compiler warning
- End;
- if (aFrame.FinalFrame) or (woIndividualFrames in Options) then
- DispatchEvent(FInitialOpcode,aFrame);
- end;
- function TWSConnection.FrameClass: TWSFrameClass;
- begin
- Result:=TWSFrame;
- end;
- procedure TWSConnection.Send(const AMessage: UTF8String);
- var
- aFrame: TWSFrame;
- begin
- aFrame:=FrameClass.Create(aMessage);
- try
- Send(aFrame);
- finally
- aFrame.Free;
- end;
- end;
- procedure TWSConnection.Send(const ABytes: TBytes);
- var
- aFrame: TWSFrame;
- begin
- aFrame:=FrameClass.Create(ftBinary,True,ABytes);
- try
- Send(aFrame);
- finally
- aFrame.Free;
- end;
- end;
- procedure TWSConnection.Close(aMessage: UTF8String);
- begin
- Close(aMessage, CLOSE_NORMAL_CLOSURE);
- end;
- procedure TWSConnection.Close(aMessage: UTF8String; aReason: word);
- var
- aData: TBytes;
- aSize: Integer;
- begin
- // first two bytes is reason of close RFC 6455 section-5.5.1
- aData := TEncoding.UTF8.GetAnsiBytes(aMessage);
- aSize := Length(aData);
- SetLength(aData, aSize + 2);
- if aSize > 0 then
- move(aData[0], aData[2], aSize);
- aData[0] := (aReason and $FF00) shr 8;
- aData[1] := aReason and $FF;
- Close(aData);
- end;
- procedure TWSConnection.Disconnect;
- begin
- DoDisconnect;
- end;
- procedure TWSConnection.Close(aData: TBytes);
- begin
- Send(ftClose,aData);
- end;
- procedure TWSConnection.Send(aFrame: TWSFrame);
- Var
- Data : TBytes;
- begin
- if FCloseState=csClosed then
- Raise EWebSocket.Create(SErrCloseAlreadySent);
- Data:=aFrame.AsBytes;
- Transport.WriteBytes(Data,Length(Data));
- if (aFrame.FrameType=ftClose) then
- begin
- if FCloseState=csNone then
- FCloseState:=csSent
- else if FCloseState=csReceived then
- FCloseState:=csClosed;
- end;
- end;
- Function TWSConnection.DoReadMessage : Boolean ;
- Var
- F : TWSFrame;
- begin
- Result:=False;
- If not Transport.CanRead(0) then
- Exit;
- f:=FrameClass.CreateFromStream(Transport);
- try
- if Assigned(F) then
- Result:=HandleIncoming(F)
- finally
- F.Free;
- end;
- end;
- function TWSConnection.CheckIncoming(aTimeout: Integer; DoRead: Boolean = True): TIncomingResult;
- begin
- if not Transport.CanRead(aTimeOut) then
- Result:=irNone
- else if Not DoRead then
- Result:=irWaiting
- else if ReadMessage then
- Result:=irOK
- else
- Result:=irClose
- end;
- constructor TWSClientConnection.Create(aOwner: TComponent; aTransport: TWSClientTransport; aOptions : TWSOptions);
- begin
- Inherited Create(aOwner,aOptions);
- FTransport:=aTransport;
- end;
- destructor TWSClientConnection.Destroy;
- begin
- FreeAndNil(FTransport);
- inherited;
- end;
- function TWSClientConnection.GetHandshakeCompleted: Boolean;
- begin
- Result:=Assigned(FHandshakeResponse);
- end;
- function TWSClientConnection.GetTransport: IWSTransport;
- begin
- Result:=FTransport;
- end;
- { TWSHandShakeRequest }
- Class Function TWSHandShakeRequest.GenerateKey : String;
- Var
- I : Integer;
- B : TBytes;
- begin
- B:=[];
- SetLength(B,16);
- For I:=0 to 15 do
- B[i]:=Random(256);
- Result:=EncodeBytesBase64(B);
- end;
- constructor TWSHandShakeRequest.Create(const aResource: string; const aExtraHeaders: TStrings);
- begin
- Inherited Create(aResource,aExtraHeaders);
- Version:=IntToStr(DefaultWebSocketVersion);
- end;
- procedure TWSHandShakeRequest.ToStrings(aHeaders: TStrings);
- procedure Add(const AName, aValue, aDefault: String);
- Var
- V : String;
- begin
- V:=aValue;
- if V='' then
- V:=aDefault;
- if V<>'' then
- aHeaders.Add(aName+': '+V)
- end;
- Var
- N,V : String;
- I : Integer;
- begin
- aHeaders.Clear;
- if Resource='' then
- Resource:='/';
- aHeaders.Add('GET ' + Resource + ' HTTP/1.1');
- V:=Host;
- if (V<>'') and (Port<>443) and (Port<>80) then
- V:=V+':'+IntToStr(Port);
- Add('Host',V,'');
- Add('Upgrade',Upgrade,'websocket');
- Add('Connection',Connection,'Upgrade');
- Add('Origin',Origin,Host);
- if Key='' then
- Key:=GenerateKey;
- Add('Sec-WebSocket-Key',Key,'');
- Add('Sec-WebSocket-Protocol',Protocol,'');
- Add('Sec-WebSocket-Version',Version,'');
- For I:=0 to RawHeaders.Count-1 do
- begin
- RawHeaders.GetNameValue(I,N,V);
- if (N<>'') and (V<>'') then
- if (aHeaders.Values[N]='') then
- Add(N,V,'')
- end;
- end;
- { TWSServerConnection }
- constructor TWSServerConnection.Create(aOwner : TComponent; aTransport : TWSServerTransport; aOptions : TWSOptions);
- begin
- Inherited Create(aOwner,aOptions);
- FHandshakeResponseSent := False;
- FTransport:=aTransport;
- FExtraHeaders:=TStringList.Create;
- FExtraHeaders.NameValueSeparator:=':';
- end;
- destructor TWSServerConnection.Destroy;
- begin
- DisConnect;
- inherited;
- end;
- procedure TWSServerConnection.PerformHandshake;
- Var
- Headers : TStrings;
- aResource,Status,aLine : String;
- HSR : TWSHandShakeRequest;
- begin
- Status:=Transport.ReadLn;
- aResource:=ExtractWord(2,Status,[' ']);
- HSR:=Nil;
- Headers:=TStringList.Create;
- try
- Headers.NameValueSeparator:=':';
- aLine:=Transport.ReadLn;
- While aLine<>'' do
- begin
- Headers.Add(aLine);
- aLine:=Transport.ReadLn;
- end;
- HSR:=TWSHandShakeRequest.Create(aResource,Headers);
- FHandshakeResponseSent:=DoHandshake(HSR);
- finally
- HSR.Free;
- Headers.Free;
- end;
- end;
- function TWSServerConnection.GetHandshakeCompleted: Boolean;
- begin
- Result:=HandshakeResponseSent;
- end;
- procedure TWSServerConnection.DoDisconnect;
- begin
- if Assigned(FTransport) then
- FTransport.CloseSocket;
- FreeAndNil(FTransPort);
- end;
- function TWSServerConnection.GetTransport: IWSTransport;
- begin
- Result:=FTransport;
- end;
- procedure TWSServerConnection.DoPrepareHandshakeResponse(aRequest: TWSHandShakeRequest; aResponse: TWSHandShakeResponse);
- begin
- If Assigned(OnHandshake) then
- OnHandShake(aRequest,aResponse);
- end;
- function TWSServerConnection.DoHandshake(const aRequest : TWSHandShakeRequest) : Boolean;
- var
- aLine,Reply : string;
- aResp : TWSHandShakeResponse;
- H : TStrings;
- B : TBytes;
- begin
- Result:=False;
- H:=Nil;
- aResp:=TWSHandShakeResponse.Create('',FExtraHeaders);
- try
- DoPrepareHandshakeResponse(aRequest,aResp);
- try
- H:=TStringList.Create;
- aResp.ToStrings(aRequest,H,True);
- Reply:='';
- For aLine in H do
- Reply:=Reply+aLine+#13#10;
- Reply:=Reply+#13#10;
- B:=TEncoding.UTF8.GetAnsiBytes(Reply);
- Transport.WriteBytes(B,Length(B));
- Result:=True;
- FHandshakeResponseSent:=True;
- except
- on E: Exception do
- begin
- // Close the connection if the handshake failed
- Disconnect;
- end;
- end;
- finally
- H.Free;
- aResp.Free;
- end;
- end;
- end.
|