httpd.c 81.3 KB
Newer Older
1
2
3
/*****************************************************************************
 * httpd.c
 *****************************************************************************
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
4
 * Copyright (C) 2004-2006 the VideoLAN team
5
 * Copyright © 2004-2007 Rémi Denis-Courmont
6
 * $Id$
7
8
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
9
 *          Rémi Denis-Courmont <rem # videolan.org>
10
11
12
13
14
15
16
17
18
19
20
21
22
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
25
 *****************************************************************************/

26
27
28
29
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

30
#include <vlc_common.h>
Rafaël Carré's avatar
Rafaël Carré committed
31
#include <vlc_httpd.h>
32

33
34
#ifdef ENABLE_HTTPD

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
35
36
#include <assert.h>

zorglub's avatar
zorglub committed
37
38
39
#include <vlc_network.h>
#include <vlc_tls.h>
#include <vlc_acl.h>
40
#include <vlc_strings.h>
41
#include "../libvlc.h"
42

43
#include <string.h>
44
45
#include <errno.h>

46
47
48
#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#endif
gbazin's avatar
gbazin committed
49
50
51
52

#ifdef HAVE_FCNTL_H
#   include <fcntl.h>
#endif
53

54
55
56
57
#ifdef HAVE_POLL
# include <poll.h>
#endif

58
59
60
61
62
63
64
65
#if defined( UNDER_CE )
#   include <winsock.h>
#elif defined( WIN32 )
#   include <winsock2.h>
#else
#   include <sys/socket.h>
#endif

66
67
68
69
70
71
72
#if defined( WIN32 )
/* We need HUGE buffer otherwise TCP throughput is very limited */
#define HTTPD_CL_BUFSIZE 1000000
#else
#define HTTPD_CL_BUFSIZE 10000
#endif

73
74
static void httpd_ClientClean( httpd_client_t *cl );

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
struct httpd_t
{
    VLC_COMMON_MEMBERS

    int          i_host;
    httpd_host_t **host;
};


/* each host run in his own thread */
struct httpd_host_t
{
    VLC_COMMON_MEMBERS

    httpd_t     *httpd;

    /* ref count */
    int         i_ref;

    /* address/port and socket for listening at connections */
    char        *psz_hostname;
    int         i_port;
97
98
    int         *fds;
    unsigned     nfd;
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

    vlc_mutex_t lock;

    /* all registered url (becarefull that 2 httpd_url_t could point at the same url)
     * This will slow down the url research but make my live easier
     * All url will have their cb trigger, but only the first one can answer
     * */
    int         i_url;
    httpd_url_t **url;

    int            i_client;
    httpd_client_t **client;

    /* TLS data */
    tls_server_t *p_tls;
};


117
118
119
120
121
122
struct httpd_url_t
{
    httpd_host_t *host;

    vlc_mutex_t lock;

123
124
125
126
    char      *psz_url;
    char      *psz_user;
    char      *psz_password;
    vlc_acl_t *p_acl;
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

    struct
    {
        httpd_callback_t     cb;
        httpd_callback_sys_t *p_sys;
    } catch[HTTPD_MSG_MAX];
};

/* status */
enum
{
    HTTPD_CLIENT_RECEIVING,
    HTTPD_CLIENT_RECEIVE_DONE,

    HTTPD_CLIENT_SENDING,
    HTTPD_CLIENT_SEND_DONE,

    HTTPD_CLIENT_WAITING,

    HTTPD_CLIENT_DEAD,
147
148
149

    HTTPD_CLIENT_TLS_HS_IN,
    HTTPD_CLIENT_TLS_HS_OUT
150
};
Jean-Paul Saman's avatar
Jean-Paul Saman committed
151

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/* mode */
enum
{
    HTTPD_CLIENT_FILE,      /* default */
    HTTPD_CLIENT_STREAM,    /* regulary get data from cb */
    HTTPD_CLIENT_BIDIR,     /* check for reading and get data from cb */
};

struct httpd_client_t
{
    httpd_url_t *url;

    int     i_ref;

    int     fd;

    int     i_mode;
    int     i_state;
    int     b_read_waiting; /* stop as soon as possible sending */

    mtime_t i_activity_date;
    mtime_t i_activity_timeout;

    /* buffer for reading header */
    int     i_buffer_size;
    int     i_buffer;
    uint8_t *p_buffer;

    /* */
    httpd_message_t query;  /* client -> httpd */
    httpd_message_t answer; /* httpd -> client */
Jean-Paul Saman's avatar
Jean-Paul Saman committed
183

184
185
    /* TLS data */
    tls_session_t *p_tls;
186
187
188
189
190
191
};


/*****************************************************************************
 * Various functions
 *****************************************************************************/
