httpd.c 79.4 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
Antoine Cellerier's avatar
Antoine Cellerier 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

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

Clément Stenac's avatar
Clément Stenac committed
35 36 37
#include <vlc_network.h>
#include <vlc_tls.h>
#include <vlc_acl.h>
38
#include <vlc_strings.h>
39
#include <vlc_rand.h>
40
#include "../libvlc.h"
41

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

45 46 47
#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#endif
48

49 50 51 52
#ifdef HAVE_POLL
# include <poll.h>
#endif

53 54 55 56 57 58 59 60
#if defined( UNDER_CE )
#   include <winsock.h>
#elif defined( WIN32 )
#   include <winsock2.h>
#else
#   include <sys/socket.h>
#endif

61 62 63 64 65 66 67
#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

68 69
static void httpd_ClientClean( httpd_client_t *cl );

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
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 */
87
    unsigned    i_ref;
88 89 90 91

    /* address/port and socket for listening at connections */
    char        *psz_hostname;
    int         i_port;
92 93
    int         *fds;
    unsigned     nfd;
94

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
95
    vlc_thread_t thread;
96
    vlc_mutex_t lock;
97
    vlc_cond_t  wait;
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

    /* 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;
};


114 115 116 117 118 119
struct httpd_url_t
{
    httpd_host_t *host;

    vlc_mutex_t lock;

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

    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,
144 145 146

    HTTPD_CLIENT_TLS_HS_IN,
    HTTPD_CLIENT_TLS_HS_OUT
147
};
Jean-Paul Saman's avatar
Jean-Paul Saman committed
148

149 150 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
/* 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
180

181 182
    /* TLS data */
    tls_session_t *p_tls;
183 184 185 186 187 188
};


/*****************************************************************************
 * Various functions
 *****************************************************************************/
189
static const struct
190
{
191
    const char psz_ext[8];
192
    const char *psz_mime;
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
} 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" },
209 210
    /* same as modules/mux/mpjpeg.c here: */
    { ".mpjpeg","multipart/x-mixed-replace; boundary=7b3cc56e5f51db803f790dad720ed50a" },
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226

    /* 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" },
227
    { ".oga",   "audio/ogg" },
228 229
    { ".ogg",   "application/ogg" },
    { ".ogm",   "application/ogg" },
230
    { ".ogv",   "video/ogg" },
231
    { ".ogx",   "application/ogg" },
232
    { ".spx",   "audio/ogg" },
233 234 235 236 237 238
    { ".wav",   "audio/wav" },
    { ".wma",   "audio/x-ms-wma" },
    { ".wmv",   "video/x-ms-wmv" },


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

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

    char *psz_ext;

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

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

263

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

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

329 330
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
331

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

336 337 338
    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
339 340 341 342 343 344

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

    return psz_fallback_reason[(i_code / 100) - 1];
}
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365


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,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
366
        (url ? " (" : ""), (url ? url : ""), (url ? ")" : ""));
367 368 369 370 371 372 373 374 375 376

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

    return (size_t)res;
}

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

378
/*****************************************************************************
379
 * High Level Functions: httpd_file_t
380 381 382 383 384 385 386 387 388 389 390 391 392
 *****************************************************************************/
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;

};

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

    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
407
    answer->i_version= 1;
408 409 410 411 412 413 414 415 416
    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 )
    {
417 418 419 420 421 422 423 424 425 426
        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;
427
    }
428 429 430 431 432 433

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

434
    uint8_t *psz_args = query->psz_args;
435 436 437 438 439 440 441
    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 );
    }

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

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

    return VLC_SUCCESS;
}

httpd_file_t *httpd_FileNew( httpd_host_t *host,
455 456
                             const char *psz_url, const char *psz_mime,
                             const char *psz_user, const char *psz_password,
457
                             const vlc_acl_t *p_acl, httpd_file_callback_t pf_fill,
458 459
                             httpd_file_sys_t *p_sys )
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
460
    httpd_file_t *file = xmalloc( sizeof( httpd_file_t ) );
461

462
    if( ( file->url = httpd_UrlNewUnique( host, psz_url, psz_user,
463
                                          psz_password, p_acl )
464
        ) == NULL )
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
    {
        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;

483 484 485 486 487 488
    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 );
489 490 491 492

    return file;
}

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

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

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

    free( file );
503 504

    return p_sys;
505 506
}

