source: trunk/libtransmission/upnp.c @ 2328

Last change on this file since 2328 was 2328, checked in by charles, 15 years ago

clean up #includes a bit.

  • Property svn:keywords set to Date Rev Author Id
File size: 42.7 KB
Line 
1/******************************************************************************
2 * $Id: upnp.c 2328 2007-07-12 17:51:45Z charles $
3 *
4 * Copyright (c) 2006-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <stdarg.h>
26#include "transmission.h"
27#include "http.h"
28#include "net.h"
29#include "upnp.h"
30#include "xml.h"
31
32/* uncomment this to log requests and responses to ~/transmission-upnp.log */
33/* #define VERBOSE_LOG */
34
35#define SSDP_ADDR               "239.255.255.250"
36#define SSDP_PORT               1900
37#define SSDP_TYPE               "upnp:rootdevice"
38#define SSDP_SUBTYPE            "ssdp:alive"
39#define SSDP_FIRST_DELAY        3750    /* 3 3/4 seconds */
40#define SSDP_MAX_DELAY          1800000 /* 30 minutes */
41#define SERVICE_TYPE_IP         "urn:schemas-upnp-org:service:WANIPConnection:1"
42#define SERVICE_TYPE_PPP        "urn:schemas-upnp-org:service:WANPPPConnection:1"
43#define SOAP_ENVELOPE           "http://schemas.xmlsoap.org/soap/envelope/"
44#define LOOP_DETECT_THRESHOLD   10 /* error on 10 add/get/del state changes */
45#define MAPPING_CHECK_INTERVAL  900000 /* 15 minutes */
46#define HTTP_REQUEST_INTERVAL   500 /* half a second */
47#define SOAP_METHOD_NOT_ALLOWED 405
48#define IGD_GENERIC_ERROR       500
49#define IGD_GENERIC_FAILED      501
50#define IGD_NO_MAPPING_EXISTS   714
51#define IGD_ADD_CONFLICT        718
52#define IGD_NO_DYNAMIC_MAPPING  725
53
54typedef struct tr_upnp_action_s
55{
56    char * name;
57    int    len;
58    struct { char * name; char * var; char dir; } * args;
59} tr_upnp_action_t;
60
61typedef struct tr_upnp_device_s
62{
63    char                    * id;
64    char                    * host;
65    char                    * root;
66    int                       port;
67    int                       ppp;
68    char                    * soap;
69    char                    * scpd;
70    int                       mappedport;
71    char                    * myaddr;
72#define UPNPDEV_STATE_ROOT              1
73#define UPNPDEV_STATE_SCPD              2
74#define UPNPDEV_STATE_READY             3
75#define UPNPDEV_STATE_ADD               4
76#define UPNPDEV_STATE_GET               5
77#define UPNPDEV_STATE_DEL               6
78#define UPNPDEV_STATE_MAPPED            7
79#define UPNPDEV_STATE_ERROR             8
80    uint8_t                   state;
81    uint8_t                   looping;
82    uint64_t                  lastrequest;
83    uint64_t                  lastcheck;
84    unsigned int              soapretry : 1;
85    tr_http_t               * http;
86    tr_upnp_action_t          getcmd;
87    tr_upnp_action_t          addcmd;
88    tr_upnp_action_t          delcmd;
89    struct tr_upnp_device_s * next;
90} tr_upnp_device_t;
91
92struct tr_upnp_s
93{
94    int                port;
95    int                infd;
96    int                outfd;
97    uint64_t           lastdiscover;
98    uint64_t           lastdelay;
99    unsigned int       active : 1;
100    unsigned int       discovering : 1;
101    tr_upnp_device_t * devices;
102};
103
104static int
105sendSSDP( int fd );
106static int
107mcastStart();
108static void
109killSock( int * sock );
110static void
111killHttp( tr_http_t ** http );
112static int
113watchSSDP( tr_upnp_device_t ** devices, int fd );
114static tr_tristate_t
115recvSSDP( int fd, char * buf, int * len );
116static int
117parseSSDP( char * buf, int len, tr_http_header_t * headers );
118static void
119deviceAdd( tr_upnp_device_t ** first, const char * id, int idLen,
120           const char * url, int urlLen );
121static void
122deviceRemove( tr_upnp_device_t ** prevptr );
123static int
124deviceStop( tr_upnp_device_t * dev );
125static int
126devicePulse( tr_upnp_device_t * dev, int port );
127static int
128devicePulseHttp( tr_upnp_device_t * dev,
129                 const char ** body, int * len );
130static tr_http_t *
131devicePulseGetHttp( tr_upnp_device_t * dev );
132static int
133parseRoot( const char * root, const char *buf, int len,
134           char ** soap, char ** scpd, int * ppp );
135static void
136addUrlbase( const char * base, char ** path );
137static int
138parseScpd( const char *buf, int len, tr_upnp_action_t * getcmd,
139           tr_upnp_action_t * addcmd, tr_upnp_action_t * delcmd );
140static int
141parseScpdArgs( const char * buf, const char * end,
142               tr_upnp_action_t * action, char dir );
143static int
144parseMapping( tr_upnp_device_t * dev, const char * buf, int len );
145static char *
146joinstrs( const char *, const char *, const char * );
147static tr_http_t *
148soapRequest( int retry, const char * host, int port, const char * path,
149             const char * type, tr_upnp_action_t * action, ... );
150static void
151actionSetup( tr_upnp_action_t * action, const char * name, int prealloc );
152static void
153actionFree( tr_upnp_action_t * action );
154static int
155actionAdd( tr_upnp_action_t * action, char * name, char * var,
156                      char dir );
157#define actionLookupVar( act, nam, len, dir ) \
158    ( actionLookup( (act), (nam), (len), (dir), 0 ) )
159#define actionLookupName( act, var, len, dir ) \
160    ( actionLookup( (act), (var), (len), (dir), 1 ) )
161static const char *
162actionLookup( tr_upnp_action_t * action, const char * key, int len,
163              char dir, int getname );
164
165#ifdef VERBOSE_LOG
166static FILE * vlog = NULL;
167#endif
168
169tr_upnp_t *
170tr_upnpInit()
171{
172    tr_upnp_t * upnp;
173
174    upnp = calloc( 1, sizeof( *upnp ) );
175    if( NULL == upnp )
176    {
177        return NULL;
178    }
179
180    upnp->infd     = -1;
181    upnp->outfd    = -1;
182
183#ifdef VERBOSE_LOG
184    if( NULL == vlog )
185    {
186        char path[MAX_PATH_LENGTH];
187        time_t stupid_api;
188        snprintf( path, sizeof path, "%s/transmission-upnp.log",
189                  tr_getHomeDirectory());
190        vlog = fopen( path, "a" );
191        stupid_api = time( NULL );
192        fprintf( vlog, "opened log at %s\n\n", ctime( &stupid_api ) );
193    }
194#endif
195
196    return upnp;
197}
198
199void
200tr_upnpStart( tr_upnp_t * upnp )
201{
202    if( !upnp->active )
203    {
204        tr_inf( "starting upnp" );
205        upnp->active = 1;
206        upnp->discovering = 1;
207        upnp->infd = mcastStart();
208        upnp->lastdiscover = 0;
209        upnp->lastdelay = SSDP_FIRST_DELAY / 2;
210    }
211}
212
213void
214tr_upnpStop( tr_upnp_t * upnp )
215{
216    if( upnp->active )
217    {
218        tr_inf( "stopping upnp" );
219        upnp->active = 0;
220        killSock( &upnp->infd );
221        killSock( &upnp->outfd );
222    }
223}
224
225int
226tr_upnpStatus( tr_upnp_t * upnp )
227{
228    tr_upnp_device_t * ii;
229    int                ret;
230
231    if( !upnp->active )
232    {
233        ret = ( NULL == upnp->devices ?
234                TR_NAT_TRAVERSAL_DISABLED : TR_NAT_TRAVERSAL_UNMAPPING );
235    }
236    else if( NULL == upnp->devices )
237    {
238        ret = TR_NAT_TRAVERSAL_NOTFOUND;
239    }
240    else
241    {
242        ret = TR_NAT_TRAVERSAL_MAPPING;
243        for( ii = upnp->devices; NULL != ii; ii = ii->next )
244        {
245            if( UPNPDEV_STATE_ERROR == ii->state )
246            {
247                ret = TR_NAT_TRAVERSAL_ERROR;
248            }
249            else if( 0 < ii->mappedport )
250            {
251                ret = TR_NAT_TRAVERSAL_MAPPED;
252                break;
253            }
254        }
255    }
256
257    return ret;
258}
259
260void
261tr_upnpForwardPort( tr_upnp_t * upnp, int port )
262{
263    tr_dbg( "upnp port changed from %i to %i", upnp->port, port );
264    upnp->port = port;
265}
266
267void
268tr_upnpRemoveForwarding( tr_upnp_t * upnp )
269{
270    tr_dbg( "upnp port unset" );
271    upnp->port = 0;
272}
273
274void
275tr_upnpClose( tr_upnp_t * upnp )
276{
277    tr_upnpStop( upnp );
278
279    while( NULL != upnp->devices )
280    {
281        deviceRemove( &upnp->devices );
282    }
283
284    free( upnp );
285
286#ifdef VERBOSE_LOG
287    if( NULL != vlog )
288    {
289        fflush( vlog );
290    }
291#endif
292
293}
294
295void
296tr_upnpPulse( tr_upnp_t * upnp )
297{
298    tr_upnp_device_t ** ii;
299
300    if( upnp->active )
301    {
302        /* pulse on all known devices */
303        upnp->discovering = 1;
304        for( ii = &upnp->devices; NULL != *ii; ii = &(*ii)->next )
305        {
306            if( devicePulse( *ii, upnp->port ) )
307            {
308                upnp->discovering = 0;
309            }
310        }
311
312        /* send an SSDP discover message */
313        if( upnp->discovering &&
314            upnp->lastdelay + upnp->lastdiscover < tr_date() )
315        {
316            upnp->outfd = sendSSDP( upnp->outfd );
317            upnp->lastdiscover = tr_date();
318            upnp->lastdelay = MIN( upnp->lastdelay * 2, SSDP_MAX_DELAY );
319        }
320
321        /* try to receive SSDP messages */
322        watchSSDP( &upnp->devices, upnp->infd );
323        if( watchSSDP( &upnp->devices, upnp->outfd ) )
324        {
325            killSock( &upnp->outfd );
326        }
327    }
328    else
329    {
330        /* delete all mappings then delete devices */
331        ii = &upnp->devices;
332        while( NULL != *ii )
333        {
334            if( deviceStop( *ii ) )
335            {
336                deviceRemove( ii );
337            }
338            else
339            {
340                devicePulse( *ii, 0 );
341                ii = &(*ii)->next;
342            }
343        }
344    }
345}
346
347static int
348sendSSDP( int fd )
349{
350    char buf[102];
351    int  len;
352    struct sockaddr_in sin;
353
354    if( 0 > fd )
355    {
356        fd = tr_netBindUDP( 0 );
357        if( 0 > fd )
358        {
359            return -1;
360        }
361    }
362
363    tr_dbg( "sending upnp ssdp discover message" );
364
365    len = snprintf( buf, sizeof( buf ),
366                    "M-SEARCH * HTTP/1.1\r\n"
367                    "Host: %s:%i\r\n"
368                    "Man: \"ssdp:discover\"\r\n"
369                    "ST: %s\r\n"
370                    "MX: 3\r\n"
371                    "\r\n",
372                    SSDP_ADDR, SSDP_PORT, SSDP_TYPE );
373
374    /* if this assertion ever fails then just increase the size of buf */
375    assert( (int) sizeof( buf ) > len );
376
377    memset( &sin, 0, sizeof( sin ) );
378    sin.sin_family      = AF_INET;
379    sin.sin_addr.s_addr = inet_addr( SSDP_ADDR );
380    sin.sin_port        = htons( SSDP_PORT );
381
382#ifdef VERBOSE_LOG
383    fprintf( vlog, "send ssdp message, %i bytes:\n", len );
384    fwrite( buf, 1, len, vlog );
385    fputs( "\n\n", vlog );
386#endif
387
388    if( 0 > sendto( fd, buf, len, 0,
389                    (struct sockaddr*) &sin, sizeof( sin ) ) )
390    {
391        if( EAGAIN != errno )
392        {
393            tr_err( "Could not send SSDP discover message (%s)",
394                    strerror( errno ) );
395        }
396        killSock( &fd );
397        return -1;
398    }
399
400    return fd;
401}
402
403static int
404mcastStart()
405{
406    int fd;
407    struct in_addr addr;
408
409    addr.s_addr = inet_addr( SSDP_ADDR );
410    fd = tr_netMcastOpen( SSDP_PORT, addr );
411    if( 0 > fd )
412    {
413        return -1;
414    }
415
416    return fd;
417}
418
419static void
420killSock( int * sock )
421{
422    if( 0 <= *sock )
423    {
424        tr_netClose( *sock );
425        *sock = -1;
426    }
427}
428
429static void
430killHttp( tr_http_t ** http )
431{
432    tr_httpClose( *http );
433    *http = NULL;
434}
435
436static int
437watchSSDP( tr_upnp_device_t ** devices, int fd )
438{
439    /* XXX what if we get a huge SSDP packet? */
440    char buf[512];
441    int len;
442    tr_http_header_t hdr[] = {
443        /* first one must be type and second must be subtype */
444        { NULL,            NULL, 0 },
445        { "NTS",           NULL, 0 },
446        /* XXX should probably look at this
447           { "Cache-control", NULL, 0 }, */
448        { "Location",      NULL, 0 },
449        { "USN",           NULL, 0 },
450        { NULL,            NULL, 0 }
451    };
452    enum { OFF_TYPE = 0, OFF_SUBTYPE, OFF_LOC, OFF_ID };
453    int ret;
454
455    if( 0 > fd )
456    {
457        return 0;
458    }
459
460    ret = 0;
461    for(;;)
462    {
463        len = sizeof( buf );
464        switch( recvSSDP( fd, buf, &len ) )
465        {
466            case TR_NET_WAIT:
467                return ret;
468            case TR_NET_ERROR:
469                return 1;
470            case TR_NET_OK:
471                ret = 1;
472                if( parseSSDP( buf, len, hdr ) &&
473                    NULL != hdr[OFF_LOC].data &&
474                    NULL != hdr[OFF_ID].data )
475                {
476                    deviceAdd( devices, hdr[OFF_ID].data, hdr[OFF_ID].len,
477                               hdr[OFF_LOC].data, hdr[OFF_LOC].len );
478                }
479        }
480    }
481}
482
483static tr_tristate_t
484recvSSDP( int fd, char * buf, int * len )
485{
486    if( 0 > fd )
487    {
488        return TR_NET_ERROR;
489    }
490
491    *len = tr_netRecv( fd, ( uint8_t * ) buf, *len );
492    if( TR_NET_BLOCK & *len )
493    {
494        return TR_NET_WAIT;
495    }
496    else if( TR_NET_CLOSE & *len )
497    {
498        tr_err( "Could not receive SSDP message (%s)", strerror( errno ) );
499        return TR_NET_ERROR;
500    }
501    else
502    {
503#ifdef VERBOSE_LOG
504        fprintf( vlog, "receive ssdp message, %i bytes:\n", *len );
505        fwrite( buf, 1, *len, vlog );
506        fputs( "\n\n", vlog );
507#endif
508        return TR_NET_OK;
509    }
510}
511
512static int
513parseSSDP( char * buf, int len, tr_http_header_t * hdr )
514{
515    char *method, *uri, *body;
516    int code;
517
518    body = NULL;
519    /* check for an HTTP NOTIFY request */
520    if( 0 <= tr_httpRequestType( buf, len, &method, &uri ) )
521    {
522        if( 0 == tr_strcasecmp( method, "NOTIFY" ) && 0 == strcmp( uri, "*" ) )
523        {
524            hdr[0].name = "NT";
525            body = tr_httpParse( buf, len, hdr );
526            if( NULL == hdr[1].name ||
527                0 != tr_strncasecmp( SSDP_SUBTYPE, hdr[1].data, hdr[1].len ) )
528            {
529                body = NULL;
530            }
531            else
532            {
533                tr_dbg( "found upnp ssdp notify request" );
534            }
535        }
536        free( method );
537        free( uri );
538    }
539    else
540    {
541        /* check for a response to our HTTP M-SEARCH request */
542        code = tr_httpResponseCode( buf, len );
543        if( TR_HTTP_STATUS_OK( code ) )
544        {
545            hdr[0].name = "ST";
546            body = tr_httpParse( buf, len, hdr );
547            if( NULL != body )
548            {
549                tr_dbg( "found upnp ssdp m-search response" );
550            }
551        }
552    }
553
554    /* did we find enough information to be useful? */
555    if( NULL != body )
556    {
557        /* the first header is the type */
558        if( NULL != hdr[0].data &&
559            0 == tr_strncasecmp( SSDP_TYPE, hdr[0].data, hdr[0].len ) )
560        {
561            return 1;
562        }
563    }
564
565    return 0;
566}
567
568static void
569deviceAdd( tr_upnp_device_t ** first, const char * id, int idLen,
570           const char * url, int urlLen )
571{
572    tr_upnp_device_t * ii;
573
574    for( ii = *first; NULL != ii; ii = ii->next )
575    {
576        if( 0 == tr_strncasecmp( ii->id, id, idLen ) )
577        {
578            /* this device may have gone away and came back, recheck it */
579            ii->lastcheck = 0;
580            return;
581        }
582    }
583
584    ii = malloc( sizeof( *ii ) );
585    if( NULL == ii )
586    {
587        return;
588    }
589    memset( ii, 0, sizeof( *ii ) );
590    if( tr_httpParseUrl( url, urlLen, &ii->host, &ii->port, &ii->root ) )
591    {
592        tr_err( "Invalid HTTP URL from UPnP" );
593        free( ii );
594        return;
595    }
596    ii->id = tr_strndup( id, idLen );
597    ii->state = UPNPDEV_STATE_ROOT;
598    actionSetup( &ii->getcmd, "GetSpecificPortMappingEntry", 8 );
599    actionSetup( &ii->addcmd, "AddPortMapping", 8 );
600    actionSetup( &ii->delcmd, "DeletePortMapping", 3 );
601    ii->next = *first;
602    *first = ii;
603
604    tr_inf( "new upnp device %s, port %i, path %s",
605            ii->host, ii->port, ii->root );
606}
607
608static void
609deviceRemove( tr_upnp_device_t ** prevptr )
610{
611    tr_upnp_device_t * dead;
612
613    dead = *prevptr;
614    *prevptr = dead->next;
615
616    tr_inf( "forgetting upnp device %s", dead->host );
617
618    free( dead->id );
619    free( dead->host );
620    free( dead->root );
621    free( dead->soap );
622    free( dead->scpd );
623    free( dead->myaddr );
624    if( NULL != dead->http )
625    {
626        killHttp( &dead->http );
627    }
628    actionFree( &dead->getcmd );
629    actionFree( &dead->addcmd );
630    actionFree( &dead->delcmd );
631    free( dead );
632}
633
634static int
635deviceStop( tr_upnp_device_t * dev )
636{
637    switch( dev->state )
638    {
639        case UPNPDEV_STATE_READY:
640        case UPNPDEV_STATE_ERROR:
641            return 1;
642        case UPNPDEV_STATE_MAPPED:
643            tr_dbg( "upnp device %s: stopping upnp, state mapped -> delete",
644                dev->host );
645            dev->state = UPNPDEV_STATE_DEL;
646            return 0;
647        default:
648            return 0;
649    }
650}
651
652static int
653devicePulse( tr_upnp_device_t * dev, int port )
654{
655    const char * body;
656    int          len, code;
657    uint8_t      laststate;
658
659    switch( dev->state )
660    {
661        case UPNPDEV_STATE_READY:
662            if( 0 < port )
663            {
664                tr_dbg( "upnp device %s: want mapping, state ready -> get",
665                        dev->host );
666                dev->mappedport = port;
667                dev->state = UPNPDEV_STATE_GET;
668                break;
669            }
670            return 1;
671        case UPNPDEV_STATE_MAPPED:
672            if( port != dev->mappedport )
673            {
674                tr_dbg( "upnp device %s: change mapping, "
675                        "state mapped -> delete", dev->host );
676                dev->state = UPNPDEV_STATE_DEL;
677                break;
678            }
679            if( tr_date() > dev->lastcheck + MAPPING_CHECK_INTERVAL )
680            {
681                tr_dbg( "upnp device %s: check mapping, "
682                        "state mapped -> get", dev->host );
683                dev->state = UPNPDEV_STATE_GET;
684            }
685            return 1;
686        case UPNPDEV_STATE_ERROR:
687            return 0;
688    }
689
690    /* gcc can be pretty annoying about it's warnings sometimes */
691    len = 0;
692    body = NULL;
693
694    code = devicePulseHttp( dev, &body, &len );
695    if( 0 > code )
696    {
697        return 1;
698    }
699
700    if( LOOP_DETECT_THRESHOLD <= dev->looping )
701    {
702        tr_dbg( "upnp device %s: loop detected, state %hhu -> error",
703                dev->host, dev->state );
704        dev->state = UPNPDEV_STATE_ERROR;
705        dev->looping = 0;
706        killHttp( &dev->http );
707        return 1;
708    }
709
710    laststate = dev->state;
711    dev->state = UPNPDEV_STATE_ERROR;
712    switch( laststate ) 
713    {
714        case UPNPDEV_STATE_ROOT:
715            if( !TR_HTTP_STATUS_OK( code ) )
716            {
717                tr_dbg( "upnp device %s: fetch root failed with http code %i",
718                        dev->host, code );
719            }
720            else if( parseRoot( dev->root, body, len,
721                                &dev->soap, &dev->scpd, &dev->ppp ) )
722            {
723                tr_dbg( "upnp device %s: parse root failed", dev->host );
724            }
725            else
726            {
727                tr_dbg( "upnp device %s: found scpd \"%s\" and soap \"%s\"",
728                        dev->root, dev->scpd, dev->soap );
729                tr_dbg( "upnp device %s: parsed root, state root -> scpd",
730                        dev->host );
731                dev->state = UPNPDEV_STATE_SCPD;
732            }
733            break;
734
735        case UPNPDEV_STATE_SCPD:
736            if( !TR_HTTP_STATUS_OK( code ) )
737            {
738                tr_dbg( "upnp device %s: fetch scpd failed with http code %i",
739                        dev->host, code );
740            }
741            else if( parseScpd( body, len, &dev->getcmd,
742                                &dev->addcmd, &dev->delcmd ) )
743            {
744                tr_dbg( "upnp device %s: parse scpd failed", dev->host );
745            }
746            else
747            {
748                tr_dbg( "upnp device %s: parsed scpd, state scpd -> ready",
749                        dev->host );
750                dev->state = UPNPDEV_STATE_READY;
751                dev->looping = 0;
752            }
753            break;
754
755        case UPNPDEV_STATE_ADD:
756            dev->looping++;
757            if( IGD_ADD_CONFLICT == code )
758            {
759                tr_dbg( "upnp device %s: add conflict, state add -> delete",
760                        dev->host );
761                dev->state = UPNPDEV_STATE_DEL;
762            }
763            else if( TR_HTTP_STATUS_OK( code ) ||
764                     IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
765            {
766                tr_dbg( "upnp device %s: add attempt, state add -> get",
767                        dev->host );
768                dev->state = UPNPDEV_STATE_GET;
769            }
770            else
771            {
772                tr_dbg( "upnp device %s: add failed with http code %i",
773                        dev->host, code );
774            }
775            break;
776
777        case UPNPDEV_STATE_GET:
778            dev->looping++;
779            if( TR_HTTP_STATUS_OK( code ) )
780            {
781                switch( parseMapping( dev, body, len ) )
782                {
783                    case -1:
784                        break;
785                    case 0:
786                        tr_dbg( "upnp device %s: invalid mapping, "
787                                "state get -> delete", dev->host );
788                        dev->state = UPNPDEV_STATE_DEL;
789                        break;
790                    case 1:
791                        tr_dbg( "upnp device %s: good mapping, "
792                                "state get -> mapped", dev->host );
793                        dev->state = UPNPDEV_STATE_MAPPED;
794                        dev->looping = 0;
795                        dev->lastcheck = tr_date();
796                        tr_inf( "upnp successful for port %i",
797                                dev->mappedport );
798                        break;
799                    default:
800                        assert( 0 );
801                        break;
802                }
803            }
804            else if( IGD_NO_MAPPING_EXISTS == code ||
805                     IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
806            {
807                tr_dbg( "upnp device %s: no mapping, state get -> add",
808                        dev->host );
809                dev->state = UPNPDEV_STATE_ADD;
810            }
811            else
812            {
813                tr_dbg( "upnp device %s: get failed with http code %i",
814                        dev->host, code );
815            }
816            break;
817
818        case UPNPDEV_STATE_DEL:
819            dev->looping++;
820            if( TR_HTTP_STATUS_OK( code ) || IGD_NO_MAPPING_EXISTS == code ||
821                IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
822            {
823                tr_dbg( "upnp device %s: deleted, state delete -> ready",
824                        dev->host );
825                dev->state = UPNPDEV_STATE_READY;
826                dev->looping = 0;
827            }
828            else
829            {
830                tr_dbg( "upnp device %s: del failed with http code %i",
831                        dev->host, code );
832            }
833            break;
834
835        default:
836            assert( 0 );
837            break;
838    }
839
840    dev->lastrequest = tr_date();
841    killHttp( &dev->http );
842
843    if( UPNPDEV_STATE_ERROR == dev->state )
844    {
845        tr_dbg( "upnp device %s: error, state %hhu -> error",
846                dev->host, laststate );
847        return 0;
848    }
849
850    return 1;
851}
852
853static tr_http_t *
854makeHttp( int method, const char * host, int port, const char * path )
855{
856    if( tr_httpIsUrl( path, -1 ) )
857    {
858        return tr_httpClientUrl( method, "%s", path );
859    }
860    else
861    {
862        return tr_httpClient( method, host, port, "%s", path );
863    }
864}
865
866static tr_http_t *
867devicePulseGetHttp( tr_upnp_device_t * dev )
868{
869    tr_http_t  * ret;
870    char         numstr[6];
871    const char * type;
872#ifdef VERBOSE_LOG
873    const char * body;
874    int          len;
875#endif
876
877    ret = NULL;
878    switch( dev->state ) 
879    {
880        case UPNPDEV_STATE_ROOT:
881            if( !dev->soapretry )
882            {
883                ret = makeHttp( TR_HTTP_GET, dev->host, dev->port, dev->root );
884            }
885            break;
886        case UPNPDEV_STATE_SCPD:
887            if( !dev->soapretry )
888            {
889                ret = makeHttp( TR_HTTP_GET, dev->host, dev->port, dev->scpd );
890            }
891            break;
892        case UPNPDEV_STATE_ADD:
893            if( NULL == dev->myaddr )
894            {
895                ret = NULL;
896                break;
897            }
898            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
899            type = ( dev->ppp ? SERVICE_TYPE_PPP : SERVICE_TYPE_IP );
900            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
901                               type, &dev->addcmd,
902                               "PortMappingEnabled", "1",
903                               "PortMappingLeaseDuration", "0",
904                               "RemoteHost", "",
905                               "ExternalPort", numstr,
906                               "InternalPort", numstr,
907                               "PortMappingProtocol", "TCP",
908                               "InternalClient", dev->myaddr,
909                               "PortMappingDescription", "Added by " TR_NAME,
910                               NULL );
911            break;
912        case UPNPDEV_STATE_GET:
913            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
914            type = ( dev->ppp ? SERVICE_TYPE_PPP : SERVICE_TYPE_IP );
915            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
916                               type, &dev->getcmd,
917                               "RemoteHost", "",
918                               "ExternalPort", numstr,
919                               "PortMappingProtocol", "TCP",
920                               NULL );
921            break;
922        case UPNPDEV_STATE_DEL:
923            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
924            type = ( dev->ppp ? SERVICE_TYPE_PPP : SERVICE_TYPE_IP );
925            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
926                               type, &dev->delcmd,
927                               "RemoteHost", "",
928                               "ExternalPort", numstr,
929                               "PortMappingProtocol", "TCP",
930                               NULL );
931            break;
932        default:
933            assert( 0 );
934            break;
935    }
936
937#ifdef VERBOSE_LOG
938    if( NULL != ret )
939    {
940        tr_httpGetHeaders( ret, &body, &len );
941        fprintf( vlog, "send http message, %i bytes (headers):\n", len );
942        fwrite( body, 1, len, vlog );
943        tr_httpGetBody( ret, &body, &len );
944        fprintf( vlog, "\n\nsend http message, %i bytes (body):\n", len );
945        fwrite( body, 1, len, vlog );
946        fputs( "\n\n", vlog );
947    }
948#endif
949
950    return ret;
951}
952
953static int
954devicePulseHttp( tr_upnp_device_t * dev,
955                 const char ** body, int * len )
956{
957    const char * headers;
958    int          hlen, code;
959
960    if( NULL == dev->http )
961    {
962        if( tr_date() < dev->lastrequest + HTTP_REQUEST_INTERVAL )
963        {
964            return -1;
965        }
966        dev->lastrequest = tr_date();
967        dev->http = devicePulseGetHttp( dev );
968        if( NULL == dev->http )
969        {
970            tr_dbg( "upnp device %s: http init failed, state %hhu -> error",
971                    dev->host, dev->state );
972            dev->state = UPNPDEV_STATE_ERROR;
973            dev->soapretry = 0;
974            return -1;
975        }
976    }
977
978    if( NULL == dev->myaddr )
979    {
980        dev->myaddr = tr_httpWhatsMyAddress( dev->http );
981    }
982
983    switch( tr_httpPulse( dev->http, &headers, &hlen ) )
984    {
985        case TR_NET_OK:
986#ifdef VERBOSE_LOG
987    fprintf( vlog, "receive http message, %i bytes:\n", hlen );
988    fwrite( headers, 1, hlen, vlog );
989    fputs( "\n\n", vlog );
990#endif
991            code = tr_httpResponseCode( headers, hlen );
992            if( SOAP_METHOD_NOT_ALLOWED == code && !dev->soapretry )
993            {
994                dev->soapretry = 1;
995                killHttp( &dev->http );
996                break;
997            }
998            dev->soapretry = 0;
999            *body = tr_httpParse( headers, hlen, NULL );
1000            *len = ( NULL == body ? 0 : hlen - ( *body - headers ) );
1001            return code;
1002        case TR_NET_ERROR:
1003            killHttp( &dev->http );
1004            if( dev->soapretry )
1005            {
1006                tr_dbg( "upnp device %s: http pulse failed, state %hhu -> error",
1007                        dev->host, dev->state );
1008                dev->state = UPNPDEV_STATE_ERROR;
1009                dev->soapretry = 0;
1010            }
1011            else
1012            {
1013                dev->soapretry = 1;
1014            }
1015            break;
1016        case TR_NET_WAIT:
1017            break;
1018    }
1019
1020    return -1;
1021}
1022
1023static int
1024parseRoot( const char * root, const char *buf, int len,
1025           char ** soap, char ** scpd, int * ppp )
1026{
1027    const char * end, * ii, * jj, * kk, * urlbase;
1028    char       * basedup;
1029
1030    *soap = NULL;
1031    *scpd = NULL;
1032    end = buf + len;
1033
1034    buf = tr_xmlFindTagContents( buf, end, "root" );
1035    urlbase = tr_xmlFindTag( buf, end, "urlBase" );
1036    urlbase = tr_xmlTagContents( urlbase, end );
1037    buf = tr_xmlFindTagContents( buf, end, "device" );
1038    if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
1039            "urn:schemas-upnp-org:device:InternetGatewayDevice:1", 1 ) )
1040    {
1041        return 1;
1042    }
1043    buf = tr_xmlFindTag( buf, end, "deviceList" );
1044    ii = tr_xmlTagContents( buf, end );
1045    for( ; NULL != ii; ii = tr_xmlSkipTag( ii, end ) )
1046    {
1047        ii = tr_xmlFindTag( ii, end, "device" );
1048        buf = tr_xmlTagContents( ii, end );
1049        if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
1050                "urn:schemas-upnp-org:device:WANDevice:1", 1 ) )
1051        {
1052            continue;
1053        }
1054        buf = tr_xmlFindTag( buf, end, "deviceList" );
1055        jj = tr_xmlTagContents( buf, end );
1056        for( ; NULL != jj; jj = tr_xmlSkipTag( jj, end ) )
1057        {
1058            jj = tr_xmlFindTag( jj, end, "device" );
1059            buf = tr_xmlTagContents( jj, end );
1060            if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
1061                    "urn:schemas-upnp-org:device:WANConnectionDevice:1", 1 ) )
1062            {
1063                continue;
1064            }
1065            buf = tr_xmlFindTag( buf, end, "serviceList" );
1066            kk = tr_xmlTagContents( buf, end );
1067            for( ; NULL != kk; kk = tr_xmlSkipTag( kk, end ) )
1068            {
1069                kk = tr_xmlFindTag( kk, end, "service" );
1070                buf = tr_xmlTagContents( kk, end );
1071                if( !tr_xmlFindTagVerifyContents( buf, end, "serviceType",
1072                                                  SERVICE_TYPE_IP, 1 ) )
1073                {
1074                    *soap = tr_xmlDupTagContents( buf, end, "controlURL");
1075                    *scpd = tr_xmlDupTagContents( buf, end, "SCPDURL");
1076                    *ppp  = 0;
1077                    break;
1078                }
1079                /* XXX we should save all services of both types and
1080                   try adding mappings for each in turn */
1081                else if( !tr_xmlFindTagVerifyContents( buf, end, "serviceType",
1082                                                       SERVICE_TYPE_PPP, 1 ) )
1083                {
1084                    *soap = tr_xmlDupTagContents( buf, end, "controlURL");
1085                    *scpd = tr_xmlDupTagContents( buf, end, "SCPDURL");
1086                    *ppp  = 1;
1087                    break;
1088                }
1089            }
1090        }
1091    }
1092
1093    if( NULL == urlbase )
1094    {
1095        basedup = strrchr( root, '/' );
1096        assert( NULL != basedup );
1097        basedup = tr_strndup( root, basedup - root + 1 );
1098    }
1099    else
1100    {
1101        basedup = tr_xmlDupContents( urlbase, end );
1102    }
1103    addUrlbase( basedup, soap );
1104    addUrlbase( basedup, scpd );
1105    free( basedup );
1106
1107    if( NULL != *soap && NULL != *scpd )
1108    {
1109        return 0;
1110    }
1111
1112    return 1;
1113}
1114
1115static void
1116addUrlbase( const char * base, char ** path )
1117{
1118    const char * middle;
1119    int          len;
1120    char       * joined;
1121
1122    if( NULL == base || NULL == *path ||
1123        '/' == **path || tr_httpIsUrl( *path, -1 ) )
1124    {
1125        return;
1126    }
1127
1128    len = strlen( base );
1129    middle = ( 0 >= len || '/' != base[len-1] ? "/" : "" );
1130    joined = joinstrs( base, middle, *path );
1131    free( *path );
1132    *path = joined;
1133}
1134
1135static int
1136parseScpd( const char * buf, int len, tr_upnp_action_t * getcmd,
1137           tr_upnp_action_t * addcmd, tr_upnp_action_t * delcmd )
1138{
1139    const char * end, * next, * sub, * name;
1140
1141    end = buf + len;
1142    next = buf;
1143
1144    next = tr_xmlFindTagContents( next, end, "scpd" );
1145    next = tr_xmlFindTagContents( next, end, "actionList" );
1146
1147    while( NULL != next )
1148    {
1149        next = tr_xmlFindTag( next, end, "action" );
1150        sub = tr_xmlTagContents( next, end );
1151        name = tr_xmlFindTagContents( sub, end, "name" );
1152        sub = tr_xmlFindTagContents( sub, end, "argumentList" );
1153        if( !tr_xmlVerifyContents( name, end, getcmd->name, 1 ) )
1154        {
1155            if( parseScpdArgs( sub, end, getcmd, 'i' ) ||
1156                parseScpdArgs( sub, end, getcmd, 'o' ) )
1157            {
1158                return 1;
1159            }
1160        }
1161        else if( !tr_xmlVerifyContents( name, end, addcmd->name, 1 ) )
1162        {
1163            if( parseScpdArgs( sub, end, addcmd, 'i' ) )
1164            {
1165                return 1;
1166            }
1167        }
1168        else if( !tr_xmlVerifyContents( name, end, delcmd->name, 1 ) )
1169        {
1170            if( parseScpdArgs( sub, end, delcmd, 'i' ) )
1171            {
1172                return 1;
1173            }
1174        }
1175        next = tr_xmlSkipTag( next, end );
1176    }
1177
1178    return 0;
1179}
1180
1181static int
1182parseScpdArgs( const char * buf, const char * end,
1183               tr_upnp_action_t * action, char dir )
1184{
1185    const char * sub, * which;
1186    char       * name, * var;
1187
1188    assert( 'i' == dir || 'o' == dir );
1189    which = ( 'i' == dir ? "in" : "out" );
1190
1191    while( NULL != buf )
1192    {
1193        sub = tr_xmlFindTagContents( buf, end, "argument" );
1194        if( !tr_xmlFindTagVerifyContents( sub, end, "direction", which, 1 ) )
1195        {
1196            name = tr_xmlDupTagContents( sub, end, "name" );
1197            var = tr_xmlDupTagContents( sub, end, "relatedStateVariable" );
1198            if( NULL == name || NULL == var )
1199            {
1200                free( name );
1201                free( var );
1202            }
1203            else if( actionAdd( action, name, var, dir ) )
1204            {
1205                return 1;
1206            }
1207        }
1208        buf = tr_xmlSkipTag( buf, end );
1209    }
1210
1211    return 0;
1212}
1213
1214static int
1215parseMapping( tr_upnp_device_t * dev, const char * buf, int len )
1216{
1217    const char * end, * down, * next, * var;
1218    int          varlen, pret, cret, eret;
1219    char       * val;
1220
1221    assert( 0 < dev->mappedport );
1222
1223    if( NULL == dev->myaddr )
1224    {
1225        return -1;
1226    }
1227
1228    pret = -1;
1229    cret = -1;
1230    eret = -1;
1231
1232    end = buf + len;
1233    down = buf;
1234    down = tr_xmlFindTagContents( down, end, "Envelope" );
1235    down = tr_xmlFindTagContents( down, end, "Body" );
1236    down = tr_xmlFindTagContents( down, end,
1237                                  "GetSpecificPortMappingEntryResponse" );
1238
1239    next = down;
1240    while( NULL != next )
1241    {
1242        var = tr_xmlTagName( next, end, &varlen );
1243        var = actionLookupVar( &dev->getcmd, var, varlen, 'o' );
1244        if( NULL != var )
1245        {
1246            val = tr_xmlDupContents( tr_xmlTagContents( next, end ), end );
1247            if( 0 == tr_strcasecmp( "InternalPort", var ) )
1248            {
1249                pret = ( strtol( val, NULL, 10 ) == dev->mappedport ? 1 : 0 );
1250            }
1251            else if( 0 == tr_strcasecmp( "InternalClient", var ) )
1252            {
1253                cret = ( 0 == strcmp( dev->myaddr, val ) ? 1 : 0 );
1254            }
1255            else if( 0 == tr_strcasecmp( "PortMappingEnabled", var ) )
1256            {
1257                eret = ( 0 == strcmp( "1", val ) ? 1 : 0 );
1258            }
1259            free( val );
1260        }
1261        next = tr_xmlSkipTag( next, end );
1262    }
1263
1264    return MIN( MIN( pret, cret), eret );
1265}
1266
1267static char *
1268joinstrs( const char * first, const char * delim, const char * second )
1269{
1270    char * ret;
1271    int    len1, len2, len3;
1272
1273    len1 = strlen( first );
1274    len2 = strlen( delim );
1275    len3 = strlen( second );
1276    ret = malloc( len1 + len2 + len3 + 1 );
1277    if( NULL == ret )
1278    {
1279        return NULL;
1280    }
1281
1282    memcpy( ret, first, len1 );
1283    memcpy( ret + len1, delim, len2 );
1284    memcpy( ret + len1 + len2, second, len3 );
1285    ret[len1 + len2 + len3] = '\0';
1286
1287    return ret;
1288}
1289
1290static tr_http_t *
1291soapRequest( int retry, const char * host, int port, const char * path,
1292             const char * type, tr_upnp_action_t * action, ... )
1293{
1294    tr_http_t  * http;
1295    va_list      ap;
1296    const char * name, * value;
1297    int          method;
1298    char       * actstr;
1299
1300    method = ( retry ? TR_HTTP_M_POST : TR_HTTP_POST );
1301    http = makeHttp( method, host, port, path );
1302    if( NULL != http )
1303    {
1304        tr_httpAddHeader( http, "Content-type",
1305                          "text/xml; encoding=\"utf-8\"" );
1306        actstr = NULL;
1307        asprintf( &actstr, "\"%s#%s\"", type, action->name );
1308        tr_httpAddHeader( http, "SOAPAction", actstr );
1309        free( actstr );
1310        if( retry )
1311        {
1312            tr_httpAddHeader( http, "Man", "\"" SOAP_ENVELOPE "\"" );
1313        }
1314        tr_httpAddBody( http, 
1315"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1316"<s:Envelope"
1317"    xmlns:s=\"" SOAP_ENVELOPE "\""
1318"    s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1319"  <s:Body>"
1320"    <u:%s xmlns:u=\"%s\">", action->name, type );
1321
1322        va_start( ap, action );
1323        do
1324        {
1325            name = va_arg( ap, const char * );
1326            value = NULL;
1327            name = actionLookupName( action, name, -1, 'i' );
1328            if( NULL != name )
1329            {
1330                value = va_arg( ap, const char * );
1331                if( NULL != value )
1332                {
1333                    tr_httpAddBody( http,
1334"      <%s>%s</%s>", name, value, name );
1335                }
1336                else 
1337                {
1338                    tr_httpAddBody( http,
1339"      <%s></%s>", name, name );
1340                }
1341            }
1342        }
1343        while( NULL != name && NULL != value );
1344        va_end( ap );
1345
1346        tr_httpAddBody( http,
1347"    </u:%s>"
1348"  </s:Body>"
1349"</s:Envelope>", action->name );
1350    }
1351
1352    return http;
1353}
1354
1355static void
1356actionSetup( tr_upnp_action_t * action, const char * name, int prealloc )
1357{
1358    action->name = strdup( name );
1359    assert( NULL == action->args );
1360    action->args = malloc( sizeof( *action->args ) * prealloc );
1361    memset( action->args, 0, sizeof( *action->args ) * prealloc );
1362    action->len = prealloc;
1363}
1364
1365static void
1366actionFree( tr_upnp_action_t * act )
1367{
1368    free( act->name );
1369    while( 0 < act->len )
1370    {
1371        act->len--;
1372        free( act->args[act->len].name );
1373        free( act->args[act->len].var );
1374    }
1375    free( act->args );
1376}
1377
1378static int
1379actionAdd( tr_upnp_action_t * act, char * name, char * var, char dir )
1380{
1381    int    ii;
1382    void * newbuf;
1383
1384    assert( 'i' == dir || 'o' == dir );
1385
1386    ii = 0;
1387    while( ii < act->len && NULL != act->args[ii].name )
1388    {
1389        ii++;
1390    }
1391
1392    if( ii == act->len )
1393    {
1394        newbuf = realloc( act->args, sizeof( *act->args ) * ( act->len + 1 ) );
1395        if( NULL == newbuf )
1396        {
1397            return 1;
1398        }
1399        act->args = newbuf;
1400        act->len++;
1401    }
1402
1403    act->args[ii].name = name;
1404    act->args[ii].var = var;
1405    act->args[ii].dir = dir;
1406
1407    return 0;
1408}
1409
1410static const char *
1411actionLookup( tr_upnp_action_t * act, const char * key, int len,
1412              char dir, int getname )
1413{
1414    int ii;
1415
1416    assert( 'i' == dir || 'o' == dir );
1417
1418    if( NULL == key || 0 == len )
1419    {
1420        return NULL;
1421    }
1422    if( 0 > len )
1423    {
1424        len = strlen( key );
1425    }
1426
1427    for( ii = 0; ii < act->len; ii++ )
1428    {
1429        if( NULL != act->args[ii].name && dir == act->args[ii].dir )
1430        {
1431            if( !getname &&
1432                0 == tr_strncasecmp( act->args[ii].name, key, len ) )
1433            {
1434                return act->args[ii].var;
1435            }
1436            else if( getname &&
1437                     0 == tr_strncasecmp( act->args[ii].var, key, len ) )
1438            {
1439                return act->args[ii].name;
1440            }
1441        }
1442    }
1443
1444    return NULL;
1445}
1446
1447#if 0
1448/* this code is used for standalone root parsing for debugging purposes */
1449/* cc -g -Wall -D__TRANSMISSION__ -o upnp upnp.c xml.c utils.c */
1450int
1451main( int argc, char * argv[] )
1452{
1453    struct stat sb;
1454    char      * data, * soap, * scpd;
1455    int         fd, ppp;
1456    ssize_t     res;
1457
1458    if( 3 != argc )
1459    {
1460        printf( "usage: %s root-url root-file\n", argv[0] );
1461        return 0;
1462    }
1463
1464    tr_msgInit();
1465    tr_setMessageLevel( 9 );
1466
1467    if( 0 > stat( argv[2], &sb ) )
1468    {
1469        tr_err( "failed to stat file %s: %s", argv[2], strerror( errno ) );
1470        return 1;
1471    }
1472
1473    data = malloc( sb.st_size );
1474    if( NULL == data )
1475    {
1476        tr_err( "failed to malloc %zd bytes", ( size_t )sb.st_size );
1477        return 1;
1478    }
1479
1480    fd = open( argv[2], O_RDONLY );
1481    if( 0 > fd )
1482    {
1483        tr_err( "failed to open file %s: %s", argv[2], strerror( errno ) );
1484        free( data );
1485        return 1;
1486    }
1487
1488    res = read( fd, data, sb.st_size );
1489    if( sb.st_size > res )
1490    {
1491        tr_err( "failed to read file %s: %s", argv[2],
1492                ( 0 > res ? strerror( errno ) : "short read count" ) );
1493        close( fd );
1494        free( data );
1495        return 1;
1496    }
1497
1498    close( fd );
1499
1500    if( parseRoot( argv[1], data, sb.st_size, &soap, &scpd, &ppp ) )
1501    {
1502        tr_err( "root parsing failed" );
1503    }
1504    else
1505    {
1506        tr_err( "soap=%s scpd=%s ppp=%s", soap, scpd, ( ppp ? "yes" : "no" ) );
1507        free( soap );
1508        free( scpd );
1509    }
1510    free( data );
1511
1512    return 0;
1513}
1514
1515int  tr_netMcastOpen( int port, struct in_addr addr ) { assert( 0 ); }
1516int  tr_netBind    ( int port, int type ) { assert( 0 ); }
1517void tr_netClose   ( int s ) { assert( 0 ); }
1518int  tr_netRecvFrom( int s, uint8_t * buf, int size, struct sockaddr_in * sin ) { assert( 0 ); }
1519int         tr_httpRequestType( const char * data, int len,
1520                                char ** method, char ** uri ) { assert( 0 ); }
1521int         tr_httpResponseCode( const char * data, int len ) { assert( 0 ); }
1522char *      tr_httpParse( const char * data, int len, tr_http_header_t *headers ) { assert( 0 ); }
1523int         tr_httpIsUrl( const char * u, int l ) { assert( 0 ); }
1524int         tr_httpParseUrl( const char * u, int l, char ** h, int * p, char ** q ) { assert( 0 ); }
1525tr_http_t   * tr_httpClient( int t, const char * h, int p, const char * u, ... ) { assert( 0 ); }
1526tr_http_t   * tr_httpClientUrl( int t, const char * u, ... ) { assert( 0 ); }
1527void          tr_httpAddHeader( tr_http_t * h, const char * n, const char * v ) { assert( 0 ); }
1528void          tr_httpAddBody( tr_http_t * h, const char * b, ... ) { assert( 0 ); }
1529tr_tristate_t tr_httpPulse( tr_http_t * h, const char ** b, int * l ) { assert( 0 ); }
1530char        * tr_httpWhatsMyAddress( tr_http_t * h ) { assert( 0 ); }
1531void          tr_httpClose( tr_http_t * h ) { assert( 0 ); }
1532
1533void tr_lockInit     ( tr_lock_t * l ) {}
1534int  pthread_mutex_lock( pthread_mutex_t * m ) { return 0; }
1535int  pthread_mutex_unlock( pthread_mutex_t * m ) { return 0; }
1536
1537#endif
Note: See TracBrowser for help on using the repository browser.