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 | } |