mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-06-07 07:04:47 -04:00
130 lines
4.3 KiB
C#
130 lines
4.3 KiB
C#
//-----------------------------------------------------------------------
|
|
// <copyright file="FaviconController.cs" company="aliasvault">
|
|
// Copyright (c) aliasvault. All rights reserved.
|
|
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
|
// </copyright>
|
|
//-----------------------------------------------------------------------
|
|
|
|
namespace AliasVault.Api.Controllers;
|
|
|
|
using AliasServerDb;
|
|
using AliasVault.Api.Controllers.Abstracts;
|
|
using AliasVault.Api.Services;
|
|
using AliasVault.Shared.Models.WebApi.Favicon;
|
|
using Asp.Versioning;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
/// <summary>
|
|
/// Controller for retrieving favicons from external websites.
|
|
/// </summary>
|
|
/// <param name="userManager">UserManager instance.</param>
|
|
/// <param name="rateLimitService">In-memory per-user favicon extraction rate limiter.</param>
|
|
/// <param name="logger">Logger instance.</param>
|
|
[ApiVersion("1")]
|
|
public class FaviconController(
|
|
UserManager<AliasVaultUser> userManager,
|
|
FaviconRateLimitService rateLimitService,
|
|
ILogger<FaviconController> logger) : AuthenticatedRequestController(userManager)
|
|
{
|
|
/// <summary>
|
|
/// Maximum number of URLs accepted in a single batch request.
|
|
/// </summary>
|
|
public const int MaxBatchSize = 10;
|
|
|
|
/// <summary>
|
|
/// Extracts the favicon from a single URL.
|
|
/// </summary>
|
|
/// <param name="url">URL to extract the favicon from.</param>
|
|
/// <returns>Favicon image bytes, or null if extraction failed.</returns>
|
|
[HttpGet("Extract")]
|
|
public async Task<IActionResult> Extract(string url)
|
|
{
|
|
var user = await GetCurrentUserAsync();
|
|
if (user == null)
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
|
|
if (!rateLimitService.TryConsume(user.Id, 1))
|
|
{
|
|
return StatusCode(StatusCodes.Status429TooManyRequests);
|
|
}
|
|
|
|
try
|
|
{
|
|
var image = await FaviconExtractor.FaviconExtractor.GetFaviconAsync(url);
|
|
return Ok(new FaviconExtractModel { Image = image });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogInformation(ex, "Failed to extract favicon from {Url}", AnonymizeUrl(url));
|
|
}
|
|
|
|
return Ok(new FaviconExtractModel { Image = null });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts favicons for multiple URLs in parallel server-side. Cuts down on round trips when
|
|
/// the client needs to fetch many favicons (initial vault import, bulk re-download from the
|
|
/// storage insights page).
|
|
/// </summary>
|
|
/// <param name="request">The batch request payload.</param>
|
|
/// <returns>A list of favicon results, one per requested URL, in the same order.</returns>
|
|
[HttpPost("ExtractBatch")]
|
|
public async Task<IActionResult> ExtractBatch([FromBody] FaviconExtractBatchRequest request)
|
|
{
|
|
var user = await GetCurrentUserAsync();
|
|
if (user == null)
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
|
|
if (request.Urls.Count == 0)
|
|
{
|
|
return Ok(new FaviconExtractBatchResponse());
|
|
}
|
|
|
|
if (request.Urls.Count > MaxBatchSize)
|
|
{
|
|
return BadRequest(new ProblemDetails
|
|
{
|
|
Title = "Too many URLs",
|
|
Detail = $"Batch size is capped at {MaxBatchSize} URLs per request.",
|
|
});
|
|
}
|
|
|
|
if (!rateLimitService.TryConsume(user.Id, request.Urls.Count))
|
|
{
|
|
return StatusCode(StatusCodes.Status429TooManyRequests);
|
|
}
|
|
|
|
var images = await FaviconExtractor.FaviconExtractor.GetFaviconsAsync(request.Urls);
|
|
|
|
var response = new FaviconExtractBatchResponse
|
|
{
|
|
Results = new List<FaviconExtractBatchResult>(request.Urls.Count),
|
|
};
|
|
|
|
for (int i = 0; i < request.Urls.Count; i++)
|
|
{
|
|
response.Results.Add(new FaviconExtractBatchResult
|
|
{
|
|
Url = request.Urls[i],
|
|
Image = images[i],
|
|
});
|
|
}
|
|
|
|
return Ok(response);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Anonymizes a URL by replacing letters with 'x'. Lets us log host structure without
|
|
/// recording the actual domain a user was browsing.
|
|
/// </summary>
|
|
private static string AnonymizeUrl(string url)
|
|
{
|
|
return new string(url.Select(c => char.IsLetter(c) ? 'x' : c).ToArray());
|
|
}
|
|
}
|