diff --git a/.gitignore b/.gitignore index 2acd9c2..4514284 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ -logs -node_modules/ -.vagrant .idea -target +*.patch \ No newline at end of file diff --git a/readme.md b/readme.md index cfb8556..63f5d89 100644 --- a/readme.md +++ b/readme.md @@ -3,9 +3,9 @@ [![Join the chat at https://gitter.im/synox/disposable-mailbox](https://badges.gitter.im/synox/disposable-mailbox.svg)](https://gitter.im/synox/disposable-mailbox?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Goals: - * easy to use: generate random name or use custom name, auto refresh - * easy to host: just php5 + imap extension - * easy to install: just copy some files + * easy to use: random or custom name, auto refresh + * easy to host: just php5 with imap extension, catch-all mailbox + * easy to install: copy-paste and imap config * minimal code base: minimal features and complexity | ![Screenshot](screenshot.png) | @@ -19,11 +19,11 @@ * Licence: Creative Commons License
disposable-mailbox by github:synox is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. -## Webserver requirements +## Requirements -* php >=5.3.0 -* [imap extension](http://php.net/manual/book.imap.php) -* apache 2 (let me know how it works on nginx!) +* apache 2 webserver with php >=5.3.0 (let me know how it works on nginx!) +* php [imap extension](http://php.net/manual/book.imap.php) +* IMAP account and a domain with catch-all configuration. (all mails go to one mailbox). ## Installation @@ -32,9 +32,10 @@ 2. download a [release](https://github.com/synox/disposable-mailbox/releases) or clone this repository -3. copy the `src` directory to your web server. +3. copy the files in the `src` directory to your web server (not the whole repo!). 4. rename `config.sample.php` to `config.php` and apply the imap settings. Move `config.php` to a safe location outside the `public_html`. 5. edit `backend.php` and set the new path to `config.php`. +6. open it in your browser, check your php error log for messages. ## Build it yourself diff --git a/screenshot.png b/screenshot.png index 000b879..baf5f2d 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/src/backend.php b/src/backend.php index 2212b57..fb67dd1 100644 --- a/src/backend.php +++ b/src/backend.php @@ -21,13 +21,20 @@ function error($status, $text) { } /** - * print all mails for the given $user as a json string. + * print all mails for the given $user. * @param $username string username * @param $address string email address */ function print_emails($username, $address) { - $mail_ids = _search_mails($address); + global $mailbox; + + // Search for mails with the recipient $address in TO or CC. + $mailsIdsTo = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, 'TO "' . $address . '"'); + $mailsIdsCc = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, 'CC "' . $address . '"'); + $mail_ids = array_merge($mailsIdsTo, $mailsIdsCc); + $emails = _load_emails($mail_ids, $address); + header('Content-type: application/json'); print(json_encode(array("mails" => $emails, 'username' => $username, 'address' => $address))); } @@ -35,25 +42,59 @@ function print_emails($username, $address) { /** * deletes emails by id and username. The $address must match the recipient in the email. * - * @param $mailid integer imap email id (integer) + * @param $mailid integer imap email id * @param $address string email address * @internal param the $username matching username */ function delete_email($mailid, $address) { global $mailbox; - // in order to avoid https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References - // the recipient in the email has to match the $address. - $emails = _load_emails(array($mailid), $address); - if (count($emails) === 1) { + if (_load_one_email($mailid, $address) !== null) { $mailbox->deleteMail($mailid); $mailbox->expungeDeletedMails(); + header('Content-type: application/json'); print(json_encode(array("success" => true))); } else { error(404, 'delete error: invalid username/mailid combination'); } } +/** + * download email by id and username. The $address must match the recipient in the email. + * + * @param $mailid integer imap email id + * @param $address string email address + * @internal param the $username matching username + */ + +function download_email($mailid, $address) { + global $mailbox; + + if (_load_one_email($mailid, $address) !== null) { + header("Content-Type: message/rfc822; charset=utf-8"); + header("Content-Disposition: attachment; filename=\"$address-$mailid.eml\""); + + $headers = imap_fetchheader($mailbox->getImapStream(), $mailid, FT_UID); + $body = imap_body($mailbox->getImapStream(), $mailid, FT_UID); + print ($headers . "\n" . $body); + } else { + error(404, 'download error: invalid username/mailid combination'); + } +} + +/** + * Load exactly one email, the $address in TO or CC has to match. + * @param $mailid integer + * @param $address String address + * @return email or null + */ +function _load_one_email($mailid, $address) { + // in order to avoid https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References + // the recipient in the email has to match the $address. + $emails = _load_emails(array($mailid), $address); + return count($emails) === 1 ? $emails[0] : null; +} + /** * Load emails using the $mail_ids, the mails have to match the $address in TO or CC. * @param $mail_ids array of integer ids @@ -67,29 +108,13 @@ function _load_emails($mail_ids, $address) { foreach ($mail_ids as $id) { $mail = $mailbox->getMail($id); // imap_search also returns partials matches. The mails have to be filtered again: - if (!array_key_exists($address, $mail->to) && !array_key_exists($address, $mail->cc)) { - continue; + if (array_key_exists($address, $mail->to) || array_key_exists($address, $mail->cc)) { + $emails[] = $mail; } - $emails[] = $mail; } return $emails; } - -/** - * Search for mails with the recipient $address. - * @param $address string address that has to match TO or CC. - * @return array email ids - */ -function _search_mails($address) { - global $mailbox; - $filterTO = 'TO "' . $address . '"'; - $filterCC = 'CC "' . $address . '"'; - $mailsIdsTo = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, $filterTO); - $mailsIdsCc = imap_sort($mailbox->getImapStream(), SORTARRIVAL, true, SE_UID, $filterCC); - return array_merge($mailsIdsTo, $mailsIdsCc); -} - /** * Remove illegal characters from username and remove everything after the @-sign. You may extend it if your server supports them. * @param $username @@ -98,34 +123,27 @@ function _search_mails($address) { function _clean_username($username) { $username = strtolower($username); $username = preg_replace('/@.*$/', "", $username); // remove part after @ - $username = preg_replace('/[^A-Za-z0-9_.+-]/', "", $username); // remove special characters - return $username; + return preg_replace('/[^A-Za-z0-9_.+-]/', "", $username); // remove special characters } - /** * deletes messages older than X days. */ function delete_old_messages() { - global $mailbox; + global $mailbox, $config; - $date = date('d-M-Y', strtotime('30 days ago')); - $ids = $mailbox->searchMailbox('BEFORE ' . $date); + $ids = $mailbox->searchMailbox('BEFORE ' . date('d-M-Y', strtotime($config['delete_messages_older_than']))); foreach ($ids as $id) { $mailbox->deleteMail($id); } $mailbox->expungeDeletedMails(); } - -header('Content-type: application/json'); - // Never cache requests: header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); - if (isset($_GET['username'])) { // perform common validation: $username = _clean_username($_GET['username']); @@ -135,7 +153,9 @@ if (isset($_GET['username'])) { $address = $username . "@" . $config['mailHostname']; // simple router: - if (isset($_GET['delete_email_id'])) { + if (isset($_GET['download_email_id'])) { + download_email($_GET['download_email_id'], $address); + } else if (isset($_GET['delete_email_id'])) { delete_email($_GET['delete_email_id'], $address); } else { print_emails($username, $address); diff --git a/src/client-libs/index.js b/src/client-libs/index.js index 4925f2b..e3f30f0 100644 --- a/src/client-libs/index.js +++ b/src/client-libs/index.js @@ -39,6 +39,8 @@ app.filter("autolink", function () { app.controller('MailboxController', ["$interval", "$http", "$log", function ($interval, $http, $log) { var self = this; + self.backend_url = backend_url; + self.updateUsername = function (username) { username = username.replace(/[@].*$/, ''); // remove part after "@" if (self.username !== username) { @@ -50,8 +52,7 @@ app.controller('MailboxController', ["$interval", "$http", "$log", function ($in self.address = self.username; // use username until real address has been loaded self.updateMails(); } else { - self.address = null; - self.mails = []; + self.randomize(); } } self.inputFieldUsername = self.username; @@ -82,15 +83,16 @@ app.controller('MailboxController', ["$interval", "$http", "$log", function ($in }; self.loadEmailsAsync = function (username) { - $log.debug("updating mails for ", username); $http.get(backend_url, {params: {username: username}}) .then(function successCallback(response) { - $log.debug("received mails for ", username); if (response.data.mails) { self.error = null; self.mails = response.data.mails; self.address = response.data.address; self.username = response.data.username; + if (self.inputFieldUsername === self.username) { + self.inputFieldUsername = self.address; + } } else { self.error = { title: "JSON_ERROR", @@ -109,11 +111,13 @@ app.controller('MailboxController', ["$interval", "$http", "$log", function ($in }); }; - self.deleteMail = function (mailid, index) { + self.deleteMail = function (mail, index) { // instantly remove from frontend. self.mails.splice(index, 1); + // remove on backend. - $http.get(backend_url, {params: {username: self.username, delete_email_id: mailid}}) + var firstTo = Object.keys(mail.to)[0]; + $http.get(backend_url, {params: {username: firstTo, delete_email_id: mail.id}}) .then( function successCallback(response) { self.updateMails(); diff --git a/src/client-libs/octicon-inbox.gif b/src/client-libs/octicon-inbox.gif deleted file mode 100644 index 76a1518..0000000 Binary files a/src/client-libs/octicon-inbox.gif and /dev/null differ diff --git a/src/client-libs/style.css b/src/client-libs/style.css index 8776b2c..4f09eff 100644 --- a/src/client-libs/style.css +++ b/src/client-libs/style.css @@ -17,15 +17,15 @@ div.min-height { min-height: 400px; } -.nav-container { +header { background-color: #D9E2E9; + /* leave some space on top */ + padding-top: 5px; } -.octicon-inbox { - display: inline-block; - width: 26px; - height: 23px; - background: url('octicon-inbox.gif') no-repeat; +#openRandomButton { + /* center vertically */ + margin-top: 6px; } .sticky-header { @@ -81,3 +81,4 @@ div.min-height { div.min-height { min-height: 400px; } + diff --git a/src/config.sample.php b/src/config.sample.php index 97154c5..9c03143 100644 --- a/src/config.sample.php +++ b/src/config.sample.php @@ -12,11 +12,13 @@ error_reporting(E_ALL); // see https://en.wikipedia.org/wiki/Cross-origin_resource_sharing // header("Access-Control-Allow-Origin: *"); -// setup imap connection -$config['imap']['host'] = "localhost"; -$config['imap']['url'] = '{' . $config['imap']['host'] . '/imap/ssl}INBOX'; +// Change IMAP settings (check SSL flags on http://php.net/manual/en/function.imap-open.php) +$config['imap']['url'] = '{example.com/imap/ssl}INBOX'; $config['imap']['username'] = "test"; $config['imap']['password'] = "test"; // email domain, usually different from imap hostname: $config['mailHostname'] = "example.com"; + +// When to delete old messages? +$config['delete_messages_older_than'] = '30 days ago'; \ No newline at end of file diff --git a/src/favicon.gif b/src/favicon.gif new file mode 100644 index 0000000..b4174b3 Binary files /dev/null and b/src/favicon.gif differ diff --git a/src/index.html b/src/index.html index 5bba59c..a40a7c2 100644 --- a/src/index.html +++ b/src/index.html @@ -8,38 +8,40 @@ - + - -
- - +
@@ -53,28 +55,38 @@ browser and your php error logs.

-

- Use the buttons above to create a new inbox, or open a specific mailbox. -

-
-

Inbox is empty.

+

Your mailbox {{$ctrl.address}} is ready.

+

Emails will appear here automatically. They will be deleted after 30 days.


-

-

Emails to {{address}} will be automatically displayed on this page.

+
+