xref: /PHP-7.3/ext/standard/base64.c (revision 986b9b5a)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2018 The PHP Group                                |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Jim Winstead <jimw@php.net>                                  |
16    |         Xinchen Hui <laruence@php.net>                               |
17    +----------------------------------------------------------------------+
18  */
19 
20 #include <string.h>
21 
22 #include "php.h"
23 #include "base64.h"
24 
25 /* {{{ base64 tables */
26 static const char base64_table[] = {
27 	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
28 	'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
29 	'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
30 	'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
31 	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', '\0'
32 };
33 
34 static const char base64_pad = '=';
35 
36 static const short base64_reverse_table[256] = {
37 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -2, -1, -2, -2,
38 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
39 	-1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
40 	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
41 	-2,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
42 	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
43 	-2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
44 	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
45 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
46 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
47 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
48 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
49 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
50 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
51 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
52 	-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
53 };
54 /* }}} */
55 
php_base64_encode_impl(const unsigned char * in,size_t inl,unsigned char * out)56 static zend_always_inline unsigned char *php_base64_encode_impl(const unsigned char *in, size_t inl, unsigned char *out) /* {{{ */
57 {
58 
59 	while (inl > 2) { /* keep going until we have less than 24 bits */
60 		*out++ = base64_table[in[0] >> 2];
61 		*out++ = base64_table[((in[0] & 0x03) << 4) + (in[1] >> 4)];
62 		*out++ = base64_table[((in[1] & 0x0f) << 2) + (in[2] >> 6)];
63 		*out++ = base64_table[in[2] & 0x3f];
64 
65 		in += 3;
66 		inl -= 3; /* we just handle 3 octets of data */
67 	}
68 
69 	/* now deal with the tail end of things */
70 	if (inl != 0) {
71 		*out++ = base64_table[in[0] >> 2];
72 		if (inl > 1) {
73 			*out++ = base64_table[((in[0] & 0x03) << 4) + (in[1] >> 4)];
74 			*out++ = base64_table[(in[1] & 0x0f) << 2];
75 			*out++ = base64_pad;
76 		} else {
77 			*out++ = base64_table[(in[0] & 0x03) << 4];
78 			*out++ = base64_pad;
79 			*out++ = base64_pad;
80 		}
81 	}
82 
83 	*out = '\0';
84 
85 	return out;
86 }
87 /* }}} */
88 
php_base64_decode_impl(const unsigned char * in,size_t inl,unsigned char * out,size_t * outl,zend_bool strict)89 static zend_always_inline int php_base64_decode_impl(const unsigned char *in, size_t inl, unsigned char *out, size_t *outl, zend_bool strict) /* {{{ */
90 {
91 	int ch;
92 	size_t i = 0, padding = 0, j = *outl;
93 
94 	/* run through the whole string, converting as we go */
95 	while (inl-- > 0) {
96 		ch = *in++;
97 		if (ch == base64_pad) {
98 			padding++;
99 			continue;
100 		}
101 
102 		ch = base64_reverse_table[ch];
103 		if (!strict) {
104 			/* skip unknown characters and whitespace */
105 			if (ch < 0) {
106 				continue;
107 			}
108 		} else {
109 			/* skip whitespace */
110 			if (ch == -1) {
111 				continue;
112 			}
113 			/* fail on bad characters or if any data follows padding */
114 			if (ch == -2 || padding) {
115 				goto fail;
116 			}
117 		}
118 
119 		switch (i % 4) {
120 			case 0:
121 				out[j] = ch << 2;
122 				break;
123 			case 1:
124 				out[j++] |= ch >> 4;
125 				out[j] = (ch & 0x0f) << 4;
126 				break;
127 			case 2:
128 				out[j++] |= ch >>2;
129 				out[j] = (ch & 0x03) << 6;
130 				break;
131 			case 3:
132 				out[j++] |= ch;
133 				break;
134 		}
135 		i++;
136 	}
137 
138 	/* fail if the input is truncated (only one char in last group) */
139 	if (strict && i % 4 == 1) {
140 		goto fail;
141 	}
142 
143 	/* fail if the padding length is wrong (not VV==, VVV=), but accept zero padding
144 	 * RFC 4648: "In some circumstances, the use of padding [--] is not required" */
145 	if (strict && padding && (padding > 2 || (i + padding) % 4 != 0)) {
146 		goto fail;
147 	}
148 
149 	*outl = j;
150 	out[j] = '\0';
151 
152 	return 1;
153 
154 fail:
155 	return 0;
156 }
157 /* }}} */
158 
159 /* {{{ php_base64_encode */
160 
161 #if ZEND_INTRIN_AVX2_NATIVE
162 # undef ZEND_INTRIN_SSSE3_NATIVE
163 # undef ZEND_INTRIN_SSSE3_RESOLVER
164 # undef ZEND_INTRIN_SSSE3_FUNC_PROTO
165 # undef ZEND_INTRIN_SSSE3_FUNC_PTR
166 #elif ZEND_INTRIN_AVX2_FUNC_PROTO && ZEND_INTRIN_SSSE3_NATIVE
167 # undef ZEND_INTRIN_SSSE3_NATIVE
168 # undef ZEND_INTRIN_SSSE3_RESOLVER
169 # define ZEND_INTRIN_SSSE3_RESOLVER 1
170 # define ZEND_INTRIN_SSSE3_FUNC_PROTO 1
171 # undef ZEND_INTRIN_SSSE3_FUNC_DECL
172 # ifdef HAVE_FUNC_ATTRIBUTE_TARGET
173 #  define ZEND_INTRIN_SSSE3_FUNC_DECL(func) ZEND_API func __attribute__((target("ssse3")))
174 # else
175 #  define ZEND_INTRIN_SSSE3_FUNC_DECL(func) ZEND_API func
176 # endif
177 #elif ZEND_INTRIN_AVX2_FUNC_PTR && ZEND_INTRIN_SSSE3_NATIVE
178 # undef ZEND_INTRIN_SSSE3_NATIVE
179 # undef ZEND_INTRIN_SSSE3_RESOLVER
180 # define ZEND_INTRIN_SSSE3_RESOLVER 1
181 # define ZEND_INTRIN_SSSE3_FUNC_PTR 1
182 # undef ZEND_INTRIN_SSSE3_FUNC_DECL
183 # ifdef HAVE_FUNC_ATTRIBUTE_TARGET
184 #  define ZEND_INTRIN_SSSE3_FUNC_DECL(func) ZEND_API func __attribute__((target("ssse3")))
185 # else
186 #  define ZEND_INTRIN_SSSE3_FUNC_DECL(func) ZEND_API func
187 # endif
188 #endif
189 
190 #if ZEND_INTRIN_AVX2_NATIVE
191 # include <immintrin.h>
192 #elif ZEND_INTRIN_SSSE3_NATIVE
193 # include <tmmintrin.h>
194 #elif (ZEND_INTRIN_SSSE3_RESOLVER || ZEND_INTRIN_AVX2_RESOLVER)
195 # if ZEND_INTRIN_AVX2_RESOLVER
196 #  include <immintrin.h>
197 # else
198 #  include <tmmintrin.h>
199 # endif /* (ZEND_INTRIN_SSSE3_RESOLVER || ZEND_INTRIN_AVX2_RESOLVER) */
200 # include "Zend/zend_cpuinfo.h"
201 
202 # if ZEND_INTRIN_AVX2_RESOLVER
203 ZEND_INTRIN_AVX2_FUNC_DECL(zend_string *php_base64_encode_avx2(const unsigned char *str, size_t length));
204 ZEND_INTRIN_AVX2_FUNC_DECL(zend_string *php_base64_decode_ex_avx2(const unsigned char *str, size_t length, zend_bool strict));
205 # endif
206 
207 # if ZEND_INTRIN_SSSE3_RESOLVER
208 ZEND_INTRIN_SSSE3_FUNC_DECL(zend_string *php_base64_encode_ssse3(const unsigned char *str, size_t length));
209 ZEND_INTRIN_SSSE3_FUNC_DECL(zend_string *php_base64_decode_ex_ssse3(const unsigned char *str, size_t length, zend_bool strict));
210 # endif
211 
212 zend_string *php_base64_encode_default(const unsigned char *str, size_t length);
213 zend_string *php_base64_decode_ex_default(const unsigned char *str, size_t length, zend_bool strict);
214 
215 # if (ZEND_INTRIN_AVX2_FUNC_PROTO || ZEND_INTRIN_SSSE3_FUNC_PROTO)
216 PHPAPI zend_string *php_base64_encode(const unsigned char *str, size_t length) __attribute__((ifunc("resolve_base64_encode")));
217 PHPAPI zend_string *php_base64_decode_ex(const unsigned char *str, size_t length, zend_bool strict) __attribute__((ifunc("resolve_base64_decode")));
218 
219 ZEND_NO_SANITIZE_ADDRESS
resolve_base64_encode()220 static void *resolve_base64_encode() {
221 # if ZEND_INTRIN_AVX2_FUNC_PROTO
222 	if (zend_cpu_supports_avx2()) {
223 		return php_base64_encode_avx2;
224 	} else
225 # endif
226 #if ZEND_INTRIN_SSSE3_FUNC_PROTO
227 	if (zend_cpu_supports_ssse3()) {
228 		return php_base64_encode_ssse3;
229 	}
230 #endif
231 	return php_base64_encode_default;
232 }
233 
234 ZEND_NO_SANITIZE_ADDRESS
resolve_base64_decode()235 static void *resolve_base64_decode() {
236 # if ZEND_INTRIN_AVX2_FUNC_PROTO
237 	if (zend_cpu_supports_avx2()) {
238 		return php_base64_decode_ex_avx2;
239 	} else
240 # endif
241 #if ZEND_INTRIN_SSSE3_FUNC_PROTO
242 	if (zend_cpu_supports_ssse3()) {
243 		return php_base64_decode_ex_ssse3;
244 	}
245 #endif
246 	return php_base64_decode_ex_default;
247 }
248 # else /* (ZEND_INTRIN_AVX2_FUNC_PROTO || ZEND_INTRIN_SSSE3_FUNC_PROTO) */
249 
250 PHPAPI zend_string *(*php_base64_encode)(const unsigned char *str, size_t length) = NULL;
251 PHPAPI zend_string *(*php_base64_decode_ex)(const unsigned char *str, size_t length, zend_bool strict) = NULL;
252 
PHP_MINIT_FUNCTION(base64_intrin)253 PHP_MINIT_FUNCTION(base64_intrin)
254 {
255 # if ZEND_INTRIN_AVX2_FUNC_PTR
256 	if (zend_cpu_supports_avx2()) {
257 		php_base64_encode = php_base64_encode_avx2;
258 		php_base64_decode_ex = php_base64_decode_ex_avx2;
259 	} else
260 # endif
261 #if ZEND_INTRIN_SSSE3_FUNC_PTR
262 	if (zend_cpu_supports_ssse3()) {
263 		php_base64_encode = php_base64_encode_ssse3;
264 		php_base64_decode_ex = php_base64_decode_ex_ssse3;
265 	} else
266 #endif
267 	{
268 		php_base64_encode = php_base64_encode_default;
269 		php_base64_decode_ex = php_base64_decode_ex_default;
270 	}
271 	return SUCCESS;
272 }
273 # endif /* (ZEND_INTRIN_AVX2_FUNC_PROTO || ZEND_INTRIN_SSSE3_FUNC_PROTO) */
274 #endif /* ZEND_INTRIN_AVX2_NATIVE */
275 
276 #if ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_AVX2_RESOLVER
277 # if ZEND_INTRIN_AVX2_RESOLVER && defined(HAVE_FUNC_ATTRIBUTE_TARGET)
278 static __m256i php_base64_encode_avx2_reshuffle(__m256i in) __attribute__((target("avx2")));
279 static __m256i php_base64_encode_avx2_translate(__m256i in) __attribute__((target("avx2")));
280 # endif
php_base64_encode_avx2_reshuffle(__m256i in)281 static __m256i php_base64_encode_avx2_reshuffle(__m256i in)
282 {
283 	/* This one works with shifted (4 bytes) input in order to
284 	 * be able to work efficiently in the 2 128-bit lanes */
285 	__m256i t0, t1, t2, t3;
286 
287 	/* input, bytes MSB to LSB:
288 	 * 0 0 0 0 x w v u t s r q p o n m
289 	 * l k j i h g f e d c b a 0 0 0 0 */
290 	in = _mm256_shuffle_epi8(in, _mm256_set_epi8(
291 		10, 11,  9, 10,
292 		 7,  8,  6,  7,
293 		 4,  5,  3,  4,
294 		 1,  2,  0,  1,
295 
296 		14, 15, 13, 14,
297 		11, 12, 10, 11,
298 		 8,  9,  7,  8,
299 		 5,  6,  4,  5));
300 
301 	t0 = _mm256_and_si256(in, _mm256_set1_epi32(0x0fc0fc00));
302 
303 	t1 = _mm256_mulhi_epu16(t0, _mm256_set1_epi32(0x04000040));
304 
305 	t2 = _mm256_and_si256(in, _mm256_set1_epi32(0x003f03f0));
306 
307 	t3 = _mm256_mullo_epi16(t2, _mm256_set1_epi32(0x01000010));
308 
309 	return _mm256_or_si256(t1, t3);
310 	/* 00xxxxxx 00wwwwXX 00vvWWWW 00VVVVVV
311 	 * 00uuuuuu 00ttttUU 00ssTTTT 00SSSSSS
312 	 * 00rrrrrr 00qqqqRR 00ppQQQQ 00PPPPPP
313 	 * 00oooooo 00nnnnOO 00mmNNNN 00MMMMMM
314 	 * 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ
315 	 * 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG
316 	 * 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD
317 	 * 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA */
318 }
319 
php_base64_encode_avx2_translate(__m256i in)320 static __m256i php_base64_encode_avx2_translate(__m256i in)
321 {
322 	__m256i lut, indices, mask;
323 
324 	lut = _mm256_setr_epi8(
325 			65, 71, -4, -4, -4, -4, -4, -4,
326 			-4, -4, -4, -4, -19, -16, 0, 0,
327 			65, 71, -4, -4, -4, -4, -4, -4,
328 			-4, -4, -4, -4, -19, -16, 0, 0);
329 
330 	indices = _mm256_subs_epu8(in, _mm256_set1_epi8(51));
331 
332 	mask = _mm256_cmpgt_epi8(in, _mm256_set1_epi8(25));
333 
334 	indices = _mm256_sub_epi8(indices, mask);
335 
336 	return _mm256_add_epi8(in, _mm256_shuffle_epi8(lut, indices));
337 
338 }
339 #endif /* ZEND_INTRIN_AVX2_NATIVE || (ZEND_INTRIN_AVX2_RESOLVER && !ZEND_INTRIN_SSSE3_NATIVE) */
340 
341 #if ZEND_INTRIN_SSSE3_NATIVE || ZEND_INTRIN_SSSE3_RESOLVER
342 
343 # if ZEND_INTRIN_SSSE3_RESOLVER && defined(HAVE_FUNC_ATTRIBUTE_TARGET)
344 static __m128i php_base64_encode_ssse3_reshuffle(__m128i in) __attribute__((target("ssse3")));
345 static __m128i php_base64_encode_ssse3_translate(__m128i in) __attribute__((target("ssse3")));
346 # endif
347 
php_base64_encode_ssse3_reshuffle(__m128i in)348 static __m128i php_base64_encode_ssse3_reshuffle(__m128i in)
349 {
350 	__m128i t0, t1, t2, t3;
351 
352 	/* input, bytes MSB to LSB:
353 	 * 0 0 0 0 l k j i h g f e d c b a */
354 	in = _mm_shuffle_epi8(in, _mm_set_epi8(
355 				10, 11,  9, 10,
356 				7,  8,  6,  7,
357 				4,  5,  3,  4,
358 				1,  2,  0,  1));
359 
360 	t0 = _mm_and_si128(in, _mm_set1_epi32(0x0fc0fc00));
361 
362 	t1 = _mm_mulhi_epu16(t0, _mm_set1_epi32(0x04000040));
363 
364 	t2 = _mm_and_si128(in, _mm_set1_epi32(0x003f03f0));
365 
366 	t3 = _mm_mullo_epi16(t2, _mm_set1_epi32(0x01000010));
367 
368 	/* output (upper case are MSB, lower case are LSB):
369 	 * 00llllll 00kkkkLL 00jjKKKK 00JJJJJJ
370 	 * 00iiiiii 00hhhhII 00ggHHHH 00GGGGGG
371 	 * 00ffffff 00eeeeFF 00ddEEEE 00DDDDDD
372 	 * 00cccccc 00bbbbCC 00aaBBBB 00AAAAAA */
373 	return _mm_or_si128(t1, t3);
374 }
375 
php_base64_encode_ssse3_translate(__m128i in)376 static __m128i php_base64_encode_ssse3_translate(__m128i in)
377 {
378 	__m128i mask, indices;
379 	__m128i lut = _mm_setr_epi8(
380 			65,  71, -4, -4,
381 			-4,  -4, -4, -4,
382 			-4,  -4, -4, -4,
383 			-19, -16,  0,  0
384 			);
385 
386 	/* Translate values 0..63 to the Base64 alphabet. There are five sets:
387 	 * #  From      To         Abs    Index  Characters
388 	 * 0  [0..25]   [65..90]   +65        0  ABCDEFGHIJKLMNOPQRSTUVWXYZ
389 	 * 1  [26..51]  [97..122]  +71        1  abcdefghijklmnopqrstuvwxyz
390 	 * 2  [52..61]  [48..57]    -4  [2..11]  0123456789
391 	 * 3  [62]      [43]       -19       12  +
392 	 * 4  [63]      [47]       -16       13  / */
393 
394 	/* Create LUT indices from input:
395 	 * the index for range #0 is right, others are 1 less than expected: */
396 	indices = _mm_subs_epu8(in, _mm_set1_epi8(51));
397 
398 	/* mask is 0xFF (-1) for range #[1..4] and 0x00 for range #0: */
399 	mask = _mm_cmpgt_epi8(in, _mm_set1_epi8(25));
400 
401 	/* subtract -1, so add 1 to indices for range #[1..4], All indices are now correct: */
402 	indices = _mm_sub_epi8(indices, mask);
403 
404 	/* Add offsets to input values: */
405 	return _mm_add_epi8(in, _mm_shuffle_epi8(lut, indices));
406 }
407 
408 #define PHP_BASE64_ENCODE_SSSE3_LOOP				\
409 	while (length > 15) {							\
410 		__m128i s = _mm_loadu_si128((__m128i *)c);	\
411 													\
412 		s = php_base64_encode_ssse3_reshuffle(s);	\
413 													\
414 		s = php_base64_encode_ssse3_translate(s);	\
415 													\
416 		_mm_storeu_si128((__m128i *)o, s);			\
417 		c += 12;									\
418 		o += 16;									\
419 		length -= 12;								\
420 	}
421 
422 #endif /* ZEND_INTRIN_SSSE3_NATIVE || (ZEND_INTRIN_SSSE3_RESOLVER && !ZEND_INTRIN_AVX2_NATIVE) */
423 
424 #if ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_AVX2_RESOLVER || ZEND_INTRIN_SSSE3_NATIVE || ZEND_INTRIN_SSSE3_RESOLVER
425 # if ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_SSSE3_NATIVE
php_base64_encode(const unsigned char * str,size_t length)426 PHPAPI zend_string *php_base64_encode(const unsigned char *str, size_t length)
427 # elif ZEND_INTRIN_AVX2_RESOLVER
428 zend_string *php_base64_encode_avx2(const unsigned char *str, size_t length)
429 # else /* ZEND_INTRIN_SSSE3_RESOLVER */
430 zend_string *php_base64_encode_ssse3(const unsigned char *str, size_t length)
431 # endif
432 {
433 	const unsigned char *c = str;
434 	unsigned char *o;
435 	zend_string *result;
436 
437 	result = zend_string_safe_alloc(((length + 2) / 3), 4 * sizeof(char), 0, 0);
438 	o = (unsigned char *)ZSTR_VAL(result);
439 # if ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_AVX2_RESOLVER
440 	if (length > 31) {
441 		__m256i s = _mm256_loadu_si256((__m256i *)c);
442 
443 		s = _mm256_permutevar8x32_epi32(s, _mm256_setr_epi32(0, 0, 1, 2, 3, 4, 5, 6));
444 
445 		for (;;) {
446 			s = php_base64_encode_avx2_reshuffle(s);
447 
448 			s = php_base64_encode_avx2_translate(s);
449 
450 			_mm256_storeu_si256((__m256i *)o, s);
451 			c += 24;
452 			o += 32;
453 			length -= 24;
454 			if (length < 28) {
455 				break;
456 			}
457 			s = _mm256_loadu_si256((__m256i *)(c - 4));
458 		}
459 	}
460 # else
461 	PHP_BASE64_ENCODE_SSSE3_LOOP;
462 # endif
463 
464 	o = php_base64_encode_impl(c, length, o);
465 
466 	ZSTR_LEN(result) = (o - (unsigned char *)ZSTR_VAL(result));
467 
468 	return result;
469 }
470 
471 # if ZEND_INTRIN_SSSE3_RESOLVER && ZEND_INTRIN_AVX2_RESOLVER
php_base64_encode_ssse3(const unsigned char * str,size_t length)472 zend_string *php_base64_encode_ssse3(const unsigned char *str, size_t length)
473 {
474 	const unsigned char *c = str;
475 	unsigned char *o;
476 	zend_string *result;
477 
478 	result = zend_string_safe_alloc(((length + 2) / 3), 4 * sizeof(char), 0, 0);
479 	o = (unsigned char *)ZSTR_VAL(result);
480 
481 	PHP_BASE64_ENCODE_SSSE3_LOOP;
482 
483 	o = php_base64_encode_impl(c, length, o);
484 
485 	ZSTR_LEN(result) = (o - (unsigned char *)ZSTR_VAL(result));
486 
487 	return result;
488 }
489 # endif
490 #endif /* ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_AVX2_RESOLVER || ZEND_INTRIN_SSSE3_NATIVE || ZEND_INTRIN_SSSE3_RESOLVER */
491 
492 /* }}} */
493 
494 /* {{{ php_base64_decode_ex */
495 /* generate reverse table (do not set index 0 to 64)
496 static unsigned short base64_reverse_table[256];
497 #define rt base64_reverse_table
498 void php_base64_init(void)
499 {
500 	char *s = emalloc(10240), *sp;
501 	char *chp;
502 	short idx;
503 
504 	for(ch = 0; ch < 256; ch++) {
505 		chp = strchr(base64_table, ch);
506 		if(ch && chp) {
507 			idx = chp - base64_table;
508 			if (idx >= 64) idx = -1;
509 			rt[ch] = idx;
510 		} else {
511 			rt[ch] = -1;
512 		}
513 	}
514 	sp = s;
515 	sprintf(sp, "static const short base64_reverse_table[256] = {\n");
516 	for(ch =0; ch < 256;) {
517 		sp = s+strlen(s);
518 		sprintf(sp, "\t% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,% 3d,\n", rt[ch+0], rt[ch+1], rt[ch+2], rt[ch+3], rt[ch+4], rt[ch+5], rt[ch+6], rt[ch+7], rt[ch+8], rt[ch+9], rt[ch+10], rt[ch+11], rt[ch+12], rt[ch+13], rt[ch+14], rt[ch+15]);
519 		ch += 16;
520 	}
521 	sprintf(sp, "};");
522 	php_error_docref(NULL, E_NOTICE, "Reverse_table:\n%s", s);
523 	efree(s);
524 }
525 */
526 
527 #if ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_AVX2_RESOLVER
528 # if ZEND_INTRIN_AVX2_RESOLVER && defined(HAVE_FUNC_ATTRIBUTE_TARGET)
529 static __m256i php_base64_decode_avx2_reshuffle(__m256i in) __attribute__((target("avx2")));
530 # endif
531 
php_base64_decode_avx2_reshuffle(__m256i in)532 static __m256i php_base64_decode_avx2_reshuffle(__m256i in)
533 {
534 	__m256i merge_ab_and_bc, out;
535 
536 	merge_ab_and_bc = _mm256_maddubs_epi16(in, _mm256_set1_epi32(0x01400140));
537 
538 	out = _mm256_madd_epi16(merge_ab_and_bc, _mm256_set1_epi32(0x00011000));
539 
540 	out = _mm256_shuffle_epi8(out, _mm256_setr_epi8(
541 				2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1,
542 				2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, -1, -1, -1, -1));
543 
544 	return _mm256_permutevar8x32_epi32(out, _mm256_setr_epi32(0, 1, 2, 4, 5, 6, -1, -1));
545 }
546 #endif
547 
548 #if ZEND_INTRIN_SSSE3_NATIVE || ZEND_INTRIN_SSSE3_RESOLVER
549 # if ZEND_INTRIN_SSSE3_RESOLVER && defined(HAVE_FUNC_ATTRIBUTE_TARGET)
550 static __m128i php_base64_decode_ssse3_reshuffle(__m128i in) __attribute__((target("ssse3")));
551 # endif
552 
php_base64_decode_ssse3_reshuffle(__m128i in)553 static __m128i php_base64_decode_ssse3_reshuffle(__m128i in)
554 {
555 	__m128i merge_ab_and_bc, out;
556 
557 	merge_ab_and_bc = _mm_maddubs_epi16(in, _mm_set1_epi32(0x01400140));
558 	/* 0000kkkk LLllllll 0000JJJJ JJjjKKKK
559 	 * 0000hhhh IIiiiiii 0000GGGG GGggHHHH
560 	 * 0000eeee FFffffff 0000DDDD DDddEEEE
561 	 * 0000bbbb CCcccccc 0000AAAA AAaaBBBB */
562 
563 	out = _mm_madd_epi16(merge_ab_and_bc, _mm_set1_epi32(0x00011000));
564 	/* 00000000 JJJJJJjj KKKKkkkk LLllllll
565 	 * 00000000 GGGGGGgg HHHHhhhh IIiiiiii
566 	 * 00000000 DDDDDDdd EEEEeeee FFffffff
567 	 * 00000000 AAAAAAaa BBBBbbbb CCcccccc */
568 
569 	return  _mm_shuffle_epi8(out, _mm_setr_epi8(
570 		 2,  1,  0,
571 		 6,  5,  4,
572 		10,  9,  8,
573 		14, 13, 12,
574 		-1, -1, -1, -1));
575 	/* 00000000 00000000 00000000 00000000
576 	 * LLllllll KKKKkkkk JJJJJJjj IIiiiiii
577 	 * HHHHhhhh GGGGGGgg FFffffff EEEEeeee
578 	 * DDDDDDdd CCcccccc BBBBbbbb AAAAAAaa */
579 }
580 
581 #define PHP_BASE64_DECODE_SSSE3_LOOP								\
582 	while (length > 15 + 6 + 2) {									\
583 		__m128i lut_lo, lut_hi, lut_roll;							\
584 		__m128i hi_nibbles, lo_nibbles, hi, lo;						\
585 		__m128i s = _mm_loadu_si128((__m128i *)c);					\
586 																	\
587 		lut_lo = _mm_setr_epi8(										\
588 				0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,		\
589 				0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A);	\
590 		lut_hi = _mm_setr_epi8(										\
591 				0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08,		\
592 				0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10);	\
593 		lut_roll = _mm_setr_epi8(									\
594 				0,  16,  19,   4, -65, -65, -71, -71,				\
595 				0,   0,   0,   0,   0,   0,   0,   0);				\
596 																	\
597 		hi_nibbles  = _mm_and_si128(								\
598 						_mm_srli_epi32(s, 4), _mm_set1_epi8(0x2f));	\
599 		lo_nibbles  = _mm_and_si128(s, _mm_set1_epi8(0x2f));		\
600 		hi          = _mm_shuffle_epi8(lut_hi, hi_nibbles);			\
601 		lo          = _mm_shuffle_epi8(lut_lo, lo_nibbles);			\
602 																	\
603 																	\
604 		if (UNEXPECTED(												\
605 			_mm_movemask_epi8(										\
606 				_mm_cmpgt_epi8(										\
607 					_mm_and_si128(lo, hi), _mm_set1_epi8(0))))) {	\
608 			break;													\
609 		} else {													\
610 			__m128i eq_2f, roll;									\
611 																	\
612 			eq_2f = _mm_cmpeq_epi8(s, _mm_set1_epi8(0x2f));			\
613 			roll = _mm_shuffle_epi8(								\
614 					lut_roll, _mm_add_epi8(eq_2f, hi_nibbles));		\
615 																	\
616 			s = _mm_add_epi8(s, roll);								\
617 			s = php_base64_decode_ssse3_reshuffle(s);				\
618 																	\
619 			_mm_storeu_si128((__m128i *)o, s);						\
620 																	\
621 			c += 16;												\
622 			o += 12;												\
623 			outl += 12;												\
624 			length -= 16;											\
625 		}															\
626 	}
627 
628 #endif
629 
630 #if ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_AVX2_RESOLVER || ZEND_INTRIN_SSSE3_NATIVE || ZEND_INTRIN_SSSE3_RESOLVER
631 # if ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_SSSE3_NATIVE
php_base64_decode_ex(const unsigned char * str,size_t length,zend_bool strict)632 PHPAPI zend_string *php_base64_decode_ex(const unsigned char *str, size_t length, zend_bool strict)
633 # elif ZEND_INTRIN_AVX2_RESOLVER
634 zend_string *php_base64_decode_ex_avx2(const unsigned char *str, size_t length, zend_bool strict)
635 # else
636 zend_string *php_base64_decode_ex_ssse3(const unsigned char *str, size_t length, zend_bool strict)
637 # endif
638 {
639 	const unsigned char *c = str;
640 	unsigned char *o;
641 	size_t outl = 0;
642 	zend_string *result;
643 
644 	result = zend_string_alloc(length, 0);
645 	o = (unsigned char *)ZSTR_VAL(result);
646 
647 	/* See: "Faster Base64 Encoding and Decoding using AVX2 Instructions"
648 	* https://arxiv.org/pdf/1704.00605.pdf */
649 # if ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_AVX2_RESOLVER
650 	while (length > 31 + 11 + 2) {
651 		__m256i lut_lo, lut_hi, lut_roll;
652 		__m256i hi_nibbles, lo_nibbles, hi, lo;
653 		__m256i str = _mm256_loadu_si256((__m256i *)c);
654 
655 		lut_lo = _mm256_setr_epi8(
656 				0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
657 				0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A,
658 				0x15, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
659 				0x11, 0x11, 0x13, 0x1A, 0x1B, 0x1B, 0x1B, 0x1A);
660 
661 		lut_hi = _mm256_setr_epi8(
662 				0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08,
663 				0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
664 				0x10, 0x10, 0x01, 0x02, 0x04, 0x08, 0x04, 0x08,
665 				0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10);
666 
667 		lut_roll = _mm256_setr_epi8(
668 				0,  16,  19,   4, -65, -65, -71, -71,
669 				0,   0,   0,   0,   0,   0,   0,   0,
670 				0,  16,  19,   4, -65, -65, -71, -71,
671 				0,   0,   0,   0,   0,   0,   0,   0);
672 
673 		hi_nibbles  = _mm256_and_si256(_mm256_srli_epi32(str, 4), _mm256_set1_epi8(0x2f));
674 		lo_nibbles  = _mm256_and_si256(str, _mm256_set1_epi8(0x2f));
675 		hi          = _mm256_shuffle_epi8(lut_hi, hi_nibbles);
676 		lo          = _mm256_shuffle_epi8(lut_lo, lo_nibbles);
677 
678 		if (!_mm256_testz_si256(lo, hi)) {
679 			break;
680 		} else {
681 			__m256i eq_2f, roll;
682 			eq_2f = _mm256_cmpeq_epi8(str, _mm256_set1_epi8(0x2f));
683 			roll  = _mm256_shuffle_epi8(lut_roll, _mm256_add_epi8(eq_2f, hi_nibbles));
684 
685 
686 			str = _mm256_add_epi8(str, roll);
687 
688 			str = php_base64_decode_avx2_reshuffle(str);
689 
690 			_mm256_storeu_si256((__m256i *)o, str);
691 
692 			c += 32;
693 			o += 24;
694 			outl += 24;
695 			length -= 32;
696 		}
697 	}
698 # else
699 	PHP_BASE64_DECODE_SSSE3_LOOP;
700 # endif
701 
702 	if (!php_base64_decode_impl(c, length, (unsigned char*)ZSTR_VAL(result), &outl, strict)) {
703 		zend_string_efree(result);
704 		return NULL;
705 	}
706 
707 	ZSTR_LEN(result) = outl;
708 
709 	return result;
710 }
711 
712 # if ZEND_INTRIN_SSSE3_RESOLVER && ZEND_INTRIN_AVX2_RESOLVER
php_base64_decode_ex_ssse3(const unsigned char * str,size_t length,zend_bool strict)713 zend_string *php_base64_decode_ex_ssse3(const unsigned char *str, size_t length, zend_bool strict)
714 {
715 	const unsigned char *c = str;
716 	unsigned char *o;
717 	size_t outl = 0;
718 	zend_string *result;
719 
720 	result = zend_string_alloc(length, 0);
721 	o = (unsigned char *)ZSTR_VAL(result);
722 
723 	PHP_BASE64_DECODE_SSSE3_LOOP;
724 
725 	if (!php_base64_decode_impl(c, length, (unsigned char*)ZSTR_VAL(result), &outl, strict)) {
726 		zend_string_efree(result);
727 		return NULL;
728 	}
729 
730 	ZSTR_LEN(result) = outl;
731 
732 	return result;
733 }
734 # endif
735 #endif /* ZEND_INTRIN_AVX2_NATIVE || ZEND_INTRIN_AVX2_RESOLVER || ZEND_INTRIN_SSSE3_NATIVE || ZEND_INTRIN_SSSE3_RESOLVER */
736 
737 #if !ZEND_INTRIN_AVX2_NATIVE && !ZEND_INTRIN_SSSE3_NATIVE
738 #if ZEND_INTRIN_AVX2_RESOLVER || ZEND_INTRIN_SSSE3_RESOLVER
php_base64_encode_default(const unsigned char * str,size_t length)739 zend_string *php_base64_encode_default(const unsigned char *str, size_t length)
740 #else
741 PHPAPI zend_string *php_base64_encode(const unsigned char *str, size_t length)
742 #endif
743 {
744 	unsigned char *p;
745 	zend_string *result;
746 
747 	result = zend_string_safe_alloc(((length + 2) / 3), 4 * sizeof(char), 0, 0);
748 	p = (unsigned char *)ZSTR_VAL(result);
749 
750 	p = php_base64_encode_impl(str, length, p);
751 
752 	ZSTR_LEN(result) = (p - (unsigned char *)ZSTR_VAL(result));
753 
754 	return result;
755 }
756 #endif
757 
758 #if !ZEND_INTRIN_AVX2_NATIVE && !ZEND_INTRIN_SSSE3_NATIVE
759 #if ZEND_INTRIN_AVX2_RESOLVER || ZEND_INTRIN_SSSE3_RESOLVER
php_base64_decode_ex_default(const unsigned char * str,size_t length,zend_bool strict)760 zend_string *php_base64_decode_ex_default(const unsigned char *str, size_t length, zend_bool strict)
761 #else
762 PHPAPI zend_string *php_base64_decode_ex(const unsigned char *str, size_t length, zend_bool strict)
763 #endif
764 {
765 	zend_string *result;
766 	size_t outl = 0;
767 
768 	result = zend_string_alloc(length, 0);
769 
770 	if (!php_base64_decode_impl(str, length, (unsigned char*)ZSTR_VAL(result), &outl, strict)) {
771 		zend_string_efree(result);
772 		return NULL;
773 	}
774 
775 	ZSTR_LEN(result) = outl;
776 
777 	return result;
778 }
779 #endif
780 /* }}} */
781 
782 /* {{{ proto string base64_encode(string str)
783    Encodes string using MIME base64 algorithm */
PHP_FUNCTION(base64_encode)784 PHP_FUNCTION(base64_encode)
785 {
786 	char *str;
787 	size_t str_len;
788 	zend_string *result;
789 
790 	ZEND_PARSE_PARAMETERS_START(1, 1)
791 		Z_PARAM_STRING(str, str_len)
792 	ZEND_PARSE_PARAMETERS_END();
793 
794 	result = php_base64_encode((unsigned char*)str, str_len);
795 	if (result != NULL) {
796 		RETURN_STR(result);
797 	} else {
798 		RETURN_FALSE;
799 	}
800 }
801 /* }}} */
802 
803 /* {{{ proto string base64_decode(string str[, bool strict])
804    Decodes string using MIME base64 algorithm */
PHP_FUNCTION(base64_decode)805 PHP_FUNCTION(base64_decode)
806 {
807 	char *str;
808 	zend_bool strict = 0;
809 	size_t str_len;
810 	zend_string *result;
811 
812 	ZEND_PARSE_PARAMETERS_START(1, 2)
813 		Z_PARAM_STRING(str, str_len)
814 		Z_PARAM_OPTIONAL
815 		Z_PARAM_BOOL(strict)
816 	ZEND_PARSE_PARAMETERS_END();
817 
818 	result = php_base64_decode_ex((unsigned char*)str, str_len, strict);
819 	if (result != NULL) {
820 		RETURN_STR(result);
821 	} else {
822 		RETURN_FALSE;
823 	}
824 }
825 /* }}} */
826 
827 /*
828  * Local variables:
829  * tab-width: 4
830  * c-basic-offset: 4
831  * End:
832  * vim600: sw=4 ts=4 fdm=marker
833  * vim<600: sw=4 ts=4
834  */
835