<?php
require_once('.contact_config.php');

/**
 * @author Gernot WALZL
 */
class Uploader {

    protected $config = null;

    function __construct($config) {
        $this->config = $config;
    }

    function to_bytes($str_val) {
        $str_val = trim($str_val);
        $modifier = strtoupper(substr($str_val, -1));
        $bytes = (float)substr($str_val, 0, strlen($str_val)-1);
        switch($modifier) {
            case 'G':
                $bytes *= 1024;
            case 'M':
                $bytes *= 1024;
            case 'K':
                $bytes *= 1024;
        }
        return $bytes;
    }

    function get_max_file_size() {
        $max_upload_size = $this->to_bytes(ini_get('upload_max_filesize'));
        $max_post_size = $this->to_bytes(ini_get('post_max_size'));

        $max_file_size = $max_upload_size;
        if ($max_upload_size > $max_post_size && $max_post_size != 0) {
            $max_file_size = $max_post_size;
        }
        return $max_file_size;
    }

    function str_startswith($str, $sub) {
        return ( substr($str, strlen($sub)) === $sub );
    }

    function str_endswith($str, $sub) {
        return ( substr($str, -strlen($sub)) === $sub );
    }

    function has_allowed_ext($filename) {
        $result = false;
        foreach($this->config->allowed_exts as $ext) {
            if ($this->str_endswith($filename, '.'.$ext)) {
                $result = true;
                break;
            }
        }
        return $result;
    }

    function get_dst_dir($abs = false) {
        $result = $this->config->dst_dir;
        if ($abs) {
            $result = dirname(__FILE__).'/'.$this->config->dst_dir;
        }
        return $result;
    }

    function get_ext($filename) {
        $result = '';
        if (strstr($filename, '.')) {
            $exploded = explode('.', $filename);
            $result = end($exploded);
        }
        return $result;
    }

    function get_valid_dst($filename) {
        $result = '';
        if (strlen($filename) == 0) {
            $filename = 'unknown';
        }
        $basename = $filename;
        $ext = $this->get_ext($filename);
        if (!empty($ext)) {
            $basename = substr($filename, 0, -strlen($ext)-1);
            if (strlen($ext) > 8) {
                $ext = substr($ext, 0, 8);
            }
            $ext = preg_replace('/[^a-zA-Z0-9\-]/', '_', $ext);
            $ext = '.'.$ext;
        }
        if (strlen($basename) > 64) {
            $basename = substr($basename, 0, 64);
        }
        $basename = preg_replace('/[^a-zA-Z0-9\-]/', '_', $basename);
        $dst_dir = $this->get_dst_dir(true);
        $dst_dir .= '/';
        $result = $dst_dir.$basename.$ext;
        if (file_exists($result)) {
            $i = 1;
            $result = $dst_dir.$basename.'.'.$i.$ext;
            while (file_exists($result)) {
                $i += 1;
                $result = $dst_dir.$basename.'.'.$i.$ext;
            }
        }
        return $result;
    }

    function compress_file($filename_src, $filename_dst) {
        $result = false;
        $zp = gzopen($filename_dst, 'wb');
        if ($zp) {
            $file = file_get_contents($filename_src);
            if ($file) {
                gzwrite($zp, $file);
                $result = true;
            }
            gzclose($zp);
        }
        if ($result) {
            unlink($filename_src);
        }
        return $result;
    }

    function handle_upload($filename_dst, $key, $compress = false) {
        if (empty($filename_dst)) {
            return false;
        }
        $result = false;
        set_time_limit(3600);  // 1 hour
        if (is_uploaded_file($_FILES[$key]['tmp_name'])) {
            if ($compress) {
                if ($this->compress_file($_FILES[$key]['tmp_name'],
                        $filename_dst)) {
                    $result = true;
                }
            } else {
                if (move_uploaded_file($_FILES[$key]['tmp_name'],
                        $filename_dst)) {
                    $result = true;
                }
            }
        }
        return $result;
    }

