/******************************************************************************
 * mod_uploader / mod_uploader.cpp
 ******************************************************************************
 * Copyright (C) 2005 Tetsuya Kimata <kimata@acapulco.dyndns.org>
 *
 * All rights reserved.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any
 * damages arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any
 * purpose, including commercial applications, and to alter it and
 * redistribute it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must
 *    not claim that you wrote the original software. If you use this
 *    software in a product, an acknowledgment in the product
 *    documentation would be appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must
 *    not be misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 *    distribution.
 *
 * $Id: mod_uploader.cpp 686 2005-09-15 10:40:49Z svn $
 *****************************************************************************/

#define __STDC_CONSTANT_MACROS

#include "mod_uploader.h"

#include "UploaderConfig.h"
#include "UploadItemCreator.h"
#include "TemplateVariableCreator.h"
#include "RssView.h"
#include "CharCodeConverter.h"
#ifdef MAKE_THUMBNAIL
#include "ThumbnailWriter.h"
#endif
#include "Auxiliary.h"
#include "Misc.h"

#include "http_core.h"

#ifdef AP_NEED_SET_MUTEX_PERMS
#ifdef __cplusplus
extern "C" {
#endif
#include "unixd.h"
#ifdef __cplusplus
}
#endif
#endif
#define APR_WANT_STRFUNC
#include "apr_want.h"

#include <stdlib.h>

#include <memory>


typedef UploaderConfig                      uconfig;
typedef UploaderConfig::page_template       page_template;
typedef TemplateExecutor::variable          variable;
typedef TemplateExecutor::scalar            scalar;
typedef TemplateExecutor::variable_map      variable_map;

static int command_handler(request_rec *r, uconfig *config, const char *arg);
static int view(request_rec *r, uconfig *config, const char *arg);
static int rss(request_rec *r, uconfig *config, const char *arg);
static int upload(request_rec *r, uconfig *config, const char *arg);
static int progress(request_rec *r, uconfig *config, const char *arg);
static int progress_data(request_rec *r, uconfig *config, const char *arg);
static int download(request_rec *r, uconfig *config, const char *arg);
static int remove(request_rec *r, uconfig *config, const char *arg);
static int error(request_rec *r, uconfig *config, const char *error);

static int input_download_pass(request_rec *r, uconfig *config, const char *arg,
                               UploadItem::header *header);
static int redirect_top(request_rec *r);
static uconfig *get_config(request_rec *r);
static apr_table_t *get_command_table(request_rec *r);
static variable *create_variable(apr_pool_t *pool, const char *str);
static variable *create_variable(apr_pool_t *pool, int i);
static variable_map *get_view_vmap(apr_pool_t *pool, UploaderConfig *config,
                                   page_template *tmpl, apr_size_t page);
static variable_map *get_download_vmap(apr_pool_t *pool, UploaderConfig *config,
                                       page_template *tmpl, UploadItem::header *header);
static variable_map *get_progress_vmap(apr_pool_t *pool, UploaderConfig *config,
                                       page_template *tmpl);
#ifdef MAKE_THUMBNAIL
static int thumbnail(request_rec *r, uconfig *config, const char *arg);
static variable_map *get_thumb_vmap(apr_pool_t *pool, UploaderConfig *config,
                                    page_template *tmpl, apr_size_t page);
#endif
static apr_size_t get_page(request_rec *r);
static apr_size_t get_upload_id(request_rec *r);
static const char *get_name_encoding(request_rec *r);

static apr_global_mutex_t *get_progress_lock(request_rec *r);
static pprogress *get_progress(request_rec *r, apr_size_t upload_id);
static pprogress *add_progress_list(request_rec *r, apr_size_t upload_id);
static pprogress *get_progress_list(request_rec *r);

static const char *get_client_address(request_rec *r);
static bool is_poster_list_contain(request_rec *r, const char *address);
static int poster_compare(const poster *a, const poster *b);
static void add_poster_list(request_rec *r, const char *address);
static poster *get_poster_list(request_rec *r);

static apr_status_t create_lock(apr_pool_t *pool, apr_global_mutex_t **lock,
                                const char *file_path, server_rec *s);
static apr_status_t attach_shm(apr_pool_t *pool, apr_shm_t **shm_data, void **data,
                               const char *file_path, server_rec *s);

#define MATCH(str, pat) (strstr(str, pat) != 0)

#define INFO(command, target) \
    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, \
                  "[" PACKAGE_NAME "] (%s) %s", command, target)

#ifdef DEBUG
#define SERROR(format, str) \
    fprintf(stderr, "[" PACKAGE_NAME "] " format, str); fputc('\n', stderr)
#define RERROR(format, str) \
    fprintf(stderr, "[" PACKAGE_NAME "] " format, str); fputc('\n', stderr)
#else
#define SERROR(format, str) \
    ap_log_error(APLOG_MARK, APLOG_CRIT, 0, server, "[" PACKAGE_NAME "] " format, str)
#define RERROR(format, str) \
    ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, "[" PACKAGE_NAME "] " format, str)
#endif


