Relaie les couvertures en même origine, pour les mettre en cache hors-ligne
Le cache hors-ligne des couvertures ne marchait que pour OpenLibrary : il lit les octets par fetch(), donc exige un en-tête CORS, alors que le formulaire livre accepte n'importe quelle URL. Ces images s'affichaient (une <img> n'a que faire du CORS) sans jamais pouvoir être rangées — et le fetch repartait à chaque affichage puisque rien n'était stocké. GET /api/couvertures relaie l'image depuis notre serveur. Un proxy est une surface SSRF : il est borné par deux verrous indépendants — l'URL doit déjà exister en base comme couverture, et la connexion ne s'ouvre que vers une adresse publiquement routable. Ce second verrou vit dans le ConnectCallback, pas dans une pré-vérification DNS, ce qui ferme aussi le DNS rebinding — et c'est ce qui permet de suivre les redirections, indispensables puisque covers.openlibrary.org répond 302. Tout refus répond 404 : distinguer les cas ferait du point d'entrée un oracle sur les URL connues et sur le réseau du serveur. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
using MaBibli.Api.Services.Couvertures;
|
||||
|
||||
namespace MaBibli.Api.Endpoints;
|
||||
|
||||
public static class CouverturesEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Relais d'images de couverture, pour la mise en cache hors-ligne.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// ⚠️ <b>Tout refus répond 404</b>, qu'il s'agisse d'une URL inconnue de la base, d'un schéma
|
||||
/// interdit, d'une adresse non routable ou d'un hôte injoignable. Distinguer ces cas ferait du
|
||||
/// point d'entrée un oracle : on y lirait quelles URL l'application connaît, et quelles
|
||||
/// adresses répondent depuis le serveur. Voir <see cref="ServiceCouvertures"/>.
|
||||
/// </remarks>
|
||||
public static IEndpointRouteBuilder MapCouverturesEndpoints(this IEndpointRouteBuilder routes)
|
||||
{
|
||||
routes.MapGet("/api/couvertures", async (
|
||||
string? url,
|
||||
IServiceCouvertures service,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
var resultat = await service.ObtenirAsync(url, ct);
|
||||
|
||||
return resultat.Trouvee
|
||||
? Results.File(resultat.Octets!, resultat.TypeContenu!)
|
||||
: Results.NotFound();
|
||||
})
|
||||
.WithName("RelayerCouverture")
|
||||
.WithTags("Couvertures")
|
||||
.WithSummary("Relaie une couverture déjà enregistrée, en même origine.")
|
||||
// ⚠️ Pas de `contentType` ici : il varie avec l'image (image/jpeg, image/png…), et
|
||||
// l'annotation OpenAPI REFUSE les jokers — « image/* » fait échouer le démarrage du
|
||||
// serveur, pas la compilation. Constaté en exécution.
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
return routes;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user