Skip to content

Commit a04fc60

Browse files
committed
feat: capacidade de autenticação via cookie e melhoria de tratamento de cookies
1 parent a1a5843 commit a04fc60

10 files changed

Lines changed: 345 additions & 75 deletions

File tree

src/base/RALConsts.pas

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ interface
1313

1414
const
1515
// Versionamento
16-
RALVERSION = '1.0.1-2';
16+
RALVERSION = '1.1.0-1';
1717
RALVERSION_MAJOR = 1;
18-
RALVERSION_MINOR = 0;
19-
RALVERSION_PATCH = 1;
18+
RALVERSION_MINOR = 1;
19+
RALVERSION_PATCH = 0;
2020
RALVERSION_FULL = RALVERSION_MAJOR * 10000
2121
+ RALVERSION_MINOR * 100
2222
+ RALVERSION_PATCH;
@@ -74,11 +74,11 @@ interface
7474

7575
resourcestring
7676
{$IF DEFINED(LANG_PTBR)}
77-
{$I ..\base\ralconsts_ptbr.inc}
77+
{$I ..\languages\ralconsts_ptbr.inc}
7878
{$ELSEIF DEFINED(LANG_ESES)}
79-
{$I ..\base\ralconsts_eses.inc}
79+
{$I ..\languages\ralconsts_eses.inc}
8080
{$ELSE}
81-
{$I ..\base\ralconsts_enus.inc}
81+
{$I ..\languages\ralconsts_enus.inc}
8282
{$IFEND}
8383

8484
implementation

src/base/RALParams.pas

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ interface
1111
RALCripto, RALCriptoAES, RALStream, RALCompress, RALConsts;
1212

1313
type
14+
TRALCookieSiteScope = (cssLax, cssNone, cssStrict);
15+
16+
TRALCookie = record
17+
Name: StringRAL;
18+
Value: StringRAL;
19+
Domain: StringRAL;
20+
Path: StringRAL;
21+
Expires: TDateTime;
22+
MaxAge: Int64;
23+
HttpOnly: Boolean;
24+
SessionOnly: Boolean;
25+
Secure: Boolean;
26+
SameSite: TRALCookieSiteScope;
27+
end;
1428

1529
{ TRALParam }
1630

@@ -234,13 +248,155 @@ TEnumerator = class
234248
write FContentDispositionInline;
235249
end;
236250

251+
function GetCookieText(ACookie: TRALCookie): StringRAL;
252+
function GetRALCookieFromText(ACookieString: StringRAL): TRALCookie;
253+
function GetRALCookieFromParam(AParamName: StringRAL; AParams: TRALParams): TRALCookie;
254+
237255
implementation
238256

239257
{ TRALParam }
240258

241259
uses
242260
RALJson;
243261