507 508 509 510 511 512 513 514 515 516 517 518
/*****************************************************************************
 * 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;

};

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

    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
536 537
    if( httpd_ClientIP( cl, psz_remote_addr ) == NULL )
        *psz_remote_addr = '\0';
538

539
    uint8_t *psz_args = query->psz_args;
540 541 542 543 544 545 546
    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
547
        char *p = (char *)answer->p_body;
548 549

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

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

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

        if( !strncmp( (char *)answer->p_body, "Status: ", 8 ) )
574 575
        {
            /* Apache-style */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
576
            i_status = strtol( (char *)&answer->p_body[8], &psz_headers, 0 );
Antoine Cellerier's avatar
Antoine Cellerier committed
577 578
            if( *psz_headers == '\r' || *psz_headers == '\n' ) psz_headers++;
            if( *psz_headers == '\n' ) psz_headers++;
579 580 581 582 583
            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
584
            psz_headers = (char *)answer->p_body;
585 586
            i_headers = answer->i_body;
        }
587 588

        psz_status = httpd_ReasonFromCode( i_status );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
589 590
        answer->i_body = sizeof("HTTP/1.0 xxx \r\n")
                        + strlen(psz_status) + i_headers - 1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
591
        psz_new = (char *)xmalloc( answer->i_body + 1);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
592
        sprintf( psz_new, "HTTP/1.0 %03d %s\r\n", i_status, psz_status );
593 594
        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
595
        answer->p_body = (uint8_t *)psz_new;
596 597 598 599 600 601 602 603 604 605 606 607
    }

    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 )
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
608
    httpd_handler_t *handler = xmalloc( sizeof( httpd_handler_t ) );
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630

    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;
}

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

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

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

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

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

    /* 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;
}

676 677
httpd_redirect_t *httpd_RedirectNew( httpd_host_t *host, const char *psz_url_dst,
                                     const char *psz_url_src )
678
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
679
    httpd_redirect_t *rdir = xmalloc( sizeof( httpd_redirect_t ) );
680

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

688 689 690 691 692 693 694 695 696
    /* 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 );
697 698 699

    return rdir;
}
700
void httpd_RedirectDelete( httpd_redirect_t *rdir )
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 727
{
    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 */
};

728 729
static int httpd_StreamCallBack( httpd_callback_sys_t *p_sys,
                                 httpd_client_t *cl, httpd_message_t *answer,
730
                                 const httpd_message_t *query )
731 732 733 734 735 736 737
{
    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
738

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

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

        if( answer->i_body_offset >= stream->i_buffer_pos )
        {
            /* fprintf( stderr, "httpd_StreamCallBack: no data\n" ); */
            return VLC_EGENERIC;    /* wait, no data available */
        }
754 755
        if( answer->i_body_offset + stream->i_buffer_size <
            stream->i_buffer_pos )
756 757
        {
            /* this client isn't fast enough */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
758
#if 0
759 760
            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
761
#endif
762 763 764 765 766
            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;
767
        if( i_write > HTTPD_CL_BUFSIZE )
768
        {
769
            i_write = HTTPD_CL_BUFSIZE;
770 771 772 773 774 775
        }
        else if( i_write <= 0 )
        {
            return VLC_EGENERIC;    /* wait, no data available */
        }

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

779 780 781 782 783 784
        /* 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;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
785
        answer->p_body = xmalloc( i_write );
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
        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;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
808
                answer->p_body = xmalloc( stream->i_header );
809 810 811 812 813 814 815 816
                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 );
817
            answer->i_body_offset = 0;
818 819 820 821
        }

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

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

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

            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,
858 859
                                 const char *psz_url, const char *psz_mime,
                                 const char *psz_user, const char *psz_password,
860
                                 const vlc_acl_t *p_acl )
861
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
862
    httpd_stream_t *stream = xmalloc( sizeof( httpd_stream_t ) );
863

864
    if( ( stream->url = httpd_UrlNewUnique( host, psz_url, psz_user,
865
                                            psz_password, p_acl )
866
        ) == NULL )
867 868 869 870
    {
        free( stream );
        return NULL;
    }
871
    vlc_mutex_init( &stream->lock );
872 873 874 875 876 877 878 879 880 881 882
    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 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
883
    stream->p_buffer = xmalloc( stream->i_buffer_size );
884 885
    /* We set to 1 to make life simpler
     * (this way i_body_offset can never be 0) */
886 887 888
    stream->i_buffer_pos = 1;
    stream->i_buffer_last_pos = 1;

889 890 891 892 893 894
    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 );
895 896 897 898

    return stream;
}

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

