281 lines
7.9 KiB
PHP
281 lines
7.9 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2013-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (GPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/gpl.
|
|
*
|
|
* @category Horde
|
|
* @copyright 2013-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/gpl GPL
|
|
* @package IMP
|
|
*/
|
|
|
|
/**
|
|
* Linked attachment data.
|
|
*
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @category Horde
|
|
* @copyright 2013-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/gpl GPL
|
|
* @package IMP
|
|
*/
|
|
class IMP_Compose_LinkedAttachment
|
|
{
|
|
/**
|
|
* Attachment data.
|
|
*
|
|
* @var IMP_Compose_Attachment_Storage
|
|
*/
|
|
protected $_atc;
|
|
|
|
/**
|
|
* Attachment ID (filename in VFS).
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_id;
|
|
|
|
/**
|
|
* Owner of the attachment.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_user;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param string $user Attachment owner.
|
|
* @param string $id ID of the attachment.
|
|
*
|
|
* @throws Horde_Vfs_Exception
|
|
* @throws IMP_Exception
|
|
*/
|
|
public function __construct($user, $id = null)
|
|
{
|
|
global $conf, $injector;
|
|
|
|
/* Sanity checking - no gettext needed. */
|
|
if (empty($conf['compose']['link_attachments'])) {
|
|
throw new IMP_Exception('Linked attachments are forbidden.');
|
|
}
|
|
|
|
$this->_atc = $injector->getInstance('IMP_Factory_ComposeAtc')->create($user, $id);
|
|
$this->_id = $id;
|
|
$this->_user = $user;
|
|
}
|
|
|
|
/**
|
|
* Send data to the browser.
|
|
*
|
|
* @throws IMP_Compose_Exception
|
|
*/
|
|
public function sendData()
|
|
{
|
|
global $browser;
|
|
|
|
if (!$this->_atc->exists()) {
|
|
throw new IMP_Exception(_("The linked attachment does not exist. It may have been deleted by the original sender or it may have expired."));
|
|
}
|
|
|
|
$data = $this->_atc->read();
|
|
|
|
$md = $this->_atc->getMetadata();
|
|
$browser->downloadHeaders($md->filename, $md->type, false, $data->length());
|
|
|
|
while (!$data->eof()) {
|
|
echo $data->substring(0, 8192);
|
|
}
|
|
$data->close();
|
|
}
|
|
|
|
/**
|
|
* Delete a linked attachment.
|
|
*
|
|
* @param string $token The delete token.
|
|
*
|
|
* @return boolean|string Filename of deleted file, or false if file was
|
|
* not deleted.
|
|
*/
|
|
public function delete($token)
|
|
{
|
|
if (empty($GLOBALS['conf']['compose']['link_attachments_notify']) ||
|
|
!($dtoken = $this->_getDeleteToken()) ||
|
|
($dtoken != $token)) {
|
|
return false;
|
|
}
|
|
|
|
$md = $this->_atc->getMetadata();
|
|
|
|
try {
|
|
$this->_atc->delete();
|
|
} catch (Exception $e) {}
|
|
|
|
$this->_atc->saveMetadata();
|
|
|
|
return $md->filename;
|
|
}
|
|
|
|
/**
|
|
* Convert filename from old (pre-6.1) format.
|
|
*
|
|
* @param string $ts Timestamp.
|
|
* @param string $file Filename.
|
|
*
|
|
* @throws IMP_Exception
|
|
*/
|
|
public function convert($ts, $file)
|
|
{
|
|
global $injector;
|
|
|
|
$vfs = $injector->getInstance('Horde_Core_Factory_Vfs')->create();
|
|
|
|
/* Build reproducible ID value from old data. */
|
|
$id = hash('sha1', $ts . '|' . $file);
|
|
|
|
/* Create new attachment object. */
|
|
$atc = $injector->getInstance('IMP_Factory_ComposeAtc')->create($this->_user, $id);
|
|
|
|
$old_path = '.horde/imp/attachments/' . $ts;
|
|
if (!$vfs->exists($old_path, $file)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$vfs->rename($old_path, $file, $atc::VFS_LINK_ATTACH_PATH, $id);
|
|
} catch (Exception $e) {
|
|
return;
|
|
}
|
|
|
|
$d_id = null;
|
|
$notify = $file . '.notify';
|
|
|
|
if ($vfs->exists($old_path, $notify)) {
|
|
try {
|
|
$d_id = $vfs->read($old_path, $notify);
|
|
$vfs->deleteFile($old_path, $notify);
|
|
} catch (Exception $e) {}
|
|
}
|
|
|
|
$md = $atc->getMetadata();
|
|
$md->dtoken = $d_id;
|
|
$md->filename = $file;
|
|
$md->time = $ts;
|
|
$md->type = 'application/octet-stream';
|
|
$atc->saveMetadata($md);
|
|
|
|
$this->_atc = $atc;
|
|
$this->_id = $id;
|
|
}
|
|
|
|
/**
|
|
* Send notification to attachment owner.
|
|
*/
|
|
public function sendNotification()
|
|
{
|
|
global $conf, $injector, $registry;
|
|
|
|
if (empty($conf['compose']['link_attachments_notify'])) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$identity = $injector->getInstance('Horde_Core_Factory_Identity')->create($this->_user);
|
|
$address = $identity->getDefaultFromAddress();
|
|
|
|
/* Ignore missing addresses, which are returned as <>. */
|
|
if ((strlen($address) < 3) || $this->_getDeleteToken()) {
|
|
return;
|
|
}
|
|
|
|
$address_full = $identity->getDefaultFromAddress(true);
|
|
|
|
/* Load user prefs to correctly translate gettext strings. */
|
|
if (!$registry->getAuth()) {
|
|
$prefs = $injector->getInstance('Horde_Core_Factory_Prefs')
|
|
->create('imp', array('user' => $this->_user));
|
|
$registry->setLanguageEnvironment($prefs->getValue('language'));
|
|
}
|
|
|
|
$h = new Horde_Mime_Headers();
|
|
$h->addReceivedHeader(array(
|
|
'dns' => $injector->getInstance('Net_DNS2_Resolver'),
|
|
'server' => $conf['server']['name']
|
|
));
|
|
$h->addMessageIdHeader();
|
|
$h->addUserAgentHeader();
|
|
$h->addHeader('Date', date('r'));
|
|
$h->addHeader('From', $address_full);
|
|
$h->addHeader('To', $address_full);
|
|
$h->addHeader('Subject', _("Notification: Linked attachment downloaded"));
|
|
$h->addHeader('Auto-Submitted', 'auto-generated');
|
|
|
|
$msg = new Horde_Mime_Part();
|
|
$msg->setType('text/plain');
|
|
$msg->setCharset('UTF-8');
|
|
|
|
$md = $this->_atc->getMetadata();
|
|
$msg->setContents(Horde_String::wrap(
|
|
_("Your linked attachment has been downloaded by at least one user.") . "\n\n" .
|
|
sprintf(_("Name: %s"), $md->filename) . "\n" .
|
|
sprintf(_("Type: %s"), $md->type) . "\n" .
|
|
sprintf(_("Sent Date: %s"), date('r', $md->time)) . "\n\n" .
|
|
_("Click on the following link to permanently delete the attachment:") . "\n" .
|
|
strval($this->_atc->link_url->add('d', $this->_getDeleteToken(true)))
|
|
));
|
|
|
|
$msg->send($address, $h, $injector->getInstance('Horde_Mail'));
|
|
} catch (Exception $e) {
|
|
Horde::log($e, 'ERR');
|
|
}
|
|
}
|
|
|
|
/* Static methods. */
|
|
|
|
/**
|
|
* Return UNIX timestamp of linked attachment expiration time.
|
|
*
|
|
* @param boolean $past If true, determine maximim creation time for
|
|
* expiration. If false, determine future expiration
|
|
* time.
|
|
*
|
|
* @return integer|null UNIX timestamp, or null if attachments are not
|
|
* pruned.
|
|
*/
|
|
static public function keepDate($past = true)
|
|
{
|
|
return ($damk = $GLOBALS['prefs']->getValue('delete_attachments_monthly_keep'))
|
|
? mktime(0, 0, 0, date('n') + ($past ? ($damk * -1) : ($damk + 1)), 1, date('Y'))
|
|
: null;
|
|
}
|
|
|
|
/* Private methods. */
|
|
|
|
/**
|
|
* Get/create the delete token.
|
|
*
|
|
* @param boolean $create Create token if it doesn't exist?
|
|
*
|
|
* @return string The delete token, or null if it doesn't exist.
|
|
*/
|
|
protected function _getDeleteToken($create = false)
|
|
{
|
|
$md = $this->_atc->getMetadata();
|
|
|
|
if (is_null($md->dtoken)) {
|
|
if (!$create) {
|
|
return null;
|
|
}
|
|
|
|
$md->dtoken = strval(new Horde_Support_Uuid);
|
|
try {
|
|
$this->_atc->saveMetadata($md);
|
|
} catch (Exception $e) {}
|
|
}
|
|
|
|
return $md->dtoken;
|
|
}
|
|
|
|
}
|