536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
348 lines
11 KiB
C#
348 lines
11 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="HttpCookieCollection.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Collection of Http cookies for request and response intrinsics
|
|
*
|
|
* Copyright (c) 1998 Microsoft Corporation
|
|
*/
|
|
|
|
namespace System.Web {
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Linq;
|
|
using System.Web.Util;
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Provides a type-safe
|
|
/// way to manipulate HTTP cookies.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public sealed class HttpCookieCollection : NameObjectCollectionBase {
|
|
// Response object to notify about changes in collection
|
|
private HttpResponse _response;
|
|
|
|
// cached All[] arrays
|
|
private HttpCookie[] _all;
|
|
private String[] _allKeys;
|
|
private bool _changed;
|
|
|
|
// for implementing granular request validation
|
|
private ValidateStringCallback _validationCallback;
|
|
private HashSet<string> _keysAwaitingValidation;
|
|
|
|
internal HttpCookieCollection(HttpResponse response, bool readOnly)
|
|
: base(StringComparer.OrdinalIgnoreCase) {
|
|
_response = response;
|
|
IsReadOnly = readOnly;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Initializes a new instance of the HttpCookieCollection
|
|
/// class.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public HttpCookieCollection(): base(StringComparer.OrdinalIgnoreCase) {
|
|
}
|
|
|
|
// This copy constructor is used by the granular request validation feature. The collections are mutable once
|
|
// created, but nobody should ever be mutating them, so it's ok for these to be out of sync. Additionally,
|
|
// we don't copy _response since this should only ever be called for the request cookies.
|
|
internal HttpCookieCollection(HttpCookieCollection col)
|
|
: base(StringComparer.OrdinalIgnoreCase) {
|
|
|
|
// 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;
|
|
}
|
|
|
|
internal bool Changed {
|
|
get { return _changed; }
|
|
set { _changed = value; }
|
|
}
|
|
internal void AddCookie(HttpCookie cookie, bool append) {
|
|
ThrowIfMaxHttpCollectionKeysExceeded();
|
|
|
|
_all = null;
|
|
_allKeys = null;
|
|
|
|
if (append) {
|
|
// DevID 251951 Cookie is getting duplicated by ASP.NET when they are added via a native module
|
|
// Need to not double add response cookies from native modules
|
|
if (!cookie.IsInResponseHeader) {
|
|
// mark cookie as new
|
|
cookie.Added = true;
|
|
}
|
|
BaseAdd(cookie.Name, cookie);
|
|
}
|
|
else {
|
|
if (BaseGet(cookie.Name) != null) {
|
|
// mark the cookie as changed because we are overriding the existing one
|
|
cookie.Changed = true;
|
|
}
|
|
BaseSet(cookie.Name, cookie);
|
|
}
|
|
}
|
|
|
|
// VSO bug #289778: when copying cookie from Response to Request, there is side effect
|
|
// which changes Added property and causes dup cookie in response header
|
|
// This method is meant to append cookie from one collection without changing cookie object
|
|
internal void Append(HttpCookieCollection cookies) {
|
|
for (int i = 0; i < cookies.Count; ++i) {
|
|
//BaseGet method doesn't trigger validation, while Get method does
|
|
HttpCookie cookie = (HttpCookie) cookies.BaseGet(i);
|
|
BaseAdd(cookie.Name, cookie);
|
|
}
|
|
}
|
|
|
|
// 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 keys, adding each to the set containing the keys awaiting validation.
|
|
// Unlike dictionaries, HashSet<T> can contain null keys, so don't need to special-case them.
|
|
_keysAwaitingValidation = new HashSet<string>(Keys.Cast<string>(), StringComparer.OrdinalIgnoreCase);
|
|
_validationCallback = validationCallback;
|
|
}
|
|
|
|
private void EnsureKeyValidated(string key, string value) {
|
|
if (_keysAwaitingValidation == null) {
|
|
// If dynamic validation hasn't been enabled, no-op.
|
|
return;
|
|
}
|
|
|
|
if (!_keysAwaitingValidation.Contains(key)) {
|
|
// If this key 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. A note:
|
|
// - Eager validation skips null/empty values, so we should, also.
|
|
if (!String.IsNullOrEmpty(value)) {
|
|
_validationCallback(key, value);
|
|
}
|
|
_keysAwaitingValidation.Remove(key);
|
|
}
|
|
|
|
internal void MakeReadOnly() {
|
|
IsReadOnly = true;
|
|
}
|
|
|
|
internal void RemoveCookie(String name) {
|
|
_all = null;
|
|
_allKeys = null;
|
|
|
|
BaseRemove(name);
|
|
|
|
_changed = true;
|
|
}
|
|
|
|
internal void Reset() {
|
|
_all = null;
|
|
_allKeys = null;
|
|
|
|
BaseClear();
|
|
_changed = true;
|
|
_keysAwaitingValidation = null;
|
|
}
|
|
|
|
//
|
|
// Public APIs to add / remove
|
|
//
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Adds a cookie to the collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public void Add(HttpCookie cookie) {
|
|
if (_response != null)
|
|
_response.BeforeCookieCollectionChange();
|
|
|
|
AddCookie(cookie, true);
|
|
|
|
if (_response != null)
|
|
_response.OnCookieAdd(cookie);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public void CopyTo(Array dest, int index) {
|
|
if (_all == null) {
|
|
int n = Count;
|
|
HttpCookie[] all = new HttpCookie[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
|
|
}
|
|
_all.CopyTo(dest, index);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para> Updates the value of a cookie.</para>
|
|
/// </devdoc>
|
|
public void Set(HttpCookie cookie) {
|
|
if (_response != null)
|
|
_response.BeforeCookieCollectionChange();
|
|
|
|
AddCookie(cookie, false);
|
|
|
|
if (_response != null)
|
|
_response.OnCookieCollectionChange();
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Removes a cookie from the collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public void Remove(String name) {
|
|
if (_response != null)
|
|
_response.BeforeCookieCollectionChange();
|
|
|
|
RemoveCookie(name);
|
|
|
|
if (_response != null)
|
|
_response.OnCookieCollectionChange();
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Clears all cookies from the collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public void Clear() {
|
|
Reset();
|
|
}
|
|
|
|
//
|
|
// Access by name
|
|
//
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Returns an <see cref='System.Web.HttpCookie'/> item from the collection.</para>
|
|
/// </devdoc>
|
|
public HttpCookie Get(String name) {
|
|
HttpCookie cookie = (HttpCookie)BaseGet(name);
|
|
|
|
if (cookie == null && _response != null) {
|
|
// response cookies are created on demand
|
|
cookie = new HttpCookie(name);
|
|
AddCookie(cookie, true);
|
|
_response.OnCookieAdd(cookie);
|
|
}
|
|
|
|
if (cookie != null) {
|
|
EnsureKeyValidated(name, cookie.Value);
|
|
}
|
|
|
|
return cookie;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Indexed value that enables access to a cookie in the collection.</para>
|
|
/// </devdoc>
|
|
public HttpCookie this[String name]
|
|
{
|
|
get { return Get(name);}
|
|
}
|
|
|
|
//
|
|
// Indexed access
|
|
//
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Returns an <see cref='System.Web.HttpCookie'/>
|
|
/// item from the collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public HttpCookie Get(int index) {
|
|
HttpCookie cookie = (HttpCookie)BaseGet(index);
|
|
|
|
// Call GetKey so that we can pass the key to the validation routine.
|
|
if (cookie != null) {
|
|
EnsureKeyValidated(GetKey(index), cookie.Value);
|
|
}
|
|
return cookie;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Returns key name from collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public String GetKey(int index) {
|
|
return BaseGetKey(index);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Default property.
|
|
/// Indexed property that enables access to a cookie in the collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public HttpCookie this[int index]
|
|
{
|
|
get { return Get(index);}
|
|
}
|
|
|
|
//
|
|
// Access to keys and values as arrays
|
|
//
|
|
|
|
/*
|
|
* All keys
|
|
*/
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Returns
|
|
/// an array of all cookie keys in the cookie collection.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public String[] AllKeys {
|
|
get {
|
|
if (_allKeys == null)
|
|
_allKeys = BaseGetAllKeys();
|
|
|
|
return _allKeys;
|
|
}
|
|
}
|
|
}
|
|
}
|