|
|
| version 1.226, 2007/11/27 09:57:08 | version 1.238, 2008/09/04 09:37:00 |
|---|---|
| Line 35 static const char * const IDENT_COMMON_C | Line 35 static const char * const IDENT_COMMON_C |
| #include "pa_charsets.h" | #include "pa_charsets.h" |
| #include "pa_http.h" | #include "pa_http.h" |
| #include "pa_request_charsets.h" | #include "pa_request_charsets.h" |
| #include "pcre.h" | |
| // some maybe-undefined constants | // some maybe-undefined constants |
| Line 92 void fix_line_breaks(char *str, size_t& | Line 93 void fix_line_breaks(char *str, size_t& |
| } | } |
| char* file_read_text(Request_charsets& charsets, | char* file_read_text(Request_charsets& charsets, |
| const String& file_spec, | const String& file_spec, |
| bool fail_on_read_problem, | bool fail_on_read_problem, |
| HashStringValue* params/*, HashStringValue* * out_fields*/) { | HashStringValue* params, |
| bool transcode_result) { | |
| File_read_result file= | File_read_result file= |
| file_read(charsets, file_spec, true, params, fail_on_read_problem); | file_read(charsets, file_spec, true, params, fail_on_read_problem, 0, 0, 0, transcode_result); |
| return file.success?file.str:0; | return file.success?file.str:0; |
| } | } |
| Line 124 struct File_read_action_info { | Line 126 struct File_read_action_info { |
| }; | }; |
| #endif | #endif |
| static void file_read_action( | static void file_read_action( |
| struct stat& finfo, | struct stat& finfo, |
| int f, | int f, |
| const String& file_spec, const char* /*fname*/, bool as_text, | const String& file_spec, const char* /*fname*/, bool as_text, |
| void *context) { | void *context) { |
| File_read_action_info& info=*static_cast<File_read_action_info *>(context); | File_read_action_info& info=*static_cast<File_read_action_info *>(context); |
| size_t to_read_size=info.count; | size_t to_read_size=info.count; |
| if(!to_read_size) | if(!to_read_size) |
| Line 142 static void file_read_action( | Line 144 static void file_read_action( |
| *info.data_size=(size_t)read(f, *info.data, to_read_size); | *info.data_size=(size_t)read(f, *info.data, to_read_size); |
| if(ssize_t(*info.data_size)<0 || *info.data_size>to_read_size) | if(ssize_t(*info.data_size)<0 || *info.data_size>to_read_size) |
| throw Exception(0, | throw Exception("file.read", |
| &file_spec, | &file_spec, |
| "read failed: actually read %u bytes count not in [0..%u] valid range", | "read failed: actually read %u bytes count not in [0..%u] valid range", |
| *info.data_size, to_read_size); | *info.data_size, to_read_size); |
| Line 155 static void file_read_action( | Line 157 static void file_read_action( |
| } | } |
| } | } |
| File_read_result file_read(Request_charsets& charsets, const String& file_spec, | File_read_result file_read(Request_charsets& charsets, const String& file_spec, |
| bool as_text, HashStringValue *params, | bool as_text, HashStringValue *params, |
| bool fail_on_read_problem, | bool fail_on_read_problem, |
| char* buf, size_t offset, size_t count) { | char* buf, size_t offset, size_t count, bool transcode_text_result) { |
| File_read_result result={false, 0, 0, 0}; | File_read_result result={false, 0, 0, 0}; |
| if(file_spec.starts_with("http://")) { | if(file_spec.starts_with("http://")) { |
| if(offset || count) | if(offset || count) |
| Line 166 File_read_result file_read(Request_chars | Line 168 File_read_result file_read(Request_chars |
| "offset and load options are not supported for HTTP:// file load"); | "offset and load options are not supported for HTTP:// file load"); |
| // fail on read problem | // fail on read problem |
| File_read_http_result http=pa_internal_file_read_http(charsets, file_spec, as_text, params); | File_read_http_result http=pa_internal_file_read_http(charsets, file_spec, as_text, params, transcode_text_result); |
| result.success=true; | result.success=true; |
| result.str=http.str; | result.str=http.str; |
| result.length=http.length; | result.length=http.length; |
| result.headers=http.headers; | result.headers=http.headers; |
| } else { | } else { |
| if(params) { | if(params){ |
| int valid_options=pa_get_valid_file_options_count(*params); | int valid_options=pa_get_valid_file_options_count(*params); |
| if(valid_options!=params->count()) | if(valid_options!=params->count()) |
| throw Exception(PARSER_RUNTIME, | throw Exception(PARSER_RUNTIME, |
| Line 186 File_read_result file_read(Request_chars | Line 188 File_read_result file_read(Request_chars |
| "read", file_read_action, &info, | "read", file_read_action, &info, |
| as_text, fail_on_read_problem); | as_text, fail_on_read_problem); |
| if(result.length && as_text && params) { | if(as_text && result.success){ |
| if( Value* vcharset_name=params->get(PA_CHARSET_NAME) ) { | if(result.length>=3 && strncmp(result.str, "\xEF\xBB\xBF", 3)==0){ |
| Charset asked_charset=::charsets.get(vcharset_name->as_string(). | // skip UTF-8 signature: EF BB BF (BOM code) |
| change_case(charsets.source(), String::CC_UPPER)); | result.str+=3; |
| result.length-=3; | |
| String::C body=String::C(result.str, result.length); | } |
| body=Charset::transcode(body, asked_charset, charsets.source()); | |
| if(result.length && transcode_text_result && params){ // must be checked because transcode returns CONST string in case length==0, which contradicts hacking few lines below | |
| result.str=const_cast<char *>(body.str); // hacking a little | if( Value* vcharset_name=params->get(PA_CHARSET_NAME) ){ |
| result.length=body.length; | Charset asked_charset=::charsets.get(vcharset_name->as_string(). |
| } | change_case(charsets.source(), String::CC_UPPER)); |
| } | |
| } | |
| if(result.success && as_text) { | String::C body=String::C(result.str, result.length); |
| // UTF-8 signature: EF BB BF | body=Charset::transcode(body, asked_charset, charsets.source()); |
| if(result.length>=3) { | |
| char *in=(char *)result.str; | result.str=const_cast<char*>(body.str); // hacking a little |
| if(strncmp(in, "\xEF\xBB\xBF", 3)==0) { | result.length=body.length; |
| result.str=in+3; result.length-=3;// skip prefix | } |
| } | } |
| } | } |
| fix_line_breaks((char *)(result.str), result.length); | |
| } | } |
| if(as_text && result.length) | |
| fix_line_breaks(result.str, result.length); | |
| return result; | return result; |
| } | } |
| Line 248 bool file_read_action_under_lock(const S | Line 249 bool file_read_action_under_lock(const S |
| // they delay update till open, so we would receive "!^test[" string | // they delay update till open, so we would receive "!^test[" string |
| // if would do stat, next open. | // if would do stat, next open. |
| // later: it seems, even this does not help sometimes | // later: it seems, even this does not help sometimes |
| if((f=open(fname, O_RDONLY|(as_text?_O_TEXT:_O_BINARY)))>=0) { | if((f=open(fname, O_RDONLY|(as_text?_O_TEXT:_O_BINARY)))>=0) { |
| try { | try { |
| if(pa_lock_shared_blocking(f)!=0) | if(pa_lock_shared_blocking(f)!=0) |
| throw Exception("file.lock", | throw Exception("file.lock", |
| Line 277 bool file_read_action_under_lock(const S | Line 278 bool file_read_action_under_lock(const S |
| pa_unlock(f);close(f); | pa_unlock(f);close(f); |
| return true; | return true; |
| } else { | } else { |
| if(fail_on_read_problem) | if(fail_on_read_problem) |
| throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0, | throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0, |
| &file_spec, | &file_spec, |
| Line 360 static void file_write_action(int f, voi | Line 361 static void file_write_action(int f, voi |
| if(info.length) { | if(info.length) { |
| int written=write(f, info.str, info.length); | int written=write(f, info.str, info.length); |
| if(written<0) | if(written<0) |
| throw Exception(0, | throw Exception("file.access", |
| 0, | 0, |
| "write failed: %s (%d)", strerror(errno), errno); | "write failed: %s (%d)", strerror(errno), errno); |
| } | } |
| Line 468 bool file_executable(const String& file_ | Line 469 bool file_executable(const String& file_ |
| } | } |
| bool file_stat(const String& file_spec, | bool file_stat(const String& file_spec, |
| size_t& rsize, | size_t& rsize, |
| time_t& ratime, | time_t& ratime, |
| time_t& rmtime, | time_t& rmtime, |
| time_t& rctime, | time_t& rctime, |
| bool fail_on_read_problem) { | bool fail_on_read_problem) { |
| const char* fname=file_spec.cstr(String::L_FILE_SPEC); | const char* fname=file_spec.cstr(String::L_FILE_SPEC); |
| struct stat finfo; | struct stat finfo; |
| if(stat(fname, &finfo)!=0) | if(stat(fname, &finfo)!=0) |
| Line 491 bool file_stat(const String& file_spec, | Line 492 bool file_stat(const String& file_spec, |
| } | } |
| char* getrow(char* *row_ref, char delim) { | char* getrow(char* *row_ref, char delim) { |
| char* result=*row_ref; | char* result=*row_ref; |
| if(result) { | if(result) { |
| *row_ref=strchr(result, delim); | *row_ref=strchr(result, delim); |
| if(*row_ref) | if(*row_ref) |
| *((*row_ref)++)=0; | *((*row_ref)++)=0; |
| else if(!*result) | else if(!*result) |
| return 0; | return 0; |
| } | } |
| return result; | return result; |
| } | } |
| char* lsplit(char* string, char delim) { | char* lsplit(char* string, char delim) { |
| if(string) { | if(string) { |
| char* v=strchr(string, delim); | char* v=strchr(string, delim); |
| if(v) { | if(v) { |
| *v=0; | *v=0; |
| return v+1; | return v+1; |
| } | } |
| } | } |
| return 0; | return 0; |
| } | } |
| char* lsplit(char* *string_ref, char delim) { | char* lsplit(char* *string_ref, char delim) { |
| char* result=*string_ref; | char* result=*string_ref; |
| char* next=lsplit(*string_ref, delim); | char* next=lsplit(*string_ref, delim); |
| *string_ref=next; | *string_ref=next; |
| return result; | return result; |
| } | } |
| char* rsplit(char* string, char delim) { | char* rsplit(char* string, char delim) { |
| if(string) { | if(string) { |
| char* v=strrchr(string, delim); | char* v=strrchr(string, delim); |
| if(v) { | if(v) { |
| *v=0; | *v=0; |
| return v+1; | return v+1; |
| } | } |
| } | } |
| return NULL; | return NULL; |
| } | } |
| /// @todo less stupid type detection | |
| // format: %[flags][width][.precision]type http://msdn.microsoft.com/ru-ru/library/56e442dc(en-us,VS.80).aspx | |
| // flags: '-', '+', ' ', '#', '0' http://msdn.microsoft.com/ru-ru/library/8aky45ct(en-us,VS.80).aspx | |
| // width, precision: non negative decimal number | |
| enum FormatType { | |
| FormatInvalid, | |
| FormatInt, | |
| FormatUInt, | |
| FormatDouble | |
| }; | |
| FormatType format_type(char* fmt){ | |
| enum FormatState { | |
| Percent, | |
| Flags, | |
| Width, | |
| Precision, | |
| Done | |
| } state=Percent; | |
| FormatType result=FormatInvalid; | |
| char* pos=fmt; | |
| while(char c=*(pos++)){ | |
| switch(state){ | |
| case Percent: | |
| if(c=='%'){ | |
| state=Flags; | |
| } else { | |
| return FormatInvalid; // 1st char must be '%' only | |
| } | |
| break; | |
| case Flags: | |
| if(strchr("-+ #0", c)!=0){ | |
| break; | |
| } | |
| // go to the next step | |
| case Width: | |
| if(c=='.'){ | |
| state=Precision; | |
| break; | |
| } | |
| // go to the next step | |
| case Precision: | |
| if(c>='0' && c<='9'){ | |
| if(state == Flags) state=Width; // no more flags | |
| break; | |
| } else if(c=='d' || c=='i'){ | |
| result=FormatInt; | |
| } else if(strchr("feEgG", c)!=0){ | |
| result=FormatDouble; | |
| } else if(strchr("uoxX", c)!=0){ | |
| result=FormatUInt; | |
| } else { | |
| return FormatInvalid; // invalid char | |
| } | |
| state=Done; | |
| break; | |
| case Done: | |
| return FormatInvalid; // no chars allowed after 'type' | |
| } | |
| } | |
| return result; | |
| } | |
| const char* format(double value, char* fmt) { | const char* format(double value, char* fmt) { |
| char local_buf[MAX_NUMBER]; | char local_buf[MAX_NUMBER]; |
| size_t size; | int size=-1; |
| if(fmt) | if(fmt && strlen(fmt)){ |
| if(strpbrk(fmt, "diouxX")) | switch(format_type(fmt)){ |
| if(strpbrk(fmt, "ouxX")) | case FormatDouble: |
| size=snprintf(local_buf, sizeof(local_buf), fmt, (uint)value); | size=snprintf(local_buf, sizeof(local_buf), fmt, value); |
| else | break; |
| case FormatInt: | |
| size=snprintf(local_buf, sizeof(local_buf), fmt, (int)value); | size=snprintf(local_buf, sizeof(local_buf), fmt, (int)value); |
| else | break; |
| size=snprintf(local_buf, sizeof(local_buf), fmt, value); | case FormatUInt: |
| else | size=snprintf(local_buf, sizeof(local_buf), fmt, (uint)value); |
| size=snprintf(local_buf, sizeof(local_buf), "%d", (int)value); | break; |
| case FormatInvalid: | |
| return pa_strdup(local_buf, size); | throw Exception(PARSER_RUNTIME, |
| 0, | |
| "Incorrect format string '%s' was specified.", fmt); | |
| } | |
| } else | |
| size=snprintf(local_buf, sizeof(local_buf), "%d", (int)value); | |
| if(size < 0 || size >= MAX_NUMBER-1){ // on win32 we manually reduce max size while printing | |
| throw Exception(PARSER_RUNTIME, | |
| 0, | |
| "Error occure white executing snprintf with format string '%s'.", fmt); | |
| } | |
| return pa_strdup(local_buf, (size_t)size); | |
| } | } |
| size_t stdout_write(const void *buf, size_t size) { | size_t stdout_write(const void *buf, size_t size) { |
| Line 567 size_t stdout_write(const void *buf, siz | Line 646 size_t stdout_write(const void *buf, siz |
| #endif | #endif |
| } | } |
| char* unescape_chars(const char* cp, int len) { | enum EscapeState { |
| char* s=new(PointerFreeGC) char[len + 1]; | EscapeRest, |
| enum EscapeState { | EscapeFirst, |
| EscapeRest, | EscapeSecond, |
| EscapeFirst, | EscapeUnicode |
| EscapeSecond | }; |
| } escapeState=EscapeRest; | |
| // @todo prescan for reduce required size (unescaped sting in 1 byte charset requires less memory usually) | |
| char* unescape_chars(const char* cp, int len, Charset* charset, bool ignore_plus){ | |
| char* s=new(PointerFreeGC) char[len+1]; // must be enough (%uXXXX==6 bytes, max utf-8 char length==6 bytes) | |
| char* dst=s; | |
| EscapeState escapeState=EscapeRest; | |
| uint escapedValue=0; | uint escapedValue=0; |
| int srcPos=0; | int srcPos=0; |
| int dstPos=0; | short int jsCnt=0; |
| while(srcPos < len) { | while(srcPos<len){ |
| uchar ch=(uchar)cp[srcPos]; | uchar c=(uchar)cp[srcPos]; |
| switch(escapeState) { | if(c=='%'){ |
| case EscapeRest: | escapeState=EscapeFirst; |
| if(ch=='%') { | } else { |
| escapeState=EscapeFirst; | switch(escapeState) { |
| } else if(ch=='+') { | case EscapeRest: |
| s[dstPos++]=' '; | if(!ignore_plus && c=='+'){ |
| } else { | *dst++=' '; |
| s[dstPos++]=ch; | } else { |
| *dst++=c; | |
| } | |
| break; | |
| case EscapeFirst: | |
| if(charset && c=='u'){ | |
| // escaped unicode value: %u0430 | |
| jsCnt=0; | |
| escapedValue=0; | |
| escapeState=EscapeUnicode; | |
| } else { | |
| if(isxdigit(c)){ | |
| escapedValue=hex_value[c] << 4; | |
| escapeState=EscapeSecond; | |
| } else { | |
| *dst++=c; | |
| escapeState=EscapeRest; | |
| } | |
| } | |
| break; | |
| case EscapeSecond: | |
| if(isxdigit(c)){ | |
| escapedValue+=hex_value[c]; | |
| *dst++=(char)escapedValue; | |
| } | |
| escapeState=EscapeRest; | |
| break; | |
| case EscapeUnicode: | |
| if(isxdigit(c)){ | |
| escapedValue=(escapedValue << 4) + hex_value[c]; | |
| if(++jsCnt==4){ | |
| // transcode utf8 char to client charset (we can lost some chars here) | |
| charset->store_Char((XMLByte*&)dst, (XMLCh)escapedValue, '?'); | |
| escapeState=EscapeRest; | |
| } | |
| } else { | |
| // not full unicode value | |
| escapeState=EscapeRest; | |
| } | |
| break; | |
| } | } |
| break; | |
| case EscapeFirst: | |
| escapedValue=hex_value[ch] << 4; | |
| escapeState=EscapeSecond; | |
| break; | |
| case EscapeSecond: | |
| escapedValue +=hex_value[ch]; | |
| s[dstPos++]=(char)escapedValue; | |
| escapeState=EscapeRest; | |
| break; | |
| } | } |
| srcPos++; | |
| srcPos++; | |
| } | } |
| s[dstPos]=0; | |
| *dst=0; // zero-termination | |
| return s; | return s; |
| } | } |
| Line 622 void slashes_to_back_slashes(char* s) { | Line 737 void slashes_to_back_slashes(char* s) { |
| */ | */ |
| #endif | #endif |
| bool StrEqNc(const char* s1, const char* s2, bool strict) { | bool StrStartFromNC(const char* str, const char* substr, bool equal){ |
| while(true) { | while(true) { |
| if(!(*s1)) { | if(!(*substr)){ |
| if(!(*s2)) | if(!(*str)) |
| return true; | return true; |
| else | else |
| return !strict; | return !equal; |
| } else if(!(*s2)) | } |
| return !strict; | if(!(*str)) |
| if(isalpha((unsigned char)*s1)) { | return false; |
| if(tolower((unsigned char)*s1) !=tolower((unsigned char)*s2)) | if(isalpha((unsigned char)*str)) { |
| if(tolower((unsigned char)*str)!=tolower((unsigned char)*substr)) | |
| return false; | return false; |
| } else if((*s1) !=(*s2)) | } else if((*str) != (*substr)) |
| return false; | return false; |
| s1++; | str++; |
| s2++; | substr++; |
| } | } |
| } | } |
| size_t strpos(const char *str, const char *substr) { | |
| const char *p = strstr(str, substr); | |
| return (p==0)?STRING_NOT_FOUND:p-str; | |
| } | |
| // content-type: xxx; charset=WE-NEED-THIS | |
| // content-type: xxx; charset="WE-NEED-THIS" | |
| // content-type: xxx; charset="WE-NEED-THIS"; | |
| Charset* detect_charset(const char* content_type){ | |
| if(content_type){ | |
| size_t len=strlen(content_type); | |
| char* CONTENT_TYPE=new(PointerFreeGC) char[len+1]; | |
| memcpy(CONTENT_TYPE, content_type, len); | |
| for(char *p=CONTENT_TYPE; *p; p++) | |
| *p=(char)toupper((unsigned char)*p); | |
| if(const char* begin=strstr(CONTENT_TYPE, "CHARSET=")){ | |
| begin+=8; // skip "CHARSET=" | |
| char* end=0; | |
| if(*begin && (*begin=='"' || *begin =='\'')){ | |
| char quote=*begin; | |
| begin++; | |
| end=(char*)strchr(begin, quote); | |
| } | |
| if(!end) | |
| end=(char*)strchr(begin, ';'); | |
| size_t len; | |
| if(end){ | |
| *end=0; // terminator | |
| len=end-begin; | |
| } else { | |
| len=strlen(begin); | |
| } | |
| String::Body NAME=String::Body(begin, len); | |
| return &charsets.get(NAME); | |
| } | |
| } | |
| return 0; | |
| } | |
| Charset* detect_charset(Charset& source_charset, const String& content_type){ | |
| const String& CONTENT_TYPE=content_type.change_case(source_charset, String::CC_UPPER); | |
| size_t begin=CONTENT_TYPE.pos("CHARSET="); | |
| if(begin!=STRING_NOT_FOUND) { | |
| begin+=8; // skip "CHARSET=" | |
| size_t end=STRING_NOT_FOUND; | |
| if(CONTENT_TYPE.pos('"', begin)==begin){ | |
| begin++; | |
| end=CONTENT_TYPE.pos('"', begin); | |
| } else if(CONTENT_TYPE.pos('\'', begin)==begin){ | |
| begin++; | |
| end=CONTENT_TYPE.pos('\'', begin); | |
| } | |
| if(end==STRING_NOT_FOUND) | |
| end=CONTENT_TYPE.pos(';', begin); | |
| if(end==STRING_NOT_FOUND) | |
| end=CONTENT_TYPE.length(); | |
| const String::Body NAME=CONTENT_TYPE.mid(begin, end); | |
| return &charsets.get(NAME); | |
| } | |
| return 0; | |
| } | |
| static bool isLeap(int year) { | static bool isLeap(int year) { |
| return !( | return !( |
| (year % 4) || ((year % 400) && !(year % 100)) | (year % 4) || ((year % 400) && !(year % 100)) |
| ); | ); |
| } | } |
| int getMonthDays(int year, int month) { | int getMonthDays(int year, int month) { |
| static int monthDays[]={ | static int monthDays[]={ |
| 31, | 31, |
| 28, | 28, |
| 31, | 31, |
| 30, | 30, |
| 31, | 31, |
| 30, | 30, |
| 31, | 31, |
| 31, | 31, |
| 30, | 30, |
| 31, | 31, |
| 30, | 30, |
| 31 | 31 |
| }; | }; |
| return (month == 2 && isLeap(year)) ? 29 : monthDays[month]; | return (month == 1 /* january -- 0 */ && isLeap(year)) ? 29 : monthDays[month]; |
| } | } |
| int remove_crlf(char* start, char* end) { | int remove_crlf(char* start, char* end) { |
| Line 1020 char* pa_base64_encode(const String& fil | Line 1206 char* pa_base64_encode(const String& fil |
| static void file_base64_file_action( | static void file_base64_file_action( |
| struct stat& finfo, | struct stat& finfo, |
| int f, | int f, |
| const String&, const char* /*fname*/, bool, | const String&, const char* /*fname*/, bool, |
| void *context) { | void *context) { |
| if(finfo.st_size) { | if(finfo.st_size) { |
| File_base64_action_info& info=*static_cast<File_base64_action_info *>(context); | File_base64_action_info& info=*static_cast<File_base64_action_info *>(context); |
| Line 1061 void pa_base64_decode(const char *in, si | Line 1247 void pa_base64_decode(const char *in, si |
| int file_block_read(const int f, unsigned char* buffer, const size_t size){ | int file_block_read(const int f, unsigned char* buffer, const size_t size){ |
| int nCount = read(f, buffer, size); | int nCount = read(f, buffer, size); |
| if (nCount < 0) | if (nCount < 0) |
| throw Exception(0, | throw Exception("file.read", |
| 0, | 0, |
| "read failed: %s (%d)", strerror(errno), errno); | "read failed: %s (%d)", strerror(errno), errno); |
| return nCount; | return nCount; |
| Line 1085 const unsigned long pa_crc32(const Strin | Line 1271 const unsigned long pa_crc32(const Strin |
| } | } |
| static void file_crc32_file_action( | static void file_crc32_file_action( |
| struct stat& finfo, | struct stat& finfo, |
| int f, | int f, |
| const String&, const char* /*fname*/, bool, | const String&, const char* /*fname*/, bool, |
| void *context) | void *context) |
| { | { |
| unsigned long& crc32=*static_cast<unsigned long *>(context); | unsigned long& crc32=*static_cast<unsigned long *>(context); |
| if(finfo.st_size) { | if(finfo.st_size) { |
| Line 1102 static void file_crc32_file_action( | Line 1288 static void file_crc32_file_action( |
| } | } |
| } | } |
| char* print_pcre_exec_error_text(int exec_result){ | |
| switch(exec_result){ | |
| case PCRE_ERROR_BADUTF8: | |
| return "validation of UTF-8 string failed while pcre_exec (%d)."; | |
| break; | |
| default: | |
| return "regular expression execute error (%d)"; | |
| } | |
| } | |