905 906 907
    stream->i_header = i_data;
    if( i_data > 0 )
    {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
908
        stream->p_header = xmalloc( i_data );
909 910 911 912 913 914 915
        memcpy( stream->p_header, p_data, i_data );
    }
    vlc_mutex_unlock( &stream->lock );

    return VLC_SUCCESS;
}

916
int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data )
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 954 955
{
    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 );
956 957 958
    free( stream->psz_mime );
    free( stream->p_header );
    free( stream->p_buffer );
959 960 961 962 963 964
    free( stream );
}

/*****************************************************************************
 * Low level
 *****************************************************************************/
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
965
static void* httpd_HostThread( void * );
966 967

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

975
static const char psz_object_type[] = "http server";
976
static vlc_mutex_t httpd_mutex = VLC_STATIC_MUTEX;
977

978 979 980 981
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 )
982 983
{
    httpd_t      *httpd;
984
    httpd_host_t *host;
985 986
    tls_server_t *p_tls;
    char *psz_host;
987
    vlc_value_t  ptrval;
988
    int i;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
989

990 991 992
    if( psz_hostname == NULL )
        psz_hostname = "";

993
    psz_host = strdup( psz_hostname );
994
    if( psz_host == NULL )
995 996 997
        return NULL;

    /* to be sure to avoid multiple creation */
998
    vlc_mutex_lock( &httpd_mutex );
999
    httpd = libvlc_priv (p_this->p_libvlc)->p_httpd;
1000

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

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

1017
        ptrval.p_address = httpd;
1018
        libvlc_priv (p_this->p_libvlc)->p_httpd = 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 1035 1036 1037
        /* Increase existing matching host reference count.
         * The reference count is written under both the global httpd and the
         * host lock. It is read with either or both locks held. The global
         * lock is always acquired first. */
        vlc_mutex_lock( &host->lock );
1038
        host->i_ref++;
1039
        vlc_mutex_unlock( &host->lock );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1040

1041
        vlc_mutex_unlock( &httpd_mutex );
1042
        return host;
1043
    }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1044

1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
    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
1072
    /* create the new host */
1073
    host = (httpd_host_t *)vlc_custom_create( p_this, sizeof (*host),
1074
                                              VLC_OBJECT_GENERIC,
1075 1076 1077 1078
                                              psz_object_type );
    if (host == NULL)
        goto error;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1079
    host->httpd = httpd;
1080
    vlc_mutex_init( &host->lock );
1081
    vlc_cond_init( &host->wait );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1082 1083
    host->i_ref = 1;

1084 1085
    vlc_object_attach( host, p_this );

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

1094 1095 1096 1097 1098 1099
    if( vlc_object_waitpipe( VLC_OBJECT( host ) ) == -1 )
    {
        msg_Err( host, "signaling pipe error: %m" );
        goto error;
    }

1100 1101 1102
    host->i_port = i_port;
    host->psz_hostname = psz_host;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1103 1104 1105 1106
    host->i_url     = 0;
    host->url       = NULL;
    host->i_client  = 0;
    host->client    = NULL;
1107

1108
    host->p_tls = p_tls;
1109

1110
    /* create the thread */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1111 1112
    if( vlc_clone( &host->thread, httpd_HostThread, host,
                   VLC_THREAD_PRIORITY_LOW ) )
1113 1114
    {
        msg_Err( p_this, "cannot spawn http host thread" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1115
        goto error;
1116 1117 1118 1119
    }

    /* now add it to httpd */
    TAB_APPEND( httpd->i_host, httpd->host, host );
1120
    vlc_mutex_unlock( &httpd_mutex );
1121 1122 1123

    return host;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1124
error:
1125
    free( psz_host );
1126 1127
    if( httpd->i_host <= 0 )
    {
1128
        libvlc_priv (httpd->p_libvlc)->p_httpd = NULL;
1129
        vlc_object_release( httpd );
1130
    }
1131
    vlc_mutex_unlock( &httpd_mutex );
1132

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1133
    if( host != NULL )
1134
    {
1135
        net_ListenClose( host->fds );
1136
        vlc_cond_destroy( &host->wait );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1137
        vlc_mutex_destroy( &host->lock );
1138
        vlc_object_release( host );
1139 1140
    }

1141 1142 1143
    if( p_tls != NULL )
        tls_ServerDelete( p_tls );