From 30b789aa49efc0e192b8d090f3d1815bd04221c6 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 1 Dec 2014 11:34:58 -0600 Subject: [PATCH] Fix epicsStrnEscapedFromRaw() and epicsStrnRawFromEscaped() Major rewrites; the original epicsStrnRawFromEscaped() could read beyond the end of the input buffer if the input count ended in the middle of an octal or hex escape sequence. Zero termination did not always match the return count, and hex escapes were always 2 digits, contrary to the C standard. New versions don't use epicsSnprintf() or sscanf() for hex and octal conversions, so should be slightly faster. Added 81 new tests to check the above issues. --- documentation/RELEASE_NOTES.html | 5 + src/libCom/misc/epicsString.c | 249 ++++++++++++++++-------------- src/libCom/test/epicsStringTest.c | 198 +++++++++++++++++++++--- 3 files changed, 316 insertions(+), 136 deletions(-) diff --git a/documentation/RELEASE_NOTES.html b/documentation/RELEASE_NOTES.html index 239d3b673..5fab30c35 100644 --- a/documentation/RELEASE_NOTES.html +++ b/documentation/RELEASE_NOTES.html @@ -14,6 +14,11 @@

Changes between 3.15.0.2 and 3.15.1

+

epicsStrnEscapedFromRaw() and epicsStrnRawFromEscaped()

+ +

These routines have been rewritten; the previous implementations did not +always behave exactly as specified.

+

Shared Library Versions

