Forráskód Böngészése

Improved images caching: since 2.3.0-beta, iTop handles inline images (case logs, ticket description) and a picture for a person (AttributeImage). This code refactoring handles a case where the browser checks for the validity of the image and the 304 response code can then be returned without checking anything because we assume that the URL of the image contains a signature of it (or the data cannot change -attachement and inline images are in this case).

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4217 a333f486-631f-4898-b8df-5754b55c2be0
romainq 9 éve
szülő
commit
9992b14ed7

+ 1 - 1
core/inlineimage.class.inc.php

@@ -16,7 +16,7 @@
 //   You should have received a copy of the GNU Affero General Public License
 //   along with iTop. If not, see <http://www.gnu.org/licenses/>
 
-define('INLINEIMAGE_DOWNLOAD_URL', 'pages/ajax.render.php?operation=download_inlineimage&id=');
+define('INLINEIMAGE_DOWNLOAD_URL', 'pages/ajax.document.php?operation=download_inlineimage&id=');
 
 /**
  * Persistent classes (internal): store images referenced inside HTML formatted text fields

+ 44 - 4
core/ormdocument.class.inc.php

@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010-2012 Combodo SARL
+// Copyright (C) 2010-2016 Combodo SARL
 //
 //   This file is part of iTop.
 //
@@ -21,7 +21,7 @@
  * ormDocument
  * encapsulate the behavior of a binary data set that will be stored an attribute of class AttributeBlob 
  *
- * @copyright   Copyright (C) 2010-2012 Combodo SARL
+ * @copyright   Copyright (C) 2010-2016 Combodo SARL
  * @license     http://opensource.org/licenses/AGPL-3.0
  */
 
@@ -126,7 +126,7 @@ class ormDocument
 	{
 		// Compute a signature to reset the cache anytime the data changes (this is acceptable if used only with icon files)
 		$sSignature = md5($this->GetData());
-		return utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode&s=$sSignature&cache=86400";
+		return utils::GetAbsoluteUrlAppRoot()."pages/ajax.document.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode&s=$sSignature&cache=86400";
 	}
 
 	
@@ -144,5 +144,45 @@ class ormDocument
 		}
 		return $bRet;
 	}
+
+	/**
+	 * Downloads a document to the browser, either as 'inline' or 'attachment'
+	 *
+	 * @param WebPage $oPage The web page for the output
+	 * @param string $sClass Class name of the object
+	 * @param mixed $id Identifier of the object
+	 * @param string $sAttCode Name of the attribute containing the document to download
+	 * @param string $sContentDisposition Either 'inline' or 'attachment'
+	 * @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
+	 * @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
+	 * @return none
+	 */
+	public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
+	{
+		try
+		{
+			$oObj = MetaModel::GetObject($sClass, $id, false, false);
+			if (!is_object($oObj))
+			{
+				throw new Exception("Invalid id ($id) for class '$sClass' - the object does not exist or you are not allowed to view it");
+			}
+			if (($sSecretField != null) && ($oObj->Get($sSecretField) != $sSecretValue))
+			{
+				usleep(200);
+				throw new Exception("Invalid secret for class '$sClass' - the object does not exist or you are not allowed to view it");
+			}
+			$oDocument = $oObj->Get($sAttCode);
+			if (is_object($oDocument))
+			{
+				$oPage->TrashUnexpectedOutput();
+				$oPage->SetContentType($oDocument->GetMimeType());
+				//$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
+				$oPage->add($oDocument->GetData());
+			}
+		}
+		catch(Exception $e)
+		{
+			$oPage->p($e->getMessage());
+		}
+	}
 }
-?>

+ 4 - 4
datamodels/1.x/itop-attachments/main.attachments.php

@@ -282,7 +282,7 @@ EOF
 						}
 						else
 						{
-							var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&id='+data.att_id+'&field=contents';
+							var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.document.php?operation=download_document&class=Attachment&id='+data.att_id+'&field=contents';
 							$('#attachments').append('<div class="attachment" id="display_attachment_'+data.att_id+'"><a href="'+sDownloadLink+'"><img src="'+data.icon+'"><br/>'+data.msg+'<input id="attachment_'+data.att_id+'" type="hidden" name="attachments[]" value="'+data.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveNewAttachment('+data.att_id+');"/></div>');
 							if($sIsDeleteEnabled)
 							{
@@ -313,7 +313,7 @@ EOF
 				$oDoc = $oAttachment->Get('contents');
 				$sFileName = $oDoc->GetFileName();
 				$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
-				$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
+				$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
 				$oPage->add('<div class="attachment" id="attachment_'.$iAttId.'"><a href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="$(\'#attachment_'.$iAttId.'\').remove();"/>&nbsp;</div>');
 			}
 			
@@ -340,7 +340,7 @@ EOF
 						$oDoc = $oAttachment->Get('contents');
 						$sFileName = $oDoc->GetFileName();
 						$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
-						$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
+						$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
 						$oPage->add('<div class="attachment" id="display_attachment_'.$iAttId.'"><a href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveNewAttachment('.$iAttId.');"/>&nbsp;</div>');
 						$oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."', false /* not an line image */]);");
 					}
