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