Sindbad~EG File Manager
<?php
namespace App\Service\Lsws;
use Exception;
use Symfony\Component\Filesystem\Filesystem;
use Twig\Environment;
class LswsVhostManager
{
/**
* @var string
*/
protected $lswsHttpdConf;
/**
* @var string
*/
protected $lswsRoot;
/**
* @var Filesystem
*/
protected $fs;
/**
* @var array
*/
private $updates;
/**
* @var array
*/
private $data;
/**
* @var Environment
*/
private $twig;
/**
* @var LswsListenerManager
*/
protected $lswsListenerManager;
const GLOBAL_VHOST_KEYS = ['vhRoot', 'configFile', 'allowSymbolLink', 'enableScript', 'restrained', 'setUIDMode', 'user', 'group'];
const CONFIG_VHOST_KEYS = ['docRoot', 'vhDomain', 'vhAliases', 'adminEmails', 'enableGzip', 'enableBr', 'enableIpGeo', 'index', 'vhssl', 'module cache'];
private const VIRTUALHOST_REGEX = '#virtualhost\s+([\w.-]+)\s+{([^}]*)}#s';
public function __construct(string $lswsHttpdConf, string $lswsRoot, Environment $twig, LswsListenerManager $lswsListenerManager){
$this->lswsHttpdConf = $lswsHttpdConf;
$this->lswsRoot = $lswsRoot;
$this->lswsListenerManager = $lswsListenerManager;
$this->twig = $twig;
$this->updates = [];
$this->data = null;
$this->fs = new Filesystem();
}
/**
* @throws Exception
*/
public function load()
{
if(!$this->fs->exists($this->lswsHttpdConf)){
throw new Exception("The OpenLitespeed Httpd configuration file doesn't exists.");
}
$this->data = [];
$httpd = file_get_contents($this->lswsHttpdConf);
preg_match_all(self::VIRTUALHOST_REGEX, $httpd, $vhost_matches, PREG_PATTERN_ORDER);
$this->lswsListenerManager->load();
foreach ($vhost_matches[2] as $k => $rawVhost) {
if (strpos($rawVhost, 'aforem-edu.net')) {
continue;
}
$vhostName = $vhost_matches[1][$k];
$vhost = [];
$vhost['o2listeners'] = [];
foreach ($this->lswsListenerManager->getListeners() as $listenerName => $listener){
if(array_key_exists('map', $listener) && array_key_exists($vhostName, $listener['map'])){
$vhost['o2listeners'][] = $listenerName;
}
}
foreach (explode("\n", trim($rawVhost)) as $line) {
$line = explode('#', $line, 2)[0];
if (trim($line) === '') {
continue;
}
if (preg_match('/(\S+)\s+(.+)$/', $line, $matches)) {
$vhost[$matches[1]] = $matches[2];
}
}
if(array_key_exists('configFile', $vhost)){
$configFile = $this->lswsRoot . $vhost['configFile'];
if(!$this->fs->exists($configFile)){
$this->data[$vhostName] = $vhost;
continue;
}
$configFileContent = file_get_contents($configFile);
$templates = ['apache', 'default'];
foreach($templates as $template){
try{
$templateData = preg_quote($this->twig->render('vhconf/' . $template . '.conf.twig', ['serverIp' => '%ip']));
$templateData = preg_replace("/[\t ]/", '[\t ]*', $templateData);
$templateData = preg_replace("/\n/", '\n*', $templateData);
$templateData = str_replace('/', '\/', $templateData);
$hasReplaced = 0;
$templateData = preg_replace_callback('/%ip/', function () use (&$hasReplaced) {
$data = "(?<ip" . $hasReplaced . ">((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))";
$hasReplaced++;
return $data;
}, $templateData);
}catch (\Throwable $e){
continue;
}
if(preg_match("/.*".$templateData."/", $configFileContent, $matches)){
$vhost['o2template'] = $template;
if(array_key_exists('ip0', $matches)){
$vhost['o2serverIp'] = $matches['ip0'];
}
$configFileContent = preg_replace('/' . $templateData . '/', '', $configFileContent);
}
}
$configFileContent = trim($configFileContent);
$configData = preg_split("/\n/", $configFileContent);
$parent = null;
foreach($configData as $line){
$lineData = explode("\t", preg_replace("/[\t ,]+/", "\t", trim($line)));
$key = $lineData[0];
unset($lineData[0]);
$lineData = array_values($lineData);
if(count($lineData) === 1){
$lineData = $lineData[0];
}
if((empty($lineData) && $lineData !== "0" && $lineData !== 0) || empty($key)){
continue;
}
if($lineData === '{'){
$parent = $key;
}elseif($key === '}'){
$parent = null;
}else{
if($parent){
if(!array_key_exists($parent, $vhost)){
$vhost[$parent] = [];
}
$vhost[$parent][$key] = $lineData;
}else{
$vhost[$key] = $lineData;
}
}
}
}
$this->data[$vhostName] = $vhost;
}
}
public function clear()
{
if($this->data){
foreach($this->data as $key => $value){
$this->remove($key);
}
}
}
public function setRule(string $vhostName, string $name, $value)
{
if(!array_key_exists($vhostName, $this->updates) || !is_array($this->updates[$vhostName])){
$this->updates[$vhostName] = [];
}
if($value == null){
if(array_key_exists($name, $this->updates[$vhostName])){
unset($this->updates[$vhostName][$name]);
}
}else{
$this->updates[$vhostName][$name] = $value;
}
if(!is_array($this->updates[$vhostName]) || (is_array($this->updates[$vhostName]) && count($this->updates[$vhostName]) === 0)){
$this->remove($vhostName);
}
}
public function remove(string $vhostName){
$this->updates[$vhostName] = null;
}
/**
* @throws Exception
*/
public function persistAll()
{
$updates = $this->updates;
$errors = [];
foreach ($updates as $vhostName => $vhostData){
try{
if($vhostData !== null){
$this->persist($vhostName);
}else{
$this->drop($vhostName);
}
}catch(\Throwable $e){
$errors[$vhostName] = $e;
}
}
if(count($errors) > 0){
$message = "There is many errors detected\n";
foreach ($errors as $v => $e){
$message .= "\033[0;31m" . $v . " : " . $e->getMessage() . "\033[0m\n";
}
throw new Exception($message);
}
}
/**
* @throws Exception
*/
private function drop(string $vhostName)
{
$pattern = '#virtualhost\s+(' . $vhostName . ')\s+{([^}]*)}#s';
$configContent = file_get_contents($this->lswsHttpdConf);
if(!preg_match($pattern, $configContent, $matches)){
return;
}
$conf = explode("\n", trim($matches[2]));
$vhRoot = null;
$configFile = null;
foreach ($conf as $line){
$data = explode("\t", trim($line));
if(count($data) === 2 && $data[0] === "vhRoot" && preg_match('/^conf\/vhosts/', $data[1])){
$vhRoot = $this->lswsRoot . $data[1];
}
if (count($data) === 2 && $data[0] === 'configFile' && preg_match('/^conf\/vhosts/', $data[1])) {
$configFile = $this->lswsRoot . $data[1];
}
}
$this->fs->dumpFile($this->lswsHttpdConf, preg_replace($pattern, '', $configContent));
$filesToRemove = [
$configFile,
$configFile . ".txt",
$configFile . "0",
$configFile . ".bak",
];
$this->fs->remove($filesToRemove);
$filesInRoot = scandir($vhRoot);
if($filesInRoot){
$filesInRoot = array_diff($filesInRoot, ['.', '..']);
if(count($filesInRoot) === 0){
$this->fs->remove($vhRoot);
}
}
$this->lswsListenerManager->load();
$listeners = array_keys($this->lswsListenerManager->getListeners());
foreach($listeners as $listener){
$this->lswsListenerManager->unmapVhost($listener, $vhostName);
}
$this->lswsListenerManager->persist();
}
/**
* @throws Exception
*/
private function persist(string $vhostName)
{
// Remove old vhost config to erase it
$this->drop($vhostName);
// Generate the vhost in the global config and create root folder and config file
$this->generate($vhostName);
// Persist data in the config file
$this->write($vhostName);
// Add the vhost to listeners
$this->persistOnListeners($vhostName);
unset($this->updates[$vhostName]);
}
/**
* @throws Exception
*/
private function generate(string $vhostName)
{
if(!$this->fs->exists($this->lswsHttpdConf)){
throw new Exception("OpenLitespeed configuration file doesn't exists.");
}
$config = $this->updates[$vhostName];
$rawConfig = "virtualhost $vhostName {";
foreach ($config as $name => $value){
if(!in_array($name, self::GLOBAL_VHOST_KEYS)){
continue;
}
$rawConfig = $this->addRawRule($rawConfig, $name, $value, 1);
}
$rawConfig .= "\n}";
$rawFileContent = file_get_contents($this->lswsHttpdConf);
$this->fs->dumpFile($this->lswsHttpdConf, preg_replace('/\n\n+/', "\n\n", $rawFileContent . "\n\n" . $rawConfig));
$this->fs->chown($this->lswsHttpdConf, 'lsadm');
$this->fs->chgrp($this->lswsHttpdConf, 'nobody');
$this->generateVhostFiles($vhostName);
}
/**
* @throws Exception
*/
private function generateVhostFiles(string $vhostName){
$config = $this->updates[$vhostName];
if (array_key_exists('vhRoot', $config) && !empty($config['vhRoot'])) {
$vhRoot = $this->lswsRoot . $config['vhRoot'];
if (!$this->fs->exists($vhRoot)) {
$this->fs->mkdir($vhRoot, 0750);
$this->fs->chown($vhRoot, 'lsadm');
$this->fs->chgrp($vhRoot, 'nobody');
}
}
if (array_key_exists('configFile', $config) && !empty($config['configFile'])) {
$configFile = $this->lswsRoot . $config['configFile'];
if (!$this->fs->exists($configFile)) {
$this->fs->touch($configFile, 0750);
$this->fs->chown($configFile, 'lsadm');
$this->fs->chgrp($configFile, 'nobody');
}
}
}
/**
* @throws Exception
*/
private function write(string $vhostName)
{
$config = $this->updates[$vhostName];
if (!array_key_exists('configFile', $config) || empty($config['configFile'])) {
return;
}
if (!array_key_exists('o2serverIp', $config) || empty($config['o2serverIp'])) {
throw new Exception("o2serverIp is not defined. You have to define it.");
}
$this->generateVhostFiles($vhostName);
if(!$this->fs->exists($this->lswsRoot . $config['configFile'])){
throw new Exception("Vhost config file doesn't exists. Impossible to create it.");
}
$rawVhostConfig = '';
foreach ($config as $name => $value){
if(!in_array($name, self::CONFIG_VHOST_KEYS)){
continue;
}
$rawVhostConfig = $this->addRawRule($rawVhostConfig, $name, $value);
}
try{
$res = $this->addTemplate($rawVhostConfig, $config['o2template'], $config['o2serverIp']);
}catch (\Throwable $e){
$res = $this->addTemplate($rawVhostConfig, "default", $config['o2serverIp']);
}
$rawVhostConfig = $res;
$this->fs->dumpFile($this->lswsRoot . $config['configFile'], $rawVhostConfig);
$this->fs->chown($this->lswsRoot . $config['configFile'], 'lsadm');
$this->fs->chgrp($this->lswsRoot . $config['configFile'], 'nobody');
}
/**
* @throws Exception
*/
private function addTemplate(string $rawConfig, string $template, string $serverIp = '127.0.0.1') : string
{
$template = $this->twig->render("vhconf/$template.conf.twig", ['serverIp' => $serverIp]);
return $rawConfig . "\n" . $template;
}
/**
* @throws Exception
*/
private function persistOnListeners(string $vhostName)
{
$config = $this->updates[$vhostName];
if(!array_key_exists('o2listeners', $config) || empty($config['o2listeners'])){
return;
}
if(!is_array($config['o2listeners'])){
throw new Exception("o2listeners params is not an array");
}
if (!array_key_exists('vhDomain', $config) || empty($config['vhDomain'])) {
throw new Exception('There is no vhDomain for this vhost. A vhDomain is required to map it with a listener');
}
$this->lswsListenerManager->load();
$availableListeners = array_keys($this->lswsListenerManager->getListeners());
foreach ($config['o2listeners'] as $listener){
if(in_array($listener, $availableListeners)){
$this->lswsListenerManager->mapVhost($listener, $vhostName, $config['vhDomain']);
}
}
$this->lswsListenerManager->persist();
}
private function addRawRule(string $rawConfig, string $name, $value, $tabCount = 0) : string
{
if(empty($value) && $value !== "0" && $value !== 0){
return $rawConfig;
}
$tabs = str_repeat("\t", $tabCount);
if(is_array($value)){
$rawConfig .= "\n" . $tabs . $name . "\t{";
foreach ($value as $k => $v){
if((empty($v) && $v !== '0' && $v !== 0) || is_array($v)){
continue;
}
$rawConfig .= "\n" . $tabs . "\t" . $k . "\t" . $v;
}
$rawConfig .= "\n" . $tabs . "}\n";
}else{
$rawConfig .= "\n" . $tabs . $name . "\t" . $value;
}
return $rawConfig;
}
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists