diff --git a/zephcore/app/CompanionMesh.cpp b/zephcore/app/CompanionMesh.cpp index f591b27..2761c04 100644 --- a/zephcore/app/CompanionMesh.cpp +++ b/zephcore/app/CompanionMesh.cpp @@ -1076,6 +1076,24 @@ bool CompanionMesh::vcontactHandleFrame(const uint8_t *data, size_t len) } if (!dup) { + /* Phone keyboards autocapitalize the first letter of a + * chat line, so a v-contact command arrives as "Get cad". + * Fold character 0 only. + * + * Safe by construction: character 0 is always inside the + * command verb -- no CLI command takes an argument at + * position 0 -- so this cannot alter a value. Deliberately + * NOT generalized beyond one character: the previous attempt + * folded the first two whitespace-delimited tokens and + * silently lowercased admin passwords (see the comment above + * CommonCLI::handleCommand in helpers/CommonCLI.cpp). + * + * Must stay after the delivery-ack hash above, which covers + * the original text the app will match against. */ + if (line[0] >= 'A' && line[0] <= 'Z') { + line[0] = (char)(line[0] - 'A' + 'a'); + } + LOG_INF("vcontact CLI: '%s'", line); char reply[VCONTACT_CLI_REPLY_SIZE]; reply[0] = '\0'; diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index e9317af..708f5cb 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -306,33 +306,22 @@ void CommonCLI::scheduleReboot(uint8_t type) k_work_schedule(&_reboot_work, K_SECONDS(2)); } +/* CLI commands are case-sensitive, matching upstream Arduino MeshCore. + * + * A case-insensitive normalizer lived here from 2026-07-12 until 2026-07-19. + * It lowercased the first two whitespace-delimited tokens before matching, on + * the assumption that a value never appears before the third token. That is + * false for "password ", whose value IS token 1 -- so any admin + * password containing uppercase was silently stored folded to lowercase and + * could never be used to log in again. ("set guest.password " was + * unaffected: three tokens.) + * + * Do not reintroduce input folding here. Any scheme that rewrites the buffer + * before dispatch has to guess where keywords end and arguments begin, and + * that guess is what broke. If case-insensitivity is wanted again, do it at + * the comparison sites so argument bytes are never touched. + */ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { - /* Case-insensitive command keywords. Lowercase only the first two - * whitespace-delimited tokens (the verb and the config key) — the value - * (3rd token onward) is preserved verbatim, so case-sensitive arguments - * like passwords, node names, owner info, and keys are never mangled. - * Fixes e.g. "Get cad" / "SET Cad.Auto on" not being recognized. */ - char norm[CLI_REPLY_SIZE]; - { - int tok = 0; /* completed tokens so far */ - bool in_tok = false; - size_t j = 0; - for (size_t i = 0; command[i] != '\0' && j < sizeof(norm) - 1; i++) { - char c = command[i]; - if (c == ' ' || c == '\t') { - if (in_tok) { in_tok = false; tok++; } - } else { - in_tok = true; - if (tok < 2 && c >= 'A' && c <= 'Z') { - c = (char)(c - 'A' + 'a'); - } - } - norm[j++] = c; - } - norm[j] = '\0'; - command = norm; - } - if (strcmp(command, "start dfu") == 0) { /* Reboot into UF2 bootloader for firmware update */ strcpy(reply, "OK - rebooting to UF2 DFU");