int command_handler(request_rec *r, uconfig *config, const char *arg)
{
    const char *command;

    if (UNLIKELY(*arg == '\0')) {
        apr_table_set(r->headers_out, "Location", apr_pstrcat(r->pool, r->uri, "/", NULL));

        return HTTP_TEMPORARY_REDIRECT;
    }
    arg++;

    command = ap_getword(r->pool, &arg, '/');

    if (LIKELY(*command == '\0')) {
        return view(r, config, arg);
    } else if (strcmp(command, RSS_COMMAND) == 0) {
        return rss(r, config, arg);
    } else if (strcmp(command, UPLOAD_COMMAND) == 0) {
        return upload(r, config, arg);
    } else if (strcmp(command, PROGRESS_COMMAND) == 0) {
        return progress(r, config, arg);
    } else if (strcmp(command, PROGRESS_DATA_COMMAND) == 0) {
        return progress_data(r, config, arg);
    } else if (strcmp(command, DOWNLOAD_COMMAND) == 0) {
        return download(r, config, arg);
    } else if (strcmp(command, REMOVE_COMMAND) == 0) {
        return remove(r, config, arg);
#ifdef MAKE_THUMBNAIL
    } else if (strcmp(command, THUMBNAIL_COMMAND) == 0) {
        return thumbnail(r, config, arg);
#endif
    } else {
        return error(r, config, "サポートされていないコマンドです．");
    }
}

int view(request_rec *r, uconfig *config, const char *arg)
{
    apr_status_t status;

    config->read_lock();

    ap_update_mtime(r, config->item_list->get_mtime());
    ap_set_last_modified(r);
    ap_set_etag(r);

    if ((status = ap_meets_conditions(r)) != OK) {
        config->read_unlock();
        return status;
    }

    ap_set_content_type(r, "text/html");
    if (r->header_only) {
        config->read_unlock();
        return OK;
    }

    try {
        page_template *tmpl = config->get_view_template(r->pool);

        auto_ptr<variable_map> vmap(get_view_vmap(r->pool, config, tmpl, get_page(r)));

        config->texecutor->exec(r->pool, r, tmpl->node, tmpl->imap, vmap.get());

        config->read_unlock();

        return OK;
    } catch(const char *message) {
        config->read_unlock();

        ap_rputs("<div class=\"warning\">", r);
        ap_rputs(message, r);
        ap_rputs("</div>", r);

        return OK;
    }
}

#ifdef MAKE_THUMBNAIL
int thumbnail(request_rec *r, uconfig *config, const char *arg)
{
    apr_status_t status;

    config->read_lock();

    ap_update_mtime(r, config->item_list->get_mtime());
    ap_set_last_modified(r);
    ap_set_etag(r);

    if ((status = ap_meets_conditions(r)) != OK) {
        config->read_unlock();
        return status;
    }

    ap_set_content_type(r, "text/html");
    if (r->header_only) {
        config->read_unlock();
        return OK;
    }

    try {
        page_template *tmpl = config->get_thumb_template(r->pool);

        auto_ptr<variable_map> vmap(get_thumb_vmap(r->pool, config, tmpl, get_page(r)));

        config->texecutor->exec(r->pool, r, tmpl->node, tmpl->imap, vmap.get());

        config->read_unlock();

        return OK;
    } catch(const char *message) {
        config->read_unlock();

        ap_rputs("<div class=\"warning\">", r);
        ap_rputs(message, r);
        ap_rputs("</div>", r);

        return OK;
    }
}
#endif

int rss(request_rec *r, uconfig *config, const char *arg)
{
    apr_status_t status;

    config->read_lock();

    ap_update_mtime(r, config->item_list->get_mtime());
    ap_set_last_modified(r);
    ap_set_etag(r);

    if ((status = ap_meets_conditions(r)) != OK) {
        config->read_unlock();
        return status;
    }

    ap_set_content_type(r, RssView::content_type);
    if (r->header_only) {
        config->read_unlock();
        return OK;
    }

    try {
        RssView view;

        view.exec(r->pool, r, config->url, config->item_list,
                  config->per_page_item_number);

        config->read_unlock();

        return OK;
    } catch(const char *message) {
        config->read_unlock();

        ap_rputs("<div class=\"warning\">", r);
        ap_rputs(message, r);
        ap_rputs("</div>", r);

        return OK;
    }
}

