cmd-append.c revision 5f1d689131a75c39f064cbd4202373e7edf78f18
/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
#include "imap-common.h"
#include "ioloop.h"
#include "istream.h"
#include "istream-chain.h"
#include "ostream.h"
#include "str.h"
#include "imap-resp-code.h"
#include "istream-binary-converter.h"
#include "mail-storage-private.h"
#include "imap-parser.h"
#include "imap-date.h"
#include "imap-util.h"
#include "imap-commands.h"
#include "imap-msgpart-url.h"
/* Don't allow internaldates to be too far in the future. At least with Maildir
they can cause problems with incremental backups since internaldate is
stored in file's mtime. But perhaps there are also some other reasons why
it might not be wanted. */
struct cmd_append_context {
struct client_command_context *cmd;
struct mailbox_transaction_context *t;
struct istream_chain *catchain;
struct imap_parser *save_parser;
struct mail_save_context *save_ctx;
unsigned int count;
bool message_input:1;
bool binary_input:1;
bool catenate:1;
bool failed:1;
};
static const char *
{
if (ctx->literal_size > 0) {
}
}
{
const char *reason;
bool finished;
case -1:
/* disconnected */
/* Reset command so that client_destroy() doesn't try to call
cmd_append_continue_message() anymore. */
return;
case -2:
if (ctx->message_input) {
/* message data, this is handled internally by
mailbox_save_continue() */
break;
}
/* parameter word is longer than max. input buffer size.
this is most likely an error, so skip the new data
until newline is found. */
return;
}
if (!finished)
(void)client_handle_unfinished_cmd(cmd);
else
if (client->disconnected)
else
}
{
/* we must put back the original flush callback before beginning to
sync (the command is still unfinished at that point) */
}
{
/* tagline was already sent, we can abort here */
return FALSE;
}
return TRUE;
}
static int
{
struct imap_msgpart_open_result mpresult;
const char *error;
int ret;
/* catenate URL */
if (ret < 0) {
return -1;
}
if (ret == 0) {
/* invalid url, abort */
return -1;
}
/* empty input */
return 0;
}
"NO [TOOBIG] Composed message grows too big.");
return -1;
}
/* add this input stream to chain */
/* save by reading the chain stream */
do {
"read(%s) failed: %s (for CATENATE URL %s)",
ret = -1;
/* save failed */
ret = -1;
} else {
/* all the input must be consumed, so istream-chain's read()
unreferences the stream and we can free its parent mail */
ret = 0;
}
return ret;
}
static int
{
struct imap_msgpart_url *mpurl;
const char *error;
int ret;
return -1;
if (ret < 0) {
return -1;
}
if (ret == 0) {
/* invalid url, abort */
return -1;
}
return ret;
}
{
"NO [TOOBIG] Composed message grows too big.");
}
/* save the mail */
if (ctx->literal_size == 0) {
/* zero length literal. RFC doesn't explicitly specify
what should be done with this, so we'll simply
handle it by skipping the empty text part. */
} else {
ctx->literal_size);
}
}
static int
{
const char *catpart;
/* Handle URLs until a TEXT literal is encountered */
const char *caturl;
/* URL <url> */
args++;
break;
/* delay failure until we can stop
parsing input */
}
/* TEXT <literal> */
args++;
break;
"Binary input allowed only when the first part is binary.");
}
return 1;
} else {
break;
}
args++;
}
if (IMAP_ARG_IS_EOL(args)) {
/* ")" */
return 0;
}
return -1;
}
{
/* APPEND has already failed */
} else {
}
}
}
{
/* eat away literal_sizes from URLs */
return TRUE;
/* error - handle it later */
return TRUE;
}
args++;
return TRUE;
}
return FALSE;
}
args++;
}
return TRUE;
}
{
const char *msg;
enum imap_parser_error parse_error;
int ret;
/* cancel the command immediately (disconnection) */
return TRUE;
}
/* we're parsing inside CATENATE (..) list after handling a TEXT part.
it's fine that this would need to fully fit into input buffer
(although clients attempting to DoS could simply insert an extra
{1+} between the URLs) */
do {
if (ret == -1) {
switch (parse_error) {
case IMAP_PARSE_ERROR_NONE:
i_unreached();
break;
default:
}
return TRUE;
}
if (ret < 0) {
/* need more data */
return FALSE;
}
/* invalid parameters, abort immediately */
return TRUE;
}
if (ret == 0) {
/* ")" */
/* last catenate part */
return cmd_append_parse_new_msg(cmd);
}
/* TEXT <literal> */
if (!nonsync) {
if (!cmd_append_send_literal_continue(ctx)) {
return TRUE;
}
}
return cmd_append_continue_message(cmd);
}
static int
{
const struct imap_arg *flags_list;
enum mail_flags flags;
const char *const *keywords_list;
struct mail_keywords *keywords;
const char *internal_date_str;
int ret, timezone_offset;
bool valid;
/* [<flags>] */
flags_list = NULL;
else
args++;
/* [<internal date>] */
else {
args++;
}
/* <message literal> | CATENATE (..) */
/* invalid */
/* invalid */
} else {
/* We'll do BINARY conversion only if the CATENATE's first
part is a literal8. If it doesn't and a literal8 is seen
later we'll abort the append with UNKNOWN-CTE. */
}
if (!valid) {
return -1;
}
flags = 0;
} else {
&flags, &keywords_list))
return -1;
if (keywords_list == NULL)
&keywords) < 0) {
/* invalid keywords - delay failure */
}
}
/* no time given, default to now. */
timezone_offset = 0;
} else if (!imap_parse_datetime(internal_date_str,
&internal_date, &timezone_offset)) {
return -1;
}
/* the client specified a time in the future, set it to now. */
timezone_offset = 0;
}
ctx->cat_msg_size = 0;
} else {
if (ctx->literal_size == 0) {
/* no message data, abort */
"NO Can't save a zero byte message.");
}
if (!*nonsync_r) {
return -1;
}
/* {0+} used. although there isn't any point in using
MULTIAPPEND here and adding more messages, it is
technically valid so we'll continue parsing.. */
}
}
if (ctx->binary_input) {
}
/* save the mail */
/* save initialization failed */
}
}
/* normal APPEND */
return 1;
/* zero parts */
return -1;
/* invalid parameters, abort immediately */
return -1;
} else if (ret == 0) {
/* CATENATE consisted only of URLs */
return 0;
} else {
/* TEXT part found from CATENATE */
return 1;
}
}
{
enum mailbox_sync_flags sync_flags;
enum imap_sync_flags imap_flags;
unsigned int save_count;
int ret;
/* eat away the trailing CRLF */
/* we failed earlier, error message is sent */
return TRUE;
}
return TRUE;
}
if (ret < 0) {
return TRUE;
}
/* not supported by backend (virtual) */
} else {
}
sync_flags = 0;
} else {
imap_flags = 0;
}
}
bool *last_literal_r)
{
*last_literal_r = FALSE;
return TRUE;
/* [(flags)] ["internal date"] <message literal> | CATENATE (..) */
args++;
args++;
return TRUE;
return TRUE;
*last_literal_r = TRUE;
}
return FALSE;
}
{
const char *msg;
enum imap_parser_error parse_error;
unsigned int arg_min_count;
bool nonsync, last_literal;
int ret;
/* this function gets called 1) after parsing APPEND <mailbox> and
2) with MULTIAPPEND extension after already saving one or more
mails. */
/* cancel the command immediately (disconnection) */
return TRUE;
}
/* if error occurs, the CRLF is already read. */
/* parse the entire line up to the first message literal, or in case
the input buffer is full of MULTIAPPEND CATENATE URLs, parse at
least until the beginning of the next message */
do {
if (!last_literal)
else {
/* we only read the literal size. now we read the
literal itself. */
}
} while (ret >= (int)arg_min_count &&
if (ret == -1) {
switch (parse_error) {
case IMAP_PARSE_ERROR_NONE:
i_unreached();
break;
default:
}
}
return TRUE;
}
if (ret < 0) {
/* need more data */
return FALSE;
}
if (IMAP_ARG_IS_EOL(args)) {
/* last message */
return cmd_append_finish_parsing(cmd);
}
if (ret < 0) {
/* invalid parameters, abort immediately */
return TRUE;
}
if (ret == 0) {
/* CATENATE contained only URLs. Finish it and see if there
are more messages. */
return cmd_append_parse_new_msg(cmd);
}
if (!nonsync) {
if (!cmd_append_send_literal_continue(ctx)) {
return TRUE;
}
}
return cmd_append_continue_message(cmd);
}
{
int ret = 0;
/* cancel the command immediately (disconnection) */
return TRUE;
}
/* we still have to finish reading the message
from client */
break;
}
break;
}
}
/* saving has already failed, we're just eating away the
literal */
}
/* finished - do one more read, to make sure istream-chain
unreferences its stream, which is needed for litinput's
unreferencing to seek the client->input to correct
position. the seek is needed to avoid trying to seek
backwards in the ctx->input's parent stream. */
/* failed above */
/* client disconnected before it finished sending the
whole message. */
/* CATENATE isn't finished yet */
}
return TRUE;
}
/* prepare for the next message (or its part with catenate) */
return cmd_append_continue_catenate(cmd);
}
return cmd_append_parse_new_msg(cmd);
}
return FALSE;
}
{
struct cmd_append_context *ctx;
const char *mailbox;
/* if transaction is created while its view is synced,
appends aren't allowed for it. */
return FALSE;
}
/* <mailbox> */
return FALSE;
/* we keep the input locked all the time */
else {
}
/* append is special because we're only waiting on client input, not
client output, so disable the standard output handler until we're
finished */
return cmd_append_parse_new_msg(cmd);
}