#if !BESTHTTP_DISABLE_COOKIES
using BestHTTP.Core;
using BestHTTP.PlatformSupport.FileSystem;
using System;
using System.Collections.Generic;
using System.Threading;
namespace BestHTTP.Cookies
{
///
/// The Cookie Jar implementation based on RFC 6265(http://tools.ietf.org/html/rfc6265).
///
public static class CookieJar
{
// Version of the cookie store. It may be used in a future version for maintaining compatibility.
private const int Version = 1;
///
/// Returns true if File apis are supported.
///
public static bool IsSavingSupported
{
get
{
#if !BESTHTTP_DISABLE_COOKIE_SAVE
if (IsSupportCheckDone)
return _isSavingSupported;
try
{
#if UNITY_WEBGL && !UNITY_EDITOR
_isSavingSupported = false;
#else
HTTPManager.IOService.DirectoryExists(HTTPManager.GetRootCacheFolder());
_isSavingSupported = true;
#endif
}
catch
{
_isSavingSupported = false;
HTTPManager.Logger.Warning("CookieJar", "Cookie saving and loading disabled!");
}
finally
{
IsSupportCheckDone = true;
}
return _isSavingSupported;
#else
return false;
#endif
}
}
///
/// The plugin will delete cookies that are accessed this threshold ago. Its default value is 7 days.
///
public static TimeSpan AccessThreshold = TimeSpan.FromDays(7);
#region Privates
///
/// List of the Cookies
///
private static List Cookies = new List();
private static string CookieFolder { get; set; }
private static string LibraryPath { get; set; }
///
/// Synchronization object for thread safety.
///
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
#if !BESTHTTP_DISABLE_COOKIE_SAVE
private static bool _isSavingSupported;
private static bool IsSupportCheckDone;
#endif
private static bool Loaded;
#endregion
#region Internal Functions
internal static void SetupFolder()
{
#if !BESTHTTP_DISABLE_COOKIE_SAVE
if (!CookieJar.IsSavingSupported)
return;
try
{
if (string.IsNullOrEmpty(CookieFolder) || string.IsNullOrEmpty(LibraryPath))
{
CookieFolder = System.IO.Path.Combine(HTTPManager.GetRootCacheFolder(), "Cookies");
LibraryPath = System.IO.Path.Combine(CookieFolder, "Library");
}
}
catch
{ }
#endif
}
///
/// Will set or update all cookies from the response object.
///
internal static bool Set(HTTPResponse response)
{
if (response == null)
return false;
List newCookies = new List();
var setCookieHeaders = response.GetHeaderValues("set-cookie");
// No cookies. :'(
if (setCookieHeaders == null)
return false;
foreach (var cookieHeader in setCookieHeaders)
{
Cookie cookie = Cookie.Parse(cookieHeader, response.baseRequest.CurrentUri, response.baseRequest.Context);
if (cookie != null)
{
rwLock.EnterWriteLock();
try
{
int idx;
var old = Find(cookie, out idx);
// if no value for the cookie or already expired then the server asked us to delete the cookie
bool expired = string.IsNullOrEmpty(cookie.Value) || !cookie.WillExpireInTheFuture();
if (!expired)
{
// no old cookie, add it straight to the list
if (old == null)
{
Cookies.Add(cookie);
newCookies.Add(cookie);
}
else
{
// Update the creation-time of the newly created cookie to match the creation-time of the old-cookie.
cookie.Date = old.Date;
Cookies[idx] = cookie;
newCookies.Add(cookie);
}
}
else if (idx != -1) // delete the cookie
Cookies.RemoveAt(idx);
}
catch
{
// Ignore cookie on error
}
finally
{
rwLock.ExitWriteLock();
}
}
}
response.Cookies = newCookies;
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
return true;
}
///
/// Deletes all expired or 'old' cookies, and will keep the sum size of cookies under the given size.
///
internal static void Maintain(bool sendEvent)
{
// It's not the same as in the rfc:
// http://tools.ietf.org/html/rfc6265#section-5.3
rwLock.EnterWriteLock();
try
{
uint size = 0;
for (int i = 0; i < Cookies.Count; )
{
var cookie = Cookies[i];
// Remove expired or not used cookies
if (!cookie.WillExpireInTheFuture() || (cookie.LastAccess + AccessThreshold) < DateTime.UtcNow)
{
Cookies.RemoveAt(i);
}
else
{
if (!cookie.IsSession)
size += cookie.GuessSize();
i++;
}
}
if (size > HTTPManager.CookieJarSize)
{
Cookies.Sort();
while (size > HTTPManager.CookieJarSize && Cookies.Count > 0)
{
var cookie = Cookies[0];
Cookies.RemoveAt(0);
size -= cookie.GuessSize();
}
}
}
catch
{ }
finally
{
rwLock.ExitWriteLock();
}
if (sendEvent)
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
}
///
/// Saves the Cookie Jar to a file.
///
/// Not implemented under Unity WebPlayer
internal static void Persist()
{
#if !BESTHTTP_DISABLE_COOKIE_SAVE
if (!IsSavingSupported)
return;
if (!Loaded)
return;
// Delete any expired cookie
Maintain(false);
rwLock.EnterWriteLock();
try
{
if (!HTTPManager.IOService.DirectoryExists(CookieFolder))
HTTPManager.IOService.DirectoryCreate(CookieFolder);
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.Create))
using (var bw = new System.IO.BinaryWriter(fs))
{
bw.Write(Version);
// Count how many non-session cookies we have
int count = 0;
foreach (var cookie in Cookies)
if (!cookie.IsSession)
count++;
bw.Write(count);
// Save only the persistable cookies
foreach (var cookie in Cookies)
if (!cookie.IsSession)
cookie.SaveTo(bw);
}
}
catch
{ }
finally
{
rwLock.ExitWriteLock();
}
#endif
}
///
/// Load previously persisted cookie library from the file.
///
internal static void Load()
{
#if !BESTHTTP_DISABLE_COOKIE_SAVE
if (!IsSavingSupported)
return;
if (Loaded)
return;
SetupFolder();
rwLock.EnterWriteLock();
try
{
Cookies.Clear();
if (!HTTPManager.IOService.DirectoryExists(CookieFolder))
HTTPManager.IOService.DirectoryCreate(CookieFolder);
if (!HTTPManager.IOService.FileExists(LibraryPath))
return;
using (var fs = HTTPManager.IOService.CreateFileStream(LibraryPath, FileStreamModes.OpenRead))
using (var br = new System.IO.BinaryReader(fs))
{
/*int version = */br.ReadInt32();
int cookieCount = br.ReadInt32();
for (int i = 0; i < cookieCount; ++i)
{
Cookie cookie = new Cookie();
cookie.LoadFrom(br);
if (cookie.WillExpireInTheFuture())
Cookies.Add(cookie);
}
}
}
catch
{
Cookies.Clear();
}
finally
{
Loaded = true;
rwLock.ExitWriteLock();
}
#endif
}
#endregion
#region Public Functions
///
/// Returns all Cookies that corresponds to the given Uri.
///
public static List Get(Uri uri)
{
Load();
rwLock.EnterReadLock();
try
{
List result = null;
for (int i = 0; i < Cookies.Count; ++i)
{
Cookie cookie = Cookies[i];
if (cookie.WillExpireInTheFuture() && (uri.Host.IndexOf(cookie.Domain) != -1 || string.Format("{0}:{1}", uri.Host, uri.Port).IndexOf(cookie.Domain) != -1) && uri.AbsolutePath.StartsWith(cookie.Path))
{
if (result == null)
result = new List();
result.Add(cookie);
}
}
return result;
}
finally
{
rwLock.ExitReadLock();
}
}
///
/// Will add a new, or overwrite an old cookie if already exists.
///
public static void Set(Uri uri, Cookie cookie)
{
Set(cookie);
}
///
/// Will add a new, or overwrite an old cookie if already exists.
///
public static void Set(Cookie cookie)
{
Load();
rwLock.EnterWriteLock();
try
{
int idx;
Find(cookie, out idx);
if (idx >= 0)
Cookies[idx] = cookie;
else
Cookies.Add(cookie);
}
finally
{
rwLock.ExitWriteLock();
}
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
}
public static List GetAll()
{
Load();
return Cookies;
}
///
/// Deletes all cookies from the Jar.
///
public static void Clear()
{
Load();
rwLock.EnterWriteLock();
try
{
Cookies.Clear();
}
finally
{
rwLock.ExitWriteLock();
}
Persist();
}
///
/// Removes cookies that older than the given parameter.
///
public static void Clear(TimeSpan olderThan)
{
Load();
rwLock.EnterWriteLock();
try
{
for (int i = 0; i < Cookies.Count; )
{
var cookie = Cookies[i];
// Remove expired or not used cookies
if (!cookie.WillExpireInTheFuture() || (cookie.Date + olderThan) < DateTime.UtcNow)
Cookies.RemoveAt(i);
else
i++;
}
}
finally
{
rwLock.ExitWriteLock();
}
Persist();
}
///
/// Removes cookies that matches to the given domain.
///
public static void Clear(string domain)
{
Load();
rwLock.EnterWriteLock();
try
{
for (int i = 0; i < Cookies.Count; )
{
var cookie = Cookies[i];
// Remove expired or not used cookies
if (!cookie.WillExpireInTheFuture() || cookie.Domain.IndexOf(domain) != -1)
Cookies.RemoveAt(i);
else
i++;
}
}
finally
{
rwLock.ExitWriteLock();
}
Persist();
}
public static void Remove(Uri uri, string name)
{
Load();
rwLock.EnterWriteLock();
try
{
for (int i = 0; i < Cookies.Count; )
{
var cookie = Cookies[i];
if (cookie.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && uri.Host.IndexOf(cookie.Domain) != -1)
Cookies.RemoveAt(i);
else
i++;
}
}
finally
{
rwLock.ExitWriteLock();
}
PluginEventHelper.EnqueuePluginEvent(new PluginEventInfo(PluginEvents.SaveCookieLibrary));
}
#endregion
#region Private Helper Functions
///
/// Find and return a Cookie and his index in the list.
///
private static Cookie Find(Cookie cookie, out int idx)
{
for (int i = 0; i < Cookies.Count; ++i)
{
Cookie c = Cookies[i];
if (c.Equals(cookie))
{
idx = i;
return c;
}
}
idx = -1;
return null;
}
#endregion
}
}
#endif