-//
-// System.Web.HttpResponse
//
-// Authors:
-// Patrik Torstensson (Patrik.Torstensson@labs2.com)
-// Gonzalo Paniagua Javier (gonzalo@ximian.com)
+// System.Web.HttpResponse.cs
//
-// (c) 2002 Ximian, Inc. (http://www.ximian.com)
+//
+// Author:
+// Miguel de Icaza (miguel@novell.com)
+// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
-
+// Copyright (C) 2005 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
// 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;
+
+using System.Text;
+using System.Web.UI;
using System.Collections;
-using System.Globalization;
+using System.Collections.Specialized;
using System.IO;
-using System.Text;
+using System.Web.Caching;
using System.Threading;
using System.Web.Util;
-using System.Web.Caching;
-
-namespace System.Web
-{
- public sealed class HttpResponse
- {
- // Chunked encoding static helpers
- static byte [] s_arrChunkSuffix = {13, 10};
- static byte [] s_arrChunkEnd = {48, 13, 10, 13, 10};
- static string s_sChunkedPrefix = "\r\n";
+using System.Globalization;
+using System.Security.Permissions;
- ArrayList _Headers;
-
- bool _bClientDisconnected;
- bool _bSuppressHeaders;
- bool _bSuppressContent;
- bool _bChunked;
- bool last_chunk_sent;
- bool _bEnded;
- bool _bBuffering;
- bool _bHeadersSent;
- bool _bFlushing;
- bool filtered;
- long _lContentLength;
- int _iStatusCode;
+namespace System.Web {
+
+ // CAS - no InheritanceDemand here as the class is sealed
+ [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
+ public sealed class HttpResponse {
+ internal HttpWorkerRequest WorkerRequest;
+ internal HttpResponseStream output_stream;
+ internal bool buffer = true;
- int _expiresInMinutes;
- bool _expiresInMinutesSet;
- DateTime _expiresAbsolute;
- bool _expiresAbsoluteSet;
-
- bool closed;
-
- string _sContentType;
- string _sCacheControl;
- string _sTransferEncoding;
- string _sCharset;
- string _sStatusDescription;
- bool forced_charset;
-
- HttpCookieCollection _Cookies;
- HttpCachePolicy _CachePolicy;
-
- Encoding _ContentEncoding;
-
- HttpContext _Context;
- HttpWriter _Writer;
- TextWriter _TextWriter;
-
- HttpWorkerRequest _WorkerRequest;
+ HttpContext context;
+ TextWriter writer;
+ HttpCachePolicy cache_policy;
+ Encoding encoding;
+ HttpCookieCollection cookies;
+
+ int status_code = 200;
+ string status_description = "OK";
- ArrayList fileDependencies;
+ string content_type = "text/html";
+ string charset;
+ bool charset_set;
CachedRawResponse cached_response;
+ string user_cache_control = "private";
+ string redirect_location;
+
+ //
+ // 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.
+ //
+ ArrayList headers = new ArrayList ();
+ bool headers_sent;
ArrayList cached_headers;
-#if NET_1_1
- string redirectLocation;
-#endif
- string app_path_mod = null;
-
- private HttpResponse ()
- {
- _bBuffering = true;
- _Headers = new ArrayList ();
- _sContentType = "text/html";
- _iStatusCode = 200;
- }
+ //
+ // Transfer encoding state
+ //
+ string transfer_encoding;
+ internal bool use_chunked;
+
+ bool closed;
+ internal bool suppress_content;
- public HttpResponse (TextWriter output) : this ()
- {
- _TextWriter = output;
- }
+ //
+ // Session State
+ //
+ string app_path_mod;
+
+ //
+ // Passed as flags
+ //
+ internal object FlagEnd = new object ();
- internal HttpResponse (HttpWorkerRequest WorkerRequest, HttpContext Context) : this ()
+ internal HttpResponse ()
{
- _Context = Context;
- _WorkerRequest = WorkerRequest;
+ output_stream = new HttpResponseStream (this);
}
-#if TARGET_J2EE
- public HttpWorkerRequest WorkerRequest{
- get{ return _WorkerRequest; }
- }
-#endif
-
- internal void InitializeWriter ()
- {
- // We cannot do this in the .ctor because HttpWriter uses configuration and
- // it may not be initialized
- if (_Writer == null) {
- _Writer = new HttpWriter (this);
- _TextWriter = _Writer;
- }
- }
-
- internal void FinalFlush ()
+ public HttpResponse (TextWriter writer) : this ()
{
- Flush (true);
+ this.writer = writer;
}
- internal void DoFilter (bool really)
+ internal HttpResponse (HttpWorkerRequest worker_request, HttpContext context) : this ()
{
- if (really && null != _Writer)
- _Writer.FilterData (true);
-
- filtered = true;
- }
-
- internal bool IsCached {
- get { return cached_response != null; }
- }
+ WorkerRequest = worker_request;
+ this.context = context;
- internal CachedRawResponse GetCachedResponse () {
- cached_response.StatusCode = StatusCode;
- cached_response.StatusDescription = StatusDescription;
- return cached_response;
- }
-
- internal void SetCachedHeaders (ArrayList headers)
- {
- cached_headers = headers;
+ if (worker_request != null)
+ use_chunked = (worker_request.GetHttpVersion () == "HTTP/1.1");
}
- private ArrayList GenerateHeaders ()
+ internal TextWriter SetTextWriter (TextWriter writer)
{
- ArrayList oHeaders = new ArrayList (_Headers);
-
- oHeaders.Add (new HttpResponseHeader ("X-Powered-By", "Mono"));
-
-#if !TARGET_J2EE
- string date = DateTime.UtcNow.ToString ("ddd, d MMM yyyy HH:mm:ss 'GMT'", CultureInfo.InvariantCulture);
- HttpResponseHeader date_header = new HttpResponseHeader ("Date", date);
- oHeaders.Add (date_header);
+ TextWriter prev = writer;
- if (IsCached)
- cached_response.DateHeader = date_header;
-
- if (_lContentLength > 0) {
- oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderContentLength,
- _lContentLength.ToString ()));
- }
-#endif
-
- // Apache2 only auto-adds 'charset=blah' for text/plain and text/html
- if (_sContentType != null) {
- string ctype = _sContentType;
- if (forced_charset || _sContentType == "text/plain" || _sContentType == "text/html") {
- if (_sContentType.IndexOf ("charset=") == -1) {
- if (Charset.Length == 0)
- Charset = ContentEncoding.HeaderName;
-
- // Time to build our string
- if (Charset.Length > 0)
- ctype += "; charset=" + Charset;
- }
- }
-
- oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderContentType, ctype));
- }
-
- if (_CachePolicy != null)
- _CachePolicy.SetHeaders (this, oHeaders);
+ this.writer = writer;
- if (_sCacheControl != null) {
- oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderPragma,
- _sCacheControl));
- }
-
- if (_sTransferEncoding != null) {
- oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderTransferEncoding,
- _sTransferEncoding));
- }
-
- if (_Cookies != null) {
- int length = _Cookies.Count;
- for (int i = 0; i < length; i++) {
- oHeaders.Add (_Cookies.Get (i).GetCookieHeader ());
- }
- }
-#if NET_1_1
- if (redirectLocation != null)
- oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderLocation,
- redirectLocation));
-#endif
- return oHeaders;
+ return prev;
}
- private void SendHeaders ()
- {
- _WorkerRequest.SendStatus (StatusCode, StatusDescription);
-
- ArrayList oHeaders;
-
- if (cached_headers != null)
- oHeaders = cached_headers;
- else
- oHeaders = GenerateHeaders ();
-
- if (cached_response != null)
- cached_response.SetHeaders (oHeaders);
-
- foreach (HttpResponseHeader oHeader in oHeaders)
- oHeader.SendContent (_WorkerRequest);
-
-#if !TARGET_J2EE //in J2EE env. we are setting headers into
- //HttpServletResponse instance -> headers are
- //still not sent
- _bHeadersSent = true;
-#endif
- }
-
- public string Status
- {
+ public bool Buffer {
get {
- return String.Format ("{0} {1}", StatusCode, StatusDescription);
+ return buffer;
}
set {
- string sMsg = "OK";
- int iCode = 200;
-
- try {
- iCode = Int32.Parse (value.Substring (0, value.IndexOf (' ')));
- sMsg = value.Substring (value.IndexOf (' ') + 1);
- } catch (Exception) {
- throw new HttpException ("Invalid status string");
- }
-
- StatusCode = iCode;
- StatusDescription = sMsg;
+ buffer = value;
}
}
- [MonoTODO()]
- public void AddCacheItemDependencies (ArrayList cacheKeys)
- {
- throw new NotImplementedException ();
- }
-
- [MonoTODO()]
- public void AddCacheItemDependency(string cacheKey)
- {
- throw new NotImplementedException ();
- }
-
- public void AddFileDependencies (ArrayList filenames)
- {
- if (filenames == null || filenames.Count == 0)
- return;
-
- if (fileDependencies == null) {
- fileDependencies = (ArrayList) filenames.Clone ();
- return;
+ public bool BufferOutput {
+ get {
+ return buffer;
}
- foreach (string fn in filenames)
- AddFileDependency (fn);
- }
-
- public void AddFileDependency (string filename)
- {
- if (fileDependencies == null)
- fileDependencies = new ArrayList ();
-
- fileDependencies.Add (filename);
- }
-
- public void AddHeader (string name, string value)
- {
- AppendHeader(name, value);
- }
-
- public void AppendCookie (HttpCookie cookie)
- {
- if (_bHeadersSent)
- throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
-
- Cookies.Add (cookie);
- }
-
- [MonoTODO()]
- public void AppendToLog (string param)
- {
- throw new NotImplementedException ();
+ set {
+ buffer = value;
+ }
}
- public string ApplyAppPathModifier (string virtualPath)
- {
- if (virtualPath == null)
- return null;
-
- if (virtualPath == "")
- return _Context.Request.RootVirtualDir;
-
- if (UrlUtils.IsRelativeUrl (virtualPath)) {
- virtualPath = UrlUtils.Combine (_Context.Request.RootVirtualDir, virtualPath);
- } else if (UrlUtils.IsRooted (virtualPath)) {
- virtualPath = UrlUtils.Reduce (virtualPath);
+ //
+ // 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;
}
- if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
- string rvd = _Context.Request.RootVirtualDir;
- string basevd = rvd.Replace (app_path_mod, "");
-
- if (!StrUtils.StartsWith (virtualPath, basevd))
- return virtualPath;
+ set {
+ if (value == null)
+ throw new ArgumentException ("ContentEncoding can not be null");
- virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
+ encoding = value;
+ HttpWriter http_writer = writer as HttpWriter;
+ if (http_writer != null)
+ http_writer.SetEncoding (encoding);
}
-
- return virtualPath;
- }
-
- internal void SetAppPathModifier (string app_path_mod)
- {
- this.app_path_mod = app_path_mod;
}
- public bool Buffer
- {
+ public string ContentType {
get {
- return BufferOutput;
+ return content_type;
}
set {
- BufferOutput = value;
+ content_type = value;
}
}
- public bool BufferOutput
- {
+ public string Charset {
get {
- return _bBuffering;
+ if (charset == null)
+ charset = ContentEncoding.WebName;
+
+ return charset;
}
-
- set {
- if (_Writer != null)
- _Writer.Update ();
- _bBuffering = value;
+ set {
+ charset_set = true;
+ charset = value;
}
}
-
- public HttpCachePolicy Cache
- {
+
+ public HttpCookieCollection Cookies {
get {
- if (null == _CachePolicy) {
- _CachePolicy = new HttpCachePolicy ();
- _CachePolicy.CacheabilityUpdated += new CacheabilityUpdatedCallback (
- OnCacheabilityUpdated);
- }
-
- return _CachePolicy;
+ if (cookies == null)
+ cookies = new HttpCookieCollection (true, false);
+ return cookies;
}
}
-
- private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
- {
- if (e.Cacheability >= HttpCacheability.Server && !IsCached)
- cached_response = new CachedRawResponse (_CachePolicy);
- else if (e.Cacheability <= HttpCacheability.Private)
- cached_response = null;
- }
- [MonoTODO("Set status in the cache policy")]
- public string CacheControl
- {
+ public int Expires {
get {
- return _sCacheControl;
+ if (cache_policy == null)
+ return 0;
+
+ return cache_policy.ExpireMinutes ();
}
set {
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
-
- _sCacheControl = value;
+ Cache.SetExpires (DateTime.Now + new TimeSpan (0, value, 0));
}
}
-
- public string Charset
- {
+
+ public DateTime ExpiresAbsolute {
get {
- if (null == _sCharset)
- _sCharset = ContentEncoding.WebName;
-
- return _sCharset;
+ return Cache.Expires;
}
set {
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
-
- if (value == null)
- value = "";
-
- forced_charset = true;
- _sCharset = value;
+ Cache.SetExpires (value);
}
}
- public Encoding ContentEncoding
- {
+ public Stream Filter {
get {
- if (_ContentEncoding == null)
- _ContentEncoding = WebEncoding.ResponseEncoding;
+ if (WorkerRequest == null)
+ return null;
- return _ContentEncoding;
+ return output_stream.Filter;
}
+ set {
+ output_stream.Filter = value;
+ }
+ }
+#if NET_2_0
+ [MonoTODO]
+ public Encoding HeaderEncoding {
+ get { throw new NotImplementedException (); }
set {
if (value == null)
- throw new ArgumentNullException ("Can't set a null as encoding");
-
- _ContentEncoding = value;
-
- if (_Writer != null)
- _Writer.Update ();
+ throw new ArgumentNullException ("HeaderEncoding");
+ throw new NotImplementedException ();
}
}
-
- public string ContentType
- {
+#endif
+ public bool IsClientConnected {
get {
- return _sContentType;
- }
-
- set {
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
+ if (WorkerRequest == null)
+ return true; // yep that's true
- _sContentType = value;
+ return WorkerRequest.IsClientConnected ();
}
}
-
- public HttpCookieCollection Cookies
- {
+#if NET_2_0
+ [MonoTODO]
+ public bool IsRequestBeingRedirected {
+ get { throw new NotImplementedException (); }
+ }
+#endif
+ public TextWriter Output {
get {
- if (null == _Cookies)
- _Cookies = new HttpCookieCollection (this, false);
+ if (writer == null)
+ writer = new HttpWriter (this);
- return _Cookies;
+ return writer;
}
}
- public int Expires
- {
+ public Stream OutputStream {
get {
- return _expiresInMinutes;
+ return output_stream;
+ }
+ }
+
+ public string RedirectLocation {
+ get {
+ return redirect_location;
}
set {
- if (!_expiresInMinutesSet || (value < _expiresInMinutes))
- {
- _expiresInMinutes = value;
- Cache.SetExpires(_Context.Timestamp.Add(new TimeSpan(0, _expiresInMinutes, 0)));
- }
- _expiresInMinutesSet = true;
+ redirect_location = value;
}
}
-
- public DateTime ExpiresAbsolute
- {
+
+ public string Status {
get {
- return _expiresAbsolute;
+ return String.Format ("{0} {1}", status_code, StatusDescription);
}
set {
- if (!_expiresAbsoluteSet || value.CompareTo(_expiresAbsolute)<0)
- {
- _expiresAbsolute = value;
- Cache.SetExpires(_expiresAbsolute);
+ int p = value.IndexOf (' ');
+ if (p == -1)
+ throw new HttpException ("Invalid format for the Status property");
+
+ string s = value.Substring (0, p);
+
+#if NET_2_0
+ if (!Int32.TryParse (s, out status_code))
+ throw new HttpException ("Invalid format for the Status property");
+#else
+
+ try {
+ status_code = Int32.Parse (s);
+ } catch {
+ throw new HttpException ("Invalid format for the Status property");
}
- _expiresAbsoluteSet = true;
+#endif
+
+ status_description = value.Substring (p+1);
}
}
- public Stream Filter
- {
+ public int StatusCode {
get {
- if (_Writer != null)
- return _Writer.GetActiveFilter ();
-
- return null;
+ return status_code;
}
set {
- if (_Writer == null)
- throw new HttpException ("Filtering is not allowed");
-
- _Writer.ActivateFilter (value);
+ if (headers_sent)
+ throw new HttpException ("headers have already been sent");
+
+ status_code = value;
+ status_description = null;
}
}
- public bool IsClientConnected {
+ public string StatusDescription {
get {
- if (_bClientDisconnected)
- return false;
+ if (status_description == null)
+ status_description = HttpWorkerRequest.GetStatusDescription (status_code);
- if (null != _WorkerRequest)
- _bClientDisconnected = (!_WorkerRequest.IsClientConnected ());
+ return status_description;
+ }
- return !_bClientDisconnected;
+ set {
+ if (headers_sent)
+ throw new HttpException ("headers have already been sent");
+
+ status_description = value;
}
}
-
- public TextWriter Output
- {
+
+ public bool SuppressContent {
get {
- return _TextWriter;
+ return suppress_content;
}
- }
-
- public Stream OutputStream
- {
- get {
- if (_Writer == null)
- throw new HttpException ("an Output stream not available when " +
- "running with custom text writer");
- return _Writer.OutputStream;
+ set {
+ suppress_content = value;
}
}
+#if NET_2_0
+ [MonoTODO]
+ public void AddCacheDependency (CacheDependency[] dependencies)
+ {
+ throw new NotImplementedException ();
+ }
-#if NET_1_1
- public string RedirectLocation {
- get { return redirectLocation; }
- set { redirectLocation = value; }
+ [MonoTODO]
+ public void AddCacheItemDependencies (string[] cacheKeys)
+ {
+ throw new NotImplementedException ();
}
#endif
-
- public string StatusDescription
+ [MonoTODO]
+ public void AddCacheItemDependencies (ArrayList cacheKeys)
{
- get {
- if (null == _sStatusDescription)
- _sStatusDescription =
- HttpWorkerRequest.GetStatusDescription (_iStatusCode);
-
- return _sStatusDescription;
- }
+ // TODO: talk to jackson about the cache
+ }
- set {
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
+ [MonoTODO]
+ public void AddCacheItemDependency (string cacheKey)
+ {
+ // TODO: talk to jackson about the cache
+ }
- _sStatusDescription = value;
- }
+ [MonoTODO]
+ public void AddFileDependencies (ArrayList filenames)
+ {
+ // TODO: talk to jackson about the cache
}
-
- public int StatusCode
+#if NET_2_0
+ [MonoTODO]
+ public void AddFileDependencies (string[] filenames)
{
- get {
- return _iStatusCode;
- }
+ throw new NotImplementedException ();
+ }
+#endif
+ [MonoTODO]
+ public void AddFileDependency (string filename)
+ {
+ // TODO: talk to jackson about the cache
+ }
- set {
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
+ public void AddHeader (string name, string value)
+ {
+ AppendHeader (name, value);
+ }
- _sStatusDescription = null;
- _iStatusCode = value;
- }
+ public void AppendCookie (HttpCookie cookie)
+ {
+ Cookies.Add (cookie);
}
- public bool SuppressContent
+ //
+ // AppendHeader:
+ // Special case for Content-Length, Content-Type, Transfer-Encoding and Cache-Control
+ //
+ //
+ public void AppendHeader (string name, string value)
{
- get {
- return _bSuppressContent;
- }
+ if (headers_sent)
+ throw new HttpException ("headers have been already sent");
- set {
- _bSuppressContent = true;
+ if (String.Compare (name, "content-length", true, CultureInfo.InvariantCulture) == 0){
+ content_length = (long) UInt64.Parse (value);
+ use_chunked = false;
+ return;
}
- }
- HttpRequest Request
- {
- get {
- if (_Context == null)
- return null;
+ if (String.Compare (name, "content-type", true, CultureInfo.InvariantCulture) == 0){
+ ContentType = value;
+ return;
+ }
- return _Context.Request;
+ if (String.Compare (name, "transfer-encoding", true, CultureInfo.InvariantCulture) == 0){
+ transfer_encoding = value;
+ use_chunked = false;
+ return;
}
- }
- internal void AppendHeader (int iIndex, string value)
- {
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
-
- switch (iIndex) {
- case HttpWorkerRequest.HeaderContentLength:
- _lContentLength = Int64.Parse (value);
- break;
- case HttpWorkerRequest.HeaderContentEncoding:
- _sContentType = value;
- break;
- case HttpWorkerRequest.HeaderTransferEncoding:
- _sTransferEncoding = value;
- _bChunked = (value == "chunked");
- break;
- case HttpWorkerRequest.HeaderPragma:
- _sCacheControl = value;
- break;
- default:
- _Headers.Add (new HttpResponseHeader (iIndex, value));
- break;
+ if (String.Compare (name, "cache-control", true, CultureInfo.InvariantCulture) == 0){
+ user_cache_control = value;
+ return;
}
+
+ headers.Add (new UnknownResponseHeader (name, value));
}
- public void AppendHeader (string name, string value)
+ [AspNetHostingPermission (SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Medium)]
+ public void AppendToLog (string param)
{
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
-
- switch (name.ToLower (CultureInfo.InvariantCulture)) {
- case "content-length":
- _lContentLength = Int64.Parse (value);
- break;
- case "content-type":
- _sContentType = value;
- break;
- case "transfer-encoding":
- _sTransferEncoding = value;
- _bChunked = (value == "chunked");
- break;
- case "pragma":
- _sCacheControl = value;
- break;
- default:
- _Headers.Add (new HttpResponseHeader (name, value));
- break;
- }
+ Console.Write ("System.Web: ");
+ Console.WriteLine (param);
}
-
- internal TextWriter SetTextWriter (TextWriter w)
+
+ public string ApplyAppPathModifier (string virtualPath)
{
- TextWriter prev = _TextWriter;
- _TextWriter = w;
- return prev;
- }
+ if (virtualPath == null)
+ return null;
+ if (virtualPath == "")
+ return context.Request.RootVirtualDir;
+
+ if (UrlUtils.IsRelativeUrl (virtualPath)) {
+ virtualPath = UrlUtils.Combine (context.Request.RootVirtualDir, virtualPath);
+ } else if (UrlUtils.IsRooted (virtualPath)) {
+ virtualPath = UrlUtils.Canonic (virtualPath);
+ }
+
+ if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
+ string rvd = context.Request.RootVirtualDir;
+ string basevd = rvd.Replace (app_path_mod, "");
+
+ if (!StrUtils.StartsWith (virtualPath, basevd))
+ return virtualPath;
+
+ virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
+ }
+
+ return virtualPath;
+ }
+
public void BinaryWrite (byte [] buffer)
{
- OutputStream.Write (buffer, 0, buffer.Length);
+ output_stream.Write (buffer, 0, buffer.Length);
}
- internal void BinaryWrite (byte [] buffer, int start, int length)
+ internal void BinaryWrite (byte [] buffer, int start, int len)
{
- OutputStream.Write (buffer, start, length);
+ output_stream.Write (buffer, start, len);
}
-
+
public void Clear ()
{
- if (_Writer != null)
- _Writer.Clear ();
+ ClearContent ();
}
public void ClearContent ()
{
- Clear();
+ output_stream.Clear ();
}
- internal void SetHeadersSent (bool val)
- {
- _bHeadersSent = val;
- }
-
public void ClearHeaders ()
{
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
-
- _sContentType = "text/html";
- forced_charset = false;
+ if (headers_sent)
+ throw new HttpException ("headers have been already sent");
- _iStatusCode = 200;
- _sCharset = null;
- _Headers.Clear ();
- _sCacheControl = null;
- _sTransferEncoding = null;
+ // Reset the special case headers.
+ content_length = -1;
+ content_type = "text/html";
+ transfer_encoding = null;
+ user_cache_control = null;
+ headers.Clear ();
+ }
- _lContentLength = 0;
- _bSuppressContent = false;
- _bSuppressHeaders = false;
- _bClientDisconnected = false;
+ internal bool HeadersSent {
+ get {
+ return headers_sent;
+ }
}
public void Close ()
{
- if (!closed && !_bClientDisconnected) {
- if (_bChunked && !last_chunk_sent) {
- last_chunk_sent = true;
- _WorkerRequest.SendResponseFromMemory (s_arrChunkEnd, s_arrChunkEnd.Length);
- }
- _WorkerRequest.CloseConnection ();
- _bClientDisconnected = true;
- closed = true;
- }
+ if (closed)
+ return;
+ if (WorkerRequest != null)
+ WorkerRequest.CloseConnection ();
+ closed = true;
}
- internal void Dispose ()
+ public void End ()
{
- if (_Writer != null) {
- _Writer.Dispose ();
- _Writer = null;
+ if (context.TimeoutPossible) {
+ Thread.CurrentThread.Abort (FlagEnd);
+ } else {
+ // If this is called from an async event, signal the completion
+ // but don't throw.
+ context.ApplicationInstance.CompleteRequest ();
}
}
- [MonoTODO("Handle callbacks into before done with session, needs to have a non ended flush here")]
- internal void FlushAtEndOfRequest ()
+ // Generate:
+ // Content-Length
+ // Content-Type
+ // Transfer-Encoding (chunked)
+ // Cache-Control
+ void AddHeadersNoCache (ArrayList write_headers, bool final_flush)
{
- Flush (true);
- }
+ //
+ // Transfer-Encoding
+ //
+ if (use_chunked)
+ write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", "chunked"));
+ else if (transfer_encoding != null)
+ write_headers.Add (new UnknownResponseHeader ("Transfer-Encoding", transfer_encoding));
- public void End ()
- {
- if (_bEnded)
- return;
+ UnknownResponseHeader date_header = new UnknownResponseHeader ("Date",
+ DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture));
+ write_headers.Add (date_header);
- if (_Context.TimeoutPossible)
-#if !TARGET_J2EE
- Thread.CurrentThread.Abort (new StepCompleteRequest ());
-#else
- throw new vmw.@internal.j2ee.StopExecutionException();
+ if (IsCached)
+ cached_response.DateHeader = date_header;
+
+ if (redirect_location != null)
+ write_headers.Add (new UnknownResponseHeader ("Location", redirect_location));
+
+ //
+ // If Content-Length is set.
+ //
+ if (content_length >= 0){
+ write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
+ content_length.ToString (CultureInfo.InvariantCulture)));
+ } else if (Buffer){
+ 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 (new KnownResponseHeader (HttpWorkerRequest.HeaderContentLength,
+ content_length.ToString (CultureInfo.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){
+#if DEBUG
+ Console.WriteLine ("Setting to close2");
#endif
+ write_headers.Add (new KnownResponseHeader (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){
+#if DEBUG
+ Console.WriteLine ("Setting to close");
+#endif
+ write_headers.Add (new KnownResponseHeader (HttpWorkerRequest.HeaderConnection, "close"));
+ }
+ }
- Flush ();
- _bEnded = true;
- _Context.ApplicationInstance.CompleteRequest ();
- }
-
- public void Flush ()
- {
- if (closed)
- throw new HttpException ("Response already finished.");
+ //
+ // 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 (new UnknownResponseHeader ("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) {
+ if (charset == null || charset == "")
+ charset = ContentEncoding.HeaderName;
+ header += "; charset=" + charset;
+ }
+ }
+
+ write_headers.Add (new UnknownResponseHeader ("Content-Type", header));
+ }
- Flush (false);
+ if (cookies != null && cookies.Count != 0){
+ int n = cookies.Count;
+ for (int i = 0; i < n; i++)
+ write_headers.Add (cookies.Get (i).GetCookieHeader ());
+ }
+
}
- private void Flush (bool bFinish)
+ internal void WriteHeaders (bool final_flush)
{
- if (_bFlushing || closed)
+ if (headers_sent)
return;
- _bFlushing = true;
-
- if (_Writer == null) {
- _TextWriter.Flush ();
- _bFlushing = false;
- return;
- }
+ if (WorkerRequest != null)
+ WorkerRequest.SendStatus (status_code, StatusDescription);
- try {
- if (_bClientDisconnected)
- return;
-
- long length = _Writer.BufferSize;
- if (!_bHeadersSent && !_bSuppressHeaders) {
- if (bFinish) {
- if (length == 0 && _lContentLength == 0)
- _sContentType = null;
-
- SendHeaders ();
- length = _Writer.BufferSize;
- _WorkerRequest.SendCalculatedContentLength ((int) length);
- } else {
- if (_lContentLength == 0 && _iStatusCode == 200 &&
- _sTransferEncoding == null) {
- // Check we are going todo chunked encoding
- string sProto = Request.ServerVariables ["SERVER_PROTOCOL"];
- if (sProto != null && sProto == "HTTP/1.1") {
- AppendHeader (
- HttpWorkerRequest.HeaderTransferEncoding,
- "chunked");
- } else {
- // Just in case, the old browsers send a HTTP/1.0
- // request with Connection: Keep-Alive
- AppendHeader (
- HttpWorkerRequest.HeaderConnection,
- "Close");
- }
- }
-
- length = _Writer.BufferSize;
- SendHeaders ();
- }
- }
-
- if (!filtered) {
- _Writer.FilterData (false);
- length = _Writer.BufferSize;
- }
-
- if (length == 0) {
- if (bFinish && _bChunked) {
- last_chunk_sent = true;
- _WorkerRequest.SendResponseFromMemory (s_arrChunkEnd, s_arrChunkEnd.Length);
- }
+ if (cached_response != null)
+ cached_response.SetHeaders (headers);
- _WorkerRequest.FlushResponse (bFinish);
- if (!bFinish)
- _Writer.Clear ();
- return;
+ // If this page is cached use the cached headers
+ // instead of the standard headers
+ ArrayList write_headers = headers;
+ if (cached_headers != null)
+ write_headers = cached_headers;
+ else
+ AddHeadersNoCache (write_headers, final_flush);
+
+ //
+ // Flush
+ //
+ if (context != null) {
+ HttpApplication app_instance = context.ApplicationInstance;
+ if (app_instance != null)
+ app_instance.TriggerPreSendRequestHeaders ();
+ }
+ if (WorkerRequest != null) {
+ foreach (BaseResponseHeader header in write_headers){
+ header.SendContent (WorkerRequest);
}
+ }
+ headers_sent = true;
+ }
- if (!_bSuppressContent && Request.HttpMethod == "HEAD")
- _bSuppressContent = true;
+ internal void DoFilter (bool close)
+ {
+ if (output_stream.HaveFilter && context != null && context.Error == null)
+ output_stream.ApplyFilter (close);
+ }
- if (_bSuppressContent)
- _Writer.Clear ();
+ internal void Flush (bool final_flush)
+ {
+ DoFilter (final_flush);
+ if (!headers_sent){
+ if (final_flush || status_code != 200)
+ use_chunked = false;
+ }
- if (!_bSuppressContent) {
- _bClientDisconnected = false;
- if (_bChunked) {
- Encoding oASCII = Encoding.ASCII;
+ 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.
+ return;
+ }
- string chunk = Convert.ToString(_Writer.BufferSize, 16);
- byte [] arrPrefix = oASCII.GetBytes (chunk + s_sChunkedPrefix);
+ if (!headers_sent)
+ WriteHeaders (final_flush);
- _WorkerRequest.SendResponseFromMemory (arrPrefix,
- arrPrefix.Length);
+ if (context != null) {
+ HttpApplication app_instance = context.ApplicationInstance;
+ if (app_instance != null)
+ app_instance.TriggerPreSendRequestContent ();
+ }
- _Writer.SendContent (_WorkerRequest);
+ if (IsCached) {
+ MemoryStream ms = output_stream.GetData ();
+ cached_response.ContentLength = (int) ms.Length;
+ cached_response.SetData (ms.GetBuffer ());
+ }
- _WorkerRequest.SendResponseFromMemory (s_arrChunkSuffix,
- s_arrChunkSuffix.Length);
- if (bFinish) {
- last_chunk_sent = true;
- _WorkerRequest.SendResponseFromMemory (
- s_arrChunkEnd, s_arrChunkEnd.Length);
- }
- } else {
- _Writer.SendContent (_WorkerRequest);
- }
- }
+ if (WorkerRequest != null)
+ output_stream.Flush (WorkerRequest, final_flush);
+ }
- _WorkerRequest.FlushResponse (bFinish);
- if (IsCached) {
- cached_response.ContentLength = (int) length;
- cached_response.SetData (_Writer.GetBuffer ());
- }
- _Writer.Clear ();
- } finally {
- if (bFinish)
- closed = true;
- _bFlushing = false;
- }
+ public void Flush ()
+ {
+ Flush (false);
}
public void Pics (string value)
AppendHeader ("PICS-Label", value);
}
-
public void Redirect (string url)
{
Redirect (url, true);
public void Redirect (string url, bool endResponse)
{
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
-
- Clear ();
+ if (headers_sent)
+ throw new HttpException ("header have been already sent");
- url = ApplyAppPathModifier (url);
+ ClearHeaders ();
+ ClearContent ();
+
StatusCode = 302;
- AppendHeader (HttpWorkerRequest.HeaderLocation, url);
+ url = ApplyAppPathModifier (url);
+ headers.Add (new UnknownResponseHeader ("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 ();
}
- internal bool RedirectCustomError (string errorPage)
+ public static void RemoveOutputCacheItem (string path)
{
- if (_bHeadersSent)
- return false;
+ if (path == null)
+ throw new ArgumentNullException ("path");
+
+ if (path.Length == 0)
+ return;
- if (Request.QueryString ["aspxerrorpath"] != null)
- return false; // Prevent endless loop
+ if (path [0] != '/')
+ throw new ArgumentException ("'" + path + "' is not an absolute virtual path.");
- Redirect (errorPage + "?aspxerrorpath=" + Request.Path, false);
- return true;
+ HttpRuntime.Cache.Remove (path);
}
-
+
+ public void SetCookie (HttpCookie cookie)
+ {
+ AppendCookie (cookie);
+ }
+
public void Write (char ch)
{
- _TextWriter.Write(ch);
+ Output.Write (ch);
}
public void Write (object obj)
{
- _TextWriter.Write(obj);
+ if (obj == null)
+ return;
+
+ Output.Write (obj.ToString ());
}
-
- public void Write (string str)
+
+ public void Write (string s)
{
- _TextWriter.Write (str);
+ Output.Write (s);
}
-
+
public void Write (char [] buffer, int index, int count)
{
- _TextWriter.Write (buffer, index, count);
+ Output.Write (buffer, index, count);
}
- public static void RemoveOutputCacheItem (string path)
+ internal void WriteFile (FileStream fs, long offset, long size)
{
- if (path == null)
- throw new ArgumentNullException ("path");
-
- if (!UrlUtils.IsRooted (path))
- throw new ArgumentException ("Invalid path for HttpResponse.RemoveOutputCacheItem '" +
- path + "'. An absolute virtual path is expected.");
+ byte [] buffer = new byte [32*1024];
+
+ if (offset != 0)
+ fs.Position = offset;
- Cache cache = HttpRuntime.Cache;
- cache.Remove (path);
+ 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 SetCookie (HttpCookie cookie)
+ public void WriteFile (string filename, bool readIntoMemory)
{
- if (_bHeadersSent)
- throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
+ if (filename == null)
+ throw new ArgumentNullException ("filename");
- Cookies.Add (cookie);
+ if (readIntoMemory){
+ using (FileStream fs = File.OpenRead (filename))
+ WriteFile (fs, 0, fs.Length);
+ } else {
+ FileInfo fi = new FileInfo (filename);
+ output_stream.WriteFile (filename, 0, fi.Length);
+ }
+ if (buffer)
+ return;
+
+ output_stream.ApplyFilter (false);
+ Flush ();
}
-#if NET_1_1
- // LAMESPEC: added in a service pack for 1.1 and available in 2.0 beta
- [MonoTODO]
- public void TransmitFile (string filename)
+#if TARGET_JVM
+ public void WriteFile (IntPtr fileHandle, long offset, long size) {
+ throw new NotSupportedException("IntPtr not supported");
+ }
+#else
+ public void WriteFile (IntPtr fileHandle, long offset, long size)
{
- throw new NotImplementedException ();
+ 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 ();
}
#endif
- private void WriteFromStream (Stream stream, long offset, long length, long bufsize)
+ public void WriteFile (string filename, long offset, long size)
{
- if (offset < 0 || length <= 0)
+ 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;
- long stLength = stream.Length;
- if (offset + length > stLength)
- length = stLength - offset;
-
- if (offset > 0)
- stream.Seek (offset, SeekOrigin.Begin);
-
- byte [] fileContent = new byte [bufsize];
- int count = (int) Math.Min (Int32.MaxValue, bufsize);
- while (length > 0 && (count = stream.Read (fileContent, 0, count)) != 0) {
- _Writer.WriteBytes (fileContent, 0, count);
- length -= count;
- count = (int) Math.Min (length, fileContent.Length);
- }
- }
+ FileStream fs = File.OpenRead (filename);
+ WriteFile (fs, offset, size);
- public void WriteFile (string filename)
- {
- WriteFile (filename, false);
- }
+ if (buffer)
+ return;
- public void WriteFile (string filename, bool readIntoMemory)
+ output_stream.ApplyFilter (false);
+ Flush ();
+ }
+#if NET_2_0
+ [MonoTODO]
+ public void WriteSubstitution (HttpResponseSubstitutionCallback callback)
{
-#if TARGET_J2EE
- if ((this.Request != null) && ((filename.Length <= 2)
- || ((filename[0] != '\\') && (filename[1] != ':'))))
- filename = Request.MapPath(filename);
+ throw new NotImplementedException ();
+ }
#endif
- FileStream fs = null;
- try {
- fs = File.OpenRead (filename);
- long size = fs.Length;
- if (readIntoMemory) {
- WriteFromStream (fs, 0, size, size);
- } else {
- WriteFromStream (fs, 0, size, 30702);
- }
- } finally {
- if (fs != null)
- fs.Close ();
- }
+ //
+ // 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);
}
- public void WriteFile (string filename, long offset, long size)
+ internal void TransmitFile (string filename, bool final_flush)
{
- FileStream fs = null;
-#if TARGET_J2EE
- if ((this.Request != null) && ((filename.Length <= 2)
- || ((filename[0] != '\\') && (filename[1] != ':'))))
- filename = Request.MapPath(filename);
-#endif
- try {
- fs = File.OpenRead (filename);
- WriteFromStream (fs, offset, size, 30702);
- } finally {
- if (fs != null)
- fs.Close ();
- }
+ 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);
}
+
-#if !TARGET_J2EE
- public void WriteFile (IntPtr fileHandle, long offset, long size)
+#region Session state support
+ internal void SetAppPathModifier (string app_modifier)
+ {
+ app_path_mod = app_modifier;
+ }
+#endregion
+
+#region Cache Support
+ internal void SetCachedHeaders (ArrayList headers)
{
- FileStream fs = null;
- try {
- fs = new FileStream (fileHandle, FileAccess.Read);
- WriteFromStream (fs, offset, size, 30702);
- } finally {
- if (fs != null)
- fs.Close ();
+ cached_headers = headers;
+ }
+
+ internal bool IsCached {
+ get {
+ return cached_response != null;
}
- }
-#endif
+ }
- [MonoTODO()]
- internal void OnCookieAdd (HttpCookie cookie)
+ public HttpCachePolicy Cache {
+ get {
+ if (cache_policy == null) {
+ cache_policy = new HttpCachePolicy ();
+ cache_policy.CacheabilityUpdated += new CacheabilityUpdatedCallback (OnCacheabilityUpdated);
+ }
+
+ return cache_policy;
+ }
+ }
+
+ private void OnCacheabilityUpdated (object sender, CacheabilityUpdatedEventArgs e)
{
-#if TARGET_J2EE //naive implementation
- Request.Cookies.Add(cookie);
-#endif
+ if (e.Cacheability >= HttpCacheability.Server && !IsCached)
+ cached_response = new CachedRawResponse (cache_policy);
+ else if (e.Cacheability <= HttpCacheability.Private)
+ cached_response = null;
}
- [MonoTODO("Do we need this?")]
- internal void OnCookieChange (HttpCookie cookie)
+ internal CachedRawResponse GetCachedResponse ()
{
+ cached_response.StatusCode = StatusCode;
+ cached_response.StatusDescription = StatusDescription;
+ return cached_response;
}
- [MonoTODO()]
- internal void GoingToChangeCookieColl ()
+ //
+ // 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, CultureInfo.InvariantCulture) == 0) {
+ Cache.SetCacheability (HttpCacheability.Public);
+ user_cache_control = "public";
+ } else if (String.Compare (value, "private", true, CultureInfo.InvariantCulture) == 0) {
+ Cache.SetCacheability (HttpCacheability.Private);
+ user_cache_control = "private";
+ } else if (String.Compare (value, "no-cache", true, CultureInfo.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 ();
}
- [MonoTODO()]
- internal void ChangedCookieColl ()
+ internal void ReleaseResources ()
{
+ output_stream.ReleaseResources (true);
+ output_stream = null;
}
}
}
+