/*
 * mod_put: implements the PUT and DELETE methods
 *
 * Original by Lyonel VINCENT <vincent@hpwww.ec-lyon.fr>
 * http://hpwww.ec-lyon.fr/~vincent/apache/mod_put.html
 * 
 * usage:
 *      EnablePut       On|Off
 *              default value:  Off
 *              context:        Directory
 *              effect:         enables (or disables) the PUT method for a
 *                              whole directory.
 *
 *      EnableDelete    On|Off
 *              default value:  Off
 *              context:        Directory
 *              effect:         enables (or disables) the DELETE method for a
 *                              whole directory.
 *      umask           <octal value>
 *              default value:  007
 *              context:        Directory
 *              effect:         sets the umask for a whole directory (see umask(1)).
 */

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

#define DEF_UMASK (S_IROTH | S_IWOTH | S_IXOTH)

typedef struct {
    int put_enabled;
    int delete_enabled;
    int umask;
} put_config_rec;

module put_module;

#define BLOCKSIZE 2048

static const char *put_set_putenabled_flag(cmd_parms *cmd,
                                    put_config_rec * sec, int arg)
{
    sec->put_enabled = arg;
    return NULL;
}

static const char *put_set_deleteenabled_flag(cmd_parms *cmd,
                                       put_config_rec * sec, int arg)
{
    sec->delete_enabled = arg;
    return NULL;
}

static const char *put_set_umask(cmd_parms *cmd,
                          put_config_rec * sec, char *arg)
{
    sec->umask = strtol(arg, (char **) NULL, 8);
    return NULL;
}

static command_rec put_cmds[] =
{
    {"EnablePut", put_set_putenabled_flag, NULL, OR_AUTHCFG, FLAG, "Limited to 'on' or 'off'"},
    {"EnableDelete", put_set_deleteenabled_flag, NULL, OR_AUTHCFG, FLAG, "Limited to 'on' or 'off'"},
    {"umask", put_set_umask, NULL, OR_AUTHCFG, TAKE1, "numeric umask"},
    {NULL}
};

static void *create_put_dir_config(pool *p, char *d)
{
    put_config_rec *sec = (put_config_rec *) ap_pcalloc(p, sizeof(put_config_rec));

    if (!sec)
        return NULL;            /* no memory... */

    sec->put_enabled = 0;
    sec->delete_enabled = 0;
    sec->umask = DEF_UMASK;
    return sec;
}

static void make_dirs(pool *p, const char *filename, int the_umask)
{
    char *sto = ap_pstrdup(p, filename), *slash = sto, *dirname = sto;

    while ((slash = strchr(slash + 1, '/'))) {
        *slash = '\0';
        *dirname = '/';
        mkdir(sto, (S_IRWXU | S_IRWXG | S_IRWXO) & ~the_umask);
        dirname = slash;
    }
}

static int do_put(request_rec *r)
{
    put_config_rec *sec =
    (put_config_rec *) ap_get_module_config(r->per_dir_config, &put_module);
    FILE *f;
    int result = OK, len = 0;
    mode_t old_umask;
    char *buffer;

    if (!sec->put_enabled)
        return FORBIDDEN;

    old_umask = umask(sec->umask);

    if (r->path_info) {         /* a directory did not exist */
        r->filename = ap_pstrcat(r->pool, r->filename, r->path_info, NULL);
        make_dirs(r->pool, r->filename, sec->umask);
    }

    f = ap_pfopen(r->pool, r->filename, "w");
    if (f == NULL) {
        ap_log_reason("file permissions deny server write access", r->filename, r);
        umask(old_umask);
        return FORBIDDEN;
    }

    if ((result = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) == OK) {
        if (ap_should_client_block(r)) {
            buffer = ap_palloc(r->pool, BLOCKSIZE);
            while ((len = ap_get_client_block(r, buffer, BLOCKSIZE)) > 0)
                fwrite(buffer, len, 1, f);
        }
        ap_send_http_header(r);
        ap_rprintf(r, "<HTML>File %s created.</HTML>\n", ap_escape_html(r->pool, r->uri));
    }

    ap_pfclose(r->pool, f);
    umask(old_umask);
    return result;
}

static int do_delete(request_rec *r)
{
    put_config_rec *sec =
    (put_config_rec *) ap_get_module_config(r->per_dir_config, &put_module);

    if (!sec->delete_enabled)
        return FORBIDDEN;
    if (r->finfo.st_mode == 0)
        return NOT_FOUND;
    if (unlink(r->filename)) {
        ap_log_reason("file permissions deny file deletion", r->filename, r);
        return FORBIDDEN;
    }

    ap_send_http_header(r);

    ap_rprintf(r, "<HTML>File %s deleted.</HTML>\n", ap_escape_html(r->pool, r->uri));

    return OK;
}

static int put_handler(request_rec *r)
{
    if (r->proxyreq)
        return DECLINED;
    if (r->method_number == M_PUT)
        return do_put(r);
    else if (r->method_number == M_DELETE)
        return do_delete(r);
    return DECLINED;
}

static handler_rec put_handlers[] =
{
    {"*/*", put_handler},
    {NULL}
};

module put_module =
{
    STANDARD_MODULE_STUFF,
    NULL,                       /* initializer */
    create_put_dir_config,      /* dir config creater */
    NULL,                       /* dir merger --- default is to override */
    NULL,                       /* server config */
    NULL,                       /* merge server config */
    put_cmds,                   /* command table */
    put_handlers,               /* handlers */
    NULL,                       /* filename translation */
    NULL,                       /* check_user_id */
    NULL,                       /* check access rights */
    NULL,                       /* check access */
    NULL,                       /* type_checker */
    NULL,                       /* fixups */
    NULL,                       /* logger */
    NULL,                       /* header parse */
    NULL,                       /* child_init */
    NULL,                       /* child_exit */
    NULL                        /* post read-request */
};
