1 /*****************************************************************************
2 * httpcookies.c: HTTP cookie utilities
3 *****************************************************************************
4 * Copyright (C) 2014 VLC authors and VideoLAN
7 * Authors: Antti Ajanki <antti.ajanki@iki.fi>
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.
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.
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 *****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_messages.h>
35 #include <vlc_strings.h>
38 typedef struct http_cookie_t
48 struct vlc_http_cookie_jar_t
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 );
67 vlc_http_cookie_jar_t * vlc_http_cookies_new()
69 vlc_http_cookie_jar_t * jar = malloc( sizeof( vlc_http_cookie_jar_t ) );
73 vlc_array_init( &jar->cookies );
74 vlc_mutex_init( &jar->lock );
79 void vlc_http_cookies_destroy( vlc_http_cookie_jar_t * p_jar )
85 for( i = 0; i < vlc_array_count( &p_jar->cookies ); i++ )
86 cookie_destroy( vlc_array_item_at_index( &p_jar->cookies, i ) );
88 vlc_array_clear( &p_jar->cookies );
89 vlc_mutex_destroy( &p_jar->lock );
94 bool vlc_http_cookies_append( vlc_http_cookie_jar_t * p_jar, const char * psz_cookie_header, const vlc_url_t *p_url )
98 http_cookie_t *cookie = cookie_parse( psz_cookie_header, p_url );
99 if( !cookie || !cookie_is_valid( cookie, p_url->psz_host ) )
101 cookie_destroy( cookie );
105 vlc_mutex_lock( &p_jar->lock );
107 for( i = 0; i < vlc_array_count( &p_jar->cookies ); i++ )
109 http_cookie_t *iter = vlc_array_item_at_index( &p_jar->cookies, i );
111 assert( iter->psz_name );
112 assert( iter->psz_domain );
113 assert( iter->psz_path );
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 )
121 /* Remove previous value for this cookie */
122 vlc_array_remove( &p_jar->cookies, i );
123 cookie_destroy(iter);
127 vlc_array_append( &p_jar->cookies, cookie );
129 vlc_mutex_unlock( &p_jar->lock );
134 char *vlc_http_cookies_for_url( vlc_http_cookie_jar_t * p_jar, const vlc_url_t * p_url )
137 char *psz_cookiebuf = NULL;
139 vlc_mutex_lock( &p_jar->lock );
141 for( i = 0; i < vlc_array_count( &p_jar->cookies ); i++ )
143 const http_cookie_t * cookie = vlc_array_item_at_index( &p_jar->cookies, i );
144 if ( cookie_should_be_sent( cookie, p_url ) )
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 )
153 // TODO: report error
154 free( psz_cookiebuf );
155 vlc_mutex_unlock( &p_jar->lock );
158 free( psz_cookiebuf );
159 psz_cookiebuf = psz_updated_buf;
163 vlc_mutex_unlock( &p_jar->lock );
165 return psz_cookiebuf;
168 static http_cookie_t * cookie_parse( const char * cookie_header, const vlc_url_t * url )
170 http_cookie_t *cookie = calloc( 1, sizeof( http_cookie_t ) );
171 if ( unlikely( !cookie ) )
174 char *content = cookie_get_content( cookie_header );
177 cookie_destroy( cookie );
181 const char *eq = strchr( content, '=' );
184 cookie->psz_name = strndup( content, eq-content );
185 cookie->psz_value = strdup( eq + 1 );
189 cookie->psz_name = strdup( content );
190 cookie->psz_value = NULL;
193 cookie->psz_domain = cookie_get_domain( cookie_header );
194 if ( !cookie->psz_domain || strlen(cookie->psz_domain) == 0 )
196 free(cookie->psz_domain);
197 cookie->psz_domain = strdup( url->psz_host );
198 cookie->b_host_only = true;
201 cookie->b_host_only = false;
203 cookie->psz_path = cookie_get_attribute_value( cookie_header, "path" );
204 if ( !cookie->psz_path || strlen(cookie->psz_path) == 0 )
206 free(cookie->psz_path);
207 cookie->psz_path = cookie_default_path( url->psz_path );
210 cookie->b_secure = cookie_has_attribute( cookie_header, "secure" );
214 if ( !cookie->psz_domain || !cookie->psz_path || !cookie->psz_name )
216 cookie_destroy( cookie );
223 static void cookie_destroy( http_cookie_t * p_cookie )
228 free( p_cookie->psz_name );
229 free( p_cookie->psz_value );
230 free( p_cookie->psz_domain );
231 free( p_cookie->psz_path );
235 /* Get the NAME=VALUE part of the Cookie */
236 static char * cookie_get_content( const char * cookie )
238 size_t content_length = strcspn( cookie, ";" );
239 return strndup( cookie, content_length );
242 /* Get the domain where the cookie is stored */
243 static char * cookie_get_domain( const char * cookie )
245 char *domain = cookie_get_attribute_value( cookie, "domain" );
246 if ( domain && *domain == '.' )
248 const char *real_domain = domain + strspn( domain, "." );
249 memmove( domain, real_domain, strlen( real_domain ) + 1 );
254 static char * cookie_get_attribute_value( const char * cookie, const char *attr )
256 if( !cookie || !attr )
259 size_t attrlen = strlen( attr );
260 const char * str = strchr( cookie, ';' );
263 /* skip ; and blank */
265 str = str + strspn( str, " " );
267 if( !vlc_ascii_strncasecmp( str, attr, attrlen ) &&
268 ( str[attrlen] == '=' ) )
271 size_t value_length = strcspn( str, ";" );
272 return strndup( str, value_length );
275 str = strchr( str, ';' );
280 static bool cookie_has_attribute( const char * cookie, const char *attr )
282 if( !cookie || !attr )
285 size_t attrlen = strlen(attr);
286 const char * str = strchr(cookie, ';');
289 /* skip ; and blank */
291 str = str + strspn( str, " " );
293 if( !vlc_ascii_strncasecmp( str, attr, attrlen ) &&
294 ( str[attrlen] == '=' || str[attrlen] == ';' || str[attrlen] == '\0' ) )
297 str = strchr(str, ';');
302 static bool cookie_should_be_sent( const http_cookie_t * cookie, const vlc_url_t * url )
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;
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 )
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);
320 static bool cookie_domain_matches( const http_cookie_t * cookie, const char *host )
322 assert( !cookie || cookie->psz_domain );
324 // TODO: should convert domain names to punycode before comparing
326 if ( !cookie || !host )
328 if ( vlc_ascii_strcasecmp(cookie->psz_domain, host) == 0 )
330 else if ( cookie->b_host_only )
333 size_t host_len = strlen(host);
334 size_t cookie_domain_len = strlen(cookie->psz_domain);
335 int i = host_len - cookie_domain_len;
336 bool is_suffix = ( i > 0 ) &&
337 vlc_ascii_strcasecmp( &host[i], cookie->psz_domain ) == 0;
338 bool has_dot_before_suffix = host[i-1] == '.';
339 bool host_is_ipv4 = strspn(host, "0123456789.") == host_len;
340 bool host_is_ipv6 = strchr(host, ':') != NULL;
341 return is_suffix && has_dot_before_suffix &&
342 !( host_is_ipv4 || host_is_ipv6 );
345 static bool cookie_path_matches( const http_cookie_t * cookie, const char *uripath )
347 if ( !cookie || !uripath )
349 else if ( strcmp(cookie->psz_path, uripath) == 0 )
352 size_t path_len = strlen( uripath );
353 size_t prefix_len = strlen( cookie->psz_path );
354 return ( path_len > prefix_len ) &&
355 ( strncmp(uripath, cookie->psz_path, prefix_len) == 0 ) &&
356 ( uripath[prefix_len - 1] == '/' || uripath[prefix_len] == '/' );
359 static bool cookie_domain_is_public_suffix( const char *domain )
361 // FIXME: should check if domain is one of "public suffixes" at
362 // http://publicsuffix.org/. The purpose of this check is to
363 // prevent a host from setting a "too wide" cookie, for example
364 // "example.com" should not be able to set a cookie for "com".
365 // The current implementation prevents all top-level domains.
366 return domain && !strchr(domain, '.');
369 static char * cookie_default_path( const char *request_path )
371 if ( !request_path || *request_path != '/' )
375 const char *query_start = strchr( request_path, '?' );
377 path = strndup( request_path, query_start - request_path );
379 path = strdup( request_path );
384 char *last_slash = strrchr(path, '/');
386 if ( last_slash == path )