<?php

/*
 +-----------------------------------------------------------------------+
 | This file is part of the Roundcube Webmail client                     |
 |                                                                       |
 | Copyright (C) The Roundcube Dev Team                                  |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Bounce/resend an email message                                      |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
*/

/**
 * Mail_mime wrapper to handle mail resend/bounce
 */
class rcmail_resend_mail extends \Mail_mime
{
    protected $orig_head;
    protected $orig_body;

    /**
     * Object constructor.
     *
     * @param array $params Class parameters derived from Mail_mime plus
     *                      'bounce_message' - rcube_message object of the original message
     *                      'bounce_headers' - An array of headers to be added to the original message
     */
    public function __construct($params = [])
    {
        // To make the code simpler always use delay_file_io=true
        $params['delay_file_io'] = true;
        $params['eol'] = "\r\n";

        if (!isset($params['bounce_headers'])) {
            $params['bounce_headers'] = [];
        }

        parent::__construct($params);
    }

    /**
     * Returns/Sets message headers
     */
    #[\Override]
    public function headers($headers = [], $overwrite = false, $skip_content = false)
    {
        // headers() wrapper that returns Resent-Cc, Resent-Bcc instead of Cc,Bcc
        // it's also called to re-add Resent-Bcc after it has been sent (to store in Sent)

        if (array_key_exists('Bcc', $headers)) {
            $this->build_params['bounce_headers']['Resent-Bcc'] = $headers['Bcc'];
        }

        foreach ($this->build_params['bounce_headers'] as $key => $val) {
            $headers[str_replace('Resent-', '', $key)] = $val;
        }

        return $headers;
    }

    /**
     * Returns all message headers as string
     */
    #[\Override]
    public function txtHeaders($headers = [], $overwrite = false, $skip_content = false)
    {
        // i.e. add Resent-* headers on top of the original message head
        $this->init_message();

        $result = [];

        foreach ($this->build_params['bounce_headers'] as $name => $value) {
            $key = str_replace('Resent-', '', $name);

            // txtHeaders() can be used to unset Bcc header
            if (array_key_exists($key, $headers)) {
                $value = $headers[$key];
                $this->build_params['bounce_headers']['Resent-' . $key] = $value;
            }

            if ($value) {
                $result[] = "{$name}: {$value}";
            }
        }

        $result = implode($this->build_params['eol'], $result);

        if (strlen($this->orig_head)) {
            $result .= $this->build_params['eol'] . $this->orig_head;
        }

        return $result;
    }

    /**
     * Save the message body to a file (if delay_file_io=true)
     */
    #[\Override]
    public function saveMessageBody($file, $params = null)
    {
        $this->init_message();

        // this will be called only once, so let just move the file
        rename($this->orig_body, $file);

        $this->orig_head = null;

        return true;
    }

    /**
     * Initialize the internal message. Fetches the message from the storage.
     */
    protected function init_message()
    {
        if ($this->orig_head !== null) {
            return;
        }

        $rcmail = rcmail::get_instance();
        $storage = $rcmail->get_storage();
        $message = $this->build_params['bounce_message'];
        $path = rcube_utils::temp_filename('bounce');

        // We'll write the body to the file and the headers to a variable
        if ($fp = fopen($path, 'w')) {
            stream_filter_register('bounce_source', 'rcmail_bounce_stream_filter');
            stream_filter_append($fp, 'bounce_source');

            // message part
            if ($message->context) {
                $message->get_part_body($message->context, false, 0, $fp);
            }
            // complete message
            else {
                $storage->set_folder($message->folder);
                $storage->get_raw_body($message->uid, $fp);
            }

            fclose($fp);

            $this->orig_head = rcmail_bounce_stream_filter::$headers;
            $this->orig_body = $path;
        }
    }
}

/**
 * Stream filter to remove message headers from the streamed
 * message source (and store them in a variable)
 */
class rcmail_bounce_stream_filter extends \php_user_filter
{
    public static $headers;

    protected $in_body = false;

    #[\Override]
    public function onCreate(): bool
    {
        self::$headers = '';

        return true;
    }

    #[\Override]
    #[\ReturnTypeWillChange]
    public function filter($in, $out, &$consumed, $closing)
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            if (!$this->in_body) {
                self::$headers .= $bucket->data;
                if (($pos = strpos(self::$headers, "\r\n\r\n")) === false) {
                    continue;
                }

                $bucket->data = substr(self::$headers, $pos + 4);
                $bucket->datalen = strlen($bucket->data);

                self::$headers = substr(self::$headers, 0, $pos);
                $this->in_body = true;
            }

            $consumed += (int) $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }

        return \PSFS_PASS_ON;
    }
}