int upload(request_rec *r, uconfig *config, const char *arg)
{
    const char *address;
    apr_size_t upload_id;
    pprogress *progress;
    apr_status_t status;

    if (r->method_number != M_POST) {
        return HTTP_BAD_REQUEST;
    }
    if ((status = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK) {
        return status;
    }
    if (!ap_should_client_block(r)) {
        return HTTP_NO_CONTENT;
    }

    address = get_client_address(r);
    if (is_poster_list_contain(r, address)) {
        return error(r, config,
                     apr_psprintf(r->pool, "%" APR_SIZE_T_FMT  " 秒間は再投稿できません．",
                                  POST_INTERVAL_SEC));
    }

    upload_id = get_upload_id(r);
    progress = add_progress_list(r, upload_id);

    try {
        UploadItem::header *header;
        const char *tmp_path;
        const char *file_name;
        apr_size_t content_size;

        content_size = static_cast<apr_size_t>(apr_atoi64(apr_table_get(r->headers_in,
                                                                        "Content-Length")));
        progress->total_size = content_size;

        auto_ptr<UploadParser::query_map>
            qmap(config->uparser->parse(r->pool, r,
                                        apr_table_get(r->headers_in, "Content-Type"),
                                        content_size, progress));

        try {
            header = UploadItemCreator::create_header(r->pool, qmap.get(), &tmp_path);
        } catch(const char *) {
            UploadParser::clean_tmp_file(r->pool, qmap.get());

            throw;
        }

        auto_ptr<UploadItemWriter> writer(config->get_uwriter(r->pool));
        file_name = writer->write(header, tmp_path);

        config->gwrite_lock();
        try {
            config->item_list->add(file_name, true);
            add_poster_list(r, address);

            config->gwrite_unlock();
        } catch(const char *) {
            config->gwrite_unlock();
            throw;
        }

        INFO("upload", file_name);

        progress->end_time = apr_time_now();

        return redirect_top(r);
    } catch(const char *message) {
        progress->end_time = apr_time_now();

        return error(r, config, message);
    }
}

int progress(request_rec *r, uconfig *config, const char *arg)
{
    try {
        page_template *tmpl = config->get_progress_template(r->pool);

        auto_ptr<variable_map> vmap(get_progress_vmap(r->pool, config, tmpl));

        ap_set_content_type(r, "text/html");
        config->texecutor->exec(r->pool, r, tmpl->node, tmpl->imap, vmap.get());

        return OK;
    } catch(const char *message) {
        ap_rputs("<div class=\"warning\">", r);
        ap_rputs(message, r);
        ap_rputs("</div>", r);

        return OK;
    }
}

int progress_data(request_rec *r, uconfig *config, const char *arg)
{
    apr_size_t upload_id;
    pprogress *progress;

    upload_id = get_upload_id(r);

    // upload_id が 0 の時はエラー．
    if (UNLIKELY(upload_id == 0)) {
        ap_rputs("e", r);
        return OK;
    }

    progress = get_progress(r, upload_id);

    if (UNLIKELY(progress == get_progress_list(r))) {
        ap_rputs("u", r);
        return OK;
    }

    ap_rprintf(r,
               "s %" APR_SIZE_T_FMT " %" APR_SIZE_T_FMT,
               progress->total_size, progress->read_size);
    return OK;
}

int download(request_rec *r, uconfig *config, const char *arg)
{
    const char *name;

    name = ap_getword(r->pool, &arg, '/');
    if (name == NULL) {
        return HTTP_BAD_REQUEST;
    }

    {
        // ファイル名のチェック
        const char *pos = name;

        while (isalnum(*pos) || (*pos == '.')) {
            pos++;
        }

        if ((static_cast<size_t>(pos-name) != strlen(name)) ||
            (strlen(name) > APR_PATH_MAX)) {
            return HTTP_FORBIDDEN;
        }
    }

    try {
        apr_file_t *file;
        UploadItem::header *header;
        const char *file_name;
        const char *password;
        const char *open_mode;
        apr_size_t size;
        apr_status_t status;

        auto_ptr<UploadItemReader> reader(config->get_ureader(r->pool));
        header = reader->read(name, &file);

        password = apr_table_get(get_command_table(r), UploadItem::DOWNLOAD_PASS_PARAM);

        // DL pass のチェック
        if ((strlen(header->download_pass) > 0) &&
            ((password == NULL) ||
             (strncmp(header->download_pass, password, strlen(header->download_pass)) != 0))) {
            return input_download_pass(r, config, arg, header);
        }

        ap_update_mtime(r, header->time);
        ap_set_last_modified(r);
        ap_set_etag(r);

        if ((status = ap_meets_conditions(r)) != OK) {
            return status;
        }

        ap_set_content_type(r, header->file_mime);

        if (r->header_only) {
            return OK;
        }

        file_name = CharCodeConverter::convert(r->pool,
                                               header->file_name,
                                               CharCodeConverter::DEFAULT_CODE,
                                               get_name_encoding(r));

        // 表示モード or ダウンロードモード
        open_mode = (r->args == NULL) ? "inline" : "attachment";

        ap_set_content_length(r, header->file_size);
        apr_table_setn(r->headers_out, "Content-Disposition",
                       apr_psprintf(r->pool,
                                    "%s; filename=\"%s\"", open_mode, file_name));

        ap_send_fd(file, r, sizeof(UploadItem::header), header->file_size, &size);

        return OK;
    } catch(const char *message) {
        return error(r, config, message);
    }
}

int input_download_pass(request_rec *r, uconfig *config, const char *arg,
                        UploadItem::header *header)
{
    try {
        page_template *tmpl = config->get_download_template(r->pool);

        auto_ptr<variable_map> vmap(get_download_vmap(r->pool, config, tmpl, header));

        ap_set_content_type(r, "text/html");
        config->texecutor->exec(r->pool, r, tmpl->node, tmpl->imap, vmap.get());

        return OK;
    } catch(const char *message) {
        ap_rputs("<div class=\"warning\">", r);
        ap_rputs(message, r);
        ap_rputs("</div>", r);

        return OK;
    }
}

int remove(request_rec *r, uconfig *config, const char *arg)
{
    apr_table_t *ctable;

    if (r->method_number != M_POST) {
        return HTTP_BAD_REQUEST;
    }

    try {
        const char *file_name;
        const char *password;

        ctable = get_command_table(r);

        file_name = apr_table_get(ctable, UploadItem::FILE_PARAM);
        password = apr_table_get(ctable, UploadItem::REMOVE_PASS_PARAM);

        if ((file_name == NULL) || (password == NULL)) {
            return error(r, config, "ファイルまたはパスワードが指定されていません．");
        }

        config->gwrite_lock();
        try {
            config->item_list->remove(file_name, password);
            config->remove_item_number++;
            config->gwrite_unlock();

            INFO("remove", file_name);
        } catch(const char *) {
            config->gwrite_unlock();
            throw;
        }

        return redirect_top(r);
    } catch(const char *message) {
        return error(r, config, message);
    }
}

int error(request_rec *r, uconfig *config, const char *error)
{
    try {
        page_template *tmpl = config->get_error_template(r->pool);
        apr_size_t ident_id;

        TemplateExecutor::variable_map vmap(tmpl->imap->size(), NULL);

        ident_id = TemplateExecutor::get_ident_id(tmpl->imap, ERROR_MESSAGE_KEY);
        vmap.at(ident_id) = TemplateVariableCreator::create(r->pool, error);

        ap_set_content_type(r, "text/html");

        config->texecutor->exec(r->pool, r, tmpl->node, tmpl->imap, &vmap);

        return OK;
    } catch(const char *message) {
        RERROR("Exception: %s", message);

        return HTTP_INTERNAL_SERVER_ERROR;
    }
}

int redirect_top(request_rec *r)
{
    // POST した後にページを簡単にリロードできるようにするため，HTML で
    // リダイレクトさせる．

    ap_set_content_type(r, "text/html");

    ap_rputs("<?xml version=\"1.0\" encoding=\"EUC-JP\"?>", r);
    ap_rputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">", r);
    ap_rputs("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"ja\" >", r);
    ap_rputs(" <head>", r);
    ap_rputs("  <meta http-equiv=\"content-type\" content=\"application/xhtml+xml\" />", r);
    ap_rputs("  <meta http-equiv=\"refresh\" content=\"0;url=../\" />", r);
    ap_rputs("  <title>mod_uploader</title>", r);
    ap_rputs(" </head>", r);
    ap_rputs(" <body />", r);
    ap_rputs("</html>", r);

    return OK;
}