262+
function DateTimeToCookieExpireDate(ADateTime: TDateTime): StringRAL;
263+
const
264+
HTTPMonths: array[1..12] of string[3] = (
265+
'Jan', 'Feb', 'Mar', 'Apr',
266+
'May', 'Jun', 'Jul', 'Aug',
267+
'Sep', 'Oct', 'Nov', 'Dec');
268+
HTTPDays: array[1..7] of string[3] = (
269+
'Sun', 'Mon', 'Tue', 'Wed',
270+
'Thu', 'Fri', 'Sat');
271+
272+
DateFormat = '"%s", dd "%s" yyyy hh:nn:ss';
273+
Expire = '%s GMT';
274+
var
275+
vInt: integer;
276+
vYear, vMonth, vDay: Word;
277+
vExpire, vValue : StringRAL;
278+
test: String;
279+
begin
280+
// Dia da semana e nome do mês precisam ter a 1a letra maiúscula
281+
ADateTime := RALDateTimeToGMT(ADateTime);
282+
DecodeDate(ADateTime, vYear, vMonth, vDay);
283+
284+
vExpire := FormatDateTime(DateFormat, ADateTime);
285+
vExpire := Format(vExpire, [HTTPDays[DayOfWeek(ADateTime)], HTTPMonths[vMonth]]);
286+
vExpire := Format(Expire, [vExpire]);
287+
Result := vExpire;
288+
//Result := 'Mon, 27 Jul 2026 14:00:00 GMT'
289+
end;
290+
291+
function GetCookieText(ACookie: TRALCookie): StringRAL;
292+
begin
293+
Result := ACookie.Name + '=' + ACookie.Value;
294+
295+
if ACookie.Domain <> '' then
296+
Result := Result + '; Domain=' + ACookie.Domain;
297+
298+
if ACookie.Path <> '' then
299+
Result := Result + '; Path=' + ACookie.Path;
300+
301+
if (not ACookie.SessionOnly) and (ACookie.Expires <> 0) then
302+
Result := Result + '; Expires=' + DateTimeToCookieExpireDate(ACookie.Expires);
303+
304+
if ACookie.Secure then
305+
Result := Result + '; Secure';
306+
307+
if ACookie.HttpOnly then
308+
Result := Result + '; HttpOnly';
309+
310+
case ACookie.SameSite of
311+
cssNone:
312+
if ACookie.Secure then
313+
Result := Result + '; SameSite=None';
314+
cssStrict:
315+
Result := Result + '; SameSite=Strict';
316+
end;
317+
end;
318+
319+
function GetRALCookieFromText(ACookieString: StringRAL): TRALCookie;
320+
var
321+
Start, P, EqPos, Len: Integer;
322+
S, Part, Name, Value: StringRAL;
323+
begin
324+
FillChar(Result, SizeOf(Result), 0);
325+
326+
S := StringReplace(ACookieString, '; ', ';', [rfReplaceAll]);
327+
Len := Length(S);
328+
if Len = 0 then
329+
Exit;
330+
331+
Start := 1;
332+
while Start <= Len do
333+
begin
334+
// Encontra o próximo ';'
335+
P := Start;
336+
while (P <= Len) and (S[P] <> ';') do
337+
Inc(P);
338+
339+
// Extrai o trecho atual (já sem espaço extra por causa do Replace)
340+
Part := Copy(S, Start, P - Start);
341+
342+
// Avança para o próximo
343+
Start := P + 1;
344+
345+
if Part = '' then
346+
Continue;
347+
348+
EqPos := Pos('=', Part);
349+
if EqPos > 0 then
350+
begin
351+
Name := Copy(Part, 1, EqPos - 1);
352+
Value := Copy(Part, EqPos + 1, MaxInt);
353+
end
354+
else
355+
begin
356+
Name := Part;
357+
Value := '';
358+
end;
359+
360+
// Comparações case-sensitive como no original (pode trocar por SameText se quiser case-insensitive)
361+
if SameText(Name, 'HttpOnly') then
362+
Result.HttpOnly := True
363+
else if SameText(Name, 'Secure') then
364+
Result.Secure := True
365+
else if SameText(Name, 'Path') then
366+
Result.Path := Value
367+
else if SameText(Name, 'Domain') then
368+
Result.Domain := Value
369+
else if SameText(Name, 'SameSite') then
370+
begin
371+
if SameText(Value, 'None') then
372+
Result.SameSite := cssNone
373+
else if SameText(Value, 'Lax') then
374+
Result.SameSite := cssLax
375+
else if SameText(Value, 'Strict') then
376+
Result.SameSite := cssStrict;
377+
end
378+
else if SameText(Name, 'Expires') then
379+
Result.Expires := HTTPDateTimeToDateTime(Value)
380+
else if SameText(Name, 'Max-Age') then
381+
Result.MaxAge := StrToInt64Def(Value, 0)
382+
else
383+
begin
384+
// Primeiro (e único) name=value que sobra é o cookie propriamente dito
385+
Result.Name := Name;
386+
Result.Value := Value;
387+
end;
388+
end;
389+
end;
390+
391+
function GetRALCookieFromParam(AParamName: StringRAL; AParams: TRALParams
392+
): TRALCookie;
393+
var
394+
vCookieStr: StringRAL;
395+
begin
396+
vCookieStr := AParams.GetKind[AParamName, rpkCOOKIE].AsString;
397+
Result := GetRALCookieFromText(vCookieStr);
398+
end;
399+
244400
procedure TRALParam.Clone(ASource: TRALParam);
245401
begin
246402
ASource.ContentDispositionInline := Self.ContentDispositionInline;

