diff --git a/src/creport.c b/src/creport.c index 17cf60fdb..eeaa563bb 100644 --- a/src/creport.c +++ b/src/creport.c @@ -517,7 +517,7 @@ static void report_crtypes(FILE * F, const struct locale *lang) assert(hash > 0); fprintf(F, "MESSAGETYPE %d\n", hash); fputc('\"', F); - fputs(str_escape(nrt_string(kmt->mtype, lang), buffer, sizeof(buffer)), F); + fputs(crescape(nrt_string(kmt->mtype, lang), buffer, sizeof(buffer)), F); fputs("\";text\n", F); fprintf(F, "\"%s\";section\n", kmt->mtype->section); } diff --git a/src/kernel/order.c b/src/kernel/order.c index b7da372c1..e660edf81 100644 --- a/src/kernel/order.c +++ b/src/kernel/order.c @@ -110,6 +110,15 @@ char* get_command(const order *ord, const struct locale *lang, char *sbuffer, si return sbuffer; } +const char *crescape(const char *str, char *buffer, size_t size) { + const char *replace = "\"\\"; + const char * pos = strpbrk(str, replace); + if (!pos) { + return str; + } + return str_escape_ex(str, buffer, size, replace); +} + int stream_order(struct stream *out, const struct order *ord, const struct locale *lang, bool escape) { const char *text; @@ -151,7 +160,7 @@ int stream_order(struct stream *out, const struct order *ord, const struct local char obuf[1024]; swrite(" ", 1, 1, out); if (escape) { - text = str_escape(text, obuf, sizeof(obuf)); + text = crescape(text, obuf, sizeof(obuf)); } swrite(text, 1, strlen(text), out); } diff --git a/src/kernel/order.h b/src/kernel/order.h index 02e03ff56..b0d771b67 100644 --- a/src/kernel/order.h +++ b/src/kernel/order.h @@ -67,6 +67,7 @@ extern "C" { bool is_exclusive(const order * ord); bool is_repeated(keyword_t kwd); bool is_long(keyword_t kwd); + const char *crescape(const char *str, char *buffer, size_t size); char *write_order(const order * ord, const struct locale *lang, char *buffer, size_t size); diff --git a/src/kernel/order.test.c b/src/kernel/order.test.c index 3f359e834..1d97578ee 100644 --- a/src/kernel/order.test.c +++ b/src/kernel/order.test.c @@ -14,6 +14,7 @@ #include #include #include +#include static void test_create_order(CuTest *tc) { char cmd[32]; @@ -484,10 +485,62 @@ static void test_study_order_unknown_quoted(CuTest *tc) { test_teardown(); } +static void test_create_order_long(CuTest *tc) { + char buffer[2048]; + order *ord; + size_t len; + struct locale *lang; + stream out; + const char * longstr = "// BESCHREIBEN EINHEIT \"In weià ƒ ƒ ƒ ƒ ƒ ƒ &#" + "131;ƒƒƒƒƒƒ?e Gewà " + "1;ƒƒƒƒƒƒƒƒƒÃƒƒƒ" + "ƒƒƒƒƒƒƒƒƒÃƒƒƒ&#" + "131;ƒƒƒƒƒƒ‚à " + "1;ƒƒƒƒ‚Ã" + "ƒ‚ÂÃ&" + "#131;ƒƒƒ‚ÂÃ&#" + "131;ƒƒ‚ÂÂÃ&#" + "130;¢‚Ã&" + "#131;ƒ‚Ã" + "31;‚Âà " + "1;ƒƒƒƒƒƒƒ‚Ã" + "ƒƒƒ‚[...]hB��2�xa�Hv$P�xa��A���A��"; + test_setup(); + lang = test_create_locale(); + ord = parse_order(longstr, lang); + len = strlen(longstr); + CuAssertIntEquals(tc, 0, ord->command); + mstream_init(&out); + stream_order(&out, ord, lang, true); + out.api->rewind(out.handle); + out.api->readln(out.handle, buffer, sizeof(buffer)); + mstream_done(&out); + free_order(ord); + test_teardown(); +} + +static void test_crescape(CuTest *tc) { + char buffer[16]; + const char *input = "12345678901234567890"; + + CuAssertStrEquals(tc, "1234", crescape("1234", buffer, 16)); + CuAssertPtrEquals(tc, (void *)input, (void *)crescape(input, buffer, 16)); + + CuAssertStrEquals(tc, "\\\"1234\\\"", crescape("\"1234\"", buffer, 16)); + CuAssertStrEquals(tc, "\\\"1234\\\"", buffer); + + CuAssertStrEquals(tc, "\\\"1234", crescape("\"1234\"", buffer, 8)); + + /* unlike in C strings, only " and \ are escaped: */ + CuAssertStrEquals(tc, "\\\"\\\\\n\r\'", crescape("\"\\\n\r\'", buffer, 16)); +} + CuSuite *get_order_suite(void) { CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_crescape); SUITE_ADD_TEST(suite, test_create_order); + SUITE_ADD_TEST(suite, test_create_order_long); SUITE_ADD_TEST(suite, test_study_orders); SUITE_ADD_TEST(suite, test_study_order); SUITE_ADD_TEST(suite, test_study_order_unknown); diff --git a/src/modules/gmcmd.c b/src/modules/gmcmd.c index 401879cd6..20e090114 100644 --- a/src/modules/gmcmd.c +++ b/src/modules/gmcmd.c @@ -46,7 +46,7 @@ static int read_permissions(variant *var, void *owner, struct gamedata *data) { - attrib *a; + attrib *a = NULL; UNUSED_ARG(var); read_attribs(data, &a, owner); a_remove(&a, a); diff --git a/src/monsters.c b/src/monsters.c index 00a733677..be4ec7525 100644 --- a/src/monsters.c +++ b/src/monsters.c @@ -172,8 +172,8 @@ static order *monster_attack(unit * u, const unit * target) if (monster_is_waiting(u)) return NULL; - if (u->region->land) { - assert(u->region->flags & RF_GUARDED); + if (u->region->land && (u->region->flags & RF_GUARDED) == 0) { + return NULL; } return create_order(K_ATTACK, u->faction->locale, "%i", target->no); } diff --git a/src/util/strings.c b/src/util/strings.c index e1fa3569e..92385a88f 100644 --- a/src/util/strings.c +++ b/src/util/strings.c @@ -20,9 +20,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #endif #include "strings.h" -#include "assert.h" /* libc includes */ +#include #include #include #include @@ -155,27 +155,28 @@ int str_hash(const char *s) return key & 0x7FFFFFFF; } -const char *str_escape(const char *str, char *buffer, size_t len) +const char *str_escape_wrong(const char *str, char *buffer, size_t len) { const char *handle_start = strchr(str, '\"'); if (!handle_start) handle_start = strchr(str, '\\'); assert(buffer); if (handle_start) { - const char *p; - char *o; + const char *p = str; + char *o = buffer; size_t skip = handle_start - str; if (skip > len) { skip = len; } - memcpy(buffer, str, skip); - o = buffer + skip; - p = str + skip; - len -= skip; + if (skip > 0) { + memcpy(buffer, str, skip); + o += skip; + p += skip; + len -= skip; + } do { if (*p == '\"' || *p == '\\') { if (len < 2) { - *o = '\0'; break; } (*o++) = '\\'; @@ -183,13 +184,15 @@ const char *str_escape(const char *str, char *buffer, size_t len) } else { if (len < 1) { - *o = '\0'; break; } --len; } - (*o++) = (*p); - } while (*p++); + if (len > 0) { + (*o++) = (*p); + } + } while (len > 0 && *p++); + *o = '\0'; return buffer; } return str; @@ -306,3 +309,166 @@ char *str_unescape(char *str) { } return str; } + +const char *str_escape_ex(const char *str, char *buffer, size_t size, const char *chars) +{ + const char *read = str; + char *write = buffer; + if (size < 1) return NULL; + while (size > 1 && *read) { + const char *pos = strpbrk(read, chars); + size_t len = size; + if (pos) { + len = pos - read; + } + if (len < size) { + unsigned char ch = *(const unsigned char *)pos; + if (len > 0) { + memmove(write, read, len); + write += len; + read += len; + size -= len; + } + switch (ch) { + case '\t': + if (size > 2) { + *write++ = '\\'; + *write++ = 't'; + size -= 2; + } + else size = 1; + break; + case '\n': + if (size > 2) { + *write++ = '\\'; + *write++ = 'n'; + size -= 2; + } + else size = 1; + break; + case '\r': + if (size > 2) { + *write++ = '\\'; + *write++ = 'r'; + size -= 2; + } + else size = 1; + break; + case '\"': + case '\'': + case '\\': + if (size > 2) { + *write++ = '\\'; + *write++ = ch; + size -= 2; + } + break; + default: + if (size > 5) { + int n = sprintf(write, "\\%03o", ch); + if (n > 0) { + assert(n == 5); + write += n; + size -= n; + } + else size = 1; + } + else size = 1; + } + ++read; + } else { + /* end of buffer space */ + len = size - 1; + if (len > 0) { + memmove(write, read, len); + write += len; + size -= len; + break; + } + } + } + *write = '\0'; + return buffer; +} + +const char *str_escape(const char *str, char *buffer, size_t size) { + return str_escape_ex(str, buffer, size, "\n\t\r\'\"\\"); +} + +const char *str_escape_slow(const char *str, char *buffer, size_t size) { + const char *read = str; + char *write = buffer; + if (size < 1) return NULL; + while (size > 1 && *read) { + size_t len; + const char *pos = read; + while (pos + 1 < read + size && *pos) { + unsigned char ch = *(unsigned char *)pos; + if (iscntrl(ch) || ch == '\"' || ch == '\\' || ch == '\'' || ch == '\n' || ch == '\r' || ch == '\t') { + len = pos - read; + memmove(write, read, len); + write += len; + size -= len; + switch (ch) { + case '\t': + if (size > 2) { + *write++ = '\\'; + *write++ = 't'; + size -= 2; + } + else size = 1; + break; + case '\n': + if (size > 2) { + *write++ = '\\'; + *write++ = 'n'; + size -= 2; + } + else size = 1; + break; + case '\r': + if (size > 2) { + *write++ = '\\'; + *write++ = 'r'; + size -= 2; + } + else size = 1; + break; + case '\"': + case '\'': + case '\\': + if (size > 2) { + *write++ = '\\'; + *write++ = ch; + size -= 2; + } + break; + default: + if (size > 5) { + int n = sprintf(write, "\\%03o", ch); + if (n > 0) { + assert(n == 5); + write += n; + size -= n; + } + else size = 1; + } + else size = 1; + } + assert(size > 0); + read = pos + 1; + break; + } + ++pos; + } + if (read < pos) { + len = pos - read; + memmove(write, read, len); + read = pos; + write += len; + size -= len; + } + } + *write = '\0'; + return buffer; +} diff --git a/src/util/strings.h b/src/util/strings.h index 156f41011..2d93b414e 100644 --- a/src/util/strings.h +++ b/src/util/strings.h @@ -32,7 +32,8 @@ extern "C" { size_t str_strlcat(char *dst, const char *src, size_t len); char *str_strdup(const char *s); - const char *str_escape(const char *str, char *buffer, size_t len); + const char *str_escape(const char *str, char *buffer, size_t size); + const char *str_escape_ex(const char *str, char *buffer, size_t size, const char *chars); char *str_unescape(char *str); unsigned int jenkins_hash(unsigned int a); diff --git a/src/util/strings.test.c b/src/util/strings.test.c index a04d0fe53..b6b0f9696 100644 --- a/src/util/strings.test.c +++ b/src/util/strings.test.c @@ -24,10 +24,24 @@ static void test_str_unescape(CuTest * tc) static void test_str_escape(CuTest * tc) { - char scratch[64]; - CuAssertStrEquals(tc, "12345678901234567890", str_escape("12345678901234567890", scratch, 16)); - CuAssertStrEquals(tc, "123456789\\\"12345", str_escape("123456789\"1234567890", scratch, 16)); - CuAssertStrEquals(tc, "1234567890123456", str_escape("1234567890123456\"890", scratch, 16)); + char scratch[16]; + + CuAssertPtrEquals(tc, NULL, (void *)str_escape("1234", scratch, 0)); + CuAssertStrEquals(tc, "", str_escape("1234", scratch, 1)); + CuAssertStrEquals(tc, "1", str_escape("1234", scratch, 2)); + CuAssertStrEquals(tc, "\\n", str_escape("\n234", scratch, 3)); + + CuAssertStrEquals(tc, "\\n\\r\\t\\\\\\\"\\\'", str_escape("\n\r\t\\\"\'", scratch, 16)); + CuAssertStrEquals(tc, "12345678", str_escape("12345678", scratch, 16)); + CuAssertStrEquals(tc, "123456789012345", str_escape("123456789012345", scratch, 16)); + CuAssertStrEquals(tc, "12345678901234", str_escape("12345678901234\n", scratch, 15)); + CuAssertStrEquals(tc, "123456789012345", str_escape("12345678901234567890", scratch, 16)); + + CuAssertStrEquals(tc, "\\\\\\\"234", str_escape("\\\"234567890", scratch, 8)); + CuAssertStrEquals(tc, "\\\"\\\\234", str_escape("\"\\234567890", scratch, 8)); + CuAssertStrEquals(tc, "123456789012345", str_escape("12345678901234567890", scratch, 16)); + CuAssertStrEquals(tc, "123456789\\\"1234", str_escape("123456789\"1234567890", scratch, 16)); + CuAssertStrEquals(tc, "123456789012345", str_escape("1234567890123456\"890", scratch, 16)); CuAssertStrEquals(tc, "hello world", str_escape("hello world", scratch, sizeof(scratch))); CuAssertStrEquals(tc, "hello \\\"world\\\"", str_escape("hello \"world\"", scratch, sizeof(scratch))); CuAssertStrEquals(tc, "\\\"\\\\", str_escape("\"\\", scratch, sizeof(scratch)));