Statistics
| Branch: | Tag: | Revision:

root / Assets / Plugins / UnityHTTP / CookieJar.cs @ 9:37719e88568d

History | View | Annotate | Download (10.5 kB)

1
using System;
2
using System.Collections.Generic;
3
using System.Text.RegularExpressions;
4
5
// Based on node-cookiejar (https://github.com/bmeck/node-cookiejar)
6
7
namespace HTTP
8
{
9
	public class CookieAccessInfo
10
	{
11
		public string domain = null;
12
		public string path = null;
13
		public bool secure = false;
14
		public bool scriptAccessible = true;
15
		
16
		public CookieAccessInfo( string domain, string path )
17
		{
18
			this.domain = domain;
19
			this.path = path;
20
		}
21
		
22
		public CookieAccessInfo( string domain, string path, bool secure )
23
		{
24
			this.domain = domain;
25
			this.path = path;
26
			this.secure = secure;
27
		}
28
		
29
		public CookieAccessInfo( string domain, string path, bool secure, bool scriptAccessible )
30
		{
31
			this.domain = domain;
32
			this.path = path;
33
			this.secure = secure;
34
			this.scriptAccessible = scriptAccessible;
35
		}
36
		
37
		public CookieAccessInfo( Cookie cookie )
38
		{
39
			this.domain = cookie.domain;
40
			this.path = cookie.path;
41
			this.secure = cookie.secure;
42
			this.scriptAccessible = cookie.scriptAccessible;
43
		}
44
	}
45
	
46
	public class Cookie
47
	{
48
		public string name = null;
49
		public string value = null;
50
		public DateTime expirationDate = DateTime.MaxValue;
51
		public string path = null;
52
		public string domain = null;
53
		public bool secure = false;
54
		public bool scriptAccessible = true;
55
		
56
		private static string cookiePattern = "\\s*([^=]+)(?:=((?:.|\\n)*))?";
57
		
58
		public Cookie( string cookieString )
59
		{
60
			string[] parts = cookieString.Split( ';' );
61
			foreach ( string part in parts )
62
			{
63
                
64
				Match match = Regex.Match( part, cookiePattern );
65
	
66
				if ( !match.Success )
67
				{
68
					throw new Exception( "Could not parse cookie string: " + cookieString );
69
				}
70
				
71
				if ( this.name == null )
72
				{
73
					this.name = match.Groups[ 1 ].Value;
74
					this.value = match.Groups[ 2 ].Value;
75
					continue;
76
				}
77
				
78
				switch( match.Groups[ 1 ].Value.ToLower() )
79
				{
80
				case "httponly":
81
					this.scriptAccessible = false;
82
					break;
83
				case "expires":
84
					this.expirationDate = DateTime.Parse( match.Groups[ 2 ].Value );
85
					break;
86
				case "path":
87
					this.path = match.Groups[ 2 ].Value;
88
					break;
89
				case "domain":
90
					this.domain = match.Groups[ 2 ].Value;
91
					break;
92
				case "secure":
93
					this.secure = true;
94
					break;
95
				default:
96
					// TODO: warn of unknown cookie setting?
97
					break;
98
				}
99
			}
100
		}
101
		
102
		public bool Matches( CookieAccessInfo accessInfo )
103
		{
104
			if (    this.secure != accessInfo.secure
105
			     || !this.CollidesWith( accessInfo ) )
106
			{
107
				return false;
108
			}
109
			
110
			return true;
111
		}
112
	
113
		public bool CollidesWith( CookieAccessInfo accessInfo )
114
		{
115
			if ( ( this.path != null && accessInfo.path == null ) || ( this.domain != null && accessInfo.domain == null ) )
116
			{
117
				return false;
118
			}
119
			
120
			if ( this.path != null && accessInfo.path != null && accessInfo.path.IndexOf( this.path ) != 0 )
121
			{
122
                return false;
123
			}
124
			
125
			if ( this.domain == accessInfo.domain )
126
			{
127
                return true;
128
			}
129
			else if ( this.domain != null && this.domain.Length >= 1 && this.domain[ 0 ] == '.' )
130
			{
131
                int wildcard = accessInfo.domain.IndexOf( this.domain.Substring( 1 ) );
132
				if( wildcard == -1 || wildcard != accessInfo.domain.Length - this.domain.Length + 1 )
133
				{
134
                    return false;
135
				}
136
			}
137
			else if ( this.domain != null )
138
			{
139
                return false;
140
			}
141
142
            return true;
143
		}
144
		
145
		public string ToValueString()
146
		{
147
			return this.name + "=" + this.value;
148
		}
149
		
150
		public override string ToString()
151
		{
152
			List< string > elements = new List< string >();
153
			elements.Add( this.name + "=" + this.value );
154
			
155
			if( this.expirationDate != DateTime.MaxValue )
156
			{
157
				elements.Add( "expires=" + this.expirationDate.ToString() );
158
			}
159
160
			if( this.domain != null )
161
			{
162
				elements.Add( "domain=" + this.domain );
163
			}
164
165
			if( this.path != null )
166
			{
167
				elements.Add( "path=" + this.path );
168
			}
169
			
170
			if( this.secure )
171
			{
172
				elements.Add( "secure" );
173
			}
174
	
175
			if( this.scriptAccessible == false )
176
			{
177
				elements.Add( "httponly" );
178
			}
179
			
180
			return String.Join( "; ", elements.ToArray() );
181
		}
182
	}
183
	
184
    public delegate void ContentsChangedDelegate();    
185
    
186
	public class CookieJar
187
	{
188
		private static string version = "v2";
189
        private object cookieJarLock = new object();
190
191
		private static CookieJar instance;
192
		public Dictionary< string, List< Cookie > > cookies;
193
194
        public ContentsChangedDelegate ContentsChanged;
195
		
196
		public static CookieJar Instance
197
		{
198
			get
199
			{
200
				if ( instance == null )
201
				{
202
					instance = new CookieJar();
203
				}
204
				return instance;
205
			}
206
		}
207
		
208
		public CookieJar ()
209
		{
210
            this.Clear();
211
		}
212
        
213
        public void Clear()
214
        {
215
            lock( cookieJarLock )
216
            {
217
                cookies = new Dictionary< string, List< Cookie > >();
218
                if ( ContentsChanged != null )
219
                {
220
                    ContentsChanged();
221
                }
222
            }
223
        }
224
225
		public bool SetCookie( Cookie cookie )
226
		{
227
            lock( cookieJarLock )
228
            {
229
                bool expired = cookie.expirationDate < DateTime.Now;
230
            
231
                if ( cookies.ContainsKey( cookie.name ) )
232
                {
233
                    for( int index = 0; index < cookies[ cookie.name ].Count; ++index )
234
                    {
235
                        Cookie collidableCookie = cookies[ cookie.name ][ index ];
236
                        if ( collidableCookie.CollidesWith( new CookieAccessInfo( cookie ) ) )
237
                        {
238
                            if( expired )
239
                            {
240
                                cookies[ cookie.name ].RemoveAt( index );
241
                                if ( cookies[ cookie.name ].Count == 0 )
242
                                {
243
                                    cookies.Remove( cookie.name );
244
                                    if ( ContentsChanged != null )
245
                                    {
246
                                        ContentsChanged();
247
                                    }
248
                                }
249
                                
250
                                return false;
251
                            }
252
                            else
253
                            {
254
                                cookies[ cookie.name ][ index ] = cookie;
255
                                if ( ContentsChanged != null )
256
                                {
257
                                    ContentsChanged();
258
                                }
259
                                return true;
260
                            }
261
                        }
262
                    }
263
                    
264
                    if ( expired )
265
                    {
266
                        return false;
267
                    }
268
                    
269
                    cookies[ cookie.name ].Add( cookie );
270
                    if ( ContentsChanged != null )
271
                    {
272
                        ContentsChanged();
273
                    }
274
                    return true;
275
                }
276
    
277
                if ( expired )
278
                {
279
                    return false;
280
                }
281
    
282
                cookies[ cookie.name ] = new List< Cookie >();
283
                cookies[ cookie.name ].Add( cookie );
284
                if ( ContentsChanged != null )
285
                {
286
                    ContentsChanged();
287
                }
288
                return true;
289
            }
290
		}
291
		
292
        // TODO: figure out a way to respect the scriptAccessible flag and supress cookies being
293
        //       returned that should not be.  The issue is that at some point, within this
294
        //       library, we need to send all the correct cookies back in the request.  Right now
295
        //       there's no way to add all cookies (regardless of script accessibility) to the
296
        //       request without exposing cookies that should not be script accessible.
297
        
298
		public Cookie GetCookie( string name, CookieAccessInfo accessInfo )
299
		{
300
			if ( !cookies.ContainsKey( name ) )
301
			{
302
                return null;
303
			}
304
			
305
			for ( int index = 0; index < cookies[ name ].Count; ++index )
306
			{
307
				Cookie cookie = cookies[ name ][ index ];
308
				if ( cookie.expirationDate > DateTime.Now && cookie.Matches( accessInfo ) )
309
				{
310
                    return cookie;
311
				}
312
			}
313
			
314
            return null;
315
		}
316
		
317
		public List< Cookie > GetCookies( CookieAccessInfo accessInfo )
318
		{
319
			List< Cookie > result = new List< Cookie >();
320
			foreach ( string cookieName in cookies.Keys )
321
			{
322
                Cookie cookie = this.GetCookie( cookieName, accessInfo );
323
				if ( cookie != null )
324
				{
325
                    result.Add( cookie );
326
				}
327
			}
328
			
329
			return result;
330
		}
331
		
332
		public void SetCookies( Cookie[] cookieObjects )
333
		{
334
			for ( var index = 0; index < cookieObjects.Length; ++index )
335
			{
336
				this.SetCookie( cookieObjects[ index ] );
337
			}
338
		}
339
		
340
		private static string cookiesStringPattern = "[:](?=\\s*[a-zA-Z0-9_\\-]+\\s*[=])";
341
342
		public void SetCookies( string cookiesString )
343
		{
344
			
345
			Match match = Regex.Match( cookiesString, cookiesStringPattern );
346
347
			if ( !match.Success )
348
			{
349
				throw new Exception( "Could not parse cookies string: " + cookiesString );
350
			}
351
			
352
			for ( int index = 0; index < match.Groups.Count; ++index )
353
			{
354
				this.SetCookie( new Cookie( match.Groups[ index ].Value ) );
355
			}
356
		}
357
358
        private static string boundary = "\n!!::!!\n";
359
360
        public string Serialize()
361
        {
362
            string result = version + boundary;
363
364
            lock( cookieJarLock )
365
            {
366
                foreach ( string key in cookies.Keys )
367
                {
368
                    for ( int index = 0; index < cookies[ key ].Count; ++index )
369
                    {
370
                        result += cookies[ key ][ index ].ToString() + boundary;
371
                    }
372
                }
373
            }
374
                
375
            return result;
376
        }
377
        
378
        public void Deserialize( string cookieJarString, bool clear )
379
        {
380
            if ( clear )
381
            {
382
                this.Clear();
383
            }
384
385
            Regex regex = new Regex( boundary );
386
            string[] cookieStrings = regex.Split( cookieJarString );
387
			bool readVersion = false;
388
            foreach ( string cookieString in cookieStrings )
389
            {
390
				if ( !readVersion )
391
				{
392
					if ( cookieString.IndexOf( version ) != 0 )
393
					{
394
						return;
395
					}
396
					readVersion = true;
397
					continue;
398
				}
399
				
400
                if ( cookieString.Length > 0 )
401
                {
402
                    this.SetCookie( new Cookie( cookieString ) );
403
                }
404
            }
405
        }
406
	}
407
}