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 static char *cookie_get_attribute_value( const char *cookie
, const char *attr
)
50 size_t attrlen
= strlen( attr
);
51 const char * str
= strchr( cookie
, ';' );
54 /* skip ; and blank */
56 str
= str
+ strspn( str
, " " );
58 if( !vlc_ascii_strncasecmp( str
, attr
, attrlen
)
59 && (str
[attrlen
] == '=') )
62 size_t value_length
= strcspn( str
, ";" );
63 return strndup( str
, value_length
);
66 str
= strchr( str
, ';' );
71 static bool cookie_has_attribute( const char *cookie
, const char *attr
)
73 size_t attrlen
= strlen(attr
);
74 const char * str
= strchr(cookie
, ';');
77 /* skip ; and blank */
79 str
+= strspn( str
, " " );
81 if( !vlc_ascii_strncasecmp( str
, attr
, attrlen
)
82 && (str
[attrlen
] == '=' || str
[attrlen
] == ';' || str
[attrlen
] == '\0') )
85 str
= strchr(str
, ';');
90 /* Get the domain where the cookie is stored */
91 static char *cookie_get_domain( const char *cookie
)
93 char *domain
= cookie_get_attribute_value( cookie
, "domain" );
97 if( domain
[0] == '.' )
99 const char *real_domain
= domain
+ strspn( domain
, "." );
100 memmove( domain
, real_domain
, strlen( real_domain
) + 1 );
105 static bool cookie_domain_matches( const http_cookie_t
*cookie
,
108 // TODO: should convert domain names to punycode before comparing
112 if ( vlc_ascii_strcasecmp(cookie
->psz_domain
, host
) == 0 )
114 else if ( cookie
->b_host_only
)
117 size_t host_len
= strlen(host
);
118 size_t cookie_domain_len
= strlen(cookie
->psz_domain
);
119 bool is_suffix
= false, has_dot_before_suffix
= false;
121 if( host_len
> cookie_domain_len
)
123 size_t i
= host_len
- cookie_domain_len
;
125 is_suffix
= vlc_ascii_strcasecmp( &host
[i
], cookie
->psz_domain
) == 0;
126 has_dot_before_suffix
= host
[i
-1] == '.';
129 bool host_is_ipv4
= strspn(host
, "0123456789.") == host_len
;
130 bool host_is_ipv6
= strchr(host
, ':') != NULL
;
131 return is_suffix
&& has_dot_before_suffix
&&
132 !( host_is_ipv4
|| host_is_ipv6
);
135 static char *cookie_get_path(const char *cookie
)
137 return cookie_get_attribute_value(cookie
, "path");
140 static bool cookie_path_matches( const http_cookie_t
* cookie
, const char *uripath
)
142 if (uripath
== NULL
)
144 else if ( strcmp(cookie
->psz_path
, uripath
) == 0 )
147 size_t path_len
= strlen( uripath
);
148 size_t prefix_len
= strlen( cookie
->psz_path
);
149 return ( path_len
> prefix_len
) &&
150 ( strncmp(uripath
, cookie
->psz_path
, prefix_len
) == 0 ) &&
151 ( uripath
[prefix_len
- 1] == '/' || uripath
[prefix_len
] == '/' );
154 static bool cookie_should_be_sent(const http_cookie_t
*cookie
, bool secure
,
155 const char *host
, const char *path
)
157 bool protocol_ok
= secure
|| !cookie
->b_secure
;
158 bool domain_ok
= cookie_domain_matches(cookie
, host
);
159 bool path_ok
= cookie_path_matches(cookie
, path
);
160 return protocol_ok
&& domain_ok
&& path_ok
;
163 static char *cookie_default_path( const char *request_path
)
165 if ( request_path
== NULL
|| request_path
[0] != '/' )
169 const char *query_start
= strchr( request_path
, '?' );
170 if ( query_start
!= NULL
)
171 path
= strndup( request_path
, query_start
- request_path
);
173 path
= strdup( request_path
);
178 char *last_slash
= strrchr(path
, '/');
179 assert(last_slash
!= NULL
);
180 if ( last_slash
== path
)
188 static void cookie_destroy(http_cookie_t
*cookie
)
190 assert(cookie
!= NULL
);
191 free(cookie
->psz_name
);
192 free(cookie
->psz_value
);
193 free(cookie
->psz_domain
);
194 free(cookie
->psz_path
);
199 static http_cookie_t
*cookie_parse(const char *value
,
200 const char *host
, const char *path
)
202 http_cookie_t
*cookie
= malloc(sizeof (*cookie
));
203 if (unlikely(cookie
== NULL
))
206 cookie
->psz_domain
= NULL
;
207 cookie
->psz_path
= NULL
;
209 /* Get the NAME=VALUE part of the Cookie */
210 size_t value_length
= strcspn(value
, ";");
211 const char *p
= memchr(value
, '=', value_length
);
215 cookie
->psz_name
= strndup(value
, p
- value
);
217 cookie
->psz_value
= strndup(p
, value_length
- (p
- value
));
218 if (unlikely(cookie
->psz_value
== NULL
))
223 cookie
->psz_name
= strndup(value
, value_length
);
224 cookie
->psz_value
= NULL
;
227 if (unlikely(cookie
->psz_name
== NULL
))
230 /* Cookie name is a token; it cannot be empty. */
231 if (cookie
->psz_name
[0] == '\0')
235 cookie
->psz_domain
= cookie_get_domain(value
);
236 if (cookie
->psz_domain
== NULL
)
238 cookie
->psz_domain
= strdup(host
);
239 if (unlikely(cookie
->psz_domain
== NULL
))
242 cookie
->b_host_only
= true;
245 cookie
->b_host_only
= false;
248 cookie
->psz_path
= cookie_get_path(value
);
249 if (cookie
->psz_path
== NULL
)
251 cookie
->psz_path
= cookie_default_path(path
);
252 if (unlikely(cookie
->psz_path
== NULL
))
256 /* Get secure flag */
257 cookie
->b_secure
= cookie_has_attribute(value
, "secure");
262 cookie_destroy(cookie
);
266 struct vlc_http_cookie_jar_t
272 vlc_http_cookie_jar_t
* vlc_http_cookies_new(void)
274 vlc_http_cookie_jar_t
* jar
= malloc( sizeof( vlc_http_cookie_jar_t
) );
275 if ( unlikely(jar
== NULL
) )
278 vlc_array_init( &jar
->cookies
);
279 vlc_mutex_init( &jar
->lock
);
284 void vlc_http_cookies_destroy( vlc_http_cookie_jar_t
* p_jar
)
289 for( size_t i
= 0; i
< vlc_array_count( &p_jar
->cookies
); i
++ )
290 cookie_destroy( vlc_array_item_at_index( &p_jar
->cookies
, i
) );
292 vlc_array_clear( &p_jar
->cookies
);
293 vlc_mutex_destroy( &p_jar
->lock
);
298 bool vlc_http_cookies_store(vlc_http_cookie_jar_t
*p_jar
, const char *cookies
,
299 const char *host
, const char *path
)
301 assert(host
!= NULL
);
302 assert(path
!= NULL
);
304 http_cookie_t
*cookie
= cookie_parse(cookies
, host
, path
);
308 /* Check if a cookie from host should be added to the cookie jar */
309 // FIXME: should check if domain is one of "public suffixes" at
310 // http://publicsuffix.org/. The purpose of this check is to
311 // prevent a host from setting a "too wide" cookie, for example
312 // "example.com" should not be able to set a cookie for "com".
313 // The current implementation prevents all top-level domains.
314 if (strchr(cookie
->psz_domain
, '.') == NULL
315 || !cookie_domain_matches(cookie
, host
))
317 cookie_destroy(cookie
);
321 vlc_mutex_lock( &p_jar
->lock
);
323 for( size_t i
= 0; i
< vlc_array_count( &p_jar
->cookies
); i
++ )
325 http_cookie_t
*iter
= vlc_array_item_at_index( &p_jar
->cookies
, i
);
327 assert( iter
->psz_name
);
328 assert( iter
->psz_domain
);
329 assert( iter
->psz_path
);
332 vlc_ascii_strcasecmp( cookie
->psz_domain
, iter
->psz_domain
) == 0;
333 bool paths_match
= strcmp( cookie
->psz_path
, iter
->psz_path
) == 0;
334 bool names_match
= strcmp( cookie
->psz_name
, iter
->psz_name
) == 0;
335 if( domains_match
&& paths_match
&& names_match
)
337 /* Remove previous value for this cookie */
338 vlc_array_remove( &p_jar
->cookies
, i
);
339 cookie_destroy(iter
);
344 bool b_ret
= (vlc_array_append( &p_jar
->cookies
, cookie
) == 0);
346 cookie_destroy( cookie
);
348 vlc_mutex_unlock( &p_jar
->lock
);
353 char *vlc_http_cookies_fetch(vlc_http_cookie_jar_t
*p_jar
, bool secure
,
354 const char *host
, const char *path
)
356 char *psz_cookiebuf
= NULL
;
358 vlc_mutex_lock( &p_jar
->lock
);
360 for( size_t i
= 0; i
< vlc_array_count( &p_jar
->cookies
); i
++ )
362 const http_cookie_t
* cookie
= vlc_array_item_at_index( &p_jar
->cookies
, i
);
363 if (cookie_should_be_sent(cookie
, secure
, host
, path
))
365 char *psz_updated_buf
= NULL
;
366 if ( asprintf(&psz_updated_buf
, "%s%s%s=%s",
367 psz_cookiebuf
? psz_cookiebuf
: "",
368 psz_cookiebuf
? "; " : "",
369 cookie
->psz_name
? cookie
->psz_name
: "",
370 cookie
->psz_value
? cookie
->psz_value
: "") == -1 )
372 // TODO: report error
373 free( psz_cookiebuf
);
374 vlc_mutex_unlock( &p_jar
->lock
);
377 free( psz_cookiebuf
);
378 psz_cookiebuf
= psz_updated_buf
;
382 vlc_mutex_unlock( &p_jar
->lock
);
384 return psz_cookiebuf
;