192
static const struct
193
{
194
    const char psz_ext[8];
195
    const char *psz_mime;
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
} http_mime[] =
{
    { ".htm",   "text/html" },
    { ".html",  "text/html" },
    { ".txt",   "text/plain" },
    { ".xml",   "text/xml" },
    { ".dtd",   "text/dtd" },

    { ".css",   "text/css" },

    /* image mime */
    { ".gif",   "image/gif" },
    { ".jpe",   "image/jpeg" },
    { ".jpg",   "image/jpeg" },
    { ".jpeg",  "image/jpeg" },
    { ".png",   "image/png" },
212
213
    /* same as modules/mux/mpjpeg.c here: */
    { ".mpjpeg","multipart/x-mixed-replace; boundary=7b3cc56e5f51db803f790dad720ed50a" },
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237

    /* media mime */
    { ".avi",   "video/avi" },
    { ".asf",   "video/x-ms-asf" },
    { ".m1a",   "audio/mpeg" },
    { ".m2a",   "audio/mpeg" },
    { ".m1v",   "video/mpeg" },
    { ".m2v",   "video/mpeg" },
    { ".mp2",   "audio/mpeg" },
    { ".mp3",   "audio/mpeg" },
    { ".mpa",   "audio/mpeg" },
    { ".mpg",   "video/mpeg" },
    { ".mpeg",  "video/mpeg" },
    { ".mpe",   "video/mpeg" },
    { ".mov",   "video/quicktime" },
    { ".moov",  "video/quicktime" },
    { ".ogg",   "application/ogg" },
    { ".ogm",   "application/ogg" },
    { ".wav",   "audio/wav" },
    { ".wma",   "audio/x-ms-wma" },
    { ".wmv",   "video/x-ms-wmv" },


    /* end */
238
    { "",       "" }
239
};
Jean-Paul Saman's avatar
Jean-Paul Saman committed
240

241
static const char *httpd_MimeFromUrl( const char *psz_url )
242
243
244
245
246
247
248
249
250
{

    char *psz_ext;

    psz_ext = strrchr( psz_url, '.' );
    if( psz_ext )
    {
        int i;

251
        for( i = 0; http_mime[i].psz_ext[0] ; i++ )
252
253
254
255
256
257
258
259
260
261
        {
            if( !strcasecmp( http_mime[i].psz_ext, psz_ext ) )
            {
                return http_mime[i].psz_mime;
            }
        }
    }
    return "application/octet-stream";
}

262

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
263
264
typedef struct
{
265
266
    unsigned   i_code;
    const char psz_reason[36];
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
267
268
269
270
271
272
} http_status_info;

