// System.Web.HttpResponse
//
// Authors:
-// Patrik Torstensson (Patrik.Torstensson@labs2.com)
+// Patrik Torstensson (Patrik.Torstensson@labs2.com)
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (c) 2002 Ximian, Inc. (http://www.ximian.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;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Text;
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 = { 10, 13 };
- static byte [] s_arrChunkEnd = { 10 , 13 };
+ static byte [] s_arrChunkSuffix = {13, 10};
+ static byte [] s_arrChunkEnd = {48, 13, 10, 13, 10};
static string s_sChunkedPrefix = "\r\n";
ArrayList _Headers;
bool _bBuffering;
bool _bHeadersSent;
bool _bFlushing;
+ bool filtered;
long _lContentLength;
int _iStatusCode;
+
+ int _expiresInMinutes;
+ bool _expiresInMinutesSet;
+ DateTime _expiresAbsolute;
+ bool _expiresAbsoluteSet;
bool _ClientDisconnected;
+ bool closed;
string _sContentType;
string _sCacheControl;
HttpWorkerRequest _WorkerRequest;
+ ArrayList fileDependencies;
+ CachedRawResponse cached_response;
+ ArrayList cached_headers;
+#if NET_1_1
+ string redirectLocation;
+#endif
+
+ string app_path_mod = null;
+
public HttpResponse (TextWriter output)
{
_bBuffering = true;
_bClientDisconnected = false;
_bChunked = false;
-
- _Writer = new HttpWriter (this);
- _TextWriter = _Writer;
}
- internal Encoder ContentEncoder
+ internal void InitializeWriter ()
{
- get {
- return ContentEncoding.GetEncoder ();
+ // 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 ()
{
Flush (true);
}
- internal void DoFilter ()
+ internal void DoFilter (bool really)
{
- if (null != _Writer)
+ if (really && null != _Writer)
_Writer.FilterData (true);
+
+ filtered = true;
+ }
+
+ internal bool IsCached {
+ get { return cached_response != null; }
}
- [MonoTODO("We need to add cache headers also")]
+ internal CachedRawResponse GetCachedResponse () {
+ cached_response.StatusCode = StatusCode;
+ cached_response.StatusDescription = StatusDescription;
+ return cached_response;
+ }
+
+ internal void SetCachedHeaders (ArrayList headers)
+ {
+ cached_headers = headers;
+ }
+
private ArrayList GenerateHeaders ()
{
ArrayList oHeaders = new ArrayList (_Headers.ToArray ());
+ oHeaders.Add (new HttpResponseHeader ("X-Powered-By", "Mono"));
// save culture info, we need us info here
CultureInfo oSavedInfo = Thread.CurrentThread.CurrentCulture;
- Thread.CurrentThread.CurrentCulture = new CultureInfo (0x0409);
+ Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
- string date = DateTime.Now.ToUniversalTime ().ToString ("ddd, d MMM yyyy HH:mm:ss");
- oHeaders.Add (new HttpResponseHeader ("Date", date + "GMT"));
+ string date = DateTime.UtcNow.ToString ("ddd, d MMM yyyy HH:mm:ss ");
+ HttpResponseHeader date_header = new HttpResponseHeader ("Date", date + "GMT");
+ oHeaders.Add (date_header);
+
+ if (IsCached)
+ cached_response.DateHeader = date_header;
Thread.CurrentThread.CurrentCulture = oSavedInfo;
_sContentType));
}
+ if (_CachePolicy != null)
+ _CachePolicy.SetHeaders (this, oHeaders);
+
if (_sCacheControl != null) {
oHeaders.Add (new HttpResponseHeader (HttpWorkerRequest.HeaderPragma,
_sCacheControl));
_sTransferEncoding));
}
- // TODO: Add Cookie headers..
-
+ 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;
}
-
+
private void SendHeaders ()
{
_WorkerRequest.SendStatus (StatusCode, StatusDescription);
- ArrayList oHeaders = GenerateHeaders ();
+ 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);
throw new NotImplementedException ();
}
- [MonoTODO()]
public void AddFileDependencies (ArrayList filenames)
{
- //throw new NotImplementedException();
+ if (filenames == null || filenames.Count == 0)
+ return;
+
+ if (fileDependencies == null) {
+ fileDependencies = (ArrayList) filenames.Clone ();
+ return;
+ }
+
+ foreach (string fn in filenames)
+ AddFileDependency (fn);
}
- [MonoTODO()]
public void AddFileDependency (string filename)
{
- //throw new NotImplementedException();
+ if (fileDependencies == null)
+ fileDependencies = new ArrayList ();
+
+ fileDependencies.Add (filename);
}
public void AddHeader (string name, string value)
AppendHeader(name, value);
}
- [MonoTODO()]
public void AppendCookie (HttpCookie cookie)
{
- throw new NotImplementedException ();
+ if (_bHeadersSent)
+ throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
+
+ Cookies.Add (cookie);
}
[MonoTODO()]
throw new NotImplementedException ();
}
- [MonoTODO()]
public string ApplyAppPathModifier (string virtualPath)
{
- throw new NotImplementedException ();
+ 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);
+ }
+
+ if (app_path_mod != null && virtualPath.IndexOf (app_path_mod) < 0) {
+ string rvd = _Context.Request.RootVirtualDir;
+ string basevd = rvd.Replace (app_path_mod, "");
+
+ if (!virtualPath.StartsWith (basevd))
+ return virtualPath;
+
+ virtualPath = UrlUtils.Combine (rvd, virtualPath.Substring (basevd.Length));
+ }
+
+ return virtualPath;
}
+ internal void SetAppPathModifier (string app_path_mod)
+ {
+ this.app_path_mod = app_path_mod;
+ }
+
public bool Buffer
{
get {
public HttpCachePolicy Cache
{
get {
- if (null == _CachePolicy)
+ if (null == _CachePolicy) {
_CachePolicy = new HttpCachePolicy ();
+ _CachePolicy.CacheabilityUpdated += new CacheabilityUpdatedCallback (
+ OnCacheabilityUpdated);
+ }
return _CachePolicy;
}
}
+ 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
{
{
get {
if (_ContentEncoding == null)
- _ContentEncoding = Encoding.UTF8;
+ _ContentEncoding = WebEncoding.ResponseEncoding;
return _ContentEncoding;
}
set {
if (value == null)
- throw new ArgumentException ("Can't set a null as encoding");
+ throw new ArgumentNullException ("Can't set a null as encoding");
_ContentEncoding = value;
}
}
- [MonoTODO("Set expires in the cache policy")]
public int Expires
{
get {
- throw new NotImplementedException ();
+ return _expiresInMinutes;
}
set {
- throw new NotImplementedException ();
+ if (!_expiresInMinutesSet || (value < _expiresInMinutes))
+ {
+ _expiresInMinutes = value;
+ Cache.SetExpires(_Context.Timestamp.Add(new TimeSpan(0, _expiresInMinutes, 0)));
+ }
+ _expiresInMinutesSet = true;
}
}
- [MonoTODO("Set expiresabsolute in the cache policy")]
public DateTime ExpiresAbsolute
{
get {
- throw new NotImplementedException ();
+ return _expiresAbsolute;
}
set {
- throw new NotImplementedException ();
+ if (!_expiresAbsoluteSet || value.CompareTo(_expiresAbsolute)<0)
+ {
+ _expiresAbsolute = value;
+ Cache.SetExpires(_expiresAbsolute);
+ }
+ _expiresAbsoluteSet = true;
}
}
}
}
+#if NET_1_1
+ public string RedirectLocation {
+ get { return redirectLocation; }
+ set { redirectLocation = value; }
+ }
+#endif
+
public string StatusDescription
{
get {
}
set {
- if (_bHeadersSent)
- throw new HttpException ("Headers has been sent to the client");
-
_bSuppressContent = true;
}
}
- public HttpRequest Request
+ HttpRequest Request
{
get {
+ if (_Context == null)
+ return null;
+
return _Context.Request;
}
}
break;
case HttpWorkerRequest.HeaderTransferEncoding:
_sTransferEncoding = value;
- if (value.Equals ("chunked")) {
- _bChunked = true;
- } else {
- _bChunked = false;
- }
+ _bChunked = (value == "chunked");
break;
case HttpWorkerRequest.HeaderPragma:
_sCacheControl = value;
break;
case "transfer-encoding":
_sTransferEncoding = value;
- if (value.Equals ("chunked")) {
- _bChunked = true;
- } else {
- _bChunked = false;
- }
+ _bChunked = (value == "chunked");
break;
case "pragma":
_sCacheControl = value;
}
}
+ internal TextWriter SetTextWriter (TextWriter w)
+ {
+ TextWriter prev = _TextWriter;
+ _TextWriter = w;
+ return prev;
+ }
+
public void BinaryWrite (byte [] buffer)
{
OutputStream.Write (buffer, 0, buffer.Length);
}
+ internal void BinaryWrite (byte [] buffer, int start, int length)
+ {
+ OutputStream.Write (buffer, start, length);
+ }
+
public void Clear ()
{
if (_Writer != null)
Clear();
}
+ internal void SetHeadersSent (bool val)
+ {
+ _bHeadersSent = val;
+ }
+
public void ClearHeaders ()
{
if (_bHeadersSent)
public void Close ()
{
- _bClientDisconnected = false;
- _WorkerRequest.CloseConnection ();
- _bClientDisconnected = true;
+ if (closed && !_bClientDisconnected) {
+ _bClientDisconnected = false;
+ _WorkerRequest.CloseConnection ();
+ _bClientDisconnected = true;
+ }
}
internal void Dispose ()
Flush (true);
}
- [MonoTODO("Check timeout and if we can cancel the thread...")]
public void End ()
{
- if (!_bEnded) {
- Flush ();
- _WorkerRequest.CloseConnection ();
- _bEnded = true;
- }
+ if (_bEnded)
+ return;
+
+ if (_Context.TimeoutPossible)
+ Thread.CurrentThread.Abort (new StepCompleteRequest ());
+
+ Flush ();
+ _bEnded = true;
+ _Context.ApplicationInstance.CompleteRequest ();
}
public void Flush ()
{
+ if (closed)
+ throw new HttpException ("Response already finished.");
+
Flush (false);
}
private void Flush (bool bFinish)
{
- if (_bFlushing)
+ if (_bFlushing || closed)
return;
_bFlushing = true;
- if (_Writer != null) {
- _Writer.FlushBuffers ();
- } else {
+ if (_Writer == null) {
_TextWriter.Flush ();
+ _bFlushing = false;
+ return;
}
try {
- if (!_bHeadersSent && !_bSuppressHeaders && !_bClientDisconnected) {
- if (_Writer != null && BufferOutput) {
- _lContentLength = _Writer.BufferSize;
- } else {
- _lContentLength = 0;
- }
+ if (_bClientDisconnected)
+ return;
+
+ long length = _Writer.BufferSize;
+ if (!_bHeadersSent && !_bSuppressHeaders) {
+ if (bFinish) {
+ if (length == 0 && _lContentLength == 0)
+ _sContentType = null;
- 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 sends a HTTP/1.0
- // request with Connection: Keep-Alive
- AppendHeader (
- HttpWorkerRequest.HeaderConnection,
- "Close");
+ SendHeaders ();
+ length = _Writer.BufferSize;
+ if (length != 0)
+ _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 ((!_bSuppressContent && Request.HttpMethod == "HEAD") || _Writer == null) {
- _bSuppressContent = true;
+
+ if (!filtered) {
+ _Writer.FilterData (false);
+ length = _Writer.BufferSize;
}
+ if (length == 0) {
+ if (bFinish && _bChunked) {
+ _WorkerRequest.SendResponseFromMemory (s_arrChunkEnd,
+ s_arrChunkEnd.Length);
+ }
+
+ _WorkerRequest.FlushResponse (bFinish);
+ if (!bFinish)
+ _Writer.Clear ();
+ return;
+ }
+
+ if (!_bSuppressContent && Request.HttpMethod == "HEAD")
+ _bSuppressContent = true;
+
+ if (_bSuppressContent)
+ _Writer.Clear ();
+
if (!_bSuppressContent) {
_bClientDisconnected = false;
if (_bChunked) {
} else {
_Writer.SendContent (_WorkerRequest);
}
+ }
- _WorkerRequest.FlushResponse (bFinish);
-
- if (!bFinish)
- _Writer.Clear ();
+ _WorkerRequest.FlushResponse (bFinish);
+ if (IsCached) {
+ cached_response.ContentLength = (int) length;
+ cached_response.SetData (_Writer.GetBuffer ());
}
+ _Writer.Clear ();
} finally {
+ if (bFinish)
+ closed = true;
_bFlushing = false;
}
}
Redirect (url, true);
}
- //FIXME: [1] this is an ugly hack to make it work until we have SimpleWorkerRequest!
- private string redirectLocation;
- public string RedirectLocation
- {
- get {
- return redirectLocation;
- }
- }
-
public void Redirect (string url, bool endResponse)
{
if (_bHeadersSent)
Clear ();
+ url = ApplyAppPathModifier (url);
StatusCode = 302;
- redirectLocation = url;
- //[1]AppendHeader(HttpWorkerRequest.HeaderLocation, url);
+ AppendHeader (HttpWorkerRequest.HeaderLocation, 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");
- /* [1]
- if (endResponse) {
- End();
- }
- */
+ if (endResponse)
+ End ();
}
+ internal bool RedirectCustomError (string errorPage)
+ {
+ if (_bHeadersSent)
+ return false;
+
+ if (Request.QueryString ["aspxerrorpath"] != null)
+ return false; // Prevent endless loop
+
+ Redirect (errorPage + "?aspxerrorpath=" + Request.Path, false);
+ return true;
+ }
+
public void Write (char ch)
{
_TextWriter.Write(ch);
_TextWriter.Write (buffer, index, count);
}
- [MonoTODO()]
public static void RemoveOutputCacheItem (string path)
{
- throw new NotImplementedException ();
+ 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.");
+
+ Cache cache = HttpRuntime.Cache;
+ cache.Remove (path);
}
- [MonoTODO()]
public void SetCookie (HttpCookie cookie)
{
- throw new NotImplementedException ();
+ if (_bHeadersSent)
+ throw new HttpException ("Cannot append cookies after HTTP headers have been sent");
+
+ Cookies.Add (cookie);
}
private void WriteFromStream (Stream stream, long offset, long length, long bufsize)
}
}
}
-