1<?php 2 3namespace RainLoop; 4 5class Service 6{ 7 /** 8 * @var \MailSo\Base\Http 9 */ 10 private $oHttp; 11 12 /** 13 * @var \RainLoop\Actions 14 */ 15 private $oActions; 16 17 /** 18 * @var \RainLoop\ServiceActions 19 */ 20 private $oServiceActions; 21 22 /** 23 * @return void 24 */ 25 private function __construct() 26 { 27 $this->oHttp = \MailSo\Base\Http::SingletonInstance(); 28 $this->oActions = \RainLoop\Api::Actions(); 29 30 $this->oServiceActions = new \RainLoop\ServiceActions($this->oHttp, $this->oActions); 31 32 if ($this->oActions->Config()->Get('debug', 'enable', false)) 33 { 34 \error_reporting(E_ALL); 35 \ini_set('display_errors', 1); 36 } 37 38 $sServer = \trim($this->oActions->Config()->Get('security', 'custom_server_signature', '')); 39 if (0 < \strlen($sServer)) 40 { 41 @\header('Server: '.$sServer, true); 42 } 43 44 $sXFrameOptionsHeader = \trim($this->oActions->Config()->Get('security', 'x_frame_options_header', '')); 45 if (0 < \strlen($sXFrameOptionsHeader)) 46 { 47 @\header('X-Frame-Options: '.$sXFrameOptionsHeader, true); 48 } 49 50 $sXssProtectionOptionsHeader = \trim($this->oActions->Config()->Get('security', 'x_xss_protection_header', '')); 51 if (0 < \strlen($sXssProtectionOptionsHeader)) 52 { 53 @\header('X-XSS-Protection: '.$sXssProtectionOptionsHeader, true); 54 } 55 56 if ($this->oActions->Config()->Get('labs', 'force_https', false) && !$this->oHttp->IsSecure()) 57 { 58 @\header('Location: https://'.$this->oHttp->GetHost(false, false).$this->oHttp->GetUrl(), true); 59 exit(0); 60 } 61 62 $this->localHandle(); 63 } 64 65 /** 66 * @return bool 67 */ 68 public function RunResult() 69 { 70 return true; 71 } 72 73 /** 74 * @staticvar bool $bOne 75 * @return bool 76 */ 77 public static function Handle() 78 { 79 static $bOne = null; 80 if (null === $bOne) 81 { 82 $oService = null; 83 if (\class_exists('MailSo\Version')) 84 { 85 $oService = new self(); 86 } 87 88 $bOne = $oService->RunResult(); 89 } 90 91 return $bOne; 92 } 93 94 /** 95 * @return \RainLoop\Service 96 */ 97 private function localHandle() 98 { 99 if (!\class_exists('MailSo\Version')) 100 { 101 return $this; 102 } 103 104 $sResult = ''; 105 $bCached = false; 106 107 $sQuery = $this->oActions->ParseQueryAuthString(); 108 109 $this->oActions->Plugins()->RunHook('filter.http-query', array(&$sQuery)); 110 $aPaths = \explode('/', $sQuery); 111 $this->oActions->Plugins()->RunHook('filter.http-paths', array(&$aPaths)); 112 113 $bAdmin = false; 114 $sAdminPanelHost = $this->oActions->Config()->Get('security', 'admin_panel_host', ''); 115 if (empty($sAdminPanelHost)) 116 { 117 $sAdminPanelKey = \strtolower($this->oActions->Config()->Get('security', 'admin_panel_key', 'admin')); 118 $bAdmin = !empty($aPaths[0]) && \strtolower($aPaths[0]) === $sAdminPanelKey; 119 } 120 else if (empty($aPaths[0]) && 121 \MailSo\Base\Utils::StrToLowerIfAscii($sAdminPanelHost) === \MailSo\Base\Utils::StrToLowerIfAscii($this->oHttp->GetHost())) 122 { 123 $bAdmin = true; 124 } 125 126 if ($this->oHttp->IsPost()) 127 { 128 $this->oHttp->ServerNoCache(); 129 } 130 131 if ($bAdmin && !$this->oActions->Config()->Get('security', 'allow_admin_panel', true)) 132 { 133 $this->oHttp->StatusHeader(403); 134 echo $this->oServiceActions->ErrorTemplates('Access Denied.', 135 'Access to the RainLoop Webmail Admin Panel is not allowed!', true); 136 137 return $this; 138 } 139 140 $bIndex = true; 141 if (0 < \count($aPaths) && !empty($aPaths[0]) && !$bAdmin && 'index' !== \strtolower($aPaths[0])) 142 { 143 $bIndex = false; 144 $sMethodName = 'Service'.\preg_replace('/@.+$/', '', $aPaths[0]); 145 $sMethodExtra = 0 < \strpos($aPaths[0], '@') ? \preg_replace('/^[^@]+@/', '', $aPaths[0]) : ''; 146 147 if (\method_exists($this->oServiceActions, $sMethodName) && 148 \is_callable(array($this->oServiceActions, $sMethodName))) 149 { 150 $this->oServiceActions->SetQuery($sQuery)->SetPaths($aPaths); 151 $sResult = \call_user_func(array($this->oServiceActions, $sMethodName), $sMethodExtra); 152 } 153 else if (!$this->oActions->Plugins()->RunAdditionalPart($aPaths[0], $aPaths)) 154 { 155 $bIndex = true; 156 } 157 } 158 159 $bMobile = false; 160 $bMobileDevice = false; 161 162 if ($this->oActions->Config()->Get('labs', 'allow_mobile_version', false)) 163 { 164 $bUseMobileVersionForTablets = $this->oActions->Config()->Get('labs', 'use_mobile_version_for_tablets', false); 165 166 $oMobileDetect = new \Detection\MobileDetect(); 167 $bMobileDevice = $oMobileDetect->isMobile() && 168 ($bUseMobileVersionForTablets ? true : !$oMobileDetect->isTablet()); 169 170 if ($bIndex) 171 { 172 $sMobileType = (string) \RainLoop\Utils::GetCookie(\RainLoop\Actions::RL_MOBILE_TYPE, ''); 173 switch ($sMobileType) { 174 default: 175 $sMobileType = ''; 176 $bMobile = $bMobileDevice; 177 break; 178 case 'mobile': 179 $bMobile = true; 180 break; 181 case 'desktop': 182 $bMobile = false; 183 break; 184 } 185 } 186 } 187 188 if ($bIndex) 189 { 190 @\header('Content-Security-Policy:'); 191 @\header_remove('Content-Security-Policy'); 192 193 @header('Content-Type: text/html; charset=utf-8'); 194 $this->oHttp->ServerNoCache(); 195 196 if (!@\is_dir(APP_DATA_FOLDER_PATH) || !@\is_writable(APP_DATA_FOLDER_PATH)) 197 { 198 echo $this->oServiceActions->ErrorTemplates( 199 'Permission denied!', 200 'RainLoop Webmail cannot access to the data folder "'.APP_DATA_FOLDER_PATH.'"' 201 ); 202 203 return $this; 204 } 205 206 $aTemplateParameters = $this->indexTemplateParameters($bAdmin, $bMobile, $bMobileDevice); 207 208 $sCacheFileName = ''; 209 if ($this->oActions->Config()->Get('labs', 'cache_system_data', true) && !empty($aTemplateParameters['{{BaseHash}}'])) 210 { 211 $sCacheFileName = 'TMPL:'.$aTemplateParameters['{{BaseHash}}']; 212 $sResult = $this->oActions->Cacher()->Get($sCacheFileName); 213 } 214 215 if (0 === \strlen($sResult)) 216 { 217 $aTemplateParameters['{{BaseTemplates}}'] = $this->oServiceActions->compileTemplates($bAdmin, false); 218 $sResult = \strtr(\file_get_contents(APP_VERSION_ROOT_PATH.'app/templates/Index.html'), $aTemplateParameters); 219 220 $sResult = \RainLoop\Utils::ClearHtmlOutput($sResult); 221 if (0 < \strlen($sCacheFileName)) 222 { 223 $this->oActions->Cacher()->Set($sCacheFileName, $sResult); 224 } 225 } 226 else 227 { 228 $bCached = true; 229 } 230 231 $sResult .= '<!--'; 232 $sResult .= '[time:'.\substr(\microtime(true) - APP_START, 0, 6); 233 234// $sResult .= '][version:'.APP_VERSION; 235 if ($this->oActions->IsOpen()) 236 { 237 $sResult .= '][AGPLv3'; 238 } 239 240 $sResult .= '][cached:'.($bCached ? 'true' : 'false'); 241// $sResult .= '][hash:'.$aTemplateParameters['{{BaseHash}}']; 242// $sResult .= '][session:'.\md5(\RainLoop\Utils::GetShortToken()); 243 244 if ($bMobile) 245 { 246 $sResult .= '][mobile:true'; 247 } 248 249 if (\RainLoop\Utils::IsOwnCloud()) 250 { 251 $sResult .= '][cloud:true'; 252 } 253 254 $sResult .= ']-->'; 255 } 256 else 257 { 258 @\header('X-XSS-Protection: 1; mode=block'); 259 } 260 261 // Output result 262 echo $sResult; 263 unset($sResult); 264 265 $this->oActions->BootEnd(); 266 return $this; 267 } 268 269 /** 270 * @param string $sPath 271 * 272 * @return string 273 */ 274 private function staticPath($sPath) 275 { 276 return $this->oActions->StaticPath($sPath); 277 } 278 279 /** 280 * @param bool $bAdmin = false 281 * @param bool $bMobile = false 282 * @param bool $bMobileDevice = false 283 * 284 * @return array 285 */ 286 private function indexTemplateParameters($bAdmin = false, $bMobile = false, $bMobileDevice = false) 287 { 288 $sLanguage = 'en'; 289 $sTheme = 'Default'; 290 291 list($sLanguage, $sTheme) = $this->oActions->GetLanguageAndTheme($bAdmin, $bMobile); 292 293 $bAppJsDebug = !!$this->oActions->Config()->Get('labs', 'use_app_debug_js', false); 294 $bAppCssDebug = !!$this->oActions->Config()->Get('labs', 'use_app_debug_css', false); 295 296 $sFaviconUrl = (string) $this->oActions->Config()->Get('webmail', 'favicon_url', ''); 297 298 $sFaviconPngLink = $sFaviconUrl ? $sFaviconUrl : $this->staticPath('apple-touch-icon.png'); 299 $sAppleTouchLink = $sFaviconUrl ? '' : $this->staticPath('apple-touch-icon.png'); 300 301 $sContentSecurityPolicy = $this->oActions->Config()->Get('security', 'content_security_policy', ''); 302 $sSentryDsn = $this->oActions->Config()->Get('logs', 'sentry_dsn', ''); 303 304 $aTemplateParameters = array( 305 '{{BaseAppHeadScriptLink}}' => $sSentryDsn ? 306 '<script type="text/javascript" data-cfasync="false" src="https://browser.sentry-cdn.com/5.4.3/bundle.min.js" crossorigin="anonymous"></script>' : '', 307 '{{BaseAppBodyScript}}' => $sSentryDsn ? 308 '<script type="text/javascript" data-cfasync="false">window && window.Sentry && window.Sentry.init({dsn:\''.$sSentryDsn.'\',ignoreErrors:[\'Document not active\']});</script>' : '', 309 '{{BaseAppFaviconPngLinkTag}}' => $sFaviconPngLink ? '<link type="image/png" rel="shortcut icon" href="'.$sFaviconPngLink.'" />' : '', 310 '{{BaseAppFaviconTouchLinkTag}}' => $sAppleTouchLink ? '<link type="image/png" rel="apple-touch-icon" href="'.$sAppleTouchLink.'" />' : '', 311 '{{BaseAppMainCssLink}}' => $this->staticPath('css/app'.($bAppCssDebug ? '' : '.min').'.css'), 312 '{{BaseAppThemeCssLink}}' => $this->oActions->ThemeLink($sTheme, $bAdmin), 313 '{{BaseAppPolyfillsScriptLink}}' => $this->staticPath('js/'.($bAppJsDebug ? '' : 'min/').'polyfills'.($bAppJsDebug ? '' : '.min').'.js'), 314 '{{BaseAppBootScriptLink}}' => $this->staticPath('js/'.($bAppJsDebug ? '' : 'min/').'boot'.($bAppJsDebug ? '' : '.min').'.js'), 315 '{{BaseViewport}}' => $bMobile ? 'width=device-width,initial-scale=1,user-scalable=no' : 'width=950,maximum-scale=2', 316 '{{BaseContentSecurityPolicy}}' => $sContentSecurityPolicy ? 317 '<meta http-equiv="Content-Security-Policy" content="'.$sContentSecurityPolicy.'" />' : '', 318 '{{BaseDir}}' => false && \in_array($sLanguage, array('ar', 'he', 'ur')) ? 'rtl' : 'ltr', 319 '{{BaseAppManifestLink}}' => $this->staticPath('manifest.json') 320 ); 321 322 $aTemplateParameters['{{RainloopBootData}}'] = \json_encode(array( 323 'admin' => $bAdmin, 324 'language' => $sLanguage, 325 'theme' => $sTheme, 326 'mobile' => $bMobile, 327 'mobileDevice' => $bMobileDevice 328 )); 329 330 $aTemplateParameters['{{BaseHash}}'] = \md5( 331 \implode('~', array( 332 $bAdmin ? '1' : '0', 333 \md5($this->oActions->Config()->Get('cache', 'index', '')), 334 $this->oActions->Plugins()->Hash(), 335 \RainLoop\Utils::WebVersionPath(), 336 APP_VERSION, 337 )). 338 \implode('~', $aTemplateParameters) 339 ); 340 341 return $aTemplateParameters; 342 } 343} 344