uconfig *get_config(request_rec *r)
{
    return static_cast<uconfig *>(ap_get_module_config(r->per_dir_config,
                                                       &uploader_module));
}

apr_table_t *get_command_table(request_rec *r)
{
    const char *command;
    const char *token;
    apr_table_t *table;

    table = apr_table_make(r->pool, MAX_COMMAND_NUMBER);

    APR_PCALLOC(command, char *, r->pool, MAX_COMMAND_LENGTH);

    if ((ap_setup_client_block(r, REQUEST_CHUNKED_ERROR) != OK) ||
        !ap_should_client_block(r)) {
        return table;
    }

    ap_get_client_block(r, const_cast<char *>(command), MAX_COMMAND_LENGTH-1);
    while ((*command != '\0') && (token = ap_getword(r->pool, &command, '&'))) {
        char *key;
        char *value;

        key = ap_getword(r->pool, &token, '=');
        value = ap_getword(r->pool, &token, '=');
        ap_unescape_url(key);
        ap_unescape_url(value);

        apr_table_set(table, key, value);
    }

    return table;
}

variable *create_variable(apr_pool_t *pool, const char *str)
{
    variable *var;

    APR_PALLOC(var, variable *, pool, sizeof(variable));

    var->type = TemplateExecutor::SCALAR;

    APR_PALLOC(var->s_val, scalar *, pool, sizeof(scalar));

    var->s_val->type = TemplateExecutor::STRING;
    var->s_val->s_val = str;

    return var;
}

variable *create_variable(apr_pool_t *pool, int i)
{
    variable *var;

    APR_PALLOC(var, variable *, pool, sizeof(variable));

    var->type = TemplateExecutor::SCALAR;

    APR_PALLOC(var->s_val, scalar *, pool, sizeof(scalar));

    var->s_val->type = TemplateExecutor::INTEGER;
    var->s_val->i_val = i;

    return var;
}

variable_map *get_view_vmap(apr_pool_t *pool, UploaderConfig *config,
                            page_template *tmpl, apr_size_t page)
{
    apr_size_t ident_id;
    variable_map *vmap;
    apr_size_t page_number;

    vmap = new variable_map(tmpl->imap->size()+SYSTEM_KEY_NUMBER, NULL);

    page_number = config->item_list->empty()
        ? 1
        : (config->item_list->size()-1)/config->per_page_item_number + 1;

    if (page > page_number) {
        page = page_number;
    }

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, PAGE_NUMBER_KEY);
    vmap->at(ident_id) = create_variable(pool, static_cast<int>(page_number));

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, FILE_LIST_KEY);
    vmap->at(ident_id) = config->item_list->to_varray(pool,
                                                      (page-1)*config->per_page_item_number,
                                                      config->per_page_item_number);

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, CURRENT_PAGE_KEY);
    vmap->at(ident_id) = create_variable(pool, static_cast<int>(page));

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, TOTAL_FILE_SIZE_KEY);
    vmap->at(ident_id) = create_variable(pool,
                                         size_str(pool, config->item_list->get_total_file_size()));

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, TOTAL_FILE_SIZE_KEY);
    vmap->at(ident_id) = create_variable(pool,
                                         size_str(pool, config->item_list->get_total_file_size()));

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, TOTAL_FILE_NUMBER_KEY);
    vmap->at(ident_id) = create_variable(pool,
                                         comma_str(pool, config->item_list->size()));

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, MAX_FILE_SIZE_KEY);
    vmap->at(ident_id) = create_variable(pool,
                                         size_str(pool, config->max_file_size));

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, TOTAL_FILE_SIZE_LIMIT_KEY);
    vmap->at(ident_id) = create_variable(pool,
                                         size_str(pool, config->total_file_size_limit));

    return vmap;
}

