tags:

views:

196

answers:

5

This sounds like it should be a common problem but I can find little solutions to it.

Basically relative links on a https page (often found within controls and the master page) will obviously link to non-secure pages as https which is undesirable.

A possible solution was to use the base tag in the head of the page to root all relative pats to http but then any relative resources within the secure page will be rooted to http which is also undesirable.

I thought about overriding the render method and if it a secure page then rewrite all relative links on the page to be http.

How should it be done?

A: 

You mean all absolute URL's? The answer is you don't use absolute urls. You use relative ones (/foo instead of http://example.org/foo).

Noon Silk
Perhaps you could have a page: `leavessl.aspx?out=foopage`, then you can change protocols there (make sure to validate the data passed to the 'out' param as valid pages).
Noon Silk
Weird, you seem to have deleted your comment. Feel free to bring it back; the formatter just messed up your links, I understood what you meant.
Noon Silk
Defiantly an option but performing redirects on every link on an ssl page seems a bit heavy handed to me. I would rather the links are rendered correctly in the first place. I thought this would be a common issue
Sheff
sorry I was trying to add context ass the comment formatted the links I added I was trying to get it to render them a literal string rather than link in the comment - My bad
Sheff
But it's not on every single link; it's only the first link to transfer them out. IMHO, I think it's not great form to be leaving SSL when you get in it, but that's another story.
Noon Silk
If you dont leave ssl when you are in an ssl page the once a user has visited an ssl page they will browse the whole site in ssl from relative links. This will have performance issues will it not? I have to admit I'm not sure what the best practice is. I can understand your point about bad form. Do you keep a user within ssl then once an ssl page has been visited?
Sheff
I do, yes. But it's also bad for performance, certainly. The argument is that anything that they are now seeing is worth protecting over SSL. Though it doesn't really hold so true. A security professional will also say that any link to the page they signed up needed to also be in SSL, otherwise how can you trust it? Then you get to secure DNS and it all falls apart. In summary: Don't worry about doing that. Get them out after authentication and it's 'probably' fine. You may research your own opinions though.
Noon Silk
A: 

One way is to implement e.g. an IHttpModule which checks incoming requests, decides from the URL whether they should be on http: or https:, and if the request is using the "wrong" protocol, issues a redirect to use the "right" protocol.

Update: I found another question where the answers give more details on the pluses and minuses of this approach. Here's an interesting link from that thread.

Vinay Sajip
hmmmm. After your update I tried to upvote your answer and because I have already voted it voted down. Now if I try to vote again it says the vote is too old to be changed. Seems like a bit if a SO usability issue there. Sorry about that.With regards to your answer I'm not sure I want to go down the redirect route but is useful info anyway.
Sheff
A: 

I tend to override the System.Web.UI.Page class and add an IsSecure attribute. You can then set this in the Page directive:

<%@ Page Language="C#" MasterPageFile="~/Default.Master" AutoEventWireup="true" 
CodeBehind="Foo.aspx.cs" Inherits="Bar.Foo" IsSecure="true" %>

In your own page class you can then override OnPreInit to do the redirect using something like this:

if (IsSecure && Request.Url.Scheme == "http")
{
    string targetUrl = String.Format(
                           "https://{0}{1}",
                           Request.Url.Host,
                           Request.RawUrl);
    Response.Redirect(targetUrl);
}
else if (!IsSecure && Request.Url.Scheme == "https")
{
    string targetUrl = String.Format(
                           "http://{0}{1}", 
                           Request.Url.Host, 
                           Request.RawUrl);
    Response.Redirect(targetUrl);
}

All of your pages will need to inherit from your own page class rather than the one in System.Web.UI, but once that's sorted you're good to go.

P.S. All of this code is from memory, so may be flaky (I can never remember if the application directory is included in RawUrl, for example) but the concept is there.

Pike65
The problem with this is that it doesn't cover non-Page resources, such as images or other static files.
Vinay Sajip
Vinay: You'll get security popups if you have insecure images accessed from a secure page.
Noon Silk
This was an option I was actually considering just trying to get a consensus on what the best practice is. I was worried this was a little heavy handed as an approach?
Sheff
It's true that this is a fairly blunt instrument approach, but in our case it was very rare that a user would be switching between secure and non-secure pages. TBH I'm curious to see what other options people bring up too.
Pike65
@silky: But the question is asking how to show an SSL page containing links to non-SSL content. It's true you get warnings, but most people turn them off after the first few occurrences (as there are *lots* of sites where the error gets displayed, otherwise).
Vinay Sajip
Well, Request.IsSecureConnection is not directly related to SSL. If you are using invalid SSL certificates this will be false. Better reliability gives Request.ServerVariables["HTTPS"].ToLower() == "on"
Marc Wittke
Ah, I didn't know that. Edited to use Request.Url.Scheme instead.
Pike65
A: 

Ok I have implemented a solution that rewrites relative links if on a ssl page by overriding the render method. I take the point silky made about it being bad form to redirect away from ssl, but I also think its bad form to use ssl on pages that don't post user information.

I would still be intereseted to hear how others deal with this problem.

Anyway heres my solution. I use HtmlAgilityPack to parse the html but you could use a regex if you prefer.

protected override void Render(System.Web.UI.HtmlTextWriter writer)
    {
        if (HttpContext.Current.Request.Url.Scheme == "https")
        {
            StringBuilder stringBuilder = new StringBuilder();
            HtmlTextWriter pageTextWriter = new HtmlTextWriter(new StringWriter(stringBuilder, System.Globalization.CultureInfo.InvariantCulture));
            base.Render(pageTextWriter);

            HtmlAgilityPack.HtmlDocument htmldoc = new HtmlAgilityPackHtmlDocument();
            htmldoc.LoadHtml(stringBuilder.ToString());
            HtmlAgilityPack.HtmlNodeCollection linkNodes = htmldoc.DocumentNode.SelectNodes("//a[@href]");
            if (linkNodes != null)
            {
                Uri baseUri = new Uri("http://www.mynonsslwebroot.co.uk/");
                foreach (HtmlAgilityPack.HtmlNode node in linkNodes)
                {
                    Uri uri;

                    if (Uri.TryCreate(baseUri, node.Attributes["href"].Value, out uri))
                    {
                        node.Attributes["href"].Value = uri.OriginalString;
                    }
                }
                writer.Write(htmldoc.DocumentNode.WriteTo());
            }
        }
        else
        {
            base.Render(writer);
        }
    }
Sheff
+1  A: 

Keep in mind the existence of protocol-independent absolute URLs (e.g. //example.com/images/artwork.jpg). Such a URL will be an https URL IFF the current base page is an https page.

Brian