OSDN Git Service

* Bug Fix.
[modchxj/mod_chxj.git] / src / mod_chxj.c
1 /*
2  * Copyright (C) 2005-2008 Atsushi Konno All rights reserved.
3  * Copyright (C) 2005 QSDN,Inc. 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 #include <limits.h>
20 #include <errno.h>
21
22 #include "httpd.h"
23 #include "http_config.h"
24 #include "http_core.h"
25 #include "http_protocol.h"
26 #include "http_log.h"
27 #include "ap_config.h"
28 #include "apr_strings.h"
29 #include "util_filter.h"
30 #include "apr_buckets.h"
31 #include "apr_lib.h"
32 #include "apr_tables.h"
33 #include "apr_dso.h"
34 #include "apr_general.h"
35 #include "apr_pools.h"
36
37 #include "mod_chxj.h"
38 #include "chxj_encoding.h"
39 #include "qs_ignore_sp.h"
40 #include "qs_log.h"
41 #include "qs_malloc.h"
42 #include "qs_parse_attr.h"
43 #include "qs_parse_file.h"
44 #include "qs_parse_string.h"
45 #include "qs_parse_tag.h"
46 #include "chxj_load_device_data.h"
47 #include "chxj_load_emoji_data.h"
48 #include "chxj_specified_device.h"
49 #include "chxj_tag_util.h"
50 #include "chxj_xhtml_mobile_1_0.h"
51 #include "chxj_hdml.h"
52 #include "chxj_chtml10.h"
53 #include "chxj_chtml20.h"
54 #include "chxj_chtml30.h"
55 #include "chxj_chtml40.h"
56 #include "chxj_chtml50.h"
57 #include "chxj_jhtml.h"
58 #include "chxj_jxhtml.h"
59 #include "chxj_img_conv_format.h"
60 #include "chxj_qr_code.h"
61 #include "chxj_encoding.h"
62 #include "chxj_apply_convrule.h"
63 #include "chxj_cookie.h"
64 #include "chxj_url_encode.h"
65 #include "chxj_str_util.h"
66 #if defined(USE_MYSQL_COOKIE)
67 #  include "chxj_mysql.h"
68 #endif
69 #include "chxj_serf.h"
70
71
72 #define CHXJ_VERSION_PREFIX PACKAGE_NAME "/"
73 #define CHXJ_VERSION        PACKAGE_VERSION
74 #define CHXJ_POST_MAX       (0x1000000)
75
76 converter_t convert_routine[] = {
77   {
78     /* CHXJ_SPEC_UNKNOWN          */
79     .converter = NULL,
80     .encoder  = NULL,
81   },
82   {
83     /* CHXJ_SPEC_Chtml_1_0        */
84     .converter = chxj_convert_chtml10,
85     .encoder  = chxj_encoding,
86   },
87   {
88     /* CHXJ_SPEC_Chtml_2_0        */
89     .converter = chxj_convert_chtml20,
90     .encoder  = chxj_encoding,
91   },
92   {
93     /* CHXJ_SPEC_Chtml_3_0        */
94     .converter = chxj_convert_chtml30,
95     .encoder  = chxj_encoding,
96   },
97   {
98     /* CHXJ_SPEC_Chtml_4_0        */
99     .converter = chxj_convert_chtml40,
100     .encoder  = chxj_encoding,
101   },
102   {
103     /* CHXJ_SPEC_Chtml_5_0        */
104     .converter = chxj_convert_chtml50,
105     .encoder  = chxj_encoding,
106   },
107   {
108     /* CHXJ_SPEC_Chtml_6_0        */
109     .converter = chxj_convert_chtml50,
110     .encoder  = chxj_encoding,
111   },
112   {
113     /* CHXJ_SPEC_Chtml_7_0        */
114     .converter = chxj_convert_chtml50,
115     .encoder  = chxj_encoding,
116   },
117   {
118     /* CHXJ_SPEC_XHtml_Mobile_1_0 */
119     .converter = chxj_convert_xhtml_mobile_1_0,
120     .encoder  = chxj_encoding,
121   },
122   {
123     /* CHXJ_SPEC_Hdml             */
124     .converter = chxj_convert_hdml,
125     .encoder  = chxj_encoding,
126   },
127   {
128     /* CHXJ_SPEC_Jhtml            */
129     .converter = chxj_convert_jhtml,
130     .encoder  = chxj_encoding,
131   },
132   {
133     /* CHXJ_SPEC_Jxhtml            */
134     .converter = chxj_convert_jxhtml,
135     .encoder  = chxj_encoding,
136   },
137   {
138     /* CHXJ_SPEC_HTML             */
139     .converter = NULL,
140     .encoder  = NULL,
141   },
142 };
143
144 static int chxj_convert_input_header(request_rec *r,chxjconvrule_entry *entryp);
145 static void s_add_cookie_id_if_has_location_header(request_rec *r, cookie_t *cookie);
146 static void s_clear_cookie_header(request_rec *r, device_table *spec);
147
148 /**
149  * Only when User-Agent is specified, the User-Agent header is camouflaged. 
150  *
151  * @param r   [i]
152  */
153 static apr_status_t 
154 chxj_headers_fixup(request_rec *r)
155 {
156   mod_chxj_config*    dconf; 
157   chxjconvrule_entry* entryp;
158   char*               user_agent;
159   device_table*       spec;
160   char                *contentType;
161   char                *contentLength;
162
163   DBG(r, "start chxj_headers_fixup()");
164   dconf = chxj_get_module_config(r->per_dir_config, &chxj_module);
165
166   user_agent = (char*)apr_table_get(r->headers_in, HTTP_USER_AGENT);
167   spec = chxj_specified_device(r, user_agent);
168
169   contentType = (char *)apr_table_get(r->headers_in, "Content-Type");
170   if (contentType
171       && strncasecmp("multipart/form-data", contentType, 19) == 0) {
172     DBG(r, "detect multipart/form-data ==> no target");
173     DBG(r, "end chxj_headers_fixup()");
174     return DECLINED;
175   }
176   if (r->method_number == M_POST) {
177     if (!apr_table_get(r->headers_in, "X-Chxj-Forward")) {
178       s_clear_cookie_header(r, spec);
179     }
180   }
181   else {
182     s_clear_cookie_header(r, spec);
183   }
184
185   switch(spec->html_spec_type) {
186   case CHXJ_SPEC_Chtml_1_0:
187   case CHXJ_SPEC_Chtml_2_0:
188   case CHXJ_SPEC_Chtml_3_0:
189   case CHXJ_SPEC_Chtml_4_0:
190   case CHXJ_SPEC_Chtml_5_0:
191   case CHXJ_SPEC_Chtml_6_0:
192   case CHXJ_SPEC_Chtml_7_0:
193   case CHXJ_SPEC_XHtml_Mobile_1_0:
194   case CHXJ_SPEC_Hdml:
195   case CHXJ_SPEC_Jhtml:
196   case CHXJ_SPEC_Jxhtml:
197     entryp = chxj_apply_convrule(r, dconf->convrules);
198     if (! entryp) {
199       DBG(r, "end chxj_headers_fixup() no pattern");
200       return DECLINED;
201     }
202     if (!entryp || !(entryp->action & CONVRULE_ENGINE_ON_BIT)) {
203       DBG(r,"EngineOff");
204       return DECLINED;
205     }
206   
207     apr_table_setn(r->headers_in, 
208                    CHXJ_HTTP_USER_AGENT, 
209                    user_agent);
210   
211     if (entryp->user_agent)
212       apr_table_setn(r->headers_in, 
213                      HTTP_USER_AGENT, 
214                      entryp->user_agent);
215
216     chxj_convert_input_header(r,entryp);
217
218     break;
219   
220   default:
221     return DECLINED;
222
223   }
224
225
226   if (r->method_number == M_POST) {
227     if (! apr_table_get(r->headers_in, "X-Chxj-Forward")) {
228         DBG(r, "set Input handler old:[%s] proxyreq:[%d] uri:[%s] filename:[%s]", r->handler, r->proxyreq, r->uri, r->filename);
229         r->proxyreq = PROXYREQ_NONE;
230         r->handler = apr_psprintf(r->pool, "chxj-input-handler");
231     }
232     else {
233       char *client_ip = (char *)apr_table_get(r->headers_in, CHXJ_HEADER_ORIG_CLIENT_IP);
234       if (client_ip) {
235         apr_sockaddr_t *address = NULL;
236         apr_status_t rv = apr_sockaddr_info_get(&address, ap_get_server_name(r), APR_UNSPEC, ap_get_server_port(r), 0, r->pool);
237         if (rv != APR_SUCCESS) {
238           char buf[256];
239           ERR(r, "%s:%d apr_sockaddr_info_get() failed: rv:[%d|%s]", APLOG_MARK, rv, apr_strerror(rv, buf, 256));
240           DBG(r, "end chxj_headers_fixup()");
241           return DECLINED;
242         }
243         char *addr;
244         if (dconf->forward_server_ip) {
245           addr = dconf->forward_server_ip;
246         }
247         else {
248           apr_sockaddr_ip_get(&addr, address);
249         }
250         DBG(r, "Client IP:[%s] vs Orig Client IP:[%s] vs Server IP:[%s]", r->connection->remote_ip, client_ip, addr);
251         if (strcmp(addr, r->connection->remote_ip) == 0) {
252           r->connection->remote_ip = apr_pstrdup(r->connection->pool, client_ip);
253         }
254         if (! apr_table_get(r->headers_in, "Content-Length")) {
255           contentLength = (char *)apr_table_get(r->headers_in, "X-Chxj-Content-Length");
256           if (contentLength) {
257             apr_table_set(r->headers_in, "Content-Length", contentLength);
258           }
259         }
260       }
261     }
262   }
263
264   DBG(r, "end chxj_headers_fixup()");
265
266   return DECLINED;
267 }
268
269
270 static void
271 s_clear_cookie_header(request_rec *r, device_table *spec)
272 {
273   switch(spec->html_spec_type) {
274   case CHXJ_SPEC_Chtml_1_0:
275   case CHXJ_SPEC_Chtml_2_0:
276   case CHXJ_SPEC_Chtml_3_0:
277   case CHXJ_SPEC_Chtml_4_0:
278   case CHXJ_SPEC_Chtml_5_0:
279   case CHXJ_SPEC_Chtml_6_0:
280   case CHXJ_SPEC_Chtml_7_0:
281   case CHXJ_SPEC_XHtml_Mobile_1_0:
282   case CHXJ_SPEC_Jhtml:
283     apr_table_unset(r->headers_in, "Cookie");
284     break;
285   default:
286     break;
287   }
288 }
289
290
291 /**
292  * It converts it from CHTML into XXML corresponding to each model. 
293  *
294  * @param r   [i]
295  * @param src [i]   It is former HTML character string. 
296  * @param len [i/o] It is length of former HTML character string. 
297  */
298 static char * 
299 chxj_convert(request_rec *r, const char **src, apr_size_t *len, device_table *spec, const char *ua, cookie_t **cookiep)
300 {
301   char                *user_agent;
302   char                *dst;
303   char                *tmp;
304   cookie_t            *cookie;
305   mod_chxj_config     *dconf; 
306   chxjconvrule_entry  *entryp;
307
308   DBG(r,"start of chxj_convert() input:[%.*s]", (int)*len, *src);
309   dst  = apr_pstrcat(r->pool, (char *)*src, NULL);
310
311   dconf = chxj_get_module_config(r->per_dir_config, &chxj_module);
312
313
314   entryp = chxj_apply_convrule(r, dconf->convrules);
315
316   if (!entryp || !(entryp->action & CONVRULE_ENGINE_ON_BIT))
317     return (char *)*src;
318
319
320   /*------------------------------------------------------------------------*/
321   /* get UserAgent from http header                                         */
322   /*------------------------------------------------------------------------*/
323   if (entryp->user_agent)
324     user_agent = (char *)apr_table_get(r->headers_in, CHXJ_HTTP_USER_AGENT);
325   else
326     user_agent = (char *)apr_table_get(r->headers_in, HTTP_USER_AGENT);
327
328   DBG(r,"User-Agent:[%s]", user_agent);
329   DBG(r,"content type is %s", r->content_type);
330
331
332   if (! STRNCASEEQ('t','T', "text/html", r->content_type, sizeof("text/html")-1)
333   &&  ! STRNCASEEQ('a','A', "application/xhtml+xml", r->content_type, sizeof("application/xhtml+xml")-1)) {
334     DBG(r,"no convert. content type is %s", r->content_type);
335     DBG(r,"end of chxj_convert()");
336     return (char *)*src;
337   }
338
339   if (ua && user_agent && strcasecmp(user_agent, ua) != 0) {
340     /* again */
341     spec = chxj_specified_device(r, user_agent);
342   }
343
344   /*
345    * save cookie.
346    */
347   cookie = NULL;
348   if (entryp->action & CONVRULE_COOKIE_ON_BIT) {
349     switch(spec->html_spec_type) {
350     case CHXJ_SPEC_Chtml_1_0:
351     case CHXJ_SPEC_Chtml_2_0:
352     case CHXJ_SPEC_Chtml_3_0:
353     case CHXJ_SPEC_Chtml_4_0:
354     case CHXJ_SPEC_Chtml_5_0:
355     case CHXJ_SPEC_Chtml_6_0:
356     case CHXJ_SPEC_Chtml_7_0:
357     case CHXJ_SPEC_XHtml_Mobile_1_0:
358     case CHXJ_SPEC_Jhtml:
359       cookie = chxj_save_cookie(r);
360       break;
361     default:
362       break;
363     }
364   }
365
366   if (!r->header_only) {
367
368     tmp = NULL;
369     if (convert_routine[spec->html_spec_type].encoder)
370       tmp = convert_routine[spec->html_spec_type].encoder(r, 
371                                                           *src, 
372                                                           (apr_size_t *)len);
373
374     if (convert_routine[spec->html_spec_type].converter) {
375       if (tmp)
376         dst = convert_routine[spec->html_spec_type].converter(r, 
377                                                               spec, 
378                                                               tmp, 
379                                                               *len, 
380                                                               len, 
381                                                               entryp, 
382                                                               cookie);
383       else
384         dst = convert_routine[spec->html_spec_type].converter(r,
385                                                               spec, 
386                                                               tmp, 
387                                                               *len, 
388                                                               len, 
389                                                               entryp, 
390                                                               cookie);
391     }
392   }
393   ap_set_content_length(r, *len);
394
395   if (*len == 0) {
396     dst = apr_psprintf(r->pool, "\n");
397     *len = 1;
398   }
399   dst[*len] = 0;
400   if (cookie) {
401     *cookiep = cookie;
402   }
403
404   DBG(r, "end of chxj_convert()");
405
406   return dst;
407 }
408
409
410 /**
411  * It converts it from HEADER.
412  *
413  * @param r   [i]
414  */
415 static int
416 chxj_convert_input_header(request_rec *r,chxjconvrule_entry *entryp) 
417 {
418
419   char       *buff;
420   char       *buff_pre;
421   apr_size_t urilen;
422   char       *result;
423   char       *pair;
424   char       *name;
425   char       *value;
426   char       *pstate;
427   char       *vstate;
428   cookie_t   *cookie;
429   int        no_update_flag = 0;
430
431   DBG(r, "start chxj_convert_input_header()");
432
433   if (! r->args) {
434     DBG(r, "r->args=[null]");
435     DBG(r, "end   chxj_convert_input_header()");
436     return 0;
437   }
438   urilen = strlen(r->args);
439
440   result = qs_alloc_zero_byte_string(r->pool);
441
442   buff_pre = apr_pstrdup(r->pool, r->args);
443
444   for (;;) {
445     char *pair_sv;
446
447     pair = apr_strtok(buff_pre, "&", &pstate);
448     if (pair == NULL)
449       break;
450
451     buff_pre = NULL;
452
453     pair_sv = apr_pstrdup(r->pool, pair);
454
455     name  = apr_strtok(pair, "=", &vstate);
456     value = apr_strtok(NULL, "=", &vstate);
457     if (strcasecmp(name, CHXJ_COOKIE_NOUPDATE_PARAM) == 0 || strcasecmp(name, chxj_url_encode(r->pool, CHXJ_COOKIE_NOUPDATE_PARAM)) == 0) {
458       DBG(r, "found cookie no update parameter");
459       no_update_flag++;
460     }
461   }
462
463   buff = apr_pstrdup(r->pool, r->args);
464   DBG(r, "r->args=[%s]", buff);
465
466   /* _chxj_dmy */
467   /* _chxj_c_ */
468   /* _chxj_r_ */
469   /* _chxj_s_ */
470   for (;;) {
471     char *pair_sv;
472
473     pair = apr_strtok(buff, "&", &pstate);
474     if (pair == NULL)
475       break;
476
477     buff = NULL;
478
479     pair_sv = apr_pstrdup(r->pool, pair);
480
481     name  = apr_strtok(pair, "=", &vstate);
482     value = apr_strtok(NULL, "=", &vstate);
483     if (strncasecmp(name, "_chxj", 5) != 0 && strncasecmp(name, "%5Fchxj", sizeof("%5Fchxj")-1) != 0) {
484       if (strlen(result) != 0) 
485         result = apr_pstrcat(r->pool, result, "&", NULL);
486
487       if (strcasecmp(entryp->encoding, "NONE") != 0) {
488         apr_size_t dlen;
489         char *dvalue;
490         char *dname;
491
492         if (value && *value != 0) {
493           value = chxj_url_decode(r->pool, value);
494           dlen   = strlen(value);
495           DBG(r, "************ before encoding[%s]", value);
496   
497           dvalue = chxj_rencoding(r, value, &dlen);
498           dvalue = chxj_url_encode(r->pool, dvalue);
499   
500           DBG(r, "************ after encoding[%s]", dvalue);
501         }
502         else {
503           dvalue = "";
504         }
505
506         if (name && *name != 0) {
507           name = chxj_url_decode(r->pool, name);
508           dlen = strlen(name);
509           dname = chxj_rencoding(r, name, &dlen);
510           dname = chxj_url_encode(r->pool, dname);
511         }
512         else {
513           dname = "";
514         }
515
516         result = apr_pstrcat(r->pool, result, dname, "=", dvalue, NULL);
517       }
518       else {
519         if (strcmp(name, pair_sv) != 0)
520           result = apr_pstrcat(r->pool, result, name, "=", value, NULL);
521         else
522           result = apr_pstrcat(r->pool, result, name, NULL);
523       }
524     }
525     else
526     if ( (strncasecmp(name, "_chxj_c_", 8) == 0 || strncasecmp(name, "%5Fchxj%5Fc%5F", sizeof("%5Fchxj%5Fc%5F")-1) == 0)
527       || (strncasecmp(name, "_chxj_r_", 8) == 0 || strncasecmp(name, "%5Fchxj%5Fr%5F", sizeof("%5Fchxj%5Fr%5F")-1) == 0)
528       || (strncasecmp(name, "_chxj_s_", 8) == 0 || strncasecmp(name, "%5Fchxj%5Fs%5F", sizeof("%5Fchxj%5Fs%5F")-1) == 0)) {
529       if (value == NULL)
530         continue;
531
532       if (strlen(value) == 0)
533         continue;
534
535       if (strlen(result) != 0)
536         result = apr_pstrcat(r->pool, result, "&", NULL);
537
538       result = apr_pstrcat(r->pool, result, &name[8], "=", value, NULL);
539     }
540     else
541     if (strcasecmp(name, CHXJ_COOKIE_PARAM) == 0 || strcasecmp(name, "%5Fchxj%5Fcc") == 0) {
542       apr_table_unset(r->headers_in, "Cookie");
543       DBG(r, "found cookie parameter[%s]", value);
544       DBG(r, "call start chxj_load_cookie()");
545       cookie_lock_t *lock = chxj_cookie_lock(r);
546       cookie = chxj_load_cookie(r, value);
547       DBG(r, "call end   chxj_load_cookie()");
548       if (! no_update_flag && cookie) {
549         chxj_update_cookie(r, cookie);
550       }
551       chxj_cookie_unlock(r, lock);
552     }
553   }
554   r->args = result;
555
556   DBG(r, "result r->args=[%s]", r->args);
557   DBG(r, "end   chxj_convert_input_header()");
558   return 0;
559 }
560
561
562 /**
563  * It converts it from POSTDATA .
564  *
565  * @param r   [i]
566  * @param src [i]   It is POSTDATA character string.
567  * @param len [i/o] It is length of former HTML character string.
568  */
569 static char *
570 chxj_input_convert(
571   request_rec         *r, 
572   const char          **src, 
573   apr_size_t          *len, 
574   chxjconvrule_entry  *entryp)
575 {
576   char     *pair;
577   char     *name;
578   char     *value;
579   char     *pstate;
580   char     *vstate;
581   char     *s;
582   char     *result;
583   cookie_t *cookie;
584   char     *buff_pre;
585   int      no_update_flag = 0;
586
587   s        = apr_pstrdup(r->pool, *src);
588   buff_pre = apr_pstrdup(r->pool, *src);
589
590   result = qs_alloc_zero_byte_string(r->pool);
591
592   DBG(r, "BEFORE input convert source = [%s]", s);
593
594   for (;;) {
595     char *pair_sv;
596
597     pair = apr_strtok(buff_pre, "&", &pstate);
598     if (pair == NULL)
599       break;
600
601     buff_pre = NULL;
602
603     pair_sv = apr_pstrdup(r->pool, pair);
604
605     name  = apr_strtok(pair, "=", &vstate);
606     value = apr_strtok(NULL, "=", &vstate);
607     if (strcasecmp(name, CHXJ_COOKIE_NOUPDATE_PARAM) == 0 || strcasecmp(name, chxj_url_encode(r->pool, CHXJ_COOKIE_NOUPDATE_PARAM)) == 0) {
608       DBG(r, "found cookie no update parameter");
609       no_update_flag++;
610     }
611   }
612
613   /* _chxj_dmy */
614   /* _chxj_c_ */
615   /* _chxj_r_ */
616   /* _chxj_s_ */
617   for (;;) {
618     pair = apr_strtok(s, "&", &pstate);
619     if (pair == NULL)
620       break;
621     s = NULL;
622
623     name  = apr_strtok(pair, "=", &vstate);
624     value = apr_strtok(NULL, "=", &vstate);
625     if (strncasecmp(name, "_chxj", 5) != 0 && strncasecmp(name, "%5Fchxj", sizeof("%5Fchxj")-1) != 0) {
626       if (strlen(result) != 0) 
627         result = apr_pstrcat(r->pool, result, "&", NULL);
628
629       if (strcasecmp(entryp->encoding, "NONE") != 0) {
630         apr_size_t dlen;
631         char *dvalue;
632         char *dname;
633
634         if (value && *value != 0) {
635           value = chxj_url_decode(r->pool, value);
636           dlen   = strlen(value);
637           DBG(r, "************ before encoding[%s]", value);
638
639           dvalue = chxj_rencoding(r, value, &dlen);
640           dvalue = chxj_url_encode(r->pool, dvalue);
641
642           DBG(r, "************ after encoding[%s]", dvalue);
643         }
644         else {
645           dvalue = "";
646         }
647
648         if (name && *name != 0) {
649           name = chxj_url_decode(r->pool, name);
650           dlen = strlen(name);
651           dname = chxj_rencoding(r, name, &dlen);
652           dname = chxj_url_encode(r->pool, dname);
653         }
654         else {
655           dname = "";
656         }
657
658         result = apr_pstrcat(r->pool, result, dname, "=", dvalue, NULL);
659       }
660       else {
661         result = apr_pstrcat(r->pool, result, name, "=", value, NULL);
662       }
663     }
664     else
665     if ( (strncasecmp(name, "_chxj_c_", 8) == 0 || strncasecmp(name, "%5Fchxj%5Fc%5F", sizeof("%5Fchxj%5Fc%5F")-1) == 0)
666       || (strncasecmp(name, "_chxj_r_", 8) == 0 || strncasecmp(name, "%5Fchxj%5Fr%5F", sizeof("%5Fchxj%5Fr%5F")-1) == 0)
667       || (strncasecmp(name, "_chxj_s_", 8) == 0 || strncasecmp(name, "%5Fchxj%5Fs%5F", sizeof("%5Fchxj%5Fs%5F")-1) == 0)) {
668       if (value == NULL)
669         continue;
670
671       if (strlen(value) == 0)
672         continue;
673
674       if (strlen(result) != 0)
675         result = apr_pstrcat(r->pool, result, "&", NULL);
676
677       if (strcasecmp(entryp->encoding, "NONE") != 0 && value && strlen(value)) {
678         apr_size_t dlen;
679         char       *dvalue;
680
681         dlen   = strlen(value);
682         value = chxj_url_decode(r->pool, value);
683         DBG(r, "************ before encoding[%s]", value);
684
685         dvalue = chxj_rencoding(r, value, &dlen);
686         dvalue = chxj_url_encode(r->pool,dvalue);
687
688         DBG(r, "************ after encoding[%s]", dvalue);
689
690         result = apr_pstrcat(r->pool, result, &name[8], "=", dvalue, NULL);
691
692       }
693       else {
694         result = apr_pstrcat(r->pool, result, &name[8], "=", value, NULL);
695       }
696     }
697     else
698     if (strcasecmp(name, CHXJ_COOKIE_PARAM) == 0 || strcasecmp(name, "%5Fchxj%5Fcc") == 0) {
699       apr_table_unset(r->headers_in, "Cookie");
700       DBG(r, "found cookie parameter[%s]", value);
701       DBG(r, "call start chxj_load_cookie()");
702       cookie = chxj_load_cookie(r, value);
703       DBG(r, "call end   chxj_load_cookie()");
704       if (! no_update_flag && cookie) {
705         chxj_update_cookie(r, cookie);
706       }
707     }
708   }
709   *len = strlen(result);
710
711   DBG(r, "AFTER input convert result = [%s]", result);
712
713   return result;
714 }
715
716
717 /**
718  * The received data is returned to the filter.
719  *
720  * @param f    [i/o] It is a filter. 
721  * @param data [i]   It is data returned to the filter. 
722  * @param len  [i]   It is length of the data returned to the filter. 
723  */
724 static apr_status_t 
725 pass_data_to_filter(ap_filter_t *f, const char *data, 
726                                         apr_size_t len)
727 {
728   request_rec         *r = f->r;
729   conn_rec            *c = r->connection;
730   apr_status_t        rv;
731   apr_bucket_brigade  *bb;
732   apr_bucket          *b;
733
734   DBG(r, "start pass_data_to_filter()");
735
736   bb = apr_brigade_create(r->pool, c->bucket_alloc);
737   b  = apr_bucket_transient_create(data, len, c->bucket_alloc);
738
739   APR_BRIGADE_INSERT_TAIL(bb, b);
740   b = apr_bucket_eos_create(f->c->bucket_alloc);
741   APR_BRIGADE_INSERT_TAIL(bb, b);
742
743   rv = ap_pass_brigade(f->next, bb);
744   if (rv != APR_SUCCESS) {
745     DBG(r, "ap_pass_brigade()");
746     return rv;
747   }
748
749   DBG(r, "end pass_data_to_filter()");
750
751   return rv;
752 }
753
754
755 /**
756  * It is the main loop of the output filter. 
757  *
758  * @param f   [i/o] It is a filter.
759  * @param bb  [i]   
760  */
761 static apr_status_t 
762 chxj_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
763 {
764   request_rec         *r;
765   apr_status_t        rv;
766   apr_bucket          *b;
767   const char          *data;
768   char                *user_agent = NULL;
769   apr_size_t          len;
770   mod_chxj_ctx        *ctx = (mod_chxj_ctx *)f->ctx;
771   cookie_t            *cookie = NULL;
772   mod_chxj_config     *dconf;
773   chxjconvrule_entry  *entryp = NULL;
774   device_table        *spec = NULL;
775   apr_pool_t          *pool;
776
777   DBG(f->r, "start of chxj_output_filter()");
778   r  = f->r;
779   rv = APR_SUCCESS;
780
781   apr_pool_create(&pool, r->pool);
782
783   entryp = ctx->entryp;
784   spec   = ctx->spec;
785   dconf  = chxj_get_module_config(r->per_dir_config, &chxj_module);
786
787   if (r->content_type) {
788     if (! STRNCASEEQ('t','T',"text/html",r->content_type, sizeof("text/html")-1)
789     &&  ! STRNCASEEQ('t','T',"text/xml", r->content_type, sizeof("text/xml")-1)
790     &&  ! STRNCASEEQ('a','A',"application/xhtml+xml", r->content_type, sizeof("application/xhtml+xml")-1)
791     &&  ! (dconf->image == CHXJ_IMG_ON
792           && ! apr_table_get(r->headers_in, "CHXJ_IMG_CONV")
793           && STRNCASEEQ('i','I',"image/",  r->content_type, sizeof("image/") -1)
794           && ( STRCASEEQ('j','J',"jpeg",            &r->content_type[6])         /* JPEG */
795             || STRCASEEQ('j','J',"jp2",             &r->content_type[6])         /* JPEG2000 */
796             || STRCASEEQ('j','J',"jpeg2000",        &r->content_type[6])         /* JPEG2000 */
797             || STRCASEEQ('j','J',"jpeg2000-image",  &r->content_type[6])         /* JPEG2000 */
798             || STRCASEEQ('x','X',"x-jpeg2000-image",&r->content_type[6])         /* JPEG2000 */
799             || STRCASEEQ('p','P',"png",             &r->content_type[6])         /* PNG */
800             || STRCASEEQ('x','X',"x-png",           &r->content_type[6])         /* PNG */
801             || STRCASEEQ('g','G',"gif",             &r->content_type[6])))) {     /* GIF */
802       
803       DBG(r, "not convert content-type:[%s] dconf->image:[%d]", r->content_type, dconf->image);
804       if (entryp->action & CONVRULE_COOKIE_ON_BIT) {
805         cookie_lock_t *lock = NULL;
806         DBG(r, "entryp->action == COOKIE_ON_BIT");
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_Chtml_6_0:
814         case CHXJ_SPEC_Chtml_7_0:
815         case CHXJ_SPEC_XHtml_Mobile_1_0:
816         case CHXJ_SPEC_Jhtml:
817           lock = chxj_cookie_lock(r);
818           cookie = chxj_save_cookie(r);
819           s_add_cookie_id_if_has_location_header(r, cookie);
820           chxj_cookie_unlock(r, lock);
821           break;
822         default:
823           break;
824         }
825       }
826       if (apr_table_get(r->headers_out, "Location") || apr_table_get(r->err_headers_out, "Location")) {
827         if (r->status < HTTP_MULTIPLE_CHOICES || r->status > HTTP_TEMPORARY_REDIRECT) {
828           r->status = HTTP_MOVED_TEMPORARILY;
829         }
830       }
831       ap_pass_brigade(f->next, bb);
832       return APR_SUCCESS;
833     }
834   }
835   else {
836     DBG(r, "not convert content-type:[(null)]");
837     ap_pass_brigade(f->next, bb);
838     return APR_SUCCESS;
839   }
840
841
842   for (b = APR_BRIGADE_FIRST(bb);
843        b != APR_BRIGADE_SENTINEL(bb); 
844        b = APR_BUCKET_NEXT(b)) {
845
846     if (apr_bucket_read(b, &data, &len, APR_BLOCK_READ) == APR_SUCCESS) {
847       DBG(r, "read data[%.*s]",(int)len, data);
848
849       /*--------------------------------------------------------------------*/
850       /* append data                                                        */
851       /*--------------------------------------------------------------------*/
852       char *tmp;
853       DBG(r, "append data start");
854       ctx = (mod_chxj_ctx *)f->ctx;
855
856       if (len > 0) {
857         tmp = apr_palloc(r->pool, ctx->len);
858         memcpy(tmp, ctx->buffer, ctx->len);
859
860         ctx->buffer = apr_palloc(pool, ctx->len + len);
861
862         memcpy(ctx->buffer, tmp, ctx->len);
863         memcpy(&ctx->buffer[ctx->len], data, len);
864
865         ctx->len += len;
866       }
867       DBG(r, "append data end");
868     }
869
870     if (APR_BUCKET_IS_EOS(b)) {
871
872       DBG(r, "eos");
873       /*----------------------------------------------------------------------*/
874       /* End Of File                                                          */
875       /*----------------------------------------------------------------------*/
876       if (ctx) {
877         cookie_lock_t *lock = NULL;
878         ctx = (mod_chxj_ctx *)f->ctx;
879
880         DBG(r, "content_type=[%s]", r->content_type);
881         lock = chxj_cookie_lock(r);
882
883         if (spec->html_spec_type != CHXJ_SPEC_UNKNOWN 
884             && r->content_type 
885             && (STRNCASEEQ('a','A',"application/xhtml+xml", r->content_type, sizeof("application/xhtml+xml")-1)
886             ||  STRNCASEEQ('t','T',"text/html", r->content_type, sizeof("text/html")-1))) {
887           DBG(r, "detect convert target:[%s]", r->content_type);
888
889           if (ctx->len) {
890             char *tmp;
891
892             tmp = apr_palloc(pool, ctx->len + 1);
893
894             memset(tmp, 0, ctx->len + 1);
895             memcpy(tmp, ctx->buffer, ctx->len);
896
897             ctx->buffer = chxj_convert(r, 
898                                        (const char **)&tmp, 
899                                        (apr_size_t *)&ctx->len,
900                                        spec,
901                                        user_agent, &cookie);
902
903           }
904           else {
905             ctx->buffer = apr_psprintf(r->pool, "\n");
906             ctx->len += 1;
907             ctx->buffer = chxj_convert(r, 
908                                        (const char **)&ctx->buffer, 
909                                        (apr_size_t *)&ctx->len,
910                                        spec,
911                                        user_agent, &cookie);
912
913           }
914         }
915         if (r->content_type
916             && *(char *)r->content_type == 't'
917             && strncmp(r->content_type, "text/xml",   8) == 0) {
918           DBG(r, "text/XML");
919
920           Doc       doc;
921           Node      *root;
922           Node      *child;
923           qr_code_t qrcode;
924           int       sts;
925       
926           memset(&doc,    0, sizeof(Doc));
927           memset(&qrcode, 0, sizeof(qr_code_t));
928           doc.r = r;
929           doc.parse_mode  = PARSE_MODE_CHTML;
930           qrcode.doc      = &doc;
931           qrcode.r        = r;
932       
933           qs_init_malloc(&doc);
934       
935           root = qs_parse_string(&doc, ctx->buffer, ctx->len);
936
937           sts = 0;
938           for (child = qs_get_child_node(&doc,root);
939                child ;
940                child = qs_get_next_node(&doc,child)) {
941             char *name = qs_get_node_name(&doc,child);
942             if (strcasecmp("qrcode",name) == 0) {
943               sts++;
944               break;
945             }
946           }
947           qs_all_free(&doc,QX_LOGMARK);
948           if (sts) {
949             r->handler = apr_psprintf(r->pool, "chxj-qrcode");
950             chxj_qrcode_node_to_qrcode(&qrcode, root);
951             sts = chxj_qrcode_create_image_data(&qrcode, &ctx->buffer, &ctx->len);
952             if (sts != OK) {
953               ERR(r, "qrcode create failed.");
954               chxj_cookie_unlock(r, lock);
955               return sts;
956             }
957             r->content_type = apr_psprintf(r->pool, "image/jpeg");
958           }
959         }
960
961         if (spec->html_spec_type != CHXJ_SPEC_UNKNOWN 
962             && r->content_type 
963             && ( *r->content_type == 'i' || *r->content_type == 'I')
964             && dconf->image == CHXJ_IMG_ON
965             && strncasecmp("image/", r->content_type, 6) == 0
966             && ( STRCASEEQ('j','J',"jpeg",            &r->content_type[6])         /* JPEG */
967               || STRCASEEQ('j','J',"jp2",             &r->content_type[6])         /* JPEG2000 */
968               || STRCASEEQ('j','J',"jpeg2000",        &r->content_type[6])         /* JPEG2000 */
969               || STRCASEEQ('j','J',"jpeg2000-image",  &r->content_type[6])         /* JPEG2000 */
970               || STRCASEEQ('x','X',"x-jpeg2000-image",&r->content_type[6])         /* JPEG2000 */
971               || STRCASEEQ('p','P',"png",             &r->content_type[6])         /* PNG */
972               || STRCASEEQ('x','X',"x-png",           &r->content_type[6])         /* PNG */
973               || STRCASEEQ('g','G',"gif",             &r->content_type[6]))) {     /* GIF */
974           if (ctx->len) {
975             char *tmp;
976
977             tmp = apr_palloc(pool, ctx->len + 1);
978
979             memset(tmp, 0, ctx->len + 1);
980             memcpy(tmp, ctx->buffer, ctx->len);
981             ctx->buffer = 
982               chxj_convert_image(r, 
983                                   (const char **)&tmp,
984                                   (apr_size_t *)&ctx->len);
985             if (ctx->buffer == NULL) {
986               ctx->buffer = tmp;
987             }
988           }
989         }
990
991         apr_table_unset(r->headers_out, "Content-Length");
992         apr_table_unset(r->err_headers_out, "Content-Length");
993         ap_set_content_length(r, (apr_off_t)ctx->len);
994
995         if (apr_table_get(r->headers_out, "Location") || apr_table_get(r->err_headers_out, "Location")) {
996           if (r->status < HTTP_MULTIPLE_CHOICES || r->status > HTTP_TEMPORARY_REDIRECT) {
997             r->status = HTTP_MOVED_TEMPORARILY;
998           }
999         }
1000         
1001         if (ctx->len > 0) {
1002           DBG(r, "call pass_data_to_filter()");
1003           s_add_cookie_id_if_has_location_header(r, cookie);
1004           chxj_cookie_unlock(r,lock);
1005           rv = pass_data_to_filter(f, 
1006                                    (const char *)ctx->buffer, 
1007                                    (apr_size_t)ctx->len);
1008         }
1009         else {
1010           chxj_cookie_unlock(r, lock);
1011
1012         }
1013         return rv;
1014       }
1015       else {
1016         DBG(r, " SAVE COOKIE[%x]", entryp->action);
1017
1018         /*
1019          * save cookie.
1020          */
1021         if (entryp->action & CONVRULE_COOKIE_ON_BIT) {
1022           cookie_lock_t *lock = NULL;
1023           DBG(r, "entryp->action == COOKIE_ON_BIT");
1024           switch(spec->html_spec_type) {
1025           case CHXJ_SPEC_Chtml_1_0:
1026           case CHXJ_SPEC_Chtml_2_0:
1027           case CHXJ_SPEC_Chtml_3_0:
1028           case CHXJ_SPEC_Chtml_4_0:
1029           case CHXJ_SPEC_Chtml_5_0:
1030           case CHXJ_SPEC_Chtml_6_0:
1031           case CHXJ_SPEC_Chtml_7_0:
1032           case CHXJ_SPEC_XHtml_Mobile_1_0:
1033           case CHXJ_SPEC_Jhtml:
1034             lock = chxj_cookie_lock(r);
1035             cookie = chxj_save_cookie(r);
1036             /*
1037              * Location Header Check to add cookie parameter.
1038              */
1039             s_add_cookie_id_if_has_location_header(r, cookie);
1040             chxj_cookie_unlock(r, lock);
1041             apr_table_unset(r->headers_out, "Set-Cookie");
1042             apr_table_unset(r->err_headers_out, "Set-Cookie");
1043             break;
1044
1045           default:
1046             break;
1047           }
1048         }
1049         if (apr_table_get(r->headers_out, "Location") || apr_table_get(r->err_headers_out, "Location")) {
1050           if (r->status < HTTP_MULTIPLE_CHOICES || r->status > HTTP_TEMPORARY_REDIRECT) {
1051             r->status = HTTP_MOVED_TEMPORARILY;
1052           }
1053         }
1054         apr_table_setn(r->headers_out, "Content-Length", "0");
1055         DBG(r, "call pass_data_to_filter()");
1056         rv = pass_data_to_filter(f, (const char *)"", (apr_size_t)0);
1057         return rv;
1058       }
1059     }
1060   }
1061   apr_brigade_destroy(bb);
1062
1063   DBG(r, "end of output filter");
1064
1065   return APR_SUCCESS;
1066 }
1067
1068 /**
1069  * Add Cookie_id if it has location header.
1070  */
1071 static void
1072 s_add_cookie_id_if_has_location_header(request_rec *r, cookie_t *cookie)
1073 {
1074   char *location_header = (char *)apr_table_get(r->headers_out, "Location");
1075   if (! location_header) {
1076     location_header = (char *)apr_table_get(r->err_headers_out, "Location");
1077   }
1078   if (cookie && location_header) {
1079     DBG(r, "Location Header=[%s]", location_header);
1080     location_header = chxj_add_cookie_parameter(r,
1081                                                 location_header,
1082                                                 cookie);
1083     apr_table_unset(r->headers_out, "Location");
1084     apr_table_setn(r->headers_out, "Location", location_header);
1085     DBG(r, "Location Header=[%s]", location_header);
1086     if (r->status < HTTP_MULTIPLE_CHOICES || r->status > HTTP_TEMPORARY_REDIRECT) {
1087       r->status = HTTP_MOVED_TEMPORARILY;
1088     }
1089   }
1090 }
1091
1092 /**
1093  * It is the main loop of the input filter handler. 
1094  *
1095  */
1096 static int 
1097 chxj_input_handler(request_rec *r)
1098 {
1099   mod_chxj_config     *dconf;
1100   chxjconvrule_entry  *entryp = NULL;
1101   device_table        *spec   = NULL;
1102   char                *post_data = NULL;
1103   apr_size_t          post_data_len = 0;
1104   char                *response;
1105   char                *user_agent;
1106   apr_pool_t          *pool;
1107   
1108   DBG(r, "start of chxj_input_handler()");
1109
1110   if (strcasecmp(r->handler, "chxj-input-handler")) {
1111     DBG(r, "end chxj_input_handler()");
1112     return DECLINED;
1113   }
1114   apr_pool_create(&pool, r->pool);
1115
1116   dconf      = chxj_get_module_config(r->per_dir_config, &chxj_module);
1117   user_agent = (char*)apr_table_get(r->headers_in, "User-Agent");
1118   spec       = chxj_specified_device(r, user_agent);
1119   entryp     = chxj_apply_convrule(r, dconf->convrules);
1120
1121   post_data = apr_pstrdup(pool, "");
1122   if (ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK) == OK) {
1123     if (ap_should_client_block(r)) {
1124       while (post_data_len < CHXJ_POST_MAX) {
1125 #define BUFSZ (256)
1126         char buffer[BUFSZ];
1127         int read_bytes = ap_get_client_block(r, buffer, BUFSZ-1);
1128         if (read_bytes<=0) {
1129           break;
1130         }
1131         buffer[read_bytes] = '\0';
1132         post_data = apr_pstrcat(pool, post_data, buffer, NULL);
1133         post_data_len += read_bytes;
1134 #undef BUFSZ
1135       }
1136     }
1137   }
1138
1139   /* 
1140    * now convert.
1141    */
1142   if (post_data_len > 0) {
1143     post_data = chxj_input_convert(r, (const char**)&post_data, (apr_size_t*)&post_data_len, entryp);
1144     DBG(r, "(in:exchange)POSTDATA:[%s]", post_data);
1145   }
1146
1147   char *url_path;
1148   if (dconf->forward_url_base) {
1149     url_path = apr_psprintf(pool, "%s%s", dconf->forward_url_base, r->uri);
1150   }
1151   else {
1152     url_path = apr_psprintf(pool, "%s://%s:%d%s", chxj_apache_run_http_scheme(r), ap_get_server_name(r), ap_get_server_port(r), r->uri);
1153   }
1154   if (r->args) {
1155     url_path = apr_pstrcat(pool, url_path, "?", r->args, NULL);
1156   }
1157   DBG(r, "==> new url_path:[%s]", url_path);
1158
1159   apr_size_t res_len;
1160   apr_table_setn(r->headers_in, CHXJ_HEADER_ORIG_CLIENT_IP, r->connection->remote_ip);
1161   apr_table_unset(r->headers_in, "Content-Length");
1162   apr_table_setn(r->headers_in, "Content-Length", apr_psprintf(pool, "%" APR_SIZE_T_FMT, post_data_len));
1163   response = chxj_serf_post(r, pool, url_path, post_data, post_data_len, 1, &res_len);
1164   DBG(r, "REQ[%X] response:[%.*s][%" APR_SIZE_T_FMT  "]", (unsigned int)(apr_size_t)r, (unsigned int)res_len, response, res_len);
1165
1166   char *chunked;
1167   if ((chunked = (char *)apr_table_get(r->headers_out, "Transfer-Encoding")) != NULL) {
1168     if (strcasecmp(chunked, "chunked") == 0) {
1169       r->chunked = 1;  
1170       apr_table_unset(r->headers_out, "Transfer-Encoding");
1171     }
1172   }
1173   {
1174     apr_pool_t *wpool;
1175     apr_pool_create(&wpool, r->pool);
1176     apr_bucket_brigade *bb;
1177     apr_bucket *e;
1178     apr_status_t rv;
1179     conn_rec *c = r->connection;
1180     
1181     bb = apr_brigade_create(wpool, c->bucket_alloc);
1182     e = apr_bucket_heap_create(response, res_len, NULL, c->bucket_alloc);
1183     APR_BRIGADE_INSERT_TAIL(bb, e);
1184     e = apr_bucket_eos_create(c->bucket_alloc);
1185     APR_BRIGADE_INSERT_TAIL(bb, e);
1186     if ((rv = ap_pass_brigade(r->output_filters, bb)) != APR_SUCCESS) {
1187       ERR(r, "%s:%d failed ap_pass_brigade()", APLOG_MARK);
1188       return rv;
1189     }
1190     apr_brigade_cleanup(bb);
1191   }  
1192
1193   DBG(r, "end of chxj_input_handler()");
1194   return APR_SUCCESS;
1195 }
1196
1197 static mod_chxj_global_config *
1198 chxj_global_config_create(apr_pool_t *pool, server_rec *s)
1199 {
1200   mod_chxj_global_config *conf;
1201
1202   SDBG(s, "start chxj_global_config_create()");
1203
1204   /*--------------------------------------------------------------------------*/
1205   /* allocate an own subpool which survives server restarts                   */
1206   /*--------------------------------------------------------------------------*/
1207   conf = (mod_chxj_global_config *)apr_palloc(pool, 
1208                   sizeof(mod_chxj_global_config));
1209 #if 0
1210   conf->cookie_db_lock = NULL;
1211 #endif
1212   SDBG(s, "end   chxj_global_config_create()");
1213
1214   return conf;
1215 }
1216
1217
1218 /**
1219  * initialize chxj module
1220  */
1221 static int 
1222 chxj_init_module(apr_pool_t *p, 
1223                  apr_pool_t *UNUSED(plog), 
1224                  apr_pool_t *UNUSED(ptemp), 
1225                  server_rec *s)
1226 {
1227   void *user_data;
1228   apr_status_t rv;
1229
1230   SDBG(s, "start chxj_init_module()");
1231
1232   apr_pool_userdata_get(&user_data, CHXJ_MOD_CONFIG_KEY, s->process->pool);
1233   SDBG(s, " ");
1234   if (user_data == NULL) {
1235     SDBG(s, " ");
1236     /*
1237      * dummy user_data set.
1238      */
1239     apr_pool_userdata_set(
1240       (const void *)(1), 
1241       CHXJ_MOD_CONFIG_KEY, 
1242       apr_pool_cleanup_null, 
1243       s->process->pool);
1244     SDBG(s, "end  chxj_init_module()");
1245     return OK;
1246   }
1247
1248   ap_add_version_component(p, CHXJ_VERSION_PREFIX CHXJ_VERSION);
1249
1250   if ((rv = apr_proc_mutex_create(&global_cookie_mutex, NULL,  APR_LOCK_FCNTL, s->process->pool)) != APR_SUCCESS) {
1251     char errstr[255];
1252     SERR(s, "%s:%d end chxj_init_module(). mutex create failure.(%d:%s)",APLOG_MARK, rv,apr_strerror(rv,errstr,255));
1253     return HTTP_INTERNAL_SERVER_ERROR;
1254   }
1255
1256   SDBG(s, "end  chxj_init_module()");
1257
1258   return OK;
1259 }
1260
1261
1262 static void 
1263 chxj_child_init(apr_pool_t *UNUSED(p), server_rec *s)
1264 {
1265   apr_status_t rv;
1266   SDBG(s, "start chxj_child_init()");
1267   if ((rv = apr_proc_mutex_child_init(&global_cookie_mutex, NULL, s->process->pool)) != APR_SUCCESS) {
1268     char errstr[255];
1269     SERR(s, "%s:%d ERROR end chxj_init_module(). mutex create failure.(%d:%s)", APLOG_MARK, rv,apr_strerror(rv,errstr,255));
1270   }
1271   SDBG(s, "end   chxj_child_init()");
1272 }
1273
1274
1275 /**
1276  * A set structure of each server is generated. 
1277  * 
1278  * @param p
1279  * @param s
1280  */
1281 static void *
1282 chxj_config_server_create(apr_pool_t *p, server_rec *s)
1283 {
1284   mod_chxj_global_config *gc;
1285
1286   gc = chxj_global_config_create(p,s);
1287
1288   return gc;
1289 }
1290
1291
1292 static int
1293 chxj_translate_name(request_rec *r)
1294 {
1295   return chxj_trans_name(r);
1296 }
1297
1298
1299 static void 
1300 chxj_insert_filter(request_rec *r)
1301 {
1302   char                *user_agent;
1303   device_table        *spec;
1304   mod_chxj_config     *dconf;
1305   chxjconvrule_entry  *entryp;
1306   mod_chxj_ctx        *ctx;
1307   apr_status_t        rv;
1308   char                *contentType;
1309
1310   DBG(r, "start chxj_insert_filter()");
1311
1312   dconf = chxj_get_module_config(r->per_dir_config, &chxj_module);
1313
1314   user_agent = (char*)apr_table_get(r->headers_in, HTTP_USER_AGENT);
1315
1316   contentType = (char *)apr_table_get(r->headers_in, "Content-Type");
1317   if (contentType
1318       && strncasecmp("multipart/form-data", contentType, 19) == 0) {
1319     DBG(r, "detect multipart/form-data ==> no target");
1320     return;
1321   }
1322
1323   spec = chxj_specified_device(r, user_agent);
1324   entryp = chxj_apply_convrule(r, dconf->convrules);
1325   if (!entryp) {
1326     DBG(r, "end chxj_insert_filter()");
1327     return;
1328   }
1329   ctx = apr_palloc(r->pool, sizeof(*ctx));
1330   memset(ctx, 0, sizeof(*ctx));
1331   if ((rv = apr_pool_create(&ctx->pool, r->pool)) != APR_SUCCESS) {
1332     ERR(r, "failed: new pool create. rv:[%d]", rv);
1333     return;
1334   }
1335   ctx->entryp = entryp;
1336   ctx->spec   = spec;
1337   ctx->buffer = apr_palloc(ctx->pool, 1);
1338   ctx->buffer[0] = 0;
1339
1340   if (!entryp || !(entryp->action & CONVRULE_ENGINE_ON_BIT)) {
1341     DBG(r,"EngineOff");
1342     return;
1343   }
1344
1345   switch(spec->html_spec_type) {
1346   case CHXJ_SPEC_Chtml_1_0:
1347   case CHXJ_SPEC_Chtml_2_0:
1348   case CHXJ_SPEC_Chtml_3_0:
1349   case CHXJ_SPEC_Chtml_4_0:
1350   case CHXJ_SPEC_Chtml_5_0:
1351   case CHXJ_SPEC_Chtml_6_0:
1352   case CHXJ_SPEC_Chtml_7_0:
1353   case CHXJ_SPEC_XHtml_Mobile_1_0:
1354   case CHXJ_SPEC_Hdml:
1355   case CHXJ_SPEC_Jhtml:
1356   case CHXJ_SPEC_Jxhtml:
1357     break;
1358
1359   default:
1360     return;
1361   }
1362
1363
1364   if (! apr_table_get(r->headers_in, "X-Chxj-Forward")) {
1365     ap_add_output_filter("chxj_output_filter", ctx, r, r->connection);
1366     DBG(r, "added Output Filter");
1367   }
1368
1369   DBG(r, "end   chxj_insert_filter()");
1370 }
1371
1372
1373 /**
1374  * The hook is registered.
1375  *
1376  * @param p
1377  */
1378 static void 
1379 chxj_register_hooks(apr_pool_t *UNUSED(p))
1380 {
1381   ap_hook_post_config(chxj_init_module,
1382                       NULL,
1383                       NULL,
1384                       APR_HOOK_REALLY_FIRST);
1385   ap_hook_child_init(chxj_child_init, 
1386                      NULL, 
1387                      NULL, 
1388                      APR_HOOK_REALLY_FIRST);
1389   ap_register_output_filter (
1390                       "chxj_output_filter", 
1391                       chxj_output_filter, 
1392                       NULL, 
1393                       AP_FTYPE_RESOURCE);
1394   ap_hook_insert_filter(chxj_insert_filter, NULL, NULL, APR_HOOK_MIDDLE);
1395   ap_hook_handler(chxj_img_conv_format_handler, NULL, NULL, APR_HOOK_MIDDLE);
1396   ap_hook_handler(chxj_qr_code_handler, NULL, NULL, APR_HOOK_MIDDLE);
1397   ap_hook_handler(chxj_input_handler, NULL, NULL, APR_HOOK_MIDDLE);
1398   ap_hook_translate_name(chxj_translate_name, NULL, NULL, APR_HOOK_MIDDLE);
1399   ap_hook_fixups(chxj_headers_fixup, NULL, NULL, APR_HOOK_FIRST);
1400 }
1401
1402
1403 /**
1404  * A set structure according to the directory is generated. 
1405  *
1406  * @param p
1407  * @param arg
1408  */
1409 static void * 
1410 chxj_create_per_dir_config(apr_pool_t *p, char *arg) 
1411 {
1412   mod_chxj_config *conf;
1413
1414   conf = apr_pcalloc(p, sizeof(mod_chxj_config));
1415   conf->device_data_file = NULL;
1416   conf->devices          = NULL;
1417   conf->emoji_data_file  = NULL;
1418   conf->emoji            = NULL;
1419   conf->emoji_tail       = NULL;
1420   conf->image            = CHXJ_IMG_NONE;
1421   conf->image_cache_dir  = apr_psprintf(p, "%s",DEFAULT_IMAGE_CACHE_DIR);
1422   conf->image_cache_limit = 0;
1423   conf->server_side_encoding = NULL;
1424   conf->cookie_db_dir    = NULL;
1425   conf->cookie_timeout   = 0;
1426   conf->cookie_store_type = COOKIE_STORE_TYPE_NONE;
1427   conf->cookie_lazy_mode  = 0;
1428 #if defined(USE_MYSQL_COOKIE)
1429   memset((void *)&conf->mysql, 0, sizeof(mysql_t));
1430   conf->mysql.port       = MYSQL_PORT;
1431   conf->mysql.host       = NULL;
1432 #endif
1433 #if defined(USE_MEMCACHE_COOKIE)
1434   memset((void *)&conf->memcache, 0, sizeof(memcache_t));
1435   conf->memcache.host    = NULL;
1436   conf->memcache.port    = 0;
1437 #endif
1438   conf->forward_url_base = NULL;
1439   conf->forward_server_ip = NULL;
1440
1441   if (arg == NULL) {
1442     conf->dir                  = NULL;
1443   }
1444   else {
1445     conf->dir                  = apr_pcalloc(p, strlen(arg)+1);
1446     memset(conf->dir, 0, strlen(arg)+1);
1447     strcpy(conf->dir, arg);
1448   }
1449   conf->convrules   = apr_array_make(p, 2, sizeof(chxjconvrule_entry));
1450
1451   /* Default is copyleft */
1452   conf->image_copyright = NULL; 
1453
1454   return conf;
1455 }
1456
1457
1458 /*
1459  *  Merge per-directory CHXJ configurations
1460  */
1461 static void *
1462 chxj_merge_per_dir_config(apr_pool_t *p, void *basev, void *addv)
1463 {
1464   mod_chxj_config *base;
1465   mod_chxj_config *add;
1466   mod_chxj_config *mrg;
1467
1468   base = (mod_chxj_config *)basev;
1469   add  = (mod_chxj_config *)addv;
1470   mrg  = (mod_chxj_config *)apr_palloc(p, sizeof(mod_chxj_config));
1471
1472   mrg->device_data_file = NULL;
1473   mrg->devices          = NULL;
1474   mrg->emoji_data_file  = NULL;
1475   mrg->image            = CHXJ_IMG_NONE;
1476   mrg->image_cache_dir  = NULL;
1477   mrg->image_copyright  = NULL;
1478   mrg->image_cache_limit  = 0;
1479   mrg->emoji            = NULL;
1480   mrg->emoji_tail       = NULL;
1481   mrg->new_line_type    = NLTYPE_NIL;
1482   mrg->forward_url_base = NULL;
1483   mrg->forward_server_ip = NULL;
1484
1485   mrg->dir = apr_pstrdup(p, add->dir);
1486
1487   if (! add->device_data_file) {
1488     mrg->devices = base->devices;
1489     mrg->device_data_file = apr_pstrdup(p, base->device_data_file);
1490   }
1491   else {
1492     mrg->devices = add->devices;
1493     mrg->device_data_file = apr_pstrdup(p, add->device_data_file);
1494   }
1495
1496   if (! add->emoji_data_file) {
1497     mrg->emoji = base->emoji;
1498     mrg->emoji_tail = base->emoji_tail;
1499     mrg->emoji_data_file = apr_pstrdup(p, base->emoji_data_file);
1500   }
1501   else {
1502     mrg->emoji = add->emoji;
1503     mrg->emoji_tail = add->emoji_tail;
1504     mrg->emoji_data_file = apr_pstrdup(p, add->emoji_data_file);
1505   }
1506
1507   if (add->image == CHXJ_IMG_NONE) {
1508     mrg->image = base->image;
1509   }
1510   else {
1511     mrg->image = add->image;
1512   }
1513
1514   if (strcasecmp(add->image_cache_dir ,DEFAULT_IMAGE_CACHE_DIR)==0) {
1515     mrg->image_cache_dir = apr_pstrdup(p, base->image_cache_dir);
1516   }
1517   else {
1518     mrg->image_cache_dir = apr_pstrdup(p, add->image_cache_dir);
1519   }
1520
1521   if (add->image_cache_limit) {
1522     mrg->image_cache_limit = add->image_cache_limit;
1523   }
1524   else {
1525     mrg->image_cache_limit = base->image_cache_limit;
1526   }
1527
1528   if (add->image_copyright) 
1529     mrg->image_copyright = apr_pstrdup(p, add->image_copyright);
1530   else
1531     mrg->image_copyright = apr_pstrdup(p, base->image_copyright);
1532
1533   if (add->server_side_encoding) {
1534     mrg->server_side_encoding = apr_pstrdup(p, add->server_side_encoding);
1535   }
1536   else 
1537   if (base->server_side_encoding) {
1538     mrg->server_side_encoding = apr_pstrdup(p, base->server_side_encoding);
1539   }
1540   else {
1541     mrg->server_side_encoding = apr_pstrdup(p, DEFAULT_SERVER_SIDE_ENCODING);
1542   }
1543
1544   mrg->convrules    = apr_array_append(p, add->convrules, base->convrules);
1545
1546   if (add->cookie_db_dir) {
1547     mrg->cookie_db_dir = apr_pstrdup(p, add->cookie_db_dir);
1548   }
1549   else
1550   if (base->cookie_db_dir) {
1551     mrg->cookie_db_dir = apr_pstrdup(p, base->cookie_db_dir);
1552   }
1553   else {
1554     mrg->cookie_db_dir = NULL;
1555   }
1556
1557   if (add->cookie_timeout) {
1558     mrg->cookie_timeout   = add->cookie_timeout;
1559   }
1560   else
1561   if (base->cookie_db_dir) {
1562     mrg->cookie_timeout   = base->cookie_timeout;
1563   }
1564   else {
1565     mrg->cookie_timeout   = 0;
1566   }
1567
1568 #if defined(USE_MYSQL_COOKIE)
1569   if (add->mysql.host) {
1570     mrg->mysql.host = apr_pstrdup(p, add->mysql.host);
1571   }
1572   else if (base->mysql.host) {
1573     mrg->mysql.host = apr_pstrdup(p, base->mysql.host);
1574   }
1575   else {
1576     mrg->mysql.host = NULL;
1577   }
1578   if (add->mysql.port) {
1579     mrg->mysql.port = add->mysql.port;
1580   }
1581   else if (base->mysql.port) {
1582     mrg->mysql.port = base->mysql.port;
1583   }
1584   else {
1585     mrg->mysql.port = 0;
1586   }
1587
1588   if (add->mysql.database) {
1589     mrg->mysql.database = apr_pstrdup(p, add->mysql.database);
1590   }
1591   else if (base->mysql.database) {
1592     mrg->mysql.database = apr_pstrdup(p, base->mysql.database);
1593   }
1594   else {
1595     mrg->mysql.database = NULL;
1596   }
1597
1598   if (add->mysql.username) {
1599     mrg->mysql.username = apr_pstrdup(p, add->mysql.username);
1600   }
1601   else if (base->mysql.username) {
1602     mrg->mysql.username = apr_pstrdup(p, base->mysql.username);
1603   }
1604   else {
1605     mrg->mysql.username = NULL;
1606   }
1607
1608   if (add->mysql.password) {
1609     mrg->mysql.password = apr_pstrdup(p, add->mysql.password);
1610   }
1611   else if (base->mysql.password) {
1612     mrg->mysql.password = apr_pstrdup(p, base->mysql.password);
1613   }
1614   else {
1615     mrg->mysql.password = NULL;
1616   }
1617
1618   if (add->mysql.tablename) {
1619     mrg->mysql.tablename = apr_pstrdup(p, add->mysql.tablename);
1620   }
1621   else if (base->mysql.tablename) {
1622     mrg->mysql.tablename = apr_pstrdup(p, base->mysql.tablename);
1623   }
1624   else {
1625     mrg->mysql.tablename = NULL;
1626   }
1627
1628   if (add->mysql.socket_path) {
1629     mrg->mysql.socket_path = apr_pstrdup(p, add->mysql.socket_path);
1630   }
1631   else if (base->mysql.socket_path) {
1632     mrg->mysql.socket_path = apr_pstrdup(p, base->mysql.socket_path);
1633   }
1634   else {
1635     mrg->mysql.socket_path = NULL;
1636   }
1637
1638   if (add->mysql.charset) {
1639     mrg->mysql.charset = apr_pstrdup(p, add->mysql.charset);
1640   }
1641   else if (base->mysql.charset) {
1642     mrg->mysql.charset = apr_pstrdup(p, base->mysql.charset);
1643   }
1644   else {
1645     mrg->mysql.charset = NULL;
1646   }
1647 #endif
1648 #if defined(USE_MEMCACHE_COOKIE)
1649   if (add->memcache.host) {
1650     mrg->memcache.host = apr_pstrdup(p, add->memcache.host);
1651   }
1652   else if (base->memcache.host) {
1653     mrg->memcache.host = apr_pstrdup(p, base->memcache.host);
1654   }
1655   else {
1656     mrg->memcache.host = NULL;
1657   }
1658   if (add->memcache.port) {
1659     mrg->memcache.port = add->memcache.port;
1660   }
1661   else if (base->memcache.port) {
1662     mrg->memcache.port = base->memcache.port;
1663   }
1664   else {
1665     mrg->memcache.port = 0;
1666   }
1667 #endif
1668   if (add->cookie_store_type) {
1669     mrg->cookie_store_type = add->cookie_store_type;
1670   }
1671   else if (base->cookie_store_type) {
1672     mrg->cookie_store_type = base->cookie_store_type;
1673   }
1674   else {
1675     mrg->cookie_store_type = COOKIE_STORE_TYPE_NONE;
1676   }
1677   if (add->cookie_lazy_mode) {
1678     mrg->cookie_lazy_mode = add->cookie_lazy_mode;
1679   }
1680   else if (base->cookie_lazy_mode) {
1681     mrg->cookie_lazy_mode = base->cookie_lazy_mode;
1682   }
1683   else {
1684     mrg->cookie_lazy_mode = 0;
1685   }
1686   if (add->new_line_type) {
1687     mrg->new_line_type = add->new_line_type;
1688   }
1689   else if (base->new_line_type) {
1690     mrg->new_line_type = base->new_line_type;
1691   }
1692   else {
1693     mrg->new_line_type = NLTYPE_NIL;
1694   }
1695   return mrg;
1696 }
1697
1698
1699 static int
1700 chxj_command_parse_take5(
1701   const char *arg, 
1702   char       **prm1, 
1703   char       **prm2, 
1704   char       **prm3, 
1705   char       **prm4, 
1706   char       **prm5)
1707 {
1708   int  isquoted;
1709   char *strp;
1710
1711   strp = (char *)arg;
1712
1713   for (;*strp == ' '||*strp == '\t'; strp++) ;
1714
1715   isquoted = 0; 
1716   if (*strp == '"') { 
1717     isquoted = 1;
1718     strp++;
1719   }
1720
1721   *prm1 = strp;
1722
1723   for (; *strp != '\0'; strp++) {
1724     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1725     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1726       strp++;
1727       continue;
1728     }
1729
1730     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1731     ||  (isquoted  && *strp == '"'))
1732       break;
1733   }
1734
1735   if (! *strp) {
1736     *prm2 = strp;
1737     *prm3 = strp;
1738     *prm4 = strp;
1739     *prm5 = strp;
1740     return 1;
1741   }
1742
1743   *strp++ = '\0';
1744
1745   for (;*strp == ' '||*strp == '\t'; strp++) ;
1746
1747   isquoted = 0; 
1748   if (*strp == '"') { 
1749     isquoted = 1;
1750     strp++;
1751   }
1752
1753   *prm2 = strp;
1754   for (; *strp != '\0'; strp++) {
1755     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1756     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1757       strp++;
1758       continue;
1759     }
1760
1761     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1762     ||  (isquoted  && *strp == '"'))
1763       break;
1764   }
1765
1766   if (! *strp) {
1767     *prm3 = strp;
1768     *prm4 = strp;
1769     *prm5 = strp;
1770     return 0;
1771   }
1772
1773   *strp++ = '\0';
1774
1775   for (;*strp == ' '||*strp == '\t'; strp++);
1776
1777   isquoted = 0; 
1778   if (*strp == '"') { 
1779     isquoted = 1;
1780     strp++;
1781   }
1782   *prm3 = strp;
1783   for (; *strp != '\0'; strp++) {
1784     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1785     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1786       strp++;
1787       continue;
1788     }
1789
1790     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1791     ||  (isquoted  && *strp == '"'))
1792       break;
1793   }
1794
1795   if (! *strp) {
1796     *prm4 = strp;
1797     *prm5 = strp;
1798     return 0;
1799   }
1800
1801   *strp++ = '\0';
1802
1803   for (;*strp == ' '||*strp == '\t'; strp++);
1804
1805   isquoted = 0; 
1806   if (*strp == '"') { 
1807     isquoted = 1;
1808     strp++;
1809   }
1810   *prm4 = strp;
1811   for (; *strp != '\0'; strp++) {
1812     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1813     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1814       strp++;
1815       continue;
1816     }
1817
1818     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1819     ||  (isquoted  && *strp == '"'))
1820       break;
1821   }
1822
1823   if (! *strp) {
1824     *prm5 = strp;
1825     return 0;
1826   }
1827
1828   *strp++ = '\0';
1829
1830   for (;*strp == ' '||*strp == '\t'; strp++);
1831
1832   isquoted = 0; 
1833   if (*strp == '"') { 
1834     isquoted = 1;
1835     strp++;
1836   }
1837   *prm5 = strp;
1838   for (; *strp != '\0'; strp++) {
1839     if ((isquoted && (*strp == ' ' || *strp == '\t'))
1840     ||  (*strp == '\\' && (*(strp+1) == ' ' || *(strp+1) == '\t'))) {
1841       strp++;
1842       continue;
1843     }
1844
1845     if ((!isquoted && (*strp == ' ' || *strp == '\t'))
1846     ||  (isquoted  && *strp == '"'))
1847       break;
1848   }
1849   *strp = '\0';
1850
1851   return 0;
1852 }
1853
1854
1855 /**
1856  * The device definition file is loaded. 
1857  *
1858  * @param arg     [i]   The name of the device definition file is specified.
1859  * @param mconfig [i/o] The pointer to a set structure is specified. 
1860  * @param parms   [i]   
1861  */
1862 static const char * 
1863 cmd_load_device_data(cmd_parms *parms, void *mconfig, const char *arg) 
1864 {
1865   mod_chxj_config  *conf;
1866   Doc              doc;
1867
1868   doc.r = NULL;
1869
1870   if (strlen(arg) > 256) 
1871     return "mod_chxj: device data filename too long.";
1872
1873   conf = (mod_chxj_config *)mconfig;
1874   conf->device_data_file = apr_pstrdup(parms->pool, arg);
1875
1876   qs_init_malloc(&doc);
1877   qs_init_root_node(&doc);
1878
1879   qs_parse_file((Doc *)&doc, (const char *)arg);
1880   chxj_load_device_data(&doc,parms->pool, conf);
1881   qs_all_free(&doc, QX_LOGMARK);
1882
1883   return NULL;
1884 }
1885
1886
1887 /**
1888  * Device definition information is loaded. 
1889  *
1890  * @param parms 
1891  * @param arg     [i]   The name of the device definition file is specified. 
1892  * @param mconfig [i/o] The pointer to a set structure is specified. 
1893  * @return 
1894  */
1895 static const char * 
1896 cmd_load_emoji_data(cmd_parms *parms, void *mconfig, const char *arg) 
1897 {
1898   mod_chxj_config *conf;
1899   char            *rtn;
1900   Doc              doc;
1901
1902   doc.r = NULL;
1903
1904
1905   if (strlen(arg) > 256) 
1906     return "mod_chxj: emoji data filename too long.";
1907
1908   conf = (mod_chxj_config *)mconfig;
1909   conf->emoji_data_file = apr_pstrdup(parms->pool, arg);
1910   qs_init_malloc(&doc);
1911   qs_init_root_node(&doc);
1912
1913   qs_parse_file((Doc *)&doc, (const char *)arg);
1914
1915   rtn = chxj_load_emoji_data(&doc,parms->pool, conf);
1916
1917   qs_all_free(&doc, QX_LOGMARK);
1918
1919
1920   return rtn;
1921 }
1922
1923
1924 static const char * 
1925 cmd_set_image_engine(cmd_parms * UNUSED(parms), void *mconfig, const char *arg) 
1926 {
1927   mod_chxj_config *conf;
1928   Doc              doc;
1929
1930   doc.r = NULL;
1931
1932   if (strlen(arg) > 256) 
1933     return "image uri is too long.";
1934
1935   conf = (mod_chxj_config*)mconfig;
1936   if (strcasecmp("ON", arg) == 0) {
1937     conf->image = CHXJ_IMG_ON;
1938   }
1939   else {
1940     conf->image = CHXJ_IMG_OFF;
1941   }
1942
1943   return NULL;
1944 }
1945
1946
1947 static const char * 
1948 cmd_set_image_cache_dir(cmd_parms *parms, void *mconfig, const char *arg) 
1949 {
1950   mod_chxj_config *conf;
1951   Doc              doc;
1952
1953   doc.r = NULL;
1954
1955   if (strlen(arg) > 256) 
1956     return "cache dir name is too long.";
1957
1958   conf = (mod_chxj_config *)mconfig;
1959   conf->image_cache_dir = apr_pstrdup(parms->pool, arg);
1960
1961   return NULL;
1962 }
1963
1964
1965 static const char * 
1966 cmd_set_image_cache_limit(cmd_parms *parms, void *mconfig, const char *arg) 
1967 {
1968   mod_chxj_config *conf;
1969   Doc              doc;
1970
1971   doc.r = NULL;
1972
1973   if (strlen(arg) > IMAGE_CACHE_LIMIT_FMT_LEN) 
1974     return "cache size is too long.";
1975
1976   conf = (mod_chxj_config *)mconfig;
1977   errno = 0;
1978   /* 
1979    * I use strtol function because strtoul is not portable function. 
1980    */
1981   conf->image_cache_limit = (unsigned long)strtol(arg, NULL, 10);
1982   switch (errno) {
1983   case EINVAL:
1984     return apr_psprintf(parms->pool, "ChxjImageCacheLimit invalid value [%s] errno:[%d]", arg, errno);
1985   case ERANGE:
1986     return apr_psprintf(parms->pool, "ChxjImageCacheLimit Out of range [%s] errno:[%d]", arg, errno);
1987   default:
1988     break;
1989   }
1990   return NULL;
1991 }
1992
1993
1994 static const char * 
1995 cmd_set_image_copyright(cmd_parms *parms, void *mconfig, const char *arg) 
1996 {
1997   mod_chxj_config *conf;
1998   Doc              doc;
1999
2000   doc.r = NULL;
2001
2002   if (strlen(arg) > 256) 
2003     return "Copyright Flag is too long.";
2004
2005   conf = (mod_chxj_config *)mconfig;
2006   conf->image_copyright = apr_pstrdup(parms->pool, arg);
2007
2008   return NULL;
2009 }
2010
2011
2012 static const char *
2013 cmd_convert_rule(cmd_parms *cmd, void *mconfig, const char *arg)
2014 {
2015   int                 mode;
2016   ap_regex_t          *regexp;
2017   mod_chxj_config     *dconf;
2018   chxjconvrule_entry  *newrule;
2019   char                *prm1;
2020   char                *prm2;
2021   char                *prm3;
2022   char                *prm4;
2023   char                *prm5;
2024   char                *pstate;
2025   char                *action;
2026   char                *pp;
2027
2028   dconf = (mod_chxj_config *)mconfig;
2029
2030   if (strlen(arg) > 4096) 
2031     return "mod_chxj: ChxjConvertRule: is too long.";
2032
2033   dconf = (mod_chxj_config *)mconfig;
2034   if (dconf->convrules == NULL)
2035     dconf->convrules   = apr_array_make(cmd->pool, 
2036                                         2, 
2037                                         sizeof(chxjconvrule_entry));
2038
2039   newrule = apr_array_push(dconf->convrules);
2040
2041   newrule->flags  = 0;
2042   newrule->action = 0;
2043
2044   if (chxj_command_parse_take5(arg, &prm1, &prm2, &prm3, &prm4, &prm5))
2045     return "ChxjConvertRule: bad argument line";
2046
2047   newrule->pattern = apr_pstrdup(cmd->pool, prm1);
2048
2049   /* Parse action */
2050   for (;;) {
2051     if ((action = apr_strtok(prm2, ",", &pstate)) == NULL)
2052       break;
2053     prm2 = NULL;
2054     switch(*action) {
2055     case 'e':
2056     case 'E':
2057       if (strcasecmp(CONVRULE_ENGINE_ON_CMD, action) == 0) {
2058         newrule->action |= CONVRULE_ENGINE_ON_BIT;
2059       }
2060       else
2061       if (strcasecmp(CONVRULE_ENGINE_OFF_CMD, action) == 0) {
2062         newrule->action |= CONVRULE_ENGINE_OFF_BIT;
2063       }
2064       break;
2065
2066     case 'C':
2067     case 'c':
2068       if (strcasecmp(CONVRULE_COOKIE_ON_CMD, action) == 0) {
2069         newrule->action |= CONVRULE_COOKIE_ON_BIT;
2070       }
2071       break;
2072     default:
2073       break;
2074     }
2075   }
2076   
2077   pp = prm1;
2078   if (*pp == '!') {
2079     newrule->flags |= CONVRULE_FLAG_NOTMATCH;
2080     pp++;
2081   }
2082
2083   mode = AP_REG_EXTENDED;
2084   if ((regexp = ap_pregcomp((apr_pool_t *)cmd->pool, (const char *)pp, mode)) == NULL)
2085     return "RewriteRule: cannot compile regular expression ";
2086
2087   newrule->regexp = regexp;
2088   if (*prm3)
2089     newrule->encoding = apr_pstrdup(cmd->pool, prm3);
2090   else
2091     newrule->encoding = apr_pstrdup(cmd->pool, "none");
2092
2093   newrule->pc_flag = CONVRULE_PC_FLAG_OFF_BIT;
2094   if (*prm4)
2095     if (strcasecmp(CONVRULE_PC_FLAG_ON_CMD, prm4) == 0)
2096       newrule->pc_flag = CONVRULE_PC_FLAG_ON_BIT;
2097
2098   newrule->user_agent = NULL;
2099   if (*prm5)
2100     newrule->user_agent = apr_pstrdup(cmd->pool, prm5);
2101     
2102   return NULL;
2103 }
2104
2105
2106 static const char *
2107 cmd_set_cookie_dir(
2108   cmd_parms   *cmd, 
2109   void        *mconfig, 
2110   const char  *arg)
2111 {
2112   mod_chxj_config *dconf;
2113
2114
2115   if (strlen(arg) > 4096) 
2116     return "mod_chxj: ChxjCookieDir is too long.";
2117
2118   dconf = (mod_chxj_config *)mconfig;
2119
2120   dconf->cookie_db_dir = apr_pstrdup(cmd->pool, arg);
2121
2122   return NULL;
2123 }
2124
2125
2126 static const char *
2127 cmd_set_cookie_timeout(
2128   cmd_parms   *UNUSED(cmd), 
2129   void        *mconfig, 
2130   const char  *arg)
2131 {
2132   mod_chxj_config *dconf;
2133
2134   if (strlen(arg) > 4096) 
2135     return "mod_chxj: ChxjCookieTimeout is too long.";
2136
2137   if (chxj_chk_numeric(arg) != 0)
2138     return "mod_chxj: ChxjCookieTimeout is not numeric.";
2139
2140   dconf = (mod_chxj_config *)mconfig;
2141
2142   dconf->cookie_timeout = atoi(arg);
2143
2144   return NULL;
2145 }
2146
2147
2148 #if defined(USE_MYSQL_COOKIE)
2149 static const char *
2150 cmd_set_cookie_mysql_database(
2151   cmd_parms   *cmd, 
2152   void        *mconfig, 
2153   const char  *arg)
2154 {
2155   mod_chxj_config  *dconf;
2156
2157   if (strlen(arg) > 255) 
2158     return "mod_chxj: ChxjCookieMysqlDatabase is too long.";
2159
2160   dconf = (mod_chxj_config *)mconfig;
2161
2162   dconf->mysql.database = apr_pstrdup(cmd->pool, arg);
2163
2164   return NULL;
2165 }
2166
2167
2168 static const char *
2169 cmd_set_cookie_mysql_username(
2170   cmd_parms   *cmd, 
2171   void        *mconfig, 
2172   const char  *arg)
2173 {
2174   mod_chxj_config  *dconf;
2175
2176   if (strlen(arg) > 255) 
2177     return "mod_chxj: ChxjCookieMysqlUsername is too long.";
2178
2179   dconf = (mod_chxj_config *)mconfig;
2180
2181   dconf->mysql.username = apr_pstrdup(cmd->pool, arg);
2182
2183   return NULL;
2184 }
2185
2186
2187 static const char *
2188 cmd_set_cookie_mysql_password(
2189   cmd_parms   *cmd, 
2190   void        *mconfig, 
2191   const char  *arg)
2192 {
2193   mod_chxj_config  *dconf;
2194
2195   if (strlen(arg) > 255) 
2196     return "mod_chxj: ChxjCookieMysqlPassword is too long.";
2197
2198   dconf = (mod_chxj_config *)mconfig;
2199
2200   dconf->mysql.password = apr_pstrdup(cmd->pool, arg);
2201
2202   return NULL;
2203 }
2204
2205
2206 static const char *
2207 cmd_set_cookie_mysql_table_name(
2208   cmd_parms   *cmd, 
2209   void        *mconfig, 
2210   const char  *arg)
2211 {
2212   mod_chxj_config  *dconf;
2213
2214   if (strlen(arg) > 255) 
2215     return "mod_chxj: ChxjCookieMysqlTableName is too long.";
2216
2217   dconf = (mod_chxj_config *)mconfig;
2218
2219   dconf->mysql.tablename = apr_pstrdup(cmd->pool, arg);
2220
2221   return NULL;
2222 }
2223
2224 static const char *
2225 cmd_set_cookie_mysql_port(
2226   cmd_parms   *UNUSED(cmd), 
2227   void        *mconfig, 
2228   const char  *arg)
2229 {
2230   mod_chxj_config *dconf;
2231
2232   if (strlen(arg) > 255) 
2233     return "mod_chxj: ChxjCookieMysqlPort is too long.";
2234
2235   dconf = (mod_chxj_config *)mconfig;
2236
2237   if (chxj_chk_numeric(arg) != 0)
2238     return "mod_chxj: ChxjCookieMysqlPort is not numeric.";
2239
2240   dconf = (mod_chxj_config *)mconfig;
2241
2242   dconf->mysql.port = chxj_atoi(arg);
2243
2244   return NULL;
2245 }
2246
2247
2248 static const char *
2249 cmd_set_cookie_mysql_host(
2250   cmd_parms   *cmd, 
2251   void        *mconfig, 
2252   const char  *arg)
2253 {
2254   mod_chxj_config  *dconf;
2255
2256   if (strlen(arg) > 255) 
2257     return "mod_chxj: ChxjCookieMysqlHost is too long.";
2258
2259   dconf = (mod_chxj_config *)mconfig;
2260
2261   dconf->mysql.host = apr_pstrdup(cmd->pool, arg);
2262
2263   return NULL;
2264 }
2265
2266
2267 static const char *
2268 cmd_set_cookie_mysql_socket_path(
2269   cmd_parms   *cmd, 
2270   void        *mconfig, 
2271   const char  *arg)
2272 {
2273   mod_chxj_config  *dconf;
2274
2275   if (strlen(arg) > 4096) 
2276     return "mod_chxj: ChxjCookieMysqlSocketPath is too long.";
2277
2278   dconf = (mod_chxj_config *)mconfig;
2279
2280   dconf->mysql.socket_path = apr_pstrdup(cmd->pool, arg);
2281
2282   return NULL;
2283 }
2284
2285
2286 static const char *
2287 cmd_set_cookie_mysql_charset(
2288   cmd_parms   *cmd, 
2289   void        *mconfig, 
2290   const char  *arg)
2291 {
2292   mod_chxj_config  *dconf;
2293
2294   if (strlen(arg) > 255) 
2295     return "mod_chxj: ChxjCookieMysqlCharset is too long.";
2296
2297   dconf = (mod_chxj_config *)mconfig;
2298
2299   dconf->mysql.charset = apr_pstrdup(cmd->pool, arg);
2300
2301   return NULL;
2302 }
2303 #endif
2304 #if defined(USE_MEMCACHE_COOKIE)
2305 static const char *
2306 cmd_set_cookie_memcache_port(
2307   cmd_parms   *UNUSED(cmd), 
2308   void        *mconfig, 
2309   const char  *arg)
2310 {
2311   mod_chxj_config *dconf;
2312
2313   if (strlen(arg) > 255) 
2314     return "mod_chxj: ChxjCookieMemcachePort is too long.";
2315
2316   dconf = (mod_chxj_config *)mconfig;
2317
2318   if (chxj_chk_numeric(arg) != 0)
2319     return "mod_chxj: ChxjCookieMemcachePort is not numeric.";
2320
2321   dconf = (mod_chxj_config *)mconfig;
2322
2323   dconf->memcache.port = (apr_port_t)chxj_atoi(arg);
2324
2325   return NULL;
2326 }
2327
2328
2329 static const char *
2330 cmd_set_cookie_memcache_host(
2331   cmd_parms   *cmd, 
2332   void        *mconfig, 
2333   const char  *arg)
2334 {
2335   mod_chxj_config  *dconf;
2336
2337   if (strlen(arg) > 255) 
2338     return "mod_chxj: ChxjCookieMemcacheHost is too long.";
2339
2340   dconf = (mod_chxj_config *)mconfig;
2341
2342   dconf->memcache.host = apr_pstrdup(cmd->pool, arg);
2343
2344   return NULL;
2345 }
2346 #endif
2347
2348 static const char *
2349 cmd_set_cookie_lazy_mode(
2350   cmd_parms   *UNUSED(cmd), 
2351   void        *mconfig, 
2352   const char  *arg)
2353 {
2354   mod_chxj_config  *dconf;
2355
2356   if (strlen(arg) > 255) 
2357     return "mod_chxj: ChxjCookieLazyMode is too long.";
2358
2359   dconf = (mod_chxj_config *)mconfig;
2360
2361   if (strcasecmp("TRUE",arg) == 0) {
2362     dconf->cookie_lazy_mode = COOKIE_LAZY_ON;
2363   }
2364   else {
2365     dconf->cookie_lazy_mode = COOKIE_LAZY_OFF;
2366   }
2367
2368   return NULL;
2369 }
2370
2371 static const char *
2372 cmd_set_cookie_store_type(
2373   cmd_parms   *UNUSED(cmd), 
2374   void        *mconfig, 
2375   const char  *arg)
2376 {
2377   mod_chxj_config  *dconf;
2378
2379   if (strlen(arg) > 255) 
2380     return "mod_chxj: ChxjCookieStoreType is too long.";
2381
2382   dconf = (mod_chxj_config *)mconfig;
2383
2384   if (strcasecmp(CHXJ_COOKIE_STORE_TYPE_DBM, arg) == 0) {
2385     dconf->cookie_store_type = COOKIE_STORE_TYPE_DBM;
2386   }
2387   else if (strcasecmp(CHXJ_COOKIE_STORE_TYPE_MYSQL, arg) == 0) {
2388     dconf->cookie_store_type = COOKIE_STORE_TYPE_MYSQL;
2389   }
2390   else if (strcasecmp(CHXJ_COOKIE_STORE_TYPE_MEMCACHE, arg) == 0) {
2391     dconf->cookie_store_type = COOKIE_STORE_TYPE_MEMCACHE;
2392   }
2393   else {
2394     dconf->cookie_store_type = COOKIE_STORE_TYPE_NONE;
2395   }
2396
2397   return NULL;
2398 }
2399
2400 static const char *
2401 cmd_set_forward_url_base(
2402   cmd_parms   *cmd,
2403   void        *mconfig,
2404   const char  *arg)
2405 {
2406  mod_chxj_config *dconf;
2407
2408   if (strlen(arg) > 255)
2409     return "mod_chxj: ChxjForwardUrlBase is too long.";
2410
2411   dconf = (mod_chxj_config *)mconfig;
2412
2413   dconf->forward_url_base = apr_pstrdup(cmd->pool, arg);
2414
2415   return NULL;
2416 }
2417
2418 static const char *
2419 cmd_set_forward_server_ip(
2420   cmd_parms   *cmd,
2421   void        *mconfig,
2422   const char  *arg)
2423 {
2424   mod_chxj_config *dconf;
2425
2426   if (strlen(arg) > 255)
2427     return "mod_chxj: ChxjForwardServerIp is too long.";
2428
2429   dconf = (mod_chxj_config *)mconfig;
2430
2431   dconf->forward_server_ip = apr_pstrdup(cmd->pool, arg);
2432
2433   return NULL;
2434 }
2435
2436 static const char *
2437 cmd_set_new_line_type(
2438   cmd_parms   *UNUSED(cmd), 
2439   void        *mconfig, 
2440   const char  *arg)
2441 {
2442   mod_chxj_config  *dconf;
2443   if (strlen(arg) > 255)
2444     return "mod_chxj: ChxjNewLineType is too long.";
2445
2446   dconf = (mod_chxj_config *)mconfig;
2447
2448   if (strcasecmp(CHXJ_NEW_LINE_TYPE_CRLF, arg) == 0) {
2449     dconf->new_line_type = NLTYPE_CRLF;
2450   }
2451   else if (strcasecmp(CHXJ_NEW_LINE_TYPE_LF, arg) == 0) {
2452     dconf->new_line_type = NLTYPE_LF;
2453   }
2454   else if (strcasecmp(CHXJ_NEW_LINE_TYPE_CR, arg) == 0) {
2455     dconf->new_line_type = NLTYPE_CR;
2456   }
2457   else if (strcasecmp(CHXJ_NEW_LINE_TYPE_NONE, arg) == 0) {
2458     dconf->new_line_type = NLTYPE_NONE;
2459   }
2460   else {
2461     return "mod_chxj: invalid value (ChxjNewLineType)";
2462   }
2463   return NULL;
2464 }
2465
2466
2467 static const command_rec cmds[] = {
2468   AP_INIT_TAKE1(
2469     "ChxjLoadDeviceData",
2470     cmd_load_device_data,
2471     NULL,
2472     OR_ALL,
2473     "Load Device Data"),
2474   AP_INIT_TAKE1(
2475     "ChxjLoadEmojiData",
2476     cmd_load_emoji_data,
2477     NULL,
2478     OR_ALL,
2479     "Load Emoji Data"),
2480   AP_INIT_TAKE1(
2481     "ChxjImageEngine",
2482     cmd_set_image_engine,
2483     NULL,
2484     OR_ALL,
2485     "Convert Target URI"),
2486   AP_INIT_TAKE1(
2487     "ChxjImageCacheDir",
2488     cmd_set_image_cache_dir,
2489     NULL,
2490     OR_ALL,
2491     "Image Cache Directory"),
2492   AP_INIT_TAKE1(
2493     "ChxjImageCacheLimit",
2494     cmd_set_image_cache_limit,
2495     NULL,
2496     OR_ALL,
2497     "Image Cache Limit"),
2498   AP_INIT_TAKE1(
2499     "ChxjImageCopyright",
2500     cmd_set_image_copyright,
2501     NULL,
2502     OR_ALL,
2503     "Copyright Flag"),
2504   AP_INIT_RAW_ARGS(
2505     "ChxjConvertRule",
2506     cmd_convert_rule,
2507     NULL, 
2508     OR_FILEINFO,
2509     "an URL-applied regexp-pattern and a substitution URL"),
2510   AP_INIT_TAKE1(
2511     "ChxjCookieDir",
2512     cmd_set_cookie_dir,
2513     NULL,
2514     OR_ALL,
2515     "save cookie.db directory."),
2516   AP_INIT_TAKE1(
2517     "ChxjCookieTimeout",
2518     cmd_set_cookie_timeout,
2519     NULL,
2520     OR_ALL,
2521     "The compulsion time-out time of the cookie is specified. "),
2522   AP_INIT_TAKE1(
2523     "ChxjCookieStoreType",
2524     cmd_set_cookie_store_type,
2525     NULL,
2526     OR_ALL,
2527     "It specifies preserving of the cookie ahead. (DBM/MYSQL/MEMCACHE)"),
2528   AP_INIT_TAKE1(
2529     "ChxjCookieLazyMode",
2530     cmd_set_cookie_lazy_mode,
2531     NULL,
2532     OR_ALL,
2533     "OneTimeID is negligently done. (TRUE/FALSE)"),
2534 #if defined(USE_MYSQL_COOKIE)
2535   AP_INIT_TAKE1(
2536     "ChxjCookieMysqlHost",
2537     cmd_set_cookie_mysql_host,
2538     NULL,
2539     OR_ALL,
2540     "The MySQL database host used by saving Cookie"),
2541   AP_INIT_TAKE1(
2542     "ChxjCookieMysqlPort",
2543     cmd_set_cookie_mysql_port,
2544     NULL,
2545     OR_ALL,
2546     "The MySQL database port used by saving Cookie"),
2547   AP_INIT_TAKE1(
2548     "ChxjCookieMysqlDatabase",
2549     cmd_set_cookie_mysql_database,
2550     NULL,
2551     OR_ALL,
2552     "The MySQL database name used by saving Cookie"),
2553   AP_INIT_TAKE1(
2554     "ChxjCookieMysqlUsername",
2555     cmd_set_cookie_mysql_username,
2556     NULL,
2557     OR_ALL,
2558     "The MySQL username used by saving Cookie"),
2559   AP_INIT_TAKE1(
2560     "ChxjCookieMysqlPassword",
2561     cmd_set_cookie_mysql_password,
2562     NULL,
2563     OR_ALL,
2564     "The MySQL password used by saving Cookie"),
2565   AP_INIT_TAKE1(
2566     "ChxjCookieMysqlTableName",
2567     cmd_set_cookie_mysql_table_name,
2568     NULL,
2569     OR_ALL,
2570     "The MySQL table name used by saving Cookie"),
2571   AP_INIT_TAKE1(
2572     "ChxjCookieMysqlSocketPath",
2573     cmd_set_cookie_mysql_socket_path,
2574     NULL,
2575     OR_ALL,
2576     "The MySQL socket path used by saving Cookie"),
2577   AP_INIT_TAKE1(
2578     "ChxjCookieMysqlCharset",
2579     cmd_set_cookie_mysql_charset,
2580     NULL,
2581     OR_ALL,
2582     "The MySQL charset used by saving Cookie"),
2583 #endif
2584 #if defined(USE_MEMCACHE_COOKIE)
2585   AP_INIT_TAKE1(
2586     "ChxjCookieMemcacheHost",
2587     cmd_set_cookie_memcache_host,
2588     NULL,
2589     OR_ALL,
2590     "The Memcached host used by saving Cookie"),
2591   AP_INIT_TAKE1(
2592     "ChxjCookieMemcachePort",
2593     cmd_set_cookie_memcache_port,
2594     NULL,
2595     OR_ALL,
2596     "The Memcached port used by saving Cookie"),
2597 #endif
2598   AP_INIT_TAKE1(
2599     "ChxjNewLineType",
2600     cmd_set_new_line_type,
2601     NULL,
2602     OR_ALL,
2603     "HTML new line type (NONE|CRLF|LF|CR). default is CRLF"),
2604   AP_INIT_TAKE1(
2605     "ChxjForwardUrlBase",
2606     cmd_set_forward_url_base,
2607     NULL,
2608     OR_ALL,
2609     "The forward url base(default: {request protocol}://{this server}:{this server port}"),
2610   AP_INIT_TAKE1(
2611     "ChxjForwardServerIp",
2612     cmd_set_forward_server_ip,
2613     NULL,
2614     OR_ALL,
2615     "The forward server ip(default: this server ip)"),
2616   {NULL}
2617 };
2618
2619
2620 /*----------------------------------------------------------------------------*/
2621 /* Dispatch list for API hooks                                                */
2622 /*----------------------------------------------------------------------------*/
2623 module AP_MODULE_DECLARE_DATA chxj_module = {
2624   STANDARD20_MODULE_STUFF, 
2625   chxj_create_per_dir_config,          /* create per-dir    config structures */
2626   chxj_merge_per_dir_config,           /* merge  per-dir    config structures */
2627   chxj_config_server_create,           /* create per-server config structures */
2628   NULL,                                /* merge  per-server config structures */
2629   cmds,                                /* table of config file commands       */
2630   chxj_register_hooks                  /* register hooks                      */
2631 };
2632 /*
2633  * vim:ts=2 et
2634  */