xref: /PHP-7.4/ext/intl/grapheme/grapheme_string.c (revision 92ac598a)
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