Handle invalid strings from game scripts more leniently
[openttd/fttd.git] / src / console.cpp
blob72d01c2225b12d39665dc0eeb21ada49a8ac6545
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file console.cpp Handling of the in-game console. */
12 #include "stdafx.h"
13 #include "console_internal.h"
14 #include "network/network.h"
15 #include "network/network_func.h"
16 #include "network/network_admin.h"
17 #include "debug.h"
18 #include "console_func.h"
19 #include "settings_type.h"
21 #include <stdarg.h>
23 static const uint ICON_TOKEN_COUNT = 20; ///< Maximum number of tokens in one command
24 static const uint ICON_MAX_ALIAS_LINES = 40; ///< Maximum number of commands executed by one alias
26 /* console parser */
27 IConsoleCmd *_iconsole_cmds; ///< list of registered commands
28 IConsoleAlias *_iconsole_aliases; ///< list of registered aliases
30 FILE *_iconsole_output_file;
32 void IConsoleInit()
34 _iconsole_output_file = NULL;
35 #ifdef ENABLE_NETWORK /* Initialize network only variables */
36 _redirect_console_to_client = INVALID_CLIENT_ID;
37 _redirect_console_to_admin = INVALID_ADMIN_ID;
38 #endif
40 IConsoleGUIInit();
42 IConsoleStdLibRegister();
45 static void IConsoleWriteToLogFile(const char *string)
47 if (_iconsole_output_file != NULL) {
48 /* if there is an console output file ... also print it there */
49 const char *header = GetLogPrefix();
50 if ((strlen(header) != 0 && fwrite(header, strlen(header), 1, _iconsole_output_file) != 1) ||
51 fwrite(string, strlen(string), 1, _iconsole_output_file) != 1 ||
52 fwrite("\n", 1, 1, _iconsole_output_file) != 1) {
53 fclose(_iconsole_output_file);
54 _iconsole_output_file = NULL;
55 IConsolePrintF(CC_DEFAULT, "cannot write to log file");
60 bool CloseConsoleLogIfActive()
62 if (_iconsole_output_file != NULL) {
63 IConsolePrintF(CC_DEFAULT, "file output complete");
64 fclose(_iconsole_output_file);
65 _iconsole_output_file = NULL;
66 return true;
69 return false;
72 void IConsoleFree()
74 IConsoleGUIFree();
75 CloseConsoleLogIfActive();
78 /**
79 * Handle the printing of text entered into the console or redirected there
80 * by any other means. Text can be redirected to other clients in a network game
81 * as well as to a logfile. If the network server is a dedicated server, all activities
82 * are also logged. All lines to print are added to a temporary buffer which can be
83 * used as a history to print them onscreen
84 * @param colour_code the colour of the command. Red in case of errors, etc.
85 * @param string the message entered or output on the console (notice, error, etc.)
87 void IConsolePrint(TextColour colour_code, const char *string)
89 assert(IsValidConsoleColour(colour_code));
91 char *str;
92 #ifdef ENABLE_NETWORK
93 if (_redirect_console_to_client != INVALID_CLIENT_ID) {
94 /* Redirect the string to the client */
95 NetworkServerSendRcon(_redirect_console_to_client, colour_code, string);
96 return;
99 if (_redirect_console_to_admin != INVALID_ADMIN_ID) {
100 NetworkServerSendAdminRcon(_redirect_console_to_admin, colour_code, string);
101 return;
103 #endif
105 /* Create a copy of the string, strip if of colours and invalid
106 * characters and (when applicable) assign it to the console buffer */
107 str = strdup(string);
108 str_strip_colours(str);
109 str_validate(str, str + strlen(str));
111 if (_network_dedicated) {
112 #ifdef ENABLE_NETWORK
113 NetworkAdminConsole("console", str);
114 #endif /* ENABLE_NETWORK */
115 fprintf(stdout, "%s%s\n", GetLogPrefix(), str);
116 fflush(stdout);
117 IConsoleWriteToLogFile(str);
118 free(str); // free duplicated string since it's not used anymore
119 return;
122 IConsoleWriteToLogFile(str);
123 IConsoleGUIPrint(colour_code, str);
127 * Handle the printing of text entered into the console or redirected there
128 * by any other means. Uses printf() style format, for more information look
129 * at IConsolePrint()
131 void CDECL IConsolePrintF(TextColour colour_code, const char *format, ...)
133 assert(IsValidConsoleColour(colour_code));
135 va_list va;
136 char buf[ICON_MAX_STREAMSIZE];
138 va_start(va, format);
139 vsnprintf(buf, sizeof(buf), format, va);
140 va_end(va);
142 IConsolePrint(colour_code, buf);
146 * It is possible to print debugging information to the console,
147 * which is achieved by using this function. Can only be used by
148 * debug() in debug.cpp. You need at least a level 2 (developer) for debugging
149 * messages to show up
150 * @param dbg debugging category
151 * @param string debugging message
153 void IConsoleDebug(const char *dbg, const char *string)
155 if (_settings_client.gui.developer <= 1) return;
156 IConsolePrintF(CC_DEBUG, "dbg: [%s] %s", dbg, string);
160 * It is possible to print warnings to the console. These are mostly
161 * errors or mishaps, but non-fatal. You need at least a level 1 (developer) for
162 * debugging messages to show up
164 void IConsoleWarning(const char *string)
166 if (_settings_client.gui.developer == 0) return;
167 IConsolePrintF(CC_WARNING, "WARNING: %s", string);
171 * It is possible to print error information to the console. This can include
172 * game errors, or errors in general you would want the user to notice
174 void IConsoleError(const char *string)
176 IConsolePrintF(CC_ERROR, "ERROR: %s", string);
180 * Change a string into its number representation. Supports
181 * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false'
182 * @param *value the variable a successful conversion will be put in
183 * @param *arg the string to be converted
184 * @return Return true on success or false on failure
186 bool GetArgumentInteger(uint32 *value, const char *arg)
188 char *endptr;
190 if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) {
191 *value = 1;
192 return true;
194 if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) {
195 *value = 0;
196 return true;
199 *value = strtoul(arg, &endptr, 0);
200 return arg != endptr;
204 * Add an item to an alphabetically sorted list.
205 * @param base first item of the list
206 * @param item_new the item to add
208 template<class T>
209 void IConsoleAddSorted(T **base, T *item_new)
211 if (*base == NULL) {
212 *base = item_new;
213 return;
216 T *item_before = NULL;
217 T *item = *base;
218 /* The list is alphabetically sorted, insert the new item at the correct location */
219 while (item != NULL) {
220 if (strcmp(item->name, item_new->name) > 0) break; // insert here
222 item_before = item;
223 item = item->next;
226 if (item_before == NULL) {
227 *base = item_new;
228 } else {
229 item_before->next = item_new;
232 item_new->next = item;
236 * Remove underscores from a string; the string will be modified!
237 * @param name The string to remove the underscores from.
238 * @return #name.
240 char *RemoveUnderscores(char *name)
242 char *q = name;
243 for (const char *p = name; *p != '\0'; p++) {
244 if (*p != '_') *q++ = *p;
246 *q = '\0';
247 return name;
251 * Register a new command to be used in the console
252 * @param name name of the command that will be used
253 * @param proc function that will be called upon execution of command
255 void IConsoleCmdRegister(const char *name, IConsoleCmdProc *proc, IConsoleHook *hook)
257 IConsoleCmd *item_new = MallocT<IConsoleCmd>(1);
258 item_new->name = RemoveUnderscores(strdup(name));
259 item_new->next = NULL;
260 item_new->proc = proc;
261 item_new->hook = hook;
263 IConsoleAddSorted(&_iconsole_cmds, item_new);
267 * Find the command pointed to by its string
268 * @param name command to be found
269 * @return return Cmdstruct of the found command, or NULL on failure
271 IConsoleCmd *IConsoleCmdGet(const char *name)
273 IConsoleCmd *item;
275 for (item = _iconsole_cmds; item != NULL; item = item->next) {
276 if (strcmp(item->name, name) == 0) return item;
278 return NULL;
282 * Register a an alias for an already existing command in the console
283 * @param name name of the alias that will be used
284 * @param cmd name of the command that 'name' will be alias of
286 void IConsoleAliasRegister(const char *name, const char *cmd)
288 if (IConsoleAliasGet(name) != NULL) {
289 IConsoleError("an alias with this name already exists; insertion aborted");
290 return;
293 char *new_alias = RemoveUnderscores(strdup(name));
294 char *cmd_aliased = strdup(cmd);
295 IConsoleAlias *item_new = MallocT<IConsoleAlias>(1);
297 item_new->next = NULL;
298 item_new->cmdline = cmd_aliased;
299 item_new->name = new_alias;
301 IConsoleAddSorted(&_iconsole_aliases, item_new);
305 * Find the alias pointed to by its string
306 * @param name alias to be found
307 * @return return Aliasstruct of the found alias, or NULL on failure
309 IConsoleAlias *IConsoleAliasGet(const char *name)
311 IConsoleAlias *item;
313 for (item = _iconsole_aliases; item != NULL; item = item->next) {
314 if (strcmp(item->name, name) == 0) return item;
317 return NULL;
320 /** copy in an argument into the aliasstream */
321 static inline int IConsoleCopyInParams(char *dst, const char *src, uint bufpos)
323 /* len is the amount of bytes to add excluding the '\0'-termination */
324 int len = min(ICON_MAX_STREAMSIZE - bufpos - 1, (uint)strlen(src));
325 strecpy(dst, src, dst + len);
327 return len;
331 * An alias is just another name for a command, or for more commands
332 * Execute it as well.
333 * @param *alias is the alias of the command
334 * @param tokencount the number of parameters passed
335 * @param *tokens are the parameters given to the original command (0 is the first param)
337 static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char *tokens[ICON_TOKEN_COUNT])
339 const char *cmdptr;
340 char *aliases[ICON_MAX_ALIAS_LINES], aliasstream[ICON_MAX_STREAMSIZE];
341 uint i;
342 uint a_index, astream_i;
344 memset(&aliases, 0, sizeof(aliases));
345 memset(&aliasstream, 0, sizeof(aliasstream));
347 DEBUG(console, 6, "Requested command is an alias; parsing...");
349 aliases[0] = aliasstream;
350 for (cmdptr = alias->cmdline, a_index = 0, astream_i = 0; *cmdptr != '\0'; cmdptr++) {
351 if (a_index >= lengthof(aliases) || astream_i >= lengthof(aliasstream)) break;
353 switch (*cmdptr) {
354 case '\'': // ' will double for ""
355 aliasstream[astream_i++] = '"';
356 break;
358 case ';': // Cmd separator, start new command
359 aliasstream[astream_i] = '\0';
360 aliases[++a_index] = &aliasstream[++astream_i];
361 cmdptr++;
362 break;
364 case '%': // Some or all parameters
365 cmdptr++;
366 switch (*cmdptr) {
367 case '+': { // All parameters separated: "[param 1]" "[param 2]"
368 for (i = 0; i != tokencount; i++) {
369 aliasstream[astream_i++] = '"';
370 astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[i], astream_i);
371 aliasstream[astream_i++] = '"';
372 aliasstream[astream_i++] = ' ';
374 break;
377 case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
378 aliasstream[astream_i++] = '"';
379 for (i = 0; i != tokencount; i++) {
380 astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[i], astream_i);
381 aliasstream[astream_i++] = ' ';
383 aliasstream[astream_i++] = '"';
384 break;
387 default: { // One specific parameter: %A = [param 1] %B = [param 2] ...
388 int param = *cmdptr - 'A';
390 if (param < 0 || param >= tokencount) {
391 IConsoleError("too many or wrong amount of parameters passed to alias, aborting");
392 IConsolePrintF(CC_WARNING, "Usage of alias '%s': %s", alias->name, alias->cmdline);
393 return;
396 aliasstream[astream_i++] = '"';
397 astream_i += IConsoleCopyInParams(&aliasstream[astream_i], tokens[param], astream_i);
398 aliasstream[astream_i++] = '"';
399 break;
402 break;
404 default:
405 aliasstream[astream_i++] = *cmdptr;
406 break;
410 for (i = 0; i <= a_index; i++) IConsoleCmdExec(aliases[i]); // execute each alias in turn
414 * Execute a given command passed to us. First chop it up into
415 * individual tokens (separated by spaces), then execute it if possible
416 * @param cmdstr string to be parsed and executed
418 void IConsoleCmdExec(const char *cmdstr)
420 const char *cmdptr;
421 char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE];
422 uint t_index, tstream_i;
424 bool longtoken = false;
425 bool foundtoken = false;
427 if (cmdstr[0] == '#') return; // comments
429 for (cmdptr = cmdstr; *cmdptr != '\0'; cmdptr++) {
430 if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) {
431 IConsoleError("command contains malformed characters, aborting");
432 IConsolePrintF(CC_ERROR, "ERROR: command was: '%s'", cmdstr);
433 return;
437 DEBUG(console, 4, "Executing cmdline: '%s'", cmdstr);
439 memset(&tokens, 0, sizeof(tokens));
440 memset(&tokenstream, 0, sizeof(tokenstream));
442 /* 1. Split up commandline into tokens, separated by spaces, commands
443 * enclosed in "" are taken as one token. We can only go as far as the amount
444 * of characters in our stream or the max amount of tokens we can handle */
445 for (cmdptr = cmdstr, t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) {
446 if (t_index >= lengthof(tokens) || tstream_i >= lengthof(tokenstream)) break;
448 switch (*cmdptr) {
449 case ' ': // Token separator
450 if (!foundtoken) break;
452 if (longtoken) {
453 tokenstream[tstream_i] = *cmdptr;
454 } else {
455 tokenstream[tstream_i] = '\0';
456 foundtoken = false;
459 tstream_i++;
460 break;
461 case '"': // Tokens enclosed in "" are one token
462 longtoken = !longtoken;
463 if (!foundtoken) {
464 tokens[t_index++] = &tokenstream[tstream_i];
465 foundtoken = true;
467 break;
468 case '\\': // Escape character for ""
469 if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) {
470 tokenstream[tstream_i++] = *++cmdptr;
471 break;
473 /* FALL THROUGH */
474 default: // Normal character
475 tokenstream[tstream_i++] = *cmdptr;
477 if (!foundtoken) {
478 tokens[t_index++] = &tokenstream[tstream_i - 1];
479 foundtoken = true;
481 break;
485 for (uint i = 0; tokens[i] != NULL; i++) {
486 DEBUG(console, 8, "Token %d is: '%s'", i, tokens[i]);
489 if (tokens[0] == '\0') return; // don't execute empty commands
490 /* 2. Determine type of command (cmd or alias) and execute
491 * First try commands, then aliases. Execute
492 * the found action taking into account its hooking code
494 RemoveUnderscores(tokens[0]);
495 IConsoleCmd *cmd = IConsoleCmdGet(tokens[0]);
496 if (cmd != NULL) {
497 ConsoleHookResult chr = (cmd->hook == NULL ? CHR_ALLOW : cmd->hook(true));
498 switch (chr) {
499 case CHR_ALLOW:
500 if (!cmd->proc(t_index, tokens)) { // index started with 0
501 cmd->proc(0, NULL); // if command failed, give help
503 return;
505 case CHR_DISALLOW: return;
506 case CHR_HIDE: break;
510 t_index--;
511 IConsoleAlias *alias = IConsoleAliasGet(tokens[0]);
512 if (alias != NULL) {
513 IConsoleAliasExec(alias, t_index, &tokens[1]);
514 return;
517 IConsoleError("command not found");