xref: /PHP-8.2/sapi/phpdbg/phpdbg_cmd.c (revision dc670cb7)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
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    | https://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    | Authors: Felipe Pena <felipe@php.net>                                |
14    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
15    | Authors: Bob Weinand <bwoebi@php.net>                                |
16    +----------------------------------------------------------------------+
17 */
18 
19 #include "phpdbg.h"
20 #include "phpdbg_cmd.h"
21 #include "phpdbg_utils.h"
22 #include "phpdbg_set.h"
23 #include "phpdbg_prompt.h"
24 #include "phpdbg_io.h"
25 
26 #ifdef HAVE_UNISTD_H
27 # include <unistd.h>
28 #endif
29 
ZEND_EXTERN_MODULE_GLOBALS(phpdbg)30 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
31 
32 static inline const char *phpdbg_command_name(const phpdbg_command_t *command, char *buffer) {
33 	size_t pos = 0;
34 
35 	if (command->parent) {
36 		memcpy(&buffer[pos], command->parent->name, command->parent->name_len);
37 		pos += command->parent->name_len;
38 		memcpy(&buffer[pos], " ", sizeof(" ")-1);
39 		pos += (sizeof(" ")-1);
40 	}
41 
42 	memcpy(&buffer[pos], command->name, command->name_len);
43 	pos += command->name_len;
44 	buffer[pos] = 0;
45 
46 	return buffer;
47 }
48 
phpdbg_get_param_type(const phpdbg_param_t * param)49 PHPDBG_API const char *phpdbg_get_param_type(const phpdbg_param_t *param) /* {{{ */
50 {
51 	switch (param->type) {
52 		case STACK_PARAM:
53 			return "stack";
54 		case EMPTY_PARAM:
55 			return "empty";
56 		case ADDR_PARAM:
57 			return "address";
58 		case NUMERIC_PARAM:
59 			return "numeric";
60 		case METHOD_PARAM:
61 			return "method";
62 		case NUMERIC_FUNCTION_PARAM:
63 			return "function opline";
64 		case NUMERIC_METHOD_PARAM:
65 			return "method opline";
66 		case FILE_PARAM:
67 			return "file or file opline";
68 		case STR_PARAM:
69 			return "string";
70 		default: /* this is bad */
71 			return "unknown";
72 	}
73 }
74 
phpdbg_clear_param(phpdbg_param_t * param)75 PHPDBG_API void phpdbg_clear_param(phpdbg_param_t *param) /* {{{ */
76 {
77 	if (param) {
78 		switch (param->type) {
79 			case FILE_PARAM:
80 				efree(param->file.name);
81 				break;
82 			case METHOD_PARAM:
83 				efree(param->method.class);
84 				efree(param->method.name);
85 				break;
86 			case STR_PARAM:
87 				efree(param->str);
88 				break;
89 			default:
90 				break;
91 		}
92 	}
93 
94 } /* }}} */
95 
phpdbg_param_tostring(const phpdbg_param_t * param,char ** pointer)96 PHPDBG_API char* phpdbg_param_tostring(const phpdbg_param_t *param, char **pointer) /* {{{ */
97 {
98 	switch (param->type) {
99 		case STR_PARAM:
100 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s", param->str));
101 		break;
102 
103 		case ADDR_PARAM:
104 			ZEND_IGNORE_VALUE(asprintf(pointer, ZEND_ULONG_FMT, param->addr));
105 		break;
106 
107 		case NUMERIC_PARAM:
108 			ZEND_IGNORE_VALUE(asprintf(pointer, ZEND_LONG_FMT, param->num));
109 		break;
110 
111 		case METHOD_PARAM:
112 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s::%s", param->method.class, param->method.name));
113 		break;
114 
115 		case FILE_PARAM:
116 			if (param->num) {
117 				ZEND_IGNORE_VALUE(asprintf(pointer, "%s:"ZEND_ULONG_FMT"#"ZEND_ULONG_FMT, param->file.name, param->file.line, param->num));
118 			} else {
119 				ZEND_IGNORE_VALUE(asprintf(pointer, "%s:"ZEND_ULONG_FMT, param->file.name, param->file.line));
120 			}
121 		break;
122 
123 		case NUMERIC_FUNCTION_PARAM:
124 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s#"ZEND_ULONG_FMT, param->str, param->num));
125 		break;
126 
127 		case NUMERIC_METHOD_PARAM:
128 			ZEND_IGNORE_VALUE(asprintf(pointer, "%s::%s#"ZEND_ULONG_FMT, param->method.class, param->method.name, param->num));
129 		break;
130 
131 		default:
132 			*pointer = strdup("unknown");
133 	}
134 
135 	return *pointer;
136 } /* }}} */
137 
phpdbg_copy_param(const phpdbg_param_t * src,phpdbg_param_t * dest)138 PHPDBG_API void phpdbg_copy_param(const phpdbg_param_t* src, phpdbg_param_t* dest) /* {{{ */
139 {
140 	switch ((dest->type = src->type)) {
141 		case STACK_PARAM:
142 			/* nope */
143 		break;
144 
145 		case STR_PARAM:
146 			dest->str = estrndup(src->str, src->len);
147 			dest->len = src->len;
148 		break;
149 
150 		case OP_PARAM:
151 			dest->str = estrndup(src->str, src->len);
152 			dest->len = src->len;
153 		break;
154 
155 		case ADDR_PARAM:
156 			dest->addr = src->addr;
157 		break;
158 
159 		case NUMERIC_PARAM:
160 			dest->num = src->num;
161 		break;
162 
163 		case METHOD_PARAM:
164 			dest->method.class = estrdup(src->method.class);
165 			dest->method.name = estrdup(src->method.name);
166 		break;
167 
168 		case NUMERIC_FILE_PARAM:
169 		case FILE_PARAM:
170 			dest->file.name = estrdup(src->file.name);
171 			dest->file.line = src->file.line;
172 			if (src->num)
173 				dest->num   = src->num;
174 		break;
175 
176 		case NUMERIC_FUNCTION_PARAM:
177 			dest->str = estrndup(src->str, src->len);
178 			dest->num = src->num;
179 			dest->len = src->len;
180 		break;
181 
182 		case NUMERIC_METHOD_PARAM:
183 			dest->method.class = estrdup(src->method.class);
184 			dest->method.name = estrdup(src->method.name);
185 			dest->num = src->num;
186 		break;
187 
188 		case EMPTY_PARAM: { /* do nothing */ } break;
189 
190 		default: {
191 			/* not yet */
192 		}
193 	}
194 } /* }}} */
195 
phpdbg_hash_param(const phpdbg_param_t * param)196 PHPDBG_API zend_ulong phpdbg_hash_param(const phpdbg_param_t *param) /* {{{ */
197 {
198 	zend_ulong hash = param->type;
199 
200 	switch (param->type) {
201 		case STACK_PARAM:
202 			/* nope */
203 		break;
204 
205 		case STR_PARAM:
206 			hash += zend_hash_func(param->str, param->len);
207 		break;
208 
209 		case METHOD_PARAM:
210 			hash += zend_hash_func(param->method.class, strlen(param->method.class));
211 			hash += zend_hash_func(param->method.name, strlen(param->method.name));
212 		break;
213 
214 		case FILE_PARAM:
215 			hash += zend_hash_func(param->file.name, strlen(param->file.name));
216 			hash += param->file.line;
217 			if (param->num)
218 				hash += param->num;
219 		break;
220 
221 		case ADDR_PARAM:
222 			hash += param->addr;
223 		break;
224 
225 		case NUMERIC_PARAM:
226 			hash += param->num;
227 		break;
228 
229 		case NUMERIC_FUNCTION_PARAM:
230 			hash += zend_hash_func(param->str, param->len);
231 			hash += param->num;
232 		break;
233 
234 		case NUMERIC_METHOD_PARAM:
235 			hash += zend_hash_func(param->method.class, strlen(param->method.class));
236 			hash += zend_hash_func(param->method.name, strlen(param->method.name));
237 			if (param->num)
238 				hash+= param->num;
239 		break;
240 
241 		case EMPTY_PARAM: { /* do nothing */ } break;
242 
243 		default: {
244 			/* not yet */
245 		}
246 	}
247 
248 	return hash;
249 } /* }}} */
250 
phpdbg_match_param(const phpdbg_param_t * l,const phpdbg_param_t * r)251 PHPDBG_API bool phpdbg_match_param(const phpdbg_param_t *l, const phpdbg_param_t *r) /* {{{ */
252 {
253 	if (l && r) {
254 		if (l->type == r->type) {
255 			switch (l->type) {
256 				case STACK_PARAM:
257 					/* nope, or yep */
258 					return 1;
259 				break;
260 
261 				case NUMERIC_FUNCTION_PARAM:
262 					if (l->num != r->num) {
263 						break;
264 					}
265 				ZEND_FALLTHROUGH;
266 
267 				case STR_PARAM:
268 					return (l->len == r->len) &&
269 							(memcmp(l->str, r->str, l->len) == SUCCESS);
270 
271 				case NUMERIC_PARAM:
272 					return (l->num == r->num);
273 
274 				case ADDR_PARAM:
275 					return (l->addr == r->addr);
276 
277 				case FILE_PARAM: {
278 					if (l->file.line == r->file.line) {
279 						size_t lengths[2] = {
280 							strlen(l->file.name), strlen(r->file.name)};
281 
282 						if (lengths[0] == lengths[1]) {
283 							if ((!l->num && !r->num) || (l->num == r->num)) {
284 								return (memcmp(
285 									l->file.name, r->file.name, lengths[0]) == SUCCESS);
286 							}
287 						}
288 					}
289 				} break;
290 
291 				case NUMERIC_METHOD_PARAM:
292 					if (l->num != r->num) {
293 						break;
294 					}
295 				ZEND_FALLTHROUGH;
296 
297 				case METHOD_PARAM: {
298 					size_t lengths[2] = {
299 						strlen(l->method.class), strlen(r->method.class)};
300 					if (lengths[0] == lengths[1]) {
301 						if (memcmp(l->method.class, r->method.class, lengths[0]) == SUCCESS) {
302 							lengths[0] = strlen(l->method.name);
303 							lengths[1] = strlen(r->method.name);
304 
305 							if (lengths[0] == lengths[1]) {
306 								return (memcmp(
307 									l->method.name, r->method.name, lengths[0]) == SUCCESS);
308 							}
309 						}
310 					}
311 				} break;
312 
313 				case EMPTY_PARAM:
314 					return 1;
315 
316 				default: {
317 					/* not yet */
318 				}
319 			}
320 		}
321 	}
322 	return 0;
323 } /* }}} */
324 
325 /* {{{ */
phpdbg_param_debug(const phpdbg_param_t * param,const char * msg)326 PHPDBG_API void phpdbg_param_debug(const phpdbg_param_t *param, const char *msg) {
327 	if (param && param->type) {
328 		switch (param->type) {
329 			case STR_PARAM:
330 				fprintf(stderr, "%s STR_PARAM(%s=%zu)\n", msg, param->str, param->len);
331 			break;
332 
333 			case ADDR_PARAM:
334 				fprintf(stderr, "%s ADDR_PARAM(" ZEND_ULONG_FMT ")\n", msg, param->addr);
335 			break;
336 
337 			case NUMERIC_FILE_PARAM:
338 				fprintf(stderr, "%s NUMERIC_FILE_PARAM(%s:#"ZEND_ULONG_FMT")\n", msg, param->file.name, param->file.line);
339 			break;
340 
341 			case FILE_PARAM:
342 				fprintf(stderr, "%s FILE_PARAM(%s:"ZEND_ULONG_FMT")\n", msg, param->file.name, param->file.line);
343 			break;
344 
345 			case METHOD_PARAM:
346 				fprintf(stderr, "%s METHOD_PARAM(%s::%s)\n", msg, param->method.class, param->method.name);
347 			break;
348 
349 			case NUMERIC_METHOD_PARAM:
350 				fprintf(stderr, "%s NUMERIC_METHOD_PARAM(%s::%s)\n", msg, param->method.class, param->method.name);
351 			break;
352 
353 			case NUMERIC_FUNCTION_PARAM:
354 				fprintf(stderr, "%s NUMERIC_FUNCTION_PARAM(%s::"ZEND_LONG_FMT")\n", msg, param->str, param->num);
355 			break;
356 
357 			case NUMERIC_PARAM:
358 				fprintf(stderr, "%s NUMERIC_PARAM("ZEND_LONG_FMT")\n", msg, param->num);
359 			break;
360 
361 			case COND_PARAM:
362 				fprintf(stderr, "%s COND_PARAM(%s=%zu)\n", msg, param->str, param->len);
363 			break;
364 
365 			case OP_PARAM:
366 				fprintf(stderr, "%s OP_PARAM(%s=%zu)\n", msg, param->str, param->len);
367 			break;
368 
369 			default: {
370 				/* not yet */
371 			}
372 		}
373 	}
374 } /* }}} */
375 
376 /* {{{ */
phpdbg_stack_free(phpdbg_param_t * stack)377 PHPDBG_API void phpdbg_stack_free(phpdbg_param_t *stack) {
378 	if (stack && stack->next) {
379 		phpdbg_param_t *remove = stack->next;
380 
381 		while (remove) {
382 			phpdbg_param_t *next = NULL;
383 
384 			if (remove->next)
385 				next = remove->next;
386 
387 			switch (remove->type) {
388 				case NUMERIC_METHOD_PARAM:
389 				case METHOD_PARAM:
390 					if (remove->method.class) {
391 						efree(remove->method.class);
392 					}
393 					if (remove->method.name) {
394 						efree(remove->method.name);
395 					}
396 				break;
397 
398 				case NUMERIC_FUNCTION_PARAM:
399 				case STR_PARAM:
400 				case OP_PARAM:
401 				case EVAL_PARAM:
402 				case SHELL_PARAM:
403 				case COND_PARAM:
404 				case RUN_PARAM:
405 					if (remove->str) {
406 						efree(remove->str);
407 					}
408 				break;
409 
410 				case NUMERIC_FILE_PARAM:
411 				case FILE_PARAM:
412 					if (remove->file.name) {
413 						efree(remove->file.name);
414 					}
415 				break;
416 
417 				default: {
418 					/* nothing */
419 				}
420 			}
421 
422 			free(remove);
423 			remove = NULL;
424 
425 			if (next)
426 				remove = next;
427 			else break;
428 		}
429 	}
430 
431 
432 	stack->next = NULL;
433 } /* }}} */
434 
435 /* {{{ */
phpdbg_stack_push(phpdbg_param_t * stack,phpdbg_param_t * param)436 PHPDBG_API void phpdbg_stack_push(phpdbg_param_t *stack, phpdbg_param_t *param) {
437 	phpdbg_param_t *next = calloc(1, sizeof(phpdbg_param_t));
438 
439 	if (!next) {
440 		return;
441 	}
442 
443 	*(next) = *(param);
444 
445 	next->next = NULL;
446 
447 	if (stack->top == NULL) {
448 		stack->top = next;
449 		next->top = NULL;
450 		stack->next = next;
451 	} else {
452 		stack->top->next = next;
453 		next->top = stack->top;
454 		stack->top = next;
455 	}
456 
457 	stack->len++;
458 } /* }}} */
459 
460 /* {{{ */
phpdbg_stack_separate(phpdbg_param_t * param)461 PHPDBG_API void phpdbg_stack_separate(phpdbg_param_t *param) {
462 	phpdbg_param_t *stack = calloc(1, sizeof(phpdbg_param_t));
463 
464 	stack->type = STACK_PARAM;
465 	stack->next = param->next;
466 	param->next = stack;
467 	stack->top = param->top;
468 } /* }}} */
469 
phpdbg_stack_verify(const phpdbg_command_t * command,phpdbg_param_t ** stack)470 PHPDBG_API int phpdbg_stack_verify(const phpdbg_command_t *command, phpdbg_param_t **stack) {
471 	if (command) {
472 		char buffer[128] = {0,};
473 		const phpdbg_param_t *top = (stack != NULL) ? *stack : NULL;
474 		const char *arg = command->args;
475 		zend_ulong least = 0L,
476 		           received = 0L,
477 		           current = 0L;
478 		bool optional = 0;
479 
480 		/* check for arg spec */
481 		if (!(arg) || !(*arg)) {
482 			if (!top || top->type == STACK_PARAM) {
483 				return SUCCESS;
484 			}
485 
486 			phpdbg_error("The command \"%s\" expected no arguments",
487 				phpdbg_command_name(command, buffer));
488 			return FAILURE;
489 		}
490 
491 		least = 0L;
492 
493 		/* count least amount of arguments */
494 		while (arg && *arg) {
495 			if (arg[0] == '|') {
496 				break;
497 			}
498 			least++;
499 			arg++;
500 		}
501 
502 		arg = command->args;
503 
504 #define verify_arg(e, a, t) if (!(a)) { \
505 	if (!optional) { \
506 		phpdbg_error("The command \"%s\" expected %s and got nothing at parameter "ZEND_ULONG_FMT, \
507 			phpdbg_command_name(command, buffer), \
508 			(e), \
509 			current); \
510 		return FAILURE;\
511 	} \
512 } else if ((a)->type != (t)) { \
513 	phpdbg_error("The command \"%s\" expected %s and got %s at parameter "ZEND_ULONG_FMT, \
514 		phpdbg_command_name(command, buffer), \
515 		(e),\
516 		phpdbg_get_param_type((a)), \
517 		current); \
518 	return FAILURE; \
519 }
520 
521 		while (arg && *arg) {
522 			if (top && top->type == STACK_PARAM) {
523 				break;
524 			}
525 
526 			current++;
527 
528 			switch (*arg) {
529 				case '|': {
530 					current--;
531 					optional = 1;
532 					arg++;
533 				} continue;
534 
535 				case 'i': verify_arg("raw input", top, STR_PARAM); break;
536 				case 's': verify_arg("string", top, STR_PARAM); break;
537 				case 'n': verify_arg("number", top, NUMERIC_PARAM); break;
538 				case 'm': verify_arg("method", top, METHOD_PARAM); break;
539 				case 'a': verify_arg("address", top, ADDR_PARAM); break;
540 				case 'f': verify_arg("file:line", top, FILE_PARAM); break;
541 				case 'c': verify_arg("condition", top, COND_PARAM); break;
542 				case 'o': verify_arg("opcode", top, OP_PARAM); break;
543 				case 'b': verify_arg("boolean", top, NUMERIC_PARAM); break;
544 
545 				case '*': { /* do nothing */ } break;
546 			}
547 
548 			if (top) {
549 				top = top->next;
550 			} else {
551 				break;
552 			}
553 
554 			received++;
555 			arg++;
556 		}
557 
558 #undef verify_arg
559 
560 		if ((received < least)) {
561 			phpdbg_error("The command \"%s\" expected at least "ZEND_ULONG_FMT" arguments (%s) and received "ZEND_ULONG_FMT,
562 				phpdbg_command_name(command, buffer),
563 				least,
564 				command->args,
565 				received);
566 			return FAILURE;
567 		}
568 	}
569 
570 	return SUCCESS;
571 }
572 
573 /* {{{ */
phpdbg_stack_resolve(const phpdbg_command_t * commands,const phpdbg_command_t * parent,phpdbg_param_t ** top)574 PHPDBG_API const phpdbg_command_t *phpdbg_stack_resolve(const phpdbg_command_t *commands, const phpdbg_command_t *parent, phpdbg_param_t **top) {
575 	const phpdbg_command_t *command = commands;
576 	phpdbg_param_t *name = *top;
577 	const phpdbg_command_t *matched[3] = {NULL, NULL, NULL};
578 	zend_ulong matches = 0L;
579 
580 	while (command && command->name && command->handler) {
581 		if (name->len == 1 || command->name_len >= name->len) {
582 			/* match single letter alias */
583 			if (command->alias && (name->len == 1)) {
584 				if (command->alias == (*name->str)) {
585 					matched[matches] = command;
586 					matches++;
587 				}
588 			} else {
589 				/* match full, case insensitive, command name */
590 				if (strncasecmp(command->name, name->str, name->len) == SUCCESS) {
591 					if (matches < 3) {
592 						/* only allow abbreviating commands that can be aliased */
593 						if ((name->len != command->name_len && command->alias) || name->len == command->name_len) {
594 							matched[matches] = command;
595 							matches++;
596 						}
597 
598 						/* exact match */
599 						if (name->len == command->name_len) {
600 							break;
601 						}
602 					} else {
603 						break;
604 					}
605 				}
606 			}
607 		}
608 
609 		command++;
610 	}
611 
612 	switch (matches) {
613 		case 0:
614 			if (parent) {
615 				phpdbg_error("The command \"%s %s\" could not be found", parent->name, name->str);
616 			} else {
617 				phpdbg_error("The command \"%s\" could not be found", name->str);
618 			}
619 			return parent;
620 
621 		case 1:
622 			(*top) = (*top)->next;
623 
624 			command = matched[0];
625 			break;
626 
627 		default: {
628 			char *list = NULL;
629 			uint32_t it = 0;
630 			size_t pos = 0;
631 
632 			while (it < matches) {
633 				if (!list) {
634 					list = emalloc(matched[it]->name_len + 1 + (it + 1 < matches ? sizeof(", ") - 1 : 0));
635 				} else {
636 					list = erealloc(list, (pos + matched[it]->name_len) + 1 + (it + 1 < matches ? sizeof(", ") - 1 : 0));
637 				}
638 				memcpy(&list[pos], matched[it]->name, matched[it]->name_len);
639 				pos += matched[it]->name_len;
640 				if ((it + 1) < matches) {
641 					memcpy(&list[pos], ", ", sizeof(", ") - 1);
642 					pos += (sizeof(", ") - 1);
643 				}
644 
645 				list[pos] = 0;
646 				it++;
647 			}
648 
649 			/* ", " separated matches */
650 			phpdbg_error("The command \"%s\" is ambiguous, matching "ZEND_ULONG_FMT" commands (%s)", name->str, matches, list);
651 			efree(list);
652 
653 			return NULL;
654 		}
655 	}
656 
657 	if (command->subs && (*top) && ((*top)->type == STR_PARAM)) {
658 		return phpdbg_stack_resolve(command->subs, command, top);
659 	} else {
660 		return command;
661 	}
662 
663 	return NULL;
664 } /* }}} */
665 
phpdbg_internal_stack_execute(phpdbg_param_t * stack,bool allow_async_unsafe)666 static int phpdbg_internal_stack_execute(phpdbg_param_t *stack, bool allow_async_unsafe) {
667 	const phpdbg_command_t *handler = NULL;
668 	phpdbg_param_t *top = (phpdbg_param_t *) stack->next;
669 
670 	switch (top->type) {
671 		case EVAL_PARAM:
672 			phpdbg_activate_err_buf(0);
673 			phpdbg_free_err_buf();
674 			return PHPDBG_COMMAND_HANDLER(ev)(top);
675 
676 		case RUN_PARAM:
677 			if (!allow_async_unsafe) {
678 				phpdbg_error("run command is disallowed during hard interrupt");
679 			}
680 			phpdbg_activate_err_buf(0);
681 			phpdbg_free_err_buf();
682 			return PHPDBG_COMMAND_HANDLER(run)(top);
683 
684 		case SHELL_PARAM:
685 			if (!allow_async_unsafe) {
686 				phpdbg_error("sh command is disallowed during hard interrupt");
687 				return FAILURE;
688 			}
689 			phpdbg_activate_err_buf(0);
690 			phpdbg_free_err_buf();
691 			return PHPDBG_COMMAND_HANDLER(sh)(top);
692 
693 		case STR_PARAM: {
694 			handler = phpdbg_stack_resolve(phpdbg_prompt_commands, NULL, &top);
695 
696 			if (handler) {
697 				if (!allow_async_unsafe && !(handler->flags & PHPDBG_ASYNC_SAFE)) {
698 					phpdbg_error("%s command is disallowed during hard interrupt", handler->name);
699 					return FAILURE;
700 				}
701 
702 				if (phpdbg_stack_verify(handler, &top) == SUCCESS) {
703 					phpdbg_activate_err_buf(0);
704 					phpdbg_free_err_buf();
705 					return handler->handler(top);
706 				}
707 			}
708 		} return FAILURE;
709 
710 		default:
711 			phpdbg_error("The first parameter makes no sense !");
712 			return FAILURE;
713 	}
714 
715 	return SUCCESS;
716 } /* }}} */
717 
718 /* {{{ */
phpdbg_stack_execute(phpdbg_param_t * stack,bool allow_async_unsafe)719 PHPDBG_API int phpdbg_stack_execute(phpdbg_param_t *stack, bool allow_async_unsafe) {
720 	phpdbg_param_t *top = stack;
721 
722 	if (stack->type != STACK_PARAM) {
723 		phpdbg_error("The passed argument was not a stack !");
724 		return FAILURE;
725 	}
726 
727 	if (!stack->len) {
728 		phpdbg_error("The stack contains nothing !");
729 		return FAILURE;
730 	}
731 
732 	do {
733 		if (top->type == STACK_PARAM) {
734 			int result;
735 			if ((result = phpdbg_internal_stack_execute(top, allow_async_unsafe)) != SUCCESS) {
736 				return result;
737 			}
738 		}
739 	} while ((top = top->next));
740 
741 	return SUCCESS;
742 } /* }}} */
743 
phpdbg_read_input(const char * buffered)744 PHPDBG_API char *phpdbg_read_input(const char *buffered) /* {{{ */
745 {
746 	char buf[PHPDBG_MAX_CMD];
747 	char *buffer = NULL;
748 
749 	if ((PHPDBG_G(flags) & (PHPDBG_IS_STOPPING | PHPDBG_IS_RUNNING)) != PHPDBG_IS_STOPPING) {
750 		if (buffered == NULL) {
751 #ifdef HAVE_PHPDBG_READLINE
752 # ifdef HAVE_UNISTD_H
753 			/* EOF makes readline write prompt again in local console mode and
754 			ignored if compiled without readline integration. */
755 			if (!isatty(PHPDBG_G(io)[PHPDBG_STDIN].fd)) {
756 				char buf[PHPDBG_MAX_CMD];
757 				phpdbg_write("%s", phpdbg_get_prompt());
758 				phpdbg_consume_stdin_line(buf);
759 				buffer = estrdup(buf);
760 			} else
761 # endif
762 			{
763 				char *cmd = readline(phpdbg_get_prompt());
764 				PHPDBG_G(last_was_newline) = 1;
765 
766 				if (!cmd) {
767 					PHPDBG_G(flags) |= PHPDBG_IS_QUITTING;
768 					zend_bailout();
769 				}
770 
771 				add_history(cmd);
772 				buffer = estrdup(cmd);
773 				free(cmd);
774 			}
775 #else
776 			phpdbg_write("%s", phpdbg_get_prompt());
777 			phpdbg_consume_stdin_line(buf);
778 			buffer = estrdup(buf);
779 #endif
780 		} else {
781 			buffer = estrdup(buffered);
782 		}
783 	}
784 
785 	if (buffer && isspace(*buffer)) {
786 		char *trimmed = buffer;
787 		while (isspace(*trimmed))
788 			trimmed++;
789 
790 		trimmed = estrdup(trimmed);
791 		efree(buffer);
792 		buffer = trimmed;
793 	}
794 
795 	if (buffer && strlen(buffer)) {
796 		if (PHPDBG_G(buffer)) {
797 			free(PHPDBG_G(buffer));
798 		}
799 		PHPDBG_G(buffer) = strdup(buffer);
800 	} else if (PHPDBG_G(buffer)) {
801 		if (buffer) {
802 			efree(buffer);
803 		}
804 		buffer = estrdup(PHPDBG_G(buffer));
805 	}
806 
807 	return buffer;
808 } /* }}} */
809 
phpdbg_destroy_input(char ** input)810 PHPDBG_API void phpdbg_destroy_input(char **input) /*{{{ */
811 {
812 	efree(*input);
813 } /* }}} */
814 
phpdbg_ask_user_permission(const char * question)815 PHPDBG_API int phpdbg_ask_user_permission(const char *question) {
816 	char buf[PHPDBG_MAX_CMD];
817 	phpdbg_out("%s", question);
818 	phpdbg_out(" (type y or n): ");
819 
820 	while (1) {
821 		phpdbg_consume_stdin_line(buf);
822 		if ((buf[1] == '\n' || (buf[1] == '\r' && buf[2] == '\n')) && (buf[0] == 'y' || buf[0] == 'n')) {
823 			if (buf[0] == 'y') {
824 				return SUCCESS;
825 			}
826 			return FAILURE;
827 		}
828 		phpdbg_out("Please enter either y (yes) or n (no): ");
829 	}
830 
831 	return SUCCESS;
832 }
833