//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ /* * 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; /// /// /// Accesses incoming files uploaded by a client (using /// multipart MIME and the Http Content-Type of multipart/formdata). /// /// public sealed class HttpFileCollection : NameObjectCollectionBase { // cached All[] arrays private HttpPostedFile[] _all; private String[] _allKeys; // for implementing granular request validation private ValidateStringCallback _validationCallback; private HashSet _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; } /// /// [To be supplied.] /// 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 can contain null keys, so don't need to special-case them. _filesAwaitingValidation = new HashSet(); 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 // /// /// /// Returns a file from /// the collection by file name. /// /// public HttpPostedFile Get(String name) { HttpPostedFile file = (HttpPostedFile)BaseGet(name); if (file != null) { EnsureFileValidated(file); } return file; } /// /// 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. /// [SuppressMessage("Microsoft.Globalization", "CA1309:UseOrdinalStringComparison", MessageId = "System.String.Equals(System.String,System.String,System.StringComparison)", Justification = @"By design")] public IList GetMultiple(string name) { List result = new List(); 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(); } /// /// Returns item value from collection. /// public HttpPostedFile this[String name] { get { return Get(name);} } // // Indexed access // /// /// /// Returns a file from /// the file collection by index. /// /// public HttpPostedFile Get(int index) { HttpPostedFile file = (HttpPostedFile)BaseGet(index); if (file != null) { EnsureFileValidated(file); } return file; } /// /// /// Returns key name from collection. /// /// public String GetKey(int index) { return BaseGetKey(index); } /// /// /// Returns an /// item from the collection. /// /// public HttpPostedFile this[int index] { get { return Get(index);} } // // Access to keys and values as arrays // /// /// /// Creates an /// array of keys in the collection. /// /// public String[] AllKeys { get { if (_allKeys == null) _allKeys = BaseGetAllKeys(); return _allKeys; } } } }