-
-
Notifications
You must be signed in to change notification settings - Fork 135
/
mormot.net.http.pas
7287 lines (6832 loc) · 263 KB
/
mormot.net.http.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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// HTTP/HTTPS Abstract Process Classes and Definitions
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.net.http;
{
*****************************************************************************
HTTP/HTTPS Abstract Process Classes and Definitions
- Shared HTTP Constants and Functions
- Reusable HTTP State Machine
- THttpSocket Implementing HTTP over plain sockets
- Abstract Server-Side Types used e.g. for Client-Server Protocol
- HTTP Server Logging/Monitoring Processors
*****************************************************************************
}
interface
{$I ..\mormot.defines.inc}
uses
sysutils,
classes,
mormot.core.base,
mormot.core.os,
mormot.core.unicode,
mormot.core.text,
mormot.core.rtti,
mormot.core.buffers,
mormot.core.datetime,
mormot.core.data,
mormot.core.zip,
mormot.net.sock;
{ ******************** Shared HTTP Constants and Functions }
type
/// event used to compress or uncompress some data during HTTP protocol
// - should always return the protocol name for ACCEPT-ENCODING: header
// e.g. 'gzip' or 'deflate' for standard HTTP format, but you can add
// your own (like 'synlz')
// - the data is compressed (if Compress=TRUE) or uncompressed (if
// Compress=FALSE) in the Data variable (i.e. it is modified in-place)
// - to be used with THttpSocket.RegisterCompress method
THttpSocketCompress = function(var Data: RawByteString; Compress: boolean): RawUtf8;
/// used to maintain a list of known compression algorithms
THttpSocketCompressRec = record
/// the compression name, as in ACCEPT-ENCODING: header (gzip,deflate,synlz)
Name: RawUtf8;
/// the function handling compression and decompression
Func: THttpSocketCompress;
/// the size in bytes after which compress will take place
// - will be 1024 e.g. for 'zip' or 'deflate'
// - could be 0 e.g. when encrypting the content, meaning "always compress"
CompressMinSize: integer;
/// a priority on which the compression is applied - highest is favored
Priority: integer;
end;
PHttpSocketCompressRec = ^THttpSocketCompressRec;
/// list of known compression algorithms
THttpSocketCompressRecDynArray = array of THttpSocketCompressRec;
/// identify some items in a list of known compression algorithms
// - filled from ACCEPT-ENCODING: header value
THttpSocketCompressSet = set of 0..31;
/// tune the 'synopsebin' protocol
// - pboCompress will compress all frames payload using SynLZ
// - pboNoLocalHostCompress won't compress frames on the loopback (127.0.0.1)
// - pboNoLocalHostEncrypt won't encrypt frames on the loopback (127.0.0.1)
TWebSocketProtocolBinaryOption = (
pboSynLzCompress,
pboNoLocalHostCompress,
pboNoLocalHostEncrypt);
/// how TWebSocketProtocolBinary implements the 'synopsebin' protocol
// - should match on both client and server ends
TWebSocketProtocolBinaryOptions = set of TWebSocketProtocolBinaryOption;
/// adjust HTTP body compression according to the supplied 'CONTENT-TYPE'
// - will detect most used compressible content (like 'text/*' or
// 'application/json') from OutContentType
procedure CompressContent(Accepted: THttpSocketCompressSet;
const Handled: THttpSocketCompressRecDynArray; const OutContentType: RawUtf8;
var OutContent: RawByteString; var OutContentEncoding: RawUtf8);
/// enable a give compression function for a HTTP link
function RegisterCompressFunc(var Comp: THttpSocketCompressRecDynArray;
CompFunction: THttpSocketCompress; var AcceptEncoding: RawUtf8;
CompMinSize, CompPriority: integer): RawUtf8;
/// decode 'CONTENT-ENCODING: ' parameter from registered compression list
function ComputeContentEncoding(const Compress: THttpSocketCompressRecDynArray;
P: PUtf8Char): THttpSocketCompressSet;
/// search for a given compression function
function CompressIndex(const Compress: THttpSocketCompressRecDynArray;
CompFunction: THttpSocketCompress): PtrInt;
/// compute the 'Authorization: Bearer ####' HTTP header of a given token value
function AuthorizationBearer(const AuthToken: RawUtf8): RawUtf8;
/// will remove most usual HTTP headers which are to be recomputed on sending
// - as used e.g. during TPublicRelay process from mormot.net.relay
function PurgeHeaders(const headers: RawUtf8; trim: boolean = false;
upIgnore: PPAnsiChar = nil): RawUtf8;
/// search, copy and remove a given HTTP header
// - FindNameValue() makes search + copy, but this function also REMOVES the header
procedure ExtractHeader(var headers: RawUtf8; const upname: RawUtf8;
out res: RawUtf8);
/// retrieve a HTTP header text value from its case-insensitive name
function GetHeader(const Headers, Name: RawUtf8; out Value: RawUtf8): boolean; overload;
/// retrieve a HTTP header 64-bit integer value from its case-insensitive name
function GetHeader(const Headers, Name: RawUtf8; out Value: Int64): boolean; overload;
/// 'HEAD' and 'OPTIONS' methods would be detected and return true
// - will check only the first four chars for efficiency
function HttpMethodWithNoBody(const method: RawUtf8): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// encode some text into a mime header compatible value
// - see https://tools.ietf.org/html/rfc2047
function MimeHeaderEncode(const header: RawUtf8): RawUtf8;
/// quick check for case-sensitive 'GET' HTTP method name
// - see also HttpMethodWithNoBody()
function IsGet(const method: RawUtf8): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// quick check for case-sensitive 'HEAD' HTTP method name
// - see also HttpMethodWithNoBody()
function IsHead(const method: RawUtf8): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// quick check for case-sensitive 'POST' HTTP method name
function IsPost(const method: RawUtf8): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// quick check for case-sensitive 'PUT' HTTP method name
function IsPut(const method: RawUtf8): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// quick check for case-sensitive 'DELETE' HTTP method name
function IsDelete(const method: RawUtf8): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// quick check for case-sensitive 'OPTIONS' HTTP method name
function IsOptions(const method: RawUtf8): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// could be used e.g. in OnBeforeBody() callback to allow a GET /favicon.ico
function IsUrlFavIcon(P: PUtf8Char): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// check if the supplied text start with 'http://' or 'https://'
function IsHttp(const text: RawUtf8): boolean;
/// true if the supplied text is case-insensitive 'none'
function IsNone(const text: RawUtf8): boolean;
/// naive detection of most used bots from a HTTP User-Agent string
// - meant to be fast, with potentially a lot of false negatives: please do not
// hesitate to send us feedback as pull requests
function IsHttpUserAgentBot(const UserAgent: RawUtf8): boolean;
/// decode a given parameter from an Url, in any position, into UTF-8 text
// - P^ should be either nil or point to P^ = '?'
// - UpperName should follow the UrlDecodeValue() format, e.g. 'NAME='
function UrlDecodeParam(P: PUtf8Char; const UpperName: RawUtf8;
out Value: RawUtf8): boolean; overload;
/// decode a given parameter from an Url, in any position, into a 32-bit cardinal
// - UpperName should follow the UrlDecodeCardinal() format, e.g. 'COUNT='
function UrlDecodeParam(P: PUtf8Char; const UpperName: RawUtf8;
out Value: cardinal): boolean; overload;
{$ifdef HASINLINE} inline; {$endif}
/// decode a given parameter from an Url, in any position, into a 64-bit Int64
// - UpperName should follow the UrlDecodeInt64() format, e.g. 'ID='
function UrlDecodeParam(P: PUtf8Char; const UpperName: RawUtf8;
out Value: Int64): boolean; overload;
{$ifdef HASINLINE} inline; {$endif}
{$ifdef OSPOSIX}
/// convert a file URL to a local file path using our TUri parser
// - mormot.core.os.pas implements this on Windows via PathCreateFromUrl() API
// - used e.g. by TNetClientProtocolFile to implement the 'file://' protocol
function GetFileNameFromUrl(const Uri: RawUtf8): TFileName;
{$endif OSPOSIX}
/// extract a 64-bit value from a 'Range: xxx-xxx ' input
// - returned P^ points to the first non digit char - not as GetNextItemQWord()
function GetNextRange(var P: PUtf8Char): Qword;
const
/// pseudo-header containing the current Synopse mORMot framework version
XPOWEREDNAME = 'X-Powered-By';
/// the full text of the current Synopse mORMot framework version
// - we don't supply full version number with build revision
// (as SYNOPSE_FRAMEWORK_VERSION), to reduce potential attacker knowledge
XPOWEREDVALUE = SYNOPSE_FRAMEWORK_NAME + ' 2';
{ ******************** Reusable HTTP State Machine }
type
PHttpRequestContext = ^THttpRequestContext;
/// a 31-bit > 0 sequence identifier of each THttpPartial.ID instance
THttpPartialID = integer;
/// maintain one partial download for THttpPartials
THttpPartial = record
/// genuine positive identifier, 0 if empty/recyclable
ID: THttpPartialID;
/// the expected full size of this download
FullSize: Int64;
/// the partial file name currently downloaded
PartFile: TFileName;
/// up to 512-bit of raw binary hash
Hash: THash512Rec;
/// the HTTP requests which are waiting for data on this partial download
HttpContext: array of PHttpRequestContext;
end;
/// maintain a list of partial downloads
THttpPartials = class
protected
/// thread-safe access to the list of partial downloads
fSafe: TLightLock;
/// 32-bit monotonic counter sequence to populate THttpPartial.ID
fLastID: cardinal;
/// store (a few) partial download states
fDownload: array of THttpPartial;
/// retrieve an index in Partial[] for a given sequence ID
function IndexFromID(aID: THttpPartialID): PtrInt;
public
/// can be assigned to TSynLog.DoLog class method for low-level logging
OnLog: TSynLogProc;
/// thread-safe register a new partial download
function Add(const Partial: TFileName; ExpectedFullSize: Int64;
HashBuf: PByte; HashLen: PtrInt): THttpPartialID;
/// register a HTTP request to a given partial
function Find(HashBuf: PByte; HashLen: PtrInt; Http: PHttpRequestContext;
out Size: Int64): TFileName;
/// notify a partial file name change, e.g. when download is complete
// - returns the number of changed entries
function ChangeFile(ID: THttpPartialID; const NewFile: TFileName): integer;
/// notify a partial file download failure, e.g. on invalid hash
// - returns the number of removeed HTTP requests
function Abort(ID: THttpPartialID): integer;
/// unregister a HTTP request to a given partial
// - called when the request is finished e.g. via
// THttpServerSocketGeneric.DoProgressiveRequestFree private method
procedure Remove(Sender: PHttpRequestContext);
end;
/// the machine states of THttpRequestContext processing
THttpRequestState = (
hrsNoStateMachine,
hrsGetCommand,
hrsGetHeaders,
hrsGetBodyChunkedHexFirst,
hrsGetBodyChunkedHexNext,
hrsGetBodyChunkedData,
hrsGetBodyChunkedDataVoidLine,
hrsGetBodyChunkedDataLastLine,
hrsGetBodyContentLength,
hrsWaitProcessing,
hrsWaitAsyncProcessing,
hrsSendBody,
hrsResponseDone,
hrsUpgraded,
hrsConnect,
hrsSendHeaders,
hrsErrorPayloadTooLarge,
hrsErrorRejected,
hrsErrorMisuse,
hrsErrorUnsupportedFormat,
hrsErrorUnsupportedRange,
hrsErrorAborted,
hrsErrorShutdownInProgress);
/// set of states for THttpRequestContext processing
THttpRequestStates = set of THttpRequestState;
/// customize THttpRequestContext process
THttpRequestOptions = set of (
hroHeadersUnfiltered);
/// map the presence of some HTTP headers for THttpRequestContext.HeaderFlags
// - separated from THttpRequestResponseFlags so that they would both be stored
// and accessed as a single byte - which is faster than word on Intel CPUs
// - do not modify unless you fix the associated ToText() overloaded function
THttpRequestHeaderFlags = set of (
nfHeadersParsed,
hfTransferChunked,
hfConnectionClose,
hfConnectionUpgrade,
hfConnectionKeepAlive,
hfExpect100,
hfHasRemoteIP,
hfHasAuthorization);
/// map the output state for THttpRequestContext.ResponseFlags
// - separated from THttpRequestHeaderFlags so that they would both be stored
// and accessed as a single byte - which is faster than word on Intel CPUs
THttpRequestResponseFlags = set of (
rfAcceptRange,
rfWantRange,
rfRange,
rfHttp10,
rfContentStreamNeedFree,
rfAsynchronous,
rfProgressiveStatic);
/// define THttpRequestContext.ProcessBody response
// - hrpSend should try to send the Dest buffer content
// - hrpWait is returned in rfProgressiveStatic mode to wait for more data
// - hrpAbort needs to close the connection
// - hrpDone indicates that the whole body content has been sent
THttpRequestProcessBody = (
hrpSend,
hrpWait,
hrpAbort,
hrpDone);
/// raw information used during THttpRequestContext header parsing
TProcessParseLine = record
P: PUtf8Char;
Len: PtrInt;
Line: PUtf8Char;
LineLen: PtrInt;
end;
/// low-level reusable State Machine to parse and process any HTTP content
// - shared e.g. by all our (web)socket-based HTTP client and server classes
// - reduce memory allocations as much as possible, and parse the most used
// headers in explicit fields
{$ifdef USERECORDWITHMETHODS}
THttpRequestContext = record
{$else}
THttpRequestContext = object
{$endif USERECORDWITHMETHODS}
private
fContentLeft: Int64;
fContentPos: PByte;
fContentEncoding, fLastHost: RawUtf8;
fProgressiveTix: cardinal;
fProgressiveID: THttpPartialID;
fProgressiveNewStreamFileName: TFileName;
procedure SetRawUtf8(var res: RawUtf8; P: pointer; PLen: PtrInt;
nointern: boolean);
function ProcessParseLine(var st: TProcessParseLine): boolean;
{$ifdef HASINLINE} inline; {$endif}
function ParseHttp(P: PUtf8Char): boolean;
{$ifdef HASINLINE} inline; {$endif}
procedure GetTrimmed(P, P2: PUtf8Char; L: PtrInt; var result: RawUtf8;
nointern: boolean = false);
{$ifdef HASINLINE} inline; {$endif}
/// implements rfProgressiveStatic mode from ProcessBody
function DoProgressive(out availablesize: Int64): THttpRequestProcessBody;
public
// reusable buffers for internal process - do not use
Head, Process: TRawByteStringBuffer;
/// the current state of this HTTP context
State: THttpRequestState;
/// map the presence of some HTTP headers, retrieved during ParseHeader
HeaderFlags: THttpRequestHeaderFlags;
/// some flags used when sending the response
ResponseFlags: THttpRequestResponseFlags;
/// customize the HTTP process
Options: THttpRequestOptions;
/// could be set so that ParseHeader/GetTrimmed will intern RawUtf8 values
Interning: PRawUtf8InterningSlot;
/// will contain the first header line on client side
// - 'HTTP/1.0 200 OK' for a GET response after Get() e.g.
// - THttpServerSocket will use it, but THttpAsyncServer won't
CommandResp: RawUtf8;
/// the HTTP method parsed from first header line, e.g. 'GET'
CommandMethod: RawUtf8;
/// the HTTP URI parsed from first header line, e.g. '/path/to/resource'
CommandUri: RawUtf8;
/// will contain all header lines after all ParseHeader
// - use HeaderGetValue() to get one HTTP header item value by name
Headers: RawUtf8;
/// same as HeaderGetValue('CONTENT-TYPE'), but retrieved during ParseHeader
ContentType: RawUtf8;
/// same as HeaderGetValue('ACCEPT-ENCODING'), but retrieved during ParseHeader
AcceptEncoding: RawUtf8;
/// same as HeaderGetValue('HOST'), but retrieved during ParseHeader
Host: RawUtf8;
/// same as HeaderGetValue('USER-AGENT'), but retrieved during ParseHeader
UserAgent: RawUtf8;
/// same as HeaderGetValue('UPGRADE'), but retrieved during ParseHeader
Upgrade: RawUtf8;
/// same as HeaderGetValue('REFERER'), but retrieved during ParseHeader
Referer: RawUtf8;
/// same as FindNameValue(aInHeaders, HEADER_BEARER_UPPER, ...),
// but retrieved during ParseHeader
// - is the raw Token, excluding 'Authorization: Bearer ' trailing chars
// - if hsrAuthorized is set, THttpServerSocketGeneric.Authorization() will
// put the authenticated User name in this field
BearerToken: RawUtf8;
/// custom server-generated headers, to be added to the request headers
// - set e.g. with hsrAuthorized and hraNegotiate authentication scheme as
// already formatted 'WWW-Authenticate: xxxxxxxxx'#13#10 header
ResponseHeaders: RawUtf8;
/// decoded 'Range: bytes=..' start value - default is 0
// - e.g. 1024 for 'Range: bytes=1024-1025'
// - equals -1 in case on unsupported multipart range requests
RangeOffset: Int64;
/// decoded 'Range: bytes=...' end value - default is -1 (until end of file)
// - e.g. 2 for 'Range: bytes=1024-1025'
// - e.g. -1 for 'Range: bytes=1024-'
// - contains size for CompressContentAndFinalizeHead Content-Range: header
RangeLength: Int64;
/// will contain the data retrieved from the server, after all ParseHeader
Content: RawByteString;
/// same as HeaderGetValue('CONTENT-LENGTH'), but retrieved during ParseHeader
// - is overridden with real Content length during HTTP body retrieval
ContentLength: Int64;
/// known GMT timestamp of output content, may be reported as 'Last-Modified:'
ContentLastModified: TUnixMSTime;
/// stream-oriented alternative to the Content in-memory buffer
// - is typically a TFileStreamEx
ContentStream: TStream;
/// same as HeaderGetValue('SERVER-INTERNALSTATE'), but retrieved by ParseHeader
// - proprietary header, used with our RESTful ORM access
ServerInternalState: integer;
/// the known Content-Encoding compression methods
Compress: THttpSocketCompressRecDynArray;
/// supported Content-Encoding compression methods as sent to the other side
CompressAcceptEncoding: RawUtf8;
/// index of protocol in Compress[], from Accept-encoding
CompressAcceptHeader: THttpSocketCompressSet;
/// same as HeaderGetValue('CONTENT-ENCODING'), but retrieved by ParseHeader
// and mapped into the Compress[] array
CompressContentEncoding: integer;
/// reset this request context to be used prior to any ProcessInit/Read/Write
procedure Reset;
/// parse CommandUri into CommandMethod/CommandUri fields on server side
// - e.g. from CommandUri = 'GET /uri HTTP/1.1'
function ParseCommand: boolean;
/// parse CommandUri into result fields on server side
// - e.g. from CommandUri = 'HTTP/1.1 200 OK'
// - returns 0 on parsing error, or the HTTP status (e.g. 200)
function ParseResponse(out RespStatus: integer): boolean;
/// parse a HTTP header text line into Header and fill internal properties
// - with default HeadersUnFiltered=false, only relevant headers are retrieved:
// use directly the ContentLength/ContentType/ServerInternalState/Upgrade
// and HeaderFlags fields since HeaderGetValue() would return ''
// - force HeadersUnFiltered=true to store all headers including the
// connection-related fields, but increase memory and reduce performance
procedure ParseHeader(P: PUtf8Char; PLen: PtrInt;
HeadersUnFiltered: boolean = false);
/// to be called once all ParseHeader lines have been done to fill Headers
// - also set CompressContentEncoding/CompressAcceptHeader from Compress[]
// and Content-Encoding header value
procedure ParseHeaderFinalize;
/// parse supplied command uri and headers
function ParseAll(aInStream: TStream; const aInContent: RawByteString;
const aCommand, aHeaders: RawUtf8): boolean;
/// search a value from the internal parsed Headers
// - supplied aUpperName should be already uppercased:
// HeaderGetValue('CONTENT-TYPE')='text/html', e.g.
// - note that GetHeader(HeadersUnFiltered=false) will set ContentType field
// but let HeaderGetValue('CONTENT-TYPE') return ''
function HeaderGetValue(const aUpperName: RawUtf8): RawUtf8;
{$ifdef HASINLINE} inline; {$endif}
/// search if a value exists from the internal parsed Headers
function HeaderHasValue(const aUpperName: RawUtf8): boolean;
{$ifdef HASINLINE} inline; {$endif}
/// initialize ContentStream/ContentLength from a given file name
// - if CompressGz is set, would also try for a cached local FileName+'.gz'
// - returns HTTP_SUCCESS, HTTP_NOTFOUND or HTTP_RANGENOTSATISFIABLE
function ContentFromFile(const FileName: TFileName; CompressGz: integer): integer;
/// uncompress Content according to CompressContentEncoding header
procedure UncompressData;
/// check RangeOffset/RangeLength against ContentLength
// - and update ResponseFlags and ContentLength properties
function ValidateRange: boolean;
/// (re)initialize the HTTP Server state machine for ProcessRead/ProcessWrite
procedure ProcessInit;
{$ifdef HASINLINE} inline; {$endif}
/// receiving socket entry point of our asynchronous HTTP Server
// - to be called with the incoming bytes from the socket receive buffer
// - caller should have checked that current State is in HTTP_REQUEST_READ
// - returns true if a new State was reached, or false if some more
// input is needed
function ProcessRead(var st: TProcessParseLine;
returnOnStateChange: boolean): boolean;
/// compress Content according to CompressAcceptHeader, adding headers
// - e.g. 'Content-Encoding: synlz' header if compressed using synlz
// - and if Content is not '', will add 'Content-Type: ' header
// - always compute ContentLength and add a 'Content-Length: ' header
// - then append small content (<MaxSizeAtOnce) to result if possible, and
// refresh the final State to hrsSendBody/hrsResponseDone
function CompressContentAndFinalizeHead(MaxSizeAtOnce: integer): PRawByteStringBuffer;
/// compute ouput headers and body from current output state
// - alternate to CompressContentAndFinalizeHead() when Headers and
// Content/ContentStream/ContentLength/ContentEncoding are manually set
function ContentToOutput(aStatus: integer; aOutStream: TStream): integer;
/// body sending socket entry point of our asynchronous HTTP Server
// - to be called when some bytes could be written to output socket
function ProcessBody(var Dest: TRawByteStringBuffer;
MaxSize: PtrInt): THttpRequestProcessBody;
/// should be done when the HTTP Server state machine is done
// - will check and process hfContentStreamNeedFree flag
procedure ProcessDone;
{$ifdef HASINLINE} inline; {$endif}
/// notify that a file in rfProgressiveStatic mode has changed
// - e.g. from THttpPeerCache.OnDownloaded/PartialChangeFile
function ChangeProgressiveFileName(
ID: THttpPartialID; const FileName: TFileName): boolean;
/// the sequence ID used in rfProgressiveStatic mode
// - equals 0 if disabled or aborted
property ProgressiveID: THttpPartialID
read fProgressiveID write fProgressiveID;
end;
const
/// when THttpRequestContext.State is expected some ProcessRead() data
HTTP_REQUEST_READ =
[hrsGetCommand,
hrsGetHeaders,
hrsGetBodyChunkedHexFirst,
hrsGetBodyChunkedHexNext,
hrsGetBodyChunkedData,
hrsGetBodyChunkedDataVoidLine,
hrsGetBodyContentLength,
hrsConnect];
/// when THttpRequestContext.State is expected some ProcessWrite() data
HTTP_REQUEST_WRITE =
[hrsSendBody,
hrsSendHeaders];
/// rfProgressiveStatic mode custom HTTP header to supply the expected file size
STATICFILE_PROGSIZE = 'STATIC-PROGSIZE:';
/// wait up to 10 seconds for new file content in rfProgressiveStatic mode
STATICFILE_PROGTIMEOUTSEC = 10;
var // filled from RTTI enum trimmed text during unit initialization
HTTP_STATE: array[THttpRequestState] of RawUtf8;
/// highest files size for THttpRequestContext.ContentFromFile load to memory
// - default to 1MB on 32-bit systems, 2MB on 64-bit systems
HttpContentFromFileSizeInMemory: PtrInt =
{$ifdef CPU32} 1 shl 20 {$else} 2 shl 20 {$endif};
function ToText(st: THttpRequestState): PShortString; overload; // HTTP_STATE[]
function ToText(hf: THttpRequestHeaderFlags): TShort8; overload;
function ToText(csp: TCrtSocketPending): PShortString; overload;
function ToText(tls: TCrtSocketTlsAfter): PShortString; overload;
function ToText(mak: TMacAddressKind): PShortString; overload;
function ToText(hrp: THttpRequestProcessBody): PShortString; overload;
{ ******************** THttpSocket Implementing HTTP over plain sockets }
type
/// exception class raised during HTTP process
EHttpSocket = class(ESynException);
/// parent of THttpClientSocket and THttpServerSocket classes
// - contain properties for implementing HTTP/1.1 using the Socket API
// - handle chunking of body content
// - can optionaly compress and uncompress on the fly the data, with
// standard gzip/deflate or custom (synlz) protocols
THttpSocket = class(TCrtSocket)
protected
fBodyRetrieved: boolean; // to call GetBody only once
procedure HttpStateReset; // Http.Clear + fBodyRetrieved := false
{$ifdef HASINLINE} inline; {$endif}
procedure CompressDataAndWriteHeaders(const OutContentType: RawUtf8;
var OutContent: RawByteString; OutStream: TStream);
public
/// the whole context of the HTTP/1.0 or HTTP/1.1 request
Http: THttpRequestContext;
/// retrieve the HTTP headers into Headers[] and fill most properties below
// - with default HeadersUnFiltered=false, only relevant headers are retrieved:
// use directly the ContentLength/ContentType/ServerInternalState/Upgrade
// and HeaderFlags fields since HeaderGetValue() would return ''
// - force HeadersUnFiltered=true to store all headers including the
// connection-related fields, but increase memory and reduce performance
function GetHeader(HeadersUnFiltered: boolean = false): boolean;
/// retrieve the HTTP body (after uncompression if necessary)
// - into Content or DestStream
procedure GetBody(DestStream: TStream = nil);
/// add an header 'name: value' entry
procedure HeaderAdd(const aValue: RawUtf8);
/// set all Header values at once, from CRLF delimited text
// - won't parse the ContentLength/ContentType/ServerInternalState/Upgrade
// and HeaderFlags fields
procedure HeaderSetText(const aText: RawUtf8; const aForcedContentType: RawUtf8 = '');
/// finalize all Http.Headers values
// - you can optionally specify a value to be added as 'RemoteIP: ' header
// - default GetHeader(HeadersUnFiltered=false) won't include the connection
// related headers like ContentLength/ContentType/ServerInternalState/Upgrade
procedure HeadersPrepare(const aRemoteIP: RawUtf8);
/// HeaderGetValue('CONTENT-TYPE')='text/html', e.g.
// - supplied aUpperName should be already uppercased
// - note that GetHeader(HeadersUnFiltered=false) will set ContentType field
// but let HeaderGetValue('CONTENT-TYPE') return ''
function HeaderGetValue(const aUpperName: RawUtf8): RawUtf8;
{$ifdef HASINLINE} inline; {$endif}
/// will register a compression algorithm
// - used e.g. to compress on the fly the data, with standard gzip/deflate
// or custom (synlz) protocols
// - returns true on success, false if this function or this
// ACCEPT-ENCODING: header was already registered
// - you can specify a minimal size (in bytes) before which the content won't
// be compressed (1024 by default, corresponding to a MTU of 1500 bytes)
// - the first registered algorithm will be the prefered one for compression
// within each priority level (the lower aPriority first)
function RegisterCompress(aFunction: THttpSocketCompress;
aCompressMinSize: integer = 1024; aPriority: integer = 10): boolean;
end;
{ ******************** Abstract Server-Side Types e.g. for Client-Server Protocol }
type
{$M+} // to have existing RTTI for published properties
THttpServerRequestAbstract = class;
{$M-}
/// a genuine identifier for a given client connection on server side
// - maps e.g. http.sys ID, or a genuine 31-bit sequence increasing counter,
// or the 'X-Conn-ID' header value behind a nginx reverse proxy
THttpServerConnectionID = Int64;
/// a dynamic array of client connection identifiers, e.g. for broadcasting
THttpServerConnectionIDDynArray = array of THttpServerConnectionID;
/// an opaque connection-specific pointers identifier with a strong type
// - each THttpAsyncServerConnection or THttpServerSocket raw connection instance
// maintains those two abstract PtrUInt tags, as a fConnectionOpaque field
// - match TRestServerConnectionOpaque as defined in mormot.rest.core
THttpServerConnectionOpaque = record
/// pointer-sized tag reserved to mORMot (e.g. to idenfity a REST session)
ValueInternal: PtrUInt;
/// pointer-sized tag free for the end-user code
// - could be used to avoid a lookup to a ConnectionID-indexed dictionary
ValueExternal: PtrUInt;
end;
/// reference to an opaque connection-specific pointer identifier
// - may be nil if unsupported, e.g. by the http.sys servers
PHttpServerConnectionOpaque = ^THttpServerConnectionOpaque;
/// the server-side available authentication schemes
// - as used by THttpServerRequest.AuthenticationStatus
// - hraNone..hraKerberos will match low-level HTTP_REQUEST_AUTH_TYPE enum as
// defined in HTTP 2.0 API
THttpServerRequestAuthentication = (
hraNone,
hraFailed,
hraBasic,
hraDigest,
hraNtlm,
hraNegotiate,
hraKerberos);
/// available THttpServerRequest connection attributes
// - hsrHttps is set if the communication was made over HTTPS
// - hsrSecured if the transmission is encrypted or in-process, using
// e.g. HTTPS/TLS or our proprietary AES/ECDHE algorithm over WebSockets
// - hsrWebsockets if communication was made using WebSockets
// - hsrInProcess when run from the same process, i.e. on server side
// - hsrConnectionUpgrade when "connection: upgrade" appears within headers
// - hsrAuthorized when a valid "authorization:" header is set (and
// THttpRequestContext.BearerToken is filled with the authorized User)
// - hsrHttp10 is set if the connection is of old HTTP/1.0 level
// - should exactly match TRestUriParamsLowLevelFlag in mormot.rest.core
THttpServerRequestFlag = (
hsrHttps,
hsrSecured,
hsrWebsockets,
hsrInProcess,
hsrConnectionUpgrade,
hsrAuthorized,
hsrHttp10);
/// the THttpServerRequest connection attributes
THttpServerRequestFlags = set of THttpServerRequestFlag;
/// event handler used by THttpServerGeneric.OnRequest, OnBeforeRequest and
// OnAfterRequest
// - Ctxt defines both input and output parameters
// - result of the function is the HTTP status/error code (200 if OK, e.g.)
TOnHttpServerRequest = function(Ctxt: THttpServerRequestAbstract): cardinal of object;
/// raw parameter type of TOnHttpServerAfterResponse
// - THttpServerRequest instance has already been reset in mormot.net.async
// - we use a record to minimize the stack size between methods calls
// - we use opaque pointer fields instead of RawUtf8 to avoid any ref-count
// - State should be hrsResponseDone on success, or the failing state
TOnHttpServerAfterResponseContext = record
User, Method, Host, Url, Referer, UserAgent, RemoteIP: pointer; // = RawUtf8
Connection: THttpServerConnectionID;
Flags: THttpServerRequestFlags;
State: THttpRequestState;
StatusCode: cardinal;
ElapsedMicroSec, Tix64: Int64;
Received, Sent: QWord;
end;
/// event handler used by THttpServerGeneric.OnAfterResponse property
// - main purpose is to apply post-response e.g. logging or real-time analysis
// using THttpAfterResponse classes (e.g. THttpLogger or THttpAnalyzer)
TOnHttpServerAfterResponse = procedure(
var Context: TOnHttpServerAfterResponseContext) of object;
/// event handler used by THttpServerGeneric.OnBeforeBody property
// - if defined, is called just before the body is retrieved from the client
// - supplied parameters reflect the current input state, and could be
// modified on the fly to adapt to the expected behavior
// - aBearerToken is either the "Authorization: Bearer xxxx" token, or
// the authenticated user name if the hsrAuthorized flag is set
// - should return HTTP_SUCCESS=200 to continue the process, or an HTTP error
// code (e.g. HTTP_FORBIDDEN or HTTP_PAYLOADTOOLARGE) to reject the request
// - returning HTTP_UNAUTHORIZED triggers the internal authentication process
// available on some servers (e.g. THttpApiServer or THttpAsyncServer)
TOnHttpServerBeforeBody = function(var aUrl, aMethod, aInHeaders,
aInContentType, aRemoteIP, aBearerToken: RawUtf8; aContentLength: Int64;
aFlags: THttpServerRequestFlags): cardinal of object;
/// event handler used by THttpServer.Process to send a local file
// when STATICFILE_CONTENT_TYPE content-type is returned by the service
// - can be defined e.g. to use NGINX X-Accel-Redirect header
// - should return true if the Context has been modified to serve the file, or
// false so that the file will be manually read and sent from memory
// - any exception during process will be returned as a HTTP_NOTFOUND page
TOnHttpServerSendFile = function(Context: THttpServerRequestAbstract;
const LocalFileName: TFileName): boolean of object;
{$M+}
/// abstract generic input/output structure used for HTTP server requests
// - URL/Method/InHeaders/InContent properties are input parameters
// - OutContent/OutContentType/OutCustomHeader are output parameters
// - this abstract class may be used in communication protocols, without
// the need to add mormot.net.server.pas dependency
THttpServerRequestAbstract = class
protected
fRemoteIP,
fUrl,
fMethod,
fInHeaders,
fInContentType,
fAuthenticatedUser,
fHost,
fAuthBearer,
fUserAgent,
fOutContentType,
fOutCustomHeaders: RawUtf8;
fInContent,
fOutContent: RawByteString;
fConnectionID: THttpServerConnectionID;
fConnectionFlags: THttpServerRequestFlags;
fAuthenticationStatus: THttpServerRequestAuthentication;
fInternalFlags: set of (ifUrlParamPosSet);
fRespStatus: integer;
fConnectionThread: TThread;
fConnectionOpaque: PHttpServerConnectionOpaque;
fUrlParamPos: PUtf8Char; // may be set by TUriTreeNode.LookupParam
fRouteNode: TRadixTreeNodeParams; // is a TUriTreeNode
fRouteName: pointer; // TRawUtf8DynArray set by TUriTreeNode.LookupParam
fRouteValuePosLen: TIntegerDynArray; // [pos1,len1,...] pairs in fUri
fHttp: PHttpRequestContext; // as supplied to Prepare()
function GetRouteValuePosLen(const Name: RawUtf8;
var Value: TValuePUtf8Char): boolean;
function GetRouteValue(const Name: RawUtf8): RawUtf8;
{$ifdef HASINLINE} inline; {$endif}
function EnsureUrlParamPosExists: PUtf8Char;
{$ifdef HASINLINE} inline; {$endif}
public
/// prepare an incoming request from a parsed THttpRequestContext
// - will set input parameters URL/Method/InHeaders/InContent/InContentType
// - won't reset other parameters: should come after a plain Create or
// an explicit THttpServerRequest.Recycle()
procedure Prepare(const aHttp: THttpRequestContext; const aRemoteIP: RawUtf8;
aAuthorize: THttpServerRequestAuthentication);
/// prepare an incoming request from explicit values
// - could be used for non-HTTP execution, e.g. from a WebSockets link
procedure PrepareDirect(const aUrl, aMethod, aInHeaders: RawUtf8;
const aInContent: RawByteString; const aInContentType, aRemoteIP: RawUtf8);
{$ifdef HASINLINE} inline; {$endif}
/// append some lines to the InHeaders input parameter
procedure AddInHeader(AppendedHeader: RawUtf8);
/// append some values to the OutCustomHeaders output parameter
// - will maintain CRLF between lines, but not on the last line
procedure AddOutHeader(const Values: array of const);
/// will extract the "content-type" from OutCustomHeaders into OutContentType
procedure ExtractOutContentType;
{$ifdef HASINLINE} inline; {$endif}
/// input parameter containing the caller message body
// - e.g. some GET/POST/PUT JSON data can be specified here
property InContent: RawByteString
read fInContent write fInContent;
/// output parameter to be set to the response message body
property OutContent: RawByteString
read fOutContent write fOutContent;
/// the thread which owns the connection of this execution context
// - may be nil, depending on the HTTP server used
// - depending on the HTTP server used, may not follow ConnectionID
property ConnectionThread: TThread
read fConnectionThread;
/// some HTTP servers support a per-connection pointer storage
// - may be nil if unsupported, e.g. by the http.sys servers
// - could be used to avoid a lookup to a ConnectionID-indexed dictionary
property ConnectionOpaque: PHttpServerConnectionOpaque
read fConnectionOpaque;
/// returns the TUriRouter <parameter> value parsed from URI as text
// - Name lookup is case-sensitive
// - is the default property to this function, so that you could write
// ! Ctxt['param']
property RouteValue[const Name: RawUtf8]: RawUtf8
read GetRouteValue; default;
/// returns the TUriRouter <parameter> value parsed from URI as Int64
// - Name lookup is case-sensitive
function RouteInt64(const Name: RawUtf8; out Value: Int64): boolean;
/// returns the TUriRouter <parameter> value parsed from URI as RawUtf8
// - Name lookup is case-sensitive
function RouteUtf8(const Name: RawUtf8; out Value: RawUtf8): boolean;
/// check a TUriRouter <parameter> value parsed from URI
// - both Name lookup and value comparison are case-sensitive
function RouteEquals(const Name, ExpectedValue: RawUtf8): boolean;
/// returns the TUriRouter <parameter> value from its 0-based index as text buffer
function RouteAt(ParamIndex: PtrUInt; var Value: TValuePUtf8Char): boolean;
/// an additional custom parameter, as provided to TUriRouter.Setup
function RouteOpaque: pointer; virtual; abstract;
/// retrieve and decode an URI-encoded parameter as UTF-8 text
// - UpperName should follow the UrlDecodeValue() format, e.g. 'NAME='
function UrlParam(const UpperName: RawUtf8; out Value: RawUtf8): boolean; overload;
/// retrieve and decode an URI-encoded parameter as 32-bit unsigned cardinal
// - UpperName should follow the UrlDecodeCardinal() format, e.g. 'COUNT='
function UrlParam(const UpperName: RawUtf8; out Value: cardinal): boolean; overload;
/// retrieve and decode an URI-encoded parameter as 64-bit signed Int64
// - UpperName should follow the UrlDecodeInt64() format, e.g. 'ID='
function UrlParam(const UpperName: RawUtf8; out Value: Int64): boolean; overload;
/// set the OutContent and OutContentType fields with the supplied JSON
function SetOutJson(const Json: RawUtf8): cardinal; overload;
{$ifdef HASINLINE} inline; {$endif}
/// set the OutContent and OutContentType fields with the supplied JSON
// - this function returns HTTP_SUCCESS
function SetOutJson(const Fmt: RawUtf8; const Args: array of const): cardinal; overload;
/// set the OutContent and OutContentType fields with the supplied text
// - this function returns HTTP_SUCCESS
function SetOutText(const Fmt: RawUtf8; const Args: array of const;
const ContentType: RawUtf8 = TEXT_CONTENT_TYPE): cardinal;
/// set the OutContent and OutContentType fields to return a specific file
// - returning status 200 with the STATICFILE_CONTENT_TYPE constant marker
// - Handle304NotModified = TRUE will check the file age and size and return
// status HTTP_NOTMODIFIED (304) if the file did not change
// - set CacheControlMaxAgeSec<>0 to include a Cache-Control: max-age=xxx header
// - can optionally return FileSize^ (0 if not found, -1 if is a folder)
function SetOutFile(const FileName: TFileName; Handle304NotModified: boolean;
const ContentType: RawUtf8 = ''; CacheControlMaxAgeSec: integer = 0;
FileSize: PInt64 = nil): cardinal;
/// set the OutContent and OutContentType fields to return a specific file
// - returning status 200 with the supplied Content (and optional ContentType)
// - Handle304NotModified = TRUE will check the supplied content and return
// status HTTP_NOTMODIFIED (304) if it did not change
function SetOutContent(const Content: RawByteString; Handle304NotModified: boolean;
const ContentType: RawUtf8 = ''; CacheControlMaxAgeSec: integer = 0): cardinal;
published
/// input parameter containing the caller URI
property Url: RawUtf8
read fUrl write fUrl;
/// input parameter containing the caller method (GET/POST...)
property Method: RawUtf8
read fMethod write fMethod;
/// input parameter containing the caller message headers
property InHeaders: RawUtf8
read fInHeaders write fInHeaders;
// input parameter defining the caller message body content type
property InContentType: RawUtf8
read fInContentType write fInContentType;
/// output HTTP response status
property RespStatus: integer
read fRespStatus write fRespStatus;
/// output parameter to define the reponse message body content type
// - if OutContentType is STATICFILE_CONTENT_TYPE (i.e. '!STATICFILE'),
// then OutContent is the UTF-8 file name of a file to be sent to the
// client via http.sys or NGINX's X-Accel-Redirect header (faster than
// local buffering/sending)
// - if OutContentType is NORESPONSE_CONTENT_TYPE (i.e. '!NORESPONSE'), then
// the actual transmission protocol may not wait for any answer - used
// e.g. for WebSockets
property OutContentType: RawUtf8
read fOutContentType write fOutContentType;
/// output parameter to be sent back as the response message header
// - e.g. to set Content-Type/Location
property OutCustomHeaders: RawUtf8
read fOutCustomHeaders write fOutCustomHeaders;
/// the client remote IP, as specified to Prepare()
property RemoteIP: RawUtf8
read fRemoteIP write fRemoteIP;
/// the "Host" HTTP header token, as specified to Prepare()
property Host: RawUtf8
read fHost write fHost;
/// the "Bearer" HTTP header token, as specified to Prepare()
property AuthBearer: RawUtf8
read fAuthBearer write fAuthBearer;
/// the "User-Agent" HTTP header token, as specified to Prepare()
property UserAgent: RawUtf8
read fUserAgent write fUserAgent;
/// the ID of the connection which called this execution context
// - e.g. mormot.net.websocket's TWebSocketProcess.NotifyCallback method
// would use this property to specify the client connection to be notified
// - is set as an Int64 to match http.sys ID type, but will be an
// increasing 31-bit integer sequence for (web)socket-based servers
property ConnectionID: THttpServerConnectionID
read fConnectionID;
/// define how the client is connected
property ConnectionFlags: THttpServerRequestFlags
read fConnectionFlags write fConnectionFlags;
/// contains the THttpServer-side authentication status
// - e.g. when using http.sys authentication with HTTP API 2.0
property AuthenticationStatus: THttpServerRequestAuthentication
read fAuthenticationStatus write fAuthenticationStatus;
/// contains the THttpServer-side authenticated user name, UTF-8 encoded
// - e.g. when using http.sys authentication with HTTP API 2.0, the
// domain user name is retrieved from the supplied AccessToken
// - could also be set by the THttpServerGeneric.Request() method, after
// proper authentication, so that it would be logged as expected
property AuthenticatedUser: RawUtf8
read fAuthenticatedUser write fAuthenticatedUser;
end;
{$M-}
/// store a list of IPv4 which should be rejected at connection
// - more tuned than TIPBan for checking just after accept()
// - used e.g. to implement hsoBan40xIP or THttpPeerCache instable
// peers list (with a per-minute resolution)
// - the DoRotate method should be called every second
THttpAcceptBan = class(TObjectOSLightLock)
protected
fCount, fLastSec: integer;
fIP: array of TCardinalDynArray; // one [0..fMax] IP array per second
fSeconds, fMax, fWhiteIP: cardinal;
fRejected, fTotal: Int64;
function IsBannedRaw(ip4: cardinal): boolean;
function DoRotateRaw: integer;
procedure SetMax(Value: cardinal);
procedure SetSeconds(Value: cardinal);
procedure SetIP;
public
/// initialize the thread-safe storage process
// - banseconds should be <= 128, and will be rounded up to a power-of-two
// - maxpersecond is the maximum number of banned IPs remembered per second
constructor Create(banseconds: cardinal = 4; maxpersecond: cardinal = 1024;
banwhiteip: cardinal = cLocalhost32); reintroduce;
/// register an IP4 to be rejected
function BanIP(ip4: cardinal): boolean; overload;
/// register an IP4 to be rejected
function BanIP(const ip4: RawUtf8): boolean; overload;
{$ifdef HASINLINE} inline; {$endif}
/// fast check if this IP4 is to be rejected
function IsBanned(const addr: TNetAddr): boolean; overload;
{$ifdef HASINLINE} inline; {$endif}
/// fast check if this IP4 is to be rejected
function IsBanned(ip4: cardinal): boolean; overload;
{$ifdef HASINLINE} inline; {$endif}
/// register an IP4 if status in >= 400 (but not 401 HTTP_UNAUTHORIZED)
function ShouldBan(status, ip4: cardinal): boolean; overload;
{$ifdef HASINLINE} inline; {$endif}
/// register an IP4 if status in >= 400 (but not 401 HTTP_UNAUTHORIZED)
function ShouldBan(status: cardinal; const ip4: RawUtf8): boolean; overload;
{$ifdef HASINLINE} inline; {$endif}
/// to be called every second to remove deprecated bans from the list
// - implemented via a round-robin list of per-second banned IPs
// - if you call it at another pace (e.g. every minute), then the list
// Time-To-Live will follow this unit of time instead of seconds
// - returns the number of freed bans
function DoRotate: integer;
{$ifdef HASINLINE} inline; {$endif}
/// a 32-bit IP4 which should never be banned
// - is set to cLocalhost32, i.e. 127.0.0.1, by default
property WhiteIP: cardinal
read fWhiteIP write fWhiteIP;
/// how many seconds a banned IP4 should be rejected
// - will set the closest power of two <= 128, with a default of 4
// - when set, any previous banned IP will be flushed
property Seconds: cardinal