Files
Password-Manager/delphi-backend/Source/PM.Router.pas
T
r-zakarya d5234e1390 fix(licence): refus de la 2e couche indiscernable d'une route inconnue
Le 404 a corps vide etait remplace par la page HTML d'Indy, distincte du
404 JSON d'une route inexistante : on pouvait lister les routes reelles.
La 2e couche renvoie maintenant exactement le meme JSON.

Ajoute un exe d'essai qui saute l'ecran d'activation, pour eprouver la
2e couche depuis l'application. Define de compilation
LICENCE_TEST_SKIP_SCREEN, jamais un reglage : un interrupteur dans
config.txt ferait sauter la 1re couche sans rapiecage. Build dedie,
.dcu a part ; verifie que seul l'exe d'essai porte le define.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 05:36:28 +01:00

122 lines
3.0 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, PM.JSON;
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.
//
// LE MEME 404 QU'UNE ROUTE INEXISTANTE, AU CARACTERE PRES. Un corps vide
// ne suffisait pas : Indy le remplace par sa propre page, qui se
// distinguait du JSON d'une route inconnue — et revelait donc quelles
// routes existent. Refuser doit etre indiscernable de ne rien avoir.
if not ApiLicenceOk then
begin
TJSONHelper.SendError(AResponse, 404, 'Not found');
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.