"Fossies" - the Fresh Open Source Software Archive

Member "dovecot-2.3.8/src/lib-mail/istream-attachment-extractor.c" (8 Oct 2019, 20386 Bytes) of package /linux/misc/dovecot-2.3.8.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "istream-attachment-extractor.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.3.7.2_vs_2.3.8.

    1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
    2 
    3 #include "lib.h"
    4 #include "istream-private.h"
    5 #include "ostream.h"
    6 #include "base64.h"
    7 #include "buffer.h"
    8 #include "str.h"
    9 #include "hash-format.h"
   10 #include "rfc822-parser.h"
   11 #include "message-parser.h"
   12 #include "istream-attachment-extractor.h"
   13 
   14 #define BASE64_ATTACHMENT_MAX_EXTRA_BYTES 1024
   15 
   16 enum mail_attachment_state {
   17     MAIL_ATTACHMENT_STATE_NO,
   18     MAIL_ATTACHMENT_STATE_MAYBE,
   19     MAIL_ATTACHMENT_STATE_YES
   20 };
   21 
   22 enum base64_state {
   23     BASE64_STATE_0 = 0,
   24     BASE64_STATE_1,
   25     BASE64_STATE_2,
   26     BASE64_STATE_3,
   27     BASE64_STATE_CR,
   28     BASE64_STATE_EOB,
   29     BASE64_STATE_EOM
   30 };
   31 
   32 struct attachment_istream_part {
   33     char *content_type, *content_disposition;
   34     enum mail_attachment_state state;
   35     /* start offset of the message part in the original input stream */
   36     uoff_t start_offset;
   37 
   38     /* for saving attachments base64-decoded: */
   39     enum base64_state base64_state;
   40     unsigned int base64_line_blocks, cur_base64_blocks;
   41     uoff_t base64_bytes;
   42     bool base64_have_crlf; /* CRLF linefeeds */
   43     bool base64_failed;
   44 
   45     int temp_fd;
   46     struct ostream *temp_output;
   47     buffer_t *part_buf;
   48 };
   49 
   50 struct attachment_istream {
   51     struct istream_private istream;
   52     pool_t pool;
   53 
   54     struct istream_attachment_settings set;
   55     void *context;
   56 
   57     struct message_parser_ctx *parser;
   58     struct message_part *cur_part;
   59     struct attachment_istream_part part;
   60 
   61     bool retry_read;
   62 };
   63 
   64 static void stream_add_data(struct attachment_istream *astream,
   65                 const void *data, size_t size)
   66 {
   67     if (size > 0) {
   68         memcpy(i_stream_alloc(&astream->istream, size), data, size);
   69         astream->istream.pos += size;
   70     }
   71 }
   72 
   73 static void parse_content_type(struct attachment_istream *astream,
   74                    const struct message_header_line *hdr)
   75 {
   76     struct rfc822_parser_context parser;
   77     string_t *content_type;
   78 
   79     if (astream->part.content_type != NULL)
   80         return;
   81 
   82     rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
   83     rfc822_skip_lwsp(&parser);
   84 
   85     T_BEGIN {
   86         content_type = t_str_new(64);
   87         (void)rfc822_parse_content_type(&parser, content_type);
   88         astream->part.content_type = i_strdup(str_c(content_type));
   89     } T_END;
   90     rfc822_parser_deinit(&parser);
   91 }
   92 
   93 static void
   94 parse_content_disposition(struct attachment_istream *astream,
   95               const struct message_header_line *hdr)
   96 {
   97     /* just pass it without parsing to is_attachment() callback */
   98     i_free(astream->part.content_disposition);
   99     astream->part.content_disposition =
  100         i_strndup(hdr->full_value, hdr->full_value_len);
  101 }
  102 
  103 static void astream_parse_header(struct attachment_istream *astream,
  104                  struct message_header_line *hdr)
  105 {
  106     if (!hdr->continued) {
  107         stream_add_data(astream, hdr->name, hdr->name_len);
  108         stream_add_data(astream, hdr->middle, hdr->middle_len);
  109     }
  110     stream_add_data(astream, hdr->value, hdr->value_len);
  111     if (!hdr->no_newline) {
  112         if (hdr->crlf_newline)
  113             stream_add_data(astream, "\r\n", 2);
  114         else
  115             stream_add_data(astream, "\n", 1);
  116     }
  117 
  118     if (hdr->continues) {
  119         hdr->use_full_value = TRUE;
  120         return;
  121     }
  122 
  123     if (strcasecmp(hdr->name, "Content-Type") == 0)
  124         parse_content_type(astream, hdr);
  125     else if (strcasecmp(hdr->name, "Content-Disposition") == 0)
  126         parse_content_disposition(astream, hdr);
  127 }
  128 
  129 static bool astream_want_attachment(struct attachment_istream *astream,
  130                     struct message_part *part)
  131 {
  132     struct istream_attachment_header ahdr;
  133 
  134     if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
  135         /* multiparts may contain attachments as children,
  136            but they're never themselves */
  137         return FALSE;
  138     }
  139     if (astream->set.want_attachment == NULL)
  140         return TRUE;
  141 
  142     i_zero(&ahdr);
  143     ahdr.part = part;
  144     ahdr.content_type = astream->part.content_type;
  145     ahdr.content_disposition = astream->part.content_disposition;
  146     return astream->set.want_attachment(&ahdr, astream->context);
  147 }
  148 
  149 static int astream_base64_decode_lf(struct attachment_istream_part *part)
  150 {
  151     if (part->base64_have_crlf && part->base64_state != BASE64_STATE_CR) {
  152         /* mixed LF vs CRLFs */
  153         return -1;
  154     }
  155     part->base64_state = BASE64_STATE_0;
  156     if (part->cur_base64_blocks < part->base64_line_blocks) {
  157         /* last line */
  158         part->base64_state = BASE64_STATE_EOM;
  159         return 0;
  160     } else if (part->base64_line_blocks == 0) {
  161         /* first line */
  162         if (part->cur_base64_blocks == 0)
  163             return -1;
  164         part->base64_line_blocks = part->cur_base64_blocks;
  165     } else if (part->cur_base64_blocks == part->base64_line_blocks) {
  166         /* line is ok */
  167     } else {
  168         return -1;
  169     }
  170     part->cur_base64_blocks = 0;
  171     return 1;
  172 }
  173 
  174 static int
  175 astream_try_base64_decode_char(struct attachment_istream_part *part,
  176                    size_t pos, char chr)
  177 {
  178     switch (part->base64_state) {
  179     case BASE64_STATE_0:
  180         if (base64_is_valid_char(chr))
  181             part->base64_state++;
  182         else if (chr == '\r')
  183             part->base64_state = BASE64_STATE_CR;
  184         else if (chr == '\n') {
  185             return astream_base64_decode_lf(part);
  186         } else {
  187             return -1;
  188         }
  189         break;
  190     case BASE64_STATE_1:
  191         if (!base64_is_valid_char(chr))
  192             return -1;
  193         part->base64_state++;
  194         break;
  195     case BASE64_STATE_2:
  196         if (base64_is_valid_char(chr))
  197             part->base64_state++;
  198         else if (chr == '=')
  199             part->base64_state = BASE64_STATE_EOB;
  200         else
  201             return -1;
  202         break;
  203     case BASE64_STATE_3:
  204         part->base64_bytes = part->temp_output->offset + pos + 1;
  205         if (base64_is_valid_char(chr)) {
  206             part->base64_state = BASE64_STATE_0;
  207             part->cur_base64_blocks++;
  208         } else if (chr == '=') {
  209             part->base64_state = BASE64_STATE_EOM;
  210             part->cur_base64_blocks++;
  211 
  212             if (part->cur_base64_blocks > part->base64_line_blocks &&
  213                 part->base64_line_blocks > 0) {
  214                 /* too many blocks */
  215                 return -1;
  216             }
  217             return 0;
  218         } else {
  219             return -1;
  220         }
  221         break;
  222     case BASE64_STATE_CR:
  223         if (chr != '\n')
  224             return -1;
  225         if (!part->base64_have_crlf) {
  226             if (part->base64_line_blocks != 0) {
  227                 /* mixed LF vs CRLFs */
  228                 return -1;
  229             }
  230             part->base64_have_crlf = TRUE;
  231         }
  232         return astream_base64_decode_lf(part);
  233     case BASE64_STATE_EOB:
  234         if (chr != '=')
  235             return -1;
  236 
  237         part->base64_bytes = part->temp_output->offset + pos + 1;
  238         part->base64_state = BASE64_STATE_EOM;
  239         part->cur_base64_blocks++;
  240 
  241         if (part->cur_base64_blocks > part->base64_line_blocks &&
  242             part->base64_line_blocks > 0) {
  243             /* too many blocks */
  244             return -1;
  245         }
  246         return 0;
  247     case BASE64_STATE_EOM:
  248         i_unreached();
  249     }
  250     return 1;
  251 }
  252 
  253 static void
  254 astream_try_base64_decode(struct attachment_istream_part *part,
  255               const unsigned char *data, size_t size)
  256 {
  257     size_t i;
  258     int ret;
  259 
  260     if (part->base64_failed || part->base64_state == BASE64_STATE_EOM)
  261         return;
  262 
  263     for (i = 0; i < size; i++) {
  264         ret = astream_try_base64_decode_char(part, i, (char)data[i]);
  265         if (ret <= 0) {
  266             if (ret < 0)
  267                 part->base64_failed = TRUE;
  268             break;
  269         }
  270     }
  271 }
  272 
  273 static int astream_open_output(struct attachment_istream *astream)
  274 {
  275     int fd;
  276 
  277     i_assert(astream->part.temp_fd == -1);
  278 
  279     fd = astream->set.open_temp_fd(astream->context);
  280     if (fd == -1)
  281         return -1;
  282 
  283     astream->part.temp_fd = fd;
  284     astream->part.temp_output = o_stream_create_fd(fd, 0);
  285     o_stream_cork(astream->part.temp_output);
  286     return 0;
  287 }
  288 
  289 static void astream_add_body(struct attachment_istream *astream,
  290                  const struct message_block *block)
  291 {
  292     struct attachment_istream_part *part = &astream->part;
  293     buffer_t *part_buf;
  294     size_t new_size;
  295 
  296     switch (part->state) {
  297     case MAIL_ATTACHMENT_STATE_NO:
  298         stream_add_data(astream, block->data, block->size);
  299         break;
  300     case MAIL_ATTACHMENT_STATE_MAYBE:
  301         /* we'll write data to in-memory buffer until we reach
  302            attachment min_size */
  303         if (part->part_buf == NULL) {
  304             part->part_buf =
  305                 buffer_create_dynamic(default_pool,
  306                               astream->set.min_size);
  307         }
  308         part_buf = part->part_buf;
  309         new_size = part_buf->used + block->size;
  310         if (new_size < astream->set.min_size) {
  311             buffer_append(part_buf, block->data, block->size);
  312             break;
  313         }
  314         /* attachment is large enough. we'll first copy the buffered
  315            data from memory to temp file */
  316         if (astream_open_output(astream) < 0) {
  317             /* failed, fallback to just saving it inline */
  318             part->state = MAIL_ATTACHMENT_STATE_NO;
  319             stream_add_data(astream, part_buf->data, part_buf->used);
  320             stream_add_data(astream, block->data, block->size);
  321             break;
  322         }
  323         part->state = MAIL_ATTACHMENT_STATE_YES;
  324         astream_try_base64_decode(part, part_buf->data, part_buf->used);
  325         hash_format_loop(astream->set.hash_format,
  326                  part_buf->data, part_buf->used);
  327         o_stream_nsend(part->temp_output,
  328                    part_buf->data, part_buf->used);
  329         buffer_set_used_size(part_buf, 0);
  330         /* fall through - write the new data to temp file */
  331     case MAIL_ATTACHMENT_STATE_YES:
  332         astream_try_base64_decode(part, block->data, block->size);
  333         hash_format_loop(astream->set.hash_format,
  334                  block->data, block->size);
  335         o_stream_nsend(part->temp_output, block->data, block->size);
  336         break;
  337     }
  338 }
  339 
  340 static int astream_decode_base64(struct attachment_istream *astream,
  341                  buffer_t **extra_buf_r)
  342 {
  343     struct attachment_istream_part *part = &astream->part;
  344     struct base64_decoder b64dec;
  345     struct istream *input, *base64_input;
  346     struct ostream *output;
  347     const unsigned char *data;
  348     size_t size;
  349     ssize_t ret;
  350     buffer_t *buf;
  351     int outfd;
  352     bool failed = FALSE;
  353 
  354     *extra_buf_r = NULL;
  355 
  356     if (part->base64_bytes < astream->set.min_size ||
  357         part->temp_output->offset > part->base64_bytes +
  358                         BASE64_ATTACHMENT_MAX_EXTRA_BYTES) {
  359         /* only a small part of the MIME part is base64-encoded. */
  360         return -1;
  361     }
  362 
  363     if (part->base64_line_blocks == 0) {
  364         /* only one line of base64 */
  365         part->base64_line_blocks = part->cur_base64_blocks;
  366         i_assert(part->base64_line_blocks > 0);
  367     }
  368 
  369     /* decode base64 data and write it to another temp file */
  370     outfd = astream->set.open_temp_fd(astream->context);
  371     if (outfd == -1)
  372         return -1;
  373 
  374     buf = buffer_create_dynamic(default_pool, 1024);
  375     input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE);
  376     base64_input = i_stream_create_limit(input, part->base64_bytes);
  377     output = o_stream_create_fd_file(outfd, 0, FALSE);
  378     o_stream_cork(output);
  379 
  380     base64_decode_init(&b64dec, &base64_scheme, 0);
  381     hash_format_reset(astream->set.hash_format);
  382     size_t bytes_needed = 1;
  383     while ((ret = i_stream_read_bytes(base64_input, &data, &size,
  384                       bytes_needed)) > 0) {
  385         buffer_set_used_size(buf, 0);
  386         if (base64_decode_more(&b64dec, data, size, &size, buf) < 0) {
  387             i_error("istream-attachment: BUG: "
  388                 "Attachment base64 data unexpectedly broke");
  389             failed = TRUE;
  390             break;
  391         }
  392         i_stream_skip(base64_input, size);
  393         o_stream_nsend(output, buf->data, buf->used);
  394         hash_format_loop(astream->set.hash_format,
  395                  buf->data, buf->used);
  396         bytes_needed = i_stream_get_data_size(base64_input) + 1;
  397     }
  398     if (ret != -1) {
  399         i_assert(failed);
  400     } else if (base64_input->stream_errno != 0) {
  401         i_error("istream-attachment: read(%s) failed: %s",
  402             i_stream_get_name(base64_input),
  403             i_stream_get_error(base64_input));
  404         failed = TRUE;
  405     }
  406     if (base64_decode_finish(&b64dec) < 0) {
  407         i_error("istream-attachment: BUG: "
  408             "Attachment base64 data unexpectedly broke");
  409         failed = TRUE;
  410     }
  411     if (o_stream_finish(output) < 0) {
  412         i_error("istream-attachment: write(%s) failed: %s",
  413             o_stream_get_name(output), o_stream_get_error(output));
  414         failed = TRUE;
  415     }
  416 
  417     buffer_free(&buf);
  418     i_stream_unref(&base64_input);
  419     o_stream_unref(&output);
  420 
  421     if (input->v_offset != part->temp_output->offset && !failed) {
  422         /* write the rest of the data to the message stream */
  423         *extra_buf_r = buffer_create_dynamic(default_pool, 1024);
  424         while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
  425             buffer_append(*extra_buf_r, data, size);
  426             i_stream_skip(input, size);
  427         }
  428         i_assert(ret == -1);
  429         if (input->stream_errno != 0) {
  430             i_error("istream-attachment: read(%s) failed: %s",
  431                 i_stream_get_name(input),
  432                 i_stream_get_error(input));
  433             failed = TRUE;
  434         }
  435     }
  436     i_stream_unref(&input);
  437 
  438     if (failed) {
  439         i_close_fd(&outfd);
  440         return -1;
  441     }
  442 
  443     /* successfully wrote it. switch to using it. */
  444     o_stream_destroy(&part->temp_output);
  445     i_close_fd(&part->temp_fd);
  446     part->temp_fd = outfd;
  447     return 0;
  448 }
  449 
  450 static int
  451 astream_part_finish(struct attachment_istream *astream, const char **error_r)
  452 {
  453     struct attachment_istream_part *part = &astream->part;
  454     struct istream_attachment_info info;
  455     struct istream *input;
  456     struct ostream *output;
  457     string_t *digest_str;
  458     buffer_t *extra_buf = NULL;
  459     const unsigned char *data;
  460     size_t size;
  461     int ret = 0;
  462 
  463     if (o_stream_finish(part->temp_output) < 0) {
  464         *error_r = t_strdup_printf("write(%s) failed: %s",
  465                        o_stream_get_name(part->temp_output),
  466                        o_stream_get_error(part->temp_output));
  467         return -1;
  468     }
  469 
  470     i_zero(&info);
  471     info.start_offset = astream->part.start_offset;
  472     /* base64_bytes contains how many valid base64 bytes there are so far.
  473        if the base64 ends properly, it'll specify how much of the MIME part
  474        is saved as an attachment. the rest of the data (typically
  475        linefeeds) is added back to main stream */
  476     info.encoded_size = part->base64_bytes;
  477     /* get the hash before base64-decoder resets it */
  478     digest_str = t_str_new(128);
  479     hash_format_write(astream->set.hash_format, digest_str);
  480     info.hash = str_c(digest_str);
  481 
  482     /* if it looks like we can decode base64 without any data loss,
  483        do it and write the decoded data to another temp file. */
  484     if (!part->base64_failed) {
  485         if (part->base64_state == BASE64_STATE_0 &&
  486             part->base64_bytes > 0) {
  487             /* there is no trailing LF or '=' characters,
  488                but it's not completely empty */
  489             part->base64_state = BASE64_STATE_EOM;
  490         }
  491         if (part->base64_state == BASE64_STATE_EOM) {
  492             /* base64 data looks ok. */
  493             if (astream_decode_base64(astream, &extra_buf) < 0)
  494                 part->base64_failed = TRUE;
  495         } else {
  496             part->base64_failed = TRUE;
  497         }
  498     }
  499 
  500     /* open attachment output file */
  501     info.part = astream->cur_part;
  502     if (!part->base64_failed) {
  503         info.base64_blocks_per_line = part->base64_line_blocks;
  504         info.base64_have_crlf = part->base64_have_crlf;
  505         /* base64-decoder updated the hash, use it */
  506         str_truncate(digest_str, 0);
  507         hash_format_write(astream->set.hash_format, digest_str);
  508         info.hash = str_c(digest_str);
  509     } else {
  510         /* couldn't decode base64, so write the entire MIME part
  511            as attachment */
  512         info.encoded_size = part->temp_output->offset;
  513     }
  514     if (astream->set.open_attachment_ostream(&info, &output, error_r,
  515                          astream->context) < 0) {
  516         buffer_free(&extra_buf);
  517         return -1;
  518     }
  519 
  520     /* copy data to attachment from temp file */
  521     input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE);
  522     while (i_stream_read_more(input, &data, &size) > 0) {
  523         o_stream_nsend(output, data, size);
  524         i_stream_skip(input, size);
  525     }
  526 
  527     if (input->stream_errno != 0) {
  528         *error_r = t_strdup_printf("read(%s) failed: %s",
  529             i_stream_get_name(input), i_stream_get_error(input));
  530         ret = -1;
  531     }
  532     i_stream_destroy(&input);
  533 
  534     if (astream->set.close_attachment_ostream(output, ret == 0, error_r,
  535                           astream->context) < 0)
  536         ret = -1;
  537     if (ret == 0 && extra_buf != NULL)
  538         stream_add_data(astream, extra_buf->data, extra_buf->used);
  539     buffer_free(&extra_buf);
  540     return ret;
  541 }
  542 
  543 static void astream_part_reset(struct attachment_istream *astream)
  544 {
  545     struct attachment_istream_part *part = &astream->part;
  546 
  547     o_stream_destroy(&part->temp_output);
  548     i_close_fd(&part->temp_fd);
  549 
  550     i_free_and_null(part->content_type);
  551     i_free_and_null(part->content_disposition);
  552     buffer_free(&part->part_buf);
  553 
  554     i_zero(part);
  555     part->temp_fd = -1;
  556     hash_format_reset(astream->set.hash_format);
  557 }
  558 
  559 static int
  560 astream_end_of_part(struct attachment_istream *astream, const char **error_r)
  561 {
  562     struct attachment_istream_part *part = &astream->part;
  563     size_t old_size;
  564     int ret = 0;
  565 
  566     /* MIME part changed. we're now parsing the end of a boundary,
  567        possibly followed by message epilogue */
  568     switch (part->state) {
  569     case MAIL_ATTACHMENT_STATE_NO:
  570         break;
  571     case MAIL_ATTACHMENT_STATE_MAYBE:
  572         /* MIME part wasn't large enough to be an attachment */
  573         if (part->part_buf != NULL) {
  574             stream_add_data(astream, part->part_buf->data,
  575                     part->part_buf->used);
  576             ret = part->part_buf->used > 0 ? 1 : 0;
  577         }
  578         break;
  579     case MAIL_ATTACHMENT_STATE_YES:
  580         old_size = astream->istream.pos - astream->istream.skip;
  581         if (astream_part_finish(astream, error_r) < 0)
  582             ret = -1;
  583         else {
  584             /* finished base64 may have added a few more trailing
  585                bytes to the stream */
  586             ret = astream->istream.pos -
  587                 astream->istream.skip - old_size;
  588         }
  589         break;
  590     }
  591     part->state = MAIL_ATTACHMENT_STATE_NO;
  592     astream_part_reset(astream);
  593     return ret;
  594 }
  595 
  596 static int astream_read_next(struct attachment_istream *astream, bool *retry_r)
  597 {
  598     struct istream_private *stream = &astream->istream;
  599     struct message_block block;
  600     size_t old_size, new_size;
  601     const char *error;
  602     int ret;
  603 
  604     *retry_r = FALSE;
  605 
  606     if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream))
  607         return -2;
  608 
  609     old_size = stream->pos - stream->skip;
  610     switch (message_parser_parse_next_block(astream->parser, &block)) {
  611     case -1:
  612         /* done / error */
  613         ret = astream_end_of_part(astream, &error);
  614         if (ret > 0) {
  615             /* final data */
  616             new_size = stream->pos - stream->skip;
  617             return new_size - old_size;
  618         }
  619         stream->istream.eof = TRUE;
  620         stream->istream.stream_errno = stream->parent->stream_errno;
  621 
  622         if (ret < 0) {
  623             io_stream_set_error(&stream->iostream, "%s", error);
  624             stream->istream.stream_errno = EIO;
  625         }
  626         astream->cur_part = NULL;
  627         return -1;
  628     case 0:
  629         /* need more data */
  630         return 0;
  631     default:
  632         break;
  633     }
  634 
  635     if (block.part != astream->cur_part && astream->cur_part != NULL) {
  636         /* end of a MIME part */
  637         if (astream_end_of_part(astream, &error) < 0) {
  638             io_stream_set_error(&stream->iostream, "%s", error);
  639             stream->istream.stream_errno = EIO;
  640             return -1;
  641         }
  642     }
  643     astream->cur_part = block.part;
  644 
  645     if (block.hdr != NULL) {
  646         /* parsing a header */
  647         astream_parse_header(astream, block.hdr);
  648     } else if (block.size == 0) {
  649         /* end of headers */
  650         if (astream_want_attachment(astream, block.part)) {
  651             astream->part.state = MAIL_ATTACHMENT_STATE_MAYBE;
  652             astream->part.start_offset = stream->parent->v_offset;
  653         }
  654     } else {
  655         astream_add_body(astream, &block);
  656     }
  657     new_size = stream->pos - stream->skip;
  658     *retry_r = new_size == old_size;
  659     return new_size - old_size;
  660 }
  661 
  662 static ssize_t
  663 i_stream_attachment_extractor_read(struct istream_private *stream)
  664 {
  665     struct attachment_istream *astream =
  666         (struct attachment_istream *)stream;
  667     bool retry;
  668     ssize_t ret;
  669 
  670     do {
  671         ret = astream_read_next(astream, &retry);
  672     } while (retry && astream->set.drain_parent_input);
  673 
  674     astream->retry_read = retry;
  675     return ret;
  676 }
  677 
  678 static void i_stream_attachment_extractor_close(struct iostream_private *stream,
  679                         bool close_parent)
  680 {
  681     struct attachment_istream *astream =
  682         (struct attachment_istream *)stream;
  683     struct message_part *parts;
  684 
  685     if (astream->parser != NULL) {
  686         message_parser_deinit(&astream->parser, &parts);
  687     }
  688     hash_format_deinit_free(&astream->set.hash_format);
  689     pool_unref(&astream->pool);
  690     if (close_parent)
  691         i_stream_close(astream->istream.parent);
  692 }
  693 
  694 struct istream *
  695 i_stream_create_attachment_extractor(struct istream *input,
  696                      struct istream_attachment_settings *set,
  697                      void *context)
  698 {
  699     struct attachment_istream *astream;
  700 
  701     i_assert(set->min_size > 0);
  702     i_assert(set->hash_format != NULL);
  703     i_assert(set->open_attachment_ostream != NULL);
  704     i_assert(set->close_attachment_ostream != NULL);
  705 
  706     astream = i_new(struct attachment_istream, 1);
  707     astream->part.temp_fd = -1;
  708     astream->set = *set;
  709     astream->context = context;
  710     astream->retry_read = TRUE;
  711 
  712     /* make sure the caller doesn't try to double-free this */
  713     set->hash_format = NULL;
  714 
  715     astream->istream.max_buffer_size = input->real_stream->max_buffer_size;
  716 
  717     astream->istream.read = i_stream_attachment_extractor_read;
  718     astream->istream.iostream.close = i_stream_attachment_extractor_close;
  719 
  720     astream->istream.istream.readable_fd = FALSE;
  721     astream->istream.istream.blocking = input->blocking;
  722     astream->istream.istream.seekable = FALSE;
  723 
  724     astream->pool = pool_alloconly_create("istream attachment", 1024);
  725     astream->parser = message_parser_init(astream->pool, input, 0,
  726                 MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
  727                 MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES);
  728     return i_stream_create(&astream->istream, input,
  729                    i_stream_get_fd(input), 0);
  730 }
  731 
  732 bool i_stream_attachment_extractor_can_retry(struct istream *input)
  733 {
  734     struct attachment_istream *astream =
  735         (struct attachment_istream *)input->real_stream;
  736 
  737     return astream->retry_read;
  738 }