static const http_status_info http_reason[] =
{
  /*{ 100, "Continue" },
    { 101, "Switching Protocols" },*/
273
274
    { 200, "OK" },
  /*{ 201, "Created" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
275
    { 202, "Accepted" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
276
277
278
279
280
281
    { 203, "Non-authoritative information" },
    { 204, "No content" },
    { 205, "Reset content" },
    { 206, "Partial content" },
    { 250, "Low on storage space" },
    { 300, "Multiple choices" },*/
282
283
    { 301, "Moved permanently" },
  /*{ 302, "Moved temporarily" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
284
285
286
287
288
    { 303, "See other" },
    { 304, "Not modified" },
    { 305, "Use proxy" },
    { 307, "Temporary redirect" },
    { 400, "Bad request" },*/
289
290
    { 401, "Unauthorized" },
  /*{ 402, "Payment Required" },*/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
291
    { 403, "Forbidden" },
292
    { 404, "Not found" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
293
294
295
296
    { 405, "Method not allowed" },
  /*{ 406, "Not acceptable" },
    { 407, "Proxy authentication required" },
    { 408, "Request time-out" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
297
298
    { 409, "Conflict" },
    { 410, "Gone" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
299
300
301
302
303
    { 411, "Length required" },
    { 412, "Precondition failed" },
    { 413, "Request entity too large" },
    { 414, "Request-URI too large" },
    { 415, "Unsupported media Type" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
304
    { 416, "Requested range not satisfiable" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
305
306
307
308
    { 417, "Expectation failed" },
    { 451, "Parameter not understood" },
    { 452, "Conference not found" },
    { 453, "Not enough bandwidth" },*/
309
    { 454, "Session not found" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
310
  /*{ 455, "Method not valid in this State" },*/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
311
    { 456, "Header field not valid for resource" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
312
  /*{ 457, "Invalid range" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
313
    { 458, "Read-only parameter" },*/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
314
    { 459, "Aggregate operation not allowed" },
315
316
317
    { 460, "Non-aggregate operation not allowed" },
    { 461, "Unsupported transport" },
  /*{ 462, "Destination unreachable" },*/
318
    { 500, "Internal server error" },
319
320
321
    { 501, "Not implemented" },
  /*{ 502, "Bad gateway" },*/
    { 503, "Service unavailable" },
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
322
323
  /*{ 504, "Gateway time-out" },*/
    { 505, "Protocol version not supported" },
324
325
    { 551, "Option not supported" },
    { 999, "" }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
326
327
};

328
329
static const char psz_fallback_reason[5][16] =
{ "Continue", "OK", "Found", "Client error", "Server error" };
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
330

331
static const char *httpd_ReasonFromCode( unsigned i_code )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
332
333
334
{
    const http_status_info *p;

335
336
337
    assert( ( i_code >= 100 ) && ( i_code <= 599 ) );

    for (p = http_reason; i_code > p->i_code; p++);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
338
339
340
341
342
343

    if( p->i_code == i_code )
        return p->psz_reason;

    return psz_fallback_reason[(i_code / 100) - 1];
}
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375


static size_t httpd_HtmlError (char **body, int code, const char *url)
{
    const char *errname = httpd_ReasonFromCode (code);
    assert (errname != NULL);

    int res = asprintf (body,
        "<?xml version=\"1.0\" encoding=\"ascii\" ?>\n"
        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
        " \"http://www.w3.org/TR/xhtml10/DTD/xhtml10strict.dtd\">\n"
        "<html lang=\"en\">\n"
        "<head>\n"
        "<title>%s</title>\n"
        "</head>\n"
        "<body>\n"
        "<h1>%d %s%s%s%s</h1>\n"
        "<hr />\n"
        "<a href=\"http://www.videolan.org\">VideoLAN</a>\n"
        "</body>\n"
        "</html>\n", errname, code, errname,
        (url ? " (" : ""), (url ?: ""), (url ? ")" : ""));

    if (res == -1)
    {
        *body = NULL;
        return 0;
    }

    return (size_t)res;
}

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
376

377
/*****************************************************************************
378
 * High Level Functions: httpd_file_t
379
380
381
382
383
384
385
386
387
388
389
390
391
 *****************************************************************************/
struct httpd_file_t
{
    httpd_url_t *url;

    char *psz_url;
    char *psz_mime;

    httpd_file_callback_t pf_fill;
    httpd_file_sys_t      *p_sys;

};

392
393
394
static int
httpd_FileCallBack( httpd_callback_sys_t *p_sys, httpd_client_t *cl,
                    httpd_message_t *answer, const httpd_message_t *query )
395
396
{
    httpd_file_t *file = (httpd_file_t*)p_sys;
397
    uint8_t **pp_body, *p_body;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
398
    const char *psz_connection;
399
    int *pi_body, i_body;
400
401
402
403
404
405

    if( answer == NULL || query == NULL )
    {
        return VLC_SUCCESS;
    }
    answer->i_proto  = HTTPD_PROTO_HTTP;
Rémi Denis-Courmont's avatar
Fixes    
Rémi Denis-Courmont committed
406
    answer->i_version= 1;
407
408
409
410
411
412
413
414
415
    answer->i_type   = HTTPD_MSG_ANSWER;

    answer->i_status = 200;

    httpd_MsgAdd( answer, "Content-type",  "%s", file->psz_mime );
    httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );

    if( query->i_type != HTTPD_MSG_HEAD )
    {
416
417
418
419
420
421
422
423
424
425
        pp_body = &answer->p_body;
        pi_body = &answer->i_body;
    }
    else
    {
        /* The file still needs to be executed. */
        p_body = NULL;
        i_body = 0;
        pp_body = &p_body;
        pi_body = &i_body;
426
    }
427
428
429
430
431
432

    if( query->i_type == HTTPD_MSG_POST )
    {
        /* msg_Warn not supported */
    }

433
    uint8_t *psz_args = query->psz_args;
434
435
436
437
438
439
440
    file->pf_fill( file->p_sys, file, psz_args, pp_body, pi_body );

    if( query->i_type == HTTPD_MSG_HEAD && p_body != NULL )
    {
        free( p_body );
    }

441
    /* We respect client request */
442
443
    psz_connection = httpd_MsgGet( &cl->query, "Connection" );
    if( psz_connection != NULL )
444
    {
445
        httpd_MsgAdd( answer, "Connection", "%s", psz_connection );
446
447
    }

448
449
450
451
452
453
    httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );

    return VLC_SUCCESS;
}

httpd_file_t *httpd_FileNew( httpd_host_t *host,
454
455
                             const char *psz_url, const char *psz_mime,
                             const char *psz_user, const char *psz_password,
456
                             const vlc_acl_t *p_acl, httpd_file_callback_t pf_fill,
457
458
459
460
                             httpd_file_sys_t *p_sys )
{
    httpd_file_t *file = malloc( sizeof( httpd_file_t ) );

461
    if( ( file->url = httpd_UrlNewUnique( host, psz_url, psz_user,
462
                                          psz_password, p_acl )
463
        ) == NULL )
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
    {
        free( file );
        return NULL;
    }

    file->psz_url  = strdup( psz_url );
    if( psz_mime && *psz_mime )
    {
        file->psz_mime = strdup( psz_mime );
    }
    else
    {
        file->psz_mime = strdup( httpd_MimeFromUrl( psz_url ) );
    }

    file->pf_fill = pf_fill;
    file->p_sys   = p_sys;

482
483
484
485
486
487
    httpd_UrlCatch( file->url, HTTPD_MSG_HEAD, httpd_FileCallBack,
                    (httpd_callback_sys_t*)file );
    httpd_UrlCatch( file->url, HTTPD_MSG_GET,  httpd_FileCallBack,
                    (httpd_callback_sys_t*)file );
    httpd_UrlCatch( file->url, HTTPD_MSG_POST, httpd_FileCallBack,
                    (httpd_callback_sys_t*)file );
488
489
490
491

    return file;
}

492
httpd_file_sys_t *httpd_FileDelete( httpd_file_t *file )
493
{
494
495
    httpd_file_sys_t *p_sys = file->p_sys;

496
497
498
499
500
501
    httpd_UrlDelete( file->url );

    free( file->psz_url );
    free( file->psz_mime );

    free( file );
502
503

    return p_sys;
504
505
}

506
507
508
509
510
511
512
513
514
515
516
517
/*****************************************************************************
 * High Level Functions: httpd_handler_t (for CGIs)
 *****************************************************************************/
struct httpd_handler_t
{
    httpd_url_t *url;

    httpd_handler_callback_t pf_fill;
    httpd_handler_sys_t      *p_sys;

};

518
519
520
static int
httpd_HandlerCallBack( httpd_callback_sys_t *p_sys, httpd_client_t *cl,
                       httpd_message_t *answer, const httpd_message_t *query )
521
522
{
    httpd_handler_t *handler = (httpd_handler_t*)p_sys;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
523
    char psz_remote_addr[NI_MAXNUMERICHOST];
524
525
526
527
528
529
530
531
532
533
534

    if( answer == NULL || query == NULL )
    {
        return VLC_SUCCESS;
    }
    answer->i_proto  = HTTPD_PROTO_NONE;
    answer->i_type   = HTTPD_MSG_ANSWER;

    /* We do it ourselves, thanks */
    answer->i_status = 0;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
535
536
    if( httpd_ClientIP( cl, psz_remote_addr ) == NULL )
        *psz_remote_addr = '\0';
537

538
    uint8_t *psz_args = query->psz_args;
539
540
541
542
543
544
545
    handler->pf_fill( handler->p_sys, handler, query->psz_url, psz_args,
                      query->i_type, query->p_body, query->i_body,
                      psz_remote_addr, NULL,
                      &answer->p_body, &answer->i_body );

    if( query->i_type == HTTPD_MSG_HEAD )
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
546
        char *p = (char *)answer->p_body;
547
548

        /* Looks for end of header (i.e. one empty line) */
549
550
551
552
553
554
555
556
        while ( (p = strchr( p, '\r' )) != NULL )
        {
            if( p[1] && p[1] == '\n' && p[2] && p[2] == '\r'
                 && p[3] && p[3] == '\n' )
            {
                break;
            }
        }
557

558
559
560
        if( p != NULL )
        {
            p[4] = '\0';
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
561
            answer->i_body = strlen((char*)answer->p_body) + 1;
562
563
564
565
            answer->p_body = realloc( answer->p_body, answer->i_body );
        }
    }

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
566
    if( strncmp( (char *)answer->p_body, "HTTP/1.", 7 ) )
567
568
    {
        int i_status, i_headers;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
569
570
        char *psz_headers, *psz_new;
        const char *psz_status;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
571
572

        if( !strncmp( (char *)answer->p_body, "Status: ", 8 ) )
573
574
        {
            /* Apache-style */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
575
            i_status = strtol( (char *)&answer->p_body[8], &psz_headers, 0 );
576
577
578
579
580
581
582
            if( *psz_headers ) psz_headers++;
            if( *psz_headers ) psz_headers++;
            i_headers = answer->i_body - (psz_headers - (char *)answer->p_body);
        }
        else
        {
            i_status = 200;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
583
            psz_headers = (char *)answer->p_body;
584
585
            i_headers = answer->i_body;
        }
586
587

        psz_status = httpd_ReasonFromCode( i_status );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
588
589
590
591
        answer->i_body = sizeof("HTTP/1.0 xxx \r\n")
                        + strlen(psz_status) + i_headers - 1;
        psz_new = (char *)malloc( answer->i_body + 1);
        sprintf( psz_new, "HTTP/1.0 %03d %s\r\n", i_status, psz_status );
592
593
        memcpy( &psz_new[strlen(psz_new)], psz_headers, i_headers );
        free( answer->p_body );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
594
        answer->p_body = (uint8_t *)psz_new;
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
    }

    return VLC_SUCCESS;
}