    /**
     * https://www.php.net/manual/en/features.file-upload.errors.php
     */
    function get_error_message($code) {
        $msg = "";
        switch ($code) {
            case UPLOAD_ERR_OK:
                $msg = "There is no error, the file uploaded with success.";
                break;
            case UPLOAD_ERR_INI_SIZE:
                $msg = "The uploaded file exceeds the upload_max_filesize directive in php.ini.";
                break;
            case UPLOAD_ERR_FORM_SIZE:
                $msg = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.";
                break;
            case UPLOAD_ERR_PARTIAL:
                $msg = "The uploaded file was only partially uploaded.";
                break;
            case UPLOAD_ERR_NO_FILE:
                $msg = "No file was uploaded.";
                break;
            case UPLOAD_ERR_NO_TMP_DIR:
                $msg = "Missing a temporary folder.";
                break;
            case UPLOAD_ERR_CANT_WRITE:
                $msg = "Failed to write file to disk.";
                break;
            case UPLOAD_ERR_EXTENSION:
                $msg = "A PHP extension stopped the file upload.";
                break;
        }
        return $msg;
    }

}

/**
 * @author Gernot WALZL
 */
class MailValidator {

    protected $config = null;

    function __construct($config) {
        $this->config = $config;
    }

    function is_valid_key($key) {
        if ($key == hash('crc32b', date('YmdH')) ||
                $key == hash('crc32b', date('YmdH', time()-3600))) {
            return true;
        }
        return false;
    }

    function is_valid_name($name) {
        if (strlen($name) <= 64 && !strpbrk($name, ":<>\n")) {
            return true;
        }
        return false;
    }

    function is_valid_mail($mail) {
        if (strlen($mail) <= 64 && !strpbrk($mail, ":<>\n") &&
                strstr($mail, '@') && strstr($mail, '.')) {
            return true;
        }
        return false;
    }

    function is_blocked($name, $mail, $msg) {
        foreach ($this->config->blocked_addrs as $blocked_addr) {
            if ($_SERVER['REMOTE_ADDR'] == $blocked_addr) {
                return true;
            }
        }
        foreach ($this->config->blocked_names as $blocked_name) {
            if (strstr($name, $blocked_name)) {
                return true;
            }
        }
        foreach ($this->config->blocked_mails as $blocked_mail) {
            if (strstr($mail, $blocked_mail)) {
                return true;
            }
        }
        foreach ($this->config->blocked_msgs as $blocked_msg) {
            if (strstr($msg, $blocked_msg)) {
                return true;
            }
        }
        return false;
    }

}

/**
 * @author Gernot WALZL
 */
class ContactHTML {

    function println($msg) {
        print($msg."\n");
    }

    function print_head() {
        $this->println('<!DOCTYPE html>');
        $this->println('<html lang="en">');
        $this->println('<head>');
        $this->println('<meta charset="utf-8" />');
        $this->println('<title>PHP Contact Form</title>');
        $this->println('</head>');
        $this->println('<body>');
        $this->println('<h1>PHP Contact Form</h1>');
    }

    function print_foot() {
        $this->println('</body>');
        $this->println('</html>');
    }

    function print_form($name='', $mail='', $msg='', $max_file_size=0) {
        $this->println('<form enctype="multipart/form-data" method="post" '.
            'action="'.$_SERVER['REQUEST_URI'].'">');
        if ($max_file_size) {
            $this->println('<input type="hidden" name="MAX_FILE_SIZE" '.
                'value="'.$max_file_size.'" />');
        }
        $this->println('<input type="hidden" id="key" name="key" '.
            'value="'.hash('crc32b', date('YmdH')).'" />');
        $this->println('<p><label for="name">Name:</label><br />');
        $this->println('<input type="text" id="name" name="name" size="64" '.
            'maxlength="64" value="'.htmlentities($name).'" /></p>');
        $this->println('<p><label for="mail">Email:</label><br />');
        $this->println('<input type="text" id="mail" name="mail" size="64" '.
            'maxlength="64" value="'.htmlentities($mail).'" /></p>');
        $this->println('<p><label for="msg">Message:</label><br />');
        $this->println('<textarea id="msg" name="msg" cols="80" rows="10">'.
            htmlentities($msg).'</textarea></p>');
        $this->println('<p><label for="attachment">Attachment:</label><br />');
        $this->println('<input type="file" id="attachment" '.
            'name="attachment" /></p>');
        $this->println('<input type="submit" value="Send" />');
        $this->println('</form>');
    }

    function print_welcome() {
        $this->println('<p>Please feel free to contact me.</p>');
    }

    function print_info_attachment_uploaded() {
        $this->println('<p><strong>Info:</strong> '.
            'Your attachment has been uploaded successfully.</p>');
    }

