Question

I'm using the Yahoo Uploader, part of the Yahoo UI Library, on my ASP.Net website to allow users to upload files. For those unfamiliar, the uploader works by using a Flash applet to give me more control over the FileOpen dialog. I can specify a filter for file types, allow multiple files to be selected, etc. It's great, but it has the following documented limitation:

Because of a known Flash bug, the Uploader running in Firefox in Windows does not send the correct cookies with the upload; instead of sending Firefox cookies, it sends Internet Explorer’s cookies for the respective domain. As a workaround, we suggest either using a cookieless upload method or appending document.cookie to the upload request.

So, if a user is using Firefox, I can't rely on cookies to persist their session when they upload a file. I need their session because I need to know who they are! As a workaround, I'm using the Application object thusly:

Guid UploadID = Guid.NewGuid();
Application.Add(Guid.ToString(), User);

So, I'm creating a unique ID and using it as a key to store the Page.User object in the Application scope. I include that ID as a variable in the POST when the file is uploaded. Then, in the handler that accepts the file upload, I grab the User object thusly:

IPrincipal User = (IPrincipal)Application[Request.Form["uploadid"]];

This actually works, but it has two glaring drawbacks:

  • If IIS, the app pool, or even just the application is restarted between the time the user visits the upload page, and actually uploads a file, their "uploadid" is deleted from application scope and the upload fails because I can't authenticate them.

  • If I ever scale to a web farm (possibly even a web garden) scenario, this will completely break. I might not be worried, except I do plan on scaling this app in the future.

Does anyone have a better way? Is there a way for me to pass the actual ASP.Net session ID in a POST variable, then use that ID at the other end to retrieve the session?

I know I can get the session ID through Session.SessionID, and I know how to use YUI to post it to the next page. What I don't know is how to use that SessionID to grab the session from the state server.

Yes, I'm using a state server to store the sessions, so they persist application/IIS restarts, and will work in a web farm scenario.

Was it helpful?

Solution

Here is a post from the maintainer of SWFUpload which explains how to load the session from an ID stored in Request.Form. I imagine the same thing would work for the Yahoo component.

Note the security disclaimers at the bottom of the post.


By including a Global.asax file and the following code you can override the missing Session ID cookie:

using System;
using System.Web;

public class Global_asax : System.Web.HttpApplication
{
    private void Application_BeginRequest(object sender, EventArgs e)
    {
        /* 
        Fix for the Flash Player Cookie bug in Non-IE browsers.
        Since Flash Player always sends the IE cookies even in FireFox
        we have to bypass the cookies by sending the values as part of the POST or GET
        and overwrite the cookies with the passed in values.

        The theory is that at this point (BeginRequest) the cookies have not been ready by
        the Session and Authentication logic and if we update the cookies here we'll get our
        Session and Authentication restored correctly
        */

        HttpRequest request = HttpContext.Current.Request;

        try
        {
            string sessionParamName = "ASPSESSID";
            string sessionCookieName = "ASP.NET_SESSIONID";

            string sessionValue = request.Form[sessionParamName] ?? request.QueryString[sessionParamName];
            if (sessionValue != null)
            {
                UpdateCookie(sessionCookieName, sessionValue);
            }
        }
        catch (Exception ex)
        {
            // TODO: Add logging here.
        }

        try
        {
            string authParamName = "AUTHID";
            string authCookieName = FormsAuthentication.FormsCookieName;

            string authValue = request.Form[authParamName] ?? request.QueryString[authParamName];
            if (authValue != null)
            {
                UpdateCookie(authCookieName, authValue);
            }
        }
        catch (Exception ex)
        {
            // TODO: Add logging here.
        }
    }

    private void UpdateCookie(string cookieName, string cookieValue)
    {
        HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(cookieName);
        if (cookie == null)
        {
            HttpCookie newCookie = new HttpCookie(cookieName, cookieValue);
            Response.Cookies.Add(newCookie);
        }
        else
        {
            cookie.Value = cookieValue;
            HttpContext.Current.Request.Cookies.Set(cookie);
        }
    }
}

Security Warning: Don't just copy and paste this code in to your ASP.Net application without knowing what you are doing. It introduces security issues and possibilities of Cross-site Scripting.

OTHER TIPS

You can get your current SessionID from the following code:

string sessionId = HttpContext.Current.Session.SessionID;

Then you can feed that into a hidden field maybe and then access that value through YUI.

It's just a get, so you hopefully won't have any scaling problems. Security-problems though, that I don't know.

Relying on this blog post, here's a function that should get you the session for any user based on the session ID, though it's not pretty:

public SessionStateStoreData GetSessionById(string sessionId)
{
    HttpApplication httpApplication = HttpContext.ApplicationInstance;

    // Black magic #1: getting to SessionStateModule
    HttpModuleCollection httpModuleCollection = httpApplication.Modules;
    SessionStateModule sessionHttpModule = httpModuleCollection["Session"] as SessionStateModule;
    if (sessionHttpModule == null)
    {
        // Couldn't find Session module
        return null;
    }

    // Black magic #2: getting to SessionStateStoreProviderBase through reflection
    FieldInfo fieldInfo = typeof(SessionStateModule).GetField("_store", BindingFlags.NonPublic | BindingFlags.Instance);
    SessionStateStoreProviderBase sessionStateStoreProviderBase = fieldInfo.GetValue(sessionHttpModule) as SessionStateStoreProviderBase;
    if (sessionStateStoreProviderBase == null)
    {
        // Couldn't find sessionStateStoreProviderBase
        return null;
    }

    // Black magic #3: generating dummy HttpContext out of the thin air. sessionStateStoreProviderBase.GetItem in #4 needs it.
    SimpleWorkerRequest request = new SimpleWorkerRequest("dummy.html", null, new StringWriter());
    HttpContext context = new HttpContext(request);

    // Black magic #4: using sessionStateStoreProviderBase.GetItem to fetch the data from session with given Id.
    bool locked;
    TimeSpan lockAge;
    object lockId;
    SessionStateActions actions;
    SessionStateStoreData sessionStateStoreData = sessionStateStoreProviderBase.GetItem(
        context, sessionId, out locked, out lockAge, out lockId, out actions);
    return sessionStateStoreData;
}

The ASP.Net Session ID is stored in Session.SessionID so you could set that in a hidden field and then post it to the next page.

I think, however, that if the application restarts, the sessionID will expire if you do not store your sessions in sql server.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top