httpd_handler_t *httpd_HandlerNew( httpd_host_t *host, const char *psz_url,
                                   const char *psz_user,
                                   const char *psz_password,
                                   const vlc_acl_t *p_acl,
                                   httpd_handler_callback_t pf_fill,
                                   httpd_handler_sys_t *p_sys )
{
    httpd_handler_t *handler = malloc( sizeof( httpd_handler_t ) );

    if( ( handler->url = httpd_UrlNewUnique( host, psz_url, psz_user,
                                             psz_password, p_acl )
        ) == NULL )
    {
        free( handler );
        return NULL;
    }

    handler->pf_fill = pf_fill;
    handler->p_sys   = p_sys;

    httpd_UrlCatch( handler->url, HTTPD_MSG_HEAD, httpd_HandlerCallBack,
                    (httpd_callback_sys_t*)handler );
    httpd_UrlCatch( handler->url, HTTPD_MSG_GET,  httpd_HandlerCallBack,
                    (httpd_callback_sys_t*)handler );
    httpd_UrlCatch( handler->url, HTTPD_MSG_POST, httpd_HandlerCallBack,
                    (httpd_callback_sys_t*)handler );

    return handler;
}

630
httpd_handler_sys_t *httpd_HandlerDelete( httpd_handler_t *handler )
631
{
632
    httpd_handler_sys_t *p_sys = handler->p_sys;
633
634
    httpd_UrlDelete( handler->url );
    free( handler );
635
    return p_sys;
636
637
}

638
/*****************************************************************************
639
 * High Level Functions: httpd_redirect_t
640
641
642
643
644
645
646
 *****************************************************************************/
struct httpd_redirect_t
{
    httpd_url_t *url;
    char        *psz_dst;
};

647
648
static int httpd_RedirectCallBack( httpd_callback_sys_t *p_sys,
                                   httpd_client_t *cl, httpd_message_t *answer,
649
                                   const httpd_message_t *query )
650
651
{
    httpd_redirect_t *rdir = (httpd_redirect_t*)p_sys;
652
    char *p_body;
653
    (void)cl;
654
655
656
657
658

    if( answer == NULL || query == NULL )
    {
        return VLC_SUCCESS;
    }
659
    answer->i_proto  = HTTPD_PROTO_HTTP;
Rémi Denis-Courmont's avatar
Fixes    
Rémi Denis-Courmont committed
660
    answer->i_version= 1;
661
662
663
    answer->i_type   = HTTPD_MSG_ANSWER;
    answer->i_status = 301;

664
    answer->i_body = httpd_HtmlError (&p_body, 301, rdir->psz_dst);
665
    answer->p_body = (unsigned char *)p_body;
666
667
668
669
670
671
672
673
674

    /* XXX check if it's ok or we need to set an absolute url */
    httpd_MsgAdd( answer, "Location",  "%s", rdir->psz_dst );

    httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );

    return VLC_SUCCESS;
}

675
676
httpd_redirect_t *httpd_RedirectNew( httpd_host_t *host, const char *psz_url_dst,
                                     const char *psz_url_src )
