#include #include #include #include "rast/filter.h" #define SUPPORTED_FRAMEWORK_VERSION 1 typedef struct { pid_t pid; int write_fd; int read_fd; } gzip_context_t; static rast_error_t * pass_while_readable(rast_filter_t *filter, gzip_context_t *context, apr_bucket_alloc_t *bucket_alloc, apr_pool_t *pool) { char out_buf[1024]; ssize_t buf_nbytes; apr_bucket_brigade *brigade = NULL; apr_bucket *bucket; while (1) { buf_nbytes = read(context->read_fd, out_buf, sizeof(out_buf)); if (buf_nbytes < 0) { if (errno == EAGAIN) { break; } return os_error_to_rast_error(errno); } if (buf_nbytes > 0) { const char *buf; buf = apr_pmemdup(pool, out_buf, buf_nbytes); bucket = apr_bucket_transient_create(buf, buf_nbytes, bucket_alloc); if (brigade == NULL) { brigade = apr_brigade_create(pool, bucket_alloc); } APR_BRIGADE_INSERT_TAIL(brigade, bucket); } if (buf_nbytes < sizeof(out_buf)) { break; } } if (brigade != NULL) { return rast_mime_filter_pass(filter, brigade, NULL, NULL); } return RAST_OK; } static rast_error_t * gzip_filter_invoke(rast_filter_t *filter, apr_bucket_brigade *brigade, const char *mime_type) { gzip_context_t *context; apr_bucket *bucket; apr_bucket_alloc_t *bucket_alloc; apr_pool_t *pool; apr_status_t status; rast_error_t *error; int flags; if (filter->context == NULL) { pid_t child_pid; int write_fds[2], read_fds[2]; pipe(write_fds); pipe(read_fds); child_pid = fork(); if (child_pid == 0) { close(write_fds[1]); close(read_fds[0]); dup2(write_fds[0], 0); dup2(read_fds[1], 1); close(write_fds[0]); close(read_fds[1]); execlp("gzip", "gzip", "-dc", NULL); } else if (child_pid < 0) { return os_error_to_rast_error(errno); } close(write_fds[0]); close(read_fds[1]); context = (gzip_context_t *) apr_palloc(filter->pool, sizeof(gzip_context_t)); context->pid = child_pid; context->write_fd = write_fds[1]; context->read_fd = read_fds[0]; flags = fcntl(context->write_fd, F_GETFL); fcntl(context->write_fd, F_SETFL, flags | O_NONBLOCK); flags = fcntl(context->read_fd, F_GETFL); fcntl(context->read_fd, F_SETFL, flags | O_NONBLOCK); } else { context = (gzip_context_t *) filter->context; } apr_pool_create(&pool, filter->pool); bucket_alloc = apr_bucket_alloc_create(pool); for (bucket = APR_BRIGADE_FIRST(brigade); bucket != APR_BRIGADE_SENTINEL(brigade); bucket = APR_BUCKET_NEXT(bucket)) { const char *out_buf; int buf_nbytes; ssize_t wrote_nbytes; if (APR_BUCKET_IS_EOS(bucket)) { apr_bucket *eos_bucket; apr_bucket_brigade *next_brigade; close(context->write_fd); flags = fcntl(context->read_fd, F_GETFL); fcntl(context->read_fd, F_SETFL, flags & ~O_NONBLOCK); error = pass_while_readable(filter, context, bucket_alloc, pool); if (error != RAST_OK) { apr_pool_destroy(pool); return error; } apr_pool_clear(pool); next_brigade = apr_brigade_create(pool, bucket_alloc); apr_bucket_copy(bucket, &eos_bucket); APR_BRIGADE_INSERT_TAIL(next_brigade, eos_bucket); error = rast_mime_filter_pass(filter, next_brigade, NULL, NULL); if (error != RAST_OK) { apr_pool_destroy(pool); return error; } } error = pass_while_readable(filter, context, bucket_alloc, pool); if (error != RAST_OK) { apr_pool_destroy(pool); return error; } status = apr_bucket_read(bucket, &out_buf, &buf_nbytes, APR_BLOCK_READ); if (status != APR_SUCCESS) { apr_pool_destroy(pool); return apr_status_to_rast_error(status); } while (buf_nbytes > 0) { pass_while_readable(filter, context, bucket_alloc, pool); wrote_nbytes = write(context->write_fd, out_buf, buf_nbytes); if (wrote_nbytes < 0) { if (errno == EAGAIN) { continue; } return os_error_to_rast_error(errno); } buf_nbytes -= wrote_nbytes; } error = pass_while_readable(filter, context, bucket_alloc, pool); if (error != RAST_OK) { apr_pool_destroy(pool); return error; } } error = pass_while_readable(filter, context, bucket_alloc, pool); apr_pool_destroy(pool); return error; } rast_error_t * rast_gzip_filter_module_initialize(rast_filter_map_t *map) { const char *mime_type = "application/x-gzip"; static rast_filter_module_t filter_module = { SUPPORTED_FRAMEWORK_VERSION, NULL, gzip_filter_invoke, }; rast_error_t *error; error = rast_filter_map_add_mime_filter(map, mime_type, &filter_module); if (error != RAST_OK) { /* todo: error handling */ rast_error_destroy(error); } error = rast_filter_map_add_extension(map, "gz", mime_type); if (error != RAST_OK) { /* todo: error handling */ rast_error_destroy(error); } return RAST_OK; }