@@ -374,7 +374,7 @@ EOF
 					$oDoc = $oAttachment->Get('contents');
 					$sFileName = $oDoc->GetFileName();
 					$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
-					$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
+					$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.document.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
 					$oPage->add('<div class="attachment" id="attachment_'.$iAttId.'"><a href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'</a><input type="hidden" name="attachments[]" value="'.$iAttId.'"/><br/>&nbsp;&nbsp;</div>');
 				}
 			}

+ 1 - 1
datamodels/2.x/itop-attachments/main.attachments.php

@@ -16,7 +16,7 @@
 //   You should have received a copy of the GNU Affero General Public License
 //   along with iTop. If not, see <http://www.gnu.org/licenses/>
 
-define('ATTACHMENT_DOWNLOAD_URL', 'pages/ajax.render.php?operation=download_document&class=Attachment&field=contents&id=');
+define('ATTACHMENT_DOWNLOAD_URL', 'pages/ajax.document.php?operation=download_document&class=Attachment&field=contents&id=');
 
 class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExtension
 {

+ 1 - 1
js/simple_graph.js

@@ -857,7 +857,7 @@ $(function()
 				jTab.find('span').html(sTabText+' <img style="vertical-align:bottom" src="../images/indicator.gif">');
 			}
 			$.post(sUrl, oParams, function(data) {
-				var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&field=contents&id='+data.att_id;
+				var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.document.php?operation=download_document&class=Attachment&field=contents&id='+data.att_id;
 				var sIcon = GetAbsoluteUrlModulesRoot()+'itop-attachments/icons/pdf.png';
 				if (jTab != null)
 				{

+ 106 - 0
pages/ajax.document.php

@@ -0,0 +1,106 @@
+<?php
+// Copyright (C) 2010-2016 Combodo SARL
+//
+//   This file is part of iTop.
+//
+//   iTop is free software; you can redistribute it and/or modify	
+//   it under the terms of the GNU Affero General Public License as published by
+//   the Free Software Foundation, either version 3 of the License, or
+//   (at your option) any later version.
+//
+//   iTop is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU Affero General Public License for more details.
+//
+//   You should have received a copy of the GNU Affero General Public License
+//   along with iTop. If not, see <http://www.gnu.org/licenses/>
+
+
+/**
+ * Handles various ajax requests
+ *
+ * @copyright   Copyright (C) 2010-2016 Combodo SARL
+ * @license     http://opensource.org/licenses/AGPL-3.0
+ */
+
+require_once('../approot.inc.php');
+require_once(APPROOT.'application/utils.inc.php');
+
+
+if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER) && (strlen($_SERVER['HTTP_IF_MODIFIED_SINCE']) > 0))
+{
+	// The content is garanteed to be unmodified since the URL includes a signature based on the contents of the document
+	header('not modified', true, 304);
+	exit;
+}
+
+try
+{
+	require_once(APPROOT.'/application/application.inc.php');
+	require_once(APPROOT.'/application/webpage.class.inc.php');
+	require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
+	require_once(APPROOT.'/application/startup.inc.php');
+
+	require_once(APPROOT.'/application/loginwebpage.class.inc.php');
+	LoginWebPage::DoLoginEx(null /* any portal */, false);
+	
+	$oPage = new ajax_page("");
+	$oPage->no_cache();
+
+	$operation = utils::ReadParam('operation', '');
+	$sClass = utils::ReadParam('class', 'MissingAjaxParam', false, 'class');
+
+	switch($operation)
+	{
+		case 'download_document':
+			$id = utils::ReadParam('id', '');
+			$sField = utils::ReadParam('field', '');
+			if ($sClass == 'Attachment')
+			{
+				$iCacheSec = 31556926; // One year ahead: an attachment cannot change
+			}
+			else
+			{
+				$iCacheSec = (int)utils::ReadParam('cache', 0);
+			}
+			if (!empty($sClass) && ($sClass != 'InlineImage') && !empty($id) && !empty($sField))
+			{
+				ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'attachment');
+				if ($iCacheSec > 0)
+				{
+					$oPage->add_header("Expires: "); // Reset the value set in ajax_page
+					$oPage->add_header("Cache-Control: no-transform,public,max-age=$iCacheSec,s-maxage=$iCacheSec");
+					$oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
+					$oPage->add_header("Last-Modified: Wed, 15 Jun 2015 13:21:15 GMT"); // An arbitrary date in the past is ok
+				}
+			}
+			break;
+
+		case 'download_inlineimage':
+			$id = utils::ReadParam('id', '');
+			$sSecret = utils::ReadParam('s', '');
+			$iCacheSec = 31556926; // One year ahead: an inline image cannot change
+			if (!empty($id) && !empty($sSecret))
+			{
+				ormDocument::DownloadDocument($oPage, 'InlineImage', $id, 'contents', 'attachment', 'secret', $sSecret);
+				$oPage->add_header("Expires: "); // Reset the value set in ajax_page
+				$oPage->add_header("Cache-Control: no-transform,public,max-age=$iCacheSec,s-maxage=$iCacheSec");
+				$oPage->add_header("Pragma: cache"); // Reset the value set .... where ?
+				$oPage->add_header("Last-Modified: Wed, 15 Jun 2016 13:21:15 GMT"); // An arbitrary date in the past is ok
+			}
+			break;
+
+		default:
+		$oPage->p("Invalid query.");
+	}
+
+	$oPage->output();
+}
+catch (Exception $e)
+{
+	// note: transform to cope with XSS attacks
+	echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8');
+	IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
+}
+

+ 1 - 75
pages/ajax.render.php

@@ -772,22 +772,7 @@ try
 		$sField = utils::ReadParam('field', '');
 		if (!empty($sClass) && ($sClass != 'InlineImage') && !empty($id) && !empty($sField))
 		{
-			DownloadDocument($oPage, $sClass, $id, $sField, 'inline');
-		}
-		break;
-		
-		case 'download_document':
-		$id = utils::ReadParam('id', '');
-		$sField = utils::ReadParam('field', '');
-		$iCacheSec = (int) utils::ReadParam('cache', 0);
-		if (!empty($sClass) && ($sClass != 'InlineImage') && !empty($id) && !empty($sField))
-		{
-			DownloadDocument($oPage, $sClass, $id, $sField, 'attachment');
-			if ($iCacheSec > 0)
-			{
-				$oPage->add_header("Expires: "); // Reset the value set in ajax_page
-				$oPage->add_header("Cache-Control: no-transform,public,max-age=$iCacheSec,s-maxage=$iCacheSec");
-			}
+			ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'inline');
 		}
 		break;
 		
