Esempio n. 1
0
static void hdr_write(string_t *str, struct message_header_line *hdr)
{
	if (!hdr->continued) {
		str_append(str, hdr->name);
		str_append_n(str, hdr->middle, hdr->middle_len);
	}
	str_append_n(str, hdr->value, hdr->value_len);
	if (!hdr->no_newline) {
		if (hdr->crlf_newline)
			str_append_c(str, '\r');
		str_append_c(str, '\n');
	}
}
Esempio n. 2
0
static void
xml_encode_data(string_t *dest, const unsigned char *data, unsigned int len)
{
	unichar_t chr;
	unsigned int i;

	for (i = 0; i < len; i++) {
		switch (data[i]) {
		case '&':
			str_append(dest, "&amp;");
			break;
		case '<':
			str_append(dest, "&lt;");
			break;
		case '>':
			str_append(dest, "&gt;");
			break;
		case '\t':
		case '\n':
		case '\r':
			/* exceptions to the following control char check */
			str_append_c(dest, data[i]);
			break;
		default:
			if (data[i] < 32) {
				/* SOLR doesn't like control characters.
				   replace them with spaces. */
				str_append_c(dest, ' ');
			} else if (data[i] >= 0x80) {
				/* make sure the character is valid for XML
				   so we don't get XML parser errors */
				unsigned int char_len =
					uni_utf8_char_bytes(data[i]);
				if (i + char_len <= len &&
				    uni_utf8_get_char_n(data + i, char_len, &chr) == 1 &&
				    is_valid_xml_char(chr))
					str_append_n(dest, data + i, char_len);
				else {
					str_append_n(dest, utf8_replacement_char,
						     UTF8_REPLACEMENT_CHAR_LEN);
				}
				i += char_len - 1;
			} else {
				str_append_c(dest, data[i]);
			}
			break;
		}
	}
}
Esempio n. 3
0
int rfc822_parse_mime_token(struct rfc822_parser_context *ctx, string_t *str)
{
    const unsigned char *start;

    for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
        if (IS_ATEXT_NON_TSPECIAL(*ctx->data) || *ctx->data == '.')
            continue;

        str_append_n(str, start, ctx->data - start);
        return rfc822_skip_lwsp(ctx);
    }

    str_append_n(str, start, ctx->data - start);
    return 0;
}
Esempio n. 4
0
static void
mbox_save_append_keyword_headers(struct mbox_save_context *ctx,
				 struct mail_keywords *keywords)
{
	unsigned char space[MBOX_HEADER_PADDING+1 +
			    sizeof("Content-Length: \n")-1 + MAX_INT_STRLEN];
	const ARRAY_TYPE(keywords) *keyword_names_list;
	const char *const *keyword_names;
	unsigned int i, count, keyword_names_count;

	keyword_names_list = mail_index_get_keywords(ctx->mbox->box.index);
	keyword_names = array_get(keyword_names_list, &keyword_names_count);

	str_append(ctx->headers, "X-Keywords:");
	count = keywords == NULL ? 0 : keywords->count;
	for (i = 0; i < count; i++) {
		i_assert(keywords->idx[i] < keyword_names_count);

		str_append_c(ctx->headers, ' ');
		str_append(ctx->headers, keyword_names[keywords->idx[i]]);
	}

