source: trunk/libtransmission/upnp.c @ 2040

Last change on this file since 2040 was 2040, checked in by joshe, 15 years ago

Fix upnp verbose logging to show outgoing http requests.

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