On architectures that can support it, the shared library version number for diff --git a/src/libCom/misc/epicsString.c b/src/libCom/misc/epicsString.c index ddf8634aa..de38005b4 100644 --- a/src/libCom/misc/epicsString.c +++ b/src/libCom/misc/epicsString.c @@ -29,148 +29,165 @@ #include "epicsString.h" /* Deprecated, use epicsStrnRawFromEscaped() instead */ -int dbTranslateEscape(char *to, const char *from) +int dbTranslateEscape(char *dst, const char *src) { - size_t big_enough = strlen(from)+1; - return epicsStrnRawFromEscaped(to, big_enough, from, big_enough); + size_t big_enough = strlen(src) + 1; + + return epicsStrnRawFromEscaped(dst, big_enough, src, big_enough); } -int epicsStrnRawFromEscaped(char *to, size_t outsize, const char *from, - size_t inlen) +int epicsStrnRawFromEscaped(char *dst, size_t dstlen, const char *src, + size_t srclen) { - const char *pfrom = from; - char *pto = to; - char c; - size_t nto = 0, nfrom = 0; + int rem = dstlen; + int ndst = 0; - if (outsize == 0) - return 0; + while (srclen--) { + int c = *src++; + #define OUT(chr) if (--rem > 0) ndst++, *dst++ = chr - while ((c = *pfrom++) && nto < outsize && nfrom < inlen) { - nfrom++; - if (c == '\\') { - if (nfrom >= inlen || *pfrom == '\0') break; - switch (*pfrom) { - case 'a': pfrom++; nfrom++; *pto++ = '\a' ; nto++; break; - case 'b': pfrom++; nfrom++; *pto++ = '\b' ; nto++; break; - case 'f': pfrom++; nfrom++; *pto++ = '\f' ; nto++; break; - case 'n': pfrom++; nfrom++; *pto++ = '\n' ; nto++; break; - case 'r': pfrom++; nfrom++; *pto++ = '\r' ; nto++; break; - case 't': pfrom++; nfrom++; *pto++ = '\t' ; nto++; break; - case 'v': pfrom++; nfrom++; *pto++ = '\v' ; nto++; break; - case '\\': pfrom++; nfrom++; *pto++ = '\\' ; nto++; break; - case '\?': pfrom++; nfrom++; *pto++ = '\?' ; nto++; break; - case '\'': pfrom++; nfrom++; *pto++ = '\'' ; nto++; break; - case '\"': pfrom++; nfrom++; *pto++ = '\"' ; nto++; break; - case '0' :case '1' :case '2' :case '3' : - case '4' :case '5' :case '6' :case '7' : - { - int i; - char strval[4] = {0,0,0,0}; - unsigned int ival; - unsigned char *pchar; + if (!c) break; - for (i=0; i<3; i++) { - if ((*pfrom < '0') || (*pfrom > '7')) break; - strval[i] = *pfrom++; nfrom++; - } - sscanf(strval,"%o",&ival); - pchar = (unsigned char *)(pto++); nto++; - *pchar = (unsigned char)(ival); - } - break; - case 'x' : - { - int i; - char strval[3] = {0,0,0}; - unsigned int ival; - unsigned char *pchar; - - pfrom++; /*skip the x*/ - for (i=0; i<2; i++) { - if (!isxdigit((int)*pfrom)) break; - strval[i] = *pfrom++; nfrom++; - } - sscanf(strval,"%x",&ival); - pchar = (unsigned char *)(pto++); nto++; - *pchar = (unsigned char)(ival); - } - break; - default: - *pto++ = *pfrom++; nfrom++; nto++; - } - } else { - *pto++ = c; nto++; + input: + if (c != '\\') { + OUT(c); + continue; } - } - if (nto == outsize) - pto--; - *pto = '\0'; - return nto; -} -int epicsStrnEscapedFromRaw(char *outbuf, size_t outsize, const char *inbuf, - size_t inlen) -{ - int maxout = outsize; - int nout = 0; - int len; - char *outpos = outbuf; + if (!srclen-- || !(c = *src++)) break; - while (inlen--) { - char c = *inbuf++; switch (c) { - case '\a': len = epicsSnprintf(outpos, maxout, "\\a"); break; - case '\b': len = epicsSnprintf(outpos, maxout, "\\b"); break; - case '\f': len = epicsSnprintf(outpos, maxout, "\\f"); break; - case '\n': len = epicsSnprintf(outpos, maxout, "\\n"); break; - case '\r': len = epicsSnprintf(outpos, maxout, "\\r"); break; - case '\t': len = epicsSnprintf(outpos, maxout, "\\t"); break; - case '\v': len = epicsSnprintf(outpos, maxout, "\\v"); break; - case '\\': len = epicsSnprintf(outpos, maxout, "\\\\"); ; break; - case '\'': len = epicsSnprintf(outpos, maxout, "\\'"); break; - case '\"': len = epicsSnprintf(outpos, maxout, "\\\""); break; - default: - if (isprint((int)c)) - len = epicsSnprintf(outpos, maxout, "%c", c); - else - len = epicsSnprintf(outpos, maxout, "\\%03o", - (unsigned char)c); - break; - } - if (len<0) return -1; - nout += len; - if (nout < outsize) { - maxout -= len; - outpos += len; - } else { - outpos = outpos + maxout -1; - maxout = 1; + case 'a': OUT('\a'); break; + case 'b': OUT('\b'); break; + case 'f': OUT('\f'); break; + case 'n': OUT('\n'); break; + case 'r': OUT('\r'); break; + case 't': OUT('\t'); break; + case 'v': OUT('\v'); break; + case '\\': OUT('\\'); break; + case '\'': OUT('\''); break; + case '\"': OUT('\"'); break; + + case '0' :case '1' :case '2' :case '3' : + case '4' :case '5' :case '6' :case '7' : + { /* \ooo */ + unsigned int u = c - '0'; + + if (!srclen-- || !(c = *src++)) { + OUT(u); goto done; + } + if (c < '0' || c > '7') { + OUT(u); goto input; + } + u = u << 3 | (c - '0'); + + if (!srclen-- || !(c = *src++)) { + OUT(u); goto done; + } + if (c < '0' || c > '7') { + OUT(u); goto input; + } + u = u << 3 | (c - '0'); + + if (u > 0377) { + /* Undefined behaviour! */ + } + OUT(u); + } + break; + + case 'x' : + { /* \xXXX... */ + unsigned int u = 0; + + if (!srclen-- || !(c = *src++)) + goto done; + + while (isxdigit(c)) { + u = u << 4 | ((c > '9') ? toupper(c) - 'A' + 10 : c - '0'); + if (u > 0xff) { + /* Undefined behaviour! */ + } + if (!srclen-- || !(c = *src++)) { + OUT(u); + goto done; + } + } + OUT(u); + goto input; + } + + default: + OUT(c); } + #undef OUT } - *outpos = '\0'; - return nout; +done: + if (dstlen) + *dst = '\0'; + return ndst; } -size_t epicsStrnEscapedFromRawSize(const char *inbuf, size_t inlen) +int epicsStrnEscapedFromRaw(char *dst, size_t dstlen, const char *src, + size_t srclen) { - size_t nout = inlen; + int rem = dstlen; + int ndst = 0; - while (inlen--) { - char c = *inbuf++; + if (dst == src) + return -1; + + while (srclen--) { + int c = *src++; + #define OUT(chr) ndst++; if (--rem > 0) *dst++ = chr + + switch (c) { + case '\a': OUT('\\'); OUT('a'); break; + case '\b': OUT('\\'); OUT('b'); break; + case '\f': OUT('\\'); OUT('f'); break; + case '\n': OUT('\\'); OUT('n'); break; + case '\r': OUT('\\'); OUT('r'); break; + case '\t': OUT('\\'); OUT('t'); break; + case '\v': OUT('\\'); OUT('v'); break; + case '\\': OUT('\\'); OUT('\\'); break; + case '\'': OUT('\\'); OUT('\''); break; + case '\"': OUT('\\'); OUT('\"'); break; + default: + if (isprint(c)) { + OUT(c); + break; + } + OUT('\\'); + OUT('0' + ((c & 0300) >> 6)); + OUT('0' + ((c & 0070) >> 3)); + OUT('0' + (c & 0007)); + } + #undef OUT + } + if (dstlen) + *dst = '\0'; + return ndst; +} + +size_t epicsStrnEscapedFromRawSize(const char *src, size_t srclen) +{ + size_t ndst = srclen; + + while (srclen--) { + int c = *src++; switch (c) { case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': case '\\': case '\'': case '\"': - nout++; + ndst++; break; default: - if (!isprint((int)c)) - nout += 3; + if (!isprint(c)) + ndst += 3; } } - return nout; + return ndst; } int epicsStrCaseCmp(const char *s1, const char *s2) diff --git a/src/libCom/test/epicsStringTest.c b/src/libCom/test/epicsStringTest.c index 36f8233b5..a5fffaf49 100644 --- a/src/libCom/test/epicsStringTest.c +++ b/src/libCom/test/epicsStringTest.c @@ -88,7 +88,7 @@ MAIN(epicsStringTest) char *s; int status; - testPlan(323); + testPlan(402); testChars(); @@ -125,41 +125,199 @@ MAIN(epicsStringTest) testGlob(); + memset(result, 'x', sizeof(result)); + status = epicsStrnEscapedFromRaw(result, 0, ABCD, 4); + testOk(status == 4, "epicsStrnEscapedFromRaw(out, 0) -> %d (exp. 4)", status); + testOk(result[0] == 'x', " No output"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 0, ABCD, 4); + testOk(status == 0, "epicsStrnRawFromEscaped(out, 0) -> %d (exp. 0)", status); + testOk(result[0] == 'x', " No output"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnEscapedFromRaw(result, 1, ABCD, 4); + testOk(status == 4, "epicsStrnEscapedFromRaw(out, 1) -> %d (exp. 4)", status); + testOk(result[0] == 0, " 0-terminated"); + testOk(result[1] == 'x', " No overrun"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 1, ABCD, 4); + testOk(status == 0, "epicsStrnRawFromEscaped(out, 1) -> %d (exp. 0)", status); + testOk(result[0] == 0, " 0-terminated"); + testOk(result[1] == 'x', " No overrun"); + + testDiag("Testing size = epicsStrnEscapedFromRawSize"); + + status = epicsStrnEscapedFromRawSize(ABCD, 3); + testOk(status == 3, "size(\"ABCD\", 3) -> %d (exp. 3)", status); + status = epicsStrnEscapedFromRawSize(ABCD, 4); + testOk(status == 4, "size(\"ABCD\", 4) -> %d (exp. 4)", status); + status = epicsStrnEscapedFromRawSize(ABCD, 5); + testOk(status == 8, "size(\"ABCD\", 5) -> %d (exp. 8)", status); + + testDiag("Testing esc = epicsStrnEscapedFromRaw(out, 4, ...)"); + memset(result, 'x', sizeof(result)); status = epicsStrnEscapedFromRaw(result, 4, ABCD, 3); - testOk(status == 3, "epicsStrnEscapedFromRaw returned %d (exp. 3)", status); - testOk(result[4] == 'x', "epicsStrnEscapedFromRaw no buffer overrun"); - testOk(result[3] == 0, "epicsStrnEscapedFromRaw 0-terminated"); + testOk(status == 3, "esc(\"ABCD\", 3) -> %d (exp. 3)", status); + testOk(result[3] == 0, " 0-terminated"); + testOk(result[4] == 'x', " No overrun"); memset(result, 'x', sizeof(result)); status = epicsStrnEscapedFromRaw(result, 4, ABCD, 4); - testOk(status == 4, "epicsStrnEscapedFromRaw returned %d (exp. 4)", status); - testOk(result[4] == 'x', "epicsStrnEscapedFromRaw no buffer overrun"); - testOk(result[3] == 0, "epicsStrnEscapedFromRaw 0-terminated"); + testOk(status == 4, "esc(\"ABCD\", 4) -> %d (exp. 4)", status); + testOk(result[3] == 0, " 0-terminated"); + testOk(result[4] == 'x', " No overrun"); memset(result, 'x', sizeof(result)); - status = epicsStrnEscapedFromRaw(result, 4, ABCDE, 5); - testOk(status == 5, "epicsStrnEscapedFromRaw returned %d (exp. 5)", status); - testOk(result[4] == 'x', "epicsStrnEscapedFromRaw no buffer overrun"); - testOk(result[3] == 0, "epicsStrnEscapedFromRaw 0-terminated"); + status = epicsStrnEscapedFromRaw(result, 4, ABCD, 5); + testOk(status == 8, "esc(\"ABCD\", 5) -> %d (exp. 8)", status); + testOk(result[3] == 0, " 0-terminated"); + testOk(result[4] == 'x', " No overrun"); + + testDiag("Testing raw = epicsStrnRawFromEscaped(out, 4, ...)"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, ABCD, 0); + testOk(status == 0, "raw(\"ABCD\", 0) -> %d (exp. 0)", status); + testOk(result[0] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, ABCD, 1); + testOk(status == 1, "raw(\"ABCD\", 1) -> %d (exp. 1)", status); + testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); + testOk(result[1] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, ABCD, 2); + testOk(status == 2, "raw(\"ABCD\", 2) -> %d (exp. 2)", status); + testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); + testOk(result[1] == 'B', " Char '%c' (exp. 'B')", result[1]); + testOk(result[2] == 0, " 0-terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, ABCD, 3); - testOk(status == 3, "epicsStrnRawFromEscaped returned %d (exp. 3)", status); - testOk(result[4] == 'x', "epicsStrnRawFromEscaped no buffer overrun"); - testOk(result[3] == 0, "epicsStrnRawFromEscaped 0-terminated"); + testOk(status == 3, "raw(\"ABCD\", 3) -> %d (exp. 3)", status); + testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); + testOk(result[1] == 'B', " Char '%c' (exp. 'B')", result[1]); + testOk(result[2] == 'C', " Char '%c' (exp. 'C')", result[2]); + testOk(result[3] == 0, " 0-terminated"); + testOk(result[4] == 'x', " No write outside buffer"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, ABCD, 4); - testOk(status == 4, "epicsStrnRawFromEscaped returned %d (exp. 4)", status); - testOk(result[4] == 'x', "epicsStrnRawFromEscaped no buffer overrun"); - testOk(result[3] == 0, "epicsStrnRawFromEscaped 0-terminated"); + testOk(status == 3, "raw(\"ABCD\", 4) -> %d (exp. 3)", status); + testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); + testOk(result[1] == 'B', " Char '%c' (exp. 'B')", result[1]); + testOk(result[2] == 'C', " Char '%c' (exp. 'C')", result[2]); + testOk(result[3] == 0, " 0-terminated"); + testOk(result[4] == 'x', " No write outside buffer"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, ABCDE, 5); - testOk(status == 4, "epicsStrnRawFromEscaped returned %d (exp. 4)", status); - testOk(result[4] == 'x', "epicsStrnRawFromEscaped no buffer overrun"); - testOk(result[3] == 0, "epicsStrnRawFromEscaped 0-terminated"); + testOk(status == 3, "raw(\"ABCDE\", 5) -> %d (exp. 3)", status); + testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); + testOk(result[1] == 'B', " Char '%c' (exp. 'B')", result[1]); + testOk(result[2] == 'C', " Char '%c' (exp. 'C')", result[2]); + testOk(result[3] == 0, " 0-terminated"); + testOk(result[4] == 'x', " No write outside buffer"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "A", 2); + testOk(status == 1, "raw(\"A\", 2) -> %d (exp. 1)", status); + testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\123", 1); + testOk(status == 0, "raw(\"\\123\", 1) -> %d (exp. 0)", status); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\123", 2); + testOk(status == 1, "raw(\"\\123\", 2) -> %d (exp. 1)", status); + testOk(result[0] == 1, " Octal escape (got \\%03o)", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\123", 3); + testOk(status == 1, "raw(\"\\123\", 3) -> %d (exp. 1)", status); + testOk(result[0] == 012, " Octal escape (got \\%03o)", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\123", 4); + testOk(status == 1, "raw(\"\\123\", 4) -> %d (exp. 1)", status); + testOk(result[0] == 0123, " Octal escape (got \\%03o)", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\812", 2); + testOk(status == 1, "raw(\"\\812\", 2) -> %d (exp. 1)", status); + testOk(result[0] == '8', " Escaped '%c')", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\182", 3); + testOk(status == 2, "raw(\"\\182\", 3) -> %d (exp. 2)", status); + testOk(result[0] == 1, " Octal escape (got \\%03o)", result[0]); + testOk(result[1] == '8', " Terminated with '%c'", result[1]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\128", 4); + testOk(status == 2, "raw(\"\\128\", 4) -> %d (exp. 2)", status); + testOk(result[0] == 012, " Octal escape (got \\%03o)", result[0]); + testOk(result[1] == '8', " Terminator char got '%c'", result[1]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\x12", 1); + testOk(status == 0, "raw(\"\\x12\", 1) -> %d (exp. 0)", status); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\x12", 2); + testOk(status == 0, "raw(\"\\x12\", 2) -> %d (exp. 0)", status); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\x12", 3); + testOk(status == 1, "raw(\"\\x12\", 3) -> %d (exp. 1)", status); + testOk(result[0] == 1, " Hex escape (got \\x%x)", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\x12", 4); + testOk(status == 1, "raw(\"\\x12\", 4) -> %d (exp. 1)", status); + testOk(result[0] == 0x12," Hex escape (got \\x%x)", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\xaF", 4); + testOk(status == 1, "raw(\"\\xaF\", 4) -> %d (exp. 1)", status); + testOk(result[0] == '\xaF'," Hex escape (got \\x%x)", result[0] & 0xFF); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\x012", 5); + testOk(status == 1, "raw(\"\\x012\", 5) -> %d (exp. 1)", status); + testOk(result[0] == 0x12," Hex escape (got \\x%x)", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\x0012", 6); + testOk(status == 1, "raw(\"\\x0012\", 6) -> %d (exp. 1)", status); + testOk(result[0] == 0x12," Hex escape (got \\x%x)", result[0]); + testOk(result[status] == 0, " 0-terminated"); + + memset(result, 'x', sizeof(result)); + status = epicsStrnRawFromEscaped(result, 4, "\\x1g", 4); + testOk(status == 2, "raw(\"\\x1g\", 4) -> %d (exp. 2)", status); + testOk(result[0] == 1, " Hex escape (got \\x%x)", result[0]); + testOk(result[1] == 'g', " Terminator char got '%c'", result[1]); + testOk(result[status] == 0, " 0-terminated"); return testDone(); }