summaryrefslogtreecommitdiffstats
path: root/src/lib-imap/imap-quote.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-imap/imap-quote.c239
1 files changed, 239 insertions, 0 deletions
diff --git a/src/lib-imap/imap-quote.c b/src/lib-imap/imap-quote.c
new file mode 100644
index 0000000..622e21c
--- /dev/null
+++ b/src/lib-imap/imap-quote.c
@@ -0,0 +1,239 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "imap-arg.h"
+#include "imap-quote.h"
+
+/* If we have quoted-specials (<">, <\>) in a string, the minimum quoted-string
+ overhead is 3 bytes ("\") while the minimum literal overhead is 5 bytes
+ ("{n}\r\n"). But the literal overhead also depends on the string size. If
+ the string length is less than 10, literal catches up to quoted-string after
+ 3 quoted-specials. If the string length is 10..99, it catches up after 4
+ quoted-specials, and so on. We'll assume that the string lengths are usually
+ in double digits, so we'll switch to literals after seeing 4
+ quoted-specials. */
+#define QUOTED_MAX_ESCAPE_CHARS 4
+
+void imap_append_string(string_t *dest, const char *src)
+{
+ i_assert(src != NULL);
+
+ imap_append_nstring(dest, src);
+}
+
+void imap_append_astring(string_t *dest, const char *src)
+{
+ unsigned int i;
+
+ i_assert(src != NULL);
+
+ for (i = 0; src[i] != '\0'; i++) {
+ if (!IS_ASTRING_CHAR(src[i])) {
+ imap_append_string(dest, src);
+ return;
+ }
+ }
+ /* don't mix up NIL and "NIL"! */
+ if (i == 0 || strcasecmp(src, "NIL") == 0)
+ imap_append_string(dest, src);
+ else
+ str_append(dest, src);
+}
+
+static void
+imap_append_literal(string_t *dest, const char *src, unsigned int pos)
+{
+ size_t full_len = pos + strlen(src+pos);
+
+ str_printfa(dest, "{%zu}\r\n", full_len);
+ buffer_append(dest, src, full_len);
+}
+
+void imap_append_nstring(string_t *dest, const char *src)
+{
+ unsigned int escape_count = 0;
+ size_t i;
+
+ if (src == NULL) {
+ str_append(dest, "NIL");
+ return;
+ }
+
+ /* first check if we can (or want to) write this as quoted or
+ as literal.
+
+ quoted-specials = DQUOTE / "\"
+ QUOTED-CHAR = <any TEXT-CHAR except quoted-specials> /
+ "\" quoted-specials
+ TEXT-CHAR = <any CHAR except CR and LF>
+ */
+ for (i = 0; src[i] != '\0'; i++) {
+ switch (src[i]) {
+ case '"':
+ case '\\':
+ if (escape_count++ < QUOTED_MAX_ESCAPE_CHARS)
+ break;
+ /* fall through */
+ case 13:
+ case 10:
+ imap_append_literal(dest, src, i);
+ return;
+ default:
+ if ((unsigned char)src[i] >= 0x80) {
+ imap_append_literal(dest, src, i);
+ return;
+ }
+ break;
+ }
+ }
+ imap_append_quoted(dest, src);
+}
+
+static void remove_newlines_and_append(string_t *dest, const char *src)
+{
+ size_t src_len;
+ string_t *src_nolf;
+ src_len = strlen(src);
+ src_nolf = t_str_new(src_len + 1);
+ for (size_t i = 0; i < src_len; ++i) {
+ if (src[i] != '\r' && src[i] != '\n') {
+ str_append_c(src_nolf, src[i]);
+ } else if (src[i+1] != ' ' &&
+ src[i+1] != '\t' &&
+ src[i+1] != '\r' &&
+ src[i+1] != '\n' &&
+ src[i+1] != '\0') {
+ /* ensure whitespace between lines if new line doesn't start with whitespace */
+ str_append_c(src_nolf, ' ');
+ }
+ }
+ imap_append_nstring(dest, str_c(src_nolf));
+}
+
+void imap_append_nstring_nolf(string_t *dest, const char *src)
+{
+ if (src == NULL || strpbrk(src, "\r\n") == NULL)
+ imap_append_nstring(dest, src);
+ else if (buffer_get_pool(dest)->datastack_pool)
+ remove_newlines_and_append(dest, src);
+ else T_BEGIN {
+ remove_newlines_and_append(dest, src);
+ } T_END;
+}
+
+void imap_append_quoted(string_t *dest, const char *src)
+{
+ str_append_c(dest, '"');
+ for (; *src != '\0'; src++) {
+ switch (*src) {
+ case 13:
+ case 10:
+ /* not allowed */
+ break;
+ case '"':
+ case '\\':
+ str_append_c(dest, '\\');
+ str_append_c(dest, *src);
+ break;
+ default:
+ if ((unsigned char)*src >= 0x80) {
+ /* 8bit input not allowed in dquotes */
+ break;
+ }
+
+ str_append_c(dest, *src);
+ break;
+ }
+ }
+ str_append_c(dest, '"');
+}
+
+void imap_append_string_for_humans(string_t *dest,
+ const unsigned char *src, size_t size)
+{
+ size_t i, pos, remove_count = 0;
+ bool whitespace_prefix = TRUE, last_lwsp = TRUE, modify = FALSE;
+
+ /* first check if there is anything to change */
+ for (i = 0; i < size; i++) {
+ switch (src[i]) {
+ case 0:
+ /* convert NUL to #0x80 */
+ last_lwsp = FALSE;
+ modify = TRUE;
+ break;
+ case 13:
+ case 10:
+ case '\t':
+ modify = TRUE;
+ /* fall through */
+ case ' ':
+ if (last_lwsp) {
+ modify = TRUE;
+ remove_count++;
+ }
+ last_lwsp = TRUE;
+ break;
+ case '"':
+ case '\\':
+ modify = TRUE;
+ last_lwsp = FALSE;
+ break;
+ default:
+ if ((src[i] & 0x80) != 0)
+ modify = TRUE;
+ last_lwsp = FALSE;
+ break;
+ }
+ if (!last_lwsp)
+ whitespace_prefix = FALSE;
+ }
+ if (last_lwsp && i > 0 && !whitespace_prefix) {
+ modify = TRUE;
+ remove_count++;
+ }
+ if (!modify) {
+ /* fast path: we can simply write it as quoted string
+ without any escaping */
+ str_append_c(dest, '"');
+ str_append_data(dest, src, size);
+ str_append_c(dest, '"');
+ return;
+ }
+ if (size == remove_count) {
+ /* contained only whitespace */
+ str_append(dest, "\"\"");
+ return;
+ }
+
+ str_printfa(dest, "{%zu}\r\n", size - remove_count);
+ pos = str_len(dest);
+
+ last_lwsp = TRUE; whitespace_prefix = TRUE;
+ for (i = 0; i < size; i++) {
+ switch (src[i]) {
+ case 0:
+ str_append_c(dest, 128);
+ last_lwsp = FALSE;
+ break;
+ case 13:
+ case 10:
+ case '\t':
+ case ' ':
+ if (!last_lwsp)
+ str_append_c(dest, ' ');
+ last_lwsp = TRUE;
+ break;
+ default:
+ last_lwsp = FALSE;
+ str_append_c(dest, src[i]);
+ break;
+ }
+ if (!last_lwsp)
+ whitespace_prefix = FALSE;
+ }
+ if (last_lwsp && i > 0 && !whitespace_prefix)
+ str_truncate(dest, str_len(dest)-1);
+ i_assert(str_len(dest) - pos == size - remove_count);
+}