. */ use Monolog\Logger; use Nyholm\Psr7\ServerRequest; use Slim\Http\ServerRequest as Request; use Xibo\Factory\ContainerFactory; use Xibo\Helper\LinkSigner; use Xibo\Support\Exception\NotFoundException; define('XIBO', true); define('PROJECT_ROOT', realpath(__DIR__ . '/..')); error_reporting(0); ini_set('display_errors', 0); require PROJECT_ROOT . '/vendor/autoload.php'; if (!file_exists(PROJECT_ROOT . '/web/settings.php')) { die('Not configured'); } // Create the container for dependency injection. try { $container = ContainerFactory::create(); } catch (Exception $e) { die($e->getMessage()); } // Logger $uidProcessor = new \Monolog\Processor\UidProcessor(7); $container->set('logger', function () use ($uidProcessor) { return (new Logger('XMDS')) ->pushProcessor($uidProcessor) ->pushHandler(new \Xibo\Helper\DatabaseLogHandler()); }); // Create a Slim application $app = \DI\Bridge\Slim\Bridge::create($container); $app->setBasePath($container->get('basePath')); // Mock a request $request = new Request(new ServerRequest('GET', $app->getBasePath())); $request = $request->withAttribute('_entryPoint', 'xmds'); $container->set('name', 'xmds'); // Start time of transaction (for logs) $startTime = microtime(true); // Set state \Xibo\Middleware\State::setState($app, $request); // Set XMR \Xibo\Middleware\Xmr::setXmr($app, false); // Set listeners \Xibo\Middleware\ListenersMiddleware::setListeners($app); // Set connectors \Xibo\Middleware\ConnectorMiddleware::setConnectors($app); // XMDS specific listeners \Xibo\Middleware\ListenersMiddleware::setXmdsListeners($app); // Configure $container->get('configService')->setDependencies($container->get('store'), '/'); // Check to see if the instance has been suspended $instanceSuspended = $container->get('configService')->getSetting('INSTANCE_SUSPENDED'); if ($instanceSuspended == 'yes' || $instanceSuspended == 'partial') { header('HTTP/1.0 503 Service Unavailable'); die('CMS suspended'); } // Load the theme $container->get('configService')->loadTheme(); // Register Middleware Dispatchers // Handle additional Middleware if (isset($container->get('configService')->middleware) && is_array($container->get('configService')->middleware)) { foreach ($container->get('configService')->middleware as $object) { if (method_exists($object, 'registerDispatcher')) { $object::registerDispatcher($app); } } } // Always have a version defined $sanitizer = $container->get('sanitizerService')->getSanitizer($_REQUEST); $version = $sanitizer->getInt('v', ['default' => 3]); // Version Request? if (isset($_GET['what'])) { die(\Xibo\Helper\Environment::$XMDS_VERSION); } // Is the WSDL being requested. if (isset($_GET['wsdl']) || isset($_GET['WSDL'])) { $wsdl = new \Xibo\Xmds\Wsdl(PROJECT_ROOT . '/lib/Xmds/service_v' . $version . '.wsdl', $version); $wsdl->output(); exit; } // Check to see if we have a file attribute set (for HTTP file downloads) if (isset($_GET['file'])) { $logger = $container->get('logService'); // Check send file mode is enabled $sendFileMode = $container->get('configService')->getSetting('SENDFILE_MODE'); if ($sendFileMode == 'Off') { $logger->notice('HTTP GetFile: request received but SendFile Mode is Off. Issuing 404'); header('HTTP/1.0 404 Not Found'); exit; } // Check nonce, output appropriate headers, log bandwidth and stop. try { if (!isset($_REQUEST['displayId']) || !isset($_REQUEST['type']) || !isset($_REQUEST['itemId'])) { $logger->error('HTTP GetFile: Missing params'); throw new NotFoundException(__('Missing params')); } $displayId = intval($_REQUEST['displayId']); // Has the URL expired if (time() > $_REQUEST['X-Amz-Expires']) { $logger->error('HTTP GetFile: Access denied: Pre-signed S3 URL expired'); throw new NotFoundException(__('Expired')); } // Validate the URL. $encryptionKey = $container->get('configService')->getApiKeyDetails()['encryptionKey']; $signature = $_REQUEST['X-Amz-Signature']; $calculatedSignature = \Xibo\Helper\LinkSigner::getSignature( parse_url(\Xibo\Xmds\Wsdl::getRoot(), PHP_URL_HOST), $_GET['file'], $_REQUEST['X-Amz-Expires'], $encryptionKey, $_REQUEST['X-Amz-Date'], true, ); if ($signature !== $calculatedSignature) { $logger->error('HTTP GetFile: Invalid URL'); throw new NotFoundException(__('Invalid URL')); } /** @var \Xibo\Factory\RequiredFileFactory $requiredFileFactory */ $requiredFileFactory = $container->get('requiredFileFactory'); $file = $requiredFileFactory->resolveRequiredFileFromRequest($_REQUEST); // Check that we've not used all of our bandwidth already (if we have an allowance) if ($container->get('bandwidthFactory')->isBandwidthExceeded( $container->get('configService')->getSetting('MONTHLY_XMDS_TRANSFER_LIMIT_KB') )) { $logger->error('HTTP GetFile: Bandwidth Exceeded'); throw new \Xibo\Support\Exception\InstanceSuspendedException('Bandwidth Exceeded'); } // Get the display /** @var \Xibo\Entity\Display $display */ $display = $container->get('displayFactory')->getById($displayId); // Check it is still authorised. if ($display->licensed == 0) { $logger->error('HTTP GetFile: Display unauthorised'); throw new NotFoundException(__('Display unauthorised')); } // Check the display specific limit next. $usage = 0; if ($container->get('bandwidthFactory')->isBandwidthExceeded( $display->bandwidthLimit, $usage, $displayId )) { $logger->error('HTTP GetFile: Bandwidth Exceeded for Display ID: ' . $displayId); throw new \Xibo\Support\Exception\InstanceSuspendedException('Bandwidth Exceeded'); } // Bandwidth // Add the size to the bytes we have already requested. $file->bytesRequested = $file->bytesRequested + $file->size; $file->save(['useTransaction' => false]); // Issue magic packet $libraryLocation = $container->get('configService')->getSetting('LIBRARY_LOCATION'); $cdnUrl = $container->get('configService')->getSetting('CDN_URL'); // Issue content type header $isCss = false; if ($file->type === 'L') { // Layouts are always XML header('Content-Type: text/xml'); } else if ($file->fileType === 'bundle' || \Illuminate\Support\Str::endsWith($file->path, '.js')) { header('Content-Type: application/javascript'); } else if ($file->fileType === 'fontCss' || \Illuminate\Support\Str::endsWith($file->path, '.css')) { $isCss = true; header('Content-Type: text/css'); } else { $contentType = mime_content_type($libraryLocation . $file->path); if ($contentType !== false) { header('Content-Type: ' . $contentType); } } // Are we a special request that needs modification before sending? // For CSS, we look up the files to replace in required files using their stored path if ($display->isPwa() && $isCss) { $logger->debug('Rewriting CSS for PWA: ' . $file->path); // Rewrite CSS for PWAs $cssFile = file_get_contents($libraryLocation . $file->path); $matches = []; preg_match_all('/url\(\'?(.*?)\'?\)/', $cssFile, $matches); foreach ($matches[1] as $match) { // Look up the file to get the right ID/path. try { $replacementFile = $requiredFileFactory->getByDisplayAndDependencyPath($displayId, $match); $url = LinkSigner::generateSignedLink( $display, $encryptionKey, $cdnUrl, 'P', $replacementFile->realId, $replacementFile->path, $file->fileType === 'fontCss' ? 'font' : 'asset', ); $cssFile = str_replace( $match, $url, $cssFile, ); } catch (Exception $exception) { $logger->error('CSS has dependency which does not exist in Required Files: ' . $match); } } $file->size = strlen($cssFile); echo $cssFile; } else { $logger->info('HTTP GetFile request redirecting to ' . $libraryLocation . $file->path); // Normal send if ($sendFileMode == 'Apache') { // Send via Apache X-Sendfile header header('X-Sendfile: ' . $libraryLocation . $file->path); } else if ($sendFileMode == 'Nginx') { // Send via Nginx X-Accel-Redirect header('X-Accel-Redirect: /download/' . $file->path); } else { header('HTTP/1.0 404 Not Found'); } // Also add to the overall bandwidth used by get file $container->get('bandwidthFactory')->createAndSave( \Xibo\Entity\Bandwidth::$GETFILE, $file->displayId, $file->size ); } } catch (\Xibo\Support\Exception\NotFoundException|\Xibo\Support\Exception\ExpiredException $e) { $logger->notice('HTTP GetFile: request received but unable to find XMDS Nonce. Issuing 404. ' . $e->getMessage()); // 404 header('HTTP/1.0 404 Not Found'); } catch (\Xibo\Support\Exception\InstanceSuspendedException $e) { $logger->debug('HTTP GetFile: Bandwidth exceeded'); header('HTTP/1.0 403 Forbidden'); } catch (\Exception $e) { $logger->error('HTTP GetFile: Unknown Error: ' . $e->getMessage()); $logger->debug($e->getTraceAsString()); // Issue a 500 header('HTTP/1.0 500 Internal Server Error'); } exit; } // Are we a CDN bandwidth log if (isset($_GET['cdn'])) { $logger = $container->get('logService'); try { // We expect a PSK CONSTANT to be defined in our configuration if (!defined('CDN_PSK')) { throw new \Xibo\Support\Exception\ConfigurationException('Missing CDN config'); } if (!array_key_exists('HTTP_X_CDN_PSK', $_SERVER) || $_SERVER['HTTP_X_CDN_PSK'] !== CDN_PSK) { throw new NotFoundException('Invalid Token'); } /** @var \Xibo\Factory\RequiredFileFactory $requiredFileFactory */ $requiredFileFactory = $container->get('requiredFileFactory'); $file = $requiredFileFactory->resolveRequiredFileFromRequest($_REQUEST); $container->get('logService')->info('CDN bandwidth request for ' . $file->path); // Do we have a usage amount provided? if (array_key_exists('HTTP_X_CDN_BW', $_SERVER) && is_numeric($_SERVER['HTTP_X_CDN_BW'])) { $usedBandwidth = intval($_SERVER['HTTP_X_CDN_BW']); // Don't allow this if we get bandwidth lower than 0 if ($usedBandwidth < 0) { $usedBandwidth = $file->size; } } // Bandwidth // Add the size to the bytes we have already requested. $file->bytesRequested = $file->bytesRequested + $file->size; $file->save(['useTransaction' => false]); // Also add to the overall bandwidth used by get file $container->get('bandwidthFactory')->createAndSave( \Xibo\Entity\Bandwidth::$GETFILE, $file->displayId, $file->size ); } catch (Exception $e) { $logger->error('Unknown Error: ' . $e->getMessage()); $logger->debug($e->getTraceAsString()); } exit; } // Connector request? if (isset($_GET['connector'])) { try { if (!isset($_GET['token'])) { header('HTTP/1.0 403 Forbidden'); exit; } // Dispatch an event to check the token $tokenEvent = new \Xibo\Event\XmdsConnectorTokenEvent(); $tokenEvent->setToken($_GET['token']); $container->get('dispatcher')->dispatch($tokenEvent, \Xibo\Event\XmdsConnectorTokenEvent::$NAME); if (empty($tokenEvent->getWidgetId())) { header('HTTP/1.0 403 Forbidden'); exit; } // Get the display /** @var \Xibo\Entity\Display $display */ $display = $container->get('displayFactory')->getById($tokenEvent->getDisplayId()); // Check it is still authorised. if ($display->licensed == 0) { throw new NotFoundException(__('Display unauthorised')); } // Check the widgetId is permissible, and in required files for the display. /** @var \Xibo\Entity\RequiredFile $file */ $file = $container->get('requiredFileFactory')->getByDisplayAndWidget( $tokenEvent->getDisplayId(), $tokenEvent->getWidgetId() ); // Get the widget $widget = $container->get('widgetFactory')->getById($tokenEvent->getWidgetId()); // It has been found, so we raise an event here to see if any connector can provide a file for it. $event = new \Xibo\Event\XmdsConnectorFileEvent($widget); $container->get('dispatcher')->dispatch($event, \Xibo\Event\XmdsConnectorFileEvent::$NAME); // What now? $emitter = new \Slim\ResponseEmitter(); $emitter->emit($event->getResponse()); } catch (\Xibo\Support\Exception\GeneralException $e) { header('HTTP/1.0 500 Internal Server Error'); echo $e->getMessage(); } catch (Exception $e) { $container->get('logService')->error('Unknown Error: ' . $e->getMessage()); $container->get('logService')->debug($e->getTraceAsString()); header('HTTP/1.0 500 Internal Server Error'); } exit; } // Town down all logging $container->get('logService')->setLevel(\Xibo\Service\LogService::resolveLogLevel('error')); try { $wsdl = PROJECT_ROOT . '/lib/Xmds/service_v' . $version . '.wsdl'; if (!file_exists($wsdl)) { throw new InvalidArgumentException(__('Your client is not the correct version to communicate with this CMS.')); } // logProcessor $logProcessor = new \Xibo\Xmds\LogProcessor($container->get('logger'), $uidProcessor->getUid()); $container->get('logger')->pushProcessor($logProcessor); // Create a SoapServer // explicitly define caching. if (\Xibo\Helper\Environment::isDevMode()) { // No cache - our WSDL might change in development $soap = new SoapServer($wsdl, ['cache_wsdl' => WSDL_CACHE_NONE]); } else { $soap = new SoapServer($wsdl, ['cache_wsdl' => WSDL_CACHE_MEMORY]); } $soap->setClass( '\Xibo\Xmds\Soap' . $version, $logProcessor, $container->get('pool'), $container->get('store'), $container->get('timeSeriesStore'), $container->get('logService'), $container->get('sanitizerService'), $container->get('configService'), $container->get('requiredFileFactory'), $container->get('moduleFactory'), $container->get('layoutFactory'), $container->get('dataSetFactory'), $container->get('displayFactory'), $container->get('userGroupFactory'), $container->get('bandwidthFactory'), $container->get('mediaFactory'), $container->get('widgetFactory'), $container->get('regionFactory'), $container->get('notificationFactory'), $container->get('displayEventFactory'), $container->get('scheduleFactory'), $container->get('dayPartFactory'), $container->get('playerVersionFactory'), $container->get('dispatcher'), $container->get('campaignFactory'), $container->get('syncGroupFactory'), $container->get('playerFaultFactory') ); // Add manual raw post data parsing, as HTTP_RAW_POST_DATA is deprecated. $soap->handle(file_get_contents('php://input')); // Get the stats for this connection $stats = $container->get('store')->stats(); $stats['length'] = microtime(true) - $startTime; $container->get('logService')->info('PDO stats: %s.', json_encode($stats, JSON_PRETTY_PRINT)); if ($container->get('store')->getConnection()->inTransaction()) { $container->get('store')->getConnection()->commit(); } // Finish any XMR work that has been logged during the request \Xibo\Middleware\Xmr::finish($app); } catch (Exception $e) { $container->get('logService')->error($e->getMessage()); if ($container->get('store')->getConnection()->inTransaction()) { $container->get('store')->getConnection()->rollBack(); } header('HTTP/1.1 500 Internal Server Error'); header('Content-Type: text/plain'); die('There has been an unknown error with XMDS, it has been logged. Please contact your administrator.'); }