variable_map *get_progress_vmap(apr_pool_t *pool, UploaderConfig *config,
                                page_template *tmpl)
{
    variable_map *vmap;

    vmap = new variable_map(tmpl->imap->size()+SYSTEM_KEY_NUMBER, NULL);

    return vmap;
}

variable_map *get_download_vmap(apr_pool_t *pool, UploaderConfig *config,
                                page_template *tmpl, UploadItem::header *header)
{
    apr_size_t ident_id;
    variable_map *vmap;
    UploadItem item(pool);

    vmap = new variable_map(tmpl->imap->size()+SYSTEM_KEY_NUMBER, NULL);
    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, FILE_KEY);
    item.set_data("", header); // ごめんなさい．
    vmap->at(ident_id) = TemplateVariableCreator::create(pool, &item);

    return vmap;
}

#ifdef MAKE_THUMBNAIL
variable_map *get_thumb_vmap(apr_pool_t *pool, UploaderConfig *config,
                            page_template *tmpl, apr_size_t page)
{
    apr_size_t ident_id;
    variable_map *vmap;
    apr_size_t page_number;

    vmap = new variable_map(tmpl->imap->size()+SYSTEM_KEY_NUMBER, NULL);

    page_number = (config->item_list->get_thumb_size() == 0)
        ? 1
        : (config->item_list->get_thumb_size()-1)/config->per_page_item_number + 1;

    if (page > page_number) {
        page = page_number;
    }

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, PAGE_NUMBER_KEY);
    vmap->at(ident_id) = create_variable(pool, static_cast<int>(page_number));

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, THUMB_LIST_KEY);
    vmap->at(ident_id) = config->item_list->to_thumb_varray(pool,
                                                            (page-1)*config->per_page_item_number,
                                                            config->per_page_item_number);

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, CURRENT_PAGE_KEY);
    vmap->at(ident_id) = create_variable(pool, static_cast<int>(page));

    ident_id = TemplateExecutor::get_ident_id(tmpl->imap, THUMB_NUMBER_KEY);
    vmap->at(ident_id) = create_variable(pool,
                                         comma_str(pool, config->item_list->get_thumb_size()));

    return vmap;
}
#endif

apr_size_t get_page(request_rec *r)
{
    apr_size_t page;
    const char *args;

    page = 1;
    args = r->args;

    if (args != NULL) {
        page = static_cast<apr_size_t>(atoi(args));
    }

    return page;
}

apr_size_t get_upload_id(request_rec *r)
{
    apr_size_t upload_id;
    const char *args;

    upload_id = 0;
    args = r->args;

    if (args != NULL) {
        upload_id = static_cast<apr_size_t>(apr_atoi64(args));
    }

    return upload_id;
}

const char *get_name_encoding(request_rec *r)
{
    const char *agent = apr_table_get(r->headers_in, "User-Agent");

    // だー．激しく場当たり的．

    if (MATCH(agent, "compatible; MSIE") || MATCH(agent, "Sleipnir")) {
        return IE_NAME_CODE;
    } else if (MATCH(agent, "Firefox") || MATCH(agent, "Mozilla")) {
        return FIREFOX_NAME_CODE;
    } else {
        return CharCodeConverter::DEFAULT_CODE;
    }
}

apr_global_mutex_t *get_progress_lock(request_rec *r)
{
    return static_cast<sconfig *>(ap_get_module_config(r->server->module_config,
                                                        &uploader_module))->progress_lock;
}

pprogress *get_progress(request_rec *r, apr_size_t upload_id)
{
    pprogress *progress_list;
    apr_size_t i;

    progress_list = get_progress_list(r);

    if (upload_id == 0) {
        return progress_list;
    }

    for (i = 1; i < PROGRESS_LIST_NUMBER; i++) {
        if (progress_list[i].upload_id == upload_id) {
            return &progress_list[i];
        }
    }

    return progress_list;
}

pprogress *add_progress_list(request_rec *r, apr_size_t upload_id)
{
    pprogress *progress_list;
    apr_global_mutex_t *lock;
    apr_time_t now;
    apr_size_t i;

    progress_list = get_progress_list(r);

    if (upload_id == 0) {
        return progress_list;
    }

    lock = get_progress_lock(r);
    if (apr_global_mutex_lock(lock) != APR_SUCCESS) {
        return progress_list;
    }

    now = apr_time_now();

    // エントリの状態
    // progress_list[i].end_time == 0        : 未使用
    // progress_list[i].end_time == 1        : 使用中(アップロード中)
    // progress_list[i].end_time == それ以外 : 使用中(アップロード完了)

    // 既に同じ upload_id があるかチェックしつつ古いエントリを未使用に．
    for (i = 1; i < PROGRESS_LIST_NUMBER; i++) {
        if (progress_list[i].end_time == 0) {
            continue;
        }

        if ((progress_list[i].end_time != 1) &&
            ((now - progress_list[i].end_time) > (PROGRESS_HOLD_SEC*APR_USEC_PER_SEC))) {
            progress_list[i].upload_id = 0;
            progress_list[i].end_time = 0;
        } else if (progress_list[i].upload_id == upload_id) {
            apr_global_mutex_unlock(lock);

            return progress_list;
        }
    }

    // 未使用エントリを探して登録．
    for (i = 1; i < PROGRESS_LIST_NUMBER; i++) {
        if (progress_list[i].end_time == 0) {
            progress_list[i].upload_id  = upload_id;
            progress_list[i].total_size = 0;
            progress_list[i].read_size  = 0;
            progress_list[i].end_time   = 1;

            apr_global_mutex_unlock(lock);

            return &progress_list[i];
        }
    }

    apr_global_mutex_unlock(lock);

    return progress_list;
}