@@ -2433,21 +2418,6 @@ EOF
 			$oPage->add("</fieldset></div>");
 			break;		
 	
-		case 'download_inlineimage':
-			$id = utils::ReadParam('id', '');
-			$sSecret = utils::ReadParam('s', '');
-			$iCacheSec = (int) utils::ReadParam('cache', 0);
-			if (!empty($id) && !empty($sSecret))
-			{
-				DownloadDocument($oPage, 'InlineImage', $id, 'contents', 'attachment', 'secret', $sSecret);
-				if ($iCacheSec > 0)
-				{
-					$oPage->add_header("Expires: "); // Reset the value set in ajax_page
-					$oPage->add_header("Cache-Control: no-transform,public,max-age=$iCacheSec,s-maxage=$iCacheSec");
-				}
-			}
-			break;
-
 		case 'custom_fields_update':
 			$oPage->SetContentType('application/json');
 			$sAttCode = utils::ReadParam('attcode', '');
@@ -2489,47 +2459,3 @@ catch (Exception $e)
 	echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8');
 	IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString());
 }
-
-
-
-/**
- * Downloads a document to the browser, either as 'inline' or 'attachment'
- *  
- * @param WebPage $oPage The web page for the output
- * @param string $sClass Class name of the object
- * @param mixed $id Identifier of the object
- * @param string $sAttCode Name of the attribute containing the document to download
- * @param string $sContentDisposition Either 'inline' or 'attachment'
- * @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
- * @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
- * @return none
- */   
-function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
-{
-	try
-	{
-		$oObj = MetaModel::GetObject($sClass, $id, false, false);
-		if (!is_object($oObj))
-		{
-			throw new Exception("Invalid id ($id) for class '$sClass' - the object does not exist or you are not allowed to view it");
-		}
-		if (($sSecretField != null) && ($oObj->Get($sSecretField) != $sSecretValue))
-		{
-			usleep(200);
-			throw new Exception("Invalid secret for class '$sClass' - the object does not exist or you are not allowed to view it");
-		}
-		$oDocument = $oObj->Get($sAttCode);
-		if (is_object($oDocument))
-		{
-			$oPage->TrashUnexpectedOutput();
-			$oPage->SetContentType($oDocument->GetMimeType());
-			$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
-			$oPage->add($oDocument->GetData());
-		}
-	}
-	catch(Exception $e)
-	{
-		$oPage->p($e->getMessage());
-	}
-}
-?>