-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathsynaws.pas
254 lines (214 loc) · 6.28 KB
/
synaws.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
unit synaws;
interface
uses
SysUtils, Classes, AnsiStrings, SyncObjs,
//
blcksock, synautil, synaip, synacode, synsock, ssl_openssl,
//
uWebSocketUpgrade, uWebSocketConst, uConnectParamInterface,
//
uCookieManagerInterface;
type
TSynaws = class
private
function RecvPacketToBuffer(const ATimeout: Integer = 0): Boolean;
function ReadFrameFromBuffer(var AData: RawByteString;
var AOpcode: TWsOpcode): Boolean;
protected
FIsConnected: Boolean;
FSocket: TTCPBlockSocket;
FConnectParam: IConnectParam;
FUpgrader: TWebSocketUpgrade;
FCookies: ICookieManager;
FUrl: AnsiString;
FOrigin: RawByteString;
FUserAgent: RawByteString;
//FAddHeaders: TAnsiStrings;
FReadBuffer: RawByteString;
FSendLock: TCriticalSection;
public
constructor Create(const AConnectParam: IConnectParam);
destructor Destroy; override;
//---
procedure Disconnect;
function Connect(const AUrl: AnsiString): Boolean;
function Send(const A: RawByteString; const ACode: TWsOpcode = wsCodeText): Boolean;
function SendText(const S: string): Boolean;
function WaitData(const ATimeout: Integer): Boolean;
function Recv(var AData: RawByteString; var ACode: TWsOpcode): Boolean;
//---
property IsConnected: Boolean read FIsConnected;
property Socket: TTCPBlockSocket read FSocket;
property Cookies: ICookieManager read FCookies write FCookies;
property ConnectParam: IConnectParam read FConnectParam write FConnectParam;
property Url: AnsiString read FUrl write FUrl;
property Origin: RawByteString read FOrigin write FOrigin;
property UserAgent: RawByteString read FUserAgent write FUserAgent;
end;
implementation
uses
AcedStrings,
//
uStringUtils;
constructor TSynaws.Create(const AConnectParam: IConnectParam);
begin
inherited Create;
FSendLock := TCriticalSection.Create;
FConnectParam := AConnectParam;
FSocket := TTCPBlockSocket.Create;
FSocket.Owner := Self;
FSocket.NagleMode := False; // nodelay switch on
FUpgrader := TWebSocketUpgrade.Create;
end;
destructor TSynaws.Destroy;
begin
FreeAndNil(FSendLock);
FreeAndNil(FUpgrader);
FreeAndNil(FSocket);
inherited Destroy;
end;
procedure TSynaws.Disconnect;
begin
FSocket.CloseSocket;
end;
function TSynaws.Connect(const AUrl: AnsiString): Boolean;
const
PROXY_PORT_DEFAULT: AnsiString = '1080';
var
lproxy: AnsiString;
lresp_headers: AnsiString;
lcookies: AnsiString;
begin
if FIsConnected then
raise Exception.Create('already connected');
// Set params
if AUrl <> '' then
FUrl := AUrl;
lcookies := '';
if Assigned(FCookies) then
lcookies := FCookies.GetCookieA(GetUrlHostA(FUrl));
FUpgrader.InitAsClient(FUrl, True);
FUpgrader.Cookies := lcookies;
FUpgrader.UserAgent := FUserAgent;
FUpgrader.Origin := FOrigin;
//FUpgrader.AddHeaders.AddStrings();
FSocket.ConnectionTimeout := FConnectParam.ConnectTimeout;
FSocket.SetTimeout(FConnectParam.RecvTimeout);
FSocket.SSL.SSLType := LT_all;
FSocket.RaiseExcept := True;
lproxy := FConnectParam.SocksProxyA;
if lproxy <> '' then
begin
FSocket.SocksType := ST_Socks4;
FSocket.SocksIP := AddrGetHost(lproxy);
FSocket.SocksPort := AddrGetPort(lproxy, PROXY_PORT_DEFAULT);
FSocket.SocksResolver := False;
end;
// Connect socket
FSocket.CloseSocket;
FSocket.Bind(FConnectParam.BindAddrA, cAnyPort);
if FSocket.LastError <> 0 then
Exit(False);
FSocket.Connect(FUpgrader.Host, FUpgrader.Port);
if FSocket.LastError <> 0 then
Exit(False);
// TLS?
if Pos('wss://', FUrl) = 1 then
begin
if FSocket.SSL.SNIHost = '' then
FSocket.SSL.SNIHost := FUpgrader.Host;
FSocket.SSLDoConnect;
FSocket.SSL.SNIHost := ''; //don't need it anymore and don't wan't to reuse it in next connection
if FSocket.LastError <> 0 then
Exit(False);
end;
// Sent Request
FSocket.SendString(FUpgrader.Headers);
if FSocket.LastError <> 0 then
Exit(False);
// read response headers
lresp_headers := FSocket.RecvTerminated(FConnectParam.RecvTimeout, CRLF + CRLF);
if FSocket.LastError <> 0 then
Exit(False);
if not FUpgrader.ClientConfirm(lresp_headers) then
begin
Exit(False);
end;
FIsConnected := True;
Exit(True)
end;
function TSynaws.Send(const A: RawByteString; const ACode: TWsOpcode): Boolean;
var z: RawByteString;
begin
z := FUpgrader.SendData(A, ACode);
FSendLock.Enter;
try
FSocket.SendString(z);
finally
FSendLock.Leave;
end;
Result := FSocket.LastError = 0
end;
function TSynaws.SendText(const S: string): Boolean;
var u: UTF8String;
begin
u := UTF8Encode(S);
Result := Send(u, wsCodeText)
end;
function TSynaws.WaitData(const ATimeout: Integer): Boolean;
begin
// wait data
Result := FSocket.CanRead(ATimeout);
if Result then
begin
{ if FConn.WaitingData() = 0 then
FConn.RecvBuffer(nil, 0) }
end;
end;
function TSynaws.ReadFrameFromBuffer(var AData: RawByteString;
var AOpcode: TWsOpcode): Boolean;
begin
AData := FUpgrader.ReadData(FReadBuffer, AOpcode);
Result := AOpcode <> wsNoFrame
end;
function TSynaws.Recv(var AData: RawByteString; var ACode: TWsOpcode): Boolean;
function IsIncompleteFrameExists: Boolean;
begin
Result := FUpgrader.IsIncompleteFragmentsExists or (FReadBuffer <> '')
end;
begin
AData := '';
ACode := wsNoFrame;
//
// there is frame in buffer
if FReadBuffer <> '' then
begin
if ReadFrameFromBuffer(AData, ACode) then
begin
Result := IsIncompleteFrameExists();
Exit;
end;
end;
//
if RecvPacketToBuffer(FConnectParam.RecvTimeout) then
begin
if ReadFrameFromBuffer(AData, ACode) then
begin
Result := IsIncompleteFrameExists();
Exit;
end;
end;
Result := False
end;
function TSynaws.RecvPacketToBuffer(const ATimeout: Integer): Boolean;
var z: RawByteString;
begin
z := FSocket.RecvPacket(ATimeout);
// SetCodePage(z, #$FFFF, False);
// SetCodePage(FReadBuffer, #$FFFF, False);
FReadBuffer := FReadBuffer + z;
SetCodePage(FReadBuffer, $FFFF, False);
//
Result := FSocket.LastError = 0;
end;
end.