src/base/RALRequest.pas

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ TRALRequest = class(TRALHTTPHeaderInfo)
8686
function AddBody(const AText: StringRAL; const AContextType: StringRAL = rctTEXTPLAIN): TRALRequest; reintroduce;
8787
/// Adds a string cookie to the body of the request.
8888
function AddCookie(const AName: StringRAL; const AValue: StringRAL): TRALRequest; reintroduce;
89+
/// Adds a RALcookie to the header of the request.
90+
function AddCookie(const ACookie: TRALCookie): TRALRequest; reintroduce;
8991
/// Adds a string param with the "Field" kind to the request.
9092
function AddField(const AName: StringRAL; const AValue: StringRAL): TRALRequest; reintroduce;
9193
/// Adds a file to the body of the request based on the given AFileName.
@@ -250,6 +252,12 @@ function TRALRequest.AddCookie(const AName: StringRAL; const AValue: StringRAL
250252
Result := Self;
251253
end;
252254

255+
function TRALRequest.AddCookie(const ACookie: TRALCookie): TRALRequest;
256+
begin
257+
inherited AddCookie(ACookie);
258+
Result := Self;
259+
end;
260+
253261
function TRALRequest.AddFile(const AFileName: StringRAL): TRALRequest;
254262
begin
255263
inherited AddFile(AFileName);

src/base/RALResponse.pas

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface
1111
type
1212

1313
{ TRALResponse }
14+
1415
/// Base class for everything related to data response
1516
TRALResponse = class(TRALHTTPHeaderInfo)
1617
private
@@ -31,7 +32,9 @@ TRALResponse = class(TRALHTTPHeaderInfo)
3132
/// Append an UTF8 String to the response
3233
function AddBody(const AText: StringRAL; const AContextType: StringRAL = rctTEXTPLAIN): TRALResponse; reintroduce;
3334
/// Append a name:value cookie to the response
34-
function AddCookie(const AName: StringRAL; const AValue: StringRAL): TRALResponse; reintroduce;
35+
function AddCookie(const AName: StringRAL; const AValue: StringRAL): TRALResponse; reintroduce; overload; deprecated 'use AddCookie(ACookie: TRALCookie) instead';
36+
/// Append a TRALCookie to the response
37+
function AddCookie(const ACookie: TRALCookie): TRALResponse; reintroduce; overload;
3538
/// Append a custom param of type "Field" to the response
3639
function AddField(const AName: StringRAL; const AValue: StringRAL): TRALResponse; reintroduce;
3740
/// Loads and append a file to the response from given AFileName
@@ -157,7 +160,8 @@ procedure TRALResponse.GetParamsCookies(ADest: TStringList; ADateTime: TDateTime
157160
if (vParam <> nil) and (vParam.Kind = rpkCOOKIE) then
158161
begin
159162
vValue := vParam.ParamName + '=' + vParam.AsString + ';';
160-
vValue := vValue + vExpire + '; path=/';
163+
vValue := vValue + vExpire //+ '; path=/'
164+
;
161165
ADest.Add(vValue);
162166
end;
163167
end;
@@ -232,6 +236,12 @@ function TRALResponse.AddCookie(const AName: StringRAL; const AValue: StringRAL
232236
Result := Self;
233237
end;
234238

239+
function TRALResponse.AddCookie(const ACookie: TRALCookie): TRALResponse;
240+
begin
241+
inherited AddCookie(ACookie);
242+
Result := Self;
243+
end;
244+
235245
function TRALResponse.AddFile(const AFileName: StringRAL): TRALResponse;
236246
begin
237247
inherited AddFile(AFileName);

src/base/RALServer.pas

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,22 +198,24 @@ TRALServer = class(TRALComponent)
198198
FOnResponse: TRALOnReply;
199199
FOnServerError: TRALOnServerError;
200200
protected
201-
// Adds a fixed subroute from other components into server routes
201+
/// Adds a fixed subroute from other components into server routes
202202
procedure AddSubRoute(ASubRoute: TRALModuleRoutes);
203-
// Processes CORS headers
203+
/// Processes CORS headers
204204
procedure CheckCORS(AAllowOptions: boolean; AAllowMethods: StringRAL;
205205
ARequest: TRALRequest; AResponse: TRALResponse);
206-
// Used by inherited members to set SSL settings
206+
/// Used by inherited members to set SSL settings
207207
function CreateRALSSL: TRALSSL; virtual;
208-
// Removes a fixed subroute used by other components
208+
/// Decode the authentication header of the request
209+
procedure DecodeAuth(AResult: TRALRequest);
210+
/// Removes a fixed subroute used by other components
209211
procedure DelSubRoute(ASubRoute: TRALModuleRoutes);
210-
// Used by inherited members to return the SSL definitions
212+
/// Used by inherited members to return the SSL definitions
211213
function GetDefaultSSL: TRALSSL;
212-
// Checks if the current server component allows IPv6
214+
/// Checks if the current server component allows IPv6
213215
function IPv6IsImplemented: boolean; virtual;
214-
// Internal function to properly dispose the component attached to the server
216+
/// Internal function to properly dispose the component attached to the server
215217
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
216-
// Function that will call Validate from the current authentication component
218+
/// Function that will call Validate from the current authentication component
217219
function ValidateAuth(ARequest: TRALRequest; var AResponse: TRALResponse): boolean;
218220
procedure SetActive(const AValue: boolean); virtual;
219221
procedure SetAuthentication(const AValue: TRALAuthServer);
@@ -455,6 +457,43 @@ function TRALServer.CreateRALSSL: TRALSSL;
455457
Result := nil;
456458
end;
457459

460+
procedure TRALServer.DecodeAuth(AResult: TRALRequest);
461+
var
462+
vStr, vAux: StringRAL;
463+
vInt: IntegerRAL;
464+
vParam: TRALParam;
465+
tempCookie: TRALCookie;
466+
begin
467+
if Authentication = nil then
468+
Exit;
469+
470+
AResult.Authorization.AuthType := ratNone;
471+
AResult.Authorization.AuthString := '';
472+
473+
vParam := AResult.Params.GetKind['Authorization', rpkHEADER];
474+
if not vParam.IsNilOrEmpty then
475+
begin
476+
vStr := vParam.AsString;
477+
if vStr <> EmptyStr then
478+
begin
479+
vInt := Pos(' ', vStr);
480+
vAux := Trim(Copy(vStr, 1, vInt - 1));
481+
if SameText(vAux, 'Basic') then
482+
AResult.Authorization.AuthType := ratBasic
483+
else if SameText(vAux, 'Bearer') then
484+
AResult.Authorization.AuthType := ratBearer;
485+
AResult.Authorization.AuthString := Copy(vStr, vInt + 1, Length(vStr));
486+
end;
487+
end
488+
else if (Authentication is TRALServerJWTAuth)
489+
And not AResult.ParamByName('Cookie').IsNilOrEmpty then
490+
begin
491+
tempCookie := GetRALCookieFromText(AResult.ParamByName('Cookie').AsString);
492+
AResult.Authorization.AuthType := ratBearer;
493+
AResult.Authorization.AuthString := tempCookie.Value;
494+
end;
495+
end;
496+
458497
function TRALServer.CreateRoute(const ARoute: StringRAL; AReplyProc: TRALOnReply;
459498
const ADescription: StringRAL): TRALRoute;
460499
begin

0 commit comments

Comments
 (0)