|
10 | 10 | | obtain it through the world-wide-web, please send a note to | |
11 | 11 | | license@php.net so we can mail you a copy immediately. | |
12 | 12 | +----------------------------------------------------------------------+ |
13 | | - | Authors: Sepehr mahmoodi <sepehrphpr@gmail.com> | |
| 13 | + | Author: Sepehr <sepehrphpr@gmail.com> | |
14 | 14 | +----------------------------------------------------------------------+ |
15 | 15 | */ |
16 | 16 |
|
|
19 | 19 | #endif |
20 | 20 |
|
21 | 21 | #include "php.h" |
| 22 | +#include "ext/standard/info.h" |
22 | 23 | #include "php_intl.h" |
| 24 | +#include "intl_common.h" |
23 | 25 | #include "intl_error.h" |
24 | | -#include "grapheme.h" |
25 | | -#include "grapheme_util.h" |
26 | | -#include "grapheme_mask.h" |
| 26 | +#include "grapheme/grapheme.h" |
| 27 | +#include "grapheme/grapheme_util.h" |
27 | 28 |
|
28 | | -#include <unicode/ubrk.h> |
29 | | -#include <unicode/utext.h> |
30 | 29 | #include <unicode/utypes.h> |
31 | 30 | #include <unicode/utf8.h> |
| 31 | +#include <unicode/utext.h> |
| 32 | +#include <unicode/ubrk.h> |
| 33 | +#include <unicode/ustring.h> |
32 | 34 |
|
33 | | -/* {{{ grapheme_mask_validate_mask_char */ |
34 | | -static UBool grapheme_mask_validate_mask_char(const char *mask_char, size_t mask_char_len) |
| 35 | +/* {{{ Checks if a string is valid UTF-8 */ |
| 36 | +static zend_bool grapheme_mask_is_valid_utf8(const char *str, size_t str_len) |
35 | 37 | { |
36 | | - if (mask_char_len == 0) { |
37 | | - return 0; |
38 | | - } |
39 | | - |
40 | | - UErrorCode status = U_ZERO_ERROR; |
41 | | - UText *ut = utext_openUTF8(NULL, mask_char, mask_char_len, &status); |
42 | | - if (U_FAILURE(status)) { |
43 | | - return 0; |
44 | | - } |
45 | | - |
46 | | - /* Use the existing grapheme_count_graphemes function */ |
47 | | - int32_t count = grapheme_count_graphemes(ut); |
48 | | - utext_close(ut); |
49 | | - |
50 | | - return (count == 1); |
| 38 | + UChar32 c; |
| 39 | + int32_t i = 0; |
| 40 | + |
| 41 | + while (i < (int32_t)str_len) { |
| 42 | + U8_NEXT(str, i, (int32_t)str_len, c); |
| 43 | + if (c < 0) { |
| 44 | + return 0; |
| 45 | + } |
| 46 | + } |
| 47 | + return 1; |
51 | 48 | } |
52 | 49 | /* }}} */ |
53 | 50 |
|
54 | | -/* {{{ PHP_FUNCTION(grapheme_mask) */ |
| 51 | +/* {{{ Masks grapheme clusters in a UTF-8 string */ |
55 | 52 | PHP_FUNCTION(grapheme_mask) |
56 | 53 | { |
57 | | - zend_string *str; |
58 | | - zend_string *mask_char = NULL; |
59 | | - zend_long offset = 0; |
60 | | - zend_long length = 0; |
61 | | - zend_bool length_is_null = 1; |
62 | | - int32_t total_graphemes = 0; |
63 | | - int32_t start = 0, mask_len = 0; |
64 | | - int32_t *boundaries = NULL; |
65 | | - int32_t boundaries_capacity = 16; |
66 | | - int32_t start_byte_offset, end_byte_offset; |
67 | | - zend_string *result; |
68 | | - char *p; |
69 | | - |
70 | | - ZEND_PARSE_PARAMETERS_START(1, 4) |
71 | | - Z_PARAM_STR(str) |
72 | | - Z_PARAM_OPTIONAL |
73 | | - Z_PARAM_STR_DEFAULT(mask_char, "*") |
74 | | - Z_PARAM_LONG(offset) |
75 | | - Z_PARAM_LONG_OR_NULL(length, length_is_null) |
76 | | - ZEND_PARSE_PARAMETERS_END(); |
77 | | - |
78 | | - /* empty string -> return as is */ |
79 | | - if (ZSTR_LEN(str) == 0) { |
80 | | - RETURN_STR_COPY(str); |
81 | | - } |
82 | | - |
83 | | - /* validate mask_char: must be exactly one grapheme cluster */ |
84 | | - if (!grapheme_mask_validate_mask_char(ZSTR_VAL(mask_char), ZSTR_LEN(mask_char))) { |
85 | | - zend_argument_value_error(2, "must be exactly one grapheme cluster"); |
86 | | - RETURN_THROWS(); |
87 | | - } |
88 | | - |
89 | | - /* validate UTF-8 input - using existing helper */ |
90 | | - if (!grapheme_ascii_check(ZSTR_VAL(str), ZSTR_LEN(str))) { |
91 | | - /* Not ASCII, need to validate UTF-8 */ |
92 | | - UErrorCode status = U_ZERO_ERROR; |
93 | | - UText *ut = utext_openUTF8(NULL, ZSTR_VAL(str), ZSTR_LEN(str), &status); |
94 | | - if (U_FAILURE(status)) { |
95 | | - intl_error_set(NULL, U_ILLEGAL_ARGUMENT_ERROR, "Invalid UTF-8 string", 1); |
96 | | - RETURN_FALSE; |
97 | | - } |
98 | | - utext_close(ut); |
99 | | - } |
100 | | - |
101 | | - /* build cache of all grapheme boundaries */ |
102 | | - { |
103 | | - UErrorCode status = U_ZERO_ERROR; |
104 | | - UText *ut = NULL; |
105 | | - UBreakIterator *bi = NULL; |
106 | | - |
107 | | - ut = utext_openUTF8(NULL, ZSTR_VAL(str), ZSTR_LEN(str), &status); |
108 | | - if (U_FAILURE(status)) { |
109 | | - intl_error_set(NULL, status, "utext_openUTF8 failed", 1); |
110 | | - RETURN_FALSE; |
111 | | - } |
112 | | - |
113 | | - bi = grapheme_get_break_iterator(&status); |
114 | | - if (U_FAILURE(status)) { |
115 | | - utext_close(ut); |
116 | | - intl_error_set(NULL, status, "grapheme_get_break_iterator failed", 1); |
117 | | - RETURN_FALSE; |
118 | | - } |
119 | | - |
120 | | - ubrk_setUText(bi, ut, &status); |
121 | | - if (U_FAILURE(status)) { |
122 | | - ubrk_close(bi); |
123 | | - utext_close(ut); |
124 | | - intl_error_set(NULL, status, "ubrk_setUText failed", 1); |
125 | | - RETURN_FALSE; |
126 | | - } |
127 | | - |
128 | | - boundaries = emalloc(boundaries_capacity * sizeof(int32_t)); |
129 | | - |
130 | | - /* collect all boundaries */ |
131 | | - int32_t pos = ubrk_first(bi); |
132 | | - while (pos != UBRK_DONE) { |
133 | | - if (total_graphemes >= boundaries_capacity) { |
134 | | - boundaries_capacity *= 2; |
135 | | - int32_t *new_boundaries = erealloc(boundaries, boundaries_capacity * sizeof(int32_t)); |
136 | | - if (!new_boundaries) { |
137 | | - ubrk_close(bi); |
138 | | - utext_close(ut); |
139 | | - efree(boundaries); |
140 | | - zend_error(E_ERROR, "Memory allocation failed"); |
141 | | - } |
142 | | - boundaries = new_boundaries; |
143 | | - } |
144 | | - boundaries[total_graphemes] = pos; |
145 | | - total_graphemes++; |
146 | | - pos = ubrk_next(bi); |
147 | | - } |
148 | | - |
149 | | - ubrk_close(bi); |
150 | | - utext_close(ut); |
151 | | - } |
152 | | - |
153 | | - /* Adjust offset */ |
154 | | - if (offset < 0) { |
155 | | - offset = total_graphemes + offset; |
156 | | - if (offset < 0) offset = 0; |
157 | | - } else if (offset > total_graphemes) { |
158 | | - offset = total_graphemes; |
159 | | - } |
160 | | - start = (int32_t)offset; |
161 | | - |
162 | | - /* Determine mask_len */ |
163 | | - if (length_is_null) { |
164 | | - mask_len = total_graphemes - start; |
165 | | - } else { |
166 | | - if (length < 0) { |
167 | | - /* negative length from end */ |
168 | | - length = total_graphemes - start + length; |
169 | | - if (length < 0) length = 0; |
170 | | - } |
171 | | - mask_len = (int32_t)length; |
172 | | - if (start + mask_len > total_graphemes) { |
173 | | - mask_len = total_graphemes - start; |
174 | | - } |
175 | | - } |
176 | | - |
177 | | - /* No-op if nothing to mask */ |
178 | | - if (mask_len <= 0 || start >= total_graphemes) { |
179 | | - efree(boundaries); |
180 | | - RETURN_STR_COPY(str); |
181 | | - } |
182 | | - |
183 | | - /* Compute byte offsets */ |
184 | | - start_byte_offset = boundaries[start]; |
185 | | - if (start + mask_len == total_graphemes) { |
186 | | - end_byte_offset = (int32_t)ZSTR_LEN(str); |
187 | | - } else { |
188 | | - end_byte_offset = boundaries[start + mask_len]; |
189 | | - } |
190 | | - |
191 | | - /* Build result string: prefix + mask_char repeated per grapheme + suffix */ |
192 | | - { |
193 | | - size_t mask_char_len = ZSTR_LEN(mask_char); |
194 | | - size_t prefix_len = start_byte_offset; |
195 | | - size_t masked_len = mask_len * mask_char_len; |
196 | | - size_t suffix_len = ZSTR_LEN(str) - end_byte_offset; |
197 | | - size_t final_len = prefix_len + masked_len + suffix_len; |
198 | | - |
199 | | - result = zend_string_alloc(final_len, 0); |
200 | | - p = ZSTR_VAL(result); |
201 | | - |
202 | | - /* Copy prefix (bytes before masking) */ |
203 | | - if (prefix_len > 0) { |
204 | | - memcpy(p, ZSTR_VAL(str), prefix_len); |
205 | | - p += prefix_len; |
206 | | - } |
207 | | - |
208 | | - /* Write mask_char for each grapheme to be masked */ |
209 | | - for (int32_t i = 0; i < mask_len; i++) { |
210 | | - memcpy(p, ZSTR_VAL(mask_char), mask_char_len); |
211 | | - p += mask_char_len; |
212 | | - } |
213 | | - |
214 | | - /* Copy suffix (bytes after masking) */ |
215 | | - if (suffix_len > 0) { |
216 | | - memcpy(p, ZSTR_VAL(str) + end_byte_offset, suffix_len); |
217 | | - } |
218 | | - |
219 | | - ZSTR_VAL(result)[final_len] = '\0'; |
220 | | - } |
221 | | - |
222 | | - efree(boundaries); |
223 | | - RETURN_NEW_STR(result); |
| 54 | + char *str, *mask_str; |
| 55 | + size_t str_len, mask_len; |
| 56 | + zend_long start = 0, length = 0; |
| 57 | + zend_bool start_is_null = 1, length_is_null = 1; |
| 58 | + |
| 59 | + /* Parse parameters: string, mask_string, [start, [length]] */ |
| 60 | + ZEND_PARSE_PARAMETERS_START(2, 4) |
| 61 | + Z_PARAM_STRING(str, str_len) |
| 62 | + Z_PARAM_STRING(mask_str, mask_len) |
| 63 | + Z_PARAM_OPTIONAL |
| 64 | + Z_PARAM_ZVAL_OR_LONG(start, start_is_null) |
| 65 | + Z_PARAM_ZVAL_OR_LONG(length, length_is_null) |
| 66 | + ZEND_PARSE_PARAMETERS_END(); |
| 67 | + |
| 68 | + /* Validate UTF-8 for the input string */ |
| 69 | + if (str_len > 0 && !grapheme_mask_is_valid_utf8(str, str_len)) { |
| 70 | + php_error_docref(NULL, E_WARNING, "Input string is not valid UTF-8"); |
| 71 | + RETURN_FALSE; |
| 72 | + } |
| 73 | + |
| 74 | + /* If start is beyond string, return empty string */ |
| 75 | + if (start >= (zend_long)str_len) { |
| 76 | + RETURN_EMPTY_STRING(); |
| 77 | + } |
| 78 | + |
| 79 | + if (mask_len == 0) { |
| 80 | + php_error_docref(NULL, E_WARNING, "Mask string must not be empty"); |
| 81 | + RETURN_FALSE; |
| 82 | + } |
| 83 | + |
| 84 | + /* Validate UTF-8 for the mask string */ |
| 85 | + if (!grapheme_mask_is_valid_utf8(mask_str, mask_len)) { |
| 86 | + php_error_docref(NULL, E_WARNING, "Mask string is not valid UTF-8"); |
| 87 | + RETURN_FALSE; |
| 88 | + } |
| 89 | + |
| 90 | + /* Open UText for the input string */ |
| 91 | + UErrorCode status = U_ZERO_ERROR; |
| 92 | + UText *ut = utext_openUTF8(NULL, str, (int64_t)str_len, &status); |
| 93 | + if (U_FAILURE(status)) { |
| 94 | + php_error_docref(NULL, E_WARNING, "Failed to open UText for input string"); |
| 95 | + RETURN_FALSE; |
| 96 | + } |
| 97 | + |
| 98 | + /* Get grapheme break iterator */ |
| 99 | + UBreakIterator *bi = grapheme_get_break_iterator(NULL, &status); |
| 100 | + if (U_FAILURE(status)) { |
| 101 | + utext_close(ut); |
| 102 | + php_error_docref(NULL, E_WARNING, "Failed to create grapheme break iterator"); |
| 103 | + RETURN_FALSE; |
| 104 | + } |
| 105 | + |
| 106 | + /* Set the break iterator text */ |
| 107 | + ubrk_setUText(bi, ut, &status); |
| 108 | + if (U_FAILURE(status)) { |
| 109 | + ubrk_close(bi); |
| 110 | + utext_close(ut); |
| 111 | + php_error_docref(NULL, E_WARNING, "Failed to set text for break iterator"); |
| 112 | + RETURN_FALSE; |
| 113 | + } |
| 114 | + |
| 115 | + /* Collect grapheme boundaries */ |
| 116 | + int32_t *boundaries; |
| 117 | + int32_t boundary_count; |
| 118 | + int32_t buflen = 128; |
| 119 | + int32_t pos; |
| 120 | + |
| 121 | + boundaries = (int32_t *)emalloc(sizeof(int32_t) * (size_t)buflen); |
| 122 | + boundary_count = 0; |
| 123 | + boundaries[boundary_count++] = 0; |
| 124 | + |
| 125 | + pos = ubrk_first(bi); |
| 126 | + while (pos != UBRK_DONE) { |
| 127 | + pos = ubrk_next(bi); |
| 128 | + if (pos != UBRK_DONE) { |
| 129 | + if (boundary_count >= buflen) { |
| 130 | + buflen *= 2; |
| 131 | + boundaries = (int32_t *)erealloc(boundaries, sizeof(int32_t) * (size_t)buflen); |
| 132 | + } |
| 133 | + boundaries[boundary_count++] = pos; |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + /* Determine range to mask */ |
| 138 | + int32_t range_start, range_end; |
| 139 | + if (start_is_null) { |
| 140 | + range_start = 0; |
| 141 | + } else { |
| 142 | + if (start < 0) { |
| 143 | + range_start = boundary_count - 1 + (int32_t)start; |
| 144 | + if (range_start < 0) range_start = 0; |
| 145 | + } else { |
| 146 | + range_start = (int32_t)start; |
| 147 | + if (range_start >= boundary_count) range_start = boundary_count - 1; |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + if (length_is_null) { |
| 152 | + range_end = boundary_count - 1; |
| 153 | + } else { |
| 154 | + if (length < 0) { |
| 155 | + range_end = boundary_count - 1 + (int32_t)length; |
| 156 | + if (range_end < 0) range_end = 0; |
| 157 | + } else { |
| 158 | + range_end = range_start + (int32_t)length; |
| 159 | + if (range_end > boundary_count - 1) range_end = boundary_count - 1; |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + /* Build result */ |
| 164 | + zend_string *result; |
| 165 | + |
| 166 | + /* If no graphemes to mask, return original string */ |
| 167 | + if (range_start >= range_end || range_start >= boundary_count - 1) { |
| 168 | + efree(boundaries); |
| 169 | + ubrk_close(bi); |
| 170 | + utext_close(ut); |
| 171 | + RETURN_STRINGL(str, str_len); |
| 172 | + } |
| 173 | + |
| 174 | + /* Prefix: from byte 0 to the start of the range */ |
| 175 | + int32_t prefix_len = boundaries[range_start]; |
| 176 | + /* Suffix: from the end of the range to the end of the string */ |
| 177 | + int32_t suffix_start = boundaries[range_end]; |
| 178 | + int32_t suffix_len = (int32_t)str_len - suffix_start; |
| 179 | + |
| 180 | + /* Calculate number of graphemes to mask */ |
| 181 | + int32_t graphemes_to_mask = range_end - range_start; |
| 182 | + |
| 183 | + /* Calculate total result length */ |
| 184 | + size_t result_len = (size_t)prefix_len + ((size_t)graphemes_to_mask * mask_len) + (size_t)suffix_len; |
| 185 | + |
| 186 | + result = zend_string_alloc(result_len, 0); |
| 187 | + |
| 188 | + /* Copy prefix */ |
| 189 | + if (prefix_len > 0) { |
| 190 | + memcpy(ZSTR_VAL(result), str, (size_t)prefix_len); |
| 191 | + } |
| 192 | + |
| 193 | + /* Copy mask for each grapheme */ |
| 194 | + char *dest = ZSTR_VAL(result) + prefix_len; |
| 195 | + for (int32_t i = 0; i < graphemes_to_mask; i++) { |
| 196 | + memcpy(dest, mask_str, mask_len); |
| 197 | + dest += mask_len; |
| 198 | + } |
| 199 | + |
| 200 | + /* Copy suffix */ |
| 201 | + if (suffix_len > 0) { |
| 202 | + memcpy(dest, str + suffix_start, (size_t)suffix_len); |
| 203 | + } |
| 204 | + |
| 205 | + ZSTR_VAL(result)[result_len] = '\0'; |
| 206 | + |
| 207 | + efree(boundaries); |
| 208 | + ubrk_close(bi); |
| 209 | + utext_close(ut); |
| 210 | + |
| 211 | + RETURN_STR(result); |
224 | 212 | } |
225 | 213 | /* }}} */ |
0 commit comments