677
678
679
{
    httpd_redirect_t *rdir = malloc( sizeof( httpd_redirect_t ) );

680
    if( !( rdir->url = httpd_UrlNewUnique( host, psz_url_src, NULL, NULL, NULL ) ) )
681
682
683
684
685
686
    {
        free( rdir );
        return NULL;
    }
    rdir->psz_dst = strdup( psz_url_dst );

687
688
689
690
691
692
693
694
695
    /* Redirect apply for all HTTP request and RTSP DESCRIBE resquest */
    httpd_UrlCatch( rdir->url, HTTPD_MSG_HEAD, httpd_RedirectCallBack,
                    (httpd_callback_sys_t*)rdir );
    httpd_UrlCatch( rdir->url, HTTPD_MSG_GET, httpd_RedirectCallBack,
                    (httpd_callback_sys_t*)rdir );
    httpd_UrlCatch( rdir->url, HTTPD_MSG_POST, httpd_RedirectCallBack,
                    (httpd_callback_sys_t*)rdir );
    httpd_UrlCatch( rdir->url, HTTPD_MSG_DESCRIBE, httpd_RedirectCallBack,
                    (httpd_callback_sys_t*)rdir );
696
697
698

    return rdir;
}
699
void httpd_RedirectDelete( httpd_redirect_t *rdir )
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
{
    httpd_UrlDelete( rdir->url );
    free( rdir->psz_dst );
    free( rdir );
}

/*****************************************************************************
 * High Level Funtions: httpd_stream_t
 *****************************************************************************/
struct httpd_stream_t
{
    vlc_mutex_t lock;
    httpd_url_t *url;

    char    *psz_mime;

    /* Header to send as first packet */
    uint8_t *p_header;
    int     i_header;

    /* circular buffer */
    int         i_buffer_size;      /* buffer size, can't be reallocated smaller */
    uint8_t     *p_buffer;          /* buffer */
    int64_t     i_buffer_pos;       /* absolute position from begining */
    int64_t     i_buffer_last_pos;  /* a new connection will start with that */
};

727
728
static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
                                 httpd_client_t *cl, httpd_message_t *answer,
729
                                 const httpd_message_t *query )
730
731
732
733
734
735
736
{
    httpd_stream_t *stream = (httpd_stream_t*)p_sys;

    if( answer == NULL || query == NULL || cl == NULL )
    {
        return VLC_SUCCESS;
    }
Jean-Paul Saman's avatar
Jean-Paul Saman committed
737

738
739
740
741
742
    if( answer->i_body_offset > 0 )
    {
        int64_t i_write;
        int     i_pos;

743
744
745
746
#if 0
        fprintf( stderr, "httpd_StreamCallBack i_body_offset=%lld\n",
                 answer->i_body_offset );
#endif
747
748
749
750
751
752

        if( answer->i_body_offset >= stream->i_buffer_pos )
        {
            /* fprintf( stderr, "httpd_StreamCallBack: no data\n" ); */
            return VLC_EGENERIC;    /* wait, no data available */
        }
753
754
        if( answer->i_body_offset + stream->i_buffer_size <
            stream->i_buffer_pos )
755
756
        {
            /* this client isn't fast enough */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
757
#if 0
758
759
            fprintf( stderr, "fixing i_body_offset (old=%lld new=%lld)\n",
                     answer->i_body_offset, stream->i_buffer_last_pos );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
760
#endif
761
762
763
764
765
            answer->i_body_offset = stream->i_buffer_last_pos;
        }

        i_pos   = answer->i_body_offset % stream->i_buffer_size;
        i_write = stream->i_buffer_pos - answer->i_body_offset;
766
        if( i_write > HTTPD_CL_BUFSIZE )
767
        {
768
            i_write = HTTPD_CL_BUFSIZE;
769
770
771
772
773
774
        }
        else if( i_write <= 0 )
        {
            return VLC_EGENERIC;    /* wait, no data available */
        }

775
776
777
        /* Don't go past the end of the circular buffer */
        i_write = __MIN( i_write, stream->i_buffer_size - i_pos );

778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
        /* using HTTPD_MSG_ANSWER -> data available */
        answer->i_proto  = HTTPD_PROTO_HTTP;
        answer->i_version= 0;
        answer->i_type   = HTTPD_MSG_ANSWER;

        answer->i_body = i_write;
        answer->p_body = malloc( i_write );
        memcpy( answer->p_body, &stream->p_buffer[i_pos], i_write );

        answer->i_body_offset += i_write;

        return VLC_SUCCESS;
    }
    else
    {
        answer->i_proto  = HTTPD_PROTO_HTTP;
        answer->i_version= 0;
        answer->i_type   = HTTPD_MSG_ANSWER;

        answer->i_status = 200;

        if( query->i_type != HTTPD_MSG_HEAD )
        {
            httpd_ClientModeStream( cl );
            vlc_mutex_lock( &stream->lock );
            /* Send the header */
            if( stream->i_header > 0 )
            {
                answer->i_body = stream->i_header;
                answer->p_body = malloc( stream->i_header );
                memcpy( answer->p_body, stream->p_header, stream->i_header );
            }
            answer->i_body_offset = stream->i_buffer_last_pos;
            vlc_mutex_unlock( &stream->lock );
        }
        else
        {
            httpd_MsgAdd( answer, "Content-Length", "%d", 0 );
816
            answer->i_body_offset = 0;
817
818
819
820
        }

        if( !strcmp( stream->psz_mime, "video/x-ms-asf-stream" ) )
        {
821
            bool b_xplaystream = false;
822
823
            int i;

824
825
            httpd_MsgAdd( answer, "Content-type", "%s",
                          "application/octet-stream" );
826
827
828
829
            httpd_MsgAdd( answer, "Server", "Cougar 4.1.0.3921" );
            httpd_MsgAdd( answer, "Pragma", "no-cache" );
            httpd_MsgAdd( answer, "Pragma", "client-id=%d", rand()&0x7fff );
            httpd_MsgAdd( answer, "Pragma", "features=\"broadcast\"" );
830

831
832
833
834
            /* Check if there is a xPlayStrm=1 */
            for( i = 0; i < query->i_name; i++ )
            {
                if( !strcasecmp( query->name[i],  "Pragma" ) &&
835
                    strstr( query->value[i], "xPlayStrm=1" ) )
836
                {
837
                    b_xplaystream = true;
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
                }
            }

            if( !b_xplaystream )
            {
                answer->i_body_offset = 0;
            }
        }
        else
        {
            httpd_MsgAdd( answer, "Content-type",  "%s", stream->psz_mime );
        }
        httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );
        return VLC_SUCCESS;
    }
}