	memset(space, ' ', sizeof(space));
	str_append_n(ctx->headers, space, sizeof(space));
	ctx->space_end_idx = str_len(ctx->headers);
	str_append_c(ctx->headers, '\n');
}
Esempio n. 5
0
void str_sanitize_append(string_t *dest, const char *src, size_t max_bytes)
{
	unsigned int initial_pos = str_len(dest);
	unichar_t chr;
	size_t i;

	for (i = 0; i < max_bytes && src[i] != '\0'; ) {
		int len = uni_utf8_get_char_n(src+i, max_bytes-i, &chr);
		if (len == 0)
			break; /* input ended too early */

		if (len < 0) {
			/* invalid UTF-8 */
			str_append_c(dest, '?');
			i++;
			continue;
		}
		if ((unsigned char)src[i] < 32)
			str_append_c(dest, '?');
		else
			str_append_n(dest, src+i, len);
		i += len;
	}

	if (src[i] != '\0') {
		if (max_bytes < 3)
			str_truncate(dest, initial_pos);
		else {
			while (str_len(dest) - initial_pos > max_bytes-3)
				str_sanitize_truncate_char(dest, initial_pos);
		}
		str_append(dest, "...");
	}
}
int client_auth_read_line(struct client *client)
{
	const unsigned char *data;
	size_t i, size;
	unsigned int len;

	if (i_stream_read_data(client->input, &data, &size, 0) == -1) {
		client_destroy(client, "Disconnected");
		return -1;
	}

	/* see if we have a full line */
	for (i = 0; i < size; i++) {
		if (data[i] == '\n')
			break;
	}
	if (client->auth_response == NULL)
		client->auth_response = str_new(default_pool, I_MAX(i+1, 256));
	if (str_len(client->auth_response) + i > LOGIN_MAX_AUTH_BUF_SIZE) {
		client_destroy(client, "Authentication response too large");
		return -1;
	}
	str_append_n(client->auth_response, data, i);
	i_stream_skip(client->input, i == size ? size : i+1);

	/* drop trailing \r */
	len = str_len(client->auth_response);
	if (len > 0 && str_c(client->auth_response)[len-1] == '\r')
		str_truncate(client->auth_response, len-1);

	return i < size;
}
Esempio n. 7
0
static int
rfc822_parse_domain_literal(struct rfc822_parser_context *ctx, string_t *str)
{
    const unsigned char *start;

    /*
       domain-literal  = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
       dcontent        = dtext / quoted-pair
       dtext           = NO-WS-CTL /     ; Non white space controls
    		     %d33-90 /       ; The rest of the US-ASCII
    		     %d94-126        ;  characters not including "[",
    				     ;  "]", or "\"
    */
    i_assert(*ctx->data == '[');

    for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
        if (*ctx->data == '\\') {
            ctx->data++;
            if (ctx->data == ctx->end)
                break;
        } else if (*ctx->data == ']') {
            ctx->data++;
            str_append_n(str, start, ctx->data - start);
            return rfc822_skip_lwsp(ctx);
        }
    }

    /* missing ']' */
    return -1;
}
Esempio n. 8
0
void checkpassword_child_input(struct chkpw_auth_request *request)
{
	unsigned char buf[1024];
	ssize_t ret;

	ret = read(request->fd_in, buf, sizeof(buf));
	if (ret <= 0) {
		if (ret < 0) {
			auth_request_log_error(request->request,
				"checkpassword", "read() failed: %m");
		}

		auth_request_log_debug(request->request, "checkpassword",
				       "Received no input");
		checkpassword_request_close(request);
		request->half_finish_callback(request);
	} else {
		if (request->input_buf == NULL)
			request->input_buf = str_new(default_pool, 512);
		str_append_n(request->input_buf, buf, ret);

		auth_request_log_debug(request->request, "checkpassword",
			"Received input: %s", str_c(request->input_buf));
	}
}
Esempio n. 9
0
static char *i_stream_next_line_finish(struct istream_private *stream, size_t i)
{
	char *ret;
	size_t end;

	if (i > 0 && stream->buffer[i-1] == '\r') {
		end = i - 1;
		stream->line_crlf = TRUE;
	} else {
		end = i;
		stream->line_crlf = FALSE;
	}

	if (stream->w_buffer != NULL) {
		/* modify the buffer directly */
		stream->w_buffer[end] = '\0';
		ret = (char *)stream->w_buffer + stream->skip;
	} else {
		/* use a temporary string to return it */
		if (stream->line_str == NULL)
			stream->line_str = str_new(default_pool, 256);
		str_truncate(stream->line_str, 0);
		str_append_n(stream->line_str, stream->buffer + stream->skip,
			     end - stream->skip);
		ret = str_c_modifiable(stream->line_str);
	}

	if (i < stream->pos)
		i++;
	stream->istream.v_offset += i - stream->skip;
	stream->skip = i;
	return ret;
}
Esempio n. 10
0
const char *str_escape(const char *str)
{
	const char *p;
	string_t *ret;

	/* see if we need to quote it */
	for (p = str; *p != '\0'; p++) {
		if (IS_ESCAPED_CHAR(*p))
			break;
	}

	if (*p == '\0')
		return str;

	/* quote */
	ret = t_str_new((size_t) (p - str) + 128);
	str_append_n(ret, str, (size_t) (p - str));

	for (; *p != '\0'; p++) {
		if (IS_ESCAPED_CHAR(*p))
			str_append_c(ret, '\\');
		str_append_c(ret, *p);
	}
	return str_c(ret);
}
Esempio n. 11
0
static void fs_sis_replace_hash_file(struct sis_fs_file *file)
{
	const char *hash_fname, *path = fs_file_path(&file->file);
	struct fs *super_fs = file->super->fs;
	string_t *temp_path;
	int ret;

	if (file->hash_input == NULL) {
		/* hash file didn't exist previously. we should be able to
		   create it with link() */
		if (fs_link(super_fs, path, file->hash_path) < 0) {
			if (errno == EEXIST) {
				/* the file was just created. it's probably
				   a duplicate, but it's too much trouble
				   trying to deduplicate it anymore */
			} else {
				i_error("fs-sis: %s", fs_last_error(super_fs));
			}
		}
		return;
	}

	temp_path = t_str_new(256);
	hash_fname = strrchr(file->hash_path, '/');
	if (hash_fname == NULL)
		hash_fname = file->hash_path;
	else {
		str_append_n(temp_path, file->hash_path,
			     (hash_fname-file->hash_path) + 1);
		hash_fname++;
	}
	str_printfa(temp_path, "%s%s.tmp",
		    super_fs->set.temp_file_prefix, hash_fname);

	/* replace existing hash file atomically */
	ret = fs_link(super_fs, path, str_c(temp_path));
	if (ret < 0 && errno == EEXIST) {
		/* either someone's racing us or it's a stale file.
		   try to continue. */
		if (fs_unlink(super_fs, str_c(temp_path)) < 0 &&
		    errno != ENOENT)
			i_error("fs-sis: %s", fs_last_error(super_fs));
		ret = fs_link(super_fs, path, str_c(temp_path));
	}
	if (ret < 0) {
		i_error("fs-sis: %s", fs_last_error(super_fs));
		return;
	}
	if (fs_rename(super_fs, str_c(temp_path), file->hash_path) < 0) {
		if (errno == ENOENT) {
			/* apparently someone else just renamed it. ignore. */
		} else {
			i_error("fs-sis: %s", fs_last_error(super_fs));
		}
		(void)fs_unlink(super_fs, str_c(temp_path));
	}
}
static void stream_data(string_t *str, const unsigned char *data, size_t size)
{
	const char *text;

	str_truncate(str, 0);
	str_append_n(str, data, size);
	text = str_tabunescape(str_c_modifiable(str));
	doveadm_print_stream(text, strlen(text));
}
Esempio n. 13
0
int rfc822_skip_comment(struct rfc822_parser_context *ctx)
{
    const unsigned char *start;
    int level = 1;

    i_assert(*ctx->data == '(');

    if (ctx->last_comment != NULL)
        str_truncate(ctx->last_comment, 0);

    start = ++ctx->data;
    for (; ctx->data != ctx->end; ctx->data++) {
        switch (*ctx->data) {
        case '(':
            level++;
            break;
        case ')':
            if (--level == 0) {
                if (ctx->last_comment != NULL) {
                    str_append_n(ctx->last_comment, start,
                                 ctx->data - start);
                }
                ctx->data++;
                return ctx->data != ctx->end;
            }
            break;
        case '\\':
            if (ctx->last_comment != NULL) {
                str_append_n(ctx->last_comment, start,
                             ctx->data - start);
            }
            start = ctx->data + 1;

            ctx->data++;
            if (ctx->data == ctx->end)
                return -1;
            break;
        }
    }

    /* missing ')' */
    return -1;
}
Esempio n. 14
0
int rfc822_parse_dot_atom(struct rfc822_parser_context *ctx, string_t *str)
{
	const unsigned char *start;
	int ret;

	/*
	   dot-atom        = [CFWS] dot-atom-text [CFWS]
	   dot-atom-text   = 1*atext *("." 1*atext)

	   atext           =
	     ; Any character except controls, SP, and specials.

	   For RFC-822 compatibility allow LWSP around '.'
	*/
	if (ctx->data == ctx->end || !IS_ATEXT(*ctx->data))
		return -1;

	for (start = ctx->data++; ctx->data != ctx->end; ) {
		if (IS_ATEXT(*ctx->data)) {
			ctx->data++;
			continue;
		}

		str_append_n(str, start, ctx->data - start);

		if ((ret = rfc822_skip_lwsp(ctx)) <= 0)
			return ret;

		if (*ctx->data != '.')
			return 1;

		ctx->data++;
		str_append_c(str, '.');

		if ((ret = rfc822_skip_lwsp(ctx)) <= 0)
			return ret;
		start = ctx->data;
	}

	str_append_n(str, start, ctx->data - start);
	return 0;
}
Esempio n. 15
0
static void
decode_test(const char *qp_input, const char *output, bool broken_input,
	    unsigned int buffer_size)
{
	unsigned int qp_input_len = strlen(qp_input);
	struct istream *input_data, *input;
	const unsigned char *data;
	size_t i, size;
	string_t *str = t_str_new(32);
	int ret = 0;

	input_data = test_istream_create_data(qp_input, qp_input_len);
	test_istream_set_max_buffer_size(input_data, buffer_size);
	test_istream_set_allow_eof(input_data, FALSE);
	input = i_stream_create_qp_decoder(input_data);

	for (i = 1; i <= qp_input_len; i++) {
		test_istream_set_size(input_data, i);
		while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
			str_append_n(str, data, size);
			i_stream_skip(input, size);
		}
		if (ret == -1 && broken_input)
			break;
		test_assert(ret == 0);
	}
	if (ret == 0) {
		test_istream_set_allow_eof(input_data, TRUE);
		while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
			str_append_n(str, data, size);
			i_stream_skip(input, size);
		}
	}
	test_assert(ret == -1);
	test_assert((input->stream_errno == 0 && !broken_input) ||
		    (input->stream_errno == EINVAL && broken_input));

	test_assert(strcmp(str_c(str), output) == 0);
	i_stream_unref(&input);
	i_stream_unref(&input_data);
}
Esempio n. 16
0
int rfc822_parse_atom(struct rfc822_parser_context *ctx, string_t *str)
{
    const unsigned char *start;

    /*
       atom            = [CFWS] 1*atext [CFWS]
       atext           =
         ; Any character except controls, SP, and specials.
    */
    if (ctx->data == ctx->end || !IS_ATEXT(*ctx->data))
        return -1;

    for (start = ctx->data++; ctx->data != ctx->end; ctx->data++) {
        if (IS_ATEXT(*ctx->data))
            continue;

        str_append_n(str, start, ctx->data - start);
        return rfc822_skip_lwsp(ctx);
    }

    str_append_n(str, start, ctx->data - start);
    return 0;
}
Esempio n. 17
0
static void
auth_cache_key_add_var(string_t *str, const char *data, unsigned int len)
{
	if (str_len(str) > 0)
		str_append_c(str, '\t');
	str_append_c(str, '%');
	if (len == 1)
		str_append_c(str, data[0]);
	else {
		str_append_c(str, '{');
		str_append_n(str, data, len);
		str_append_c(str, '}');
	}
}
Esempio n. 18
0
static void test_str_append_n(void)
{
	string_t *str = t_str_new(32);

	test_begin("str_append_n()");
	str_append_n(str, "foo", 0);
	test_assert(str->used == 0);

	str_append_n(str, "\0foo", 4);
	test_assert(str->used == 0);

	str_append_n(str, "foo", 3);
	test_assert(str->used == 3 && memcmp(str_data(str), "foo", 3) == 0);
	str_truncate(str, 0);

	str_append_n(str, "foo", 2);
	test_assert(str->used == 2 && memcmp(str_data(str), "fo", 2) == 0);
	str_truncate(str, 0);

	str_append_n(str, "foo\0bar", 7);
	test_assert(str->used == 3 && memcmp(str_data(str), "foo", 3) == 0);
	str_truncate(str, 0);
	test_end();
}
Esempio n. 19
0
static int
rfc822_parse_atom_or_dot(struct rfc822_parser_context *ctx, string_t *str)
{
    const unsigned char *start;

    /*
       atom            = [CFWS] 1*atext [CFWS]
       atext           =
         ; Any character except controls, SP, and specials.

       The difference between this function and rfc822_parse_dot_atom()
       is that this doesn't just silently skip over all the whitespace.
    */
    for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
        if (IS_ATEXT(*ctx->data) || *ctx->data == '.')
            continue;

        str_append_n(str, start, ctx->data - start);
        return rfc822_skip_lwsp(ctx);
    }

    str_append_n(str, start, ctx->data - start);
    return 0;
}
Esempio n. 20
0
int rfc822_parse_quoted_string(struct rfc822_parser_context *ctx, string_t *str)
{
    const unsigned char *start;
    size_t len;

    i_assert(*ctx->data == '"');
    ctx->data++;

    for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
        switch (*ctx->data) {
        case '"':
            str_append_n(str, start, ctx->data - start);
            ctx->data++;
            return rfc822_skip_lwsp(ctx);
        case '\n':
            /* folding whitespace, remove the (CR)LF */
            len = ctx->data - start;
            if (len > 0 && start[len-1] == '\r')
                len--;
            str_append_n(str, start, len);
            start = ctx->data + 1;
            break;
        case '\\':
            ctx->data++;
            if (ctx->data == ctx->end)
                return -1;

            str_append_n(str, start, ctx->data - start);
            start = ctx->data;
            break;
        }
    }

    /* missing '"' */
    return -1;
}
Esempio n. 21
0
const char *str_tabescape(const char *str)
{
	string_t *tmp;
	const char *p;

	for (p = str; *p != '\0'; p++) {
		if (*p <= '\r') {
			tmp = t_str_new(128);
			str_append_n(tmp, str, p-str);
			str_append_tabescaped(tmp, p);
			return str_c(tmp);
		}
	}
	return str;
}
Esempio n. 22
0
static int
uri_parse_ip_literal(struct uri_parser *parser, string_t *literal,
		     struct in6_addr *ip6_r)
{
	const unsigned char *p;
	const char *address;
	int ret;

