#region License

// Dimebrain TweetSharp
// (www.dimebrain.com)
//
// The MIT License
//
// Copyright (c) 2009 - 2010 Dimebrain
//
// 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.
//

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Web;

namespace Demo.Silverlight.Web.Modules
{
/// <summary>
/// An <see cref="IHttpModule" /> that can forward TweetSharp queries from
/// Silverlight to Twitter and return the results.
/// </summary>
public class TweetSharpProxyModule : IHttpModule
{
// Request Headers
private const string ProxyAcceptHeader = "X-Twitter-Accept";
private const string ProxyAgentHeader = "X-Twitter-Agent";
private const string ProxyAuthorizationHeader = "X-Twitter-Auth";
private const string ProxyMethodHeader = "X-Twitter-Method";
private const string ProxyQueryHeader = "X-Twitter-Query";

// Response Headers
private const string ProxyStatusCode = "X-Twitter-StatusCode";
private const string ProxyStatusDescription = "X-Twitter-StatusDescription";

private static readonly ICollection<string>
_skipHeaders
= new[]
{
"Connection",
"Keep-Alive",
"Accept",
"Host",
"User-Agent",
"Content-Length",
"Content-Type",
"Accept-Encoding",
"Authorization",
"Referer",
ProxyMethodHeader,
ProxyAuthorizationHeader,
ProxyAcceptHeader,
ProxyAgentHeader,
ProxyQueryHeader
};

#region IHttpModule Members

public void Dispose()
{

}

public void Init(HttpApplication context)
{
context.BeginRequest += ContextBeginRequest;
}

#endregion

static void ContextBeginRequest(object sender, EventArgs e)
{
var context = (HttpApplication)sender;
var request = context.Request;

var path = request.Path.ToLowerInvariant();
if (!path.StartsWith("/proxy/"))
{
return;
}

var url = context.Request.Headers[ProxyQueryHeader];

Trace.WriteLine(String.Format("Proxy request handled for {0} from {1}.",
request.Path,
request.UserHostAddress));

string response;

try
{
// Set stock properties for the proxied call
var proxyRequest = (HttpWebRequest)WebRequest.Create(url);

// Set the HTTP method, preferring a proxy header
var methodOverride = request.Headers[ProxyMethodHeader];
proxyRequest.Method = methodOverride != null
? request.Headers[ProxyMethodHeader]
: request.HttpMethod;

// Set the user agent, preferring a proxy header
var userAgent = request.Headers[ProxyAgentHeader];
proxyRequest.UserAgent = userAgent != null
? request.Headers[ProxyAgentHeader]
: request.UserAgent;

// Set compression support, preferring a proxy header
// Use compression if indicated
var acceptEncoding =
request.Headers[ProxyAcceptHeader] ??
request.Headers["Accept-Encoding"];

if (acceptEncoding != null)
{
acceptEncoding = acceptEncoding.ToLower();
if (acceptEncoding.Contains("gzip"))
{
proxyRequest.AutomaticDecompression =
DecompressionMethods.GZip;
}
else if (acceptEncoding.Contains("deflate"))
{
proxyRequest.AutomaticDecompression =
DecompressionMethods.Deflate;
}
}

proxyRequest.ContentType = request.ContentType;
proxyRequest.ContentLength = request.ContentLength;

// Set referer, preferring a proxy header
proxyRequest.Referer = request.UrlReferrer != null
? request.UrlReferrer.ToString()
: null;
proxyRequest.KeepAlive = true;

if (request.Headers[ProxyAuthorizationHeader] != null)
{
proxyRequest.Headers["Authorization"] =
request.Headers[ProxyAuthorizationHeader];
}

foreach (var header in request.Headers.Keys)
{
var name = header.ToString();
if (_skipHeaders.Contains(name))
{
continue;
}

var value = request.Headers[name];
proxyRequest.Headers[name] = value;
}

var stream = context.Request.InputStream;
var content = new byte[context.Request.InputStream.Length];
stream.Read(content, 0, (int)context.Request.InputStream.Length);

response = ProxyRequest(proxyRequest, content);
}
catch (WebException ex)
{
response = HandleWebException(ex);
if (ex.Response is HttpWebResponse)
{
var http = (HttpWebResponse)ex.Response;

// [DC]: AddHeader required for IIS Classic Mode
context.Response.AddHeader(ProxyStatusCode, ((int)http.StatusCode).ToString());
context.Response.AddHeader(ProxyStatusDescription, http.StatusDescription);
}
}

context.Response.ClearContent();
context.Response.Write(response);
context.Response.End(); // calls flush
}

private static string ProxyRequest(WebRequest proxyRequest, byte[] content)
{
switch(proxyRequest.Method)
{
case "GET":
case "DELETE":
using (var response = proxyRequest.GetResponse())
{
try
{
var stream = response.GetResponseStream();
using (var reader = new StreamReader(stream))
{
var results = reader.ReadToEnd();

return results;
}
}
catch (WebException ex)
{
return HandleWebException(ex);
}
}
case "POST":
case "PUT":
try
{
using (var stream = proxyRequest.GetRequestStream())
{
stream.Write(content, 0, content.Length);
stream.Close();

using (var response = proxyRequest.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
var result = reader.ReadToEnd();

return result;
}
}
}
}
catch (WebException ex)
{
return HandleWebException(ex);
}
default:
throw new NotSupportedException();
}
}

private static string HandleWebException(WebException ex)
{
if (ex.Response is HttpWebResponse && ex.Response != null)
{
using (var reader = new StreamReader(ex.Response.GetResponseStream()))
{
var result = reader.ReadToEnd();

return result;
}
}

throw ex;
}
}
}