Files
Password-Manager/delphi-backend/Source/PM.Router.pas
T
r-zakarya 1fa5113c02 feat(licence): deuxieme couche, l'API refuse sans licence valable
L'ecran d'activation ne consultait qu'un branchement : le rapiecer
ouvrait tout, l'API ne demandant rien. Chaque route de PM.Router passe
maintenant par PM.LicenceGate.ApiLicenceOk avant son handler.

Aucune fonction de decision partagee avec PM.Licence — sinon un seul
rapiecage ouvrirait les deux couches. La verification est refaite de
bout en bout, appels CNG redeclares compris ; seules la cle publique et
le nom de la pref sont partages, parce que ce sont des donnees.

Branchee dans DispatchRequest : les fichiers de l'ecran d'activation
passent par TryServeEmbedded, non garde, sinon l'ecran ne se chargerait
plus. Un oui est retenu pour le processus, un non jamais.

Le risque etait qu'elle refuse un client licencie. Auto-test sur un
vecteur lie a une empreinte fixe, sens accepte en premier ; et accord
verifie en conditions reelles sur ce poste : IsLicensed = TRUE,
ApiLicenceOk = TRUE.

Documente aussi le piege du jour, sans lien avec ce code : des .dcu TMS
x64 deposes a cote des sources cassaient le build Win32, et la sortie
redirigee vers E: faute de place sur Z:.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 22:55:33 +01:00

119 lines
2.7 KiB
ObjectPascal

unit PM.Router;
{
URL dispatcher. Mirrors the switch(true) pattern of api.php.
Each handler unit registers its routes here. The Router itself owns no state.
}
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections,
System.RegularExpressions,
IdCustomHTTPServer;
type
TRouteParams = TArray<string>;
TRouteHandler = reference to procedure(
ARequest: TIdHTTPRequestInfo;
AResponse: TIdHTTPResponseInfo;
const AParams: TRouteParams);
TRoute = record
Method: string;
Pattern: string; // regex; ^ and $ added automatically
Regex: TRegEx;
Handler: TRouteHandler;
end;
TPMRouter = class
private
FRoutes: TList<TRoute>;
public
constructor Create;
destructor Destroy; override;
procedure Register(const AMethod, APattern: string;
const AHandler: TRouteHandler);
function DispatchRequest(ARequest: TIdHTTPRequestInfo;
AResponse: TIdHTTPResponseInfo): Boolean;
end;
var
Router: TPMRouter;
implementation
uses
PM.LicenceGate;
constructor TPMRouter.Create;
begin
inherited;
FRoutes := TList<TRoute>.Create;
end;
destructor TPMRouter.Destroy;
begin
FRoutes.Free;
inherited;
end;
procedure TPMRouter.Register(const AMethod, APattern: string;
const AHandler: TRouteHandler);
var
R: TRoute;
begin
R.Method := UpperCase(AMethod);
R.Pattern := APattern;
R.Regex := TRegEx.Create('^' + APattern + '$');
R.Handler := AHandler;
FRoutes.Add(R);
end;
function TPMRouter.DispatchRequest(ARequest: TIdHTTPRequestInfo;
AResponse: TIdHTTPResponseInfo): Boolean;
var
LRoute: TRoute;
LMatch: TMatch;
LParams: TRouteParams;
I: Integer;
LMethod, LPath: string;
begin
Result := False;
LMethod := UpperCase(ARequest.Command);
LPath := ARequest.Document;
for LRoute in FRoutes do
begin
if LRoute.Method <> LMethod then Continue;
LMatch := LRoute.Regex.Match(LPath);
if LMatch.Success then
begin
// DEUXIEME COUCHE DE LICENCE : ici, et seulement ici. Seules les routes
// de l'API arrivent a cette ligne — les fichiers de l'ecran d'activation
// passent par TryServeEmbedded, qui n'est pas garde, sinon l'ecran qui
// permet de s'activer ne pourrait plus se charger. 404 et non 403, comme
// le reste du verrouillage : ne pas dire ce qui manque.
if not ApiLicenceOk then
begin
AResponse.ResponseNo := 404;
AResponse.ContentText := '';
Exit(True);
end;
SetLength(LParams, LMatch.Groups.Count - 1);
for I := 1 to LMatch.Groups.Count - 1 do
LParams[I - 1] := LMatch.Groups[I].Value;
LRoute.Handler(ARequest, AResponse, LParams);
Exit(True);
end;
end;
end;
initialization
Router := TPMRouter.Create;
finalization
Router.Free;
end.