pprogress *get_progress_list(request_rec *r)
{
    return static_cast<sconfig *>(ap_get_module_config(r->server->module_config,
                                                       &uploader_module))->progress_list;
}

const char *get_client_address(request_rec *r)
{
    char *address = "";

    apr_sockaddr_ip_get(&address, r->connection->remote_addr);

    return address;
}

bool is_poster_list_contain(request_rec *r, const char *address)
{
    poster *poster_list;
    apr_time_t now;
    apr_size_t i;

    poster_list = get_poster_list(r);

    now = apr_time_now();
    for (i = 0; i < POSTER_LIST_NUMBER; i++) {
        if ((now - poster_list[i].time) > (POST_INTERVAL_SEC*APR_USEC_PER_SEC)) {
            break;
        }

        if (strcmp(poster_list[i].address, address) == 0) {
            return true;
        }
    }

    return false;
}

int poster_compare(const poster *a, const poster *b)
{
    return b->time > a->time;
}

void add_poster_list(request_rec *r, const char *address)
{
    poster *poster_list;
    apr_time_t now;
    apr_size_t i, j;

    poster_list = get_poster_list(r);

    now = apr_time_now();
    for (i = 0; i < POSTER_LIST_NUMBER; i++) {
        if ((now - poster_list[i].time) > (POST_INTERVAL_SEC*APR_USEC_PER_SEC)) {
            break;
        }
    }

    if (i == POSTER_LIST_NUMBER) {
        i--;
    }
    poster_list[i].time = now;
    strncpy(poster_list[i].address, address, sizeof(poster_list[i].address));
    j = i;

    while (++i < POSTER_LIST_NUMBER) {
        poster_list[i].time = 0;
    }

    // 必要最低限の範囲しかソートしない
    qsort(poster_list, j + 1, sizeof(poster),
          reinterpret_cast<int(*)(const void *, const void *)>(poster_compare));
}

poster *get_poster_list(request_rec *r)
{
    return static_cast<sconfig *>(ap_get_module_config(r->server->module_config,
                                                       &uploader_module))->poster_list;
}

apr_status_t create_lock(apr_pool_t *pool, apr_global_mutex_t **lock,
                         const char *file_path, server_rec *server)
{
    apr_status_t status;
    char error[MAX_ERROR_MESSAGE];

    status = apr_global_mutex_create(lock, file_path, APR_LOCK_DEFAULT, pool);
    if (status != APR_SUCCESS) {
        SERROR("Can not create global mutex (%s).", file_path);
        SERROR("(%s)", apr_strerror(status, error, sizeof(error)));

        return HTTP_INTERNAL_SERVER_ERROR;
    }

#ifdef AP_NEED_SET_MUTEX_PERMS
    status = unixd_set_global_mutex_perms(*lock);
    if (status != APR_SUCCESS) {
        SERROR("Can not set permissions on global mutex (%s).", file_path);
        SERROR("(%s)", apr_strerror(status, error, sizeof(error)));

        return HTTP_INTERNAL_SERVER_ERROR;
    }
#endif

    return APR_SUCCESS;
}

apr_status_t create_shm(apr_pool_t *pool, apr_shm_t **shm_data, void **data,
                        const char *file_path, apr_size_t size, server_rec *server)
{
    apr_status_t status;
    char error[MAX_ERROR_MESSAGE];

    // まずは，Anonymous Shared Memory を試してみる．
    status = apr_shm_create(shm_data, size, NULL, pool);
    if (status == APR_ENOTIMPL) {
        status = apr_shm_create(shm_data, size, file_path, pool);
    }
    if (status != APR_SUCCESS) {
        SERROR("Can not create shared segment file (%s).", file_path);
        SERROR("(%s)", apr_strerror(status, error, sizeof(error)));

        return status;
    }

    *data = apr_shm_baseaddr_get(*shm_data);
    memset(*data, 0, size);

    return APR_SUCCESS;
}

apr_status_t attach_shm(apr_pool_t *pool, apr_shm_t **shm_data, void **data,
                        const char *file_path, server_rec *server)
{
    apr_status_t status;
    char error[MAX_ERROR_MESSAGE];

    if (*shm_data == NULL) {
        status = apr_shm_attach(shm_data, file_path, pool);

        if (status != APR_SUCCESS) {
            SERROR("Can not attach to shared segment file (%s).", file_path);
            SERROR("(%s)", apr_strerror(status, error, sizeof(error)));

            return status;
        }
    }

    *data = apr_shm_baseaddr_get(*shm_data);

    return APR_SUCCESS;
}

