1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | http://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Ed Batutis <ed@batutis.com> |
14 +----------------------------------------------------------------------+
15 */
16
17 /* {{{ includes */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include <php.h>
23 #include "grapheme.h"
24 #include "grapheme_util.h"
25
26 #include <unicode/utypes.h>
27 #include <unicode/utf8.h>
28 #include <unicode/ucol.h>
29 #include <unicode/ustring.h>
30 #include <unicode/ubrk.h>
31
32 #include "ext/standard/php_string.h"
33
34 /* }}} */
35
36 #define GRAPHEME_EXTRACT_TYPE_COUNT 0
37 #define GRAPHEME_EXTRACT_TYPE_MAXBYTES 1
38 #define GRAPHEME_EXTRACT_TYPE_MAXCHARS 2
39 #define GRAPHEME_EXTRACT_TYPE_MIN GRAPHEME_EXTRACT_TYPE_COUNT
40 #define GRAPHEME_EXTRACT_TYPE_MAX GRAPHEME_EXTRACT_TYPE_MAXCHARS
41
42
43 /* {{{ grapheme_register_constants
44 * Register API constants
45 */
grapheme_register_constants(INIT_FUNC_ARGS)46 void grapheme_register_constants( INIT_FUNC_ARGS )
47 {
48 REGISTER_LONG_CONSTANT("GRAPHEME_EXTR_COUNT", GRAPHEME_EXTRACT_TYPE_COUNT, CONST_CS | CONST_PERSISTENT);
49 REGISTER_LONG_CONSTANT("GRAPHEME_EXTR_MAXBYTES", GRAPHEME_EXTRACT_TYPE_MAXBYTES, CONST_CS | CONST_PERSISTENT);
50 REGISTER_LONG_CONSTANT("GRAPHEME_EXTR_MAXCHARS", GRAPHEME_EXTRACT_TYPE_MAXCHARS, CONST_CS | CONST_PERSISTENT);
51 }
52 /* }}} */
53
54 /* {{{ proto size_t grapheme_strlen(string str)
55 Get number of graphemes in a string */
PHP_FUNCTION(grapheme_strlen)56 PHP_FUNCTION(grapheme_strlen)
57 {
58 char* string;
59 size_t string_len;
60 UChar* ustring = NULL;
61 int ustring_len = 0;
62 zend_long ret_len;
63 UErrorCode status;
64
65 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &string, &string_len) == FAILURE) {
66 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
67 "grapheme_strlen: unable to parse input param", 0 );
68 RETURN_FALSE;
69 }
70
71 ret_len = grapheme_ascii_check((unsigned char *)string, string_len);
72
73 if ( ret_len >= 0 )
74 RETURN_LONG(string_len);
75
76 /* convert the string to UTF-16. */
77 status = U_ZERO_ERROR;
78 intl_convert_utf8_to_utf16(&ustring, &ustring_len, string, string_len, &status );
79
80 if ( U_FAILURE( status ) ) {
81 /* Set global error code. */
82 intl_error_set_code( NULL, status );
83
84 /* Set error messages. */
85 intl_error_set_custom_msg( NULL, "Error converting input string to UTF-16", 0 );
86 if (ustring) {
87 efree( ustring );
88 }
89 RETURN_NULL();
90 }
91
92 ret_len = grapheme_split_string(ustring, ustring_len, NULL, 0 );
93
94 if (ustring) {
95 efree( ustring );
96 }
97
98 if (ret_len >= 0) {
99 RETVAL_LONG(ret_len);
100 } else {
101 RETVAL_FALSE;
102 }
103 }
104 /* }}} */
105
106 /* {{{ proto int grapheme_strpos(string haystack, string needle [, int offset ])
107 Find position of first occurrence of a string within another */
PHP_FUNCTION(grapheme_strpos)108 PHP_FUNCTION(grapheme_strpos)
109 {
110 char *haystack, *needle;
111 size_t haystack_len, needle_len;
112 const char *found;
113 zend_long loffset = 0;
114 int32_t offset = 0;
115 size_t noffset = 0;
116 zend_long ret_pos;
117
118 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|l", &haystack, &haystack_len, &needle, &needle_len, &loffset) == FAILURE) {
119 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
120 "grapheme_strpos: unable to parse input param", 0 );
121 RETURN_FALSE;
122 }
123
124 if ( OUTSIDE_STRING(loffset, haystack_len) ) {
125 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_strpos: Offset not contained in string", 1 );
126 RETURN_FALSE;
127 }
128
129 /* we checked that it will fit: */
130 offset = (int32_t) loffset;
131 noffset = offset >= 0 ? offset : (int32_t)haystack_len + offset;
132
133 /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */
134
135 if (needle_len == 0) {
136 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_strpos: Empty delimiter", 1 );
137 RETURN_FALSE;
138 }
139
140 if (offset >= 0) {
141 /* quick check to see if the string might be there
142 * I realize that 'offset' is 'grapheme count offset' but will work in spite of that
143 */
144 found = php_memnstr(haystack + noffset, needle, needle_len, haystack + haystack_len);
145
146 /* if it isn't there the we are done */
147 if (!found) {
148 RETURN_FALSE;
149 }
150
151 /* if it is there, and if the haystack is ascii, we are all done */
152 if ( grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0 ) {
153 RETURN_LONG(found - haystack);
154 }
155 }
156
157 /* do utf16 part of the strpos */
158 ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, 0 /* fIgnoreCase */, 0 /* last */ );
159
160 if ( ret_pos >= 0 ) {
161 RETURN_LONG(ret_pos);
162 } else {
163 RETURN_FALSE;
164 }
165
166 }
167 /* }}} */
168
169 /* {{{ proto int grapheme_stripos(string haystack, string needle [, int offset ])
170 Find position of first occurrence of a string within another, ignoring case differences */
PHP_FUNCTION(grapheme_stripos)171 PHP_FUNCTION(grapheme_stripos)
172 {
173 char *haystack, *needle;
174 size_t haystack_len, needle_len;
175 const char *found;
176 zend_long loffset = 0;
177 int32_t offset = 0;
178 zend_long ret_pos;
179 int is_ascii;
180
181 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|l", &haystack, &haystack_len, &needle, &needle_len, &loffset) == FAILURE) {
182 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
183 "grapheme_stripos: unable to parse input param", 0 );
184 RETURN_FALSE;
185 }
186
187 if ( OUTSIDE_STRING(loffset, haystack_len) ) {
188 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_stripos: Offset not contained in string", 1 );
189 RETURN_FALSE;
190 }
191
192 /* we checked that it will fit: */
193 offset = (int32_t) loffset;
194
195 /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */
196
197 if (needle_len == 0) {
198 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_stripos: Empty delimiter", 1 );
199 RETURN_FALSE;
200 }
201
202 is_ascii = ( grapheme_ascii_check((unsigned char*)haystack, haystack_len) >= 0 );
203
204 if ( is_ascii ) {
205 char *haystack_dup, *needle_dup;
206 int32_t noffset = offset >= 0 ? offset : (int32_t)haystack_len + offset;
207 needle_dup = estrndup(needle, needle_len);
208 php_strtolower(needle_dup, needle_len);
209 haystack_dup = estrndup(haystack, haystack_len);
210 php_strtolower(haystack_dup, haystack_len);
211
212 found = php_memnstr(haystack_dup + noffset, needle_dup, needle_len, haystack_dup + haystack_len);
213
214 efree(haystack_dup);
215 efree(needle_dup);
216
217 if (found) {
218 RETURN_LONG(found - haystack_dup);
219 }
220
221 /* if needle was ascii too, we are all done, otherwise we need to try using Unicode to see what we get */
222 if ( grapheme_ascii_check((unsigned char *)needle, needle_len) >= 0 ) {
223 RETURN_FALSE;
224 }
225 }
226
227 /* do utf16 part of the strpos */
228 ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, 1 /* fIgnoreCase */, 0 /*last */ );
229
230 if ( ret_pos >= 0 ) {
231 RETURN_LONG(ret_pos);
232 } else {
233 RETURN_FALSE;
234 }
235
236 }
237 /* }}} */
238
239 /* {{{ proto int grapheme_strrpos(string haystack, string needle [, int offset])
240 Find position of last occurrence of a string within another */
PHP_FUNCTION(grapheme_strrpos)241 PHP_FUNCTION(grapheme_strrpos)
242 {
243 char *haystack, *needle;
244 size_t haystack_len, needle_len;
245 zend_long loffset = 0;
246 int32_t offset = 0;
247 zend_long ret_pos;
248 int is_ascii;
249
250 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|l", &haystack, &haystack_len, &needle, &needle_len, &loffset) == FAILURE) {
251 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
252 "grapheme_strrpos: unable to parse input param", 0 );
253 RETURN_FALSE;
254 }
255
256 if ( OUTSIDE_STRING(loffset, haystack_len) ) {
257 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_strpos: Offset not contained in string", 1 );
258 RETURN_FALSE;
259 }
260
261 /* we checked that it will fit: */
262 offset = (int32_t) loffset;
263
264 /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */
265
266 if (needle_len == 0) {
267 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_strpos: Empty delimiter", 1 );
268 RETURN_FALSE;
269 }
270
271 is_ascii = grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0;
272
273 if ( is_ascii ) {
274
275 ret_pos = grapheme_strrpos_ascii(haystack, haystack_len, needle, needle_len, offset);
276
277 if ( ret_pos >= 0 ) {
278 RETURN_LONG(ret_pos);
279 }
280
281 /* if the needle was ascii too, we are done */
282
283 if ( grapheme_ascii_check((unsigned char *)needle, needle_len) >= 0 ) {
284 RETURN_FALSE;
285 }
286
287 /* else we need to continue via utf16 */
288 }
289
290 ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, 0 /* f_ignore_case */, 1/* last */);
291
292 if ( ret_pos >= 0 ) {
293 RETURN_LONG(ret_pos);
294 } else {
295 RETURN_FALSE;
296 }
297
298
299 }
300 /* }}} */
301
302 /* {{{ proto int grapheme_strripos(string haystack, string needle [, int offset])
303 Find position of last occurrence of a string within another, ignoring case */
PHP_FUNCTION(grapheme_strripos)304 PHP_FUNCTION(grapheme_strripos)
305 {
306 char *haystack, *needle;
307 size_t haystack_len, needle_len;
308 zend_long loffset = 0;
309 int32_t offset = 0;
310 zend_long ret_pos;
311 int is_ascii;
312
313 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|l", &haystack, &haystack_len, &needle, &needle_len, &loffset) == FAILURE) {
314 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
315 "grapheme_strrpos: unable to parse input param", 0 );
316 RETURN_FALSE;
317 }
318
319 if ( OUTSIDE_STRING(loffset, haystack_len) ) {
320 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_strpos: Offset not contained in string", 1 );
321 RETURN_FALSE;
322 }
323
324 /* we checked that it will fit: */
325 offset = (int32_t) loffset;
326
327 /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */
328
329 if (needle_len == 0) {
330 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_strpos: Empty delimiter", 1 );
331 RETURN_FALSE;
332 }
333
334 is_ascii = grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0;
335
336 if ( is_ascii ) {
337 char *needle_dup, *haystack_dup;
338
339 needle_dup = estrndup(needle, needle_len);
340 php_strtolower(needle_dup, needle_len);
341 haystack_dup = estrndup(haystack, haystack_len);
342 php_strtolower(haystack_dup, haystack_len);
343
344 ret_pos = grapheme_strrpos_ascii(haystack_dup, haystack_len, needle_dup, needle_len, offset);
345
346 efree(haystack_dup);
347 efree(needle_dup);
348
349 if ( ret_pos >= 0 ) {
350 RETURN_LONG(ret_pos);
351 }
352
353 /* if the needle was ascii too, we are done */
354
355 if ( grapheme_ascii_check((unsigned char *)needle, needle_len) >= 0 ) {
356 RETURN_FALSE;
357 }
358
359 /* else we need to continue via utf16 */
360 }
361
362 ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, offset, NULL, 1 /* f_ignore_case */, 1 /*last */);
363
364 if ( ret_pos >= 0 ) {
365 RETURN_LONG(ret_pos);
366 } else {
367 RETURN_FALSE;
368 }
369
370
371 }
372 /* }}} */
373
374 /* {{{ proto string grapheme_substr(string str, int start [, int length])
375 Returns part of a string */
PHP_FUNCTION(grapheme_substr)376 PHP_FUNCTION(grapheme_substr)
377 {
378 char *str;
379 zend_string *u8_sub_str;
380 UChar *ustr;
381 size_t str_len;
382 int32_t ustr_len;
383 zend_long lstart = 0, length = 0;
384 int32_t start = 0;
385 int iter_val;
386 UErrorCode status;
387 unsigned char u_break_iterator_buffer[U_BRK_SAFECLONE_BUFFERSIZE];
388 UBreakIterator* bi = NULL;
389 int sub_str_start_pos, sub_str_end_pos;
390 int32_t (*iter_func)(UBreakIterator *);
391 zend_bool no_length = 1;
392
393 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|l!", &str, &str_len, &lstart, &length, &no_length) == FAILURE) {
394 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
395 "grapheme_substr: unable to parse input param", 0 );
396 RETURN_FALSE;
397 }
398
399 if ( OUTSIDE_STRING(lstart, str_len)) {
400 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_substr: start not contained in string", 1 );
401 RETURN_FALSE;
402 }
403
404 /* we checked that it will fit: */
405 start = (int32_t) lstart;
406
407 if(no_length) {
408 length = str_len;
409 }
410
411 if(length < INT32_MIN) {
412 length = INT32_MIN;
413 } else if(length > INT32_MAX) {
414 length = INT32_MAX;
415 }
416
417 /* the offset is 'grapheme count offset' so it still might be invalid - we'll check it later */
418
419 if ( grapheme_ascii_check((unsigned char *)str, str_len) >= 0 ) {
420 int32_t asub_str_len;
421 char *sub_str;
422 grapheme_substr_ascii(str, str_len, start, (int32_t)length, &sub_str, &asub_str_len);
423
424 if ( NULL == sub_str ) {
425 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_substr: invalid parameters", 1 );
426 RETURN_FALSE;
427 }
428
429 RETURN_STRINGL(sub_str, asub_str_len);
430 }
431
432 ustr = NULL;
433 ustr_len = 0;
434 status = U_ZERO_ERROR;
435 intl_convert_utf8_to_utf16(&ustr, &ustr_len, str, str_len, &status);
436
437 if ( U_FAILURE( status ) ) {
438 /* Set global error code. */
439 intl_error_set_code( NULL, status );
440
441 /* Set error messages. */
442 intl_error_set_custom_msg( NULL, "Error converting input string to UTF-16", 0 );
443 if (ustr) {
444 efree( ustr );
445 }
446 RETURN_FALSE;
447 }
448
449 bi = grapheme_get_break_iterator((void*)u_break_iterator_buffer, &status );
450
451 if( U_FAILURE(status) ) {
452 RETURN_FALSE;
453 }
454
455 ubrk_setText(bi, ustr, ustr_len, &status);
456
457 if ( start < 0 ) {
458 iter_func = ubrk_previous;
459 ubrk_last(bi);
460 iter_val = 1;
461 }
462 else {
463 iter_func = ubrk_next;
464 iter_val = -1;
465 }
466
467 sub_str_start_pos = 0;
468
469 while ( start ) {
470 sub_str_start_pos = iter_func(bi);
471
472 if ( UBRK_DONE == sub_str_start_pos ) {
473 break;
474 }
475
476 start += iter_val;
477 }
478
479 if ( 0 != start || sub_str_start_pos >= ustr_len ) {
480
481 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_substr: start not contained in string", 1 );
482
483 if (ustr) {
484 efree(ustr);
485 }
486 ubrk_close(bi);
487 RETURN_FALSE;
488 }
489
490 /* OK to convert here since if str_len were big, convert above would fail */
491 if (length >= (int32_t)str_len) {
492
493 /* no length supplied or length is too big, return the rest of the string */
494
495 status = U_ZERO_ERROR;
496 u8_sub_str = intl_convert_utf16_to_utf8(ustr + sub_str_start_pos, ustr_len - sub_str_start_pos, &status);
497
498 if (ustr) {
499 efree( ustr );
500 }
501 ubrk_close( bi );
502
503 if ( !u8_sub_str ) {
504 /* Set global error code. */
505 intl_error_set_code( NULL, status );
506
507 /* Set error messages. */
508 intl_error_set_custom_msg( NULL, "Error converting output string to UTF-8", 0 );
509
510 RETURN_FALSE;
511 }
512
513 /* return the allocated string, not a duplicate */
514 RETVAL_NEW_STR(u8_sub_str);
515 return;
516 }
517
518 if(length == 0) {
519 /* empty length - we've validated start, we can return "" now */
520 if (ustr) {
521 efree(ustr);
522 }
523 ubrk_close(bi);
524 RETURN_EMPTY_STRING();
525 }
526
527 /* find the end point of the string to return */
528
529 if ( length < 0 ) {
530 iter_func = ubrk_previous;
531 ubrk_last(bi);
532 iter_val = 1;
533 }
534 else {
535 iter_func = ubrk_next;
536 iter_val = -1;
537 }
538
539 sub_str_end_pos = 0;
540
541 while ( length ) {
542 sub_str_end_pos = iter_func(bi);
543
544 if ( UBRK_DONE == sub_str_end_pos ) {
545 break;
546 }
547
548 length += iter_val;
549 }
550
551 ubrk_close(bi);
552
553 if ( UBRK_DONE == sub_str_end_pos) {
554 if(length < 0) {
555 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_substr: length not contained in string", 1 );
556
557 efree(ustr);
558 RETURN_FALSE;
559 } else {
560 sub_str_end_pos = ustr_len;
561 }
562 }
563
564 if(sub_str_start_pos > sub_str_end_pos) {
565 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_substr: length is beyond start", 1 );
566
567 efree(ustr);
568 RETURN_FALSE;
569 }
570
571 status = U_ZERO_ERROR;
572 u8_sub_str = intl_convert_utf16_to_utf8(ustr + sub_str_start_pos, ( sub_str_end_pos - sub_str_start_pos ), &status);
573
574 efree( ustr );
575
576 if ( !u8_sub_str ) {
577 /* Set global error code. */
578 intl_error_set_code( NULL, status );
579
580 /* Set error messages. */
581 intl_error_set_custom_msg( NULL, "Error converting output string to UTF-8", 0 );
582
583 RETURN_FALSE;
584 }
585
586 /* return the allocated string, not a duplicate */
587 RETVAL_NEW_STR(u8_sub_str);
588 }
589 /* }}} */
590
591 /* {{{ strstr_common_handler */
strstr_common_handler(INTERNAL_FUNCTION_PARAMETERS,int f_ignore_case)592 static void strstr_common_handler(INTERNAL_FUNCTION_PARAMETERS, int f_ignore_case)
593 {
594 char *haystack, *needle;
595 const char *found;
596 size_t haystack_len, needle_len;
597 int32_t ret_pos, uchar_pos;
598 zend_bool part = 0;
599
600 if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|b", &haystack, &haystack_len, &needle, &needle_len, &part) == FAILURE) {
601
602 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
603 "grapheme_strstr: unable to parse input param", 0 );
604
605 RETURN_FALSE;
606 }
607
608 if (needle_len == 0) {
609
610 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_strpos: Empty delimiter", 1 );
611
612 RETURN_FALSE;
613 }
614
615
616 if ( !f_ignore_case ) {
617
618 /* ASCII optimization: quick check to see if the string might be there
619 * I realize that 'offset' is 'grapheme count offset' but will work in spite of that
620 */
621 found = php_memnstr(haystack, needle, needle_len, haystack + haystack_len);
622
623 /* if it isn't there the we are done */
624 if ( !found ) {
625 RETURN_FALSE;
626 }
627
628 /* if it is there, and if the haystack is ascii, we are all done */
629 if ( grapheme_ascii_check((unsigned char *)haystack, haystack_len) >= 0 ) {
630 size_t found_offset = found - haystack;
631
632 if (part) {
633 RETURN_STRINGL(haystack, found_offset);
634 } else {
635 RETURN_STRINGL(found, haystack_len - found_offset);
636 }
637 }
638
639 }
640
641 /* need to work in utf16 */
642 ret_pos = grapheme_strpos_utf16(haystack, haystack_len, needle, needle_len, 0, &uchar_pos, f_ignore_case, 0 /*last */ );
643
644 if ( ret_pos < 0 ) {
645 RETURN_FALSE;
646 }
647
648 /* uchar_pos is the 'nth' Unicode character position of the needle */
649
650 ret_pos = 0;
651 U8_FWD_N(haystack, ret_pos, haystack_len, uchar_pos);
652
653 if (part) {
654 RETURN_STRINGL(haystack, ret_pos);
655 } else {
656 RETURN_STRINGL(haystack + ret_pos, haystack_len - ret_pos);
657 }
658
659 }
660 /* }}} */
661
662 /* {{{ proto string grapheme_strstr(string haystack, string needle[, bool part])
663 Finds first occurrence of a string within another */
PHP_FUNCTION(grapheme_strstr)664 PHP_FUNCTION(grapheme_strstr)
665 {
666 strstr_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0 /* f_ignore_case */);
667 }
668 /* }}} */
669
670 /* {{{ proto string grapheme_stristr(string haystack, string needle[, bool part])
671 Finds first occurrence of a string within another */
PHP_FUNCTION(grapheme_stristr)672 PHP_FUNCTION(grapheme_stristr)
673 {
674 strstr_common_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1 /* f_ignore_case */);
675 }
676 /* }}} */
677
678 /* {{{ grapheme_extract_charcount_iter - grapheme iterator for grapheme_extract MAXCHARS */
679 static inline int32_t
grapheme_extract_charcount_iter(UBreakIterator * bi,int32_t csize,unsigned char * pstr,int32_t str_len)680 grapheme_extract_charcount_iter(UBreakIterator *bi, int32_t csize, unsigned char *pstr, int32_t str_len)
681 {
682 int pos = 0;
683 int ret_pos = 0;
684 int break_pos, prev_break_pos;
685 int count = 0;
686
687 while ( 1 ) {
688 pos = ubrk_next(bi);
689
690 if ( UBRK_DONE == pos ) {
691 break;
692 }
693
694 for ( break_pos = ret_pos; break_pos < pos; ) {
695 count++;
696 prev_break_pos = break_pos;
697 U8_FWD_1(pstr, break_pos, str_len);
698
699 if ( prev_break_pos == break_pos ) {
700 /* something wrong - malformed utf8? */
701 csize = 0;
702 break;
703 }
704 }
705
706 /* if we are beyond our limit, then the loop is done */
707 if ( count > csize ) {
708 break;
709 }
710
711 ret_pos = break_pos;
712 }
713
714 return ret_pos;
715 }
716 /* }}} */
717
718 /* {{{ grapheme_extract_bytecount_iter - grapheme iterator for grapheme_extract MAXBYTES */
719 static inline int32_t
grapheme_extract_bytecount_iter(UBreakIterator * bi,int32_t bsize,unsigned char * pstr,int32_t str_len)720 grapheme_extract_bytecount_iter(UBreakIterator *bi, int32_t bsize, unsigned char *pstr, int32_t str_len)
721 {
722 int pos = 0;
723 int ret_pos = 0;
724
725 while ( 1 ) {
726 pos = ubrk_next(bi);
727
728 if ( UBRK_DONE == pos ) {
729 break;
730 }
731
732 if ( pos > bsize ) {
733 break;
734 }
735
736 ret_pos = pos;
737 }
738
739 return ret_pos;
740 }
741 /* }}} */
742
743 /* {{{ grapheme_extract_count_iter - grapheme iterator for grapheme_extract COUNT */
744 static inline int32_t
grapheme_extract_count_iter(UBreakIterator * bi,int32_t size,unsigned char * pstr,int32_t str_len)745 grapheme_extract_count_iter(UBreakIterator *bi, int32_t size, unsigned char *pstr, int32_t str_len)
746 {
747 int next_pos = 0;
748 int ret_pos = 0;
749
750 while ( size ) {
751 next_pos = ubrk_next(bi);
752
753 if ( UBRK_DONE == next_pos ) {
754 break;
755 }
756 ret_pos = next_pos;
757 size--;
758 }
759
760 return ret_pos;
761 }
762 /* }}} */
763
764 /* {{{ grapheme extract iter function pointer array */
765 typedef int32_t (*grapheme_extract_iter)(UBreakIterator * /*bi*/, int32_t /*size*/, unsigned char * /*pstr*/, int32_t /*str_len*/);
766
767 static grapheme_extract_iter grapheme_extract_iters[] = {
768 &grapheme_extract_count_iter,
769 &grapheme_extract_bytecount_iter,
770 &grapheme_extract_charcount_iter,
771 };
772 /* }}} */
773
774 /* {{{ proto string grapheme_extract(string str, int size[, int extract_type[, int start[, int next]]])
775 Function to extract a sequence of default grapheme clusters */
PHP_FUNCTION(grapheme_extract)776 PHP_FUNCTION(grapheme_extract)
777 {
778 char *str, *pstr;
779 UText ut = UTEXT_INITIALIZER;
780 size_t str_len;
781 zend_long size; /* maximum number of grapheme clusters, bytes, or characters (based on extract_type) to return */
782 zend_long lstart = 0; /* starting position in str in bytes */
783 int32_t start = 0;
784 zend_long extract_type = GRAPHEME_EXTRACT_TYPE_COUNT;
785 UErrorCode status;
786 unsigned char u_break_iterator_buffer[U_BRK_SAFECLONE_BUFFERSIZE];
787 UBreakIterator* bi = NULL;
788 int ret_pos;
789 zval *next = NULL; /* return offset of next part of the string */
790
791 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl|llz", &str, &str_len, &size, &extract_type, &lstart, &next) == FAILURE) {
792 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
793 "grapheme_extract: unable to parse input param", 0 );
794 RETURN_FALSE;
795 }
796
797 if (lstart < 0) {
798 lstart += str_len;
799 }
800
801 if ( NULL != next ) {
802 if ( !Z_ISREF_P(next) ) {
803 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
804 "grapheme_extract: 'next' was not passed by reference", 0 );
805 RETURN_FALSE;
806 } else {
807 ZVAL_DEREF(next);
808 /* initialize next */
809 zval_ptr_dtor(next);
810 ZVAL_LONG(next, lstart);
811 }
812 }
813
814 if ( extract_type < GRAPHEME_EXTRACT_TYPE_MIN || extract_type > GRAPHEME_EXTRACT_TYPE_MAX ) {
815 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
816 "grapheme_extract: unknown extract type param", 0 );
817 RETURN_FALSE;
818 }
819
820 if ( lstart > INT32_MAX || lstart < 0 || (size_t)lstart >= str_len ) {
821 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_extract: start not contained in string", 0 );
822 RETURN_FALSE;
823 }
824
825 if ( size > INT32_MAX || size < 0) {
826 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "grapheme_extract: size is invalid", 0 );
827 RETURN_FALSE;
828 }
829 if (size == 0) {
830 RETURN_EMPTY_STRING();
831 }
832
833 /* we checked that it will fit: */
834 start = (int32_t) lstart;
835
836 pstr = str + start;
837
838 /* just in case pstr points in the middle of a character, move forward to the start of the next char */
839 if ( !U8_IS_SINGLE(*pstr) && !U8_IS_LEAD(*pstr) ) {
840 char *str_end = str + str_len;
841
842 while ( !U8_IS_SINGLE(*pstr) && !U8_IS_LEAD(*pstr) ) {
843 pstr++;
844 if ( pstr >= str_end ) {
845 intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR,
846 "grapheme_extract: invalid input string", 0 );
847
848 RETURN_FALSE;
849 }
850 }
851 }
852
853 str_len -= (pstr - str);
854
855 /* if the string is all ASCII up to size+1 - or str_len whichever is first - then we are done.
856 (size + 1 because the size-th character might be the beginning of a grapheme cluster)
857 */
858
859 if ( -1 != grapheme_ascii_check((unsigned char *)pstr, MIN(size + 1, str_len)) ) {
860 size_t nsize = MIN(size, str_len);
861 if ( NULL != next ) {
862 ZVAL_LONG(next, start+nsize);
863 }
864 RETURN_STRINGL(pstr, nsize);
865 }
866
867 status = U_ZERO_ERROR;
868 utext_openUTF8(&ut, pstr, str_len, &status);
869
870 if ( U_FAILURE( status ) ) {
871 /* Set global error code. */
872 intl_error_set_code( NULL, status );
873
874 /* Set error messages. */
875 intl_error_set_custom_msg( NULL, "Error opening UTF-8 text", 0 );
876
877 RETURN_FALSE;
878 }
879
880 bi = NULL;
881 status = U_ZERO_ERROR;
882 bi = grapheme_get_break_iterator(u_break_iterator_buffer, &status );
883
884 ubrk_setUText(bi, &ut, &status);
885 /* if the caller put us in the middle of a grapheme, we can't detect it in all cases since we
886 can't back up. So, we will not do anything. */
887
888 /* now we need to find the end of the chunk the user wants us to return */
889 /* it's ok to convert str_len to in32_t since if it were too big intl_convert_utf8_to_utf16 above would fail */
890 ret_pos = (*grapheme_extract_iters[extract_type])(bi, size, (unsigned char *)pstr, (int32_t)str_len);
891
892 utext_close(&ut);
893 ubrk_close(bi);
894
895 if ( NULL != next ) {
896 ZVAL_LONG(next, start+ret_pos);
897 }
898
899 RETURN_STRINGL(((char *)pstr), ret_pos);
900 }
901
902 /* }}} */
903