f78febaae156584e25680d55336a4f9ff8508d48
[tine20] / tine20 / Tinebase / Frontend / Http.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Server
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * HTTP interface to Tine
14  *
15  * @package     Tinebase
16  * @subpackage  Server
17  */
18 class Tinebase_Frontend_Http extends Tinebase_Frontend_Http_Abstract
19 {
20     const REQUEST_TYPE = 'HttpPost';
21     
22     /**
23      * get json-api service map
24      * 
25      * @return string
26      */
27     public static function getServiceMap()
28     {
29         $smd = Tinebase_Server_Json::getServiceMap();
30         
31         $smdArray = $smd->toArray();
32         unset($smdArray['methods']);
33         
34         if (! isset($_REQUEST['method']) || $_REQUEST['method'] != 'Tinebase.getServiceMap') {
35             return $smdArray;
36         }
37         
38         header('Content-type: application/json');
39         echo '_smd = ' . json_encode($smdArray);
40         die();
41     }
42     
43     /**
44      * return xrds file
45      * used to autodiscover openId servers
46      * 
47      * @return void
48      */
49     public function getXRDS()
50     {
51         // selfUrl == http://servername/pathtotine20/users/loginname
52         $url = dirname(dirname(Zend_OpenId::selfUrl())) . '/index.php?method=Tinebase.openId';
53         
54         header('Content-type: application/xrds+xml');
55         
56         echo '<?xml version="1.0"?>
57             <xrds:XRDS xmlns="xri://$xrd*($v*2.0)" xmlns:xrds="xri://$xrds">
58               <XRD>
59                 <Service priority="0">
60                   <Type>http://specs.openid.net/auth/2.0/signon</Type>
61                   <URI>' . $url . '</URI>
62                 </Service>
63                 <Service priority="1">
64                   <Type>http://openid.net/signon/1.1</Type>
65                   <URI>' . $url . '</URI>
66                 </Service>
67                 <Service priority="2">
68                   <Type>http://openid.net/signon/1.0</Type>
69                   <URI>' . $url . '</URI>
70                 </Service>
71               </XRD>
72             </xrds:XRDS>';
73     }
74     
75     /**
76      * display user info page
77      * 
78      * in the future we can display public informations about the user here too
79      * currently it is only used as entry point for openId
80      * 
81      * @param string $username the username
82      * @return void
83      */
84     public function userInfoPage($username)
85     {
86         // selfUrl == http://servername/pathtotine20/users/loginname
87         $openIdUrl = dirname(dirname(Zend_OpenId::selfUrl())) . '/index.php?method=Tinebase.openId';
88         
89         $view = new Zend_View();
90         $view->setScriptPath('Tinebase/views');
91         
92         $view->openIdUrl = $openIdUrl;
93         $view->username = $username;
94
95         header('Content-Type: text/html; charset=utf-8');
96         echo $view->render('userInfoPage.php');
97     }
98     
99     /**
100      * handle all kinds of openId requests
101      * 
102      * @return void
103      */
104     public function openId()
105     {
106         Tinebase_Core::startCoreSession();
107         $server = new Tinebase_OpenId_Provider(
108             null,
109             null,
110             new Tinebase_OpenId_Provider_User_Tine20(),
111             new Tinebase_OpenId_Provider_Storage()
112         );
113         $server->setOpEndpoint(dirname(Zend_OpenId::selfUrl()) . '/index.php?method=Tinebase.openId');
114         
115         // handle openId login form
116         if (isset($_POST['openid_action']) && $_POST['openid_action'] === 'login') {
117             $server->login($_POST['openid_identifier'], $_POST['password'], $_POST['username']);
118             unset($_GET['openid_action']);
119             $this->_setJsonKeyCookie();
120             Zend_OpenId::redirect(dirname(Zend_OpenId::selfUrl()) . '/index.php', $_GET);
121
122         // display openId login form
123         } else if (isset($_GET['openid_action']) && $_GET['openid_action'] === 'login') {
124             $view = new Zend_View();
125             $view->setScriptPath('Tinebase/views');
126             
127             $view->openIdIdentity = $_GET['openid_identity'];
128             $view->loginName = $_GET['openid_identity'];
129     
130             header('Content-Type: text/html; charset=utf-8');
131             echo $view->render('openidLogin.php');
132
133         // handle openId trust form
134         } else if (isset($_POST['openid_action']) && $_POST['openid_action'] === 'trust') {
135             if (isset($_POST['allow'])) {
136                 if (isset($_POST['forever'])) {
137                     $server->allowSite($server->getSiteRoot($_GET));
138                 }
139                 $server->respondToConsumer($_GET);
140             } else if (isset($_POST['deny'])) {
141                 if (isset($_POST['forever'])) {
142                     $server->denySite($server->getSiteRoot($_GET));
143                 }
144                 Zend_OpenId::redirect($_GET['openid_return_to'],
145                                       array('openid.mode'=>'cancel'));
146             }
147
148         // display openId trust form
149         } else if (isset($_GET['openid_action']) && $_GET['openid_action'] === 'trust') {
150             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
151                 . " Display openId trust screen");
152             $view = new Zend_View();
153             $view->setScriptPath('Tinebase/views');
154             
155             $view->openIdConsumer = $server->getSiteRoot($_GET);
156             $view->openIdIdentity = $server->getLoggedInUser();
157             
158             header('Content-Type: text/html; charset=utf-8');
159             echo $view->render('openidTrust.php');
160
161         // handle all other openId requests
162         } else {
163             $result = $server->handle();
164
165             if (is_string($result)) {
166                 echo $result;
167             } elseif ($result !== true) {
168                 header('HTTP/1.0 403 Forbidden');
169                 return;
170             }
171         }
172     }
173     
174     /**
175      * checks if a user is logged in. If not we redirect to login
176      */
177     protected function checkAuth()
178     {
179         try {
180             Tinebase_Core::getUser();
181         } catch (Exception $e) {
182             header('HTTP/1.0 403 Forbidden');
183             exit;
184         }
185     }
186     
187     /**
188      * renders the login dialog
189      *
190      * @todo perhaps we could add a config option to display the update dialog if it is set
191      */
192     public function login()
193     {
194         // redirect to REDIRECTURL if set
195         $redirectUrl = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, '');
196
197         if ($redirectUrl !== '' && Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTALWAYS, FALSE)) {
198             header('Location: ' . $redirectUrl);
199             return;
200         }
201         
202         // check if setup/update required
203         $setupController = Setup_Controller::getInstance();
204         $applications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED);
205         foreach ($applications as $application) {
206             if ($setupController->updateNeeded($application)) {
207                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
208                     . " " . $application->name . ' needs an update');
209                 $this->setupRequired();
210             }
211         }
212         
213         $this->_renderMainScreen();
214         
215         /**
216          * old code used to display user registration
217          * @todo must be reworked
218          * 
219         
220         $view = new Zend_View();
221         $view->setScriptPath('Tinebase/views');
222
223         header('Content-Type: text/html; charset=utf-8');
224         
225         // check if registration is active
226         if(isset(Tinebase_Core::getConfig()->login)) {
227             $registrationConfig = Tinebase_Core::getConfig()->registration;
228             $view->userRegistration = (isset($registrationConfig->active)) ? $registrationConfig->active : '';
229         } else {
230             $view->userRegistration = 0;
231         }        
232         
233         echo $view->render('jsclient.php');
234         */
235     }
236     
237     /**
238      * renders the main screen
239      * 
240      * @return void
241      */
242     protected function _renderMainScreen()
243     {
244         $view = new Zend_View();
245         $baseDir = dirname(dirname(dirname(__FILE__)));
246         $view->setScriptPath("$baseDir/Tinebase/views");
247
248         if (TINE20_BUILDTYPE =='DEVELOPMENT') {
249             $jsFilestoInclude = $this->_getFilesToWatch('js');
250             $view->devIncludes = $jsFilestoInclude;
251         }
252
253         $view->registryData = array();
254         
255         $this->_setMainscreenHeaders();
256         
257         echo $view->render('jsclient.php');
258     }
259     
260     /**
261      * set headers for mainscreen
262      */
263     protected function _setMainscreenHeaders()
264     {
265         if (headers_sent()) {
266             return;
267         }
268         
269         header('Content-Type: text/html; charset=utf-8');
270         
271         // obsoleted by CSP see https://www.w3.org/TR/CSP2/#directive-frame-ancestors
272         //header('X-Frame-Options: SAMEORIGIN');
273
274         $frameAncestors = implode(' ' ,array_merge(
275             (array) Tinebase_Core::getConfig()->get(Tinebase_Config::ALLOWEDJSONORIGINS, array()),
276             array("'self'")
277         ));
278
279         // set Content-Security-Policy header against clickjacking and XSS
280         // @see https://developer.mozilla.org/en/Security/CSP/CSP_policy_directives
281         $scriptSrcs = array("'self'", "'unsafe-eval'", 'https://versioncheck.tine20.net');
282         if (TINE20_BUILDTYPE == 'DEVELOPMENT') {
283             $scriptSrcs[] = Tinebase_Core::getUrl('protocol') . '://' . Tinebase_Core::getUrl('host') . ":10443";
284         }
285         $scriptSrc = implode(' ', $scriptSrcs);
286         header("Content-Security-Policy: default-src 'self'");
287         header("Content-Security-Policy: script-src $scriptSrc");
288         header("Content-Security-Policy: frame-ancestors $frameAncestors");
289
290         // headers for IE 10+11
291         header("X-Content-Security-Policy: default-src 'self'");
292         header("X-Content-Security-Policy: script-src $scriptSrc");
293         header("X-Content-Security-Policy: frame-ancestors $frameAncestors");
294
295         // set Strict-Transport-Security; used only when served over HTTPS
296         header('Strict-Transport-Security: max-age=16070400');
297
298         // cache mainscreen for one day in production
299         $maxAge = ! defined('TINE20_BUILDTYPE') || TINE20_BUILDTYPE != 'DEVELOPMENT' ? 86400 : -10000;
300         header('Cache-Control: private, max-age=' . $maxAge);
301         header("Expires: " . gmdate('D, d M Y H:i:s', Tinebase_DateTime::now()->addSecond($maxAge)->getTimestamp()) . " GMT");
302         header_remove('Pragma');
303     }
304     
305     /**
306      * renders the setup/update required dialog
307      */
308     public function setupRequired()
309     {
310         $view = new Zend_View();
311         $view->setScriptPath('Tinebase/views');
312
313         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->DEBUG(__CLASS__ . '::' . __METHOD__ . ' (' . __LINE__ .') Update/Setup required!');
314
315         header('Content-Type: text/html; charset=utf-8');
316         echo $view->render('update.php');
317         exit();
318     }
319
320     /**
321      * login from HTTP post 
322      * 
323      * redirects the tine main screen if authentication is successful
324      * otherwise redirects back to login url 
325      */
326     public function loginFromPost($username, $password)
327     {
328         Tinebase_Core::startCoreSession();
329         
330         if (!empty($username)) {
331             // try to login user
332             $success = (Tinebase_Controller::getInstance()->login(
333                 $username,
334                 $password,
335                 Tinebase_Core::get(Tinebase_Core::REQUEST),
336                 self::REQUEST_TYPE
337             ) === TRUE);
338         } else {
339             $success = FALSE;
340         }
341         
342         if ($success === TRUE) {
343             $this->_setJsonKeyCookie();
344
345             $ccAdapter = Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter();
346             if (Tinebase_Core::isRegistered(Tinebase_Core::USERCREDENTIALCACHE)) {
347                 $ccAdapter->setCache(Tinebase_Core::getUserCredentialCache());
348             } else {
349                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Something went wrong with the CredentialCache / no CC registered.');
350                 $success = FALSE;
351                 $ccAdapter->resetCache();
352             }
353         
354         }
355
356         $request = new Sabre\HTTP\Request();
357         $redirectUrl = str_replace('index.php', '', $request->getAbsoluteUri());
358
359         // authentication failed
360         if ($success !== TRUE) {
361             $_SESSION = array();
362             Tinebase_Session::destroyAndRemoveCookie();
363             
364             // redirect back to loginurl if needed
365             $redirectUrl = Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL, $redirectUrl);
366         }
367
368         // load the client with GET
369         header('Location: ' . $redirectUrl);
370     }
371
372     /**
373      * put jsonKey into separate cookie
374      *
375      * this is needed if login is not done by the client itself
376      */
377     protected function _setJsonKeyCookie()
378     {
379         // SSO Login
380         $cookie_params = session_get_cookie_params();
381
382         // don't issue errors in unit tests
383         @setcookie(
384             'TINE20JSONKEY',
385             Tinebase_Core::get('jsonKey'),
386             0,
387             $cookie_params['path'],
388             $cookie_params['domain'],
389             $cookie_params['secure']
390         );
391     }
392
393     /**
394      * checks authentication and display Tine 2.0 main screen 
395      */
396     public function mainScreen()
397     {
398         $this->checkAuth();
399         $this->_renderMainScreen();
400     }
401     
402     /**
403      * handle session exception for http requests
404      * 
405      * we force the client to delete session cookie, but we don't destroy
406      * the session on server side. This way we prevent session DOS from thrid party
407      */
408     public function sessionException()
409     {
410         Tinebase_Session::expireSessionCookie();
411         echo "
412             <script type='text/javascript'>
413                 window.location.href = window.location.href;
414             </script>
415         ";
416         /*
417         ob_start();
418         $html = $this->login();
419         $html = ob_get_clean();
420         
421         $script = "
422             <script type='text/javascript'>
423                 exception = {code: 401};
424                 Ext.onReady(function() {
425                     Ext.MessageBox.show({
426                         title: _('Authorisation Required'), 
427                         msg: _('Your session is not valid. You need to login again.'),
428                         buttons: Ext.Msg.OK,
429                         icon: Ext.MessageBox.WARNING
430                     });
431                 });
432             </script>";
433         
434         echo preg_replace('/<\/head.*>/', $script . '</head>', $html);
435         */
436     }
437     
438     /**
439      * generic http exception occurred
440      */
441     public function exception()
442     {
443         ob_start();
444         $this->_renderMainScreen();
445         $html = ob_get_clean();
446         
447         $script = "
448         <script type='text/javascript'>
449             exception = {code: 400};
450             Ext.onReady(function() {
451                 Ext.MessageBox.show({
452                     title: _('Abnormal End'), 
453                     msg: _('An error occurred, the program ended abnormal.'),
454                     buttons: Ext.Msg.OK,
455                     icon: Ext.MessageBox.WARNING
456                 });
457             });
458         </script>";
459         
460         echo preg_replace('/<\/head.*>/', $script . '</head>', $html);
461     }
462     
463     /**
464      * returns javascript of translations for the currently configured locale
465      * 
466      * @return string (javascript)
467      */
468     public function getJsTranslations()
469     {
470         if (! in_array(TINE20_BUILDTYPE, array('DEBUG', 'RELEASE'))) {
471             $locale = Tinebase_Core::get('locale');
472             $translations = Tinebase_Translation::getJsTranslations($locale);
473             header('Content-Type: application/javascript');
474             die($translations);
475         }
476
477         $this->_deliverChangedFiles('lang');
478     }
479     
480     /**
481      * return javascript files if changed
482      * 
483      */
484     public function getJsFiles()
485     {
486         $this->_deliverChangedFiles('js');
487     }
488
489     /**
490      * check if js files have changed and return all js as one big file or return "HTTP/1.0 304 Not Modified" if files don't have changed
491      * 
492      * @param string $_fileType
493      * @param array $filesToWatch
494      */
495     protected function _deliverChangedFiles($_fileType, $filesToWatch=null)
496     {
497         // close session to allow other requests
498         Tinebase_Session::writeClose(true);
499
500         $filesToWatch = $filesToWatch ? $filesToWatch : $this->_getFilesToWatch($_fileType);
501         if ($_fileType == 'js' && TINE20_BUILDTYPE != 'DEVELOPMENT') {
502             $customJSFiles = Tinebase_Config::getInstance()->get(Tinebase_Config::FAT_CLIENT_CUSTOM_JS);
503             if (! empty($customJSFiles)) {
504                 $filesToWatch = array_merge($filesToWatch, (array)$customJSFiles);
505             }
506         }
507
508         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__CLASS__ . '::' . __METHOD__
509             . ' (' . __LINE__ .') Got files to watch: ' . print_r($filesToWatch, true));
510
511         // cache for one day
512         $maxAge = 86400;
513         header('Cache-Control: private, max-age=' . $maxAge);
514         header("Expires: " . gmdate('D, d M Y H:i:s', Tinebase_DateTime::now()->addSecond($maxAge)->getTimestamp()) . " GMT");
515         
516         // remove Pragma header from session
517         header_remove('Pragma');
518
519         $clientETag = isset($_SERVER['If_None_Match'])
520             ? $_SERVER['If_None_Match']
521             : (isset($_SERVER['HTTP_IF_NONE_MATCH']) ? $_SERVER['HTTP_IF_NONE_MATCH'] : '');
522
523         if (preg_match('/[a-f0-9]{40}/', $clientETag, $matches)) {
524             $clientETag = $matches[0];
525         }
526
527         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__CLASS__ . '::' . __METHOD__
528             . ' (' . __LINE__ .') $clientETag: ' . $clientETag);
529
530         $serverETag = $this->getAssetHash();
531
532         if ($clientETag == $serverETag) {
533             header("HTTP/1.0 304 Not Modified");
534         } else {
535             header('Content-Type: application/javascript');
536             header('Etag: "' . $serverETag . '"');
537
538             // send files to client
539             foreach ($filesToWatch as $file) {
540                 readfile($file);
541             }
542             if ($_fileType != 'lang') {
543                 // adds new server version etag for client version check
544                 echo "Tine.clientVersion.filesHash = '$serverETag';";
545             }
546         }
547     }
548
549     /**
550      * get map of asset files
551      *
552      * @return array
553      */
554     public static function getAssetsMap($asJson=false)
555     {
556         $jsonFile = 'Tinebase/js/webpack-assets-FAT.json';
557
558         if (TINE20_BUILDTYPE =='DEVELOPMENT') {
559             $devServerURL = Tinebase_Config::getInstance()->get('webpackDevServerURL', 'http://localhost:10443');
560             $jsonFileUri = $devServerURL . '/' . $jsonFile;
561             $json = Tinebase_Helper::getFileOrUriContents($jsonFileUri);
562             if (! $json) {
563                 Tinebase_Core::getLogger()->ERR(__CLASS__ . '::' . __METHOD__ . ' (' . __LINE__ .') Could not get json file: ' . $jsonFile);
564                 throw new Exception('You need to run webpack-dev-server in dev mode! See https://wiki.tine20.org/Developers/Getting_Started/Working_with_GIT#Install_webpack');
565             }
566         } else {
567             $json = file_get_contents(dirname(dirname(__DIR__)) . '/' . $jsonFile);
568         }
569
570         return $asJson ? $json : json_decode($json, true);
571     }
572
573     public static function getAssetHash()
574     {
575         return sha1(self::getAssetsMap(true) . TINE20_BUILDTYPE);
576     }
577
578     /**
579      * @param string $_fileType
580      * @return array
581      * @throws Exception
582      * @throws Tinebase_Exception_InvalidArgument
583      */
584     protected function _getFilesToWatch($_fileType)
585     {
586         $requiredApplications = array('Tinebase', 'Admin', 'Addressbook');
587         $enabledApplications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED)->name;
588         $orderedApplications = array_merge($requiredApplications, array_diff($enabledApplications, $requiredApplications));
589         $filesToWatch = array();
590         $fileMap = $this->getAssetsMap();
591
592         foreach ($orderedApplications as $application) {
593             switch($_fileType) {
594                 case 'js':
595                     if (isset($fileMap["{$application}/js/{$application}"])) {
596                         $jsFile = $fileMap["{$application}/js/{$application}"]['js'];
597                     }
598
599                     if (TINE20_BUILDTYPE =='DEBUG') {
600                         $jsFile = preg_replace('/\.js$/', '.debug.js', $jsFile);
601                     }
602
603                     $filesToWatch[] = $jsFile;
604                     break;
605                 case 'lang':
606                     $fileName = "{$application}/js/{$application}-lang-" . Tinebase_Core::getLocale() . (TINE20_BUILDTYPE == 'DEBUG' ? '-debug' : null) . '.js';
607                     $lang = Tinebase_Core::getLocale();
608                     $customPath = Tinebase_Config::getInstance()->translations;
609                     $basePath = is_readable("$customPath/$lang/$fileName") ?  "$customPath/$lang" : '.';
610
611                     $langFile = "{$basePath}/{$application}/js/{$application}-lang-" . Tinebase_Core::getLocale() . (TINE20_BUILDTYPE == 'DEBUG' ? '-debug' : null) . '.js';
612                     $filesToWatch[] = $langFile;
613                     break;
614                 default:
615                     throw new Exception('no such fileType');
616                     break;
617             }
618         }
619
620         return $filesToWatch;
621     }
622
623     /**
624      * dev mode custom js delivery
625      */
626     public function getCustomJsFiles()
627     {
628         try {
629             $customJSFiles = Tinebase_Config::getInstance()->get(Tinebase_Config::FAT_CLIENT_CUSTOM_JS);
630             if (! empty($customJSFiles)) {
631                 $this->_deliverChangedFiles('js', $customJSFiles);
632             }
633         } catch (Exception $exception) {
634             Tinebase_Core::getLogger()->WARN(__METHOD__ . '::' . __LINE__ . " can't deliver custom js: \n" . $exception);
635
636         }
637     }
638
639     /**
640      * return last modified timestamp formated in gmt
641      * 
642      * @param  array  $_files
643      * @return array
644      */
645     protected function _getLastModified(array $_files)
646     {
647         $timeStamp = null;
648         
649         foreach ($_files as $file) {
650             $mtime = filemtime($file);
651             if ($mtime > $timeStamp) {
652                 $timeStamp = $mtime;
653             }
654         }
655
656         return gmdate("D, d M Y H:i:s", $timeStamp) . " GMT";
657     }
658
659     /**
660      * receives file uploads and stores it in the file_uploads db
661      * 
662      * @throws Tinebase_Exception_UnexpectedValue
663      * @throws Tinebase_Exception_NotFound
664      */
665     public function uploadTempFile()
666     {
667         try {
668             $this->checkAuth();
669             
670             // close session to allow other requests
671             Tinebase_Session::writeClose(true);
672         
673             $tempFile = Tinebase_TempFile::getInstance()->uploadTempFile();
674             
675             die(Zend_Json::encode(array(
676                'status'   => 'success',
677                'tempFile' => $tempFile->toArray(),
678             )));
679         } catch (Tinebase_Exception $exception) {
680             Tinebase_Core::getLogger()->WARN(__METHOD__ . '::' . __LINE__ . " File upload could not be done, due to the following exception: \n" . $exception);
681             
682             if (! headers_sent()) {
683                header("HTTP/1.0 500 Internal Server Error");
684             }
685             die(Zend_Json::encode(array(
686                'status'   => 'failed',
687             )));
688         }
689     }
690     
691     /**
692      * downloads an image/thumbnail at a given size
693      *
694      * @param unknown_type $application
695      * @param string $id
696      * @param string $location
697      * @param int $width
698      * @param int $height
699      * @param int $ratiomode
700      */
701     public function getImage($application, $id, $location, $width, $height, $ratiomode)
702     {
703         $this->checkAuth();
704
705         // close session to allow other requests
706         Tinebase_Session::writeClose(true);
707         
708         $clientETag      = null;
709         $ifModifiedSince = null;
710         
711         if (isset($_SERVER['If_None_Match'])) {
712             $clientETag     = trim($_SERVER['If_None_Match'], '"');
713             $ifModifiedSince = trim($_SERVER['If_Modified_Since'], '"');
714         } elseif (isset($_SERVER['HTTP_IF_NONE_MATCH']) && isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
715             $clientETag     = trim($_SERVER['HTTP_IF_NONE_MATCH'], '"');
716             $ifModifiedSince = trim($_SERVER['HTTP_IF_MODIFIED_SINCE'], '"');
717         }
718         
719         $image = Tinebase_Controller::getInstance()->getImage($application, $id, $location);
720
721         if (is_array($image)) {
722         }
723         
724         $serverETag = sha1($image->blob . $width . $height . $ratiomode);
725         
726         // cache for 3600 seconds
727         $maxAge = 3600;
728         header('Cache-Control: private, max-age=' . $maxAge);
729         header("Expires: " . gmdate('D, d M Y H:i:s', Tinebase_DateTime::now()->addSecond($maxAge)->getTimestamp()) . " GMT");
730         
731         // overwrite Pragma header from session
732         header("Pragma: cache");
733         
734         // if the cache id is still valid
735         if ($clientETag == $serverETag) {
736             header("Last-Modified: " . $ifModifiedSince);
737             header("HTTP/1.0 304 Not Modified");
738             header('Content-Length: 0');
739         } else {
740             #$cache = Tinebase_Core::getCache();
741             
742             #if ($cache->test($serverETag) === true) {
743             #    $image = $cache->load($serverETag);
744             #} else {
745                 if ($width != -1 && $height != -1) {
746                     Tinebase_ImageHelper::resize($image, $width, $height, $ratiomode);
747                 }
748             #    $cache->save($image, $serverETag);
749             #}
750         
751             header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
752             header('Content-Type: '. $image->mime);
753             header('Etag: "' . $serverETag . '"');
754             
755             flush();
756             
757             die($image->blob);
758         }
759     }
760     
761     /**
762      * crops a image identified by an imgageURL and returns a new tempFileImage
763      * 
764      * @param  string $imageurl imageURL of the image to be croped
765      * @param  int    $left     left position of crop window
766      * @param  int    $top      top  position of crop window
767      * @param  int    $widht    widht  of crop window
768      * @param  int    $height   heidht of crop window
769      * @return string imageURL of new temp image
770      * 
771      */
772     public function cropImage($imageurl, $left, $top, $widht, $height)
773     {
774         $this->checkAuth();
775         
776         $image = Tinebase_Model_Image::getImageFromImageURL($imageurl);
777         Tinebase_ImageHelper::crop($image, $left, $top, $widht, $height);
778         
779     }
780     
781     /**
782      * download file attachment
783      * 
784      * @param string $nodeId
785      * @param string $recordId
786      * @param string $modelName
787      */
788     public function downloadRecordAttachment($nodeId, $recordId, $modelName)
789     {
790         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
791             . ' Downloading attachment of ' . $modelName . ' record with id ' . $recordId);
792         
793         $recordController = Tinebase_Core::getApplicationInstance($modelName);
794         $record = $recordController->get($recordId);
795         
796         $node = Tinebase_FileSystem::getInstance()->get($nodeId);
797         $node->grants =
798         $path = Tinebase_Model_Tree_Node_Path::STREAMWRAPPERPREFIX
799             . Tinebase_FileSystem_RecordAttachments::getInstance()->getRecordAttachmentPath($record)
800             . '/' . $node->name;
801         
802         $this->_downloadFileNode($node, $path, /* revision */ null, /* $ignoreAcl */ true);
803         exit;
804     }
805
806     /**
807      * Download temp file to review
808      *
809      * @param $tmpfileId
810      */
811     public function downloadTempfile($tmpfileId)
812     {
813         $tmpFile = Tinebase_TempFile::getInstance()->getTempFile($tmpfileId);
814         $this->_downloadFileNode($tmpFile, $tmpFile->path);
815         exit;
816     }
817
818     public function getPostalXWindow()
819     {
820         $view = new Zend_View();
821         $view->setScriptPath('Tinebase/views');
822
823         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
824             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' getPostalXWindow');
825
826         $this->_setMainscreenHeaders();
827
828         echo $view->render('postal.xwindow.php');
829         exit();
830     }
831 }