httpd_stream_t *httpd_StreamNew( httpd_host_t *host,
856
857
                                 const char *psz_url, const char *psz_mime,
                                 const char *psz_user, const char *psz_password,
858
                                 const vlc_acl_t *p_acl )
859
860
861
{
    httpd_stream_t *stream = malloc( sizeof( httpd_stream_t ) );

862
    if( ( stream->url = httpd_UrlNewUnique( host, psz_url, psz_user,
863
                                            psz_password, p_acl )
864
        ) == NULL )
865
866
867
868
    {
        free( stream );
        return NULL;
    }
869
    vlc_mutex_init( &stream->lock );
870
871
872
873
874
875
876
877
878
879
880
881
    if( psz_mime && *psz_mime )
    {
        stream->psz_mime = strdup( psz_mime );
    }
    else
    {
        stream->psz_mime = strdup( httpd_MimeFromUrl( psz_url ) );
    }
    stream->i_header = 0;
    stream->p_header = NULL;
    stream->i_buffer_size = 5000000;    /* 5 Mo per stream */
    stream->p_buffer = malloc( stream->i_buffer_size );
882
883
    /* We set to 1 to make life simpler
     * (this way i_body_offset can never be 0) */
884
885
886
    stream->i_buffer_pos = 1;
    stream->i_buffer_last_pos = 1;

887
888
889
890
891
892
    httpd_UrlCatch( stream->url, HTTPD_MSG_HEAD, httpd_StreamCallBack,
                    (httpd_callback_sys_t*)stream );
    httpd_UrlCatch( stream->url, HTTPD_MSG_GET, httpd_StreamCallBack,
                    (httpd_callback_sys_t*)stream );
    httpd_UrlCatch( stream->url, HTTPD_MSG_POST, httpd_StreamCallBack,
                    (httpd_callback_sys_t*)stream );
893
894
895
896

    return stream;
}

897
int httpd_StreamHeader( httpd_stream_t *stream, uint8_t *p_data, int i_data )
898
899
{
    vlc_mutex_lock( &stream->lock );
900
901
902
    free( stream->p_header );
    stream->p_header = NULL;

903
904
905
906
907
908
909
910
911
912
913
    stream->i_header = i_data;
    if( i_data > 0 )
    {
        stream->p_header = malloc( i_data );
        memcpy( stream->p_header, p_data, i_data );
    }
    vlc_mutex_unlock( &stream->lock );

    return VLC_SUCCESS;
}

914
int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data )
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
{
    int i_count;
    int i_pos;

    if( i_data < 0 || p_data == NULL )
    {
        return VLC_SUCCESS;
    }
    vlc_mutex_lock( &stream->lock );

    /* save this pointer (to be used by new connection) */
    stream->i_buffer_last_pos = stream->i_buffer_pos;

    i_pos = stream->i_buffer_pos % stream->i_buffer_size;
    i_count = i_data;
    while( i_count > 0)
    {
        int i_copy;

        i_copy = __MIN( i_count, stream->i_buffer_size - i_pos );

        /* Ok, we can't go past the end of our buffer */
        memcpy( &stream->p_buffer[i_pos], p_data, i_copy );

        i_pos = ( i_pos + i_copy ) % stream->i_buffer_size;
        i_count -= i_copy;
        p_data  += i_copy;
    }

    stream->i_buffer_pos += i_data;

    vlc_mutex_unlock( &stream->lock );
    return VLC_SUCCESS;
}

void httpd_StreamDelete( httpd_stream_t *stream )
{
    httpd_UrlDelete( stream->url );
    vlc_mutex_destroy( &stream->lock );
954
955
956
    free( stream->psz_mime );
    free( stream->p_header );
    free( stream->p_buffer );
957
958
959
960
961
962
    free( stream );
}

/*****************************************************************************
 * Low level
 *****************************************************************************/
ivoire's avatar
ivoire committed
963
static void* httpd_HostThread( vlc_object_t * );
964
965

/* create a new host */
966
httpd_host_t *httpd_HostNew( vlc_object_t *p_this, const char *psz_host,
967
968
                             int i_port )
{
969
970
    return httpd_TLSHostNew( p_this, psz_host, i_port, NULL, NULL, NULL, NULL
                           );
971
972
}

973
974
static const char psz_object_type[] = "http server";

975
976
977
978
httpd_host_t *httpd_TLSHostNew( vlc_object_t *p_this, const char *psz_hostname,
                                int i_port,
                                const char *psz_cert, const char *psz_key,
                                const char *psz_ca, const char *psz_crl )
