httpd.c 78 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

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

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

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

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

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

52 53 54 55
#ifdef HAVE_POLL
# include <poll.h>
#endif

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

64 65 66 67 68 69 70
#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

71 72
static void httpd_ClientClean( httpd_client_t *cl );

73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
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;
95 96
    int         *fds;
    unsigned     nfd;
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114

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


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

    vlc_mutex_t lock;

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

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

    HTTPD_CLIENT_TLS_HS_IN,
    HTTPD_CLIENT_TLS_HS_OUT
148
};
Jean-Paul Saman's avatar
Jean-Paul Saman committed
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 180
/* 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
181

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


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

    /* 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 */
236
    { "",       "" }
237
};
Jean-Paul Saman's avatar
Jean-Paul Saman committed
238

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

    char *psz_ext;

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

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

260

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

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

326 327
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
328

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

333 334 335
    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
336 337 338 339 340 341

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

    return psz_fallback_reason[(i_code / 100) - 1];
}
342 343 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


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
374

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

};

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

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

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

431
    uint8_t *psz_args = query->psz_args;
432 433 434 435 436 437 438
    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 );
    }

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

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

    return VLC_SUCCESS;
}

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

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

480 481 482 483 484 485
    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 );
486 487 488 489

    return file;
}

490
httpd_file_sys_t *httpd_FileDelete( httpd_file_t *file )
491
{
492 493
    httpd_file_sys_t *p_sys = file->p_sys;

494 495 496 497 498 499
    httpd_UrlDelete( file->url );

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

    free( file );
500 501

    return p_sys;
502 503
}

504 505 506 507 508 509 510 511 512 513 514 515
/*****************************************************************************
 * 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;

};

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

    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
533 534
    if( httpd_ClientIP( cl, psz_remote_addr ) == NULL )
        *psz_remote_addr = '\0';
535

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

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

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

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

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

        psz_status = httpd_ReasonFromCode( i_status );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
586 587 588 589
        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 );
590 591
        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
592
        answer->p_body = (uint8_t *)psz_new;
593 594 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
    }

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

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

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

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

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

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

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

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

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

685 686 687 688 689 690 691 692 693
    /* 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 );
694 695 696

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

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

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

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

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

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

776 777 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
        /* 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 );
814
            answer->i_body_offset = 0;
815 816 817 818
        }

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

822 823
            httpd_MsgAdd( answer, "Content-type", "%s",
                          "application/octet-stream" );
824 825 826 827
            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\"" );
828

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

            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,
854 855
                                 const char *psz_url, const char *psz_mime,
                                 const char *psz_user, const char *psz_password,
856
                                 const vlc_acl_t *p_acl )
857 858 859
{
    httpd_stream_t *stream = malloc( sizeof( httpd_stream_t ) );

860
    if( ( stream->url = httpd_UrlNewUnique( host, psz_url, psz_user,
861
                                            psz_password, p_acl )
862
        ) == NULL )
863 864 865 866
    {
        free( stream );
        return NULL;
    }
867
    vlc_mutex_init( &stream->lock );
868 869 870 871 872 873 874 875 876 877 878 879
    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 );
880 881
    /* We set to 1 to make life simpler
     * (this way i_body_offset can never be 0) */
882 883 884
    stream->i_buffer_pos = 1;
    stream->i_buffer_last_pos = 1;

885 886 887 888 889 890
    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 );
891 892 893 894

    return stream;
}

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

901 902 903 904 905 906 907 908 909 910 911
    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;
}

912
int httpd_StreamSend( httpd_stream_t *stream, uint8_t *p_data, int i_data )
913 914 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
{
    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 );
952 953 954
    free( stream->psz_mime );
    free( stream->p_header );
    free( stream->p_buffer );
955 956 957 958 959 960
    free( stream );
}

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

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

971 972
static const char psz_object_type[] = "http server";

973 974 975 976
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 )
977 978
{
    httpd_t      *httpd;
979
    httpd_host_t *host;
980 981
    tls_server_t *p_tls;
    char *psz_host;
982
    vlc_value_t  lockval, ptrval;
983
    int i;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
984

985 986 987
    if( psz_hostname == NULL )
        psz_hostname = "";

988
    psz_host = strdup( psz_hostname );
989
    if( psz_host == NULL )
990 991 992
        return NULL;

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

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

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

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

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

1025 1026 1027 1028
        /* 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
1029 1030
            continue;

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

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

1038 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
    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
1065
    /* create the new host */
1066
    host = (httpd_host_t *)vlc_custom_create( p_this, sizeof (*host),
1067
                                              VLC_OBJECT_GENERIC,
1068 1069 1070 1071
                                              psz_object_type );
    if (host == NULL)
        goto error;

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

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

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

1093 1094 1095
    host->i_port = i_port;
    host->psz_hostname = psz_host;

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

1101
    host->p_tls = p_tls;
1102

1103
    /* create the thread */
1104
    if( vlc_thread_create( host, "httpd host thread", httpd_HostThread,