Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

230 lines
8.3 KiB

/*
Part of the TERMINAL MODULE
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#include <vector>
#include <cctype>
#include "terminal_parsing.h"
namespace terminal {
namespace parsing {
// c/p with minor modifications from redis / sds, so that we don't have to roll a custom parser
// ref:
// - https://github.com/antirez/sds/blob/master/sds.c
// - https://github.com/antirez/redis/blob/unstable/src/networking.c
//
// Things are kept mostly the same, we are replacing Redis-specific things:
// - sds structure -> String
// - sds array -> std::vector<String>
// - we return always return custom structure, nullptr can no longer be used
// to notify about the missing / unterminated / mismatching quotes
// - hex_... function helpers types are changed
// Original code is part of the SDSLib 2.0 -- A C dynamic strings library
// *
// * Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
// * Copyright (c) 2015, Oran Agra
// * Copyright (c) 2015, Redis Labs, Inc
// * All rights reserved.
// *
// * Redistribution and use in source and binary forms, with or without
// * modification, are permitted provided that the following conditions are met:
// *
// * * Redistributions of source code must retain the above copyright notice,
// * this list of conditions and the following disclaimer.
// * * Redistributions in binary form must reproduce the above copyright
// * notice, this list of conditions and the following disclaimer in the
// * documentation and/or other materials provided with the distribution.
// * * Neither the name of Redis nor the names of its contributors may be used
// * to endorse or promote products derived from this software without
// * specific prior written permission.
// *
// * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// * POSSIBILITY OF SUCH DAMAGE.
// Helper functions to handle \xHH codes
static bool is_hex_digit(char c) {
return (c >= '0' && c <= '9') \
||(c >= 'a' && c <= 'f') \
||(c >= 'A' && c <= 'F');
}
static char hex_digit_to_int(char c) {
switch (c) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
default: return 0;
}
}
// Our port of `sdssplitargs`
CommandLine parse_commandline(const char *line) {
const char *p = line;
CommandLine result {{}, 0};
result.argv.reserve(4);
String current;
while(1) {
/* skip blanks */
while(*p && isspace(*p)) p++;
if (*p) {
/* get a token */
int inq=0; /* set to 1 if we are in "quotes" */
int insq=0; /* set to 1 if we are in 'single quotes' */
int done=0;
while(!done) {
if (inq) {
if (*p == '\\' && *(p+1) == 'x' &&
is_hex_digit(*(p+2)) &&
is_hex_digit(*(p+3)))
{
// XXX: make sure that we append `char` or `char[]`,
// even with -funsigned-char this can accidentally append itoa conversion
unsigned char byte =
(hex_digit_to_int(*(p+2))*16)+
hex_digit_to_int(*(p+3));
char buf[2] { static_cast<char>(byte), 0x00 };
current += buf;
p += 3;
} else if (*p == '\\' && *(p+1)) {
char c;
p++;
switch(*p) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'b': c = '\b'; break;
case 'a': c = '\a'; break;
default: c = *p; break;
}
current += c;
} else if (*p == '"') {
/* closing quote must be followed by a space or
* nothing at all. */
if (*(p+1) && !isspace(*(p+1))) goto err;
done=1;
} else if (!*p) {
/* unterminated quotes */
goto err;
} else {
char buf[2] {*p, '\0'};
current += buf;
}
} else if (insq) {
if (*p == '\\' && *(p+1) == '\'') {
p++;
current += '\'';
} else if (*p == '\'') {
/* closing quote must be followed by a space or
* nothing at all. */
if (*(p+1) && !isspace(*(p+1))) goto err;
done=1;
} else if (!*p) {
/* unterminated quotes */
goto err;
} else {
char buf[2] {*p, '\0'};
current += buf;
}
} else {
switch(*p) {
case ' ':
case '\n':
case '\r':
case '\t':
case '\0':
done=1;
break;
case '"':
inq=1;
break;
case '\'':
insq=1;
break;
default: {
char buf[2] {*p, '\0'};
current += buf;
break;
}
}
}
if (*p) p++;
}
/* add the token to the vector */
result.argv.emplace_back(std::move(current));
++result.argc;
} else {
/* Even on empty input string return something not NULL. */
return result;
}
}
err:
result.argc = 0;
result.argv.clear();
return result;
}
// Fowler–Noll–Vo hash function to hash command strings that treats input as lowercase
// ref: https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
//
// This is here in case `std::unordered_map` becomes viable
// TODO: afaik, map implementation should handle collisions (however rare they are in our case)
// if not, we can always roll static commands allocation and just match strings with strcmp_P
uint32_t lowercase_fnv1_hash(const char* ptr) {
constexpr uint32_t fnv_prime = 16777619u;
constexpr uint32_t fnv_basis = 2166136261u;
const auto length = strlen_P(ptr);
uint32_t hash = fnv_basis;
for (size_t idx = 0; idx < length; ++idx) {
hash = hash ^ static_cast<uint32_t>(tolower(pgm_read_byte(&ptr[idx])));
hash = hash * fnv_prime;
}
return hash;
}
uint32_t lowercase_fnv1_hash(const __FlashStringHelper* ptr) {
return lowercase_fnv1_hash(reinterpret_cast<const char*>(ptr));
}
} // namespace parsing
} // namespace terminal