979
980
{
    httpd_t      *httpd;
981
    httpd_host_t *host;
982
983
    tls_server_t *p_tls;
    char *psz_host;
984
    vlc_value_t  lockval, ptrval;
985
    int i;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
986

987
988
989
    if( psz_hostname == NULL )
        psz_hostname = "";

990
    psz_host = strdup( psz_hostname );
991
    if( psz_host == NULL )
992
993
994
        return NULL;

    /* to be sure to avoid multiple creation */
995
996
    var_Create( p_this->p_libvlc, "httpd_mutex", VLC_VAR_MUTEX );
    var_Get( p_this->p_libvlc, "httpd_mutex", &lockval );
997
    vlc_mutex_lock( lockval.p_address );
998
    httpd = libvlc_priv (p_this->p_libvlc)->p_httpd;
999

1000
    if( httpd == NULL )
1001
1002
    {
        msg_Info( p_this, "creating httpd" );
1003
        httpd = (httpd_t *)vlc_custom_create( p_this, sizeof (*httpd),
1004
                                              VLC_OBJECT_GENERIC,
1005
                                              psz_object_type );
1006
        if( httpd == NULL )
1007
1008
        {
            vlc_mutex_unlock( lockval.p_address );
1009
            free( psz_host );
1010
1011
1012
1013
1014
1015
            return NULL;
        }

        httpd->i_host = 0;
        httpd->host   = NULL;

1016
        ptrval.p_address = httpd;
1017
        libvlc_priv (p_this->p_libvlc)->p_httpd = httpd;
1018
        vlc_object_hold( httpd );
1019
        vlc_object_attach( httpd, p_this->p_libvlc );
1020
1021
    }

1022
1023
    /* verify if it already exist */
    for( i = httpd->i_host - 1; i >= 0; i-- )
1024
    {
1025
        host = httpd->host[i];
1026

1027
1028
1029
1030
        /* cannot mix TLS and non-TLS hosts */
        if( ( ( httpd->host[i]->p_tls != NULL ) != ( psz_cert != NULL ) )
         || ( host->i_port != i_port )
         || strcmp( host->psz_hostname, psz_hostname ) )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1031
1032
            continue;

1033
1034
        /* yep found */
        host->i_ref++;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1035

1036
1037
        vlc_mutex_unlock( lockval.p_address );
        return host;
1038
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1039

1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
    host = NULL;

    /* determine TLS configuration */
    if ( psz_cert != NULL )
    {
        p_tls = tls_ServerCreate( p_this, psz_cert, psz_key );
        if ( p_tls == NULL )
        {
            msg_Err( p_this, "TLS initialization error" );
            goto error;
        }

        if ( ( psz_ca != NULL) && tls_ServerAddCA( p_tls, psz_ca ) )
        {
            msg_Err( p_this, "TLS CA error" );
            goto error;
        }

        if ( ( psz_crl != NULL) && tls_ServerAddCRL( p_tls, psz_crl ) )
        {
            msg_Err( p_this, "TLS CRL error" );
            goto error;
        }
    }
    else
        p_tls = NULL;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1067
    /* create the new host */
1068
    host = (httpd_host_t *)vlc_custom_create( p_this, sizeof (*host),
1069
                                              VLC_OBJECT_GENERIC,
1070
1071
1072
1073
                                              psz_object_type );
    if (host == NULL)
        goto error;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1074
    vlc_object_lock( host );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1075
    if( vlc_object_waitpipe( VLC_OBJECT( host ) ) == -1 )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1076
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1077
        msg_Err( host, "signaling pipe error: %m" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1078
1079
1080
1081
1082
        vlc_object_unlock( host );
        goto error;
    }
    vlc_object_unlock( host );

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1083
    host->httpd = httpd;
1084
    vlc_mutex_init( &host->lock );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1085
1086
    host->i_ref = 1;

1087
1088
    host->fds = net_ListenTCP( p_this, psz_host, i_port );
    if( host->fds == NULL )
1089
1090
1091
1092
    {
        msg_Err( p_this, "cannot create socket(s) for HTTP host" );
        goto error;
    }
1093
    for (host->nfd = 0; host->fds[host->nfd] != -1; host->nfd++);
1094

1095
1096
1097
    host->i_port = i_port;
    host->psz_hostname = psz_host;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1098
1099
1100
1101
    host->i_url     = 0;
    host->url       = NULL;
    host->i_client  = 0;
    host->client    = NULL;
1102

1103
    host->p_tls = p_tls;
1104

1105
    /* create the thread */
1106
    if( vlc_thread_create( host, "httpd host thread", httpd_HostThread,
1107
                           VLC_THREAD_PRIORITY_LOW, false ) )
1108
1109
    {
        msg_Err( p_this, "cannot spawn http host thread" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1110
        goto error;
1111
1112
1113
1114
1115
1116
1117
1118
    }

    /* now add it to httpd */
    TAB_APPEND( httpd->i_host, httpd->host, host );
    vlc_mutex_unlock( lockval.p_address );

    return host;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1119
error:
1120
    free( psz_host );
1121
1122
    if( httpd->i_host <= 0 )
    {
sebastien's avatar
sebastien committed
1123
        libvlc_priv (httpd->p_libvlc)->p_httpd = NULL;
1124
1125
        vlc_object_release( httpd );
        vlc_object_detach( httpd );
1126
        vlc_object_release( httpd );
1127
    }
1128
1129
    vlc_mutex_unlock( lockval.p_address );

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1130
    if( host != NULL )
1131
    {
1132
        net_ListenClose( host->fds );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1133
        vlc_mutex_destroy( &host->lock );
1134
        vlc_object_release( host );
1135
1136
    }

1137
1138
1139
    if( p_tls != NULL )
        tls_ServerDelete( p_tls );

1140
1141
1142
1143
    return NULL;
}

/* delete a host */
1144
void httpd_HostDelete( httpd_host_t *host )
1145
1146
1147
1148
1149
{
    httpd_t *httpd = host->httpd;
    vlc_value_t lockval;
    int i;

1150
    var_Get( httpd->p_libvlc, "httpd_mutex", &lockval );
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
    vlc_mutex_lock( lockval.p_address );

    host->i_ref--;
    if( host->i_ref > 0 )
    {
        /* still used */
        vlc_mutex_unlock( lockval.p_address );
        msg_Dbg( host, "httpd_HostDelete: host still used" );
        return;
    }
    TAB_REMOVE( httpd->i_host, httpd->host, host );

1163
    vlc_object_kill( host );
1164
1165
    vlc_thread_join( host );

zorglub's avatar
zorglub committed
1166
    msg_Dbg( host, "HTTP host removed" );
1167
1168
1169

    for( i = 0; i < host->i_url; i++ )
    {
1170
        msg_Err( host, "url still registered: %s", host->url[i]->psz_url );
1171
1172
1173
1174
1175
    }
    for( i = 0; i < host->i_client; i++ )
    {
        httpd_client_t *cl = host->client[i];
        msg_Warn( host, "client still connected" );
1176
1177
1178
1179
        httpd_ClientClean( cl );
        TAB_REMOVE( host->i_client, host->client, cl );
        free( cl );
        i--;
1180
1181
1182
        /* TODO */
    }

1183
1184
    if( host->p_tls != NULL)
        tls_ServerDelete( host->p_tls );
1185

1186
    net_ListenClose( host->fds );
1187
1188
    free( host->psz_hostname );

1189
    vlc_mutex_destroy( &host->lock );
1190
    vlc_object_release( host );
1191

1192
    vlc_object_release( httpd );
1193
1194
    if( httpd->i_host <= 0 )
    {
zorglub's avatar
zorglub committed
1195
        msg_Dbg( httpd, "no host left, stopping httpd" );
1196

1197
        libvlc_priv (httpd->p_libvlc)->p_httpd = NULL;
1198
        vlc_object_detach( httpd );
1199
        vlc_object_release( httpd );
1200

1201
1202
1203
1204
1205
    }
    vlc_mutex_unlock( lockval.p_address );
}

/* register a new url */
1206
1207
static httpd_url_t *httpd_UrlNewPrivate( httpd_host_t *host, const char *psz_url,
                                         const char *psz_user, const char *psz_password,
1208
                                         const vlc_acl_t *p_acl, bool b_check )
1209
1210
1211
1212
{
    httpd_url_t *url;
    int         i;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1213
1214
    assert( psz_url != NULL );

1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
    vlc_mutex_lock( &host->lock );
    if( b_check )
    {
        for( i = 0; i < host->i_url; i++ )
        {
            if( !strcmp( psz_url, host->url[i]->psz_url ) )
            {
                msg_Warn( host->httpd,
                          "cannot add '%s' (url already defined)", psz_url );
                vlc_mutex_unlock( &host->lock );
                return NULL;
            }
        }
    }

    url = malloc( sizeof( httpd_url_t ) );
    url->host = host;

1233
    vlc_mutex_init( &url->lock );
1234
1235
1236
    url->psz_url = strdup( psz_url );
    url->psz_user = strdup( psz_user ? psz_user : "" );
    url->psz_password = strdup( psz_password ? psz_password : "" );
1237
    url->p_acl = ACL_Duplicate( host, p_acl );
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
    for( i = 0; i < HTTPD_MSG_MAX; i++ )
    {
        url->catch[i].cb = NULL;
        url->catch[i].p_sys = NULL;
    }

    TAB_APPEND( host->i_url, host->url, url );
    vlc_mutex_unlock( &host->lock );

    return url;
}

1250
1251
httpd_url_t *httpd_UrlNew( httpd_host_t *host, const char *psz_url,
                           const char *psz_user, const char *psz_password,
1252
                           const vlc_acl_t *p_acl )
1253
{
1254
    return httpd_UrlNewPrivate( host, psz_url, psz_user,
1255
                                psz_password, p_acl, false );
1256
1257
}

1258
1259
httpd_url_t *httpd_UrlNewUnique( httpd_host_t *host, const char *psz_url,
                                 const char *psz_user, const char *psz_password,
1260
                                 const vlc_acl_t *p_acl )
1261
{
1262
    return httpd_UrlNewPrivate( host, psz_url, psz_user,
1263
                                psz_password, p_acl, true );
1264
1265
1266
}

/* register callback on a url */
1267
1268
int httpd_UrlCatch( httpd_url_t *url, int i_msg, httpd_callback_t cb,
                    httpd_callback_sys_t *p_sys )
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
{
    vlc_mutex_lock( &url->lock );
    url->catch[i_msg].cb   = cb;
    url->catch[i_msg].p_sys= p_sys;
    vlc_mutex_unlock( &url->lock );

    return VLC_SUCCESS;
}

/* delete an url */
1279
void httpd_UrlDelete( httpd_url_t *url )
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
{
    httpd_host_t *host = url->host;
    int          i;

    vlc_mutex_lock( &host->lock );
    TAB_REMOVE( host->i_url, host->url, url );

    vlc_mutex_destroy( &url->lock );
    free( url->psz_url );
    free( url->psz_user );
    free( url->psz_password );
1291
    ACL_Destroy( url->p_acl );