extern "C" {
static int uploader_handler(request_rec *r)
{
    uconfig *config;

    if (strcmp(r->handler, "uploader")) {
        return DECLINED;
    }

    if (UNLIKELY(!ap_is_initial_req(r))) {
        return DECLINED;
    }

    config = get_config(r);

    try {
        config->init(r);
    } catch(const char *message) {
        RERROR("Exception: %s", message);

        return HTTP_INTERNAL_SERVER_ERROR;
    }

    return command_handler(r, config, r->path_info);
}

static apr_status_t finalize_server_config(void *data)
{
    sconfig *config;

    config = static_cast<sconfig *>(data);

    if (config->upload_lock != NULL) {
        apr_global_mutex_destroy(config->upload_lock);
        config->upload_lock = NULL;
    }
    if (config->progress_lock != NULL) {
        apr_global_mutex_destroy(config->progress_lock);
        config->progress_lock = NULL;
    }
    if (config->poster_list_shm != NULL) {
        apr_shm_destroy(config->poster_list_shm);
        config->poster_list_shm = NULL;
    }
    if (config->progress_list_shm != NULL) {
        apr_shm_destroy(config->progress_list_shm);
        config->progress_list_shm = NULL;
    }

    return APR_SUCCESS;
}

static apr_status_t finalize_dir_config(void *data)
{
    uconfig *config;

    config = static_cast<uconfig *>(data);
    config->finalize();

    return APR_SUCCESS;
}

static void *create_server_config(apr_pool_t *p, server_rec *s)
{
    sconfig *config;
    const char *tmp_dir;

    APR_PCALLOC(config, sconfig *, p, sizeof(sconfig));

    apr_temp_dir_get(&tmp_dir, p);

    apr_filepath_merge(&(config->upload_lock_path), tmp_dir,
                       apr_pstrcat(p, UPLOAD_LOCK_NAME, "_", s->server_hostname, NULL),
                       APR_FILEPATH_NOTABOVEROOT, p);
    apr_filepath_merge(&(config->progress_lock_path), tmp_dir,
                       apr_pstrcat(p, PROGRESS_LOCK_NAME, "_", s->server_hostname, NULL),
                       APR_FILEPATH_NOTABOVEROOT, p);
    apr_filepath_merge(&(config->poster_list_path), tmp_dir,
                       apr_pstrcat(p, POSTER_LIST_NAME, "_", s->server_hostname, NULL),
                       APR_FILEPATH_NOTABOVEROOT, p);
    apr_filepath_merge(&(config->progress_list_path), tmp_dir,
                       apr_pstrcat(p, PROGRESS_LIST_NAME, "_", s->server_hostname, NULL),
                       APR_FILEPATH_NOTABOVEROOT, p);

    config->poster_list_shm = NULL;
    config->progress_list_shm = NULL;

    return config;
}

static void *create_dir_config(apr_pool_t *pool, char *dirspec)
{
    uconfig *config;

    APR_PCALLOC(config, uconfig *, pool, sizeof(uconfig));
    new(config) uconfig;

    apr_pool_cleanup_register(pool, config, finalize_dir_config, apr_pool_cleanup_null);

    return config;
}

static apr_status_t post_config_server(apr_pool_t *pool, server_rec *server)
{
    sconfig *config;

    config = static_cast<sconfig *>(ap_get_module_config(server->module_config, &uploader_module));

    if (create_lock(pool, &(config->upload_lock), config->upload_lock_path,
                    server) != APR_SUCCESS) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    if (create_lock(pool, &(config->progress_lock), config->progress_lock_path,
                    server) != APR_SUCCESS) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    if (create_shm(pool, &(config->poster_list_shm),
                   reinterpret_cast<void **>(&config->poster_list),
                   config->poster_list_path, sizeof(poster)*POSTER_LIST_NUMBER,
                   server) != APR_SUCCESS) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    if (create_shm(pool, &(config->progress_list_shm),
                   reinterpret_cast<void **>(&(config->progress_list)),
                   config->progress_list_path, sizeof(pprogress)*PROGRESS_LIST_NUMBER,
                   server) != APR_SUCCESS) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    apr_pool_cleanup_register(pool, config, finalize_server_config, apr_pool_cleanup_null);

    return OK;
}

static void child_init_server(apr_pool_t *pool, server_rec *server)
{
    sconfig *config;

    config = static_cast<sconfig *>(ap_get_module_config(server->module_config, &uploader_module));

    if (apr_global_mutex_child_init(&(config->upload_lock),
                                    config->upload_lock_path, pool) != APR_SUCCESS) {
        SERROR("Can not attach to global mutex (%s).", config->upload_lock_path);

        return;
    }

    if (apr_global_mutex_child_init(&(config->progress_lock),
                                    config->progress_lock_path, pool) != APR_SUCCESS) {
        SERROR("Can not attach to global mutex (%s).", config->progress_lock_path);

        return;
    }

    if (attach_shm(pool, &(config->poster_list_shm),
                   reinterpret_cast<void **>(&config->poster_list),
                   config->poster_list_path, server) != APR_SUCCESS) {
        return;
    }

    if (attach_shm(pool, &(config->progress_list_shm),
                   reinterpret_cast<void **>(&config->progress_list),
                   config->progress_list_path, server) != APR_SUCCESS) {
        return;
    }
}

static int post_config(apr_pool_t *pconf, apr_pool_t *plog,
                       apr_pool_t *ptemp, server_rec *s)
{
    void *user_data;
    apr_status_t status;

    apr_pool_userdata_get(&user_data, USER_DATA_KEY, s->process->pool);
    if (user_data == NULL) {
        apr_pool_userdata_set(reinterpret_cast<const void *>(1), USER_DATA_KEY,
                              apr_pool_cleanup_null, s->process->pool);
        return OK;
    }

    do {
        status = post_config_server(plog, s);

        if (status != OK) {
            return status;
        }
    } while ((s = s->next) != NULL);

    // windows でこれをするとサービス名が変わってしまうらしい．
#ifndef WIN32
    ap_add_version_component(pconf, PACKAGE_NAME "/" PACKAGE_VERSION);
#endif

    return OK;
}

static void child_init(apr_pool_t *p, server_rec *s)
{
    do {
        child_init_server(p, s);
    } while ((s = s->next) != NULL);
}

static const char *set_url(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->url = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_file_dir(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->file_dir = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_thumb_dir(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->thumb_dir = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_tmp_dir(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->tmp_dir = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_view_tmpl_path(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->view_tmpl_path = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_progress_tmpl_path(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->progress_tmpl_path = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_download_tmpl_path(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->download_tmpl_path = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_thumb_tmpl_path(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->thumb_tmpl_path = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_error_tmpl_path(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->error_tmpl_path = apr_pstrdup(parms->pool, arg);

    return NULL;
}

static const char *set_max_file_size(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->max_file_size = atoi(arg) * 1024;

    return NULL;
}

static const char *set_total_file_size_limit(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->total_file_size_limit = atoi(arg) * 1024;

    return NULL;
}

static const char *set_total_file_number_limit(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->total_file_number_limit = atoi(arg);

    return NULL;
}

static const char *set_per_page_item_number(cmd_parms *parms, void *mconfig, char *arg)
{
    uconfig *config = static_cast<uconfig *>(mconfig);

    config->per_page_item_number = atoi(arg);

    return NULL;
}

static const command_rec uploader_cmds[] = {
    AP_INIT_TAKE1(URL_PARAM,
                  reinterpret_cast<const char*(*)()>(set_url), NULL,
                  ACCESS_CONF, "Uploader URL"),
    AP_INIT_TAKE1(FILE_DIRECTORY_PARAM,
                  reinterpret_cast<const char*(*)()>(set_file_dir), NULL,
                  ACCESS_CONF, "Upload File Directory"),
    AP_INIT_TAKE1(THUMB_DIRECTORY_PARAM,
                  reinterpret_cast<const char*(*)()>(set_thumb_dir), NULL,
                  ACCESS_CONF, "Thumbnail File Directory"),
    AP_INIT_TAKE1(TMP_DIRECTORY_PARAM,
                  reinterpret_cast<const char*(*)()>(set_tmp_dir), NULL,
                  ACCESS_CONF, "Temporary File Directory"),
    AP_INIT_TAKE1(VIEW_TEMPLATE_FILE_PARAM,
                  reinterpret_cast<const char*(*)()>(set_view_tmpl_path), NULL,
                  ACCESS_CONF, "View Template File"),
    AP_INIT_TAKE1(PROGRESS_TEMPLATE_FILE_PARAM,
                  reinterpret_cast<const char*(*)()>(set_progress_tmpl_path), NULL,
                  ACCESS_CONF, "Progress Template File"),
    AP_INIT_TAKE1(DOWNLOAD_TEMPLATE_FILE_PARAM,
                  reinterpret_cast<const char*(*)()>(set_download_tmpl_path), NULL,
                  ACCESS_CONF, "Download Template File"),
    AP_INIT_TAKE1(THUMB_TEMPLATE_FILE_PARAM,
                  reinterpret_cast<const char*(*)()>(set_thumb_tmpl_path), NULL,
                  ACCESS_CONF, "Thumbnail Template File"),
    AP_INIT_TAKE1(ERROR_TEMPLATE_FILE_PARAM,
                  reinterpret_cast<const char*(*)()>(set_error_tmpl_path), NULL,
                  ACCESS_CONF, "Error Template File"),
    AP_INIT_TAKE1(MAX_FILE_SIZE_PARAM,
                  reinterpret_cast<const char*(*)()>(set_max_file_size), NULL,
                  ACCESS_CONF, "Max File Size (KB)"),
    AP_INIT_TAKE1(TOTAL_FILE_SIZE_LIMIT_PARAM,
                  reinterpret_cast<const char*(*)()>(set_total_file_size_limit), NULL,
                  ACCESS_CONF, "Total File Size Limit (KB)"),
    AP_INIT_TAKE1(TOTAL_FILE_NUMBER_LIMIT_PARAM,
                  reinterpret_cast<const char*(*)()>(set_total_file_number_limit), NULL,
                  ACCESS_CONF, "Total File Number Limit"),
    AP_INIT_TAKE1(PER_PAGE_ITEM_NUMBER_PARAM,
                  reinterpret_cast<const char*(*)()>(set_per_page_item_number), NULL,
                  ACCESS_CONF, "Per Page Item Number"),
    { NULL }
};

static void uploader_register_hooks(apr_pool_t *pool)
{
    ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
    ap_hook_child_init(child_init, NULL, NULL, APR_HOOK_REALLY_FIRST);
    ap_hook_handler(uploader_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA uploader_module = {
    STANDARD20_MODULE_STUFF,
    create_dir_config,
    NULL,
    create_server_config,
    NULL,
    uploader_cmds,
    uploader_register_hooks
};
}

// Local Variables:
// mode: c++
// buffer-file-coding-system: euc-japan-dos
// End:
