At my day job, I’m responsible for bringing our various web products up to modern standards.  Right now, that involves pulling our old website off a static, table-based system built with FrontPage and executing a new design using ASP.NET MVC 3.

It’s been a fun project so far, except for one major hang-up.

We have a legacy system our customers use to download content from our site.  The legacy system is a CGI script on the server that handles rudimentary authentication and presents a list of user-specific downloadable files.  Simple, straight-forward, but something we want to throw out entirely.

My next project will be to re-build this system from the ground up.  But for now, I needed to carry over the old content into the new website.  And this presented problems.

Domain Issues

First of all, the download system is hosted on the same domain as the website.  When we launch the new site, though, we’re moving it to a new server entirely – transitioning from a shared host somewhere to a VPS.  But we’re leaving the download system on the old host.

This means the new site needs to reference the old system by its IP address.  Not a problem for file download links; a huge problem for an iFrame.

The download system has the CSS and table-based layout of the old site hard-coded into the CGI.  When you view it through an iFrame on the new site, you see a page-within-a-page that looks, frankly, butt ugly.

If both pages were being served off the same domain, I could use jQuery to inject some styling into the iFrame and clean up the content.  With the new site on the real domain and the old download system on a naked IP address, though, no browser will let me do that.

They all scream about cross-site scripting vulnerabilities and throw out my code.

Proxy Solution

Instead of loading the remote site into an iFrame, I load a proxy from the same server.  Essentially, a script on the new server fetches the content from the old site and re-displays it.  This gets around the same-domain policy issue I had before and allows me to remove superfluous table designs and add new colors and images to the styling.

[cc lang="c#" width="580"]
public partial class Proxy : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var proxyURL = String.Empty;
try
{
proxyURL = HttpUtility.UrlDecode(Request.QueryString["u"].ToString());
}
catch { }

if (proxyURL != string.Empty)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(proxyURL);
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

if (response.StatusCode.ToString().ToLower() == "ok")
{
var contentType = response.ContentType;
var content = response.GetResponseStream();
var contentReader = new StreamReader(content);
Response.ContentType = contentType;
Response.Write(contentReader.ReadToEnd());
}
}
}
}
[/cc]

Unfortunately, the embedded HTML generated by the CGI script is still submitting form callbacks to the old server … and breaking the iFrame in the process.

Enter jQuery AJAX

The final step to cleaning things up was to add a jQuery listener to the iFrame that intercepts all form clicks, postbacks, and any event that would track back to the old system and instead pass it on to the proxy file.

My proxy file essentially has two actions: “login” and default.  The default action requests the raw content from the old CGI file and delivers our user’s login form to the screen.  When a user enters information, it’s sent back to the proxy file via jQuery and invokes the “login” action.

[cc lang="javascript" width="580"]
var iframe = $('#downloadcenter');

iframe.contents().find(':submit').click(function (e) {
e.preventDefault();

var user = iframe.contents().find('input[name=Username]').val(),
pass = iframe.contents().find('input[name=Password]').val();

$.ajax({
url: '/Proxy.aspx?action=login&user=' + user + '&pass=' + pass,
type: 'POST',
success: function (data) {
$('#downloadcenter').contents().find('body').html(data);
}
});
});

[/cc]

The proxy file passes the user’s credentials on to the CGI file and returns any response (login failure or success) to the screen.

[cc lang="c#" width="580"]
public partial class Proxy : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var proxyURL = ConfigurationManager.AppSettings["DownloadCenterURI"];
var action = String.Empty;
try
{
action = HttpUtility.UrlDecode(Request.QueryString["action"].ToString());
}
catch { }

HttpWebRequest request;
HttpWebResponse response;

switch (action)
{
// Log in a user
case "login":
var user = HttpUtility.UrlDecode(Request.QueryString["user"].ToString());
var pass = HttpUtility.UrlDecode(Request.QueryString["pass"].ToString());

if (user.IsNullOrEmpty() || pass.IsNullOrEmpty()) goto default;

request = (HttpWebRequest)WebRequest.Create(proxyURL);
request.Method = "POST";

var dataString = "Username=" + user + "&Password=" + pass;
var data = (new System.Text.ASCIIEncoding()).GetBytes(dataString);

request.ContentType = "application/x-www-form-encoded";
request.ContentLength = data.Length;

var dataStream = request.GetRequestStream();
dataStream.Write(data, 0, data.Length);
dataStream.Close();

response = (HttpWebResponse)request.GetResponse();

if (response.StatusCode.ToString().ToLower() == "ok")
{
var contentType = response.ContentType;
var content = response.GetResponseStream();
var contentReader = new StreamReader(content);
Response.ContentType = contentType;
Response.Write(contentReader.ReadToEnd());
}

break;
// Present the login screen
default:
request = (HttpWebRequest)WebRequest.Create(proxyURL);
request.Method = "GET";

response = (HttpWebResponse)request.GetResponse();

if (response.StatusCode.ToString().ToLower() == "ok")
{
var contentType = response.ContentType;
var content = response.GetResponseStream();
var contentReader = new StreamReader(content);
Response.ContentType = contentType;
Response.Write(contentReader.ReadToEnd());
}
break;
}
}
}

[/cc]

It’s a nice trick that solves a major problem we were having with the new website.  Originally, we pushed our release schedule back by about a month so I could rebuild the download management system.  With the new embedded iFrame, we can launch the new site without worrying about a download overhaul just yet.

But it will be coming.  I guarantee that much.