From e28111ce46c7bf771c72ec02184cbb34e4aa7086 Mon Sep 17 00:00:00 2001 From: Andreas Fankhauser hiddenalpha.ch Date: Sun, 1 Mar 2026 23:24:58 +0100 Subject: encoder hardening for edge-cases --- src/quotprint.c | 122 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 64 insertions(+), 58 deletions(-) (limited to 'src/quotprint.c') diff --git a/src/quotprint.c b/src/quotprint.c index 9e1caff..c5c7719 100644 --- a/src/quotprint.c +++ b/src/quotprint.c @@ -12,6 +12,7 @@ #define FLG_isEnc (1<<0) #define FLG_isDec (1<<1) #define FLG_isError (1<<2) +#define FLG_isBinary (1<<3) #ifndef BUF_CAP /* aka "buffer capacity" */ # define BUF_CAP 0x10000 @@ -91,8 +92,13 @@ printHelp( void ){ " quotprint - QuotedPrintable codec\n" " Version: " STR_QUOT(PROJECT_VERSION) "\n" " \n" + " About the quoted-printable encoding see:\n" + " https://www.rfc-editor.org/rfc/rfc2045#section-6.7\n" + " \n" " -e `encode` to QuotedPrintable\n" " -d `decode` from QuotedPrintable\n" + " -b When given to `-e`, will encode `binary` safe. Eg newlines will\n" + " be quoted always.\n" " \n" "\n"); } @@ -123,6 +129,8 @@ nextArg: this->flg |= FLG_isEnc; }else if( !strcmp(arg, "-d") ){ this->flg |= FLG_isDec; + }else if( !strcmp(arg, "-b") ){ + this->flg |= FLG_isBinary; }else{ fprintf(stderr, "EINVAL: %s\n", arg); return -1; @@ -291,62 +299,57 @@ mainOfEncode( void*_ ){ if( iSrc >= LEN_SRC ) { goto onSrcBufDrained; } uint_fast8_t c = BUF_SRC[iSrc++]; - switch( c ){ - - /*these are allowed to be raw*/ - case 0x20: /*space*/ - case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: - case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: /*!"#$%&'()*+,-./ */ - case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: - case 0x38: case 0x39: /* 0-9 */ - case 0x3A: case 0x3B: case 0x3C: /* :;< */ - case 0x3E: case 0x3F: case 0x40: /* >?@ */ - case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: case 0x48: - case 0x49: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: case 0x4F: case 0x50: - case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: - case 0x59: case 0x5A: /* A-Z */ - case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60: /* [\]^_` */ - case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: - case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: case 0x70: - case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: case 0x78: - case 0x79: case 0x7A: /* a-z */ - case 0x7B: case 0x7C: case 0x7D: case 0x7E: /* {|}~ */ - if( iDst - lineBeg >= 76 - 1 ){ - BUF_DST[iDst++] = '='; - BUF_DST[iDst++] = '\r'; - BUF_DST[iDst++] = '\n'; - lineBeg = iDst; - } + #define IS_WHITESP(C) (C == 0x09 || C == 0x0A || C == 0x0D || C == 0x20) + #define NEXT_SRC_CHR (BUF_SRC[iSrc]) + #define IS_NEXT_CHR_PRESENT (iSrc + 1 < LEN_SRC) + #define IS_DST_SPACE_AT_LEAST(N) (iDst + (N) < DST_CAP) + #define IS_BINARY_MODE ((this->flg & FLG_isBinary)==0) + #define IS_EBCDIC_RISKY(C) (C == 0x21 || C == 0x23 || C == 0x24 || C == 0x34 \ + || C == 0x40 || C == 0x5B || C == 0x5C || C == 0x5D || C == 0x5E || C == 0x60 \ + || C == 0x7B || C == 0x7C || C == 0x7D || C == 0x7E) + int const isCrLfRaw = (c == 0x0D && IS_NEXT_CHR_PRESENT && IS_DST_SPACE_AT_LEAST(2) + && NEXT_SRC_CHR == 0x0A && IS_BINARY_MODE); + int const isRawOk = 0 + /* A CRLF sequence if a special case, as it CAN be + * printed raw under some circumstances. */ + || isCrLfRaw + /*print as-is. Risk is pretty low to get in trouble with these*/ + || (c == 0x22) + || (c >= 0x25 && c <= 0x33) + || (c >= 0x35 && c <= 0x3C) + || (c >= 0x3E && c <= 0x3F) + || (c >= 0x41 && c <= 0x5A) + || (c >= 0x5F && c <= 0x5F) + || (c >= 0x61 && c <= 0x7A) + /*for EBCDIC, some chars are risky. For now, we'll encode them if binary + * mode given, as I do NOT want to introduce yet-another-bullshit-flag*/ + || (IS_EBCDIC_RISKY(c) && !IS_BINARY_MODE) + /*space MUST NOT be at endOfLine, so check some more conditions*/ + || (c == 0x20 && IS_NEXT_CHR_PRESENT && IS_DST_SPACE_AT_LEAST(4) + && !IS_WHITESP(NEXT_SRC_CHR)) + ; + #undef IS_WHITESP + #undef NEXT_SRC_CHR + #undef IS_NEXT_CHR_PRESENT + #undef IS_DST_HAS_SPACE + #undef IS_EBCDIC_RISKY + int const nBytesOutput = ((isRawOk != 0) * 1 + (isRawOk == 0) * 3); + int const isSoftWrapNecessary = (iDst - lineBeg >= 76 - nBytesOutput); + if( isSoftWrapNecessary ){ + BUF_DST[iDst++] = '='; + BUF_DST[iDst++] = '\r'; + BUF_DST[iDst++] = '\n'; + lineBeg = iDst; + } + if( isRawOk ){ BUF_DST[iDst++] = c; - break; - /* The CrLf sequence if a special case, as it CAN be printed raw. So lets - * try if we have enough context (eg the next byte) to do it. */ - case 0x0D:; - int const isNxtChrPresent = (iSrc + 1 < LEN_SRC); - int const isDstHasSpace = (iDst + 2 < DST_CAP); - if( isNxtChrPresent && isDstHasSpace && BUF_SRC[iSrc] == 0x0A ){ - /* we CAN print raw, as we know it IS a CrLf sequence! */ - BUF_DST[iDst++] = c; - BUF_DST[iDst++] = BUF_SRC[iSrc++]; - lineBeg = iDst; - break; - }else{ - /* fall, to escape them the same way */ - } - /* everything else has to go through escaping */ - default: - if( iDst - lineBeg >= (76-3) ){ - BUF_DST[iDst++] = '='; - BUF_DST[iDst++] = '\r'; - BUF_DST[iDst++] = '\n'; - lineBeg = iDst; - } - char const hx[] = "0123456789ABCDEF"; + if( isCrLfRaw ) /* we have to add the LF octet too */ + { BUF_DST[iDst++] = BUF_SRC[iSrc++]; } + }else{ + static char const hx[] = "0123456789ABCDEF"; BUF_DST[iDst++] = '='; BUF_DST[iDst++] = hx[c >> 4]; BUF_DST[iDst++] = hx[c & 0xF]; - break; - } } assert(!"unreachable"); @@ -415,6 +418,9 @@ mainOfEncode( void*_ ){ INLINE(int) quotPrintDecode( App*this ){ assert(!"TODO_i5TKHBVqBa0lRhqy"); + /* Cited from RFC: when decoding a Quoted-Printable body, any + * trailing white space on a line must be deleted, as it will + * necessarily have been added by intermediate transport agents */ } @@ -427,6 +433,12 @@ run( App*this ){ LOGD("dstBufReadyToFill := dstBufOne.buf\n"); this->srcBufReadyToFill = this->srcBufOne.buf; this->dstBufReadyToFill = this->dstBufOne.buf; + }/*launch a thread for reading*/{ + err = pthread_create(&this->thrdRd, NULL, mainOfInputThread, this); + if( err ){ + assert(err > 0); + err = -err; goto resolveWithErr; + } }/*launch a thread for processing*/{ void *(*processor)(void*) = (this->flg & FLG_isEnc) ? mainOfEncode @@ -437,12 +449,6 @@ run( App*this ){ assert(err > 0); err = -err; goto resolveWithErr; } - }/*launch a thread for reading*/{ - err = pthread_create(&this->thrdRd, NULL, mainOfInputThread, this); - if( err ){ - assert(err > 0); - err = -err; goto resolveWithErr; - } }/*use main thread to do the output writing*/{ mainOfOutputThread(this); err = 0; -- cgit v1.3.1