e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
233 lines
8.2 KiB
C#
233 lines
8.2 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="HttpFileCollection.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Collection of posted files for the request intrinsic
|
|
*
|
|
* Copyright (c) 1998 Microsoft Corporation
|
|
*/
|
|
|
|
namespace System.Web {
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Specialized;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Web.Util;
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Accesses incoming files uploaded by a client (using
|
|
/// multipart MIME and the Http Content-Type of multipart/formdata).
|
|
/// </para>
|
|
/// </devdoc>
|
|
public sealed class HttpFileCollection : NameObjectCollectionBase {
|
|
// cached All[] arrays
|
|
private HttpPostedFile[] _all;
|
|
private String[] _allKeys;
|
|
|
|
// for implementing granular request validation
|
|
private ValidateStringCallback _validationCallback;
|
|
private HashSet<HttpPostedFile> _filesAwaitingValidation;
|
|
|
|
|
|
[SuppressMessage("Microsoft.Globalization", "CA1309:UseOrdinalStringComparison", MessageId = "System.Collections.Specialized.NameObjectCollectionBase.#ctor(System.Collections.IEqualityComparer)", Justification = @"By design")]
|
|
internal HttpFileCollection()
|
|
: base(StringComparer.InvariantCultureIgnoreCase) {
|
|
}
|
|
|
|
// This copy constructor is used by the granular request validation feature. Since these collections are immutable
|
|
// once created, it's ok for us to have two collections containing the same data.
|
|
internal HttpFileCollection(HttpFileCollection col)
|
|
: this() {
|
|
|
|
// We explicitly don't copy validation-related fields, as we want the copy to "reset" validation state.
|
|
|
|
// Copy the file references from the original collection into this instance
|
|
for (int i = 0; i < col.Count; i++) {
|
|
ThrowIfMaxHttpCollectionKeysExceeded();
|
|
string key = col.BaseGetKey(i);
|
|
object value = col.BaseGet(i);
|
|
BaseAdd(key, value);
|
|
}
|
|
|
|
IsReadOnly = col.IsReadOnly;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public void CopyTo(Array dest, int index) {
|
|
if (_all == null) {
|
|
int n = Count;
|
|
HttpPostedFile[] all = new HttpPostedFile[n];
|
|
for (int i = 0; i < n; i++)
|
|
all[i] = Get(i);
|
|
_all = all; // wait until end of loop to set _all reference in case Get throws
|
|
}
|
|
|
|
if (_all != null) {
|
|
_all.CopyTo(dest, index);
|
|
}
|
|
}
|
|
|
|
internal void AddFile(String key, HttpPostedFile file) {
|
|
ThrowIfMaxHttpCollectionKeysExceeded();
|
|
|
|
_all = null;
|
|
_allKeys = null;
|
|
|
|
BaseAdd(key, file);
|
|
}
|
|
|
|
// MSRC 12038: limit the maximum number of items that can be added to the collection,
|
|
// as a large number of items potentially can result in too many hash collisions that may cause DoS
|
|
private void ThrowIfMaxHttpCollectionKeysExceeded() {
|
|
if (Count >= AppSettings.MaxHttpCollectionKeys) {
|
|
throw new InvalidOperationException(SR.GetString(SR.CollectionCountExceeded_HttpValueCollection, AppSettings.MaxHttpCollectionKeys));
|
|
}
|
|
}
|
|
|
|
internal void EnableGranularValidation(ValidateStringCallback validationCallback) {
|
|
// Iterate over all the files, adding each to the set containing the files awaiting validation.
|
|
// Unlike dictionaries, HashSet<T> can contain null keys, so don't need to special-case them.
|
|
_filesAwaitingValidation = new HashSet<HttpPostedFile>();
|
|
for (int i = 0; i < Count; i++) {
|
|
_filesAwaitingValidation.Add((HttpPostedFile)BaseGet(i));
|
|
}
|
|
_validationCallback = validationCallback;
|
|
}
|
|
|
|
private void EnsureFileValidated(HttpPostedFile file) {
|
|
if (_filesAwaitingValidation == null) {
|
|
// If dynamic validation hasn't been enabled, no-op.
|
|
return;
|
|
}
|
|
|
|
if (!_filesAwaitingValidation.Contains(file)) {
|
|
// If this file has already been validated (or is excluded), no-op.
|
|
return;
|
|
}
|
|
|
|
// If validation fails, the callback will throw an exception. If validation succeeds,
|
|
// we can remove it from the candidates list. Key is unused.
|
|
_validationCallback(null /* key */, file.FileName);
|
|
_filesAwaitingValidation.Remove(file);
|
|
}
|
|
|
|
//
|
|
// Access by name
|
|
//
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Returns a file from
|
|
/// the collection by file name.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public HttpPostedFile Get(String name) {
|
|
HttpPostedFile file = (HttpPostedFile)BaseGet(name);
|
|
if (file != null) {
|
|
EnsureFileValidated(file);
|
|
}
|
|
return file;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Returns all files from this collection that match the given name.
|
|
/// This is to support multi-file uploads (either via HTML5 or JS emulators).
|
|
/// This method returns a new collection instance for each invocation and callers are
|
|
/// encouraged to call this method once per name per request.
|
|
/// </devdoc>
|
|
|
|
[SuppressMessage("Microsoft.Globalization", "CA1309:UseOrdinalStringComparison", MessageId = "System.String.Equals(System.String,System.String,System.StringComparison)", Justification = @"By design")]
|
|
public IList<HttpPostedFile> GetMultiple(string name) {
|
|
List<HttpPostedFile> result = new List<HttpPostedFile>();
|
|
for (int i = 0; i < Count; i++) {
|
|
string key = GetKey(i);
|
|
// Use InvariantCultureIgnoreCase since this is the comparison used for this collection
|
|
if (String.Equals(key, name, StringComparison.InvariantCultureIgnoreCase)) {
|
|
// Call the Get() method instead of looking at the _all array directly
|
|
// to ensure that request validation happens for the file.
|
|
result.Add(Get(i));
|
|
}
|
|
}
|
|
return result.AsReadOnly();
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Returns item value from collection.</para>
|
|
/// </devdoc>
|
|
public HttpPostedFile this[String name]
|
|
{
|
|
get { return Get(name);}
|
|
}
|
|
|
|
//
|
|
// Indexed access
|
|
//
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Returns a file from
|
|
/// the file collection by index.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public HttpPostedFile Get(int index) {
|
|
HttpPostedFile file = (HttpPostedFile)BaseGet(index);
|
|
if (file != null) {
|
|
EnsureFileValidated(file);
|
|
}
|
|
return file;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Returns key name from collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public String GetKey(int index) {
|
|
return BaseGetKey(index);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Returns an
|
|
/// item from the collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public HttpPostedFile this[int index]
|
|
{
|
|
get { return Get(index);}
|
|
}
|
|
|
|
//
|
|
// Access to keys and values as arrays
|
|
//
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Creates an
|
|
/// array of keys in the collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public String[] AllKeys {
|
|
get {
|
|
if (_allKeys == null)
|
|
_allKeys = BaseGetAllKeys();
|
|
|
|
return _allKeys;
|
|
}
|
|
}
|
|
}
|
|
}
|