-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathServeThread.bmx
207 lines (147 loc) · 6.42 KB
/
ServeThread.bmx
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
SuperStrict
Import BRL.Retro
Import "Utils.bmx"
Import "DataLevel.bmx"
Import "ProtoHTTP.bmx"
Import "ProtoWebDAV.bmx"
Type TServeThread Extends TRunnable
Field Parameters:ServeThreadParameters
Method New(Parameters:ServeThreadParameters)
Self.Parameters = Parameters
End Method
Method Run()
set_thread_parameters(Parameters)
Local ThreadStartupMS:ULong = MilliSecs()
Local ThreadStartupuS:ULong = microseconds()
LoggedPrint(" = = = New client = = = ")
PrintClientIP(Parameters.ClientSocket, Parameters.EnableHostnameLookup)
Parameters.ThreadStartupMS = ThreadStartupMS
Parameters.ThreadLastActivityMS = ThreadStartupMS
Parameters.ClientStream:TStream = CreateSocketStream(Parameters.ClientSocket)
If Not Parameters.ClientStream
LoggedPrint("ABORTING: failed to create client stream.")
Return
End If
' Main serving loop. We will return from this function when the client has disconnected or timed out.
WaitRequests(Parameters)
CloseConnection(Parameters)
LoggedPrint("Finished. Ran for " + (microseconds() - ThreadStartupuS) + " uSec. / " + ((microseconds() - ThreadStartupuS) / 1000000.0) + " Sec.")
LoggedPrint(" = = = = = = = = = = = =")
Return
End Method
End Type
Function WaitRequests(Parameters:ServeThreadParameters)
Local ClientSocket:TSocket = Parameters.ClientSocket
Local ClientStream:TStream = Parameters.ClientStream
Local ParsedRequest:HTTPRequestStruct
Local Header:String
Local PayloadPresent:Int = 0
Local PayloadLength:Long
Local i:Int
Repeat
LoggedPrint("Waiting for request.")
Repeat
If RunAbilityCheck(Parameters, 1) = 0 Then Return
If SocketReadAvail(ClientSocket) > 0 Then Exit
ClientSocket.Recv(Null, 0)
Forever
LoggedPrint("Got request.")
Parameters.ThreadLastActivityMS = MilliSecs()
' = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Parameters.KeepAliveEnabled = 0 ' Zero these out just in case
Parameters.ExpectsContinue = 0
Parameters.EncodingMode = ""
PayloadLength = 0
i = 0
' Parse request
ParsedRequest = ParseRequest(ReadLine(ClientStream))
If Not ParsedRequest
LoggedPrint("ABORTING: Failed to parse request. Probably not HTTP.")
Return
End If
If ParsedRequest.Version = "HTTP/1.1" And Parameters.KeepAliveAllowed = 1
LoggedPrint("Keep-alive enabled per protocol version.")
Parameters.KeepAliveEnabled = 1
End If
' Parse headers
Repeat
Header = ReadLine(ClientStream)
'If Len(Header) > 2048
' LoggedPrint("ABORTING: Header line too long.")
' Return
'End If
Select Lower(Header.Split(":")[0])
Case "connection"
Parameters.ConnectionFlags = ExtractHeaderFlags(Header)
Case "content-length"
PayloadLength = Long(ExtractHeaderFlags(Header)[0])
PayloadPresent = 1
LoggedPrint("Got a hint for payload length: " + PayloadLength)
Case "accept-encoding"
If IsInArray("gzip", ExtractHeaderFlags(Header)) Then Parameters.EncodingMode = "gzip"
If IsInArray("zstd", ExtractHeaderFlags(Header)) Then Parameters.EncodingMode = "zstd"
Case "content-encoding"
Parameters.RequestPayloadEncodingMode = ExtractHeaderFlags(Header)[0]
Case "destination"
ParsedRequest.Destination = ParseDestination(ExtractHeaderFlags(Header)[0])
LoggedPrint("Got destination: " + ParsedRequest.Destination)
Case "expect"
If IsInArray("100-continue", ExtractHeaderFlags(Header)) Then Parameters.ExpectsContinue = 1
LoggedPrint("Got a hint for expected response: " + ExtractHeaderFlags(Header)[0] + ".")
Case "range"
ParsedRequest.RangeStart = ExtractRanges(Header)[0]
ParsedRequest.RangeStop = ExtractRanges(Header)[1]
LoggedPrint("Got ranges: " + ParsedRequest.RangeStart + "-" + ParsedRequest.RangeStop)
Default
' If something doesn't work, uncomment this
' Perhaps there's some problem with case sensetivity happening
' LoggedPrint("Unknown header: " + Header)
End Select
i :+ 1
If Not RunAbilityCheck(Parameters) Then Return
Until Header = ""
If IsInArray("keep-alive", Parameters.ConnectionFlags) And Parameters.KeepAliveAllowed = 1
LoggedPrint("Keep-alive enabled per header.")
Parameters.KeepAliveEnabled = 1
End If
If IsInArray("te", Parameters.ConnectionFlags)
LoggedPrint("Clients wants Transfer-Mode header. Ignoring it.")
End If
' = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
If SocketReadAvail(ClientSocket) > 0 And PayloadPresent = 0
LoggedPrint("There's "+SocketReadAvail(ClientSocket)+" bytes of payload within the request but no Content-Length header.")
End If
If PayloadLength > Parameters.RequestPayloadLengthLimit
' This situation can occur if a client tried to upload a file that's too big
' We'll tell them that there's a problem and bail
LoggedPrint("Error 413. Request payload is over the limit: " + PayloadLength + " bytes vs " + Parameters.RequestPayloadLengthLimit + " bytes.")
SendError(413, Parameters)
Return
End If
If Parameters.ExpectsContinue
' If we are needed to, say that everything is looking good
' CarotDAV uses 100-Continue during file uploads, though it will still work fine even if
' you remove this code block completely
SendStatus(100, Parameters)
End If
If PayloadLength > 0
ParsedRequest.PayloadSize = PayloadLength
End If
' = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
ParsedRequest.Target = URLDecode(ParsedRequest.Target) ' Turns "%20" into spaces
If (ParsedRequest.Target.Contains("//") Or ParsedRequest.Target.Contains(".."))
LoggedPrint("ABORTING: Suspicious request target.")
Return
End If
Select ParsedRequest.Action
Case "GET", "OPTIONS", "HEAD", "POST", "PUT", "DELETE", "MOVE"
ProcessHTTPRequest(ParsedRequest, Parameters)
Case "PROPFIND", "PROPPATCH"
ProcessWebDAVRequest(ParsedRequest, Parameters)
Default ' 405 Method not supported
LoggedPrint("Error 405. Method ["+ParsedRequest.Action+"] not supported.")
SendError(405, Parameters)
End Select
' = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
Until Parameters.KeepAliveEnabled = 0 Or RunAbilityCheck(Parameters) = 0
End Function