2 * This file Copyright (C) 2008-2010 Mnemosyne LLC
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
10 * $Id: web.c 11398 2010-11-11 15:31:11Z charles $
16 #include <sys/select.h>
19 #include <curl/curl.h>
22 #include "transmission.h"
24 #include "net.h" /* tr_address */
25 #include "platform.h" /* mutex */
27 #include "trevent.h" /* tr_runInEventThread() */
29 #include "version.h" /* User-Agent */
32 #if LIBCURL_VERSION_NUM >= 0x070F06 /* CURLOPT_SOCKOPT* was added in 7.15.6 */
33 #define USE_LIBCURL_SOCKOPT
38 THREADFUNC_MAX_SLEEP_MSEC
= 1000,
44 fprintf( stderr, __VA_ARGS__ ); \
45 fprintf( stderr, "\n" ); \
48 #define dbgmsg( ... ) \
50 if( tr_deepLoggingIsActive( ) ) \
51 tr_deepLog( __FILE__, __LINE__, "web", __VA_ARGS__ ); \
74 struct evbuffer
* response
;
78 tr_web_done_func
* done_func
;
79 void * done_func_user_data
;
83 task_free( struct tr_web_task
* task
)
85 evbuffer_free( task
->response
);
86 tr_free( task
->range
);
96 writeFunc( void * ptr
, size_t size
, size_t nmemb
, void * vtask
)
98 const size_t byteCount
= size
* nmemb
;
99 struct tr_web_task
* task
= vtask
;
100 evbuffer_add( task
->response
, ptr
, byteCount
);
101 dbgmsg( "wrote %zu bytes to task %p's buffer", byteCount
, task
);
105 #ifdef USE_LIBCURL_SOCKOPT
107 sockoptfunction( void * vtask
, curl_socket_t fd
, curlsocktype purpose UNUSED
)
109 struct tr_web_task
* task
= vtask
;
110 const tr_bool isScrape
= strstr( task
->url
, "scrape" ) != NULL
;
111 const tr_bool isAnnounce
= strstr( task
->url
, "announce" ) != NULL
;
113 /* announce and scrape requests have tiny payloads. */
114 if( isScrape
|| isAnnounce
)
116 const int sndbuf
= 1024;
117 const int rcvbuf
= isScrape
? 2048 : 3072;
118 setsockopt( fd
, SOL_SOCKET
, SO_SNDBUF
, &sndbuf
, sizeof(sndbuf
) );
119 setsockopt( fd
, SOL_SOCKET
, SO_RCVBUF
, &rcvbuf
, sizeof(rcvbuf
) );
122 /* return nonzero if this function encountered an error */
128 getTimeoutFromURL( const struct tr_web_task
* task
)
131 const tr_session
* session
= task
->session
;
133 if( !session
|| session
->isClosed
) timeout
= 20L;
134 else if( strstr( task
->url
, "scrape" ) != NULL
) timeout
= 30L;
135 else if( strstr( task
->url
, "announce" ) != NULL
) timeout
= 90L;
142 createEasy( tr_session
* s
, struct tr_web_task
* task
)
144 const tr_address
* addr
;
145 CURL
* e
= curl_easy_init( );
146 const long verbose
= getenv( "TR_CURL_VERBOSE" ) != NULL
;
147 char * cookie_filename
= tr_buildPath( s
->configDir
, "cookies.txt", NULL
);
149 curl_easy_setopt( e
, CURLOPT_AUTOREFERER
, 1L );
150 curl_easy_setopt( e
, CURLOPT_COOKIEFILE
, cookie_filename
);
151 curl_easy_setopt( e
, CURLOPT_ENCODING
, "gzip;q=1.0, deflate, identity" );
152 curl_easy_setopt( e
, CURLOPT_FOLLOWLOCATION
, 1L );
153 curl_easy_setopt( e
, CURLOPT_MAXREDIRS
, -1L );
154 curl_easy_setopt( e
, CURLOPT_NOSIGNAL
, 1L );
155 curl_easy_setopt( e
, CURLOPT_PRIVATE
, task
);
156 #ifdef USE_LIBCURL_SOCKOPT
157 curl_easy_setopt( e
, CURLOPT_SOCKOPTFUNCTION
, sockoptfunction
);
158 curl_easy_setopt( e
, CURLOPT_SOCKOPTDATA
, task
);
160 curl_easy_setopt( e
, CURLOPT_SSL_VERIFYHOST
, 0L );
161 curl_easy_setopt( e
, CURLOPT_SSL_VERIFYPEER
, 0L );
162 curl_easy_setopt( e
, CURLOPT_TIMEOUT
, getTimeoutFromURL( task
) );
163 curl_easy_setopt( e
, CURLOPT_URL
, task
->url
);
164 curl_easy_setopt( e
, CURLOPT_USERAGENT
, TR_NAME
"/" SHORT_VERSION_STRING
);
165 curl_easy_setopt( e
, CURLOPT_VERBOSE
, verbose
);
166 curl_easy_setopt( e
, CURLOPT_WRITEDATA
, task
);
167 curl_easy_setopt( e
, CURLOPT_WRITEFUNCTION
, writeFunc
);
169 if(( addr
= tr_sessionGetPublicAddress( s
, TR_AF_INET
)))
170 curl_easy_setopt( e
, CURLOPT_INTERFACE
, tr_ntop_non_ts( addr
) );
173 curl_easy_setopt( e
, CURLOPT_RANGE
, task
->range
);
175 tr_free( cookie_filename
);
184 task_finish_func( void * vtask
)
186 struct tr_web_task
* task
= vtask
;
187 dbgmsg( "finished web task %p; got %ld", task
, task
->code
);
189 if( task
->done_func
!= NULL
)
190 task
->done_func( task
->session
,
192 EVBUFFER_DATA( task
->response
),
193 EVBUFFER_LENGTH( task
->response
),
194 task
->done_func_user_data
);
204 tr_webRun( tr_session
* session
,
207 tr_web_done_func done_func
,
208 void * done_func_user_data
)
210 struct tr_web
* web
= session
->web
;
214 struct tr_web_task
* task
= tr_new0( struct tr_web_task
, 1 );
216 task
->session
= session
;
217 task
->url
= tr_strdup( url
);
218 task
->range
= tr_strdup( range
);
219 task
->done_func
= done_func
;
220 task
->done_func_user_data
= done_func_user_data
;
221 task
->response
= evbuffer_new( );
223 tr_lockLock( web
->taskLock
);
224 tr_list_append( &web
->tasks
, task
);
225 tr_lockUnlock( web
->taskLock
);
230 * Portability wrapper for select().
232 * http://msdn.microsoft.com/en-us/library/ms740141%28VS.85%29.aspx
233 * On win32, any two of the parameters, readfds, writefds, or exceptfds,
234 * can be given as null. At least one must be non-null, and any non-null
235 * descriptor set must contain at least one handle to a socket.
239 fd_set
* r_fd_set
, fd_set
* w_fd_set
, fd_set
* c_fd_set
,
243 if( !r_fd_set
->fd_count
&& !w_fd_set
->fd_count
&& !c_fd_set
->fd_count
)
245 const long int msec
= t
->tv_sec
*1000 + t
->tv_usec
/1000;
246 tr_wait_msec( msec
);
248 else if( select( 0, r_fd_set
->fd_count
? r_fd_set
: NULL
,
249 w_fd_set
->fd_count
? w_fd_set
: NULL
,
250 c_fd_set
->fd_count
? c_fd_set
: NULL
, t
) < 0 )
253 const int e
= EVUTIL_SOCKET_ERROR( );
254 tr_net_strerror( errstr
, sizeof( errstr
), e
);
255 dbgmsg( "Error: select (%d) %s", e
, errstr
);
258 select( nfds
, r_fd_set
, w_fd_set
, c_fd_set
, t
);
263 tr_webThreadFunc( void * vsession
)
269 tr_session
* session
= vsession
;
271 /* try to enable ssl for https support; but if that fails,
272 * try a plain vanilla init */
273 if( curl_global_init( CURL_GLOBAL_SSL
) )
274 curl_global_init( 0 );
276 web
= tr_new0( struct tr_web
, 1 );
277 web
->close_mode
= ~0;
278 web
->taskLock
= tr_lockNew( );
280 multi
= curl_multi_init( );
288 struct tr_web_task
* task
;
290 if( web
->close_mode
== TR_WEB_CLOSE_NOW
)
292 if( ( web
->close_mode
== TR_WEB_CLOSE_WHEN_IDLE
) && !taskCount
)
295 /* add tasks from the queue */
296 tr_lockLock( web
->taskLock
);
297 while(( task
= tr_list_pop_front( &web
->tasks
)))
299 dbgmsg( "adding task to curl: [%s]\n", task
->url
);
300 curl_multi_add_handle( multi
, createEasy( session
, task
));
301 /*fprintf( stderr, "adding a task.. taskCount is now %d\n", taskCount );*/
304 tr_lockUnlock( web
->taskLock
);
306 /* maybe wait a little while before calling curl_multi_perform() */
308 curl_multi_timeout( multi
, &msec
);
310 msec
= THREADFUNC_MAX_SLEEP_MSEC
;
316 fd_set r_fd_set
, w_fd_set
, c_fd_set
;
319 FD_ZERO( &r_fd_set
);
320 FD_ZERO( &w_fd_set
);
321 FD_ZERO( &c_fd_set
);
322 curl_multi_fdset( multi
, &r_fd_set
, &w_fd_set
, &c_fd_set
, &max_fd
);
324 if( msec
> THREADFUNC_MAX_SLEEP_MSEC
)
325 msec
= THREADFUNC_MAX_SLEEP_MSEC
;
328 t
.tv_sec
= usec
/ 1000000;
329 t
.tv_usec
= usec
% 1000000;
331 tr_select( max_fd
+1, &r_fd_set
, &w_fd_set
, &c_fd_set
, &t
);
334 /* call curl_multi_perform() */
336 mcode
= curl_multi_perform( multi
, &unused
);
337 } while( mcode
== CURLM_CALL_MULTI_PERFORM
);
339 /* pump completed tasks from the multi */
340 while(( msg
= curl_multi_info_read( multi
, &unused
)))
342 if(( msg
->msg
== CURLMSG_DONE
) && ( msg
->easy_handle
!= NULL
))
344 struct tr_web_task
* task
;
345 CURL
* e
= msg
->easy_handle
;
346 curl_easy_getinfo( e
, CURLINFO_PRIVATE
, (void*)&task
);
347 curl_easy_getinfo( e
, CURLINFO_RESPONSE_CODE
, &task
->code
);
348 curl_multi_remove_handle( multi
, e
);
349 curl_easy_cleanup( e
);
350 /*fprintf( stderr, "removing a completed task.. taskCount is now %d (response code: %d, response len: %d)\n", taskCount, (int)task->code, (int)EVBUFFER_LENGTH(task->response) );*/
351 tr_runInEventThread( task
->session
, task_finish_func
, task
);
358 curl_multi_cleanup( multi
);
359 tr_lockFree( web
->taskLock
);
365 tr_webInit( tr_session
* session
)
367 tr_threadNew( tr_webThreadFunc
, session
);
371 tr_webClose( tr_session
* session
, tr_web_close_mode close_mode
)
373 if( session
->web
!= NULL
)
375 session
->web
->close_mode
= close_mode
;
377 if( close_mode
== TR_WEB_CLOSE_NOW
)
378 while( session
->web
!= NULL
)
389 tr_webGetResponseStr( long code
)
393 case 0: return "No Response";
394 case 101: return "Switching Protocols";
395 case 200: return "OK";
396 case 201: return "Created";
397 case 202: return "Accepted";
398 case 203: return "Non-Authoritative Information";
399 case 204: return "No Content";
400 case 205: return "Reset Content";
401 case 206: return "Partial Content";
402 case 300: return "Multiple Choices";
403 case 301: return "Moved Permanently";
404 case 302: return "Found";
405 case 303: return "See Other";
406 case 304: return "Not Modified";
407 case 305: return "Use Proxy";
408 case 306: return "(Unused)";
409 case 307: return "Temporary Redirect";
410 case 400: return "Bad Request";
411 case 401: return "Unauthorized";
412 case 402: return "Payment Required";
413 case 403: return "Forbidden";
414 case 404: return "Not Found";
415 case 405: return "Method Not Allowed";
416 case 406: return "Not Acceptable";
417 case 407: return "Proxy Authentication Required";
418 case 408: return "Request Timeout";
419 case 409: return "Conflict";
420 case 410: return "Gone";
421 case 411: return "Length Required";
422 case 412: return "Precondition Failed";
423 case 413: return "Request Entity Too Large";
424 case 414: return "Request-URI Too Long";
425 case 415: return "Unsupported Media Type";
426 case 416: return "Requested Range Not Satisfiable";
427 case 417: return "Expectation Failed";
428 case 500: return "Internal Server Error";
429 case 501: return "Not Implemented";
430 case 502: return "Bad Gateway";
431 case 503: return "Service Unavailable";
432 case 504: return "Gateway Timeout";
433 case 505: return "HTTP Version Not Supported";
434 default: return "Unknown Error";
439 tr_http_escape( struct evbuffer
* out
,
440 const char * str
, int len
, tr_bool escape_slashes
)
444 if( ( len
< 0 ) && ( str
!= NULL
) )
447 for( end
=str
+len
; str
&& str
!=end
; ++str
) {
451 || ( ( '0' <= *str
) && ( *str
<= '9' ) )
452 || ( ( 'A' <= *str
) && ( *str
<= 'Z' ) )
453 || ( ( 'a' <= *str
) && ( *str
<= 'z' ) )
454 || ( ( *str
== '/' ) && ( !escape_slashes
) ) )
455 evbuffer_add( out
, str
, 1 );
457 evbuffer_add_printf( out
, "%%%02X", (unsigned)(*str
&0xFF) );
462 tr_http_unescape( const char * str
, int len
)
464 char * tmp
= curl_unescape( str
, len
);
465 char * ret
= tr_strdup( tmp
);