OSDN Git Service

* change writting.
[modchxj/mod_chxj.git] / src / mod_chxj.c
1 /*
2  * Copyright (C) 2005 QSDN,Inc. All rights reserved.
3  * Copyright (C) 2005 Atsushi Konno All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 #include <unistd.h>
18 #include <string.h>
19
20 #include "httpd.h"
21 #include "http_config.h"
22 #include "http_core.h"
23 #include "http_protocol.h"
24 #include "http_log.h"
25 #include "ap_config.h"
26 #include "apr_strings.h"
27 #include "util_filter.h"
28 #include "apr_buckets.h"
29 #include "apr_lib.h"
30 #include "apr_tables.h"
31 #include "apr_dso.h"
32 #include "apr_general.h"
33 #include "apr_pools.h"
34
35 #include "mod_chxj.h"
36 #include "chxj_encoding.h"
37 #include "qs_ignore_sp.h"
38 #include "qs_log.h"
39 #include "qs_malloc.h"
40 #include "qs_parse_attr.h"
41 #include "qs_parse_file.h"
42 #include "qs_parse_string.h"
43 #include "qs_parse_tag.h"
44 #include "chxj_load_device_data.h"
45 #include "chxj_load_emoji_data.h"
46 #include "chxj_specified_device.h"
47 #include "chxj_tag_util.h"
48 #include "chxj_xhtml_mobile_1_0.h"
49 #include "chxj_hdml.h"
50 #include "chxj_chtml10.h"
51 #include "chxj_chtml20.h"
52 #include "chxj_chtml30.h"
53 #include "chxj_jhtml.h"
54
55 #include "chxj_img_conv_format.h"
56 #include "chxj_qr_code.h"
57 #include "chxj_encoding.h"
58 #include "chxj_apply_convrule.h"
59 #include "chxj_cookie.h"
60 #include "chxj_url_encode.h"
61
62
63 #define CHXJ_VERSION_PREFIX PACKAGE_NAME "/"
64 #define CHXJ_VERSION        PACKAGE_VERSION
65
66 converter_t convert_routine[] = {
67   {
68     /* CHXJ_SPEC_UNKNOWN          */
69     .converter = NULL,
70     .encoder  = NULL,
71   },
72   {
73     /* CHXJ_SPEC_Chtml_1_0        */
74     .converter = chxj_exchange_chtml10,
75     .encoder  = chxj_encoding,
76   },
77   {
78     /* CHXJ_SPEC_Chtml_2_0        */
79     .converter = chxj_exchange_chtml20,
80     .encoder  = chxj_encoding,
81   },
82   {
83     /* CHXJ_SPEC_Chtml_3_0        */
84     .converter = chxj_exchange_chtml30,
85     .encoder  = chxj_encoding,
86   },
87   {
88     /* CHXJ_SPEC_Chtml_4_0        */
89     .converter = chxj_exchange_chtml30,
90     .encoder  = chxj_encoding,
91   },
92   {
93     /* CHXJ_SPEC_Chtml_5_0        */
94     .converter = chxj_exchange_chtml30,
95     .encoder  = chxj_encoding,
96   },
97   {
98     /* CHXJ_SPEC_XHtml_Mobile_1_0 */
99     .converter = chxj_exchange_xhtml_mobile_1_0,
100     .encoder  = chxj_encoding,
101   },
102   {
103     /* CHXJ_SPEC_Hdml             */
104     .converter = chxj_exchange_hdml,
105     .encoder  = chxj_encoding,
106   },
107   {
108     /* CHXJ_SPEC_Jhtml            */
109     .converter = chxj_exchange_jhtml,
110     .encoder  = chxj_encoding,
111   },
112   {
113     /* CHXJ_SPEC_HTML             */
114     .converter = NULL,
115     .encoder  = NULL,
116   },
117   {
118     NULL
119   }
120 };
121
122 static int chxj_convert_input_header(request_rec *r,chxjconvrule_entry* entryp);
123
124 /**
125  * Only when User-Agent is specified, the User-Agent header is camouflaged. 
126  *
127  * @param r   [i]
128  */
129 static apr_status_t 
130 chxj_headers_fixup(request_rec *r)
131 {
132   mod_chxj_config*    dconf; 
133   chxjconvrule_entry* entryp;
134   char*               user_agent;
135   device_table*       spec;
136
137   DBG(r, "start chxj_headers_fixup()");
138   dconf = ap_get_module_config(r->per_dir_config, &chxj_module);
139
140   user_agent = (char*)apr_table_get(r->headers_in, HTTP_USER_AGENT);
141   spec = chxj_specified_device(r, user_agent);
142
143   switch(spec->html_spec_type) {
144   case CHXJ_SPEC_Chtml_1_0:
145   case CHXJ_SPEC_Chtml_2_0:
146   case CHXJ_SPEC_Chtml_3_0:
147   case CHXJ_SPEC_Chtml_4_0:
148   case CHXJ_SPEC_Chtml_5_0:
149   case CHXJ_SPEC_XHtml_Mobile_1_0:
150   case CHXJ_SPEC_Hdml:
151   case CHXJ_SPEC_Jhtml:
152     entryp = chxj_apply_convrule(r, dconf->convrules);
153     if (! entryp) {
154       DBG(r, "end chxj_headers_fixup() no pattern");
155       return DECLINED;
156     }
157   
158     apr_table_setn(r->headers_in, 
159                    CHXJ_HTTP_USER_AGENT, 
160                    user_agent);
161   
162     if (entryp->user_agent)
163       apr_table_setn(r->headers_in, 
164                      HTTP_USER_AGENT, 
165                      entryp->user_agent);
166
167     chxj_convert_input_header(r,entryp);
168
169     break;
170   
171   default:
172     break;
173
174   }
175
176   DBG(r, "end chxj_headers_fixup()");
177
178   return DECLINED;
179 }
180
181
182 /**
183  * It converts it from CHTML into XXML corresponding to each model. 
184  *
185  * @param r   [i]
186  * @param src [i]   It is former HTML character string. 
187  * @param len [i/o] It is length of former HTML character string. 
188  */
189 static char* 
190 chxj_exchange(request_rec *r, const char** src, apr_size_t* len)
191 {
192   char*               user_agent;
193   char*               dst;
194   char*               tmp;
195   char*               cookie_id;
196   mod_chxj_config*    dconf; 
197   chxjconvrule_entry* entryp;
198
199   dst  = apr_pstrcat(r->pool, (char*)*src, NULL);
200
201   dconf = ap_get_module_config(r->per_dir_config, &chxj_module);
202
203
204   entryp = chxj_apply_convrule(r, dconf->convrules);
205
206   if (!entryp || !(entryp->action & CONVRULE_ENGINE_ON_BIT))
207     return (char*)*src;
208
209
210   /*------------------------------------------------------------------------*/
211   /* get UserAgent from http header                                         */
212   /*------------------------------------------------------------------------*/
213   if (entryp->user_agent)
214     user_agent = (char*)apr_table_get(r->headers_in, CHXJ_HTTP_USER_AGENT);
215   else
216     user_agent = (char*)apr_table_get(r->headers_in, HTTP_USER_AGENT);
217
218   DBG1(r,"User-Agent:[%s]", user_agent);
219   DBG(r, "start chxj_exchange()");
220   DBG1(r,"content type is %s", r->content_type);
221
222
223   if (*(char*)r->content_type == 't' 
224   && strncmp(r->content_type, "text/html",   9) != 0) {
225     DBG1(r,"content type is %s", r->content_type);
226     return (char*)*src;
227   }
228
229   /*
230    * save cookie.
231    */
232   cookie_id = NULL;
233   if (entryp->action & CONVRULE_COOKIE_ON_BIT)
234     cookie_id = chxj_save_cookie(r);
235
236   if (!r->header_only) {
237     device_table* spec = chxj_specified_device(r, user_agent);
238
239     tmp = NULL;
240     if (convert_routine[spec->html_spec_type].encoder)
241       tmp = convert_routine[spec->html_spec_type].encoder(r, 
242                                                           *src, 
243                                                           (apr_size_t*)len);
244
245     if (convert_routine[spec->html_spec_type].converter) {
246       if (tmp)
247         dst = convert_routine[spec->html_spec_type].converter(r, 
248                                                               spec, 
249                                                               tmp, 
250                                                               *len, 
251                                                               len, 
252                                                               entryp, 
253                                                               cookie_id);
254       else
255         dst = convert_routine[spec->html_spec_type].converter(r,
256                                                               spec, 
257                                                               tmp, 
258                                                               *len, 
259                                                               len, 
260                                                               entryp, 
261                                                               cookie_id);
262     }
263   }
264
265   if (*len == 0) {
266     dst = apr_psprintf(r->pool, "\n");
267     *len = 1;
268   }
269   dst[*len] = 0;
270
271   DBG(r, "end chxj_exchange()");
272
273   return dst;
274 }
275
276
277 /**
278  * It converts it from HEADER.
279  *
280  * @param r   [i]
281  */
282 static int
283 chxj_convert_input_header(request_rec *r,chxjconvrule_entry* entryp) 
284 {
285
286   char*      buff;
287   apr_size_t urilen;
288   char*      result;
289   char*      pair;
290   char*      name;
291   char*      value;
292   char*      pstate;
293   char*      vstate;
294
295   DBG(r, "start chxj_convert_input_header()");
296
297   if (! r->args) {
298     DBG(r, "r->args=[null]");
299     DBG(r, "end   chxj_convert_input_header()");
300     return 0;
301   }
302   urilen = strlen(r->args);
303
304   result = qs_alloc_zero_byte_string(r);
305
306   buff = apr_pstrdup(r->pool, r->args);
307   DBG1(r, "r->args=[%s]", buff);
308
309   /* _chxj_dmy */
310   /* _chxj_c_ */
311   /* _chxj_r_ */
312   /* _chxj_s_ */
313   for (;;) {
314
315     pair = apr_strtok(buff, "&", &pstate);
316     if (pair == NULL)
317       break;
318
319     buff = NULL;
320
321     name  = apr_strtok(pair, "=", &vstate);
322     value = apr_strtok(NULL, "=", &vstate);
323     if (strncasecmp(name, "_chxj", 5) != 0) {
324       if (strlen(result) != 0) 
325         result = apr_pstrcat(r->pool, result, "&", NULL);
326
327       if (strcasecmp(entryp->encoding, "NONE") != 0 && value && strlen(value)) {
328         apr_size_t dlen;
329         char* dvalue;
330
331         dlen   = strlen(value);
332         value = chxj_url_decode(r, value);
333         DBG1(r, "************ before encoding[%s]", value);
334
335         dvalue = chxj_rencoding(r, value, &dlen);
336         dvalue = chxj_url_encode(r, dvalue);
337
338         DBG1(r, "************ after encoding[%s]", dvalue);
339
340         result = apr_pstrcat(r->pool, result, name, "=", dvalue, NULL);
341       }
342       else {
343         if (strcmp(name, pair) != 0)
344           result = apr_pstrcat(r->pool, result, name, "=", value, NULL);
345         else
346           result = apr_pstrcat(r->pool, result, name, NULL);
347       }
348     }
349     else
350     if (strncasecmp(name, "_chxj_c_", 8) == 0 
351     ||  strncasecmp(name, "_chxj_r_", 8) == 0
352     ||  strncasecmp(name, "_chxj_s_", 8) == 0) {
353       if (value == NULL)
354         continue;
355
356       if (strlen(value) == 0)
357         continue;
358
359       if (strlen(result) != 0)
360         result = apr_pstrcat(r->pool, result, "&", NULL);
361
362       result = apr_pstrcat(r->pool, result, &name[8], "=", value, NULL);
363     }
364     else
365     if (strcasecmp(name, CHXJ_COOKIE_PARAM) == 0) {
366       DBG1(r, "found cookie parameter[%s]", value);
367       chxj_load_cookie(r, value);
368     }
369   }
370   r->args = result;
371
372   DBG1(r, "result r->args=[%s]", r->args);
373   DBG(r, "end   chxj_convert_input_header()");
374   return 0;
375 }
376
377
378 /**
379  * It converts it from POSTDATA .
380  *
381  * @param r   [i]
382  * @param src [i]   It is POSTDATA character string.
383  * @param len [i/o] It is length of former HTML character string.
384  */
385 static char*
386 chxj_input_convert(
387   request_rec*        r, 
388   const char**        src, 
389   apr_size_t*         len, 
390   chxjconvrule_entry* entryp)
391 {
392   char* pair;
393   char* name;
394   char* value;
395   char* pstate;
396   char* vstate;
397   char* s;
398   char* result;
399
400   s = apr_pstrdup(r->pool, *src);
401
402   result = qs_alloc_zero_byte_string(r);
403
404   DBG1(r, "BEFORE input convert source = [%s]", s);
405
406   /* _chxj_dmy */
407   /* _chxj_c_ */
408   /* _chxj_r_ */
409   /* _chxj_s_ */
410   for (;;) {
411     pair = apr_strtok(s, "&", &pstate);
412     if (pair == NULL)
413       break;
414     s = NULL;
415
416     name  = apr_strtok(pair, "=", &vstate);
417     value = apr_strtok(NULL, "=", &vstate);
418     if (strncasecmp(name, "_chxj", 5) != 0) {
419       if (strlen(result) != 0) 
420         result = apr_pstrcat(r->pool, result, "&", NULL);
421
422       if (strcasecmp(entryp->encoding, "NONE") != 0 
423       &&  value && strlen(value)) {
424         apr_size_t dlen;
425         char*      dvalue;
426
427         dlen   = strlen(value);
428         value = chxj_url_decode(r, value);
429         DBG1(r, "************ before encoding[%s]", value);
430
431         dvalue = chxj_rencoding(r, value, &dlen);
432         dvalue = chxj_url_encode(r,dvalue);
433
434         DBG1(r, "************ after encoding[%s]", dvalue);
435
436         result = apr_pstrcat(r->pool, result, name, "=", dvalue, NULL);
437
438       }
439       else {
440         result = apr_pstrcat(r->pool, result, name, "=", value, NULL);
441       }
442     }
443     else
444     if (strncasecmp(name, "_chxj_c_", 8) == 0 
445     ||  strncasecmp(name, "_chxj_r_", 8) == 0
446     ||  strncasecmp(name, "_chxj_s_", 8) == 0) {
447       if (value == NULL)
448         continue;
449
450       if (strlen(value) == 0)
451         continue;
452
453       if (strlen(result) != 0)
454         result = apr_pstrcat(r->pool, result, "&", NULL);
455
456       if (strcasecmp(entryp->encoding, "NONE") != 0 && value && strlen(value)) {
457         apr_size_t dlen;
458         char*      dvalue;
459
460         dlen   = strlen(value);
461         value = chxj_url_decode(r, value);
462         DBG1(r, "************ before encoding[%s]", value);
463
464         dvalue = chxj_rencoding(r, value, &dlen);
465         dvalue = chxj_url_encode(r,dvalue);
466
467         DBG1(r, "************ after encoding[%s]", dvalue);
468
469         result = apr_pstrcat(r->pool, result, &name[8], "=", dvalue, NULL);
470
471       }
472       else {
473         result = apr_pstrcat(r->pool, result, &name[8], "=", value, NULL);
474       }
475     }
476     else
477     if (strcasecmp(name, CHXJ_COOKIE_PARAM) == 0) {
478       DBG1(r, "found cookie parameter[%s]", value);
479       chxj_load_cookie(r, value);
480     }
481   }
482   *len = strlen(result);
483
484   DBG1(r, "AFTER input convert result = [%s]", result);
485
486   return result;
487 }
488
489
490 /**
491  * The received data is returned to the filter.
492  *
493  * @param f    [i/o] It is a filter. 
494  * @param data [i]   It is data returned to the filter. 
495  * @param len  [i]   It is length of the data returned to the filter. 
496  */
497 static apr_status_t 
498 pass_data_to_filter(ap_filter_t *f, const char *data, 
499                                         apr_size_t len)
500 {
501   request_rec*        r = f->r;
502   conn_rec*           c = r->connection;
503   apr_status_t        rv;
504   apr_bucket_brigade* bb;
505   apr_bucket*         b;
506
507   DBG(r, "start pass_data_to_filter()");
508
509   bb = apr_brigade_create(r->pool, c->bucket_alloc);
510   b  = apr_bucket_transient_create(data, len, c->bucket_alloc);
511
512   APR_BRIGADE_INSERT_TAIL(bb, b);
513   b = apr_bucket_eos_create(f->c->bucket_alloc);
514   APR_BRIGADE_INSERT_TAIL(bb, b);
515
516   rv = ap_pass_brigade(f->next, bb);
517   if (rv != APR_SUCCESS) {
518     DBG(r, "ap_pass_brigade()");
519     return rv;
520   }
521
522   DBG(r, "end pass_data_to_filter()");
523
524   return rv;
525 }
526
527
528 /**
529  * It is the main loop of the output filter. 
530  *
531  * @param f   [i/o] It is a filter.
532  * @param bb  [i]   
533  */
534 static apr_status_t 
535 chxj_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
536 {
537   request_rec*        r;
538   apr_status_t        rv;
539   apr_bucket*         b;
540   const char*         data;
541   char*               contentLength;
542   apr_size_t          len;
543   mod_chxj_ctx*       ctx;
544   char*               cookie_id;
545   char*               location_header;
546   mod_chxj_config*    dconf;
547   chxjconvrule_entry* entryp;
548
549
550
551   DBG(f->r, "start of chxj_output_filter()");
552
553   r  = f->r;
554   rv = APR_SUCCESS;
555
556   if (!f->ctx) {
557     if ((f->r->proto_num >= 1001) 
558     &&  !f->r->main 
559     &&  !f->r->prev) 
560       f->r->chunked = 1;
561   }
562
563   dconf  = ap_get_module_config(r->per_dir_config, &chxj_module);
564   entryp = chxj_apply_convrule(r, dconf->convrules);
565
566
567   for (b = APR_BRIGADE_FIRST(bb);
568        b != APR_BRIGADE_SENTINEL(bb); 
569        b = APR_BUCKET_NEXT(b)) {
570
571     if (APR_BUCKET_IS_EOS(b)) {
572
573       DBG(r, "eos");
574       /*----------------------------------------------------------------------*/
575       /* End Of File                                                          */
576       /*----------------------------------------------------------------------*/
577       if (f->ctx) {
578
579         ctx = (mod_chxj_ctx*)f->ctx;
580
581         DBG1(r, "content_type=[%s]", r->content_type);
582
583         if (r->content_type 
584         && *(char*)r->content_type == 't' 
585         && strncmp(r->content_type, "text/html",   9) == 0) {
586
587           if (ctx->len) {
588             char* tmp;
589
590             tmp = apr_palloc(r->pool, ctx->len + 1);
591
592             memset(tmp, 0, ctx->len + 1);
593             memcpy(tmp, ctx->buffer, ctx->len);
594
595             DBG2(r, "input data=[%s] len=[%d]", tmp, ctx->len);
596
597             ctx->buffer = chxj_exchange(r, 
598                                         (const char**)&tmp, 
599                                         (apr_size_t*)&ctx->len);
600
601             DBG2(r, "output data=[%.*s]", ctx->len,ctx->buffer);
602           }
603           else {
604             ctx->buffer = apr_psprintf(r->pool, "\n");
605             ctx->len += 1;
606             ctx->buffer = chxj_exchange(r, 
607                                         (const char**)&ctx->buffer, 
608                                         (apr_size_t*)&ctx->len);
609
610           }
611         }
612
613         if (r->content_type 
614         && *(char*)r->content_type == 'i' 
615         && strncmp(r->content_type, "image/", 6) == 0) {
616           if (ctx->len) {
617             char* tmp;
618
619             tmp = apr_palloc(r->pool, ctx->len + 1);
620
621             memset(tmp, 0, ctx->len + 1);
622             memcpy(tmp, ctx->buffer, ctx->len);
623
624             DBG1(r, "input data=[%s]", tmp);
625
626             ctx->buffer = 
627               chxj_exchange_image(r, 
628                                   (const char**)&tmp,
629                                   (apr_size_t*)&ctx->len);
630
631             if (ctx->buffer == NULL)
632               ctx->buffer = tmp;
633
634             DBG2(r, "output data=[%.*s]", ctx->len,ctx->buffer);
635           }
636         }
637
638         contentLength = apr_psprintf(r->pool, "%d", ctx->len);
639         apr_table_setn(r->headers_out, "Content-Length", contentLength);
640         
641         if (ctx->len > 0) {
642           rv = pass_data_to_filter(f, 
643                                    (const char*)ctx->buffer, 
644                                    (apr_size_t)ctx->len);
645         }
646         f->ctx = NULL;
647
648         return rv;
649       }
650       else {
651         DBG1(r, " SAVE COOKIE[%x]", entryp->action);
652
653         /*
654          * save cookie.
655          */
656         if (entryp->action & CONVRULE_COOKIE_ON_BIT) {
657           DBG(r, "entryp->action == COOKIE_ON_BIT");
658
659           cookie_id = chxj_save_cookie(r);
660
661           /*
662            * Location Header Check to add cookie parameter.
663            */
664           location_header = (char*)apr_table_get(r->headers_out, "Location");
665           if (location_header) {
666             DBG1(r, "Location Header=[%s]", location_header);
667             location_header = chxj_add_cookie_parameter(r,
668                                                         location_header,
669                                                         cookie_id);
670             apr_table_setn(r->headers_out, "Location", location_header);
671             DBG1(r, "Location Header=[%s]", location_header);
672           }
673         }
674
675         apr_table_setn(r->headers_out, "Content-Length", "0");
676         rv = pass_data_to_filter(f, (const char*)"", (apr_size_t)0);
677
678         return rv;
679       }
680     }
681     else
682     if (apr_bucket_read(b, &data, &len, APR_BLOCK_READ) == APR_SUCCESS) {
683       DBG2(r, "read data[%.*s]",len, data);
684
685       if (f->ctx == NULL) {
686         /*--------------------------------------------------------------------*/
687         /* Start                                                              */
688         /*--------------------------------------------------------------------*/
689         DBG(r, "new context");
690         ctx = (mod_chxj_ctx*)apr_palloc(r->pool, sizeof(mod_chxj_ctx));
691         if (len > 0) {
692           ctx->buffer = apr_palloc(r->pool, len);
693           memcpy(ctx->buffer, data, len);
694         }
695         else {
696           ctx->buffer = apr_palloc(r->pool, 1);
697           ctx->buffer = '\0';
698         }
699         ctx->len = len;
700         f->ctx = (void*)ctx;
701       }
702       else {
703         /*--------------------------------------------------------------------*/
704         /* append data                                                        */
705         /*--------------------------------------------------------------------*/
706         char* tmp;
707         DBG(r, "append data start");
708         ctx = (mod_chxj_ctx*)f->ctx;
709
710         if (len > 0) {
711           tmp = apr_palloc(r->pool, ctx->len);
712           memcpy(tmp, ctx->buffer, ctx->len);
713
714           ctx->buffer = apr_palloc(r->pool, ctx->len + len);
715
716           memcpy(ctx->buffer, tmp, ctx->len);
717           memcpy(&ctx->buffer[ctx->len], data, len);
718
719           ctx->len += len;
720         }
721         DBG(r, "append data end");
722       }
723     }
724   }
725   apr_brigade_destroy(bb);
726
727   DBG(r, "end of output filter");
728
729   return APR_SUCCESS;
730 }
731
732
733 /**
734  * It is the main loop of the input filter. 
735  *
736  * @param f         [i/o] It is a filter.
737  * @param bb        [i]   brigade
738  * @param mode      [i]   mode
739  * @param block     [i]   block
740  * @param readbytes [i]   readbyte
741  */
742 static apr_status_t 
743 chxj_input_filter(ap_filter_t*        f, 
744                  apr_bucket_brigade*  bb,
745                  ap_input_mode_t      mode, 
746                  apr_read_type_e      block,
747                  apr_off_t            readbytes)
748 {
749   request_rec*        r;
750   apr_status_t        rv;
751   conn_rec*           c;
752   apr_bucket*         b;
753   /*--------------------------------------------------------------------------*/
754   /* It is the brigade area for output                                        */
755   /*--------------------------------------------------------------------------*/
756   apr_bucket_brigade* ibb;            
757   /*--------------------------------------------------------------------------*/
758   /* It is the brigade area for input                                         */
759   /*--------------------------------------------------------------------------*/
760   apr_bucket_brigade* obb;
761   apr_size_t          len;
762   apr_bucket*         tmp_heap;
763   apr_bucket*         eos;
764   const char*         data;
765   char*               data_bucket;
766   char*               data_brigade;
767   char*               content_type;
768   device_table*       spec ;
769   char*               user_agent;
770   mod_chxj_config*    dconf;
771   chxjconvrule_entry* entryp;
772
773   r = f->r;
774   c = r->connection;
775
776   DBG(r, "start of chxj_input_filter()");
777
778   data_brigade = qs_alloc_zero_byte_string(r);
779
780
781   ibb = apr_brigade_create(r->pool, c->bucket_alloc);
782   obb = apr_brigade_create(r->pool, c->bucket_alloc);
783
784   content_type = (char*)apr_table_get(r->headers_in, "Content-Type");
785   if (content_type 
786   && strncasecmp("multipart/form-data", content_type, 19) == 0) {
787
788     DBG(r, "detect multipart/form-data");
789     ap_remove_input_filter(f);
790
791     return ap_get_brigade(f->next, bb, mode, block, readbytes);
792   }
793
794   dconf = ap_get_module_config(r->per_dir_config, &chxj_module);
795
796   entryp = chxj_apply_convrule(r, dconf->convrules);
797   if (!entryp || !(entryp->action & CONVRULE_ENGINE_ON_BIT)) {
798     DBG(r,"EngineOff");
799
800     ap_remove_input_filter(f);
801     return ap_get_brigade(f->next, bb, mode, block, readbytes);
802   }
803
804   user_agent = (char*)apr_table_get(r->headers_in, "User-Agent");
805   spec = chxj_specified_device(r, user_agent);
806
807   switch(spec->html_spec_type) {
808   case CHXJ_SPEC_Chtml_1_0:
809   case CHXJ_SPEC_Chtml_2_0:
810   case CHXJ_SPEC_Chtml_3_0:
811   case CHXJ_SPEC_Chtml_4_0:
812   case CHXJ_SPEC_Chtml_5_0:
813   case CHXJ_SPEC_XHtml_Mobile_1_0:
814   case CHXJ_SPEC_Hdml:
815   case CHXJ_SPEC_Jhtml:
816     break;
817
818   default:
819     ap_remove_input_filter(f);
820     return ap_get_brigade(f->next, bb, mode, block, readbytes);
821   }
822
823
824   rv = ap_get_brigade(f->next, ibb, mode, block, readbytes);
825   if (rv != APR_SUCCESS) {
826     DBG(r, "ap_get_brigade() failed");
827     return rv;
828   }
829
830   APR_BRIGADE_FOREACH(b, ibb) {
831     rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
832     if (rv != APR_SUCCESS) {
833       DBG(r, "apr_bucket_read() failed");
834       return rv;
835     }
836
837     if (data != NULL) {
838       data_bucket = apr_palloc(r->pool, len+1);
839       memset((void*)data_bucket, 0, len+1);
840       memcpy(data_bucket, data, len);
841       DBG1(r, "(in)POSTDATA:[%s]", data_bucket);
842   
843       data_brigade = apr_pstrcat(r->pool, data_brigade, data_bucket, NULL);
844     }
845
846     if (APR_BUCKET_IS_EOS(b)) {
847       break;
848     }
849   }
850   apr_brigade_cleanup(ibb);
851
852
853   len = strlen(data_brigade);
854   if (len == 0) {
855     DBG(r,"data_brigade length is 0");
856     DBG(r,"end of chxj_input_filter()");
857     ap_remove_input_filter(f);
858     return ap_get_brigade(f->next, bb, mode, block, readbytes);
859   }
860
861   data_brigade = chxj_input_convert(
862     r, 
863     (const char**)&data_brigade, 
864     (apr_size_t*)&len,
865     entryp
866     );
867
868   if (len > 0) {
869     DBG1(r, "(in:exchange)POSTDATA:[%s]", data_brigade);
870
871     obb = apr_brigade_create(r->pool, c->bucket_alloc);
872
873     tmp_heap = apr_bucket_heap_create(data_brigade, 
874                                       len, 
875                                       NULL, 
876                                       f->c->bucket_alloc);
877     eos      = apr_bucket_eos_create(f->c->bucket_alloc);
878
879     APR_BRIGADE_INSERT_TAIL(obb, tmp_heap);
880     APR_BRIGADE_INSERT_TAIL(obb, eos);
881     APR_BRIGADE_CONCAT(bb, obb);
882   }
883
884   DBG(r, "end of chxj_input_filter()");
885
886   return APR_SUCCESS;
887 }
888
889
890 static mod_chxj_global_config*
891 chxj_global_config_create(apr_pool_t* pool, server_rec* s)
892 {
893   mod_chxj_global_config* conf;
894
895   SDBG(s, "start chxj_global_config_create()");
896
897   /*--------------------------------------------------------------------------*/
898   /* allocate an own subpool which survives server restarts                   */
899   /*--------------------------------------------------------------------------*/
900   conf = (mod_chxj_global_config*)apr_palloc(pool, 
901                   sizeof(mod_chxj_global_config));
902 #if 0
903   conf->cookie_db_lock = NULL;
904 #endif
905   SDBG(s, "end   chxj_global_config_create()");
906
907   return conf;
908 }
909
910 /**
911  * initialize chxj module
912  */
913 static int 
914 chxj_init_module(apr_pool_t *p, 
915                   apr_pool_t *plog, 
916                   apr_pool_t *ptemp, 
917                   server_rec *s)
918 {
919 #if 0
920   mod_chxj_global_config* conf;
921 #endif
922   void *user_data;
923
924   SDBG(s, "start chxj_init_module()");
925
926   apr_pool_userdata_get(&user_data, CHXJ_MOD_CONFIG_KEY, s->process->pool);
927   SDBG(s, " ");
928   if (user_data == NULL) {
929     SDBG(s, " ");
930     /*
931      * dummy user_data set.
932      */
933     apr_pool_userdata_set(
934       (const void *)(1), 
935       CHXJ_MOD_CONFIG_KEY, 
936       apr_pool_cleanup_null, 
937       s->process->pool);
938     SDBG(s, "end  chxj_init_module()");
939     return OK;
940   }
941
942   ap_add_version_component(p, CHXJ_VERSION_PREFIX CHXJ_VERSION);
943
944
945 #if 0
946   conf = (mod_chxj_global_config *)ap_get_module_config(s->module_config, 
947                                                         &chxj_module);
948
949   if (apr_global_mutex_create(&(conf->cookie_db_lock), 
950                               NULL, APR_LOCK_DEFAULT, p) != APR_SUCCESS) {
951     SERR(s, "end  chxj_init_module()");
952     return HTTP_INTERNAL_SERVER_ERROR;
953   }
954
955 #ifdef AP_NEED_SET_MUTEX_PERMS
956   if (unixd_set_global_mutex_perms(conf->cookie_db_lock) != APR_SUCCESS) {
957     SERR(s, "end  chxj_init_module()");
958     return HTTP_INTERNAL_SERVER_ERROR;
959   }
960 #endif
961 #endif
962
963
964   SDBG(s, "end  chxj_init_module()");
965
966   return OK;
967 }
968
969 static void 
970 chxj_child_init(apr_pool_t *p, server_rec *s)
971 {
972 #if 0
973   mod_chxj_global_config* conf;
974 #endif
975
976   SDBG(s, "start chxj_child_init()");
977
978 #if 0
979   conf = (mod_chxj_global_config*)ap_get_module_config(s->module_config, 
980                                                        &chxj_module);
981
982   if (apr_global_mutex_child_init(&conf->cookie_db_lock, NULL, p) 
983   != APR_SUCCESS) {
984     SERR(s, "Can't attach global mutex.");
985     return;
986   }
987 #endif
988
989   SDBG(s, "end   chxj_child_init()");
990 }
991
992
993 /**
994  * A set structure of each server is generated. 
995  * 
996  * @param p
997  * @param s
998  */
999 static void*
1000 chxj_config_server_create(apr_pool_t *p, server_rec *s)
1001 {
1002   mod_chxj_global_config *gc;
1003
1004   gc = chxj_global_config_create(p,s);
1005
1006   return gc;
1007 }
1008
1009
1010 static int
1011 chxj_translate_name(request_rec *r)
1012 {
1013   return chxj_trans_name(r);
1014 }
1015
1016
1017 /**
1018  * The hook is registered.
1019  *
1020  * @param p
1021  */
1022 static void 
1023 chxj_register_hooks(apr_pool_t *p)
1024 {
1025   ap_hook_post_config(chxj_init_module,
1026                       NULL,
1027                       NULL,
1028                       APR_HOOK_REALLY_FIRST);
1029   ap_hook_child_init(chxj_child_init, 
1030                      NULL, 
1031                      NULL, 
1032                      APR_HOOK_REALLY_FIRST);
1033   ap_register_output_filter (
1034                       "chxj_output_filter", 
1035                       chxj_output_filter, 
1036                       NULL, 
1037                       AP_FTYPE_RESOURCE);
1038   ap_register_input_filter(
1039                       "chxj_input_filter", 
1040                       chxj_input_filter, 
1041                       NULL, 
1042                       AP_FTYPE_RESOURCE);
1043   ap_hook_handler(chxj_img_conv_format_handler, NULL, NULL, APR_HOOK_MIDDLE);
1044   ap_hook_handler(chxj_qr_code_handler, NULL, NULL, APR_HOOK_MIDDLE);
1045   ap_hook_translate_name(chxj_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
1046   ap_hook_fixups(chxj_headers_fixup, NULL, NULL, APR_HOOK_LAST);
1047 }
1048
1049
1050 /**
1051  * A set structure according to the directory is generated. 
1052  *
1053  * @param p
1054  * @param arg
1055  */
1056 static void* 
1057 chxj_create_per_dir_config(apr_pool_t *p, char *arg) 
1058 {
1059   mod_chxj_config* conf;
1060
1061   conf = apr_pcalloc(p, sizeof(mod_chxj_config));
1062   conf->device_data_file = NULL;
1063   conf->devices          = NULL;
1064   conf->emoji_data_file  = NULL;
1065   conf->emoji            = NULL;
1066   conf->emoji_tail       = NULL;
1067   conf->image            = CHXJ_IMG_OFF;
1068   conf->image_cache_dir  = apr_psprintf(p, "%s",DEFAULT_IMAGE_CACHE_DIR);
1069   conf->server_side_encoding = NULL;
1070   if (arg == NULL) {
1071     conf->dir                  = NULL;
1072   }
1073   else {
1074     conf->dir                  = apr_pcalloc(p, strlen(arg)+1);
1075     memset(conf->dir, 0, strlen(arg)+1);
1076     strcpy(conf->dir, arg);
1077   }
1078   conf->convrules   = apr_array_make(p, 2, sizeof(chxjconvrule_entry));
1079
1080   /* Default is copyleft */
1081   conf->image_copyright = NULL; 
1082
1083   return conf;
1084 }
1085
1086
1087 /*
1088  *  Merge per-directory CHXJ configurations
1089  */
1090 static void*
1091 chxj_merge_per_dir_config(apr_pool_t *p, void *basev, void *addv)
1092 {
1093   mod_chxj_config *base;
1094   mod_chxj_config *add;
1095   mod_chxj_config *mrg;
1096
1097   base = (mod_chxj_config*)basev;
1098   add  = (mod_chxj_config*)addv;
1099   mrg  = (mod_chxj_config*)apr_palloc(p, sizeof(mod_chxj_config));
1100
1101   mrg->device_data_file = NULL;
1102   mrg->devices          = NULL;
1103   mrg->emoji_data_file  = NULL;
1104   mrg->image            = CHXJ_IMG_OFF;
1105   mrg->image_cache_dir  = NULL;
1106   mrg->image_copyright  = NULL;
1107   mrg->emoji            = NULL;
1108   mrg->emoji_tail       = NULL;
1109
1110   mrg->dir = apr_pstrdup(p, add->dir);
1111
1112   if (! add->device_data_file) {
1113     mrg->devices = base->devices;
1114     mrg->device_data_file = apr_pstrdup(p, base->device_data_file);
1115   }
1116   else {
1117     mrg->devices = add->devices;
1118     mrg->device_data_file = apr_pstrdup(p, add->device_data_file);
1119   }
1120
1121   if (! add->emoji_data_file) {
1122     mrg->emoji = base->emoji;
1123     mrg->emoji_tail = base->emoji_tail;
1124     mrg->emoji_data_file = apr_pstrdup(p, base->emoji_data_file);
1125   }
1126   else {
1127     mrg->emoji = add->emoji;
1128     mrg->emoji_tail = add->emoji_tail;
1129     mrg->emoji_data_file = apr_pstrdup(p, add->emoji_data_file);
1130   }
1131
1132   if (add->image == CHXJ_IMG_OFF) 
1133     mrg->image = base->image;
1134   else 
1135     mrg->image = add->image;
1136
1137
1138   if (strcasecmp(add->image_cache_dir ,DEFAULT_IMAGE_CACHE_DIR)==0) 
1139     mrg->image_cache_dir = apr_pstrdup(p, base->image_cache_dir);
1140   else 
1141     mrg->image_cache_dir = apr_pstrdup(p, add->image_cache_dir);
1142
1143   if (add->image_copyright) 
1144     mrg->image_copyright = apr_pstrdup(p, add->image_copyright);
1145   else
1146     mrg->image_copyright = apr_pstrdup(p, base->image_copyright);
1147
1148   if (add->server_side_encoding) {
1149     mrg->server_side_encoding = apr_pstrdup(p, add->server_side_encoding);
1150   }
1151   else 
1152   if (base->server_side_encoding) {
1153     mrg->server_side_encoding = apr_pstrdup(p, base->server_side_encoding);
1154   }
1155   else {
1156     mrg->server_side_encoding = apr_pstrdup(p, DEFAULT_SERVER_SIDE_ENCODING);
1157   }
1158
1159   mrg->convrules    = apr_array_append(p, add->convrules, base->convrules);
1160
1161   return mrg;
1162 }
1163
1164
1165 static int
1166 chxj_command_parse_take5(
1167   const char* arg, 
1168   char** prm1, 
1169   char** prm2, 
1170   char** prm3, 
1171   char** prm4, 
1172   char** prm5)
1173 {
1174   int isquoted;
1175   char* strp;
1176
1177   strp = (char*)arg;
1178
1179   for (;*strp == ' '||*strp == '\t'; strp++) ;
1180
1181   isquoted = 0; 
1182   if (*strp == '"') { 
1183     isquoted = 1;
1184     strp++;
1185   }
1186
1187   *prm1 = strp;
1188
1189   for (; *strp != '\0'; strp++) {
1190     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1191     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1192       strp++;
1193       continue;
1194     }
1195
1196     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1197     ||  (isquoted  && *strp == '"'))
1198       break;
1199   }
1200
1201   if (! *strp) {
1202     *prm2 = strp;
1203     *prm3 = strp;
1204     *prm4 = strp;
1205     *prm5 = strp;
1206     return 1;
1207   }
1208
1209   *strp++ = '\0';
1210
1211   for (;*strp == ' '||*strp == '\t'; strp++) ;
1212
1213   isquoted = 0; 
1214   if (*strp == '"') { 
1215     isquoted = 1;
1216     strp++;
1217   }
1218
1219   *prm2 = strp;
1220   for (; *strp != '\0'; strp++) {
1221     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1222     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1223       strp++;
1224       continue;
1225     }
1226
1227     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1228     ||  (isquoted  && *strp == '"'))
1229       break;
1230   }
1231
1232   if (! *strp) {
1233     *prm3 = strp;
1234     *prm4 = strp;
1235     *prm5 = strp;
1236     return 0;
1237   }
1238
1239   *strp++ = '\0';
1240
1241   for (;*strp == ' '||*strp == '\t'; strp++);
1242
1243   isquoted = 0; 
1244   if (*strp == '"') { 
1245     isquoted = 1;
1246     strp++;
1247   }
1248   *prm3 = strp;
1249   for (; *strp != '\0'; strp++) {
1250     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1251     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1252       strp++;
1253       continue;
1254     }
1255
1256     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1257     ||  (isquoted  && *strp == '"'))
1258       break;
1259   }
1260
1261   if (! *strp) {
1262     *prm4 = strp;
1263     *prm5 = strp;
1264     return 0;
1265   }
1266
1267   *strp++ = '\0';
1268
1269   for (;*strp == ' '||*strp == '\t'; strp++);
1270
1271   isquoted = 0; 
1272   if (*strp == '"') { 
1273     isquoted = 1;
1274     strp++;
1275   }
1276   *prm4 = strp;
1277   for (; *strp != '\0'; strp++) {
1278     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1279     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1280       strp++;
1281       continue;
1282     }
1283
1284     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1285     ||  (isquoted  && *strp == '"'))
1286       break;
1287   }
1288
1289   if (! *strp) {
1290     *prm5 = strp;
1291     return 0;
1292   }
1293
1294   *strp++ = '\0';
1295
1296   for (;*strp == ' '||*strp == '\t'; strp++);
1297
1298   isquoted = 0; 
1299   if (*strp == '"') { 
1300     isquoted = 1;
1301     strp++;
1302   }
1303   *prm5 = strp;
1304   for (; *strp != '\0'; strp++) {
1305     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1306     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1307       strp++;
1308       continue;
1309     }
1310
1311     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1312     ||  (isquoted  && *strp == '"'))
1313       break;
1314   }
1315   *strp = '\0';
1316
1317   return 0;
1318 }
1319
1320
1321 /**
1322  * The device definition file is loaded. 
1323  *
1324  * @param arg     [i]   The name of the device definition file is specified.
1325  * @param mconfig [i/o] The pointer to a set structure is specified. 
1326  * @param parms   [i]   
1327  */
1328 static const char* 
1329 cmd_load_device_data(cmd_parms *parms, void *mconfig, const char* arg) 
1330 {
1331   mod_chxj_config* conf;
1332   Doc              doc;
1333
1334   doc.r = NULL;
1335
1336   if (strlen(arg) > 256) 
1337     return "device data filename too long.";
1338
1339   conf = (mod_chxj_config*)mconfig;
1340   conf->device_data_file = apr_pstrdup(parms->pool, arg);
1341
1342   qs_init_malloc(&doc);
1343   qs_init_root_node(&doc);
1344
1345   qs_parse_file((Doc*)&doc, (const char*)arg);
1346   chxj_load_device_data(&doc,parms->pool, conf);
1347   qs_all_free(&doc, QX_LOGMARK);
1348
1349   return NULL;
1350 }
1351
1352
1353 /**
1354  * Device definition information is loaded. 
1355  *
1356  * @param parms 
1357  * @param arg     [i]   The name of the device definition file is specified. 
1358  * @param mconfig [i/o] The pointer to a set structure is specified. 
1359  * @return 
1360  */
1361 static const char* 
1362 cmd_load_emoji_data(cmd_parms *parms, void *mconfig, const char* arg) 
1363 {
1364   mod_chxj_config* conf;
1365   char*            rtn;
1366   Doc              doc;
1367
1368   doc.r = NULL;
1369
1370
1371   if (strlen(arg) > 256) 
1372     return "emoji data filename too long.";
1373
1374   conf = (mod_chxj_config*)mconfig;
1375   conf->emoji_data_file = apr_pstrdup(parms->pool, arg);
1376   qs_init_malloc(&doc);
1377   qs_init_root_node(&doc);
1378
1379   qs_parse_file((Doc*)&doc, (const char*)arg);
1380
1381   rtn = chxj_load_emoji_data(&doc,parms->pool, conf);
1382
1383   qs_all_free(&doc, QX_LOGMARK);
1384
1385
1386   return rtn;
1387 }
1388
1389
1390 static const char* 
1391 cmd_set_image_engine(cmd_parms *parms, void *mconfig, const char* arg) 
1392 {
1393   mod_chxj_config* conf;
1394   Doc              doc;
1395
1396   doc.r = NULL;
1397
1398   if (strlen(arg) > 256) 
1399     return "image uri is too long.";
1400
1401   conf = (mod_chxj_config*)mconfig;
1402   if (strcasecmp("ON", arg) == 0)
1403     conf->image = CHXJ_IMG_ON;
1404   else
1405     conf->image = CHXJ_IMG_OFF;
1406
1407   return NULL;
1408 }
1409
1410
1411 static const char* 
1412 cmd_set_image_cache_dir(cmd_parms *parms, void *mconfig, const char* arg) 
1413 {
1414   mod_chxj_config* conf;
1415   Doc              doc;
1416
1417   doc.r = NULL;
1418
1419   if (strlen(arg) > 256) 
1420     return "cache dir name is too long.";
1421
1422   conf = (mod_chxj_config*)mconfig;
1423   conf->image_cache_dir = apr_pstrdup(parms->pool, arg);
1424
1425   return NULL;
1426 }
1427
1428
1429 static const char* 
1430 cmd_set_image_copyright(cmd_parms *parms, void* mconfig, const char* arg) 
1431 {
1432   mod_chxj_config* conf;
1433   Doc              doc;
1434
1435   doc.r = NULL;
1436
1437   if (strlen(arg) > 256) 
1438     return "Copyright Flag is too long.";
1439
1440   conf = (mod_chxj_config*)mconfig;
1441   conf->image_copyright = apr_pstrdup(parms->pool, arg);
1442
1443   return NULL;
1444 }
1445
1446
1447 static const char*
1448 cmd_convert_rule(cmd_parms *cmd, void* mconfig, const char *arg)
1449 {
1450   int                 mode;
1451   ap_regex_t*         regexp;
1452   mod_chxj_config*    dconf;
1453   chxjconvrule_entry* newrule;
1454   char*               prm1;
1455   char*               prm2;
1456   char*               prm3;
1457   char*               prm4;
1458   char*               prm5;
1459   char*               pstate;
1460   char*               action;
1461   char*               pp;
1462
1463   dconf = (mod_chxj_config*)mconfig;
1464
1465   if (strlen(arg) > 4096) 
1466     return "ChxjConvertRule: is too long.";
1467
1468   dconf = (mod_chxj_config*)mconfig;
1469   if (dconf->convrules == NULL)
1470     dconf->convrules   = apr_array_make(cmd->pool, 
1471                                         2, 
1472                                         sizeof(chxjconvrule_entry));
1473
1474   newrule = apr_array_push(dconf->convrules);
1475
1476   newrule->flags  = 0;
1477   newrule->action = 0;
1478
1479   if (chxj_command_parse_take5(arg, &prm1, &prm2, &prm3, &prm4, &prm5))
1480     return "ChxjConvertRule: bad argument line";
1481
1482   newrule->pattern = apr_pstrdup(cmd->pool, prm1);
1483
1484   /* Parse action */
1485   for (;;) {
1486     if ((action = apr_strtok(prm2, ",", &pstate)) == NULL)
1487       break;
1488     prm2 = NULL;
1489     switch(*action) {
1490     case 'e':
1491     case 'E':
1492       if (strcasecmp(CONVRULE_ENGINE_ON_CMD, action) == 0) {
1493         newrule->action |= CONVRULE_ENGINE_ON_BIT;
1494       }
1495       else
1496       if (strcasecmp(CONVRULE_ENGINE_OFF_CMD, action) == 0) {
1497         newrule->action |= CONVRULE_ENGINE_OFF_BIT;
1498       }
1499       break;
1500
1501     case 'C':
1502     case 'c':
1503       if (strcasecmp(CONVRULE_COOKIE_ON_CMD, action) == 0) {
1504         newrule->action |= CONVRULE_COOKIE_ON_BIT;
1505       }
1506       break;
1507     default:
1508       break;
1509     }
1510   }
1511   
1512   pp = prm1;
1513   if (*pp == '!') {
1514     newrule->flags |= CONVRULE_FLAG_NOTMATCH;
1515     pp++;
1516   }
1517
1518   mode = AP_REG_EXTENDED;
1519   if ((regexp = ap_pregcomp(cmd->pool, pp, mode)) == NULL)
1520     return "RewriteRule: cannot compile regular expression ";
1521
1522   newrule->regexp = regexp;
1523   if (*prm3)
1524     newrule->encoding = apr_pstrdup(cmd->pool, prm3);
1525   else
1526     newrule->encoding = apr_pstrdup(cmd->pool, "none");
1527
1528   newrule->pc_flag = CONVRULE_PC_FLAG_OFF_BIT;
1529   if (*prm4)
1530     if (strcasecmp(CONVRULE_PC_FLAG_ON_CMD, prm4) == 0)
1531       newrule->pc_flag = CONVRULE_PC_FLAG_ON_BIT;
1532
1533   newrule->user_agent = NULL;
1534   if (*prm5)
1535     newrule->user_agent = apr_pstrdup(cmd->pool, prm5);
1536     
1537   return NULL;
1538 }
1539
1540
1541 static const command_rec cmds[] = {
1542   AP_INIT_TAKE1(
1543     "ChxjLoadDeviceData",
1544     cmd_load_device_data,
1545     NULL,
1546     OR_ALL,
1547     "Load Device Data"),
1548   AP_INIT_TAKE1(
1549     "ChxjLoadEmojiData",
1550     cmd_load_emoji_data,
1551     NULL,
1552     OR_ALL,
1553     "Load Emoji Data"),
1554   AP_INIT_TAKE1(
1555     "ChxjImageEngine",
1556     cmd_set_image_engine,
1557     NULL,
1558     OR_ALL,
1559     "Convert Target URI"),
1560   AP_INIT_TAKE1(
1561     "ChxjImageCacheDir",
1562     cmd_set_image_cache_dir,
1563     NULL,
1564     OR_ALL,
1565     "Image Cache Directory"),
1566   AP_INIT_TAKE1(
1567     "ChxjImageCopyright",
1568     cmd_set_image_copyright,
1569     NULL,
1570     OR_ALL,
1571     "Copyright Flag"),
1572   AP_INIT_RAW_ARGS(
1573     "ChxjConvertRule",
1574     cmd_convert_rule,
1575     NULL, 
1576     OR_FILEINFO,
1577     "an URL-applied regexp-pattern and a substitution URL"),
1578   { NULL },
1579 };
1580
1581
1582 /*----------------------------------------------------------------------------*/
1583 /* Dispatch list for API hooks                                                */
1584 /*----------------------------------------------------------------------------*/
1585 module AP_MODULE_DECLARE_DATA chxj_module = {
1586   STANDARD20_MODULE_STUFF, 
1587   chxj_create_per_dir_config,          /* create per-dir    config structures */
1588   chxj_merge_per_dir_config,           /* merge  per-dir    config structures */
1589   chxj_config_server_create,           /* create per-server config structures */
1590   NULL,                                /* merge  per-server config structures */
1591   cmds,                                /* table of config file commands       */
1592   chxj_register_hooks                  /* register hooks                      */
1593 };
1594 /*
1595  * vim:ts=2 et
1596  */