/**
 *  Copyright 2006 Paul Querna
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 *  Modified on 4.II.2008 by Artur Bodera (artur@bodera.com)
 *      - added offset reset if invalid
 *      - fixed content-length header generation
 *      - added last-modified header
 *      - inspiration: http://apachedev.ru/2006/07/13/mod_flvx-peredacha-potokovogo-flash-video
 *
 *  Usage:
 *      1. Compile and install with apxs tool:
 *          apxs -c -i ./mod_flvx.c
 *
 *           (http://httpd.apache.org/docs/2.0/programs/apxs.html)
 *          
 *      2. Add the following lines to your httpd.conf or create a
 *         dedicated /etc/httpd/conf.d/mod_flvx.conf config file:
 *         
 *         LoadModule flvx_module modules/mod_flvx.so
 *         AddHandler flv-stream .flv
 *
 *      3. Restart Apache!
 *      
 *      4. Now your flv files can be streamed using ?start= parameter
 *
 */

#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_log.h"
#include "apr_buckets.h"

#define FLVX_HANDLER "flv-stream"

#define FLVX_HEADER "FLV\x1\x1\0\0\0\x9\0\0\0\x9"
#define FLVX_HEADER_LEN (sizeof(FLVX_HEADER)-1)

static apr_off_t get_start(request_rec *r)
{
    apr_off_t start = 0;
    char *p = NULL;

    if (!r->args) {
        return start;
    }
    
    p = strstr(r->args, "start=");
    
    if (p) {
        p = p + 6;
        apr_strtoff(&start, p, NULL, 10);
    }

    return start;
}

static int drive_flvx(request_rec *r)
{
    apr_finfo_t fi;
    apr_bucket_brigade *bb;
    apr_off_t offset = 0;
    apr_off_t length = 0;
    apr_file_t *fp = NULL;
    apr_status_t rv = APR_SUCCESS;
    
    rv = apr_stat(&fi, r->filename, APR_FINFO_SIZE, r->pool);
    
    if (rv) {
        /* Let the core handle it. */
        return DECLINED;
    }
    
    /* Open the file */
    rv = apr_file_open(&fp, r->filename, APR_READ,
                       APR_OS_DEFAULT, r->pool);
    
    if (rv) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                      "file permissions deny server access: %s", r->filename);
        return HTTP_FORBIDDEN;
    }
    
    offset = get_start(r);
    
    if (offset != 0 && offset < fi.size) {
        length = fi.size - offset;
    }
    else {
        length = fi.size;
        
        /* Offset should be reset if invalid            (mod by Artur Bodera (artur@bodera.com) */
        offset = 0;
    }
    
    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);

    if (offset != 0) {
        rv = apr_brigade_write(bb, NULL, NULL, FLVX_HEADER, FLVX_HEADER_LEN);
        if (rv) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                          "unable to write flv header in brigade");
            return HTTP_INTERNAL_SERVER_ERROR;
        }
    }
    
    apr_brigade_insert_file(bb, fp, offset, length, r->pool);
    
    ap_set_content_type(r, "video/x-flv");
    
    /* Content length should include FLV header         (mod by Artur Bodera (artur@bodera.com) */
    ap_set_content_length(r, length + FLVX_HEADER_LEN);
    
    /* Add last-modified headers                        (mod by Artur Bodera (artur@bodera.com) */
    ap_update_mtime(r, r->finfo.mtime);
    ap_set_last_modified(r);    
    
    return ap_pass_brigade(r->output_filters, bb);
}

static int flvx_handler(request_rec *r)
{
    if ((!r->handler) ||
        (strcmp(r->handler, FLVX_HANDLER))) {
        return DECLINED;
    }
    
    r->allowed |= (AP_METHOD_BIT << M_GET);
    if (r->method_number != M_GET) {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    
    return drive_flvx(r);
}

static const command_rec flvx_cmds[] =
{
    {NULL}
};

static void register_hooks(apr_pool_t *p)
{
    ap_hook_handler(flvx_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA flvx_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    flvx_cmds,
    register_hooks
};
