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
| ///////////////////////////////////////////////////////////////////////////
// //
// Copyright (c) 2015 by Charta Software B.V. //
// All rights reserved //
// //
// Version: 1.7.0.83525 //
// Web site: https://pascal.chartasoftware.com/ //
// //
// This code and information are provided "as is" without warranty of //
// any kind. Dissemination of this information or reproduction of //
// this material is strictly forbidden unless prior written permission //
// is obtained from Charta Software B.V.. //
// //
///////////////////////////////////////////////////////////////////////////
unit Http.Request;
interface
uses
Boolean,
Collection.List,
DateTime.Calendar.Gregorian.Instant,
Http.Authorization,
Http.Cookie,
Http.Depth,
Http.Header.Field,
Http.Message,
Http.Range,
Http.Request.Line,
Http.Version,
Internet.MediaType,
Socket.Established,
Stream.Input,
Stream.Output,
Text,
Uri;
type
THttpRequest = class(THttpMessage)
private
FIfModifiedSince: TGregorianInstant;
procedure SetIfModifiedSince(const Value: TGregorianInstant);
protected
procedure TranslateHeaderField(Field: THttpHeaderField);
procedure TranslateHeaderFields();
procedure ParseAcceptField(Value: TText);
public
ClientSocket: TEstablishedSocket;
RequestLine: THttpRequestLine;
Host: TUriAuthority;
Authorization: THttpAuthorization;
Referer: TUri;
Accept: TList<TMediaType>;
AcceptEncoding: TText;
KeepAlive: TText;
UserAgent: TText;
Cookies: THttpCookies;
Depth: THttpDepth;
Range: THttpRange;
Destination: TUri;
Overwrite: TBoolean;
constructor Create(); override;
destructor Destroy(); override;
procedure Read(Stream: TInputStream);
procedure Write(Stream: TOutputStream);
function GetVersion(): THttpVersion; override;
property IfModifiedSince: TGregorianInstant read FIfModifiedSince write SetIfModifiedSince;
end;
implementation
uses
Character.Encoding,
Http.Connection,
Http.DateTime.TextFormat,
Http.Header.Reader,
Http.TransferEncoding,
Integer._32,
Integer._32.TextFormat,
Object_.List,
Object_.Reference.Counter,
Stream,
Text.Collation,
Text.Writer;
{ THttpRequest }
constructor THttpRequest.Create();
begin
inherited Create();
Cookies := THttpCookies.Create();
Cookies.DuplicatePolicy := hdcpFree;
Host := TUriAuthority.New();
Overwrite := True;
Accept := TObjectList<TMediaType>.Create();
Accept.ElementManager := TReferenceCounter<TMediaType>.Create();
RequestLine := THttpRequestLine.Create();
RequestLine.Version.Major := 1;
RequestLine.Version.Minor := 1;
end;
destructor THttpRequest.Destroy();
begin
IfModifiedSince.ReleaseReference();
Cookies.Free();
Range.Free();
Accept.Free();
RequestLine.Free();
Authorization.Free();
inherited Destroy();
end;
function THttpRequest.GetVersion(): THttpVersion;
begin
Result := RequestLine.Version;
end;
procedure THttpRequest.ParseAcceptField(Value: TText);
var
MediaType: TMediaType;
begin
while Value.Count > 0 do
begin
MediaType := TMediaType.FromText(Value.Parse(','));
if MediaType <> nil then
Accept.Add(MediaType);
end;
end;
procedure THttpRequest.Read(Stream: TInputStream);
var
Reader: THttpHeaderReader;
begin
Reader := THttpHeaderReader.Create(Stream);
Header := Reader.Read();
RequestLine.SetFromText(Header.StartLine);
TranslateHeaderFields();
ReadEntity(Reader, Stream);
Reader.Free();
end;
procedure THttpRequest.SetIfModifiedSince(const Value: TGregorianInstant);
begin
if Value <> IfModifiedSince then
begin
Value.AddReference();
IfModifiedSince.ReleaseReference();
FIfModifiedSince := Value;
end;
end;
procedure THttpRequest.TranslateHeaderField(Field: THttpHeaderField);
var
Name: TText;
begin
Name := Field.Name;
if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Host') then
Host := TUriAuthority.NewFromText(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Referer') then
Referer := TUri.New(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Connection') then
Connection := TextToHttpConnection(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Content-Length') then
ContentLength := TextToInteger32(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Content-Type') then
Entity.ContentType := TMediaType.FromText(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Cookie') then
Cookies.ParseFromRequest(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Keep-Alive') then
KeepAlive := Field.Value
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Accept') then
ParseAcceptField(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Accept-Encoding') then
AcceptEncoding := Field.Value // TODO: Accept-Encoding follows a specific format and is not plain text
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'User-Agent') then
UserAgent := Field.Value
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'If-Modified-Since') then
IfModifiedSince := HttpTextToGregorianInstant(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Depth') then
Depth := TextToHttpDepth(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Authorization') then
Authorization := THttpAuthorization.FromText(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Range') then
Range := THttpRange.FromText(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Lock-Token') then
LockToken := Field.Value
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Destination') then
Destination := TUri.New(Field.Value)
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Overwrite') then
Overwrite := Field.Value = 'T' // TODO: we should check whether a valid value is specified because the only other value allowed is 'F' which means False
else if TTextCollation.DefaultCaseInsensitive.Equal(Name, 'Transfer-Encoding') then
TransferEncoding := TextToTransferEncoding(Field.Value);
end;
procedure THttpRequest.TranslateHeaderFields();
var
i: TInteger32;
begin
for i := 0 to Header.Fields.Count - 1 do
TranslateHeaderField(Header.Fields[i]);
end;
procedure THttpRequest.Write(Stream: TOutputStream);
var
Writer: TTextWriter;
ContentStream: TInputStream;
Index: TInteger32;
begin
Writer := TTextWriter.Create(Stream, TCharacterEncoding.Iso8859_1, TText.CarriageReturnLineFeed);
Writer.WriteLine(RequestLine.ToText());
if not Host.IsEmpty() then
Writer.WriteLine('Host: ' + Host.ToText())
else
Writer.WriteLine('Host: ' + RequestLine.RequestUri.Authority.HostName);
if Entity.ContentType <> nil then
Writer.WriteLine('Content-Type: ' + Entity.ContentType.ToText());
if Entity.ContentEncoding.Count > 0 then
Writer.WriteLine('Content-Encoding: ' + Entity.ContentEncoding);
if ContentLength > 0 then
Writer.WriteLine('Content-Length: ' + Integer32ToText(ContentLength));
if Accept.Count > 0 then
begin
// TODO: Currently we ignore the parameters of media types.
Writer.Write('Accept: ');
for Index := 0 to Accept.Count - 2 do
begin
Writer.Write(Accept[Index].ToText());
Writer.Write(',');
end;
Writer.WriteLine(Accept.Last.ToText());
end;
if AcceptEncoding <> '' then
Writer.WriteLine('Accept-Encoding: ' + AcceptEncoding);
if Authorization <> nil then
Writer.WriteLine('Authorization: ' + Authorization.ToText());
if not Referer.IsEmpty then
Writer.WriteLine('Referer: ' + Referer.ToText());
if Cookies.Count > 0 then
Writer.WriteLine('Cookie: ' + Cookies.ToText(hctRequest));
if UserAgent <> '' then
Writer.WriteLine('User-Agent: ' + UserAgent);
if KeepAlive <> '' then
Writer.WriteLine('Keep-Alive: ' + KeepAlive);
if IfModifiedSince <> nil then
Writer.WriteLine('If-Modified-Since: ' + GregorianInstantToHttpText(IfModifiedSince));
if LockToken <> '' then
Writer.WriteLine('Lock-Token: ' + LockToken);
if not Destination.IsEmpty() then
Writer.WriteLine('Destination: ' + Destination.ToText());
if not Overwrite then
Writer.WriteLine('Overwrite: F');
if Connection <> [] then
Writer.WriteLine('Connection: ' + HttpConnectionToText(Connection));
if TransferEncoding <> teNone then
Writer.WriteLine('Transfer-Encoding: ' + TransferEncodingToText(TransferEncoding));
if Range <> nil then
Writer.WriteLine('Range: ' + Range.ToText());
Writer.WriteLine('');
Writer.Free();
if ContentLength > 0 then
begin
ContentStream := Entity.Content.CreateInputStream();
CopyStream(ContentStream, Stream, ContentLength);
ContentStream.Free();
end;
Stream.Flush();
end;
end.
|