/* * Copyright (C) 2005 Network Applied Communication Laboratory Co., Ltd. * * This file is part of Rast. * See the file COPYING for redistribution information. * */ #include #include #include #include #include #include #include #include "rast/rast.h" #include "rast/error.h" #include "rast/filter.h" #include "rast/util.h" #define CURRENT_FRAMEWORK_VERSION 1 const char * const RAST_JAPANESE_ENCODINGS[] = { "ISO-2022-JP", "EUC-JP", "UTF-8", "Shift_JIS", "CP932", "ISO-8859-1", "ASCII", NULL }; struct rast_filter_selector_t { const char *mime_type; rast_mime_filter_t *filter; apr_pool_t *pool; }; struct rast_filter_chain_t { rast_document_t *doc; rast_text_filter_t *head_filter; apr_pool_t *pool; rast_filter_selector_t *filter_selector; }; typedef struct dso_handle_entry_t { APR_RING_ENTRY(dso_handle_entry_t) link; apr_dso_handle_t *dso_handle; } dso_handle_entry_t; typedef APR_RING_HEAD(dso_handle_head_t, dso_handle_entry_t) dso_handle_head_t; static rast_filter_map_t *filter_map = NULL; static dso_handle_head_t *filter_modules; static rast_filter_selector_t *filter_selector_create(apr_pool_t *pool); static rast_error_t *filter_selector_exec(rast_filter_selector_t *selector, rast_filter_chain_t *chain, apr_bucket_brigade *brigade, const char *mime_type, const char *filename); static const char * check_mime_type_from_filename(const char *filename) { const char *p; if (filename == NULL) { return NULL; } p = strrchr(filename, '.'); if (p == NULL) { p = filename; } else { p++; } return (char *) apr_hash_get(filter_map->ext_to_mime, p, strlen(p)); } static rast_error_t * check_mime_type_from_magic(apr_bucket *bucket, const char **mime_type, apr_pool_t *pool) { magic_t cookie; const char *p, *q, *text; apr_size_t nbytes; const char *allowed_characters = "0123456789" "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-=;+/ "; cookie = magic_open(MAGIC_MIME); if (cookie == NULL) { return rast_error(RAST_ERROR_GENERAL, magic_error(cookie)); } if (magic_load(cookie, NULL) != 0) { rast_error_t *error; error = rast_error(RAST_ERROR_GENERAL, magic_error(cookie)); magic_close(cookie); return error; } apr_bucket_read(bucket, &text, &nbytes, APR_BLOCK_READ); p = magic_buffer(cookie, text, nbytes); if (p == NULL) { magic_close(cookie); return rast_error(RAST_ERROR_GENERAL, magic_error(cookie)); } q = p; while (*q != '\0' && strchr(allowed_characters, *q) != NULL) { q++; } *mime_type = apr_pstrndup(pool, p, q - p); magic_close(cookie); return RAST_OK; } rast_error_t * rast_encoding_converter_guess(const char * const *candidate_encodings, const char *text, size_t text_nbytes, const char **result) { iconv_t cd; char out_buf[1024]; size_t in_nbytes, out_nbytes, res; char *in_p, *out_p; int i; for (i = 0; candidate_encodings[i] != NULL; i++) { cd = iconv_open("UTF-8", candidate_encodings[i]); if (cd == (iconv_t) -1) { return os_error_to_rast_error(errno); } in_p = (char *) text; in_nbytes = text_nbytes; out_p = out_buf; out_nbytes = sizeof(out_buf); res = iconv(cd, &in_p, &in_nbytes, &out_p, &out_nbytes); if (iconv_close(cd) == -1) { return os_error_to_rast_error(errno); } if (res != (size_t) -1 || errno == EINVAL || errno == E2BIG) { *result = candidate_encodings[i]; return RAST_OK; } } *result = NULL; return RAST_OK; } static apr_status_t converter_cleanup(void *cd) { (void) iconv_close(*(iconv_t *) cd); return APR_SUCCESS; } rast_error_t * rast_encoding_converter_create(rast_encoding_converter_t **result, const char *from_encoding, const char **to_encodings, apr_pool_t *pool) { rast_encoding_converter_t *converter; /* todo: use multiple to_encodings */ const char *to_encoding = to_encodings[0]; apr_pool_t *sub_pool; apr_pool_create(&sub_pool, pool); converter = (rast_encoding_converter_t *) apr_palloc(pool, sizeof(rast_encoding_converter_t)); converter->cd = iconv_open(to_encoding, from_encoding); converter->head_buf = NULL; converter->head_buf_nbytes = 0; converter->done = 1; converter->pool = sub_pool; apr_pool_cleanup_register(pool, &converter->cd, converter_cleanup, NULL); *result = converter; return RAST_OK; } rast_error_t * rast_encoding_converter_add_text(rast_encoding_converter_t *converter, const char *buf, int buf_nbytes) { char *tmp_buf; int nbytes; nbytes = converter->head_buf_nbytes + buf_nbytes; tmp_buf = (char *) apr_palloc(converter->pool, nbytes); memcpy(tmp_buf, converter->head_buf, converter->head_buf_nbytes); memcpy(tmp_buf + converter->head_buf_nbytes, buf, buf_nbytes); converter->head_buf = tmp_buf; converter->head_buf_nbytes = nbytes; converter->done = 0; return RAST_OK; } rast_error_t * rast_encoding_converter_get_next(rast_encoding_converter_t *converter, char *out_buf, int *out_buf_nbytes) { size_t size, nbytes = *out_buf_nbytes; size = iconv(converter->cd, &converter->head_buf, &converter->head_buf_nbytes, &out_buf, &nbytes); *out_buf_nbytes -= nbytes; if (converter->head_buf_nbytes == 0) { apr_pool_clear(converter->pool); converter->head_buf = NULL; } if ((size != (size_t) -1 && converter->head_buf_nbytes == 0) || errno == EINVAL) { converter->done = 1; return RAST_OK; } if (errno == E2BIG) { return RAST_OK; } return rast_error(RAST_ERROR_INVALID_SQUENCE, "invalid multibyte sequence"); } int rast_encoding_converter_is_done(rast_encoding_converter_t *converter) { return converter->done; } rast_error_t * rast_convert_encoding(const char *from_encoding, const char *to_encoding, const char *text, size_t nbytes, char **out_buf, size_t *out_buf_nbytes, apr_pool_t *pool) { rast_encoding_converter_t *converter; const char *to_encodings[] = { to_encoding, NULL }; char *p; size_t alloced_nbytes, alloc_unit; apr_pool_t *sub_pool; rast_buf_ring_head_t *buffers; rast_buf_ring_entry_t *entry; rast_error_t *error; apr_pool_create(&sub_pool, pool); error = rast_encoding_converter_create(&converter, from_encoding, to_encodings, sub_pool); if (error != RAST_OK) { apr_pool_destroy(sub_pool); return error; } error = rast_encoding_converter_add_text(converter, text, nbytes); if (error != RAST_OK) { apr_pool_destroy(sub_pool); return error; } buffers = (rast_buf_ring_head_t *) apr_palloc(sub_pool, sizeof(rast_buf_ring_head_t)); APR_RING_INIT(buffers, rast_buf_ring_entry_t, link); alloc_unit = nbytes * 1.5; alloced_nbytes = 0; do { char *tmp = (char *) apr_palloc(sub_pool, alloc_unit); size_t tmp_nbytes = alloc_unit; error = rast_encoding_converter_get_next(converter, tmp, &tmp_nbytes); if (error != RAST_OK) { apr_pool_destroy(sub_pool); return error; } entry = (rast_buf_ring_entry_t *) apr_palloc(sub_pool, sizeof(rast_buf_ring_entry_t)); entry->buf = tmp; entry->nbytes = tmp_nbytes; APR_RING_INSERT_TAIL(buffers, entry, rast_buf_ring_entry_t, link); alloced_nbytes += tmp_nbytes; } while(!rast_encoding_converter_is_done(converter)); *out_buf = (char *) apr_palloc(pool, alloced_nbytes); p = *out_buf; for (entry = APR_RING_FIRST(buffers); entry != APR_RING_SENTINEL(buffers, rast_buf_ring_entry_t, link); entry = APR_RING_NEXT(entry, link)) { memcpy(p, entry->buf, entry->nbytes); p += entry->nbytes; } apr_pool_destroy(sub_pool); *out_buf_nbytes = alloced_nbytes; return RAST_OK; } static rast_error_t *register_filter_invoke(rast_filter_t *filter, apr_bucket_brigade *brigade, const char *mime_type); rast_error_t * rast_load_filters(const char *dirname) { apr_status_t status; apr_dir_t *dir; apr_finfo_t finfo; int name_len, shrext_len = strlen(SHREXT); apr_pool_t *pool, *sub_pool; static rast_filter_module_t register_filter_module = { CURRENT_FRAMEWORK_VERSION, NULL, register_filter_invoke, }; if (filter_map != NULL) { return rast_error(RAST_ERROR_GENERAL, "text filter modules are already loaded"); } apr_pool_create(&pool, rast_get_global_pool()); filter_map = (rast_filter_map_t *) apr_palloc(pool, sizeof(rast_filter_map_t)); filter_map->pool = pool; filter_map->mime_filters = apr_hash_make(filter_map->pool); filter_map->text_filters = apr_hash_make(filter_map->pool); filter_map->ext_to_mime = apr_hash_make(filter_map->pool); (void) rast_filter_map_add_mime_filter(filter_map, "text/plain", ®ister_filter_module); apr_pool_create(&sub_pool, pool); status = apr_dir_open(&dir, dirname, sub_pool); if (status != APR_SUCCESS) { apr_pool_destroy(sub_pool); return apr_status_to_rast_error(status); } filter_modules = (dso_handle_head_t *) apr_palloc(filter_map->pool, sizeof(dso_handle_head_t)); APR_RING_INIT(filter_modules, dso_handle_entry_t, link); while (1) { char *path, *func_name; const char *module_name; apr_dso_handle_t *handle; apr_dso_handle_sym_t sym; dso_handle_entry_t *entry; rast_filter_initialize_func_t *func; status = apr_dir_read(&finfo, APR_FINFO_TYPE | APR_FINFO_NAME, dir); if (status == APR_ENOENT) { break; } if (status != APR_SUCCESS || finfo.filetype != APR_REG) { continue; } name_len = strlen(finfo.name); if (name_len <= shrext_len || strcmp(finfo.name + name_len - shrext_len, SHREXT) != 0) { continue; } path = apr_pstrcat(sub_pool, dirname, "/", finfo.name, NULL); status = apr_dso_load(&handle, path, filter_map->pool); if (status != APR_SUCCESS) { continue; } module_name = apr_pstrndup(sub_pool, finfo.name, name_len - shrext_len); func_name = apr_pstrcat(sub_pool, "rast_", module_name, "_filter_module_initialize", NULL); status = apr_dso_sym(&sym, handle, func_name); if (status != APR_SUCCESS) { apr_dso_unload(handle); apr_dir_close(dir); apr_pool_destroy(sub_pool); return apr_status_to_rast_error(status); } entry = (dso_handle_entry_t *) apr_palloc(filter_map->pool, sizeof(dso_handle_entry_t)); entry->dso_handle = handle; APR_RING_INSERT_HEAD(filter_modules, entry, dso_handle_entry_t, link); func = (rast_filter_initialize_func_t *) sym; func(filter_map); } status = apr_dir_close(dir); apr_pool_destroy(sub_pool); return apr_status_to_rast_error(status); } rast_error_t * rast_unload_filters() { apr_status_t status; rast_error_t *error = RAST_OK; dso_handle_entry_t *entry; if (filter_map == NULL) { return RAST_OK; } for (entry = APR_RING_FIRST(filter_modules); entry != APR_RING_SENTINEL(filter_modules, dso_handle_entry_t, link); entry = APR_RING_NEXT(entry, link)) { status = apr_dso_unload(entry->dso_handle); if (status != APR_SUCCESS) { error = apr_status_to_rast_error(status); } } apr_pool_destroy(filter_map->pool); filter_map = NULL; return error; } rast_error_t * rast_filter_map_add_mime_filter(rast_filter_map_t *map, const char *mime_type, rast_filter_module_t *filter_module) { char *key; if (CURRENT_FRAMEWORK_VERSION < filter_module->supported_version) { return rast_error(RAST_ERROR_UNSUPPORTED_FILTER, "unsupported filter module: %d %s", filter_module->supported_version, mime_type); } key = apr_pstrdup(map->pool, mime_type); apr_hash_set(map->mime_filters, key, strlen(key), filter_module); return RAST_OK; } rast_error_t * rast_filter_map_add_text_filter(rast_filter_map_t *map, const char *name, rast_filter_module_t *filter_module) { char *key; if (CURRENT_FRAMEWORK_VERSION < filter_module->supported_version) { return rast_error(RAST_ERROR_UNSUPPORTED_FILTER, "unsupported filter module: %d %s", filter_module->supported_version, name); } key = apr_pstrdup(map->pool, name); apr_hash_set(map->text_filters, key, strlen(key), filter_module); return RAST_OK; } rast_error_t * rast_filter_map_add_extension(rast_filter_map_t *map, const char *ext, const char *mime_type) { char *key = apr_pstrdup(map->pool, ext); apr_hash_set(map->ext_to_mime, key, strlen(key), apr_pstrdup(map->pool, mime_type)); return RAST_OK; } static rast_filter_t * filter_create(apr_size_t alloc_nbytes, rast_filter_module_t *filter_module, rast_filter_chain_t *chain, apr_pool_t *pool) { rast_filter_t *filter; apr_pool_t *sub_pool; apr_pool_create(&sub_pool, pool); filter = (rast_filter_t *) apr_palloc(sub_pool, alloc_nbytes); filter->context = NULL; filter->filter_module = filter_module; filter->pool = sub_pool; filter->chain = chain; return filter; } static rast_mime_filter_t * mime_filter_create(rast_filter_module_t *filter_module, rast_filter_chain_t *chain, apr_pool_t *pool) { rast_mime_filter_t *filter; filter = (rast_mime_filter_t *) filter_create(sizeof(rast_mime_filter_t), filter_module, chain, pool); filter->selector = NULL; return filter; } static rast_text_filter_t * text_filter_create(rast_filter_module_t *filter_module, rast_filter_chain_t *chain, apr_pool_t *pool) { rast_text_filter_t *filter; filter = (rast_text_filter_t *) filter_create(sizeof(rast_text_filter_t), filter_module, chain, pool); filter->next = NULL; return filter; } static void filter_destroy(rast_filter_t *filter) { apr_pool_destroy(filter->pool); } rast_error_t * rast_mime_filter_pass(rast_filter_t *base, apr_bucket_brigade *brigade, const char *mime_type, const char *filename) { rast_mime_filter_t *filter = (rast_mime_filter_t *) base; if (filter->selector == NULL) { filter->selector = filter_selector_create(base->pool); } return filter_selector_exec(filter->selector, base->chain, brigade, mime_type, filename); } rast_error_t * rast_text_filter_pass(rast_filter_t *base, apr_bucket_brigade *brigade, const char *mime_type) { rast_text_filter_t *filter = (rast_text_filter_t *) base; return filter->next->base.filter_module->invoke(&filter->next->base, brigade, mime_type); } rast_error_t * rast_filter_set_property(rast_filter_t *filter, const char *name, const rast_value_t *value) { return rast_document_set_property(filter->chain->doc, name, value); } const char * rast_filter_db_encoding(rast_filter_t *filter) { return rast_db_encoding(filter->chain->doc->db); } static const char * mime_type_to_encoding(const char *mime_type) { const char *p; p = strchr(mime_type, '='); if (p == NULL) { return NULL; } return p + 1; } typedef struct { rast_encoding_converter_t *converter; apr_pool_t *converter_pool; const char *db_encoding; const char *input_encoding; } register_text_context_t; static rast_error_t * set_converter(register_text_context_t *context, const char *mime_type, const char *text, size_t nbytes) { rast_error_t *error; const char *encodings[] = { context->db_encoding, NULL }; context->input_encoding = mime_type_to_encoding(mime_type); if (context->input_encoding == NULL) { error = rast_encoding_converter_guess(RAST_JAPANESE_ENCODINGS, text, nbytes, &context->input_encoding); if (error != RAST_OK) { return error; } if (context->input_encoding == NULL) { context->input_encoding = context->db_encoding; } } return rast_encoding_converter_create(&context->converter, context->input_encoding, encodings, context->converter_pool); } static rast_error_t * register_text_filter_invoke(rast_filter_t *filter, apr_bucket_brigade *brigade, const char *mime_type) { const char *buf; apr_size_t nbytes; rast_error_t *error; apr_bucket *bucket; apr_status_t status; register_text_context_t *context; if (filter->context == NULL) { context = (register_text_context_t *) apr_palloc(filter->pool, sizeof(register_text_context_t)); context->db_encoding = rast_db_encoding(filter->chain->doc->db); if (context->db_encoding == NULL) { return rast_error(RAST_ERROR_GENERAL, "unknown encoding"); } apr_pool_create(&context->converter_pool, filter->pool); context->input_encoding = NULL; context->converter = NULL; filter->context = context; } else { context = (register_text_context_t *) filter->context; } for (bucket = APR_BRIGADE_FIRST(brigade); bucket != APR_BRIGADE_SENTINEL(brigade); bucket = APR_BUCKET_NEXT(bucket)) { if (APR_BUCKET_IS_EOS(bucket)) { if (context->converter != NULL) { apr_pool_clear(context->converter_pool); context->input_encoding = NULL; context->converter = NULL; } continue; } status = apr_bucket_read(bucket, &buf, &nbytes, APR_BLOCK_READ); if (status != APR_SUCCESS) { return apr_status_to_rast_error(status); } if (context->converter == NULL) { error = set_converter(context, mime_type, buf, nbytes); if (error != RAST_OK) { return error; } } error = rast_encoding_converter_add_text(context->converter, buf, nbytes); if (error != RAST_OK) { return error; } do { char out_buf[1024]; size_t out_buf_nbytes = sizeof(out_buf); error = rast_encoding_converter_get_next(context->converter, out_buf, &out_buf_nbytes); if (error != RAST_OK) { return error; } error = rast_document_add_text(filter->chain->doc, out_buf, out_buf_nbytes); if (error != RAST_OK) { return error; } } while (!rast_encoding_converter_is_done(context->converter)); } return RAST_OK; } static rast_error_t * register_filter_invoke(rast_filter_t *filter, apr_bucket_brigade *brigade, const char *mime_type) { rast_text_filter_t *head_filter; head_filter = filter->chain->head_filter; return head_filter->base.filter_module->invoke(&head_filter->base, brigade, mime_type); } static rast_filter_selector_t * filter_selector_create(apr_pool_t *pool) { rast_filter_selector_t *selector; apr_pool_t *sub_pool; apr_pool_create(&sub_pool, pool); selector = (rast_filter_selector_t *) apr_palloc(sub_pool, sizeof(rast_filter_selector_t)); selector->mime_type = NULL; selector->filter = NULL; selector->pool = sub_pool; return selector; } static rast_error_t * filter_selector_exec(rast_filter_selector_t *selector, rast_filter_chain_t *chain, apr_bucket_brigade *brigade, const char *mime_type, const char *filename) { apr_bucket *bucket; apr_pool_t *pool; rast_filter_module_t *filter_module; apr_bucket_alloc_t *bucket_alloc; apr_bucket_brigade *next_brigade = NULL; rast_error_t *error = RAST_OK; apr_pool_create(&pool, selector->pool); for (bucket = APR_BRIGADE_FIRST(brigade); bucket != APR_BRIGADE_SENTINEL(brigade); bucket = APR_BUCKET_NEXT(bucket)) { apr_bucket *next_bucket; const char *buf; apr_size_t nbytes; apr_status_t status; if (selector->mime_type == NULL) { if (APR_BUCKET_IS_EOS(bucket)) { continue; } if (mime_type == NULL) { mime_type = check_mime_type_from_filename(filename); if (mime_type == NULL) { error = check_mime_type_from_magic(bucket, &mime_type, selector->pool); if (error != RAST_OK) { apr_pool_destroy(pool); return error; } } } selector->mime_type = mime_type; buf = strchr(mime_type, ';'); if (buf == NULL) { nbytes = strlen(mime_type); } else { nbytes = buf - mime_type; } filter_module = apr_hash_get(filter_map->mime_filters, mime_type, nbytes); if (filter_module == NULL) { apr_pool_destroy(pool); return rast_error(RAST_ERROR_NOT_IMPLEMENTED, "no such filter module (%s)", mime_type); } selector->filter = mime_filter_create(filter_module, chain, selector->pool); } status = apr_bucket_copy(bucket, &next_bucket); if (status != APR_SUCCESS) { apr_pool_destroy(pool); return apr_status_to_rast_error(status); } if (next_brigade == NULL) { bucket_alloc = apr_bucket_alloc_create(pool); next_brigade = apr_brigade_create(pool, bucket_alloc); } APR_BRIGADE_INSERT_TAIL(next_brigade, next_bucket); if (APR_BUCKET_IS_EOS(bucket)) { filter_module = selector->filter->base.filter_module; error = filter_module->invoke(&selector->filter->base, next_brigade, selector->mime_type); if (error != RAST_OK) { apr_pool_destroy(pool); return error; } next_brigade = NULL; apr_pool_clear(pool); filter_destroy(&selector->filter->base); selector->mime_type = NULL; selector->filter = NULL; } } if (next_brigade != NULL) { filter_module = selector->filter->base.filter_module; error = filter_module->invoke(&selector->filter->base, next_brigade, selector->mime_type); } apr_pool_destroy(pool); return error; } static rast_filter_module_t * get_text_filter_module(const char *name) { return apr_hash_get(filter_map->text_filters, name, strlen(name)); } rast_error_t * rast_filter_chain_create(rast_filter_chain_t **result, rast_document_t *doc, const char **final_filters, int num_final_filters, apr_pool_t *pool) { static rast_filter_module_t register_text_filter_module = { CURRENT_FRAMEWORK_VERSION, NULL, register_text_filter_invoke, }; rast_filter_chain_t *chain; rast_text_filter_t *register_text_filter; apr_pool_t *sub_pool; apr_pool_create(&sub_pool, pool); chain = (rast_filter_chain_t *) apr_palloc(sub_pool, sizeof(rast_filter_chain_t)); chain->pool = sub_pool; chain->doc = doc; chain->filter_selector = filter_selector_create(chain->pool); register_text_filter = text_filter_create(®ister_text_filter_module, chain, chain->pool); if (num_final_filters == 0) { chain->head_filter = register_text_filter; } else { rast_text_filter_t *text_filter, *next_text_filter; rast_filter_module_t *filter_module; int i; filter_module = get_text_filter_module(final_filters[0]); if (filter_module == NULL) { return rast_error(RAST_ERROR_INVALID_ARGUMENT, "no such text filter: %s", final_filters[0]); } chain->head_filter = text_filter_create(filter_module, chain, chain->pool); text_filter = chain->head_filter; for (i = 1; i < num_final_filters; i++) { filter_module = get_text_filter_module(final_filters[i]); if (filter_module == NULL) { return rast_error(RAST_ERROR_INVALID_ARGUMENT, "no such text filter: %s", final_filters[i]); } next_text_filter = text_filter_create(filter_module, chain, chain->pool); text_filter->next = next_text_filter; text_filter = text_filter->next; } text_filter->next = register_text_filter; } *result = chain; return RAST_OK; } rast_error_t * rast_filter_chain_invoke(rast_filter_chain_t *chain, apr_bucket_brigade *brigade, const char *mime_type, const char *filename) { return filter_selector_exec(chain->filter_selector, chain, brigade, mime_type, filename); } rast_error_t * rast_filter_chain_commit(rast_filter_chain_t *chain) { return rast_document_commit(chain->doc); }