]> git.sesse.net Git - vlc/blob - src/misc/httpcookies.c
httpcookies: fix heap read overflow (fixes #12674)
[vlc] / src / misc / httpcookies.c
1 /*****************************************************************************
2  * httpcookies.c: HTTP cookie utilities
3  *****************************************************************************
4  * Copyright (C) 2014 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Antti Ajanki <antti.ajanki@iki.fi>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <assert.h>
32
33 #include <vlc_common.h>
34 #include <vlc_messages.h>
35 #include <vlc_strings.h>
36 #include <vlc_http.h>
37
38 typedef struct http_cookie_t
39 {
40     char *psz_name;
41     char *psz_value;
42     char *psz_domain;
43     char *psz_path;
44     bool b_host_only;
45     bool b_secure;
46 } http_cookie_t;
47
48 struct vlc_http_cookie_jar_t
49 {
50     vlc_array_t cookies;
51     vlc_mutex_t lock;
52 };
53
54 static http_cookie_t * cookie_parse( const char * cookie_header, const vlc_url_t * url );
55 static void cookie_destroy( http_cookie_t * p_cookie );
56 static char * cookie_get_content( const char * cookie );
57 static char * cookie_get_domain( const char * cookie );
58 static char * cookie_get_attribute_value( const char * cookie, const char *attr );
59 static bool cookie_has_attribute( const char * cookie, const char *attr );
60 static bool cookie_should_be_sent( const http_cookie_t * cookie, const vlc_url_t * url );
61 static bool cookie_is_valid( const http_cookie_t * cookie, const char *host );
62 static bool cookie_domain_matches( const http_cookie_t * cookie, const char *host );
63 static bool cookie_path_matches( const http_cookie_t * cookie, const char *path );
64 static bool cookie_domain_is_public_suffix( const char *domain );
65 static char * cookie_default_path( const char *request_path );
66
67 vlc_http_cookie_jar_t * vlc_http_cookies_new()
68 {
69     vlc_http_cookie_jar_t * jar = malloc( sizeof( vlc_http_cookie_jar_t ) );
70     if ( !jar )
71         return NULL;
72
73     vlc_array_init( &jar->cookies );
74     vlc_mutex_init( &jar->lock );
75
76     return jar;
77 }
78
79 void vlc_http_cookies_destroy( vlc_http_cookie_jar_t * p_jar )
80 {
81     if ( !p_jar )
82         return;
83
84     int i;
85     for( i = 0; i < vlc_array_count( &p_jar->cookies ); i++ )
86         cookie_destroy( vlc_array_item_at_index( &p_jar->cookies, i ) );
87
88     vlc_array_clear( &p_jar->cookies );
89     vlc_mutex_destroy( &p_jar->lock );
90
91     free( p_jar );
92 }
93
94 bool vlc_http_cookies_append( vlc_http_cookie_jar_t * p_jar, const char * psz_cookie_header, const vlc_url_t *p_url )
95 {
96     int i;
97
98     http_cookie_t *cookie = cookie_parse( psz_cookie_header, p_url );
99     if( !cookie || !cookie_is_valid( cookie, p_url->psz_host ) )
100     {
101         cookie_destroy( cookie );
102         return false;
103     }
104
105     vlc_mutex_lock( &p_jar->lock );
106
107     for( i = 0; i < vlc_array_count( &p_jar->cookies ); i++ )
108     {
109         http_cookie_t *iter = vlc_array_item_at_index( &p_jar->cookies, i );
110
111         assert( iter->psz_name );
112         assert( iter->psz_domain );
113         assert( iter->psz_path );
114
115         bool domains_match =
116             vlc_ascii_strcasecmp( cookie->psz_domain, iter->psz_domain ) == 0;
117         bool paths_match = strcmp( cookie->psz_path, iter->psz_path ) == 0;
118         bool names_match = strcmp( cookie->psz_name, iter->psz_name ) == 0;
119         if( domains_match && paths_match && names_match )
120         {
121             /* Remove previous value for this cookie */
122             vlc_array_remove( &p_jar->cookies, i );
123             cookie_destroy(iter);
124             break;
125         }
126     }
127     vlc_array_append( &p_jar->cookies, cookie );
128
129     vlc_mutex_unlock( &p_jar->lock );
130
131     return true;
132 }
133
134 char *vlc_http_cookies_for_url( vlc_http_cookie_jar_t * p_jar, const vlc_url_t * p_url )
135 {
136     int i;
137     char *psz_cookiebuf = NULL;
138
139     vlc_mutex_lock( &p_jar->lock );
140
141     for( i = 0; i < vlc_array_count( &p_jar->cookies ); i++ )
142     {
143         const http_cookie_t * cookie = vlc_array_item_at_index( &p_jar->cookies, i );
144         if ( cookie_should_be_sent( cookie, p_url ) )
145         {
146             char *psz_updated_buf = NULL;
147             if ( asprintf(&psz_updated_buf, "%s%s%s=%s",
148                           psz_cookiebuf ? psz_cookiebuf : "",
149                           psz_cookiebuf ? "; " : "",
150                           cookie->psz_name ? cookie->psz_name : "",
151                           cookie->psz_value ? cookie->psz_value : "") == -1 )
152             {
153                 // TODO: report error
154                 free( psz_cookiebuf );
155                 vlc_mutex_unlock( &p_jar->lock );
156                 return NULL;
157             }
158             free( psz_cookiebuf );
159             psz_cookiebuf = psz_updated_buf;
160         }
161     }
162
163     vlc_mutex_unlock( &p_jar->lock );
164
165     return psz_cookiebuf;
166 }
167
168 static http_cookie_t * cookie_parse( const char * cookie_header, const vlc_url_t * url )
169 {
170     http_cookie_t *cookie = calloc( 1, sizeof( http_cookie_t ) );
171     if ( unlikely( !cookie ) )
172         return NULL;
173
174     char *content = cookie_get_content( cookie_header );
175     if ( !content )
176     {
177         cookie_destroy( cookie );
178         return NULL;
179     }
180
181     const char *eq = strchr( content, '=' );
182     if ( eq )
183     {
184         cookie->psz_name = strndup( content, eq-content );
185         cookie->psz_value = strdup( eq + 1 );
186     }
187     else
188     {
189         cookie->psz_name = strdup( content );
190         cookie->psz_value = NULL;
191     }
192
193     cookie->psz_domain = cookie_get_domain( cookie_header );
194     if ( !cookie->psz_domain || strlen(cookie->psz_domain) == 0 )
195     {
196         free(cookie->psz_domain);
197         cookie->psz_domain = strdup( url->psz_host );
198         cookie->b_host_only = true;
199     }
200     else
201         cookie->b_host_only = false;
202
203     cookie->psz_path = cookie_get_attribute_value( cookie_header, "path" );
204     if ( !cookie->psz_path || strlen(cookie->psz_path) == 0 )
205     {
206         free(cookie->psz_path);
207         cookie->psz_path = cookie_default_path( url->psz_path );
208     }
209
210     cookie->b_secure = cookie_has_attribute( cookie_header, "secure" );
211
212     FREENULL( content );
213
214     if ( !cookie->psz_domain || !cookie->psz_path || !cookie->psz_name )
215     {
216         cookie_destroy( cookie );
217         return NULL;
218     }
219
220     return cookie;
221 }
222
223 static void cookie_destroy( http_cookie_t * p_cookie )
224 {
225     if ( !p_cookie )
226         return;
227
228     free( p_cookie->psz_name );
229     free( p_cookie->psz_value );
230     free( p_cookie->psz_domain );
231     free( p_cookie->psz_path );
232     free( p_cookie );
233 }
234
235 /* Get the NAME=VALUE part of the Cookie */
236 static char * cookie_get_content( const char * cookie )
237 {
238     size_t content_length = strcspn( cookie, ";" );
239     return strndup( cookie, content_length );
240 }
241
242 /* Get the domain where the cookie is stored */
243 static char * cookie_get_domain( const char * cookie )
244 {
245     char *domain = cookie_get_attribute_value( cookie, "domain" );
246     if ( domain && *domain == '.' )
247     {
248         const char *real_domain = domain + strspn( domain, "." );
249         memmove( domain, real_domain, strlen( real_domain ) + 1 );
250     }
251     return domain;
252 }
253
254 static char * cookie_get_attribute_value( const char * cookie, const char *attr )
255 {
256     if( !cookie || !attr )
257         return NULL;
258
259     size_t attrlen = strlen( attr );
260     const char * str = strchr( cookie, ';' );
261     while( str )
262     {
263         /* skip ; and blank */
264         str++;
265         str = str + strspn( str, " " );
266
267         if( !vlc_ascii_strncasecmp( str, attr, attrlen ) &&
268             ( str[attrlen] == '=' ) )
269         {
270             str += attrlen + 1;
271             size_t value_length = strcspn( str, ";" );
272             return strndup( str, value_length );
273         }
274
275         str = strchr( str, ';' );
276     }
277     return NULL;
278 }
279
280 static bool cookie_has_attribute( const char * cookie, const char *attr )
281 {
282     if( !cookie || !attr )
283         return false;
284
285     size_t attrlen = strlen(attr);
286     const char * str = strchr(cookie, ';');
287     while( str )
288     {
289         /* skip ; and blank */
290         str++;
291         str = str + strspn( str, " " );
292
293         if( !vlc_ascii_strncasecmp( str, attr, attrlen ) &&
294             ( str[attrlen] == '=' || str[attrlen] == ';' || str[attrlen] == '\0' ) )
295             return true;
296
297         str = strchr(str, ';');
298     }
299     return false;
300 }
301
302 static bool cookie_should_be_sent( const http_cookie_t * cookie, const vlc_url_t * url )
303 {
304     bool protocol_ok = !cookie->b_secure ||
305         ( url->psz_protocol && strcasecmp(url->psz_protocol, "https") == 0 );
306     bool domain_ok = cookie_domain_matches( cookie, url->psz_host );
307     bool path_ok = cookie_path_matches( cookie, url->psz_path );
308     return protocol_ok && domain_ok && path_ok;
309 }
310
311 /* Check if a cookie from host should be added to the cookie jar */
312 static bool cookie_is_valid( const http_cookie_t * cookie, const char *host )
313 {
314     return cookie && cookie->psz_name && strlen(cookie->psz_name) > 0 &&
315         cookie->psz_domain &&
316         !cookie_domain_is_public_suffix(cookie->psz_domain) &&
317         cookie_domain_matches(cookie, host);
318 }
319
320 static bool cookie_domain_matches( const http_cookie_t * cookie, const char *host )
321 {
322     assert( !cookie || cookie->psz_domain );
323
324     // TODO: should convert domain names to punycode before comparing
325
326     if ( !cookie || !host )
327         return false;
328     if ( vlc_ascii_strcasecmp(cookie->psz_domain, host) == 0 )
329         return true;
330     else if ( cookie->b_host_only )
331         return false;
332
333     size_t host_len = strlen(host);
334     size_t cookie_domain_len = strlen(cookie->psz_domain);
335     bool is_suffix = false, has_dot_before_suffix = false;
336
337     if( host_len > cookie_domain_len )
338     {
339         size_t i = host_len - cookie_domain_len;
340
341         is_suffix = vlc_ascii_strcasecmp( &host[i], cookie->psz_domain ) == 0;
342         has_dot_before_suffix = host[i-1] == '.';
343     }
344
345     bool host_is_ipv4 = strspn(host, "0123456789.") == host_len;
346     bool host_is_ipv6 = strchr(host, ':') != NULL;
347     return is_suffix && has_dot_before_suffix &&
348         !( host_is_ipv4 || host_is_ipv6 );
349 }
350
351 static bool cookie_path_matches( const http_cookie_t * cookie, const char *uripath )
352 {
353     if ( !cookie || !uripath )
354         return false;
355     else if ( strcmp(cookie->psz_path, uripath) == 0 )
356         return true;
357
358     size_t path_len = strlen( uripath );
359     size_t prefix_len = strlen( cookie->psz_path );
360     return ( path_len > prefix_len ) &&
361         ( strncmp(uripath, cookie->psz_path, prefix_len) == 0 ) &&
362         ( uripath[prefix_len - 1] == '/' || uripath[prefix_len] == '/' );
363 }
364
365 static bool cookie_domain_is_public_suffix( const char *domain )
366 {
367     // FIXME: should check if domain is one of "public suffixes" at
368     // http://publicsuffix.org/. The purpose of this check is to
369     // prevent a host from setting a "too wide" cookie, for example
370     // "example.com" should not be able to set a cookie for "com".
371     // The current implementation prevents all top-level domains.
372     return domain && !strchr(domain, '.');
373 }
374
375 static char * cookie_default_path( const char *request_path )
376 {
377     if ( !request_path || *request_path != '/' )
378         return strdup("/");
379
380     char *path;
381     const char *query_start = strchr( request_path, '?' );
382     if ( query_start )
383         path = strndup( request_path, query_start - request_path );
384     else
385         path = strdup( request_path );
386
387     if ( !path )
388         return NULL;
389
390     char *last_slash = strrchr(path, '/');
391     assert(last_slash);
392     if ( last_slash == path )
393         path[1] = '\0';
394     else
395         *last_slash = '\0';
396
397     return path;
398 }