|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <limits.h>
|
|
|
|
#include "base.h"
|
|
#include "log.h"
|
|
#include "buffer.h"
|
|
|
|
#include "plugin.h"
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
/**
|
|
* this is a post_to_disk lighttpd plugin
|
|
*/
|
|
|
|
/* plugin config for all request/connections */
|
|
|
|
typedef struct {
|
|
buffer *tmp_dir_buf;
|
|
buffer *done_dir_buf;
|
|
buffer *failed_dir_buf;
|
|
} plugin_config;
|
|
|
|
typedef struct {
|
|
PLUGIN_DATA;
|
|
|
|
plugin_config **config_storage;
|
|
|
|
plugin_config conf;
|
|
|
|
} plugin_data;
|
|
|
|
typedef struct {
|
|
int fd;
|
|
char tmp_file[PATH_MAX];
|
|
char done_file[PATH_MAX];
|
|
char failed_file[PATH_MAX];
|
|
} handler_ctx;
|
|
|
|
static handler_ctx * handler_ctx_init() {
|
|
handler_ctx * hctx;
|
|
|
|
hctx = calloc(1, sizeof(*hctx));
|
|
hctx->fd = -1;
|
|
|
|
return hctx;
|
|
}
|
|
|
|
static void handler_ctx_free(handler_ctx *hctx) {
|
|
|
|
free(hctx);
|
|
}
|
|
|
|
/* init the plugin data */
|
|
INIT_FUNC(mod_post_to_disk_init) {
|
|
UNUSED(srv);
|
|
|
|
plugin_data *p;
|
|
|
|
p = calloc(1, sizeof(*p));
|
|
|
|
return p;
|
|
}
|
|
|
|
/* detroy the plugin data */
|
|
FREE_FUNC(mod_post_to_disk_free) {
|
|
plugin_data *p = p_d;
|
|
|
|
UNUSED(srv);
|
|
|
|
if (!p) return HANDLER_GO_ON;
|
|
|
|
if (p->config_storage) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < srv->config_context->used; i++) {
|
|
plugin_config *s = p->config_storage[i];
|
|
|
|
if (!s) continue;
|
|
|
|
buffer_free(s->tmp_dir_buf);
|
|
buffer_free(s->done_dir_buf);
|
|
buffer_free(s->failed_dir_buf);
|
|
|
|
free(s);
|
|
}
|
|
free(p->config_storage);
|
|
}
|
|
|
|
//buffer_free(p->tmp_dir_buf);
|
|
//buffer_free(p->done_dir_buf);
|
|
//buffer_free(p->failed_dir_buf);
|
|
|
|
free(p);
|
|
|
|
return HANDLER_GO_ON;
|
|
}
|
|
|
|
/* handle plugin config and check values */
|
|
|
|
SETDEFAULTS_FUNC(mod_post_to_disk_set_defaults) {
|
|
plugin_data *p = p_d;
|
|
size_t i = 0;
|
|
|
|
config_values_t cv[] = {
|
|
{ "post_to_disk.tmp-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
|
|
{ "post_to_disk.done-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
|
|
{ "post_to_disk.failed-dir", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
|
|
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
|
|
};
|
|
|
|
if (!p) return HANDLER_ERROR;
|
|
|
|
p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
|
|
|
|
for (i = 0; i < srv->config_context->used; i++) {
|
|
plugin_config *s;
|
|
|
|
s = calloc(1, sizeof(plugin_config));
|
|
|
|
s->tmp_dir_buf = buffer_init();
|
|
s->done_dir_buf = buffer_init();
|
|
s->failed_dir_buf = buffer_init();
|
|
|
|
cv[0].destination = s->tmp_dir_buf;
|
|
cv[1].destination = s->done_dir_buf;
|
|
cv[2].destination = s->failed_dir_buf;
|
|
|
|
p->config_storage[i] = s;
|
|
|
|
if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
|
|
return HANDLER_ERROR;
|
|
}
|
|
}
|
|
|
|
return HANDLER_GO_ON;
|
|
}
|
|
|
|
static int mod_post_to_disk_patch_connection(server *srv, connection *con, plugin_data *p) {
|
|
size_t i, j;
|
|
plugin_config *s = p->config_storage[0];
|
|
|
|
PATCH_OPTION(tmp_dir_buf);
|
|
PATCH_OPTION(done_dir_buf);
|
|
PATCH_OPTION(failed_dir_buf);
|
|
|
|
/* skip the first, the global context */
|
|
for (i = 1; i < srv->config_context->used; i++) {
|
|
data_config *dc = (data_config *)srv->config_context->data[i];
|
|
s = p->config_storage[i];
|
|
|
|
/* condition didn't match */
|
|
if (!config_check_cond(srv, con, dc)) continue;
|
|
|
|
/* merge config */
|
|
for (j = 0; j < dc->value->used; j++) {
|
|
data_unset *du = dc->value->data[j];
|
|
|
|
if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.tmp-dir"))) {
|
|
PATCH_OPTION(tmp_dir_buf);
|
|
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.done-dir"))) {
|
|
PATCH_OPTION(done_dir_buf);
|
|
} else if (buffer_is_equal_string(du->key, CONST_STR_LEN("post_to_disk.failed-dir"))) {
|
|
PATCH_OPTION(failed_dir_buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void reconstruct_request(request *r, buffer *b) {
|
|
// copy past from mod_proxy_backend_http
|
|
|
|
buffer_append_string(b, get_http_method_name(r->http_method));
|
|
BUFFER_APPEND_STRING_CONST(b, " ");
|
|
|
|
/* request uri */
|
|
buffer_append_string_buffer(b, r->uri);
|
|
|
|
if (r->http_version == HTTP_VERSION_1_1) {
|
|
BUFFER_APPEND_STRING_CONST(b, " HTTP/1.1\r\n");
|
|
} else {
|
|
BUFFER_APPEND_STRING_CONST(b, " HTTP/1.0\r\n");
|
|
}
|
|
|
|
for (size_t i = 0; i < r->headers->used; i++) {
|
|
data_string *ds;
|
|
|
|
ds = (data_string *)r->headers->data[i];
|
|
|
|
buffer_append_string_buffer(b, ds->key);
|
|
BUFFER_APPEND_STRING_CONST(b, ": ");
|
|
buffer_append_string_buffer(b, ds->value);
|
|
BUFFER_APPEND_STRING_CONST(b, "\r\n");
|
|
//ERROR("YYY: %s: %s\n", ds->key->ptr, ds->value->ptr);
|
|
}
|
|
|
|
BUFFER_APPEND_STRING_CONST(b, "\r\n");
|
|
|
|
// end of copy-paste
|
|
}
|
|
|
|
static void check_directory_writable(const char *path) {
|
|
int r;
|
|
|
|
struct stat s;
|
|
memset(&s, 0, sizeof(s));
|
|
|
|
r = stat(path, &s);
|
|
if (r < 0) {
|
|
ERROR("failed to stat %s: %s", path, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if (!S_ISDIR(s.st_mode)) {
|
|
ERROR("%s is not a directory", path);
|
|
return;
|
|
}
|
|
|
|
r = access(path, W_OK|X_OK);
|
|
if (r < 0) {
|
|
ERROR("failed to check access on %s: %s", path, strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
|
|
URIHANDLER_FUNC(mod_post_to_disk_uri_handler) {
|
|
plugin_data *p = p_d;
|
|
handler_ctx *hctx;
|
|
|
|
UNUSED(srv);
|
|
|
|
if (con->uri.path->used == 0) return HANDLER_GO_ON;
|
|
|
|
mod_post_to_disk_patch_connection(srv, con, p);
|
|
|
|
if (p->conf.tmp_dir_buf->used == 0 && p->conf.done_dir_buf->used == 0 && p->conf.failed_dir_buf->used == 0) {
|
|
return HANDLER_GO_ON;
|
|
}
|
|
|
|
if (p->conf.tmp_dir_buf->used == 0) {
|
|
ERROR("tmp-dir param not set", "");
|
|
return HANDLER_ERROR;
|
|
}
|
|
|
|
if (p->conf.done_dir_buf->used == 0) {
|
|
ERROR("done-dir param not set", "");
|
|
return HANDLER_ERROR;
|
|
}
|
|
|
|
if (p->conf.failed_dir_buf->used == 0) {
|
|
ERROR("failed-dir param not set", "");
|
|
return HANDLER_ERROR;
|
|
}
|
|
|
|
con->mode = p->id;
|
|
|
|
if (con->plugin_ctx[p->id]) {
|
|
hctx = con->plugin_ctx[p->id];
|
|
} else {
|
|
hctx = handler_ctx_init();
|
|
con->plugin_ctx[p->id] = hctx;
|
|
}
|
|
|
|
assert(hctx->fd == -1);
|
|
|
|
time_t t = time(NULL);
|
|
|
|
char time_string[20];
|
|
strftime(time_string, sizeof(time_string), "%Y%m%d-%H%M%S", localtime(&t));
|
|
|
|
char filename[40];
|
|
snprintf(filename, 40, "%s.%d.%08lx", time_string, getpid(), random());
|
|
|
|
snprintf(hctx->tmp_file, PATH_MAX, "%s/%s", p->conf.tmp_dir_buf->ptr, filename);
|
|
snprintf(hctx->done_file, PATH_MAX, "%s/%s", p->conf.done_dir_buf->ptr, filename);
|
|
snprintf(hctx->failed_file, PATH_MAX, "%s/%s", p->conf.failed_dir_buf->ptr, filename);
|
|
|
|
check_directory_writable(p->conf.tmp_dir_buf->ptr);
|
|
check_directory_writable(p->conf.done_dir_buf->ptr);
|
|
check_directory_writable(p->conf.failed_dir_buf->ptr);
|
|
|
|
int fd = open(hctx->tmp_file, O_CREAT|O_TRUNC|O_WRONLY, S_IRWXU|S_IRWXG|S_IRWXO);
|
|
if (fd < 0) {
|
|
ERROR("failed to open tmp file", hctx->tmp_file);
|
|
return HANDLER_ERROR;
|
|
}
|
|
|
|
// temp hack agains umask
|
|
chmod(hctx->tmp_file, 0666);
|
|
|
|
hctx->fd = fd;
|
|
|
|
int rv = 0;
|
|
buffer *b = buffer_init();
|
|
reconstruct_request(&con->request, b);
|
|
|
|
size_t written = 0;
|
|
while (written < b->used - 1) {
|
|
int r = write(fd, b->ptr + written, b->used - 1 - written);
|
|
if (r < 0) {
|
|
ERROR("failed to write headers", "");
|
|
rv = HANDLER_ERROR;
|
|
goto finally;
|
|
}
|
|
|
|
written += r;
|
|
}
|
|
|
|
/*
|
|
iosocket sock;
|
|
sock.fd = fd;
|
|
|
|
while (cq->bytes_in < cq->bytes_out) {
|
|
int r = network_write_chunkqueue_write(srv, con, &sock, cq);
|
|
switch (r) {
|
|
case NETWORK_STATUS_SUCCESS:
|
|
break;
|
|
default:
|
|
ERROR("failed to write to file", "");
|
|
rv = HANDLER_ERROR;
|
|
goto finally;
|
|
}
|
|
}
|
|
*/
|
|
|
|
rv = HANDLER_GO_ON;
|
|
finally:
|
|
buffer_free(b);
|
|
return rv;
|
|
}
|
|
|
|
SUBREQUEST_FUNC(mod_post_to_disk_read_response_content) {
|
|
plugin_data *p = p_d;
|
|
|
|
UNUSED(srv);
|
|
|
|
if (con->mode != p->id) return HANDLER_GO_ON;
|
|
|
|
//if (con->send->is_closed == 0) {
|
|
buffer *b;
|
|
|
|
b = chunkqueue_get_append_buffer(con->send);
|
|
BUFFER_APPEND_STRING_CONST(b, "uploaded successfully");
|
|
|
|
con->send->is_closed = 1;
|
|
//}
|
|
|
|
return HANDLER_FINISHED;
|
|
}
|
|
|
|
URIHANDLER_FUNC(mod_post_to_disk_send_request_content) {
|
|
plugin_data *p = p_d;
|
|
|
|
handler_ctx *hctx = con->plugin_ctx[p->id];
|
|
|
|
if (con->mode != p->id) return HANDLER_GO_ON;
|
|
|
|
assert(hctx->fd >= 0);
|
|
|
|
iosocket sock;
|
|
sock.fd = hctx->fd;
|
|
|
|
while (con->request.content_length > 0 && con->recv->bytes_in > con->recv->bytes_out) {
|
|
int r = network_write_chunkqueue_write(srv, con, &sock, con->recv);
|
|
switch (r) {
|
|
case NETWORK_STATUS_SUCCESS:
|
|
break;
|
|
case NETWORK_STATUS_WAIT_FOR_EVENT:
|
|
case NETWORK_STATUS_WAIT_FOR_AIO_EVENT:
|
|
break; // not sure
|
|
default:
|
|
ERROR("failed to write", "");
|
|
return HANDLER_ERROR;
|
|
}
|
|
chunkqueue_remove_finished_chunks(con->recv);
|
|
}
|
|
|
|
/* We have to close file and finish the request */
|
|
if ((con->recv->is_closed && con->recv->bytes_in == con->recv->bytes_out) ||
|
|
con->request.content_length <= 0)
|
|
{
|
|
close(hctx->fd);
|
|
hctx->fd = -1;
|
|
int r = rename(hctx->tmp_file, hctx->done_file);
|
|
if (r < 0) {
|
|
ERROR("failed to rename %s to %s: %s", hctx->tmp_file, hctx->done_file, strerror(errno));
|
|
}
|
|
} else {
|
|
/* There is more data to write */
|
|
return HANDLER_GO_ON;
|
|
}
|
|
|
|
// strange behaviour of lighttpd: function must be called explicitly here?
|
|
return mod_post_to_disk_read_response_content(srv, con, p_d);
|
|
}
|
|
|
|
REQUESTDONE_FUNC(mod_post_to_disk_connection_reset) {
|
|
plugin_data *p = p_d;
|
|
handler_ctx *hctx = con->plugin_ctx[p->id];
|
|
|
|
UNUSED(srv);
|
|
|
|
if (hctx) {
|
|
// cleanup; maybe there is a better place
|
|
|
|
if (hctx->fd >= 0) {
|
|
close(hctx->fd);
|
|
hctx->fd = -1;
|
|
}
|
|
|
|
rename(hctx->tmp_file, hctx->failed_file);
|
|
}
|
|
|
|
if (con->plugin_ctx[p->id]) {
|
|
handler_ctx_free(con->plugin_ctx[p->id]);
|
|
con->plugin_ctx[p->id] = NULL;
|
|
}
|
|
|
|
return HANDLER_GO_ON;
|
|
}
|
|
|
|
/* this function is called at dlopen() time and inits the callbacks */
|
|
|
|
int mod_post_to_disk_plugin_init(plugin *p) {
|
|
p->version = LIGHTTPD_VERSION_ID;
|
|
p->name = buffer_init_string("post_to_disk");
|
|
|
|
p->init = mod_post_to_disk_init;
|
|
p->cleanup = mod_post_to_disk_free;
|
|
|
|
p->handle_send_request_content = mod_post_to_disk_send_request_content;
|
|
//p->handle_read_response_content = mod_post_to_disk_read_response_content;
|
|
|
|
p->connection_reset = mod_post_to_disk_connection_reset;
|
|
|
|
p->handle_uri_clean = mod_post_to_disk_uri_handler;
|
|
p->set_defaults = mod_post_to_disk_set_defaults;
|
|
|
|
p->data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// vim: syntax=off ts=4 sw=4 et:
|