    function print_error_attachment($max_file_size) {
        $mod = 1024;
        $units = array('B', 'KB', 'MB', 'GB', 'TB', 'PB');
        for ($i = 0; $max_file_size > $mod; $i++) {
            $max_file_size /= $mod;
        }
        $file_size_hr = round($max_file_size, 2).'&nbsp;'.$units[$i];
        $this->println('<p><strong>Error:</strong> '.
            'Attachment has not been uploaded.<br />');
        $this->println('The file size is limited to '.$file_size_hr.'.</p>');
    }

    function print_info_msg_sent() {
        $this->println('<p><strong>Info:</strong> '.
            'Your message has been sent successfully.</p>');
    }

    function print_error_msg() {
        $this->println('<p><strong>Error:</strong> '.
            'Could not send your message.</p>');
        $this->println('<ul>');
        $this->println('<li>Fill out your name, email, and message.</li>');
        $this->println('<li>Check your email address.</li>');
        $this->println('<li>Your message may contain spam.</li>');
        $this->println('</ul>');
    }

}


$html_contact = new ContactHTML();
$cfg_upload = new UploadConfig();
$uploader = new Uploader($cfg_upload);

$key = '';
if (!empty($_POST['key'])) {
    $key = trim($_POST['key']);
}
$name = '';
if (!empty($_POST['name'])) {
    $name = trim($_POST['name']);
}
$mail = '';
if (!empty($_POST['mail'])) {
    $mail = trim($_POST['mail']);
}
$msg = '';
if (!empty($_POST['msg'])) {
    $msg = trim($_POST['msg']);
}
$attachment = false;
if (!empty($_FILES['attachment']['name'])) {
    $attachment = true;
}

$sent = false;
$attachment_uploaded = false;
if (!empty($key) && !empty($name) && !empty($mail) &&
        (!empty($msg) || $attachment)) {
    $cfg_mail = new MailConfig();
    $validator = new MailValidator($cfg_mail);
    if ($validator->is_valid_key($key) &&
            $validator->is_valid_name($name) &&
            $validator->is_valid_mail($mail) &&
            !$validator->is_blocked($name, $mail, $msg)) {
        $headers = 'From: '.$name.' <'.$mail.'>';

        $message = 'DATE = '.date(DATE_ATOM)."\n";
        $message .= 'REMOTE_ADDR = '.$_SERVER['REMOTE_ADDR']."\n";
        $message .= 'HTTP_REFERER = '.$_SERVER['HTTP_REFERER']."\n";
        $message .= 'HTTP_USER_AGENT = '.$_SERVER['HTTP_USER_AGENT']."\n";

        if (!empty($msg)) {
            $message .= "\n";
            $message .= $msg."\n";
        }

        if ($attachment) {
            $filename = $_FILES['attachment']['name'];
            $filename_dst = '';
            $compress = false;
            if ($uploader->has_allowed_ext($filename)) {
                $filename_dst = $uploader->get_valid_dst($filename);
            } else {
                $filename_dst = $uploader->get_valid_dst($filename.'.gz');
                $compress = true;
            }
            $message .= "\n";
            $message .= "ATTACHMENT\n";
            $message .= $filename."\n";
            if ($uploader->handle_upload($filename_dst, 'attachment',
                    $compress)) {
                $message .= $cfg_upload->link.$cfg_upload->dst_dir.'/'.
                    basename($filename_dst)."\n";
                $attachment_uploaded = true;
            } else {
                $message .= 'Error: '.$uploader->get_error_message(
                    $_FILES['attachment']['error'])."\n";
            }
        }

        if (mail($cfg_mail->to, $cfg_mail->subject, $message, $headers)) {
            $sent = true;
        }
    }
}

//$html_contact->print_head();
$max_file_size = $uploader->get_max_file_size();
if (!empty($name) || !empty($mail) || !empty($msg) || $attachment) {
    if ($sent) {
        if ($attachment) {
            if ($attachment_uploaded) {
                $html_contact->print_info_attachment_uploaded();
            } else {
                $html_contact->print_error_attachment($max_file_size);
            }
        }
        $html_contact->print_info_msg_sent();
        $msg = '';
    } else {
        $html_contact->print_error_msg();
    }
} else {
    $html_contact->print_welcome();
}
$html_contact->print_form($name, $mail, $msg, $max_file_size);
//$html_contact->print_foot();

?>