Événements
Championnats du monde Power BI DataViz
14 févr., 16 h - 31 mars, 16 h
Avec 4 chances d’entrer, vous pourriez gagner un package de conférence et le rendre à la Live Grand Finale à Las Vegas
En savoir plusCe navigateur n’est plus pris en charge.
Effectuez une mise à niveau vers Microsoft Edge pour tirer parti des dernières fonctionnalités, des mises à jour de sécurité et du support technique.
Par Rutger Storm
ASP.NET Core prend en charge le téléchargement d'un ou de plusieurs fichiers à l'aide de la liaison de modèle tamponnée pour les petits fichiers et de la diffusion en continu non tamponnée pour les fichiers plus volumineux.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Soyez vigilant lorsque vous fournissez aux utilisateurs la possibilité de charger des fichiers sur un serveur. Les cyberattaquants peuvent tenter de :
Les étapes de sécurité qui réduisent la probabilité d’une attaque réussie sont les suivantes :
†L’exemple d’application illustre une approche qui répond aux critères.
Avertissement
Le chargement d’un code malveillant sur un système est généralement la première étape de l’exécution de code capable de :
Pour plus d’informations sur la réduction des vulnérabilités quand vous acceptez des fichiers d’utilisateurs, consultez les ressources suivantes :
Pour plus d’informations sur l’implémentation de mesures de sécurité, notamment des exemples de l’exemple d’application, consultez la section Validation.
Les options de stockage courantes pour les fichiers sont les suivantes :
Base de données
Stockage physique (système de fichiers ou partage réseau)
Service de stockage de données cloud, par exemple, Stockage Blob Azure.
Pour plus d’informations, consultez Démarrage rapide : Utiliser .NET pour créer un objet blob dans le stockage d’objets.
La définition des fichiers petits et grands dépend des ressources informatiques disponibles. Les applications doivent évaluer l’approche de stockage utilisée pour s’assurer qu’elles peuvent gérer les tailles attendues. Performances de la mémoire, du processeur, du disque et de la base de données de référence.
Bien que des limites spécifiques ne puissent pas être fournies sur ce qui est petit ou grand pour votre déploiement, voici quelques-unes des valeurs par défaut associées d’AspNetCore pour FormOptions
(documentation d’API) :
HttpRequest.Form
ne met pas en mémoire tampon l’ensemble du corps de la demande (BufferBody), mais il met en mémoire tampon tous les fichiers de formulaire multipart inclus.MemoryBufferThreshold
sert de limite entre les fichiers de petite et grande taille, qui est levée ou abaissée en fonction des ressources et scénarios des applications.Pour plus d’informations sur FormOptions, consultez la classe FormOptions
dans la source de référence ASP.NET Core.
Notes
Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Deux approches générales pour le chargement de fichiers sont la mise en mémoire tampon et la diffusion en continu.
des réponses
Le fichier entier est lu dans un IFormFile. IFormFile
est une représentation C# du fichier utilisé pour traiter ou enregistrer le fichier.
Le disque et la mémoire utilisés par les téléchargements de fichiers dépendent du nombre et de la taille des téléchargements de fichiers simultanés. Si une application tente de mettre en mémoire tampon un trop grand nombre de téléchargements, le site tombe en panne lorsqu'il manque de mémoire ou d'espace disque. Si la taille ou la fréquence des chargements de fichiers épuise les ressources d’application, utilisez la diffusion en continu.
Tout fichier mis en mémoire tampon unique dépassant 64 Ko est déplacé de la mémoire vers un fichier temporaire sur le disque.
Les fichiers temporaires pour les demandes plus volumineuses sont écrits à l’emplacement nommé dans la variable d’environnement ASPNETCORE_TEMP
. Si ASPNETCORE_TEMP
n’est pas défini, les fichiers sont écrits dans le dossier temporaire de l’utilisateur actuel.
La mise en mémoire tampon de petits fichiers est abordée dans les sections suivantes de cette rubrique :
Streaming
Le fichier est reçu à partir d’une requête en plusieurs parties et directement traité ou enregistré par l’application. La diffusion en continu n’améliore pas sensiblement les performances. La diffusion en continu réduit les demandes de mémoire ou d’espace disque lors du chargement de fichiers.
La diffusion en continu de fichiers volumineux est traitée dans la section Charger des fichiers volumineux avec la diffusion en continu.
Pour charger des petits fichiers, utilisez un formulaire en plusieurs parties ou construisez une demande POST avec JavaScript.
L’exemple suivant illustre l’utilisation d’un Razor formulaire Pages pour charger un seul fichier (Pages/BufferedSingleFileUploadPhysical.cshtml
dans l’exemple d’application) :
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file" />
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
L’exemple suivant est analogue à l’exemple précédent, sauf que :
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
Pour effectuer le formulaire POST en JavaScript pour les clients qui ne prennent pas en charge l’API Fetch, utilisez l’une des approches suivantes :
Utilisez un polyfill Fetch (récupération) (par exemple, window.fetch polyfill (github/fetch)).
Utiliser XMLHttpRequest
. Par exemple :
<script>
"use strict";
function AJAXSubmit (oFormElement) {
var oReq = new XMLHttpRequest();
oReq.onload = function(e) {
oFormElement.elements.namedItem("result").value =
'Result: ' + this.status + ' ' + this.statusText;
};
oReq.open("post", oFormElement.action);
oReq.send(new FormData(oFormElement));
}
</script>
Afin de prendre en charge les téléchargements de fichiers, les formulaires HTML doivent spécifier un type d'encodage (enctype
) de multipart/form-data
.
Pour qu’un files
élément d’entrée prend en charge le chargement de plusieurs fichiers, fournissez l’attribut multiple
sur l’élément <input>
:
<input asp-for="FileUpload.FormFiles" type="file" multiple />
Les fichiers individuels téléchargés sur le serveur sont accessibles via Liaison de données en utilisant IFormFile. L’exemple d’application illustre plusieurs chargements de fichiers mis en mémoire tampon pour les scénarios de stockage physique et de base de données.
Avertissement
N’utilisez pas la FileName
propriété de IFormFile autre que pour l’affichage et la journalisation. Lors de l’affichage ou de la journalisation, html encode le nom du fichier. Un cyberattaquant peut fournir un nom de fichier malveillant, y compris des chemins d’accès complets ou relatifs. Les applications doivent :
Le code suivant supprime le chemin d’accès du nom de fichier :
string untrustedFileName = Path.GetFileName(pathName);
Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :
Lors du chargement de fichiers à l’aide de la liaison de modèle et IFormFile, la méthode d’action peut accepter :
Notes
La liaison correspond aux fichiers de formulaire par nom. Par exemple, la valeur HTML name
dans <input type="file" name="formFile">
doit correspondre au paramètre/à la propriété C# lié (FormFile
). Pour plus d’informations, consultez la section Correspondance de la valeur de l’attribut de nom au nom de paramètre de la méthode POST.
L’exemple suivant :
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Utilisez Path.GetRandomFileName
pour générer un nom de fichier sans chemin d’accès. Dans l’exemple suivant, le chemin est obtenu à partir de la configuration :
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
Le chemin passé à FileStream doit inclure le nom de fichier. Si le nom de fichier n’est pas fourni, une exception UnauthorizedAccessException est levée à l’exécution.
Les fichiers téléchargés à l'aide de cette technique IFormFile sont mis en mémoire tampon dans la mémoire ou sur le disque du serveur avant d'être traités. Dans la méthode d’action, le contenu de IFormFile est accessible sous forme de Stream. En plus du système de fichiers local, les fichiers peuvent être enregistrés dans un partage réseau ou dans un service de stockage de fichiers, tel que stockage Blob Azure.
Pour un autre exemple qui boucle plusieurs fichiers pour le chargement et utilise des noms de fichiers fiables, consultez Pages/BufferedMultipleFileUploadPhysical.cshtml.cs
dans l’exemple d’application.
Avertissement
Path.GetTempFileName provoque une IOException si plus de 65 535 fichiers sont créés sans que les fichiers temporaires précédents soient supprimés. La limite de 65 535 fichiers est une limite par serveur. Pour plus d’informations sur cette limite sur le système d’exploitation Windows, consultez les remarques dans les rubriques suivantes :
Pour stocker les données de fichiers binaires dans une base de données avec Entity Framework, définissez une propriété de type Byte sur l’entité :
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Spécifiez une propriété de modèle de page pour la classe qui inclut un IFormFile :
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Notes
IFormFile peut être utilisé directement comme paramètre d'une méthode d'action ou comme propriété d'un modèle lié. L’exemple précédent utilise une propriété de modèle lié.
FileUpload
est utilisé dans le Razor formulaire Pages :
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Lorsque le formulaire est POSTed sur le serveur, copiez le dans un flux et enregistrez-le IFormFile en tant que tableau d’octets dans la base de données. Dans l’exemple suivant, _dbContext
stocke le contexte de base de données de l’application :
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
L’exemple précédent est similaire à un scénario illustré dans l’exemple d’application :
Pages/BufferedSingleFileUploadDb.cshtml
Pages/BufferedSingleFileUploadDb.cshtml.cs
Avertissement
Soyez prudent quand vous stockez des données binaires dans des bases de données relationnelles, car cela peut avoir un impact négatif sur les performances.
Ne vous basez pas ou ne faites pas confiance à la propriété FileName
de IFormFile sans validation. La propriété FileName
ne doit être utilisée qu’à des fins d’affichage et uniquement après l’encodage HTML.
Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :
L’exemple 3.1 montre comment utiliser JavaScript pour diffuser un fichier vers une action de contrôleur. Le jeton anti-contrefaçon du fichier est généré en utilisant un attribut de filtre personnalisé, et il est passé dans les en-têtes HTTP de client et non pas dans le corps de la requête. Comme la méthode d'action traite directement les données téléchargées, la liaison au modèle de formulaire est désactivée par un autre filtre personnalisé. Dans l’action, le contenu du formulaire est lu en avec MultipartReader
, qui lit chaque MultipartSection
individuelle, traitant le fichier ou enregistrant le contenu selon ce qui est approprié. Une fois les sections multiparties lues, l’action effectue sa propre liaison de modèle.
La réponse initiale de la page charge le formulaire et enregistre un jeton de sécurité dans un cookie (via l'attribut GenerateAntiforgeryTokenCookieAttribute
). L’attribut utilise la prise en charge anti-contrefaçon intégrée d’ASP.NET Core pour définir un cookie avec un jeton de requête :
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
DisableFormValueModelBindingAttribute
est utilisé pour désactiver la liaison de modèle :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
Dans l’exemple d’application, GenerateAntiforgeryTokenCookieAttribute
et DisableFormValueModelBindingAttribute
sont appliqués en tant que filtres aux modèles d’application de page de /StreamedSingleFileUploadDb
et /StreamedSingleFileUploadPhysical
dans Startup.ConfigureServices
à l’aide deRazorconventions Pages :
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
});
Étant donné que la liaison de modèle ne lit pas le formulaire, les paramètres liés à partir du formulaire ne sont pas liés (la requête, l’itinéraire et l’en-tête continuent de fonctionner). La méthode d’action fonctionne directement avec la propriété Request
. Un MultipartReader
est utilisé pour lire chaque section. Les données de clé/valeur sont stockées dans un KeyValueAccumulator
. Après la lecture des sections multipartites, le contenu de KeyValueAccumulator
est utilisé pour lier les données du formulaire à un type de modèle.
Méthode complète StreamingController.UploadDatabase
pour la diffusion en continu vers une base de données avec EF Core :
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper
(Utilities/MultipartRequestHelper.cs
):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
Méthode complète StreamingController.UploadPhysical
pour la diffusion en continu vers un emplacement physique :
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
Dans l’exemple d’application, les vérifications de validation sont gérées par FileHelpers.ProcessStreamedFile
.
La classe de l’exemple FileHelpers
d’application illustre plusieurs vérifications des chargements de fichiers IFormFile mis en mémoire tampon et en streaming. Pour traiter IFormFile les téléchargements de fichiers mis en mémoire tampon dans l’exemple d’application, consultez la méthode ProcessFormFile
dans le fichier Utilities/FileHelpers.cs
. Pour le traitement des fichiers diffusés en continu, consultez la méthode ProcessStreamedFile
dans le même fichier.
Avertissement
Les méthodes de traitement de validation présentées dans l’exemple d’application n’analysent pas le contenu des fichiers chargés. Dans la plupart des scénarios de production, une API d’analyseur de virus/programmes malveillants est utilisée sur le fichier avant de mettre le fichier à la disposition des utilisateurs ou d’autres systèmes.
Bien que l’exemple de rubrique fournisse un exemple fonctionnel de techniques de validation, n’implémentez pas la classe FileHelpers
dans une application de production, sauf si vous :
N’implémentez jamais sans discrimination le code de sécurité dans une application sans répondre à ces exigences.
Utilisez une API d’analyse des virus/programmes malveillants tierce sur le contenu chargé.
L’analyse des fichiers est exigeante pour les ressources du serveur dans les scénarios de volume élevé. Si les performances de traitement des demandes sont réduites en raison de l’analyse des fichiers, envisagez de décharger le travail d’analyse vers un service en arrière-plan, éventuellement un service s’exécutant sur un serveur différent du serveur de l’application. En règle générale, les fichiers chargés sont conservés dans une zone mise en quarantaine jusqu’à ce que l’analyse antivirus en arrière-plan les vérifie. Lorsqu’un fichier passe, le fichier est déplacé vers l’emplacement de stockage normal du fichier. Ces étapes sont généralement effectuées conjointement avec un enregistrement de base de données qui indique l’état d’analyse d’un fichier. En utilisant une telle approche, l’application et le serveur d’applications restent concentrés sur la réponse aux demandes.
L’extension du fichier chargé doit être vérifiée par rapport à une liste d’extensions autorisées. Par exemple :
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
La signature d’un fichier est déterminée par les premiers octets au début d’un fichier. Ces octets peuvent être utilisés pour indiquer si l’extension correspond au contenu du fichier. L’exemple d’application vérifie les signatures de fichier pour quelques types de fichiers courants. Dans l’exemple suivant, la signature de fichier d’une image JPEG est vérifiée par rapport au fichier :
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
Pour obtenir des signatures de fichier supplémentaires, utilisez une base de données de signatures de fichier (résultat de recherche Google) et des spécifications de fichier officielles. La consultation des spécifications de fichiers officiels peut s’assurer que les signatures sélectionnées sont valides.
N’utilisez jamais un nom de fichier fourni par le client pour enregistrer un fichier dans le stockage physique. Créez un nom de fichier sécurisé pour le fichier en utilisant Path.GetRandomFileName ou Path.GetTempFileName pour créer un chemin d’accès complet (y compris le nom de fichier) pour le stockage temporaire.
Razor code automatiquement les valeurs de propriété html pour l’affichage. Le code suivant peut être utilisé en toute sécurité :
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
En dehors de Razor, toujours HtmlEncode le contenu de nom de fichier à partir de la demande d’un utilisateur.
De nombreuses implémentations doivent inclure une vérification que le fichier existe; sinon, le fichier est remplacé par un fichier du même nom. Fournissez une logique supplémentaire pour répondre aux spécifications de votre application.
Limitez la taille des fichiers chargés.
Dans l’exemple d’application, la taille du fichier est limitée à 2 Mo (indiqué en octets). La limite est fournie via configuration à partir du fichier appsettings.json
:
{
"FileSizeLimit": 2097152
}
est FileSizeLimit
injecté dans des classesPageModel
:
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Lorsqu’une taille de fichier dépasse la limite, le fichier est rejeté :
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
Dans les formulaires Razor autres que les données de formulaire POST ou qui utilisent directement JavaScript FormData
, le nom spécifié dans l’élément du formulaire ou FormData
doit correspondre au nom du paramètre dans l’action du contrôleur.
Dans l’exemple suivant :
Lors de l’utilisation d’un élément <input>
, l’attribut name
est défini sur la valeur battlePlans
:
<input type="file" name="battlePlans" multiple>
Lors de l’utilisation de FormData
dans JavaScript, le nom est défini sur la valeur battlePlans
:
var formData = new FormData();
for (var file in files) {
formData.append("battlePlans", file, file.name);
}
Utilisez un nom correspondant pour le paramètre de la méthode C# (battlePlans
) :
Pour une Razor méthode de gestionnaire de pages Pages nommée Upload
:
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
Pour une méthode d’action de contrôleur POST MVC :
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
MultipartBodyLengthLimit définit la limite de longueur de chaque corps en plusieurs parties. Les sections de formulaire qui dépassent cette limite lèvent un lors de l’analyse InvalidDataException. La valeur par défaut est 134 217 728 (128 Mo). Personnalisez la limite à l’aide du paramètre MultipartBodyLengthLimit dans Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute est utilisé pour définir le MultipartBodyLengthLimit pour une seule page ou action.
Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices
:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
});
Dans une Razor application Pages ou une application MVC, appliquez le filtre au modèle de page ou à la méthode d’action :
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Pour les applications hébergées par Kestrel, la taille maximale par défaut du corps de la requête est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite à l’aide de l’option de serveur MaxRequestBodySizeKestrel :
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
})
.UseStartup<Startup>();
});
RequestSizeLimitAttribute est utilisé pour définir MaxRequestBodySize pour une seule page ou action.
Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices
:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});
Dans une Razor application pages ou une application MVC, appliquez le filtre à la classe ou à la méthode d’action du gestionnaire de pages :
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Peut RequestSizeLimitAttribute
également être appliqué à l’aide de la directive @attribute
Razor :
@attribute [RequestSizeLimitAttribute(52428800)]
D’autres Kestrel limites peuvent s’appliquer aux applications hébergées par Kestrel :
La limite de requête par défaut (maxAllowedContentLength
) est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite dans le fichier web.config
. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
Le paramètre maxAllowedContentLength
s’applique seulement à IIS. Pour plus d’informations, consultez Limites de requête<requestLimits>
.
Voici certains problèmes courants rencontrés avec le chargement de fichiers et leurs solutions possibles.
L’erreur suivante indique que le fichier chargé dépasse la longueur de contenu configurée du serveur :
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
Pour plus d’informations, consultez la section IIS.
Une erreur de connexion et une réinitialisation de la connexion au serveur indiquent probablement que le fichier chargé dépasse Kestrella taille maximale du corps de la demande. Pour plus d’informations, consultez la section KestrelTaille maximale du corps de la demande. Kestrel Les limites de connexion client peuvent également nécessiter un ajustement.
Si le contrôleur accepte les fichiers téléchargés avec IFormFile mais que la valeur est null
, confirmez que le formulaire HTML spécifie une valeur enctype
de multipart/form-data
. Si cet attribut n’est pas défini sur l’élément <form>
, le chargement des fichiers ne se produit pas et les arguments IFormFile liés sont null
. Vérifiez également que le nommage de chargement dans les données de formulaire correspond à celui de l’application.
Les exemples de cette rubrique s’appuient sur MemoryStream pour contenir le contenu du fichier chargé. La limite de taille d’un MemoryStream
est int.MaxValue
. Si le scénario de chargement de fichiers de l’application nécessite que le contenu du fichier dépasse 50 Mo, utilisez une autre approche qui ne repose pas sur un seul MemoryStream
pour conserver le contenu d’un fichier chargé.
ASP.NET Core prend en charge le téléchargement d'un ou de plusieurs fichiers à l'aide de la liaison de modèle tamponnée pour les petits fichiers et de la diffusion en continu non tamponnée pour les fichiers plus volumineux.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Soyez vigilant lorsque vous fournissez aux utilisateurs la possibilité de charger des fichiers sur un serveur. Les cyberattaquants peuvent tenter de :
Les étapes de sécurité qui réduisent la probabilité d’une attaque réussie sont les suivantes :
†L’exemple d’application illustre une approche qui répond aux critères.
Avertissement
Le chargement d’un code malveillant sur un système est généralement la première étape de l’exécution de code capable de :
Pour plus d’informations sur la réduction des vulnérabilités quand vous acceptez des fichiers d’utilisateurs, consultez les ressources suivantes :
Pour plus d’informations sur l’implémentation de mesures de sécurité, notamment des exemples de l’exemple d’application, consultez la section Validation.
Les options de stockage courantes pour les fichiers sont les suivantes :
Base de données
Stockage physique (système de fichiers ou partage réseau)
Service de stockage de données, par exemple, Stockage Blob Azure
Pour plus d’informations, consultez Démarrage rapide : Utiliser .NET pour créer un objet blob dans le stockage d’objets.
Deux approches générales pour le chargement de fichiers sont la mise en mémoire tampon et la diffusion en continu.
des réponses
Le fichier entier est lu dans un IFormFile, qui est une représentation C# du fichier utilisé pour traiter ou enregistrer le fichier.
Les ressources (disque, mémoire) utilisées par les chargements de fichiers varient selon le nombre et la taille des chargements de fichiers simultanés. Si une application tente de mettre en mémoire tampon un trop grand nombre de téléchargements, le site tombe en panne lorsqu'il manque de mémoire ou d'espace disque. Si la taille ou la fréquence des chargements de fichiers épuise les ressources d’application, utilisez la diffusion en continu.
Notes
Tout fichier mis en mémoire tampon unique dépassant 64 Ko est déplacé de la mémoire vers un fichier temporaire sur le disque.
La mise en mémoire tampon de petits fichiers est abordée dans les sections suivantes de cette rubrique :
Streaming
Le fichier est reçu à partir d’une requête en plusieurs parties et directement traité ou enregistré par l’application. La diffusion en continu n’améliore pas sensiblement les performances. La diffusion en continu réduit les demandes de mémoire ou d’espace disque lors du chargement de fichiers.
La diffusion en continu de fichiers volumineux est traitée dans la section Charger des fichiers volumineux avec la diffusion en continu.
Pour charger des petits fichiers, utilisez un formulaire en plusieurs parties ou construisez une demande POST avec JavaScript.
L’exemple suivant illustre l’utilisation d’un Razor formulaire Pages pour charger un seul fichier (Pages/BufferedSingleFileUploadPhysical.cshtml
dans l’exemple d’application) :
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
L’exemple suivant est analogue à l’exemple précédent, sauf que :
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
Pour effectuer le formulaire POST en JavaScript pour les clients qui ne prennent pas en charge l’API Fetch, utilisez l’une des approches suivantes :
Utilisez un polyfill Fetch (récupération) (par exemple, window.fetch polyfill (github/fetch)).
Utiliser XMLHttpRequest
. Par exemple :
<script>
"use strict";
function AJAXSubmit (oFormElement) {
var oReq = new XMLHttpRequest();
oReq.onload = function(e) {
oFormElement.elements.namedItem("result").value =
'Result: ' + this.status + ' ' + this.statusText;
};
oReq.open("post", oFormElement.action);
oReq.send(new FormData(oFormElement));
}
</script>
Afin de prendre en charge les téléchargements de fichiers, les formulaires HTML doivent spécifier un type d'encodage (enctype
) de multipart/form-data
.
Pour qu’un files
élément d’entrée prend en charge le chargement de plusieurs fichiers, fournissez l’attribut multiple
sur l’élément <input>
:
<input asp-for="FileUpload.FormFiles" type="file" multiple>
Les fichiers individuels téléchargés sur le serveur sont accessibles via Liaison de données en utilisant IFormFile. L’exemple d’application illustre plusieurs chargements de fichiers mis en mémoire tampon pour les scénarios de stockage physique et de base de données.
Avertissement
N’utilisez pas la FileName
propriété de IFormFile autre que pour l’affichage et la journalisation. Lors de l’affichage ou de la journalisation, html encode le nom du fichier. Un cyberattaquant peut fournir un nom de fichier malveillant, y compris des chemins d’accès complets ou relatifs. Les applications doivent :
Le code suivant supprime le chemin d’accès du nom de fichier :
string untrustedFileName = Path.GetFileName(pathName);
Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :
Lors du chargement de fichiers à l’aide de la liaison de modèle et IFormFile, la méthode d’action peut accepter :
Notes
La liaison correspond aux fichiers de formulaire par nom. Par exemple, la valeur HTML name
dans <input type="file" name="formFile">
doit correspondre au paramètre/à la propriété C# lié (FormFile
). Pour plus d’informations, consultez la section Correspondance de la valeur de l’attribut de nom au nom de paramètre de la méthode POST.
L’exemple suivant :
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Utilisez Path.GetRandomFileName
pour générer un nom de fichier sans chemin d’accès. Dans l’exemple suivant, le chemin est obtenu à partir de la configuration :
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
Le chemin passé à FileStream doit inclure le nom de fichier. Si le nom de fichier n’est pas fourni, une exception UnauthorizedAccessException est levée à l’exécution.
Les fichiers téléchargés à l'aide de cette technique IFormFile sont mis en mémoire tampon dans la mémoire ou sur le disque du serveur avant d'être traités. Dans la méthode d’action, le contenu de IFormFile est accessible sous forme de Stream. En plus du système de fichiers local, les fichiers peuvent être enregistrés dans un partage réseau ou dans un service de stockage de fichiers, tel que stockage Blob Azure.
Pour un autre exemple qui boucle plusieurs fichiers pour le chargement et utilise des noms de fichiers fiables, consultez Pages/BufferedMultipleFileUploadPhysical.cshtml.cs
dans l’exemple d’application.
Avertissement
Path.GetTempFileName provoque une IOException si plus de 65 535 fichiers sont créés sans que les fichiers temporaires précédents soient supprimés. La limite de 65 535 fichiers est une limite par serveur. Pour plus d’informations sur cette limite sur le système d’exploitation Windows, consultez les remarques dans les rubriques suivantes :
Pour stocker les données de fichiers binaires dans une base de données avec Entity Framework, définissez une propriété de type Byte sur l’entité :
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Spécifiez une propriété de modèle de page pour la classe qui inclut un IFormFile :
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Notes
IFormFile peut être utilisé directement comme paramètre d'une méthode d'action ou comme propriété d'un modèle lié. L’exemple précédent utilise une propriété de modèle lié.
FileUpload
est utilisé dans le Razor formulaire Pages :
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Lorsque le formulaire est POSTed sur le serveur, copiez le dans un flux et enregistrez-le IFormFile en tant que tableau d’octets dans la base de données. Dans l’exemple suivant, _dbContext
stocke le contexte de base de données de l’application :
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
L’exemple précédent est similaire à un scénario illustré dans l’exemple d’application :
Pages/BufferedSingleFileUploadDb.cshtml
Pages/BufferedSingleFileUploadDb.cshtml.cs
Avertissement
Soyez prudent quand vous stockez des données binaires dans des bases de données relationnelles, car cela peut avoir un impact négatif sur les performances.
Ne vous basez pas ou ne faites pas confiance à la propriété FileName
de IFormFile sans validation. La propriété FileName
ne doit être utilisée qu’à des fins d’affichage et uniquement après l’encodage HTML.
Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :
L’exemple suivant montre comment utiliser JavaScript pour diffuser un fichier vers une action de contrôleur. Le jeton anti-contrefaçon du fichier est généré en utilisant un attribut de filtre personnalisé, et il est passé dans les en-têtes HTTP de client et non pas dans le corps de la requête. Comme la méthode d'action traite directement les données téléchargées, la liaison au modèle de formulaire est désactivée par un autre filtre personnalisé. Dans l’action, le contenu du formulaire est lu en avec MultipartReader
, qui lit chaque MultipartSection
individuelle, traitant le fichier ou enregistrant le contenu selon ce qui est approprié. Une fois les sections multiparties lues, l’action effectue sa propre liaison de modèle.
La réponse initiale de la page charge le formulaire et enregistre un jeton de sécurité dans un cookie (via l'attribut GenerateAntiforgeryTokenCookieAttribute
). L’attribut utilise la prise en charge anti-contrefaçon intégrée d’ASP.NET Core pour définir un cookie avec un jeton de requête :
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
DisableFormValueModelBindingAttribute
est utilisé pour désactiver la liaison de modèle :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
Dans l’exemple d’application, GenerateAntiforgeryTokenCookieAttribute
et DisableFormValueModelBindingAttribute
sont appliqués en tant que filtres aux modèles d’application de page de /StreamedSingleFileUploadDb
et /StreamedSingleFileUploadPhysical
dans Startup.ConfigureServices
à l’aide deRazorconventions Pages :
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
});
Étant donné que la liaison de modèle ne lit pas le formulaire, les paramètres liés à partir du formulaire ne sont pas liés (la requête, l’itinéraire et l’en-tête continuent de fonctionner). La méthode d’action fonctionne directement avec la propriété Request
. Un MultipartReader
est utilisé pour lire chaque section. Les données de clé/valeur sont stockées dans un KeyValueAccumulator
. Après la lecture des sections multipartites, le contenu de KeyValueAccumulator
est utilisé pour lier les données du formulaire à un type de modèle.
Méthode complète StreamingController.UploadDatabase
pour la diffusion en continu vers une base de données avec EF Core :
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper
(Utilities/MultipartRequestHelper.cs
):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
Méthode complète StreamingController.UploadPhysical
pour la diffusion en continu vers un emplacement physique :
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
Dans l’exemple d’application, les vérifications de validation sont gérées par FileHelpers.ProcessStreamedFile
.
La classe de l’exemple FileHelpers
d’application illustre plusieurs vérifications des chargements de fichiers IFormFile mis en mémoire tampon et en streaming. Pour traiter IFormFile les téléchargements de fichiers mis en mémoire tampon dans l’exemple d’application, consultez la méthode ProcessFormFile
dans le fichier Utilities/FileHelpers.cs
. Pour le traitement des fichiers diffusés en continu, consultez la méthode ProcessStreamedFile
dans le même fichier.
Avertissement
Les méthodes de traitement de validation présentées dans l’exemple d’application n’analysent pas le contenu des fichiers chargés. Dans la plupart des scénarios de production, une API d’analyseur de virus/programmes malveillants est utilisée sur le fichier avant de mettre le fichier à la disposition des utilisateurs ou d’autres systèmes.
Bien que l’exemple de rubrique fournisse un exemple fonctionnel de techniques de validation, n’implémentez pas la classe FileHelpers
dans une application de production, sauf si vous :
N’implémentez jamais sans discrimination le code de sécurité dans une application sans répondre à ces exigences.
Utilisez une API d’analyse des virus/programmes malveillants tierce sur le contenu chargé.
L’analyse des fichiers est exigeante pour les ressources du serveur dans les scénarios de volume élevé. Si les performances de traitement des demandes sont réduites en raison de l’analyse des fichiers, envisagez de décharger le travail d’analyse vers un service en arrière-plan, éventuellement un service s’exécutant sur un serveur différent du serveur de l’application. En règle générale, les fichiers chargés sont conservés dans une zone mise en quarantaine jusqu’à ce que l’analyse antivirus en arrière-plan les vérifie. Lorsqu’un fichier passe, le fichier est déplacé vers l’emplacement de stockage normal du fichier. Ces étapes sont généralement effectuées conjointement avec un enregistrement de base de données qui indique l’état d’analyse d’un fichier. En utilisant une telle approche, l’application et le serveur d’applications restent concentrés sur la réponse aux demandes.
L’extension du fichier chargé doit être vérifiée par rapport à une liste d’extensions autorisées. Par exemple :
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
La signature d’un fichier est déterminée par les premiers octets au début d’un fichier. Ces octets peuvent être utilisés pour indiquer si l’extension correspond au contenu du fichier. L’exemple d’application vérifie les signatures de fichier pour quelques types de fichiers courants. Dans l’exemple suivant, la signature de fichier d’une image JPEG est vérifiée par rapport au fichier :
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
Pour obtenir des signatures de fichier supplémentaires, utilisez une base de données de signatures de fichier (résultat de recherche Google) et des spécifications de fichier officielles. La consultation des spécifications de fichiers officiels peut s’assurer que les signatures sélectionnées sont valides.
N’utilisez jamais un nom de fichier fourni par le client pour enregistrer un fichier dans le stockage physique. Créez un nom de fichier sécurisé pour le fichier en utilisant Path.GetRandomFileName ou Path.GetTempFileName pour créer un chemin d’accès complet (y compris le nom de fichier) pour le stockage temporaire.
Razor code automatiquement les valeurs de propriété html pour l’affichage. Le code suivant peut être utilisé en toute sécurité :
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
En dehors de Razor, toujours HtmlEncode le contenu de nom de fichier à partir de la demande d’un utilisateur.
De nombreuses implémentations doivent inclure une vérification que le fichier existe; sinon, le fichier est remplacé par un fichier du même nom. Fournissez une logique supplémentaire pour répondre aux spécifications de votre application.
Limitez la taille des fichiers chargés.
Dans l’exemple d’application, la taille du fichier est limitée à 2 Mo (indiqué en octets). La limite est fournie via configuration à partir du fichier appsettings.json
:
{
"FileSizeLimit": 2097152
}
est FileSizeLimit
injecté dans des classesPageModel
:
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Lorsqu’une taille de fichier dépasse la limite, le fichier est rejeté :
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
Dans les formulaires Razor autres que les données de formulaire POST ou qui utilisent directement JavaScript FormData
, le nom spécifié dans l’élément du formulaire ou FormData
doit correspondre au nom du paramètre dans l’action du contrôleur.
Dans l’exemple suivant :
Lors de l’utilisation d’un élément <input>
, l’attribut name
est défini sur la valeur battlePlans
:
<input type="file" name="battlePlans" multiple>
Lors de l’utilisation de FormData
dans JavaScript, le nom est défini sur la valeur battlePlans
:
var formData = new FormData();
for (var file in files) {
formData.append("battlePlans", file, file.name);
}
Utilisez un nom correspondant pour le paramètre de la méthode C# (battlePlans
) :
Pour une Razor méthode de gestionnaire de pages Pages nommée Upload
:
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
Pour une méthode d’action de contrôleur POST MVC :
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
MultipartBodyLengthLimit définit la limite de longueur de chaque corps en plusieurs parties. Les sections de formulaire qui dépassent cette limite lèvent un lors de l’analyse InvalidDataException. La valeur par défaut est 134 217 728 (128 Mo). Personnalisez la limite à l’aide du paramètre MultipartBodyLengthLimit dans Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute est utilisé pour définir le MultipartBodyLengthLimit pour une seule page ou action.
Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices
:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
});
Dans une Razor application Pages ou une application MVC, appliquez le filtre au modèle de page ou à la méthode d’action :
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Pour les applications hébergées par Kestrel, la taille maximale par défaut du corps de la requête est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite à l’aide de l’option de serveur MaxRequestBodySizeKestrel :
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
})
.UseStartup<Startup>();
});
RequestSizeLimitAttribute est utilisé pour définir MaxRequestBodySize pour une seule page ou action.
Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices
:
services.AddRazorPages(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
});
Dans une Razor application pages ou une application MVC, appliquez le filtre à la classe ou à la méthode d’action du gestionnaire de pages :
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Peut RequestSizeLimitAttribute
également être appliqué à l’aide de la directive @attribute
Razor :
@attribute [RequestSizeLimitAttribute(52428800)]
D’autres Kestrel limites peuvent s’appliquer aux applications hébergées par Kestrel :
La limite de requête par défaut (maxAllowedContentLength
) est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite dans le fichier web.config
. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
Le paramètre maxAllowedContentLength
s’applique seulement à IIS. Pour plus d’informations, consultez Limites de requête<requestLimits>
.
Augmentez la taille maximale du corps de la requête HTTP en définissant IISServerOptions.MaxRequestBodySize dans Startup.ConfigureServices
. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = 52428800;
});
Pour plus d’informations, consultez Héberger ASP.NET Core sur Windows avec IIS.
Voici certains problèmes courants rencontrés avec le chargement de fichiers et leurs solutions possibles.
L’erreur suivante indique que le fichier chargé dépasse la longueur de contenu configurée du serveur :
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
Pour plus d’informations, consultez la section IIS.
Une erreur de connexion et une réinitialisation de la connexion au serveur indiquent probablement que le fichier chargé dépasse Kestrella taille maximale du corps de la demande. Pour plus d’informations, consultez la section KestrelTaille maximale du corps de la demande. Kestrel Les limites de connexion client peuvent également nécessiter un ajustement.
Si le contrôleur accepte les fichiers téléchargés avec IFormFile mais que la valeur est null
, confirmez que le formulaire HTML spécifie une valeur enctype
de multipart/form-data
. Si cet attribut n’est pas défini sur l’élément <form>
, le chargement des fichiers ne se produit pas et les arguments IFormFile liés sont null
. Vérifiez également que le nommage de chargement dans les données de formulaire correspond à celui de l’application.
Les exemples de cette rubrique s’appuient sur MemoryStream pour contenir le contenu du fichier chargé. La limite de taille d’un MemoryStream
est int.MaxValue
. Si le scénario de chargement de fichiers de l’application nécessite que le contenu du fichier dépasse 50 Mo, utilisez une autre approche qui ne repose pas sur un seul MemoryStream
pour conserver le contenu d’un fichier chargé.
ASP.NET Core prend en charge le téléchargement d'un ou de plusieurs fichiers à l'aide de la liaison de modèle tamponnée pour les petits fichiers et de la diffusion en continu non tamponnée pour les fichiers plus volumineux.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Soyez vigilant lorsque vous fournissez aux utilisateurs la possibilité de charger des fichiers sur un serveur. Les cyberattaquants peuvent tenter de :
Les étapes de sécurité qui réduisent la probabilité d’une attaque réussie sont les suivantes :
†L’exemple d’application illustre une approche qui répond aux critères.
Avertissement
Le chargement d’un code malveillant sur un système est généralement la première étape de l’exécution de code capable de :
Pour plus d’informations sur la réduction des vulnérabilités quand vous acceptez des fichiers d’utilisateurs, consultez les ressources suivantes :
Pour plus d’informations sur l’implémentation de mesures de sécurité, notamment des exemples de l’exemple d’application, consultez la section Validation.
Les options de stockage courantes pour les fichiers sont les suivantes :
Base de données
Stockage physique (système de fichiers ou partage réseau)
Service de stockage de données, par exemple, Stockage Blob Azure
Pour plus d’informations, consultez Démarrage rapide : Utiliser .NET pour créer un objet blob dans le stockage d’objets. La rubrique illustre UploadFromFileAsync, mais UploadFromStreamAsync peut être utilisée pour enregistrer un dans le FileStream stockage blob lors de l’utilisation d’un Stream.
Deux approches générales pour le chargement de fichiers sont la mise en mémoire tampon et la diffusion en continu.
des réponses
Le fichier entier est lu dans un IFormFile, qui est une représentation C# du fichier utilisé pour traiter ou enregistrer le fichier.
Les ressources (disque, mémoire) utilisées par les chargements de fichiers varient selon le nombre et la taille des chargements de fichiers simultanés. Si une application tente de mettre en mémoire tampon un trop grand nombre de téléchargements, le site tombe en panne lorsqu'il manque de mémoire ou d'espace disque. Si la taille ou la fréquence des chargements de fichiers épuise les ressources d’application, utilisez la diffusion en continu.
Notes
Tout fichier mis en mémoire tampon unique dépassant 64 Ko est déplacé de la mémoire vers un fichier temporaire sur le disque.
La mise en mémoire tampon de petits fichiers est abordée dans les sections suivantes de cette rubrique :
Streaming
Le fichier est reçu à partir d’une requête en plusieurs parties et directement traité ou enregistré par l’application. La diffusion en continu n’améliore pas sensiblement les performances. La diffusion en continu réduit les demandes de mémoire ou d’espace disque lors du chargement de fichiers.
La diffusion en continu de fichiers volumineux est traitée dans la section Charger des fichiers volumineux avec la diffusion en continu.
Pour charger des petits fichiers, utilisez un formulaire en plusieurs parties ou construisez une demande POST avec JavaScript.
L’exemple suivant illustre l’utilisation d’un Razor formulaire Pages pour charger un seul fichier (Pages/BufferedSingleFileUploadPhysical.cshtml
dans l’exemple d’application) :
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
<span asp-validation-for="FileUpload.FormFile"></span>
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload" />
</form>
L’exemple suivant est analogue à l’exemple précédent, sauf que :
<form action="BufferedSingleFileUploadPhysical/?handler=Upload"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;"
method="post">
<dl>
<dt>
<label for="FileUpload_FormFile">File</label>
</dt>
<dd>
<input id="FileUpload_FormFile" type="file"
name="FileUpload.FormFile" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output name="result"></output>
</div>
</form>
<script>
async function AJAXSubmit (oFormElement) {
var resultElement = oFormElement.elements.namedItem("result");
const formData = new FormData(oFormElement);
try {
const response = await fetch(oFormElement.action, {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.href = '/';
}
resultElement.value = 'Result: ' + response.status + ' ' +
response.statusText;
} catch (error) {
console.error('Error:', error);
}
}
</script>
Pour effectuer le formulaire POST en JavaScript pour les clients qui ne prennent pas en charge l’API Fetch, utilisez l’une des approches suivantes :
Utilisez un polyfill Fetch (récupération) (par exemple, window.fetch polyfill (github/fetch)).
Utiliser XMLHttpRequest
. Par exemple :
<script>
"use strict";
function AJAXSubmit (oFormElement) {
var oReq = new XMLHttpRequest();
oReq.onload = function(e) {
oFormElement.elements.namedItem("result").value =
'Result: ' + this.status + ' ' + this.statusText;
};
oReq.open("post", oFormElement.action);
oReq.send(new FormData(oFormElement));
}
</script>
Afin de prendre en charge les téléchargements de fichiers, les formulaires HTML doivent spécifier un type d'encodage (enctype
) de multipart/form-data
.
Pour qu’un files
élément d’entrée prend en charge le chargement de plusieurs fichiers, fournissez l’attribut multiple
sur l’élément <input>
:
<input asp-for="FileUpload.FormFiles" type="file" multiple>
Les fichiers individuels téléchargés sur le serveur sont accessibles via Liaison de données en utilisant IFormFile. L’exemple d’application illustre plusieurs chargements de fichiers mis en mémoire tampon pour les scénarios de stockage physique et de base de données.
Avertissement
N’utilisez pas la FileName
propriété de IFormFile autre que pour l’affichage et la journalisation. Lors de l’affichage ou de la journalisation, html encode le nom du fichier. Un cyberattaquant peut fournir un nom de fichier malveillant, y compris des chemins d’accès complets ou relatifs. Les applications doivent :
Le code suivant supprime le chemin d’accès du nom de fichier :
string untrustedFileName = Path.GetFileName(pathName);
Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :
Lors du chargement de fichiers à l’aide de la liaison de modèle et IFormFile, la méthode d’action peut accepter :
Notes
La liaison correspond aux fichiers de formulaire par nom. Par exemple, la valeur HTML name
dans <input type="file" name="formFile">
doit correspondre au paramètre/à la propriété C# lié (FormFile
). Pour plus d’informations, consultez la section Correspondance de la valeur de l’attribut de nom au nom de paramètre de la méthode POST.
L’exemple suivant :
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.GetTempFileName();
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size });
}
Utilisez Path.GetRandomFileName
pour générer un nom de fichier sans chemin d’accès. Dans l’exemple suivant, le chemin est obtenu à partir de la configuration :
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
var filePath = Path.Combine(_config["StoredFilesPath"],
Path.GetRandomFileName());
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
Le chemin passé à FileStream doit inclure le nom de fichier. Si le nom de fichier n’est pas fourni, une exception UnauthorizedAccessException est levée à l’exécution.
Les fichiers téléchargés à l'aide de cette technique IFormFile sont mis en mémoire tampon dans la mémoire ou sur le disque du serveur avant d'être traités. Dans la méthode d’action, le contenu de IFormFile est accessible sous forme de Stream. En plus du système de fichiers local, les fichiers peuvent être enregistrés dans un partage réseau ou dans un service de stockage de fichiers, tel que stockage Blob Azure.
Pour un autre exemple qui boucle plusieurs fichiers pour le chargement et utilise des noms de fichiers fiables, consultez Pages/BufferedMultipleFileUploadPhysical.cshtml.cs
dans l’exemple d’application.
Avertissement
Path.GetTempFileName provoque une IOException si plus de 65 535 fichiers sont créés sans que les fichiers temporaires précédents soient supprimés. La limite de 65 535 fichiers est une limite par serveur. Pour plus d’informations sur cette limite sur le système d’exploitation Windows, consultez les remarques dans les rubriques suivantes :
Pour stocker les données de fichiers binaires dans une base de données avec Entity Framework, définissez une propriété de type Byte sur l’entité :
public class AppFile
{
public int Id { get; set; }
public byte[] Content { get; set; }
}
Spécifiez une propriété de modèle de page pour la classe qui inclut un IFormFile :
public class BufferedSingleFileUploadDbModel : PageModel
{
...
[BindProperty]
public BufferedSingleFileUploadDb FileUpload { get; set; }
...
}
public class BufferedSingleFileUploadDb
{
[Required]
[Display(Name="File")]
public IFormFile FormFile { get; set; }
}
Notes
IFormFile peut être utilisé directement comme paramètre d'une méthode d'action ou comme propriété d'un modèle lié. L’exemple précédent utilise une propriété de modèle lié.
FileUpload
est utilisé dans le Razor formulaire Pages :
<form enctype="multipart/form-data" method="post">
<dl>
<dt>
<label asp-for="FileUpload.FormFile"></label>
</dt>
<dd>
<input asp-for="FileUpload.FormFile" type="file">
</dd>
</dl>
<input asp-page-handler="Upload" class="btn" type="submit" value="Upload">
</form>
Lorsque le formulaire est POSTed sur le serveur, copiez le dans un flux et enregistrez-le IFormFile en tant que tableau d’octets dans la base de données. Dans l’exemple suivant, _dbContext
stocke le contexte de base de données de l’application :
public async Task<IActionResult> OnPostUploadAsync()
{
using (var memoryStream = new MemoryStream())
{
await FileUpload.FormFile.CopyToAsync(memoryStream);
// Upload the file if less than 2 MB
if (memoryStream.Length < 2097152)
{
var file = new AppFile()
{
Content = memoryStream.ToArray()
};
_dbContext.File.Add(file);
await _dbContext.SaveChangesAsync();
}
else
{
ModelState.AddModelError("File", "The file is too large.");
}
}
return Page();
}
L’exemple précédent est similaire à un scénario illustré dans l’exemple d’application :
Pages/BufferedSingleFileUploadDb.cshtml
Pages/BufferedSingleFileUploadDb.cshtml.cs
Avertissement
Soyez prudent quand vous stockez des données binaires dans des bases de données relationnelles, car cela peut avoir un impact négatif sur les performances.
Ne vous basez pas ou ne faites pas confiance à la propriété FileName
de IFormFile sans validation. La propriété FileName
ne doit être utilisée qu’à des fins d’affichage et uniquement après l’encodage HTML.
Les exemples fournis jusqu’à présent ne prennent pas en compte les considérations de sécurité. Les sections suivantes et l’exemple d’application fournissent des informations supplémentaires :
L’exemple suivant montre comment utiliser JavaScript pour diffuser un fichier vers une action de contrôleur. Le jeton anti-contrefaçon du fichier est généré en utilisant un attribut de filtre personnalisé, et il est passé dans les en-têtes HTTP de client et non pas dans le corps de la requête. Comme la méthode d'action traite directement les données téléchargées, la liaison au modèle de formulaire est désactivée par un autre filtre personnalisé. Dans l’action, le contenu du formulaire est lu en avec MultipartReader
, qui lit chaque MultipartSection
individuelle, traitant le fichier ou enregistrant le contenu selon ce qui est approprié. Une fois les sections multiparties lues, l’action effectue sa propre liaison de modèle.
La réponse initiale de la page charge le formulaire et enregistre un jeton de sécurité dans un cookie (via l'attribut GenerateAntiforgeryTokenCookieAttribute
). L’attribut utilise la prise en charge anti-contrefaçon intégrée d’ASP.NET Core pour définir un cookie avec un jeton de requête :
public class GenerateAntiforgeryTokenCookieAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
var antiforgery = context.HttpContext.RequestServices.GetService<IAntiforgery>();
// Send the request token as a JavaScript-readable cookie
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append(
"RequestVerificationToken",
tokens.RequestToken,
new CookieOptions() { HttpOnly = false });
}
public override void OnResultExecuted(ResultExecutedContext context)
{
}
}
DisableFormValueModelBindingAttribute
est utilisé pour désactiver la liaison de modèle :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
Dans l’exemple d’application, GenerateAntiforgeryTokenCookieAttribute
et DisableFormValueModelBindingAttribute
sont appliqués en tant que filtres aux modèles d’application de page de /StreamedSingleFileUploadDb
et /StreamedSingleFileUploadPhysical
dans Startup.ConfigureServices
à l’aide deRazorconventions Pages :
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadDb",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
options.Conventions
.AddPageApplicationModelConvention("/StreamedSingleFileUploadPhysical",
model =>
{
model.Filters.Add(
new GenerateAntiforgeryTokenCookieAttribute());
model.Filters.Add(
new DisableFormValueModelBindingAttribute());
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Étant donné que la liaison de modèle ne lit pas le formulaire, les paramètres liés à partir du formulaire ne sont pas liés (la requête, l’itinéraire et l’en-tête continuent de fonctionner). La méthode d’action fonctionne directement avec la propriété Request
. Un MultipartReader
est utilisé pour lire chaque section. Les données de clé/valeur sont stockées dans un KeyValueAccumulator
. Après la lecture des sections multipartites, le contenu de KeyValueAccumulator
est utilisé pour lier les données du formulaire à un type de modèle.
Méthode complète StreamingController.UploadDatabase
pour la diffusion en continu vers une base de données avec EF Core :
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadDatabase()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
// Accumulate the form data key-value pairs in the request (formAccumulator).
var formAccumulator = new KeyValueAccumulator();
var trustedFileNameForDisplay = string.Empty;
var untrustedFileNameForStorage = string.Empty;
var streamedFileContent = Array.Empty<byte>();
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
if (MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
untrustedFileNameForStorage = contentDisposition.FileName.Value;
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
streamedFileContent =
await FileHelpers.ProcessStreamedFile(section, contentDisposition,
ModelState, _permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
}
else if (MultipartRequestHelper
.HasFormDataContentDisposition(contentDisposition))
{
// Don't limit the key name length because the
// multipart headers length limit is already in effect.
var key = HeaderUtilities
.RemoveQuotes(contentDisposition.Name).Value;
var encoding = GetEncoding(section);
if (encoding == null)
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
using (var streamReader = new StreamReader(
section.Body,
encoding,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true))
{
// The value length limit is enforced by
// MultipartBodyLengthLimit
var value = await streamReader.ReadToEndAsync();
if (string.Equals(value, "undefined",
StringComparison.OrdinalIgnoreCase))
{
value = string.Empty;
}
formAccumulator.Append(key, value);
if (formAccumulator.ValueCount >
_defaultFormOptions.ValueCountLimit)
{
// Form key count limit of
// _defaultFormOptions.ValueCountLimit
// is exceeded.
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 3).");
// Log error
return BadRequest(ModelState);
}
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
// Bind form data to the model
var formData = new FormData();
var formValueProvider = new FormValueProvider(
BindingSource.Form,
new FormCollection(formAccumulator.GetResults()),
CultureInfo.CurrentCulture);
var bindingSuccessful = await TryUpdateModelAsync(formData, prefix: "",
valueProvider: formValueProvider);
if (!bindingSuccessful)
{
ModelState.AddModelError("File",
"The request couldn't be processed (Error 5).");
// Log error
return BadRequest(ModelState);
}
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample app.
var file = new AppFile()
{
Content = streamedFileContent,
UntrustedName = untrustedFileNameForStorage,
Note = formData.Note,
Size = streamedFileContent.Length,
UploadDT = DateTime.UtcNow
};
_context.File.Add(file);
await _context.SaveChangesAsync();
return Created(nameof(StreamingController), null);
}
MultipartRequestHelper
(Utilities/MultipartRequestHelper.cs
):
using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace SampleApp.Utilities
{
public static class MultipartRequestHelper
{
// Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
// The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary;
}
public static bool IsMultipartContentType(string contentType)
{
return !string.IsNullOrEmpty(contentType)
&& contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="key";
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& string.IsNullOrEmpty(contentDisposition.FileName.Value)
&& string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
}
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
{
// Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
return contentDisposition != null
&& contentDisposition.DispositionType.Equals("form-data")
&& (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
|| !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
}
}
}
Méthode complète StreamingController.UploadPhysical
pour la diffusion en continu vers un emplacement physique :
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
else
{
// Don't trust the file name sent by the client. To display
// the file name, HTML-encode the value.
var trustedFileNameForDisplay = WebUtility.HtmlEncode(
contentDisposition.FileName.Value);
var trustedFileNameForFileStorage = Path.GetRandomFileName();
// **WARNING!**
// In the following example, the file is saved without
// scanning the file's contents. In most production
// scenarios, an anti-virus/anti-malware scanner API
// is used on the file before making the file available
// for download or for use by other systems.
// For more information, see the topic that accompanies
// this sample.
var streamedFileContent = await FileHelpers.ProcessStreamedFile(
section, contentDisposition, ModelState,
_permittedExtensions, _fileSizeLimit);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
using (var targetStream = System.IO.File.Create(
Path.Combine(_targetFilePath, trustedFileNameForFileStorage)))
{
await targetStream.WriteAsync(streamedFileContent);
_logger.LogInformation(
"Uploaded file '{TrustedFileNameForDisplay}' saved to " +
"'{TargetFilePath}' as {TrustedFileNameForFileStorage}",
trustedFileNameForDisplay, _targetFilePath,
trustedFileNameForFileStorage);
}
}
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
Dans l’exemple d’application, les vérifications de validation sont gérées par FileHelpers.ProcessStreamedFile
.
La classe de l’exemple FileHelpers
d’application illustre plusieurs vérifications des chargements de fichiers IFormFile mis en mémoire tampon et en streaming. Pour traiter IFormFile les téléchargements de fichiers mis en mémoire tampon dans l’exemple d’application, consultez la méthode ProcessFormFile
dans le fichier Utilities/FileHelpers.cs
. Pour le traitement des fichiers diffusés en continu, consultez la méthode ProcessStreamedFile
dans le même fichier.
Avertissement
Les méthodes de traitement de validation présentées dans l’exemple d’application n’analysent pas le contenu des fichiers chargés. Dans la plupart des scénarios de production, une API d’analyseur de virus/programmes malveillants est utilisée sur le fichier avant de mettre le fichier à la disposition des utilisateurs ou d’autres systèmes.
Bien que l’exemple de rubrique fournisse un exemple fonctionnel de techniques de validation, n’implémentez pas la classe FileHelpers
dans une application de production, sauf si vous :
N’implémentez jamais sans discrimination le code de sécurité dans une application sans répondre à ces exigences.
Utilisez une API d’analyse des virus/programmes malveillants tierce sur le contenu chargé.
L’analyse des fichiers est exigeante pour les ressources du serveur dans les scénarios de volume élevé. Si les performances de traitement des demandes sont réduites en raison de l’analyse des fichiers, envisagez de décharger le travail d’analyse vers un service en arrière-plan, éventuellement un service s’exécutant sur un serveur différent du serveur de l’application. En règle générale, les fichiers chargés sont conservés dans une zone mise en quarantaine jusqu’à ce que l’analyse antivirus en arrière-plan les vérifie. Lorsqu’un fichier passe, le fichier est déplacé vers l’emplacement de stockage normal du fichier. Ces étapes sont généralement effectuées conjointement avec un enregistrement de base de données qui indique l’état d’analyse d’un fichier. En utilisant une telle approche, l’application et le serveur d’applications restent concentrés sur la réponse aux demandes.
L’extension du fichier chargé doit être vérifiée par rapport à une liste d’extensions autorisées. Par exemple :
private string[] permittedExtensions = { ".txt", ".pdf" };
var ext = Path.GetExtension(uploadedFileName).ToLowerInvariant();
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
{
// The extension is invalid ... discontinue processing the file
}
La signature d’un fichier est déterminée par les premiers octets au début d’un fichier. Ces octets peuvent être utilisés pour indiquer si l’extension correspond au contenu du fichier. L’exemple d’application vérifie les signatures de fichier pour quelques types de fichiers courants. Dans l’exemple suivant, la signature de fichier d’une image JPEG est vérifiée par rapport au fichier :
private static readonly Dictionary<string, List<byte[]>> _fileSignature =
new Dictionary<string, List<byte[]>>
{
{ ".jpeg", new List<byte[]>
{
new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
}
},
};
using (var reader = new BinaryReader(uploadedFileData))
{
var signatures = _fileSignature[ext];
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
return signatures.Any(signature =>
headerBytes.Take(signature.Length).SequenceEqual(signature));
}
Pour obtenir des signatures de fichier supplémentaires, utilisez une base de données de signatures de fichier (résultat de recherche Google) et des spécifications de fichier officielles. La consultation des spécifications de fichiers officiels peut s’assurer que les signatures sélectionnées sont valides.
N’utilisez jamais un nom de fichier fourni par le client pour enregistrer un fichier dans le stockage physique. Créez un nom de fichier sécurisé pour le fichier en utilisant Path.GetRandomFileName ou Path.GetTempFileName pour créer un chemin d’accès complet (y compris le nom de fichier) pour le stockage temporaire.
Razor code automatiquement les valeurs de propriété html pour l’affichage. Le code suivant peut être utilisé en toute sécurité :
@foreach (var file in Model.DatabaseFiles) {
<tr>
<td>
@file.UntrustedName
</td>
</tr>
}
En dehors de Razor, toujours HtmlEncode le contenu de nom de fichier à partir de la demande d’un utilisateur.
De nombreuses implémentations doivent inclure une vérification que le fichier existe; sinon, le fichier est remplacé par un fichier du même nom. Fournissez une logique supplémentaire pour répondre aux spécifications de votre application.
Limitez la taille des fichiers chargés.
Dans l’exemple d’application, la taille du fichier est limitée à 2 Mo (indiqué en octets). La limite est fournie via configuration à partir du fichier appsettings.json
:
{
"FileSizeLimit": 2097152
}
est FileSizeLimit
injecté dans des classesPageModel
:
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
private readonly long _fileSizeLimit;
public BufferedSingleFileUploadPhysicalModel(IConfiguration config)
{
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
}
...
}
Lorsqu’une taille de fichier dépasse la limite, le fichier est rejeté :
if (formFile.Length > _fileSizeLimit)
{
// The file is too large ... discontinue processing the file
}
Dans les formulaires Razor autres que les données de formulaire POST ou qui utilisent directement JavaScript FormData
, le nom spécifié dans l’élément du formulaire ou FormData
doit correspondre au nom du paramètre dans l’action du contrôleur.
Dans l’exemple suivant :
Lors de l’utilisation d’un élément <input>
, l’attribut name
est défini sur la valeur battlePlans
:
<input type="file" name="battlePlans" multiple>
Lors de l’utilisation de FormData
dans JavaScript, le nom est défini sur la valeur battlePlans
:
var formData = new FormData();
for (var file in files) {
formData.append("battlePlans", file, file.name);
}
Utilisez un nom correspondant pour le paramètre de la méthode C# (battlePlans
) :
Pour une Razor méthode de gestionnaire de pages Pages nommée Upload
:
public async Task<IActionResult> OnPostUploadAsync(List<IFormFile> battlePlans)
Pour une méthode d’action de contrôleur POST MVC :
public async Task<IActionResult> Post(List<IFormFile> battlePlans)
MultipartBodyLengthLimit définit la limite de longueur de chaque corps en plusieurs parties. Les sections de formulaire qui dépassent cette limite lèvent un lors de l’analyse InvalidDataException. La valeur par défaut est 134 217 728 (128 Mo). Personnalisez la limite à l’aide du paramètre MultipartBodyLengthLimit dans Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(options =>
{
// Set the limit to 256 MB
options.MultipartBodyLengthLimit = 268435456;
});
}
RequestFormLimitsAttribute est utilisé pour définir le MultipartBodyLengthLimit pour une seule page ou action.
Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices
:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model.Filters.Add(
new RequestFormLimitsAttribute()
{
// Set the limit to 256 MB
MultipartBodyLengthLimit = 268435456
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Dans une Razor application Pages ou une application MVC, appliquez le filtre au modèle de page ou à la méthode d’action :
// Set the limit to 256 MB
[RequestFormLimits(MultipartBodyLengthLimit = 268435456)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
Pour les applications hébergées par Kestrel, la taille maximale par défaut du corps de la requête est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite à l’aide de l’option de serveur MaxRequestBodySizeKestrel :
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureKestrel((context, options) =>
{
// Handle requests up to 50 MB
options.Limits.MaxRequestBodySize = 52428800;
});
RequestSizeLimitAttribute est utilisé pour définir MaxRequestBodySize pour une seule page ou action.
Dans une Razor application Pages, appliquez le filtre avec une convention dans Startup.ConfigureServices
:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions
.AddPageApplicationModelConvention("/FileUploadPage",
model =>
{
// Handle requests up to 50 MB
model.Filters.Add(
new RequestSizeLimitAttribute(52428800));
});
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
Dans une Razor application pages ou une application MVC, appliquez le filtre à la classe ou à la méthode d’action du gestionnaire de pages :
// Handle requests up to 50 MB
[RequestSizeLimit(52428800)]
public class BufferedSingleFileUploadPhysicalModel : PageModel
{
...
}
D’autres Kestrel limites peuvent s’appliquer aux applications hébergées par Kestrel :
La limite de requête par défaut (maxAllowedContentLength
) est de 30 000 000 octets, soit environ 28,6 Mo. Personnalisez la limite dans le fichier web.config
. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="52428800" />
</requestFiltering>
</security>
</system.webServer>
Le paramètre maxAllowedContentLength
s’applique seulement à IIS. Pour plus d’informations, consultez Limites de requête<requestLimits>
.
Augmentez la taille maximale du corps de la requête HTTP en définissant IISServerOptions.MaxRequestBodySize dans Startup.ConfigureServices
. Dans l’exemple suivant, la limite est définie sur 50 Mo (52 428 800 octets) :
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = 52428800;
});
Pour plus d’informations, consultez Héberger ASP.NET Core sur Windows avec IIS.
Voici certains problèmes courants rencontrés avec le chargement de fichiers et leurs solutions possibles.
L’erreur suivante indique que le fichier chargé dépasse la longueur de contenu configurée du serveur :
HTTP 404.13 - Not Found
The request filtering module is configured to deny a request that exceeds the request content length.
Pour plus d’informations, consultez la section IIS.
Une erreur de connexion et une réinitialisation de la connexion au serveur indiquent probablement que le fichier chargé dépasse Kestrella taille maximale du corps de la demande. Pour plus d’informations, consultez la section KestrelTaille maximale du corps de la demande. Kestrel Les limites de connexion client peuvent également nécessiter un ajustement.
Si le contrôleur accepte les fichiers téléchargés avec IFormFile mais que la valeur est null
, confirmez que le formulaire HTML spécifie une valeur enctype
de multipart/form-data
. Si cet attribut n’est pas défini sur l’élément <form>
, le chargement des fichiers ne se produit pas et les arguments IFormFile liés sont null
. Vérifiez également que le nommage de chargement dans les données de formulaire correspond à celui de l’application.
Les exemples de cette rubrique s’appuient sur MemoryStream pour contenir le contenu du fichier chargé. La limite de taille d’un MemoryStream
est int.MaxValue
. Si le scénario de chargement de fichiers de l’application nécessite que le contenu du fichier dépasse 50 Mo, utilisez une autre approche qui ne repose pas sur un seul MemoryStream
pour conserver le contenu d’un fichier chargé.
Commentaires sur ASP.NET Core
ASP.NET Core est un projet open source. Sélectionnez un lien pour fournir des commentaires :
Événements
Championnats du monde Power BI DataViz
14 févr., 16 h - 31 mars, 16 h
Avec 4 chances d’entrer, vous pourriez gagner un package de conférence et le rendre à la Live Grand Finale à Las Vegas
En savoir plusEntrainement
Module
Découvrez comment répertorier, télécharger et charger les fichiers d’un utilisateur dans une application ASP.NET Core à l’aide de Microsoft Graph.