diff --git a/attachment.php b/attachment.php index 1c0941d7d545d5bbdfaeaa50029d9900ba6e5b39..73dbfd710b9a58f164034d35239842f01a102f17 100644 --- a/attachment.php +++ b/attachment.php @@ -25,7 +25,7 @@ if(!$thisclient die('Unknown attachment!'); //Validate session access hash - we want to make sure the link is FRESH! and the user has access to the parent ticket!! -$vhash=md5($attachment->getFileId().session_id().$file->getHash()); +$vhash=md5($attachment->getFileId().session_id().strtolower($file->getKey())); if(strcasecmp(trim($_GET['h']),$vhash) || !($ticket=$attachment->getTicket()) || !$ticket->checkUserAccess($thisclient)) diff --git a/include/ajax.draft.php b/include/ajax.draft.php index 0caaeeb7bb497cbd06ca7a4e83b860f16232ec0b..3cad72194b839d4b4c26b114c0902c6201d9c727 100644 --- a/include/ajax.draft.php +++ b/include/ajax.draft.php @@ -107,7 +107,7 @@ class DraftAjaxAPI extends AjaxController { return Http::response(500, 'Unable to attach image'); echo JsonDataEncoder::encode(array( - 'content_id' => 'cid:'.$f->getHash(), + 'content_id' => 'cid:'.$f->getKey(), 'filelink' => sprintf('image.php?h=%s', $f->getDownloadHash()) )); } diff --git a/include/class.config.php b/include/class.config.php index f5c7077d7dcbe07cddca31d10e83d1e63a7cb32e..54928de8f28a5e49934d516df8cda0bd5b52e2a2 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -149,6 +149,7 @@ class OsticketConfig extends Config { 'name_format' => 'full', # First Last 'auto_claim_tickets'=> true, 'system_language' => 'en_US', + 'default_storage_bk' => 'D', ); function OsticketConfig($section=null) { @@ -725,6 +726,10 @@ class OsticketConfig extends Config { return $this->get('upload_dir'); } + function getDefaultStorageBackendChar() { + return $this->get('default_storage_bk'); + } + function getVar($name) { return $this->get($name); } @@ -855,6 +860,9 @@ class OsticketConfig extends Config { if(!Validator::process($f, $vars, $errors) || $errors) return false; + if (isset($vars['default_storage_bk'])) + $this->update('default_storage_bk', $vars['default_storage_bk']); + return $this->updateAll(array( 'random_ticket_ids'=>$vars['random_ticket_ids'], 'default_priority_id'=>$vars['default_priority_id'], diff --git a/include/class.cron.php b/include/class.cron.php index 2dcfc1b4ba544dbedbca90ce76895e70f288828b..06d992cdcea53dcca5f02891014be3c6ce27a37a 100644 --- a/include/class.cron.php +++ b/include/class.cron.php @@ -102,7 +102,7 @@ class Cron { self::PurgeDrafts(); self::MaybeOptimizeTables(); - Signal::send('cron'); + Signal::send('cron', null); } } ?> diff --git a/include/class.file.php b/include/class.file.php index a5cc07940e67f7e62bfcfcd8acc9bc9cdf5f133c..52451de550c6400e91c23870de0b1ec3243a505c 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -29,7 +29,7 @@ class AttachmentFile { if(!$id && !($id=$this->getId())) return false; - $sql='SELECT id, f.type, size, name, hash, ft, f.created, ' + $sql='SELECT id, f.type, size, name, `key`, signature, ft, bk, f.created, ' .' count(DISTINCT a.object_id) as canned, count(DISTINCT t.ticket_id) as tickets ' .' FROM '.FILE_TABLE.' f ' .' LEFT JOIN '.ATTACHMENT_TABLE.' a ON(a.file_id=f.id) ' @@ -78,7 +78,7 @@ class AttachmentFile { } function getBackend() { - return $this->ht['ft']; + return $this->ht['bk']; } function getMime() { @@ -93,8 +93,14 @@ class AttachmentFile { return $this->ht['name']; } - function getHash() { - return $this->ht['hash']; + function getKey() { + return $this->ht['key']; + } + + function getSignature() { + $sig = $this->ht['signature']; + if (!$sig) return $this->getKey(); + return $sig; } function lastModified() { @@ -102,15 +108,15 @@ class AttachmentFile { } /** - * Retrieve a hash that can be sent to scp/file.php?h= in order to + * Retrieve a signature that can be sent to scp/file.php?h= in order to * download this file */ function getDownloadHash() { - return strtolower($this->getHash() . md5($this->getId().session_id().$this->getHash())); + return strtolower($this->getKey() . md5($this->getId().session_id().$this->getKey())); } function open() { - return AttachmentStorageBackend::getInstance($this); + return FileStorageBackend::getInstance($this); } function sendData($redirect=true) { @@ -155,7 +161,7 @@ class AttachmentFile { } function makeCacheable($ttl=86400) { - Http::cacheable($this->getHash(), $this->lastModified(), $ttl); + Http::cacheable($this->getSignature(), $this->lastModified(), $ttl); } function display($scale=false) { @@ -242,29 +248,29 @@ class AttachmentFile { $hash = str_replace( array('=','+','/'), array('','-','_'), - substr($sha1, -16) . substr($md5, -16)); + substr($sha1, 0, 16) . substr($md5, 0, 16)); return array($key, $hash); } /* Function assumes the files types have been validated */ - function upload($file, $ft=false) { + function upload($file, $ft='T') { if(!$file['name'] || $file['error'] || !is_uploaded_file($file['tmp_name'])) return false; - list($key, $hash) = static::_getKeyAndHash($file['tmp_name'], true); + list($key, $sig) = self::_getKeyAndHash($file['tmp_name'], true); $info=array('type'=>$file['type'], 'filetype'=>$ft, 'size'=>$file['size'], 'name'=>$file['name'], 'key'=>$key, - 'hash'=>$hash, - 'tmp_nape'=>$file['tmp_name'], + 'signature'=>$sig, + 'tmp_name'=>$file['tmp_name'], ); - return AttachmentFile::save($info); + return AttachmentFile::save($info, $ft); } function uploadLogo($file, &$error, $aspect_ratio=3) { @@ -298,7 +304,7 @@ class AttachmentFile { return false; } - function save($file, $save_bk=true) { + function save($file, $ft=false) { if (isset($file['data'])) { // Allow a callback function to delay or avoid reading or @@ -306,7 +312,7 @@ class AttachmentFile { if (is_callable($file['data'])) $file['data'] = $file['data'](); - list($file['key'], $file['hash']) + list($file['key'], $file['signature']) = static::_getKeyAndHash($file['data']); if (!isset($file['size'])) @@ -315,7 +321,7 @@ class AttachmentFile { // Check and see if the file is already on record $sql = 'SELECT id FROM '.FILE_TABLE - .' WHERE hash='.db_input($file['hash']) + .' WHERE signature='.db_input($file['signature']) .' AND size='.db_input($file['size']); // If the record exists in the database already, a file with the @@ -327,7 +333,8 @@ class AttachmentFile { .',type='.db_input(strtolower($file['type'])) .',size='.db_input($file['size']) .',name='.db_input($file['name']) - .',hash='.db_input($file['hash']); + .',`key`='.db_input($file['key']) + .',signature='.db_input($file['signature']); if (!(db_query($sql) && ($file['id']=db_insert_id()))) return false; @@ -343,10 +350,11 @@ class AttachmentFile { } # XXX: ft does not exists during the upgrade when attachments are - # migrated! - if ($save_bk) { + # migrated! Neither does `bk` + if ($ft) { $sql .= 'UPDATE '.FILE_TABLE.' SET ft=' - .db_input(AttachmentStorageBackend::getTypeChar($bk)) + .db_input(FileStorageBackend::getBkChar($bk)) + .', ft='.db_input($ft) .' WHERE id='.db_input($file->getId()); db_query($sql); } @@ -397,8 +405,8 @@ class AttachmentFile { } print("Updating file meta table\n"); - $sql = 'UPDATE '.FILE_TABLE.' SET ft=' - .db_input($target->getTypeChar()) + $sql = 'UPDATE '.FILE_TABLE.' SET bk=' + .db_input($target->getBkChar()) .' WHERE id='.db_input($this->getId()); if (!db_query($sql) || db_affected_rows()!=1) return false; @@ -407,14 +415,17 @@ class AttachmentFile { return $source->unlink(); } - static function getBackendForFile($info) { - return AttachmentStorageBackend::lookup('T', $info); + static function getBackendForFile($file) { + global $cfg; + + $char = $cfg->getDefaultStorageBackendChar(); + return FileStorageBackend::lookup($char, $file); } /* Static functions */ function getIdByHash($hash) { - $sql='SELECT id FROM '.FILE_TABLE.' WHERE hash='.db_input($hash); + $sql='SELECT id FROM '.FILE_TABLE.' WHERE `key`='.db_input($hash); if(($res=db_query($sql)) && db_num_rows($res)) list($id)=db_fetch_row($res); @@ -497,15 +508,14 @@ class AttachmentFile { .'SELECT file_id FROM '.TICKET_ATTACHMENT_TABLE .' UNION ' .'SELECT file_id FROM '.ATTACHMENT_TABLE - .") AND `ft` NOT IN ('L')"; + .") AND `ft` = 'T'"; if (!($res = db_query($sql))) return false; while (list($id) = db_fetch_row($res)) - if (($file = self::lookup($id)) - && ($bk = $file->open())) - $bk->unlink(); + if (($file = self::lookup($id)) && !$file->delete()) + break; return true; } @@ -522,7 +532,7 @@ class AttachmentFile { } } -class AttachmentStorageBackend { +class FileStorageBackend { var $meta; static $desc = false; static $registry; @@ -543,7 +553,7 @@ class AttachmentStorageBackend { * Retrieves the type char registered for this storage backend's class. * Null is returned if the backend is not properly registered. */ - function getTypeChar() { + function getBkChar() { foreach (self::$registry as $tc=>$class) if ($this instanceof $class) return $tc; @@ -593,7 +603,7 @@ class AttachmentStorageBackend { * contents into memory. */ function upload($filepath) { - return static::write(file_get_contents($filepath)); + return $this->write(file_get_contents($filepath)); } /** @@ -635,13 +645,14 @@ class AttachmentStorageBackend { } } + /** * Attachments stored in the database are cut into 500kB chunks and stored * in the FILE_CHUNK_TABLE to overcome the max_allowed_packet limitation of * LOB fields in the MySQL database */ define('CHUNK_SIZE', 500*1024); # Beware if you change this... -class AttachmentChunkedData extends AttachmentStorageBackend { +class AttachmentChunkedData extends FileStorageBackend { static $desc = "In the database"; function __construct($file) { @@ -685,6 +696,6 @@ class AttachmentChunkedData extends AttachmentStorageBackend { return db_affected_rows() > 0; } } -AttachmentStorageBackend::register('T', 'AttachmentChunkedData'); +FileStorageBackend::register('D', 'AttachmentChunkedData'); ?> diff --git a/include/class.mailer.php b/include/class.mailer.php index 21a7d157d68a28191b88e798dade0633d3c15725..f6b83784edd727361e927d195a1c65702759a26d 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -166,7 +166,7 @@ class Mailer { return $match[0]; $mime->addHTMLImage($file->getData(), $file->getType(), $file->getName(), false, - $file->getHash().'@'.$domain); + $file->getKey().'@'.$domain); // Don't re-attach the image below unset($self->attachments[$file->getId()]); return $match[0].'@'.$domain; diff --git a/include/class.thread.php b/include/class.thread.php index 6fa0d9c13cc74e85dd8e57dce0c4f90411c1166f..7a1af0738caac9fd54afce0e3a07f99afcec37fc 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -528,7 +528,7 @@ Class ThreadEntry { return $this->attachments; //XXX: inner join the file table instead? - $sql='SELECT a.attach_id, f.id as file_id, f.size, f.hash as file_hash, f.name ' + $sql='SELECT a.attach_id, f.id as file_id, f.size, lower(f.`key`) as file_hash, f.name ' .' FROM '.FILE_TABLE.' f ' .' INNER JOIN '.TICKET_ATTACHMENT_TABLE.' a ON(f.id=a.file_id) ' .' WHERE a.ticket_id='.db_input($this->getTicketId()) diff --git a/include/class.yaml.php b/include/class.yaml.php index fc340d70abc0153b3e3f8071b8981c3da17f5cbc..63ceb37543d3fe4044172e1a7775c84e6734feb8 100644 --- a/include/class.yaml.php +++ b/include/class.yaml.php @@ -37,6 +37,6 @@ class YamlDataParser { } class YamlParserError extends Error { - var $title = 'Error parsing YAML document'; + static $title = 'Error parsing YAML document'; } ?> diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index c12a3e29409d401d5036bc94f4f0e23bbf643cbc..b25ad9d597c4daed162e69aa0df78a5f3ef7e32a 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -240,14 +240,16 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) <input type="checkbox" name="email_attachments" <?php echo $config['email_attachments']?'checked="checked"':''; ?> >Email attachments to the user </td> </tr> - <?php if (($bks = AttachmentStorageBackend::allRegistered()) + <?php if (($bks = FileStorageBackend::allRegistered()) && count($bks) > 1) { ?> <tr> <td width="180">Store Attachments:</td> <td><select name="default_storage_bk"><?php foreach ($bks as $char=>$class) { - ?><option value="<?php echo $char; ?> - "><?php echo $class::$desc; ?></option><?php + $selected = $config['default_storage_bk'] == $char + ? 'selected="selected"' : ''; + ?><option <?php echo $selected; ?> value="<?php echo $char; ?>" + ><?php echo $class::$desc; ?></option><?php } ?> </td> </tr> diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index 7fe18f3820d783b5fff99a0e154dc131a174f245..a1b180aae2c0c8d47a8819d29595c83d2b6a0489 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -278,7 +278,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); if($info['cannedattachments']) { foreach($info['cannedattachments'] as $k=>$id) { if(!($file=AttachmentFile::lookup($id))) continue; - $hash=$file->getHash().md5($file->getId().session_id().$file->getHash()); + $hash=$file->getKey().md5($file->getId().session_id().$file->getKey()); echo sprintf('<label><input type="checkbox" name="cannedattachments[]" id="f%d" value="%d" checked="checked" <a href="file.php?h=%s">%s</a> </label> ', diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig index ac8e6b7747418718552fa3ecf14189a652bf9901..9c7611253e6438aec902c1091dd72e2415f2e564 100644 --- a/include/upgrader/streams/core.sig +++ b/include/upgrader/streams/core.sig @@ -1 +1 @@ -934954de8914d9bd2bb8343e805340ae +227215f11d6a2625e13a528e40a156dd diff --git a/include/upgrader/streams/core/ed60ba20-ee1f4b27.patch.sql b/include/upgrader/streams/core/ed60ba20-ee1f4b27.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..cbf4e9d0610ccbdedd23a33a5a986bb351a2a770 --- /dev/null +++ b/include/upgrader/streams/core/ed60ba20-ee1f4b27.patch.sql @@ -0,0 +1,19 @@ +/** + * @version v1.8.1 + * @signature ee1f4b2752ee7b4be24a4d9dfe96185a + * @title Pluggable Storage + * + * This patch will allow attachments to be stored outside the database (like + * on the filesystem) + */ + +ALTER TABLE `%TABLE_PREFIX%file` + ADD `bk` CHAR(1) NOT NULL DEFAULT 'D' AFTER `ft`, + CHANGE `hash` `key` VARCHAR(125) COLLATE ascii_general_ci, + ADD `signature` VARCHAR(125) COLLATE ascii_bin AFTER `key`, + ADD INDEX (`signature`); + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `value` = 'ee1f4b2752ee7b4be24a4d9dfe96185a' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/kb/file.php b/kb/file.php index c411002b8f1ed2f2674f7eb52528dc9355570891..21336765817fa588a82a983af5e8b52dc8da2a85 100644 --- a/kb/file.php +++ b/kb/file.php @@ -23,7 +23,7 @@ $h=trim($_GET['h']); //basic checks if(!$h || strlen($h)!=64 //32*2 || !($file=AttachmentFile::lookup(substr($h,0,32))) //first 32 is the file hash. - || strcasecmp(substr($h,-32),md5($file->getId().session_id().$file->getHash()))) //next 32 is file id + session hash. + || strcasecmp(substr($h,-32),md5($file->getId().session_id().$file->getKey()))) //next 32 is file id + session hash. die('Unknown or invalid file. #'.Format::htmlchars($_GET['h'])); $file->download(); diff --git a/scp/attachment.php b/scp/attachment.php index 44a49d61bd264edef8e54b1c107126a11964aeed..28b9a185b4df488674d9157a13292cefb39bbc60 100644 --- a/scp/attachment.php +++ b/scp/attachment.php @@ -23,7 +23,7 @@ if(!$thisstaff || !$_GET['id'] || !$_GET['h'] die('Unknown attachment!'); //Validate session access hash - we want to make sure the link is FRESH! and the user has access to the parent ticket!! -$vhash=md5($attachment->getFileId().session_id().$file->getHash()); +$vhash=md5($attachment->getFileId().session_id().strtolower($file->getKey())); if(strcasecmp(trim($_GET['h']),$vhash) || !($ticket=$attachment->getTicket()) || !$ticket->checkStaffAccess($thisstaff)) die('Access Denied'); //Download the file.. diff --git a/scp/file.php b/scp/file.php index c02562eb2a2fb1fe334884f5bd7f66f99800f181..4ccc3b828c04f240de799714319781974a0353bd 100644 --- a/scp/file.php +++ b/scp/file.php @@ -23,7 +23,7 @@ $h=trim($_GET['h']); //basic checks if(!$h || strlen($h)!=64 //32*2 || !($file=AttachmentFile::lookup(substr($h,0,32))) //first 32 is the file hash. - || strcasecmp(substr($h,-32),md5($file->getId().session_id().$file->getHash()))) //next 32 is file id + session hash. + || strcasecmp(substr($h,-32),md5($file->getId().session_id().strtolower($file->getKey())))) //next 32 is file id + session hash. die('Unknown or invalid file. #'.Format::htmlchars($_GET['h'])); $file->download(); diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index 1dbdd3b7794c5ebd17a4ae6d8494d2642fcbcde3..a8592ef317175889f8d7ee6cdd61bcb04a1cd51d 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -317,14 +317,17 @@ DROP TABLE IF EXISTS `%TABLE_PREFIX%file`; CREATE TABLE `%TABLE_PREFIX%file` ( `id` int(11) NOT NULL auto_increment, `ft` CHAR( 1 ) NOT NULL DEFAULT 'T', + `bk` CHAR( 1 ) NOT NULL DEFAULT 'D', `type` varchar(255) NOT NULL default '', `size` varchar(25) NOT NULL default '', - `hash` varchar(125) NOT NULL, + `key` varchar(125) collate ascii_bin NOT NULL, + `signature` varchar(125) collate ascii_bin NOT NULL, `name` varchar(255) NOT NULL default '', `created` datetime NOT NULL, PRIMARY KEY (`id`), KEY `ft` (`ft`), - KEY `hash` (`hash`) + KEY `key` (`key`) + KEY `signature` (`signature`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%file_chunk`;