	/* IP-literal    = "[" ( IPv6address / IPvFuture  ) "]"
	 * IPvFuture     = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
	 * IPv6address   = ; Syntax not relevant: parsed using inet_pton()
	 */

	/* "[" already verified */

	/* Scan for end of address */
	for (p = parser->cur+1; p < parser->end; p++) {
		if (*p == ']')
			break;
	}

	if (p >= parser->end || *p != ']') {
		parser->error = "Expecting ']' at end of IP-literal";
		return -1;
	}

	if (literal != NULL)
		str_append_n(literal, parser->cur, p-parser->cur+1);
	address = t_strdup_until(parser->cur+1, p);
	parser->cur = p + 1;	

	if (*address == '\0') {
		parser->error = "Empty IPv6 host address";
		return -1;
	}
	if (*address == 'v') {
		parser->error = t_strdup_printf(
			"Future IP host address '%s' not supported", address);
		return -1;
	}
	if ((ret = inet_pton(AF_INET6, address, ip6_r)) <= 0) {
		parser->error = t_strdup_printf(
			"Invalid IPv6 host address '%s'", address);
		return -1;
	}
	return 1;
}
static void server_flush_field(struct server_connection *conn, string_t *str,
			       const unsigned char *data, size_t size)
{
	if (conn->streaming) {
		conn->streaming = FALSE;
		if (size > 0)
			stream_data(str, data, size);
		doveadm_print_stream("", 0);
	} else {
		const char *text;

		str_truncate(str, 0);
		str_append_n(str, data, size);
		text = str_tabunescape(str_c_modifiable(str));
		doveadm_print(text);
	}
}
Esempio n. 24
0
const char *str_sanitize(const char *src, size_t max_len)
{
	string_t *str;
	size_t i;

	if (src == NULL)
		return NULL;

	i = str_sanitize_skip_start(src, max_len);
	if (src[i] == '\0')
		return src;

	str = t_str_new(I_MIN(max_len, 256));
	str_append_n(str, src, i);
	str_sanitize_append(str, src + i, max_len - i);
	return str_c(str);
}
Esempio n. 25
0
/* push current expression */
static void push_expr(ParseCtx *ctx)
{
	STR_DEFINE(expr_text, STR_SIZE);
	Expr *expr;
	Sym  *expr_p;
	Bool  last_was_prefix;

	/* build expression text - split constant prefixes from numbers and names */
	str_clear(expr_text);
	last_was_prefix = FALSE;
	for (expr_p = ctx->expr_start; expr_p < ctx->p; expr_p++)
	{
		if (last_was_prefix && expr_p->tlen > 0 &&
			(isalnum(*expr_p->tstart) || *expr_p->tstart == '"'))
		{
			str_append_char(expr_text, ' ');
			last_was_prefix = FALSE;
		}

		str_append_n(expr_text, expr_p->tstart, expr_p->tlen);

		if (expr_p->tlen > 0)
		{
			switch (expr_p->tstart[expr_p->tlen - 1])
			{
			case '@':
			case '%':
			case '$':
				last_was_prefix = TRUE;
				break;

			default:
				last_was_prefix = FALSE;
			}
		}
	}
	
	/* parse expression */
	expr = parse_expr(str_data(expr_text));

	/* push the new expression, or NULL on error */
	utarray_push_back(ctx->exprs, &expr);

	STR_DELETE(expr_text);
}
Esempio n. 26
0
void str_append_tabunescaped(string_t *dest, const void *src, size_t src_size)
{
	const unsigned char *src_c = src;
	size_t start = 0, i = 0;

	while (i < src_size) {
		for (; i < src_size; i++) {
			if (src_c[i] == '\001')
				break;
		}

		str_append_n(dest, src_c + start, i-start);

		if (i < src_size) {
			i++;
			if (i < src_size) {
				switch (src_c[i]) {
				case '0':
					str_append_c(dest, '\000');
					break;
				case '1':
					str_append_c(dest, '\001');
					break;
				case 't':
					str_append_c(dest, '\t');
					break;
				case 'r':
					str_append_c(dest, '\r');
					break;
				case 'n':
					str_append_c(dest, '\n');
					break;
				default:
					str_append_c(dest, src_c[i]);
					break;
				}
				i++;
			}
		}
		start = i;
	}
}
Esempio n. 27
0
static int http_header_parse_name(struct http_header_parser *parser)
{
	const unsigned char *first = parser->cur;

	/* field-name     = token
	   token          = 1*tchar
	 */
	while (parser->cur < parser->end && http_char_is_token(*parser->cur))
		parser->cur++;

	str_append_n(parser->name, first, parser->cur-first);

	if (parser->cur == parser->end)
		return 0;
	if (str_len(parser->name) == 0) {
		parser->error = "Empty header field name";
		return -1;
	}
	return 1;
}
Esempio n. 28
0
void smtp_xtext_encode(string_t *out, const unsigned char *data,
	size_t size)
{
	const unsigned char *p, *pbegin, *pend;

	p = data;
	pend = p + size;
	while (p < pend) {
		pbegin = p;
		while (p < pend && smtp_char_is_xtext(*p))
			p++;

		str_append_n(out, pbegin, p-pbegin);
		if (p >= pend)
			break;

		str_printfa(out, "+%02X", (unsigned int)*p);
		p++;
	}
}
Esempio n. 29
0
void str_append_unescaped(string_t *dest, const void *src, size_t src_size)
{
	const unsigned char *src_c = src;
	size_t start = 0, i = 0;

	while (i < src_size) {
		for (; i < src_size; i++) {
			if (src_c[i] == '\\')
				break;
		}

		str_append_n(dest, src_c + start, i-start);

		if (i < src_size) {
			if (++i == src_size)
				break;
			str_append_c(dest, src_c[i++]);
		}
		start = i;
	}
}
Esempio n. 30
0
void smtp_string_write(string_t *out, const char *value)
{
	bool quoted = FALSE;
	const unsigned char *p, *pend, *pblock;
	size_t begin = str_len(out);

	if (value == NULL)
		return;
	p = (const unsigned char *)value;
	pend = p + strlen(value);
	while (p < pend) {
		pblock = p;
		while (p < pend && smtp_char_is_atext(*p))
			p++;

		if (!quoted && p < pend) {
			quoted = TRUE;
			str_insert(out, begin, "\"");
		}

		str_append_n(out, pblock, p-pblock);
		if (p >= pend)
			break;

		i_assert(quoted);
		i_assert(smtp_char_is_qpair(*p));

		if (!smtp_char_is_qtext(*p))
			str_append_c(out, '\\');
		str_append_c(out, *p);

		p++;
	}

	if (quoted)
		str_append_c(out, '\"');
}