- Home /
Workaround for SET-COOKIE bug in www.responseHeaders?
There is a bug in the way Unity handles the HTTP response headers. A typical HTTP response might look like this:
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 19 Oct 2014 19:48:07 GMT
Set-Cookie: session-data=a3izTzllfjmJvIAedFWH8_RF1VUoTVbszM-4KtITK8QBZwE
Set-Cookie: AWSELB=8FCF616716267D5222A365F68CB5F524241ADC40F2A79E054E56BD41E6C10E5921CA9D8BE1291E904BD712CDA6EB211CD74D6780AE1E44EE07A58093B97B9C3AA5121EF17E09420E;PATH=/
transfer-encoding: chunked
Connection: keep-alive
However, since Unity parses this as a Dictionary, only one Set-Cookie header gets parsed. In other words, only one cookie will get through and the others will be ignored / removed.
I submitted a bug report about this. In the meantime, does anyone know of a workaround?
Answer by fractiv · Oct 20, 2014 at 04:32 AM
Answering my own question. Discovered a gross hack that lets me get at the data by using reflection to access the responseHeadersString property of the WWW class, which is a protected member. Below is an extension method class I wrote that provides methods for getting, parsing and sending cookies. Enjoy!
//
// UnityCookies.cs
// by Sam McGrath
//
// Use as you please.
//
// Usage:
// Dictionary<string,string> cookies = www.ParseCookies();
//
// To send cookies in a WWW response:
// var www = new WWW( url, null, UnityCookies.GetCookieRequestHeader(cookies) );
// (if other headers are needed, merge them with the dictionary returned by GetCookieRequestHeader)
//
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
public static class UnityCookies {
public static string GetRawCookieString( this WWW www ) {
if ( !www.responseHeaders.ContainsKey("SET-COOKIE") ) {
return null;
}
// HACK: workaround for Unity bug that doesn't allow multiple SET-COOKIE headers
var rhsPropInfo = typeof(WWW).GetProperty( "responseHeadersString",BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance );
if ( rhsPropInfo == null ) {
Debug.LogError( "www.responseHeadersString not found in WWW class." );
return null;
}
var headersString = rhsPropInfo.GetValue( www, null ) as string;
if ( headersString == null ) {
return null;
}
// concat cookie headers
var allCookies = new StringBuilder();
string[] lines = headersString.Split( new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries );
foreach( var l in lines ) {
var colIdx = l.IndexOf( ':' );
if ( colIdx < 1 ) {
continue;
}
var headerType = l.Substring( 0,colIdx ).Trim();
if ( headerType.ToUpperInvariant() != "SET-COOKIE" ) {
continue;
}
var headerVal = l.Substring( colIdx+1 ).Trim();
if ( allCookies.Length > 0 ) {
allCookies.Append( "; " );
}
allCookies.Append( headerVal );
}
return allCookies.ToString();
}
public static Dictionary<string,string> ParseCookies( this WWW www ) {
return ParseCookies( www.GetRawCookieString() );
}
public static Dictionary<string,string> ParseCookies( string str ) {
// cookie parsing adapted from node.js cookie module, so it should be pretty robust.
var dict = new Dictionary<string,string>();
if ( str != null ) {
var pairs = Regex.Split( str, "; *" );
foreach( var pair in pairs ) {
var eqIdx = pair.IndexOf( '=' );
if ( eqIdx == -1 ) {
continue;
}
var key = pair.Substring( 0,eqIdx ).Trim();
if ( dict.ContainsKey(key) ) {
continue;
}
var val = pair.Substring( eqIdx+1 ).Trim();
if ( val[0] == '"' ) {
val = val.Substring( 1, val.Length-2 );
}
dict[ key ] = WWW.UnEscapeURL( val );
}
}
return dict;
}
public static Dictionary<string,string> GetCookieRequestHeader( Dictionary<string,string> cookies ) {
var str = new StringBuilder();
foreach( var c in cookies ) {
if ( str.Length > 0 )
str.Append( "; " );
str.Append( c.Key ).Append( '=' ).Append( WWW.EscapeURL(c.Value) );
}
return new Dictionary<string,string>{ {"Cookie", str.ToString() } };
}
}
Works like a charm! It's really sad this issue has not been addressed, but in the meantime, your solution works very well. I will use your code in my project, please let me know how to credit you, in the mean time I'll use your user name.
Cheers!
Answer by Bunny83 · Oct 20, 2014 at 02:39 AM
Sure, there are a few, however not all are available in all situations. If you're in a webplayer you can use the browsers webinterface (via site-javascript code) to perform webrequests and read manually read the headers.
For the other platforms you can use any .NET / Mono web class (System.Net) that actually works with headerfields. However since those classes requires Sockets to work, it's not available for Android and iOS Free. Only for the pro version. Standalone builds should work fine.
See the license comparison page (seciont "code": .NET Socket Support)
You probably want to use either the HttpWebRequest if you do single requests or the WebClient class which actually handles cookies itself and is designed for multiple requests on the same domain.
ps: If someone comes across and still has some votes on the feedback site left, feel free to vote it up ;)
I gave it 10 votes. In the meantime, I discovered a workaround that lets you get at all the cookies from the WWW object. It's a gross hack that uses reflection to get at the data of the protected responseHeadersString property. I wrote an extension library here. Enjoy! https://s3.amazonaws.com/fractiv.unitystuff/UnityCookies.cs
Sure ;) That's also possible, however the internal implementation of the WWW class already has changed a few times in the past, so use it with caution.