6bdd276d05
Former-commit-id: fd56571888259555122d8a0f58c68838229cea2b
1346 lines
33 KiB
C#
1346 lines
33 KiB
C#
//
|
|
// System.Web.HttpResponse.cs
|
|
//
|
|
//
|
|
// Author:
|
|
// Miguel de Icaza (miguel@novell.com)
|
|
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
|
// Marek Habersack <mhabersack@novell.com>
|
|
//
|
|
// Copyright (C) 2005-2010 Novell, Inc (http://www.novell.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System.Text;
|
|
using System.Web.UI;
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.IO;
|
|
using System.Web.Caching;
|
|
using System.Threading;
|
|
using System.Web.Util;
|
|
using System.Web.Configuration;
|
|
using System.Globalization;
|
|
using System.Security.Permissions;
|
|
using System.Web.Hosting;
|
|
using System.Web.SessionState;
|
|
|
|
using System.Web.Routing;
|
|
|
|
namespace System.Web
|
|
{
|
|
// CAS - no InheritanceDemand here as the class is sealed
|
|
[AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
|
|
public sealed partial class HttpResponse
|
|
{
|
|
internal HttpWorkerRequest WorkerRequest;
|
|
internal HttpResponseStream output_stream;
|
|
internal bool buffer = true;
|
|
|
|
ArrayList fileDependencies;
|
|
|
|
HttpContext context;
|
|
TextWriter writer;
|
|
HttpCachePolicy cache_policy;
|
|
Encoding encoding;
|
|
HttpCookieCollection cookies;
|
|
|
|
int status_code = 200;
|
|
string status_description = "OK";
|
|
|
|
string content_type = "text/html";
|
|
string charset;
|
|
bool charset_set;
|
|
CachedRawResponse cached_response;
|
|
string user_cache_control = "private";
|
|
string redirect_location;
|
|
string version_header;
|
|
bool version_header_checked;
|
|
|
|
//
|
|
// Negative Content-Length means we auto-compute the size of content-length
|
|
// can be overwritten with AppendHeader ("Content-Length", value)
|
|
//
|
|
long content_length = -1;
|
|
|
|
//
|
|
// The list of the headers that we will send back to the client, except
|
|
// the headers that we compute here.
|
|
//
|
|
|
|
HttpHeaderCollection headers;
|
|
bool headers_sent;
|
|
NameValueCollection cached_headers;
|
|
|
|
//
|
|
// Transfer encoding state
|
|
//
|
|
string transfer_encoding;
|
|
internal bool use_chunked;
|
|
|
|
bool closed;
|
|
bool completed;
|
|
internal bool suppress_content;
|
|
|
|
//
|
|
// Session State
|
|
//
|
|
string app_path_mod;
|
|
bool is_request_being_redirected;
|
|
Encoding headerEncoding;
|
|
|
|
internal HttpResponse ()
|
|
{
|
|
output_stream = new HttpResponseStream (this);
|
|
writer = new HttpWriter (this);
|
|
}
|
|
|
|
public HttpResponse (TextWriter writer) : this ()
|
|
{
|
|
|
|
this.writer = writer;
|
|
}
|
|
|
|
internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
|
|
{
|
|
WorkerRequest = worker_request;
|
|
this.context = context;
|
|
|
|
if (worker_request != null && worker_request.GetHttpVersion () == "HTTP/1.1") {
|
|
string gi = worker_request.GetServerVariable ("GATEWAY_INTERFACE");
|
|
use_chunked = (String.IsNullOrEmpty (gi) ||
|
|
!gi.StartsWith ("cgi", StringComparison.OrdinalIgnoreCase));
|
|
} else {
|
|
use_chunked = false;
|
|
}
|
|
writer = new HttpWriter (this);
|
|
}
|
|
|
|
internal TextWriter SetTextWriter (TextWriter writer)
|
|
{
|
|
TextWriter prev = this.writer;
|
|
this.writer = writer;
|
|
return prev;
|
|
}
|
|
|
|
internal string VersionHeader {
|
|
get {
|
|
if (!version_header_checked && version_header == null) {
|
|
version_header_checked = true;
|
|
HttpRuntimeSection config = HttpRuntime.Section;
|
|
if (config != null && config.EnableVersionHeader)
|
|
version_header = Environment.Version.ToString (3);
|
|
}
|
|
|
|
return version_header;
|
|
}
|
|
}
|
|
|
|
internal HttpContext Context {
|
|
get { return context; }
|
|
set { context = value; }
|
|
}
|
|
|
|
internal string[] FileDependencies {
|
|
get {
|
|
if (fileDependencies == null || fileDependencies.Count == 0)
|
|
return new string[0] {};
|
|
return (string[]) fileDependencies.ToArray (typeof (string));
|
|
}
|
|
}
|
|
|
|
ArrayList FileDependenciesArray {
|
|
get {
|
|
if (fileDependencies == null)
|
|
fileDependencies = new ArrayList ();
|
|
return fileDependencies;
|
|
}
|
|
}
|
|
|
|
public bool Buffer {
|
|
get {
|
|
return buffer;
|
|
}
|
|
|
|
set {
|
|
buffer = value;
|
|
}
|
|
}
|
|
|
|
public bool BufferOutput {
|
|
get {
|
|
return buffer;
|
|
}
|
|
|
|
set {
|
|
buffer = value;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Use the default from <globalization> section if the client has not set the encoding
|
|
//
|
|
public Encoding ContentEncoding {
|
|
get {
|
|
if (encoding == null) {
|
|
if (context != null) {
|
|
string client_content_type = context.Request.ContentType;
|
|
string parameter = HttpRequest.GetParameter (client_content_type, "; charset=");
|
|
if (parameter != null) {
|
|
try {
|
|
// Do what the #1 web server does
|
|
encoding = Encoding.GetEncoding (parameter);
|
|
} catch {
|
|
}
|
|
}
|
|
}
|
|
if (encoding == null)
|
|
encoding = WebEncoding.ResponseEncoding;
|
|
}
|
|
return encoding;
|
|
}
|
|
|
|
set {
|
|
if (value == null)
|
|
throw new ArgumentException ("ContentEncoding can not be null");
|
|
|
|
encoding = value;
|
|
HttpWriter http_writer = writer as HttpWriter;
|
|
if (http_writer != null)
|
|
http_writer.SetEncoding (encoding);
|
|
}
|
|
}
|
|
|
|
public string ContentType {
|
|
get {
|
|
return content_type;
|
|
}
|
|
|
|
set {
|
|
content_type = value;
|
|
}
|
|
}
|
|
|
|
public string Charset {
|
|
get {
|
|
if (charset == null)
|
|
charset = ContentEncoding.WebName;
|
|
|
|
return charset;
|
|
}
|
|
|
|
set {
|
|
charset_set = true;
|
|
charset = value;
|
|
}
|
|
}
|
|
|
|
public HttpCookieCollection Cookies {
|
|
get {
|
|
if (cookies == null)
|
|
cookies = new HttpCookieCollection (true, false);
|
|
return cookies;
|
|
}
|
|
}
|
|
|
|
public int Expires {
|
|
get {
|
|
if (cache_policy == null)
|
|
return 0;
|
|
|
|
return cache_policy.ExpireMinutes ();
|
|
}
|
|
|
|
set {
|
|
Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
|
|
}
|
|
}
|
|
|
|
public DateTime ExpiresAbsolute {
|
|
get {
|
|
return Cache.Expires;
|
|
}
|
|
|
|
set {
|
|
Cache.SetExpires (value);
|
|
}
|
|
}
|
|
|
|
public Stream Filter {
|
|
get {
|
|
if (WorkerRequest == null)
|
|
return null;
|
|
|
|
return output_stream.Filter;
|
|
}
|
|
|
|
set {
|
|
output_stream.Filter = value;
|
|
}
|
|
}
|
|
|
|
public Encoding HeaderEncoding {
|
|
get {
|
|
if (headerEncoding == null) {
|
|
GlobalizationSection gs = WebConfigurationManager.SafeGetSection ("system.web/globalization", typeof (GlobalizationSection)) as GlobalizationSection;
|
|
|
|
if (gs == null)
|
|
headerEncoding = Encoding.UTF8;
|
|
else {
|
|
headerEncoding = gs.ResponseHeaderEncoding;
|
|
if (headerEncoding == Encoding.Unicode)
|
|
throw new HttpException ("HeaderEncoding must not be Unicode");
|
|
}
|
|
}
|
|
return headerEncoding;
|
|
}
|
|
set {
|
|
if (headers_sent)
|
|
throw new HttpException ("headers have already been sent");
|
|
if (value == null)
|
|
throw new ArgumentNullException ("HeaderEncoding");
|
|
if (value == Encoding.Unicode)
|
|
throw new HttpException ("HeaderEncoding must not be Unicode");
|
|
headerEncoding = value;
|
|
}
|
|
}
|
|
|
|
public NameValueCollection Headers {
|
|
get {
|
|
if (headers == null)
|
|
headers = new HttpHeaderCollection ();
|
|
|
|
return headers;
|
|
}
|
|
}
|
|
|
|
|
|
public bool IsClientConnected {
|
|
get {
|
|
if (WorkerRequest == null)
|
|
return true; // yep that's true
|
|
|
|
return WorkerRequest.IsClientConnected ();
|
|
}
|
|
}
|
|
|
|
public bool IsRequestBeingRedirected {
|
|
get { return is_request_being_redirected; }
|
|
}
|
|
|
|
public TextWriter Output {
|
|
get {
|
|
return writer;
|
|
}
|
|
set { writer = value; }
|
|
}
|
|
|
|
public Stream OutputStream {
|
|
get {
|
|
return output_stream;
|
|
}
|
|
}
|
|
|
|
public string RedirectLocation {
|
|
get {
|
|
return redirect_location;
|
|
}
|
|
|
|
set {
|
|
redirect_location = value;
|
|
}
|
|
}
|
|
|
|
public string Status {
|
|
get { return String.Concat (status_code.ToString (), " ", StatusDescription); }
|
|
|
|
set {
|
|
int p = value.IndexOf (' ');
|
|
if (p == -1)
|
|
throw new HttpException ("Invalid format for the Status property");
|
|
|
|
string s = value.Substring (0, p);
|
|
|
|
if (!Int32.TryParse (s, out status_code))
|
|
throw new HttpException ("Invalid format for the Status property");
|
|
|
|
status_description = value.Substring (p+1);
|
|
}
|
|
}
|
|
|
|
// We ignore the two properties on Mono as they are for use with IIS7, but there is
|
|
// no point in throwing PlatformNotSupportedException. We might find a use for them
|
|
// some day.
|
|
public int SubStatusCode {
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public bool SuppressFormsAuthenticationRedirect {
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public bool TrySkipIisCustomErrors {
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public int StatusCode {
|
|
get {
|
|
return status_code;
|
|
}
|
|
|
|
set {
|
|
if (headers_sent)
|
|
throw new HttpException ("headers have already been sent");
|
|
|
|
status_code = value;
|
|
status_description = null;
|
|
}
|
|
}
|
|
|
|
public string StatusDescription {
|
|
get {
|
|
if (status_description == null)
|
|
status_description = HttpWorkerRequest.GetStatusDescription (status_code);
|
|
|
|
return status_description;
|
|
}
|
|
|
|
set {
|
|
if (headers_sent)
|
|
throw new HttpException ("headers have already been sent");
|
|
|
|
status_description = value;
|
|
}
|
|
}
|
|
|
|
public bool SuppressContent {
|
|
get {
|
|
return suppress_content;
|
|
}
|
|
|
|
set {
|
|
suppress_content = value;
|
|
}
|
|
}
|
|
|
|
[MonoTODO ("Not implemented")]
|
|
public void AddCacheDependency (params CacheDependency[] dependencies)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[MonoTODO ("Not implemented")]
|
|
public void AddCacheItemDependencies (string[] cacheKeys)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
[MonoTODO("Currently does nothing")]
|
|
public void AddCacheItemDependencies (ArrayList cacheKeys)
|
|
{
|
|
// TODO: talk to jackson about the cache
|
|
}
|
|
|
|
[MonoTODO("Currently does nothing")]
|
|
public void AddCacheItemDependency (string cacheKey)
|
|
{
|
|
// TODO: talk to jackson about the cache
|
|
}
|
|
|
|
public void AddFileDependencies (ArrayList filenames)
|
|
{
|
|
if (filenames == null || filenames.Count == 0)
|
|
return;
|
|
FileDependenciesArray.AddRange (filenames);
|
|
}
|
|
|
|
public void AddFileDependencies (string[] filenames)
|
|
{
|
|
if (filenames == null || filenames.Length == 0)
|
|
return;
|
|
FileDependenciesArray.AddRange (filenames);
|
|
}
|
|
|
|
public void AddFileDependency (string filename)
|
|
{
|
|
if (filename == null || filename == String.Empty)
|
|
return;
|
|
FileDependenciesArray.Add (filename);
|
|
}
|
|
|
|
public void AddHeader (string name, string value)
|
|
{
|
|
AppendHeader (name, value);
|
|
}
|
|
|
|
public void AppendCookie (HttpCookie cookie)
|
|
{
|
|
Cookies.Add (cookie);
|
|
}
|
|
|
|
//
|
|
// AppendHeader:
|
|
// Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
|
|
//
|
|
//
|
|
public void AppendHeader (string name, string value)
|
|
{
|
|
if (headers_sent)
|
|
throw new HttpException ("Headers have been already sent");
|
|
if (String.Compare (name, "content-length", StringComparison.OrdinalIgnoreCase) == 0){
|
|
content_length = (long) UInt64.Parse (value);
|
|
use_chunked = false;
|
|
return;
|
|
}
|
|
|
|
if (String.Compare (name, "content-type", StringComparison.OrdinalIgnoreCase) == 0){
|
|
ContentType = value;
|
|
return;
|
|
}
|
|
|
|
if (String.Compare (name, "transfer-encoding", StringComparison.OrdinalIgnoreCase) == 0){
|
|
transfer_encoding = value;
|
|
use_chunked = false;
|
|
return;
|
|
}
|
|
|
|
if (String.Compare (name, "cache-control", StringComparison.OrdinalIgnoreCase) == 0){
|
|
user_cache_control = value;
|
|
return;
|
|
}
|
|
|
|
Headers.Add (name, value);
|
|
}
|
|
|
|
[AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
|
|
public void AppendToLog (string param)
|
|
{
|
|
Console.Write ("System.Web: ");
|
|
Console.WriteLine (param);
|
|
}
|
|
|
|
public string ApplyAppPathModifier (string virtualPath)
|
|
{
|
|
if (virtualPath == null || context == null)
|
|
return null;
|
|
|
|
if (virtualPath.Length == 0)
|
|
return context.Request.RootVirtualDir;
|
|
|
|
if (UrlUtils.IsRelativeUrl (virtualPath)) {
|
|
virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
|
|
} else if (UrlUtils.IsRooted (virtualPath)) {
|
|
virtualPath = UrlUtils.Canonic (virtualPath);
|
|
}
|
|
|
|
bool cookieless = false;
|
|
SessionStateSection config = WebConfigurationManager.GetWebApplicationSection ("system.web/sessionState") as SessionStateSection;
|
|
cookieless = SessionStateModule.IsCookieLess (context, config);
|
|
|
|
if (!cookieless)
|
|
return virtualPath;
|
|
|
|
if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
|
|
if (UrlUtils.HasSessionId (virtualPath))
|
|
virtualPath = UrlUtils.RemoveSessionId (VirtualPathUtility.GetDirectory (virtualPath), virtualPath);
|
|
return UrlUtils.InsertSessionId (app_path_mod, virtualPath);
|
|
}
|
|
|
|
return virtualPath;
|
|
}
|
|
|
|
public void BinaryWrite (byte [] buffer)
|
|
{
|
|
output_stream.Write (buffer, 0, buffer.Length);
|
|
}
|
|
|
|
internal void BinaryWrite (byte [] buffer, int start, int len)
|
|
{
|
|
output_stream.Write (buffer, start, len);
|
|
}
|
|
|
|
public void Clear ()
|
|
{
|
|
ClearContent ();
|
|
}
|
|
|
|
public void ClearContent ()
|
|
{
|
|
output_stream.Clear ();
|
|
content_length = -1;
|
|
}
|
|
|
|
public void ClearHeaders ()
|
|
{
|
|
if (headers_sent)
|
|
throw new HttpException ("headers have been already sent");
|
|
|
|
// Reset the special case headers.
|
|
content_length = -1;
|
|
content_type = "text/html";
|
|
transfer_encoding = null;
|
|
user_cache_control = "private";
|
|
if (cache_policy != null)
|
|
cache_policy.Cacheability = HttpCacheability.Private;
|
|
|
|
if (headers != null)
|
|
headers.Clear ();
|
|
}
|
|
|
|
internal bool HeadersSent {
|
|
get {
|
|
return headers_sent;
|
|
}
|
|
}
|
|
|
|
public void Close ()
|
|
{
|
|
if (closed)
|
|
return;
|
|
if (WorkerRequest != null)
|
|
WorkerRequest.CloseConnection ();
|
|
closed = true;
|
|
}
|
|
|
|
public void DisableKernelCache ()
|
|
{
|
|
// does nothing in Mono
|
|
}
|
|
|
|
public void End ()
|
|
{
|
|
if (context == null)
|
|
return;
|
|
|
|
if (context.TimeoutPossible) {
|
|
Thread.CurrentThread.Abort (FlagEnd.Value);
|
|
} else {
|
|
// If this is called from an async event, signal the completion
|
|
// but don't throw.
|
|
HttpApplication app_instance = context.ApplicationInstance;
|
|
if (app_instance != null)
|
|
app_instance.CompleteRequest ();
|
|
}
|
|
}
|
|
|
|
// Generate:
|
|
// Content-Length
|
|
// Content-Type
|
|
// Transfer-Encoding (chunked)
|
|
// Cache-Control
|
|
// X-AspNet-Version
|
|
void AddHeadersNoCache (NameValueCollection write_headers, bool final_flush)
|
|
{
|
|
//
|
|
// Transfer-Encoding
|
|
//
|
|
if (use_chunked)
|
|
write_headers.Add ("Transfer-Encoding", "chunked");
|
|
else if (transfer_encoding != null)
|
|
write_headers.Add ("Transfer-Encoding", transfer_encoding);
|
|
if (redirect_location != null)
|
|
write_headers.Add ("Location", redirect_location);
|
|
|
|
string vh = VersionHeader;
|
|
if (vh != null)
|
|
write_headers.Add ("X-AspNet-Version", vh);
|
|
|
|
//
|
|
// If Content-Length is set.
|
|
//
|
|
if (content_length >= 0) {
|
|
write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
|
|
content_length.ToString (Helpers.InvariantCulture));
|
|
} else if (BufferOutput) {
|
|
if (final_flush) {
|
|
//
|
|
// If we are buffering and this is the last flush, not a middle-flush,
|
|
// we know the content-length.
|
|
//
|
|
content_length = output_stream.total;
|
|
write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderContentLength),
|
|
content_length.ToString (Helpers.InvariantCulture));
|
|
} else {
|
|
//
|
|
// We are buffering, and this is a flush in the middle.
|
|
// If we are not chunked, we need to set "Connection: close".
|
|
//
|
|
if (use_chunked){
|
|
write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
|
|
}
|
|
}
|
|
} else {
|
|
//
|
|
// If the content-length is not set, and we are not buffering, we must
|
|
// close at the end.
|
|
//
|
|
if (use_chunked){
|
|
write_headers.Add (HttpWorkerRequest.GetKnownResponseHeaderName (HttpWorkerRequest.HeaderConnection), "close");
|
|
}
|
|
}
|
|
|
|
//
|
|
// Cache Control, the cache policy takes precedence over the cache_control property.
|
|
//
|
|
if (cache_policy != null)
|
|
cache_policy.SetHeaders (this, headers);
|
|
else
|
|
write_headers.Add ("Cache-Control", CacheControl);
|
|
|
|
//
|
|
// Content-Type
|
|
//
|
|
if (content_type != null){
|
|
string header = content_type;
|
|
|
|
if (charset_set || header == "text/plain" || header == "text/html") {
|
|
if (header.IndexOf ("charset=") == -1 && !string.IsNullOrEmpty (charset)) {
|
|
header += "; charset=" + charset;
|
|
}
|
|
}
|
|
|
|
write_headers.Add ("Content-Type", header);
|
|
}
|
|
|
|
if (cookies != null && cookies.Count != 0){
|
|
int n = cookies.Count;
|
|
for (int i = 0; i < n; i++)
|
|
write_headers.Add ("Set-Cookie", cookies.Get (i).GetCookieHeaderValue ());
|
|
}
|
|
}
|
|
|
|
internal void WriteHeaders (bool final_flush)
|
|
{
|
|
if (headers_sent)
|
|
return;
|
|
|
|
//
|
|
// Flush
|
|
//
|
|
if (context != null) {
|
|
HttpApplication app_instance = context.ApplicationInstance;
|
|
if (app_instance != null)
|
|
app_instance.TriggerPreSendRequestHeaders ();
|
|
}
|
|
|
|
headers_sent = true;
|
|
|
|
if (cached_response != null)
|
|
cached_response.SetHeaders (headers);
|
|
|
|
// If this page is cached use the cached headers
|
|
// instead of the standard headers
|
|
NameValueCollection write_headers;
|
|
if (cached_headers != null)
|
|
write_headers = cached_headers;
|
|
else {
|
|
write_headers = Headers;
|
|
AddHeadersNoCache (write_headers, final_flush);
|
|
}
|
|
|
|
if (WorkerRequest != null)
|
|
WorkerRequest.SendStatus (status_code, StatusDescription);
|
|
|
|
if (WorkerRequest != null) {
|
|
string header_name;
|
|
string[] values;
|
|
int header_index;
|
|
|
|
for (int i = 0; i < write_headers.Count; i++) {
|
|
header_name = write_headers.GetKey (i);
|
|
header_index = HttpWorkerRequest.GetKnownResponseHeaderIndex (header_name);
|
|
values = write_headers.GetValues (i);
|
|
if (values == null)
|
|
continue;
|
|
|
|
foreach (string val in values) {
|
|
if (header_index > -1)
|
|
WorkerRequest.SendKnownResponseHeader (header_index, val);
|
|
else
|
|
WorkerRequest.SendUnknownResponseHeader (header_name, val);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void DoFilter (bool close)
|
|
{
|
|
if (output_stream.HaveFilter && context != null && context.Error == null)
|
|
output_stream.ApplyFilter (close);
|
|
}
|
|
|
|
internal void Flush (bool final_flush)
|
|
{
|
|
if (completed)
|
|
throw new HttpException ("Server cannot flush a completed response");
|
|
|
|
DoFilter (final_flush);
|
|
if (!headers_sent){
|
|
if (final_flush || status_code != 200)
|
|
use_chunked = false;
|
|
}
|
|
|
|
bool head = ((context != null) && (context.Request.HttpMethod == "HEAD"));
|
|
if (suppress_content || head) {
|
|
if (!headers_sent)
|
|
WriteHeaders (true);
|
|
output_stream.Clear ();
|
|
if (WorkerRequest != null)
|
|
output_stream.Flush (WorkerRequest, true); // ignore final_flush here.
|
|
completed = true;
|
|
return;
|
|
}
|
|
completed = final_flush;
|
|
|
|
if (!headers_sent)
|
|
WriteHeaders (final_flush);
|
|
|
|
if (context != null) {
|
|
HttpApplication app_instance = context.ApplicationInstance;
|
|
if (app_instance != null)
|
|
app_instance.TriggerPreSendRequestContent ();
|
|
}
|
|
|
|
if (IsCached)
|
|
cached_response.SetData (output_stream.GetData ());
|
|
|
|
if (WorkerRequest != null)
|
|
output_stream.Flush (WorkerRequest, final_flush);
|
|
}
|
|
|
|
public void Flush ()
|
|
{
|
|
Flush (false);
|
|
}
|
|
|
|
public void Pics (string value)
|
|
{
|
|
AppendHeader ("PICS-Label", value);
|
|
}
|
|
|
|
void Redirect (string url, bool endResponse, int code)
|
|
{
|
|
if (url == null)
|
|
throw new ArgumentNullException ("url");
|
|
|
|
if (headers_sent)
|
|
throw new HttpException ("Headers have already been sent");
|
|
|
|
if (url.IndexOf ('\n') != -1)
|
|
throw new ArgumentException ("Redirect URI cannot contain newline characters.", "url");
|
|
|
|
is_request_being_redirected = true;
|
|
ClearHeaders ();
|
|
ClearContent ();
|
|
|
|
StatusCode = code;
|
|
url = ApplyAppPathModifier (url);
|
|
|
|
bool isFullyQualified;
|
|
if (StrUtils.StartsWith (url, "http:", true) ||
|
|
StrUtils.StartsWith (url, "https:", true) ||
|
|
StrUtils.StartsWith (url, "file:", true) ||
|
|
StrUtils.StartsWith (url, "ftp:", true))
|
|
isFullyQualified = true;
|
|
else
|
|
isFullyQualified = false;
|
|
|
|
if (!isFullyQualified) {
|
|
HttpRuntimeSection config = HttpRuntime.Section;
|
|
if (config != null && config.UseFullyQualifiedRedirectUrl) {
|
|
var ub = new UriBuilder (context.Request.Url);
|
|
int qpos = url.IndexOf ('?');
|
|
if (qpos == -1) {
|
|
ub.Path = url;
|
|
ub.Query = null;
|
|
} else {
|
|
ub.Path = url.Substring (0, qpos);
|
|
ub.Query = url.Substring (qpos + 1);
|
|
}
|
|
ub.Fragment = null;
|
|
ub.Password = null;
|
|
ub.UserName = null;
|
|
url = ub.Uri.ToString ();
|
|
}
|
|
}
|
|
|
|
redirect_location = url;
|
|
|
|
// Text for browsers that can't handle location header
|
|
Write ("<html><head><title>Object moved</title></head><body>\r\n");
|
|
Write ("<h2>Object moved to <a href=\"" + url + "\">here</a></h2>\r\n");
|
|
Write ("</body><html>\r\n");
|
|
|
|
if (endResponse)
|
|
End ();
|
|
is_request_being_redirected = false;
|
|
}
|
|
|
|
public void Redirect (string url)
|
|
{
|
|
Redirect (url, true);
|
|
}
|
|
|
|
public void Redirect (string url, bool endResponse)
|
|
{
|
|
Redirect (url, endResponse, 302);
|
|
}
|
|
public void RedirectPermanent (string url)
|
|
{
|
|
RedirectPermanent (url, true);
|
|
}
|
|
|
|
public void RedirectPermanent (string url, bool endResponse)
|
|
{
|
|
Redirect (url, endResponse, 301);
|
|
}
|
|
|
|
public void RedirectToRoute (object routeValues)
|
|
{
|
|
RedirectToRoute ("RedirectToRoute", null, new RouteValueDictionary (routeValues), 302, true);
|
|
}
|
|
|
|
public void RedirectToRoute (RouteValueDictionary routeValues)
|
|
{
|
|
RedirectToRoute ("RedirectToRoute", null, routeValues, 302, true);
|
|
}
|
|
|
|
public void RedirectToRoute (string routeName)
|
|
{
|
|
RedirectToRoute ("RedirectToRoute", routeName, null, 302, true);
|
|
}
|
|
|
|
public void RedirectToRoute (string routeName, object routeValues)
|
|
{
|
|
RedirectToRoute ("RedirectToRoute", routeName, new RouteValueDictionary (routeValues), 302, true);
|
|
}
|
|
|
|
public void RedirectToRoute (string routeName, RouteValueDictionary routeValues)
|
|
{
|
|
RedirectToRoute ("RedirectToRoute", routeName, routeValues, 302, true);
|
|
}
|
|
|
|
public void RedirectToRoutePermanent (object routeValues)
|
|
{
|
|
RedirectToRoute ("RedirectToRoutePermanent", null, new RouteValueDictionary (routeValues), 301, false);
|
|
}
|
|
|
|
public void RedirectToRoutePermanent (RouteValueDictionary routeValues)
|
|
{
|
|
RedirectToRoute ("RedirectToRoutePermanent", null, routeValues, 301, false);
|
|
}
|
|
|
|
public void RedirectToRoutePermanent (string routeName)
|
|
{
|
|
RedirectToRoute ("RedirectToRoutePermanent", routeName, null, 301, false);
|
|
}
|
|
|
|
public void RedirectToRoutePermanent (string routeName, object routeValues)
|
|
{
|
|
RedirectToRoute ("RedirectToRoutePermanent", routeName, new RouteValueDictionary (routeValues), 301, false);
|
|
}
|
|
|
|
public void RedirectToRoutePermanent (string routeName, RouteValueDictionary routeValues)
|
|
{
|
|
RedirectToRoute ("RedirectToRoutePermanent", routeName, routeValues, 301, false);
|
|
}
|
|
|
|
void RedirectToRoute (string callerName, string routeName, RouteValueDictionary routeValues, int redirectCode, bool endResponse)
|
|
{
|
|
HttpContext ctx = context ?? HttpContext.Current;
|
|
HttpRequest req = ctx != null ? ctx.Request : null;
|
|
|
|
if (req == null)
|
|
// Let's emulate .NET
|
|
throw new NullReferenceException ();
|
|
|
|
VirtualPathData vpd = RouteTable.Routes.GetVirtualPath (req.RequestContext, routeName, routeValues);
|
|
string redirectUrl = vpd != null ? vpd.VirtualPath : null;
|
|
if (String.IsNullOrEmpty (redirectUrl))
|
|
throw new InvalidOperationException ("No matching route found for RedirectToRoute");
|
|
|
|
Redirect (redirectUrl, true, redirectCode);
|
|
}
|
|
|
|
public static void RemoveOutputCacheItem (string path, string providerName)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException ("path");
|
|
|
|
if (path.Length > 0 && path [0] != '/')
|
|
throw new ArgumentException ("Invalid path for HttpResponse.RemoveOutputCacheItem: '" + path + "'. An absolute virtual path is expected");
|
|
|
|
OutputCache.RemoveFromProvider (path, providerName);
|
|
}
|
|
public static void RemoveOutputCacheItem (string path)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException ("path");
|
|
|
|
if (path.Length == 0)
|
|
return;
|
|
|
|
if (path [0] != '/')
|
|
throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
|
|
|
|
RemoveOutputCacheItem (path, OutputCache.DefaultProviderName);
|
|
}
|
|
|
|
public void SetCookie (HttpCookie cookie)
|
|
{
|
|
AppendCookie (cookie);
|
|
}
|
|
|
|
public void Write (char ch)
|
|
{
|
|
TextWriter writer = Output;
|
|
// Emulating .NET
|
|
if (writer == null)
|
|
throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
|
|
writer.Write (ch);
|
|
}
|
|
|
|
public void Write (object obj)
|
|
{
|
|
TextWriter writer = Output;
|
|
// Emulating .NET
|
|
if (writer == null)
|
|
throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
|
|
if (obj == null)
|
|
return;
|
|
|
|
writer.Write (obj.ToString ());
|
|
}
|
|
|
|
public void Write (string s)
|
|
{
|
|
TextWriter writer = Output;
|
|
// Emulating .NET
|
|
if (writer == null)
|
|
throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
|
|
writer.Write (s);
|
|
}
|
|
|
|
public void Write (char [] buffer, int index, int count)
|
|
{
|
|
TextWriter writer = Output;
|
|
// Emulating .NET
|
|
if (writer == null)
|
|
throw new NullReferenceException (".NET 4.0 emulation. A null value was found where an object was required.");
|
|
writer.Write (buffer, index, count);
|
|
}
|
|
|
|
bool IsFileSystemDirSeparator (char ch)
|
|
{
|
|
return ch == '\\' || ch == '/';
|
|
}
|
|
|
|
string GetNormalizedFileName (string fn)
|
|
{
|
|
if (String.IsNullOrEmpty (fn))
|
|
return fn;
|
|
|
|
// On Linux we don't change \ to / since filenames with \ are valid. We also
|
|
// don't remove drive: designator for the same reason.
|
|
int len = fn.Length;
|
|
if (len >= 3 && fn [1] == ':' && IsFileSystemDirSeparator (fn [2]))
|
|
return Path.GetFullPath (fn); // drive-qualified absolute file path
|
|
|
|
bool startsWithDirSeparator = IsFileSystemDirSeparator (fn [0]);
|
|
if (len >= 2 && startsWithDirSeparator && IsFileSystemDirSeparator (fn [1]))
|
|
return Path.GetFullPath (fn); // UNC path
|
|
|
|
if (!startsWithDirSeparator) {
|
|
HttpContext ctx = context ?? HttpContext.Current;
|
|
HttpRequest req = ctx != null ? ctx.Request : null;
|
|
|
|
if (req != null)
|
|
return req.MapPath (fn);
|
|
}
|
|
|
|
return fn; // Or should we rather throw?
|
|
}
|
|
|
|
internal void WriteFile (FileStream fs, long offset, long size)
|
|
{
|
|
byte [] buffer = new byte [32*1024];
|
|
|
|
if (offset != 0)
|
|
fs.Position = offset;
|
|
|
|
long remain = size;
|
|
int n;
|
|
while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
|
|
remain -= n;
|
|
output_stream.Write (buffer, 0, n);
|
|
}
|
|
}
|
|
|
|
public void WriteFile (string filename)
|
|
{
|
|
WriteFile (filename, false);
|
|
}
|
|
|
|
public void WriteFile (string filename, bool readIntoMemory)
|
|
{
|
|
if (filename == null)
|
|
throw new ArgumentNullException ("filename");
|
|
|
|
string fn = GetNormalizedFileName (filename);
|
|
if (readIntoMemory){
|
|
using (FileStream fs = File.OpenRead (fn))
|
|
WriteFile (fs, 0, fs.Length);
|
|
} else {
|
|
FileInfo fi = new FileInfo (fn);
|
|
output_stream.WriteFile (fn, 0, fi.Length);
|
|
}
|
|
if (buffer)
|
|
return;
|
|
|
|
output_stream.ApplyFilter (false);
|
|
Flush ();
|
|
}
|
|
|
|
public void WriteFile (IntPtr fileHandle, long offset, long size)
|
|
{
|
|
if (offset < 0)
|
|
throw new ArgumentNullException ("offset can not be negative");
|
|
if (size < 0)
|
|
throw new ArgumentNullException ("size can not be negative");
|
|
|
|
if (size == 0)
|
|
return;
|
|
|
|
// Note: this .ctor will throw a SecurityException if the caller
|
|
// doesn't have the UnmanagedCode permission
|
|
using (FileStream fs = new FileStream (fileHandle, FileAccess.Read))
|
|
WriteFile (fs, offset, size);
|
|
|
|
if (buffer)
|
|
return;
|
|
output_stream.ApplyFilter (false);
|
|
Flush ();
|
|
}
|
|
|
|
public void WriteFile (string filename, long offset, long size)
|
|
{
|
|
if (filename == null)
|
|
throw new ArgumentNullException ("filename");
|
|
if (offset < 0)
|
|
throw new ArgumentNullException ("offset can not be negative");
|
|
if (size < 0)
|
|
throw new ArgumentNullException ("size can not be negative");
|
|
|
|
if (size == 0)
|
|
return;
|
|
|
|
FileStream fs = File.OpenRead (filename);
|
|
WriteFile (fs, offset, size);
|
|
|
|
if (buffer)
|
|
return;
|
|
|
|
output_stream.ApplyFilter (false);
|
|
Flush ();
|
|
}
|
|
|
|
public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
|
|
{
|
|
// Emulation of .NET behavior
|
|
if (callback == null)
|
|
throw new NullReferenceException ();
|
|
|
|
object target = callback.Target;
|
|
if (target != null && target.GetType () == typeof (Control))
|
|
throw new ArgumentException ("callback");
|
|
|
|
string s = callback (context);
|
|
if (!IsCached) {
|
|
Write (s);
|
|
return;
|
|
}
|
|
|
|
Cache.Cacheability = HttpCacheability.Server;
|
|
Flush ();
|
|
if (WorkerRequest == null)
|
|
Write (s); // better this than nothing
|
|
else {
|
|
byte[] bytes = WebEncoding.ResponseEncoding.GetBytes (s);
|
|
WorkerRequest.SendResponseFromMemory (bytes, bytes.Length);
|
|
}
|
|
|
|
cached_response.SetData (callback);
|
|
}
|
|
|
|
//
|
|
// Like WriteFile, but never buffers, so we manually Flush here
|
|
//
|
|
public void TransmitFile (string filename)
|
|
{
|
|
if (filename == null)
|
|
throw new ArgumentNullException ("filename");
|
|
|
|
TransmitFile (filename, false);
|
|
}
|
|
|
|
internal void TransmitFile (string filename, bool final_flush)
|
|
{
|
|
FileInfo fi = new FileInfo (filename);
|
|
using (Stream s = fi.OpenRead ()) { } // Just check if we can read.
|
|
output_stream.WriteFile (filename, 0, fi.Length);
|
|
output_stream.ApplyFilter (final_flush);
|
|
Flush (final_flush);
|
|
}
|
|
|
|
public void TransmitFile (string filename, long offset, long length)
|
|
{
|
|
output_stream.WriteFile (filename, offset, length);
|
|
output_stream.ApplyFilter (false);
|
|
Flush (false);
|
|
}
|
|
|
|
internal void TransmitFile (VirtualFile vf)
|
|
{
|
|
TransmitFile (vf, false);
|
|
}
|
|
|
|
const int bufLen = 65535;
|
|
internal void TransmitFile (VirtualFile vf, bool final_flush)
|
|
{
|
|
if (vf == null)
|
|
throw new ArgumentNullException ("vf");
|
|
|
|
if (vf is DefaultVirtualFile) {
|
|
TransmitFile (HostingEnvironment.MapPath (vf.VirtualPath), final_flush);
|
|
return;
|
|
}
|
|
|
|
byte[] buf = new byte [bufLen];
|
|
using (Stream s = vf.Open ()) {
|
|
int readBytes;
|
|
while ((readBytes = s.Read (buf, 0, bufLen)) > 0) {
|
|
output_stream.Write (buf, 0, readBytes);
|
|
output_stream.ApplyFilter (final_flush);
|
|
Flush (false);
|
|
}
|
|
if (final_flush)
|
|
Flush (true);
|
|
}
|
|
}
|
|
|
|
#region Session state support
|
|
internal void SetAppPathModifier (string app_modifier)
|
|
{
|
|
app_path_mod = app_modifier;
|
|
}
|
|
#endregion
|
|
|
|
#region Cache Support
|
|
internal void SetCachedHeaders (NameValueCollection headers)
|
|
{
|
|
cached_headers = headers;
|
|
|
|
}
|
|
|
|
internal bool IsCached {
|
|
get { return cached_response != null; }
|
|
set {
|
|
if (value)
|
|
cached_response = new CachedRawResponse (cache_policy);
|
|
else
|
|
cached_response = null;
|
|
}
|
|
}
|
|
|
|
public HttpCachePolicy Cache {
|
|
get {
|
|
if (cache_policy == null)
|
|
cache_policy = new HttpCachePolicy ();
|
|
|
|
return cache_policy;
|
|
}
|
|
}
|
|
|
|
internal CachedRawResponse GetCachedResponse ()
|
|
{
|
|
if (cached_response != null) {
|
|
cached_response.StatusCode = StatusCode;
|
|
cached_response.StatusDescription = StatusDescription;
|
|
}
|
|
|
|
return cached_response;
|
|
}
|
|
|
|
//
|
|
// This is one of the old ASP compatibility methods, the real cache
|
|
// control is in the Cache property, and this is a second class citizen
|
|
//
|
|
public string CacheControl {
|
|
set {
|
|
if (value == null || value == "") {
|
|
Cache.SetCacheability (HttpCacheability.NoCache);
|
|
user_cache_control = null;
|
|
} else if (String.Compare (value, "public", true, Helpers.InvariantCulture) == 0) {
|
|
Cache.SetCacheability (HttpCacheability.Public);
|
|
user_cache_control = "public";
|
|
} else if (String.Compare (value, "private", true, Helpers.InvariantCulture) == 0) {
|
|
Cache.SetCacheability (HttpCacheability.Private);
|
|
user_cache_control = "private";
|
|
} else if (String.Compare (value, "no-cache", true, Helpers.InvariantCulture) == 0) {
|
|
Cache.SetCacheability (HttpCacheability.NoCache);
|
|
user_cache_control = "no-cache";
|
|
} else
|
|
throw new ArgumentException ("CacheControl property only allows `public', " +
|
|
"`private' or no-cache, for different uses, use " +
|
|
"Response.AppendHeader");
|
|
}
|
|
|
|
get { return (user_cache_control != null) ? user_cache_control : "private"; }
|
|
}
|
|
#endregion
|
|
|
|
internal int GetOutputByteCount ()
|
|
{
|
|
return output_stream.GetTotalLength ();
|
|
}
|
|
|
|
internal void ReleaseResources ()
|
|
{
|
|
if (output_stream != null)
|
|
output_stream.ReleaseResources (true);
|
|
if (completed)
|
|
return;
|
|
|
|
Close ();
|
|
completed = true;
|
|
}
|
|
}
|
|
|
|
static class FlagEnd
|
|
{
|
|
public static readonly object Value = new object ();
|
|
}
|
|
}
|
|
|