cmdline.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. /**
  2. * WinPR: Windows Portable Runtime
  3. * Command-Line Utils
  4. *
  5. * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. */
  19. #ifdef HAVE_CONFIG_H
  20. #include "config.h"
  21. #endif
  22. #include <winpr/crt.h>
  23. #include <winpr/cmdline.h>
  24. #include "../log.h"
  25. #define TAG WINPR_TAG("commandline")
  26. /**
  27. * Command-line syntax: some basic concepts:
  28. * https://pythonconquerstheuniverse.wordpress.com/2010/07/25/command-line-syntax-some-basic-concepts/
  29. */
  30. /**
  31. * Command-Line Syntax:
  32. *
  33. * <sigil><keyword><separator><value>
  34. *
  35. * <sigil>: '/' or '-' or ('+' | '-')
  36. *
  37. * <keyword>: option, named argument, flag
  38. *
  39. * <separator>: ':' or '='
  40. *
  41. * <value>: argument value
  42. *
  43. */
  44. static void log_error(DWORD flags, LPCSTR message, int index, LPCSTR argv)
  45. {
  46. if ((flags & COMMAND_LINE_SILENCE_PARSER) == 0)
  47. WLog_ERR(TAG, message, index, argv);
  48. }
  49. int CommandLineParseArgumentsA(int argc, LPSTR* argv, COMMAND_LINE_ARGUMENT_A* options, DWORD flags,
  50. void* context, COMMAND_LINE_PRE_FILTER_FN_A preFilter,
  51. COMMAND_LINE_POST_FILTER_FN_A postFilter)
  52. {
  53. int i, j;
  54. int status;
  55. int count;
  56. size_t length;
  57. BOOL notescaped;
  58. const char* sigil;
  59. size_t sigil_length;
  60. char* keyword;
  61. size_t keyword_length;
  62. SSIZE_T keyword_index;
  63. char* separator;
  64. char* value;
  65. int toggle;
  66. status = 0;
  67. notescaped = FALSE;
  68. if (!argv)
  69. return status;
  70. if (argc == 1)
  71. {
  72. if (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD)
  73. status = 0;
  74. else
  75. status = COMMAND_LINE_STATUS_PRINT_HELP;
  76. return status;
  77. }
  78. for (i = 1; i < argc; i++)
  79. {
  80. BOOL found = FALSE;
  81. BOOL escaped = TRUE;
  82. if (preFilter)
  83. {
  84. count = preFilter(context, i, argc, argv);
  85. if (count < 0)
  86. {
  87. log_error(flags, "Failed for index %d [%s]: PreFilter rule could not be applied", i,
  88. argv[i]);
  89. status = COMMAND_LINE_ERROR;
  90. return status;
  91. }
  92. if (count > 0)
  93. {
  94. i += (count - 1);
  95. continue;
  96. }
  97. }
  98. sigil = argv[i];
  99. length = strlen(argv[i]);
  100. if ((sigil[0] == '/') && (flags & COMMAND_LINE_SIGIL_SLASH))
  101. {
  102. sigil_length = 1;
  103. }
  104. else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_DASH))
  105. {
  106. sigil_length = 1;
  107. if (length > 2)
  108. {
  109. if ((sigil[1] == '-') && (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH))
  110. sigil_length = 2;
  111. }
  112. }
  113. else if ((sigil[0] == '+') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
  114. {
  115. sigil_length = 1;
  116. }
  117. else if ((sigil[0] == '-') && (flags & COMMAND_LINE_SIGIL_PLUS_MINUS))
  118. {
  119. sigil_length = 1;
  120. }
  121. else if (flags & COMMAND_LINE_SIGIL_NONE)
  122. {
  123. sigil_length = 0;
  124. }
  125. else if (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED)
  126. {
  127. if (notescaped)
  128. {
  129. log_error(flags, "Failed at index %d [%s]: Unescaped sigil", i, argv[i]);
  130. return COMMAND_LINE_ERROR;
  131. }
  132. sigil_length = 0;
  133. escaped = FALSE;
  134. notescaped = TRUE;
  135. }
  136. else
  137. {
  138. log_error(flags, "Failed at index %d [%s]: Invalid sigil", i, argv[i]);
  139. return COMMAND_LINE_ERROR;
  140. }
  141. if ((sigil_length > 0) || (flags & COMMAND_LINE_SIGIL_NONE) ||
  142. (flags & COMMAND_LINE_SIGIL_NOT_ESCAPED))
  143. {
  144. if (length < (sigil_length + 1))
  145. {
  146. if ((flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD))
  147. continue;
  148. return COMMAND_LINE_ERROR_NO_KEYWORD;
  149. }
  150. keyword_index = sigil_length;
  151. keyword = &argv[i][keyword_index];
  152. toggle = -1;
  153. if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
  154. {
  155. if (strncmp(keyword, "enable-", 7) == 0)
  156. {
  157. toggle = TRUE;
  158. keyword_index += 7;
  159. keyword = &argv[i][keyword_index];
  160. }
  161. else if (strncmp(keyword, "disable-", 8) == 0)
  162. {
  163. toggle = FALSE;
  164. keyword_index += 8;
  165. keyword = &argv[i][keyword_index];
  166. }
  167. }
  168. separator = NULL;
  169. if ((flags & COMMAND_LINE_SEPARATOR_COLON) && (!separator))
  170. separator = strchr(keyword, ':');
  171. if ((flags & COMMAND_LINE_SEPARATOR_EQUAL) && (!separator))
  172. separator = strchr(keyword, '=');
  173. if (separator)
  174. {
  175. SSIZE_T separator_index = (separator - argv[i]);
  176. SSIZE_T value_index = separator_index + 1;
  177. keyword_length = (separator - keyword);
  178. value = &argv[i][value_index];
  179. }
  180. else
  181. {
  182. keyword_length = (length - keyword_index);
  183. value = NULL;
  184. }
  185. if (!escaped)
  186. continue;
  187. for (j = 0; options[j].Name != NULL; j++)
  188. {
  189. BOOL match = FALSE;
  190. if (strncmp(options[j].Name, keyword, keyword_length) == 0)
  191. {
  192. if (strlen(options[j].Name) == keyword_length)
  193. match = TRUE;
  194. }
  195. if ((!match) && (options[j].Alias != NULL))
  196. {
  197. if (strncmp(options[j].Alias, keyword, keyword_length) == 0)
  198. {
  199. if (strlen(options[j].Alias) == keyword_length)
  200. match = TRUE;
  201. }
  202. }
  203. if (!match)
  204. continue;
  205. found = match;
  206. options[j].Index = i;
  207. if ((flags & COMMAND_LINE_SEPARATOR_SPACE) && ((i + 1) < argc))
  208. {
  209. BOOL argument;
  210. int value_present = 1;
  211. if (flags & COMMAND_LINE_SIGIL_DASH)
  212. {
  213. if (strncmp(argv[i + 1], "-", 1) == 0)
  214. value_present = 0;
  215. }
  216. if (flags & COMMAND_LINE_SIGIL_DOUBLE_DASH)
  217. {
  218. if (strncmp(argv[i + 1], "--", 2) == 0)
  219. value_present = 0;
  220. }
  221. if (flags & COMMAND_LINE_SIGIL_SLASH)
  222. {
  223. if (strncmp(argv[i + 1], "/", 1) == 0)
  224. value_present = 0;
  225. }
  226. if ((options[j].Flags & COMMAND_LINE_VALUE_REQUIRED) ||
  227. (options[j].Flags & COMMAND_LINE_VALUE_OPTIONAL))
  228. argument = TRUE;
  229. else
  230. argument = FALSE;
  231. if (value_present && argument)
  232. {
  233. i++;
  234. value = argv[i];
  235. }
  236. else if (!value_present && (options[j].Flags & COMMAND_LINE_VALUE_OPTIONAL))
  237. {
  238. value = NULL;
  239. }
  240. else if (!value_present && argument)
  241. {
  242. log_error(flags, "Failed at index %d [%s]: Argument required", i, argv[i]);
  243. return COMMAND_LINE_ERROR;
  244. }
  245. }
  246. if (!(flags & COMMAND_LINE_SEPARATOR_SPACE))
  247. {
  248. if (value && (options[j].Flags & COMMAND_LINE_VALUE_FLAG))
  249. {
  250. log_error(flags, "Failed at index %d [%s]: Unexpected value", i, argv[i]);
  251. return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
  252. }
  253. }
  254. else
  255. {
  256. if (value && (options[j].Flags & COMMAND_LINE_VALUE_FLAG))
  257. {
  258. i--;
  259. value = NULL;
  260. }
  261. }
  262. if (!value && (options[j].Flags & COMMAND_LINE_VALUE_REQUIRED))
  263. {
  264. log_error(flags, "Failed at index %d [%s]: Missing value", i, argv[i]);
  265. status = COMMAND_LINE_ERROR_MISSING_VALUE;
  266. return status;
  267. }
  268. options[j].Flags |= COMMAND_LINE_ARGUMENT_PRESENT;
  269. if (value)
  270. {
  271. if (options[j].Flags & (COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_VALUE_BOOL))
  272. {
  273. log_error(flags, "Failed at index %d [%s]: Unexpected value", i, argv[i]);
  274. return COMMAND_LINE_ERROR_UNEXPECTED_VALUE;
  275. }
  276. options[j].Value = value;
  277. options[j].Flags |= COMMAND_LINE_VALUE_PRESENT;
  278. }
  279. else
  280. {
  281. if (options[j].Flags & COMMAND_LINE_VALUE_FLAG)
  282. {
  283. options[j].Value = (LPSTR)1;
  284. options[j].Flags |= COMMAND_LINE_VALUE_PRESENT;
  285. }
  286. else if (options[j].Flags & COMMAND_LINE_VALUE_BOOL)
  287. {
  288. if (flags & COMMAND_LINE_SIGIL_ENABLE_DISABLE)
  289. {
  290. if (toggle == -1)
  291. options[j].Value = BoolValueTrue;
  292. else if (!toggle)
  293. options[j].Value = BoolValueFalse;
  294. else
  295. options[j].Value = BoolValueTrue;
  296. }
  297. else
  298. {
  299. if (sigil[0] == '+')
  300. options[j].Value = BoolValueTrue;
  301. else if (sigil[0] == '-')
  302. options[j].Value = BoolValueFalse;
  303. else
  304. options[j].Value = BoolValueTrue;
  305. }
  306. options[j].Flags |= COMMAND_LINE_VALUE_PRESENT;
  307. }
  308. }
  309. if (postFilter)
  310. {
  311. count = postFilter(context, &options[j]);
  312. if (count < 0)
  313. {
  314. log_error(flags,
  315. "Failed at index %d [%s]: PostFilter rule could not be applied",
  316. i, argv[i]);
  317. status = COMMAND_LINE_ERROR;
  318. return status;
  319. }
  320. }
  321. if (options[j].Flags & COMMAND_LINE_PRINT)
  322. return COMMAND_LINE_STATUS_PRINT;
  323. else if (options[j].Flags & COMMAND_LINE_PRINT_HELP)
  324. return COMMAND_LINE_STATUS_PRINT_HELP;
  325. else if (options[j].Flags & COMMAND_LINE_PRINT_VERSION)
  326. return COMMAND_LINE_STATUS_PRINT_VERSION;
  327. else if (options[j].Flags & COMMAND_LINE_PRINT_BUILDCONFIG)
  328. return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
  329. }
  330. if (!found && (flags & COMMAND_LINE_IGN_UNKNOWN_KEYWORD) == 0)
  331. {
  332. log_error(flags, "Failed at index %d [%s]: Unexpected keyword", i, argv[i]);
  333. return COMMAND_LINE_ERROR_NO_KEYWORD;
  334. }
  335. }
  336. }
  337. return status;
  338. }
  339. int CommandLineParseArgumentsW(int argc, LPWSTR* argv, COMMAND_LINE_ARGUMENT_W* options,
  340. DWORD flags, void* context, COMMAND_LINE_PRE_FILTER_FN_W preFilter,
  341. COMMAND_LINE_POST_FILTER_FN_W postFilter)
  342. {
  343. return 0;
  344. }
  345. int CommandLineClearArgumentsA(COMMAND_LINE_ARGUMENT_A* options)
  346. {
  347. size_t i;
  348. for (i = 0; options[i].Name != NULL; i++)
  349. {
  350. options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
  351. options[i].Value = NULL;
  352. }
  353. return 0;
  354. }
  355. int CommandLineClearArgumentsW(COMMAND_LINE_ARGUMENT_W* options)
  356. {
  357. int i;
  358. for (i = 0; options[i].Name != NULL; i++)
  359. {
  360. options[i].Flags &= COMMAND_LINE_INPUT_FLAG_MASK;
  361. options[i].Value = NULL;
  362. }
  363. return 0;
  364. }
  365. COMMAND_LINE_ARGUMENT_A* CommandLineFindArgumentA(COMMAND_LINE_ARGUMENT_A* options, LPCSTR Name)
  366. {
  367. int i;
  368. for (i = 0; options[i].Name != NULL; i++)
  369. {
  370. if (strcmp(options[i].Name, Name) == 0)
  371. return &options[i];
  372. if (options[i].Alias != NULL)
  373. {
  374. if (strcmp(options[i].Alias, Name) == 0)
  375. return &options[i];
  376. }
  377. }
  378. return NULL;
  379. }
  380. COMMAND_LINE_ARGUMENT_W* CommandLineFindArgumentW(COMMAND_LINE_ARGUMENT_W* options, LPCWSTR Name)
  381. {
  382. int i;
  383. for (i = 0; options[i].Name != NULL; i++)
  384. {
  385. if (_wcscmp(options[i].Name, Name) == 0)
  386. return &options[i];
  387. if (options[i].Alias != NULL)
  388. {
  389. if (_wcscmp(options[i].Alias, Name) == 0)
  390. return &options[i];
  391. }
  392. }
  393. return NULL;
  394. }
  395. COMMAND_LINE_ARGUMENT_A* CommandLineFindNextArgumentA(COMMAND_LINE_ARGUMENT_A* argument)
  396. {
  397. COMMAND_LINE_ARGUMENT_A* nextArgument;
  398. if (!argument || !argument->Name)
  399. return NULL;
  400. nextArgument = &argument[1];
  401. if (nextArgument->Name == NULL)
  402. return NULL;
  403. return nextArgument;
  404. }
  405. char** CommandLineParseCommaSeparatedValuesEx(const char* name, const char* list, size_t* count)
  406. {
  407. char** p;
  408. char* str;
  409. size_t nArgs;
  410. size_t index;
  411. size_t nCommas;
  412. size_t prefix, len;
  413. nCommas = 0;
  414. if (count == NULL)
  415. return NULL;
  416. *count = 0;
  417. if (!list)
  418. {
  419. if (name)
  420. {
  421. size_t len = strlen(name);
  422. p = (char**)calloc(2UL + len, sizeof(char*));
  423. if (p)
  424. {
  425. char* dst = (char*)&p[1];
  426. p[0] = dst;
  427. sprintf_s(dst, len + 1, "%s", name);
  428. *count = 1;
  429. return p;
  430. }
  431. }
  432. return NULL;
  433. }
  434. {
  435. const char* it = list;
  436. while ((it = strchr(it, ',')) != NULL)
  437. {
  438. it++;
  439. nCommas++;
  440. }
  441. }
  442. nArgs = nCommas + 1;
  443. if (name)
  444. nArgs++;
  445. prefix = (nArgs + 1UL) * sizeof(char*);
  446. len = strlen(list);
  447. p = (char**)calloc(len + prefix + 1, sizeof(char*));
  448. if (!p)
  449. return NULL;
  450. str = &((char*)p)[prefix];
  451. memcpy(str, list, len);
  452. if (name)
  453. p[0] = (char*)name;
  454. for (index = name ? 1 : 0; index < nArgs; index++)
  455. {
  456. char* comma = strchr(str, ',');
  457. p[index] = str;
  458. if (comma)
  459. {
  460. str = comma + 1;
  461. *comma = '\0';
  462. }
  463. }
  464. *count = nArgs;
  465. return p;
  466. }
  467. char** CommandLineParseCommaSeparatedValues(const char* list, size_t* count)
  468. {
  469. return CommandLineParseCommaSeparatedValuesEx(NULL, list, count);
  470. }