From fa0bb3d55ae031ebfec89b05c3cc6c71baeaed5a Mon Sep 17 00:00:00 2001 From: pwlin <160443+pwlin@users.noreply.github.com> Date: Thu, 8 Aug 2024 20:14:47 +0100 Subject: [PATCH] fixed all the bugs when running the backend on wasmer --- .github/workflows/ci.yml | 42 +++++++++++++++++++++++++++ .gitignore | 1 + client/config.example.json | 2 +- client/index.html | 2 +- client/js/config.js | 2 +- client/js/model.js | 7 ++--- package.json | 2 +- server/index.php | 5 ++++ server/salad-api.php | 59 +++++++++++++++++++------------------- wasmer.toml | 13 +++++++++ 10 files changed, 97 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 wasmer.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5597213 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: Continuous Integration + +on: + pull_request: + push: + branches: + - main + +env: + GITHUB_TOKEN: ${{ github.token }} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build_and_deploy: + name: Build and Deploy PHP site to Wasmer Edge + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install Wasmer + uses: wasmerio/setup-wasmer@v2 + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + # tools: composer + # - name: Get composer cache directory + # id: composer-cache + # run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + # - name: Cache dependencies + # uses: actions/cache@v4 + # with: + # path: ${{ steps.composer-cache.outputs.dir }} + # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + # restore-keys: ${{ runner.os }}-composer- + # - name: Install dependencies + # run: composer install --prefer-dist + - name: Deploy app to Wasmer Edge + run: wasmer deploy --token=${{ secrets.WASMER_CIUSER_PROD_TOKEN }} --non-interactive --no-wait --no-persist-id diff --git a/.gitignore b/.gitignore index 3878834..891158f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ client/feeds.json client/Neo.toml package-lock.json server/cache +app.yaml diff --git a/client/config.example.json b/client/config.example.json index 97209ca..9ded174 100644 --- a/client/config.example.json +++ b/client/config.example.json @@ -3,7 +3,7 @@ "main_color": "red", "second_color": "black", "api_key": "dev-api-key", - "api_url": "http://127.0.0.1:8080/index.php", + "api_url": "http://127.0.0.1:8081/index.php", "favicons_enabled": false, "redirlinks_enabled": false } \ No newline at end of file diff --git a/client/index.html b/client/index.html index d2046c7..833d1a2 100644 --- a/client/index.html +++ b/client/index.html @@ -38,7 +38,7 @@ diff --git a/client/js/config.js b/client/js/config.js index b147f00..2fa9f48 100644 --- a/client/js/config.js +++ b/client/js/config.js @@ -32,7 +32,7 @@ class Config { constructor() { this.config = null; this.defaultConfig = { - 'xml2json_endpoint': '/xml2json?url=__URL__&_t=__TS__', + 'xml2json_endpoint': '/xml2json?url=__URL__&_t=__TS__&api_key=__API_KEY__', 'favicon_endpoint': '/favicon?url=__URL__&api_key=__API_KEY__', 'revredir_endpoint': '/revredir?url=__URL__&api_key=__API_KEY__' }; diff --git a/client/js/model.js b/client/js/model.js index f93fce5..fe086db 100644 --- a/client/js/model.js +++ b/client/js/model.js @@ -38,13 +38,12 @@ class Model { * @returns {Promise} A promise that resolves to the normalized feed data. */ async fetchFeed(feed) { - const apiKey = config.get('api_key'); - const serviceUrl = `${config.get('api_url')}${config.get('xml2json_endpoint').replace('__URL__', encodeURIComponent(feed.feedUrl)).replace('__TS__', utils.ts())}`; - const response = await fetch(serviceUrl, { + const serviceUrl = `${config.get('api_url')}${config.get('xml2json_endpoint').replace('__URL__', encodeURIComponent(feed.feedUrl)).replace('__TS__', utils.ts()).replace('__API_KEY__', config.get('api_key'))}`; + const response = await fetch(serviceUrl/*, { headers: { 'Authorization': `Bearer ${apiKey}` } - }); + }*/); const content = await response.json(); // Normalize the general structure of the feed entries diff --git a/package.json b/package.json index 6c45399..3879617 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "dev-client": "node node_modules/vite/bin/vite.js --clearScreen false", "build-client": "node node_modules/vite/bin/vite.js build", "preview-client": "node node_modules/vite/bin/vite.js preview", - "dev-server": "php -S localhost:8080 -t ./server" + "dev-server": "php -S localhost:8081 -t ./server" }, "devDependencies": { "vite": "^5.3.4", diff --git a/server/index.php b/server/index.php index 693b9a1..4c0f5a8 100644 --- a/server/index.php +++ b/server/index.php @@ -1,4 +1,9 @@ salad=$salad;}private function loginWithAPIKey($selectedApiKey,$accessLevelNeeded){$configApiKeys=$this->salad->config->get('api_keys');if(!empty($configApiKeys)){$apiKeys=['result'=>$configApiKeys];}else{$apiKeys=$this->salad->db->select('SELECT * FROM '.$this->salad->config->get('db_api_keys_table_name'));}$ret=false;foreach($apiKeys['result'] as $v){if( $v['key1']===$selectedApiKey && intval($v['access_level']) >= $accessLevelNeeded && in_array($v['enabled'],['yes','true',true]) ){$ret=true;if(!empty($v['tags_whitelist'])){$this->salad->request->setParam('tags_whitelist',$v['tags_whitelist']);}break;}} return $ret;}private function loginWithHttpDigest($accessLevelNeeded){$realm='Salad Restricted Area';if(empty($_SERVER['PHP_AUTH_DIGEST'])){$this->showLoginDialog($realm);return false;}else{$data=$this->httpDigestParse($_SERVER['PHP_AUTH_DIGEST']);$digestUsers=$this->salad->config->get('auth_digest_users',[]);$selectedUser=[];foreach($digestUsers as $u){if( $u['username']===@$data['username'] && $u['access_level'] >= $accessLevelNeeded && in_array($u['enabled'],['yes','true',true]) ){$selectedUser=$u;break;}} if(empty($selectedUser)){return false;}$A1=md5($data['username'].':'.$realm.':'.$selectedUser['password']);$A2=md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);$validResponse=md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);if($data['response']!==$validResponse){return false;}else{return true;}}}public function login($args=[]){$args['access_level_needed']=intval($args['access_level_needed']);if($args['access_level_needed']!==0 && !isset($args['access_level_needed'])){$args['access_level_needed']=9;}if( $this->salad->request->getParam('is_cli')===true || $args['access_level_needed']===0 || @$args['skip_auth']===true ){return true;}$selectedApiKey=$this->apiKeyFromHeader();if(empty($selectedApiKey)){$selectedApiKey=@$args['api_key'];}if(!empty($selectedApiKey) && $this->salad->config->get('auth_api_keys_enabled')===true){return $this->loginWithAPIKey($selectedApiKey,$args['access_level_needed']);}elseif($this->salad->config->get('auth_digest_enabled')===true){return $this->loginWithHttpDigest($args['access_level_needed']);}else{return false;}} private function apiKeyFromHeader(){$apiKey='';$headers=getallheaders();if(isset($headers['Authorization'])){$authHeader=$headers['Authorization'];if(preg_match('/Bearer\s(\S+)/',$authHeader,$matches)){$apiKey=$matches[1];}} elseif(isset($headers['X-Api-Key'])){$apiKey=$headers['X-Api-Key'];}else if(isset($headers['X-Auth-Token'])){$apiKey=$headers['X-Auth-Token'];}return $apiKey;}private function showLoginDialog($realm){header('HTTP/1.1 401 Unauthorized');header('WWW-Authenticate:Digest realm="'.$realm.'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');}private function httpDigestParse($txt){$needed_parts=array('nonce'=>1,'nc'=>1,'cnonce'=>1,'qop'=>1,'username'=>1,'uri'=>1,'response'=>1);$data=array();$keys=implode('|',array_keys($needed_parts));preg_match_all('@('.$keys.')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@',$txt,$matches,PREG_SET_ORDER);foreach($matches as $m){$data[$m[1]]=$m[3]?$m[3]:$m[4];unset($needed_parts[$m[1]]);}return $needed_parts?false:$data;}} -class Cache{private int $ttl=86400;private $salad;public function __construct($salad){$this->salad=$salad;}public function makeCacheKey($key=[],$prefix=''){return $prefix.md5($prefix.serialize($key));}public function get($key,$default=false){if($this->salad->config->get('cache_type')==='none'){return false;}$cacheFile=$this->salad->config->get('cache_path').'/'.$key;if(is_file($cacheFile)){$data=json_decode(file_get_contents($cacheFile),true);if($data['expires']>0 && $data['expires']salad->config->get('cache_type')==='none'){return;}if($ttl<0){$ttl=0;}else{if($ttl===false){$ttl=intval($this->salad->config->get('cache_ttl',1440)) * 60;}$ttl=time() + $ttl;}$data=array( 'expires'=>$ttl,'value'=>$value );$path=$this->salad->config->get('cache_path').'/';if(!is_dir($path.dirname($key))){$this->mkdirRecursive($path.dirname($key));}@file_put_contents($path.$key,json_encode($data));@chmod($path.$key,0666);}private function mkdirRecursive($pathname,$mode=0777){if(!is_dir(dirname($pathname))){return $this->mkdirRecursive(dirname($pathname),$mode);}if(!is_dir($pathname)){mkdir($pathname);@chmod($pathname,$mode);}return is_dir($pathname);}public function cleanFolder($folderName){if($this->salad->config->get('cache_type')==='none'){return;}foreach(glob($this->salad->config->get('cache_path').'/'.$folderName.'/'.'*') as $filename){@unlink($filename);}@rmdir($this->salad->config->get('cache_path').'/'.$folderName);}} +class Cache{private $salad;public function __construct($salad){$this->salad=$salad;}public function makeCacheKey($key=[],$prefix=''){return $prefix.md5($prefix.serialize($key));}public function get($key,$default=false){if($this->salad->config->get('cache_type')==='none'){return false;}$cacheFile=$this->salad->config->get('cache_path').'/'.$key;if(is_file($cacheFile)){$data=json_decode(file_get_contents($cacheFile),true);if($data['expires']>0 && $data['expires']salad->config->get('cache_type')==='none'){return;}if($ttl<0){$ttl=0;}else{if($ttl===false){$ttl=intval($this->salad->config->get('cache_ttl',1440)) * 60;}$ttl=time() + $ttl;}$data=array( 'expires'=>$ttl,'value'=>$value );$path=$this->salad->config->get('cache_path').'/';if(!is_dir($path.dirname($key))){$this->mkdirRecursive($path.dirname($key));}@file_put_contents($path.$key,json_encode($data));@chmod($path.$key,0666);}private function mkdirRecursive($pathname,$mode=0777){if(!is_dir(dirname($pathname))){return $this->mkdirRecursive(dirname($pathname),$mode);}if(!is_dir($pathname)){mkdir($pathname);@chmod($pathname,$mode);}return is_dir($pathname);}public function cleanFolder($folderName){if($this->salad->config->get('cache_type')==='none'){return;}foreach(glob($this->salad->config->get('cache_path').'/'.$folderName.'/'.'*') as $filename){@unlink($filename);}@rmdir($this->salad->config->get('cache_path').'/'.$folderName);}} class Config{private array $params=[];private function setDefaultParams(){$this->params=['cache_type'=>'none','cache_path'=>'../cache','cache_ttl'=>1440,'db_bookmarks_table_name'=>'bookmarks','db_api_keys_table_name'=>'api_keys','db_type'=>'mysql','db_file'=>'../db/salad.sqlite','db_host'=>'127.0.0.1','db_port'=>'3306','db_name'=>'salad','db_username'=>'root','db_password'=>'','enable_cors_headers'=>false,'enable_web_api'=>false,'fetch_favicons'=>false,'fetch_popular_tags'=>false,'minify_html_output'=>false,'insert_example_bookmarks'=>true,'auth_api_keys_enabled'=>false,'api_keys'=>[['key1'=>'dev-api-key','access_level'=>1,'enabled'=>false]],'auth_digest_enabled'=>false,'auth_digest_users'=>[['username'=>'admin','password'=>'admin','access_level'=>9,'enabled'=>false],],'route_not_found'=>['func'=>'\Salad\API\Controllers\NotFound::index','format'=>!empty($_GET['format'])?$_GET['format']:'html','args'=>['access_level_needed'=>0]],'routes'=>[['uri'=>'/','func'=>'\Salad\API\Controllers\Home::index','format'=>!empty($_GET['format'])?$_GET['format']:'html','args'=>['access_level_needed'=>0]],['uri'=>'/tags','func'=>'\Salad\API\Controllers\Home::index','format'=>!empty($_GET['format'])?$_GET['format']:'html','args'=>['access_level_needed'=>9]],['uri'=>'/posts','func'=>'\Salad\API\Controllers\Home::index','format'=>!empty($_GET['format'])?$_GET['format']:'html','args'=>['access_level_needed'=>9]],['uri'=>'/tags/get','func'=>'\Salad\API\Controllers\Tags\Get::index','format'=>@$_GET['format'],'args'=>['tag'=>@$_GET['tag'],'search'=>@$_GET['search'],'sort'=>@$_GET['sort'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/tags/recent','func'=>'\Salad\API\Controllers\Tags\Recent::index','format'=>@$_GET['format'],'args'=>['api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/tags/rename','func'=>'\Salad\API\Controllers\Tags\Rename::index','format'=>@$_GET['format'],'args'=>['old'=>@$_GET['old'],'new'=>@$_GET['new'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/tags/delete','func'=>'\Salad\API\Controllers\Tags\Delete::index','format'=>@$_GET['format'],'args'=>['tag'=>@$_GET['tag'],'also_delete_bookmarks'=>@$_GET['also_delete_bookmarks'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/update','func'=>'\Salad\API\Controllers\Posts\Update::index','format'=>@$_GET['format'],'args'=>['api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/add','func'=>'\Salad\API\Controllers\Posts\Add::index','format'=>@$_GET['format'],'args'=>['url'=>@$_GET['url'],'description'=>@$_GET['description'],'extended'=>@$_GET['extended'],'tags'=>@$_GET['tags'],'dt'=>@$_GET['dt'],'replace'=>@$_GET['replace'],'shared'=>@$_GET['shared'],'old_hash'=>@$_GET['old_hash'],'shared'=>@$_GET['shared'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/delete','func'=>'\Salad\API\Controllers\Posts\Delete::index','format'=>@$_GET['format'],'args'=>['url'=>@$_GET['url'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/get','func'=>'\Salad\API\Controllers\Posts\Get::index','format'=>@$_GET['format'],'args'=>['tag'=>@$_GET['tag'],'search'=>@$_GET['search'],'dt'=>@$_GET['dt'],'url'=>@$_GET['url'],'hashes'=>@$_GET['hashes'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/recent','func'=>'\Salad\API\Controllers\Posts\Recent::index','format'=>@$_GET['format'],'args'=>['tag'=>@$_GET['tag'],'count'=>@$_GET['count'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/dates','func'=>'\Salad\API\Controllers\Posts\Dates::index','format'=>@$_GET['format'],'args'=>['tag'=>@$_GET['tag'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/all?hashes','func'=>'\Salad\API\Controllers\Posts\AllHashes::index','format'=>@$_GET['format'],'args'=>['api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/all','func'=>'\Salad\API\Controllers\Posts\All::index','format'=>@$_GET['format'],'args'=>['tag'=>@$_GET['tag'],'search'=>@$_GET['search'],'start'=>@$_GET['start'],'results'=>@$_GET['results'],'count'=>@$_GET['count'],'count_only'=>@$_GET['count_only'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/suggest','func'=>'\Salad\API\Controllers\Posts\Suggest::index','format'=>@$_GET['format'],'args'=>['url'=>@$_GET['url'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/posts/favicon','func'=>'\Salad\API\Controllers\Posts\FavIcon::index','format'=>'png','args'=>['hash'=>@$_GET['hash'],'url'=>@$_GET['url'],'api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/keys/gen','func'=>'\Salad\API\Controllers\Keys\Gen::index','format'=>@$_GET['format'],'args'=>['api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/keys/all','func'=>'\Salad\API\Controllers\Keys\All::index','format'=>@$_GET['format'],'args'=>['api_key'=>@$_GET['api_key'],'access_level_needed'=>9]],['uri'=>'/keys/add','func'=>'\Salad\API\Controllers\Keys\Add::index','format'=>@$_GET['format'],'args'=>['api_key'=>@$_GET['api_key'],'key1'=>@$_GET['key1'],'description'=>@$_GET['description'],'access_level'=>@$_GET['access_level'],'enabled'=>@$_GET['enabled'],'tags_whitelist'=>@$_GET['tags_whitelist'],'access_level_needed'=>9]],['uri'=>'/keys/update','func'=>'\Salad\API\Controllers\Keys\Update::index','format'=>@$_GET['format'],'args'=>['api_key'=>@$_GET['api_key'],'id'=>@$_GET['id'],'key1'=>@$_GET['key1'],'description'=>@$_GET['description'],'access_level'=>@$_GET['access_level'],'enabled'=>@$_GET['enabled'],'tags_whitelist'=>@$_GET['tags_whitelist'],'access_level_needed'=>9]],['uri'=>'/keys/delete','func'=>'\Salad\API\Controllers\Keys\Delete::index','format'=>@$_GET['format'],'args'=>['api_key'=>@$_GET['api_key'],'id'=>@$_GET['id'],'access_level_needed'=>9]]]];}public function __construct($salad){$this->setDefaultParams();if(is_file('./config.php')){$config=include('./config.php');if(is_array($config)){$this->params=array_merge($this->params,$config);}} if(is_file('./config.user.php')){$userConfig=include('./config.user.php');if(is_array($userConfig)){$this->params=array_merge($this->params,$userConfig);}}}public function get($key,$default=false){return isset($this->params[$key])?$this->params[$key]:$default;}public function getAll(){return $this->params;}public function set($key,$val=''){$configItems=[];if(!is_array($key)){$configItems[$key]=$val;}else{$configItems=$key;}foreach($configItems as $k=>$v){$this->params[$k]=$v;}} } -class DB{private $dbh=false;private $salad;public function __construct($salad){$this->salad=$salad;}public function query($sql){return $this->dbh->query($sql);}public function init(){try{switch($this->salad->config->get('db_type')){case 'sqlite';default:$this->dbh=new \PDO('sqlite:'.$this->salad->config->get('db_file'));$this->exec('PRAGMA encoding="UTF-8"');break;case 'mysql':$this->dbh=new \PDO('mysql:host='.$this->salad->config->get('db_host').';port='.$this->salad->config->get('db_port').';charset=utf8mb4;dbname='.$this->salad->config->get('db_name'),$this->salad->config->get('db_username'),$this->salad->config->get('db_password'),[]);break;}$this->dbh->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION);if(!$this->tableExists($this->salad->config->get('db_bookmarks_table_name'))){$this->createInitialDB();if($this->salad->config->get('insert_example_bookmarks')===true){$this->insertSaladBookmarks();$this->insertExampleAPIKeys();}}}catch (\PDOException $e){exit('DB Error:'.$e->getMessage());}} public function exec($query,$params=[]){if($this->dbh===false){$this->init();}$queries=[];if(is_array($query)){$queries=$query;}else{$queries[]=['sql'=>$query,'params'=>$params];}$this->dbh->beginTransaction();try{foreach($queries as $q){$qParams=[];if(is_array($q)){$qParams=!empty($q['params'])?$q['params']:[];$qSQL=$q['sql'];}else{$qSQL=$q;}$stmt=$this->dbh->prepare($qSQL);$stmt->execute($qParams);}$result=$this->dbh->commit();return ['msg'=>'ok','result'=>$result];}catch (\PDOException $e){$this->dbh->rollBack();return ['msg'=>$e->getMessage(),'result'=>0,'sql'=>$query,'params'=>$params];}} public function select($query,$params=[]){if($this->dbh===false){$this->init();}try{$sth=$this->dbh->prepare($query);$sth->setFetchMode(\PDO::FETCH_ASSOC);$sth->execute($params);$result=$sth->fetchAll();return ['msg'=>'ok','result'=>$result];}catch (\PDOException $e){return ['msg'=>$e->getMessage(),'result'=>[],'sql'=>$query,'params'=>$params];}} private function createInitialDB(){switch($this->salad->config->get('db_type')){case 'mysql':$schema=explode(';','CREATE TABLE `bookmarks` ( `id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `url` TEXT NOT NULL, `description` TEXT NOT NULL, `extended` TEXT DEFAULT "", `tags` TEXT DEFAULT "", `created` VARCHAR(15) NOT NULL, `modified` VARCHAR(15) NOT NULL, `hash` VARCHAR(32) NOT NULL, `shared` VARCHAR(3) NOT NULL DEFAULT "yes", INDEX (`created`), INDEX (`modified`), INDEX (`shared`), UNIQUE (`hash`)) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;CREATE TABLE `api_keys` ( `id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `key1` VARCHAR(255) NOT NULL, `description` VARCHAR(255) DEFAULT "", `access_level` VARCHAR(2) NOT NULL DEFAULT "1", `enabled` VARCHAR(3) NOT NULL DEFAULT "no", `tags_whitelist` TEXT DEFAULT "", INDEX (`enabled`), UNIQUE (`key1`)) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');break;case 'sqlite':default:$schema=explode(';','CREATE TABLE "bookmarks" ("id" INTEGER PRIMARY KEY AUTOINCREMENT,"url" TEXT NOT NULL,"description" TEXT NOT NULL,"extended" TEXT DEFAULT "","tags" TEXT DEFAULT "","created" TEXT NOT NULL,"modified" TEXT NOT NULL,"hash" TEXT NOT NULL,"shared" TEXT NOT NULL DEFAULT "yes");CREATE INDEX "created" ON "bookmarks" ("created");CREATE INDEX "modified" ON "bookmarks" ("modified");CREATE INDEX "shared" ON "bookmarks" ("shared");CREATE UNIQUE INDEX "hash" ON "bookmarks" ("hash");CREATE TABLE "api_keys" ("id" INTEGER PRIMARY KEY AUTOINCREMENT,"key1" TEXT NOT NULL,"description" TEXT DEFAULT "","access_level" TEXT NOT NULL DEFAULT "1","enabled" TEXT NOT NULL DEFAULT "no","tags_whitelist" TEXT DEFAULT "");CREATE INDEX "enabled" ON "api_keys" ("enabled");CREATE UNIQUE INDEX "key1" ON "api_keys" ("key1");');break;}$schema=array_filter($schema,'trim');$schema=array_filter($schema,'strlen');$schema=array_map(function ($value){return str_replace( ['CREATE TABLE `bookmarks`','CREATE TABLE "bookmarks"','CREATE TABLE `api_keys`','CREATE TABLE "api_keys"'],['CREATE TABLE `'.$this->salad->config->get('db_bookmarks_table_name').'`','CREATE TABLE "'.$this->salad->config->get('db_bookmarks_table_name').'"','CREATE TABLE `'.$this->salad->config->get('db_api_keys_table_name').'`','CREATE TABLE "'.$this->salad->config->get('db_api_keys_table_name').'"'],$value );},$schema);foreach($schema as $query){$this->dbh->exec($query);}} private function tableExists($table){try{$this->dbh->query('SELECT 1 FROM '.$table);}catch (\PDOException $e){return false;}return true;}private function insertExampleBookmarks(){$bookmarks=json_decode('[ { "url": "https://github.com/notwaldorf/dear-sir-or-madam", "description": "notwaldorf/dear-sir-or-madam: 💌 Bookmarklet that ransomifies your internets", "tags": "javascript bookmarklet github color" }, { "url": "http://www.srehttp.org/apps/gif_text/mkgiftxt.htm", "description": "GIF_TEXT: A Graphics Text Generator", "tags": "image generator gif" }, { "url": "https://browsers.evolt.org/", "description": "evolt.org - Browser Archive", "tags": "browsers archive javascript" }, { "url": "https://www.mercurytheatre.info/", "description": "The Mercury Theatre on the Air", "tags": "audio media radio" }, { "url": "color:FFFFFF,81C757,333333,D3D2D2,0000FF,DDDDDD,FF0000", "description": "Salad color scheme", "tags": "color swatches" }, { "url": "https://web.archive.org/web/20050207095344/http://home.earthlink.net/~sarasohn/aboutgs.html", "description": "Father Guido Sarducci", "tags": "media comedy tv" }]',true);$queries=[];$url='';$tags='';foreach($bookmarks as $link){if(!empty($link['post'])){$link=$link['post'];}if(!empty($link['url'])){$url=$link['url'];}elseif(!empty($link['href'])){$url=$link['href'];}if(!empty($link['tag'])){$tags=$link['tag'];}elseif(!empty($link['tags'])){$tags=$link['tags'];}if(!empty($link['time'])){$dt=strtotime($link['time']);}else{$dt=strtotime('now');}$url2=$url;$queries[]=['sql'=>'INSERT INTO '.$this->salad->config->get('db_bookmarks_table_name').' (url,description,extended,tags,created,modified,hash,shared) VALUES (:url,:description,:extended,:tags,:created,:modified,:hash,:shared)','params'=>['url'=>$url2,'description'=>@$link['description'],'extended'=>@$link['extended'],'tags'=>' '.$tags.' ','created'=>$dt,'modified'=>$dt,'hash'=>md5($url),'shared'=>'yes']];}$this->exec($queries);}private function insertExampleAPIKeys(){$queries=[];$queries[]=['sql'=>'INSERT INTO '.$this->salad->config->get('db_api_keys_table_name').' (key1,description,access_level,enabled) VALUES (:key1,:description,:access_level,:enabled)','params'=>['key1'=>'1234','description'=>'key1','access_level'=>'1','enabled'=>'no']];$this->exec($queries);}private function insertSaladBookmarks(){$saladBookmarksFile=realpath(__DIR__.'/../Utils/Schema/salad.bookmarks.json');if(!is_file($saladBookmarksFile)){$this->insertExampleBookmarks();return;}$bookmarks=json_decode(file_get_contents(realpath(__DIR__.'/../Utils/Schema/salad.bookmarks.json')),true);$queries=[];$bookmarks=$bookmarks[2]['data'];foreach($bookmarks as $link){$link['url']=str_replace('+',' ',$link['url']);$queries[]=['sql'=>'INSERT INTO '.$this->salad->config->get('db_bookmarks_table_name').' (url,description,extended,tags,created,modified,hash,shared) VALUES (:url,:description,:extended,:tags,:created,:modified,:hash,:shared)','params'=>['url'=>$link['url'],'description'=>@$link['description'],'extended'=>@$link['extended'],'tags'=>' '.trim($link['tags']).' ','created'=>$link['created'],'modified'=>$link['modified'],'hash'=>$link['hash'],'shared'=>'yes']];}$exec=$this->exec($queries);}} +class DB{private $dbh;private $salad;public function __construct($salad){$this->salad=$salad;}public function query($sql){return $this->dbh->query($sql);}public function init(){try{switch($this->salad->config->get('db_type')){case 'sqlite';default:$this->dbh=new \PDO('sqlite:'.$this->salad->config->get('db_file'));$this->exec('PRAGMA encoding="UTF-8"');break;case 'mysql':$this->dbh=new \PDO('mysql:host='.$this->salad->config->get('db_host').';port='.$this->salad->config->get('db_port').';charset=utf8mb4;dbname='.$this->salad->config->get('db_name'),$this->salad->config->get('db_username'),$this->salad->config->get('db_password'),[]);break;}$this->dbh->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION);if(!$this->tableExists($this->salad->config->get('db_bookmarks_table_name'))){$this->createInitialDB();if($this->salad->config->get('insert_example_bookmarks')===true){$this->insertExampleBookmarks();$this->insertExampleAPIKeys();}}}catch (\PDOException $e){exit('DB Error:'.$e->getMessage());}} public function exec($query,$params=[]){if($this->dbh===null){$this->init();}$queries=[];if(is_array($query)){$queries=$query;}else{$queries[]=['sql'=>$query,'params'=>$params];}$this->dbh->beginTransaction();try{foreach($queries as $q){$qParams=[];if(is_array($q)){$qParams=!empty($q['params'])?$q['params']:[];$qSQL=$q['sql'];}else{$qSQL=$q;}$stmt=$this->dbh->prepare($qSQL);$stmt->execute($qParams);}$result=$this->dbh->commit();return ['msg'=>'ok','result'=>$result];}catch (\PDOException $e){$this->dbh->rollBack();return ['msg'=>$e->getMessage(),'result'=>0,'sql'=>$query,'params'=>$params];}} public function select($query,$params=[]){if($this->dbh===null){$this->init();}try{$sth=$this->dbh->prepare($query);$sth->setFetchMode(\PDO::FETCH_ASSOC);$sth->execute($params);$result=$sth->fetchAll();return ['msg'=>'ok','result'=>$result];}catch (\PDOException $e){return ['msg'=>$e->getMessage(),'result'=>[],'sql'=>$query,'params'=>$params];}} private function createInitialDB(){switch($this->salad->config->get('db_type')){case 'mysql':$schema=explode(';','CREATE TABLE `bookmarks` ( `id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `url` TEXT NOT NULL, `description` TEXT NOT NULL, `extended` TEXT DEFAULT "", `tags` TEXT DEFAULT "", `created` VARCHAR(15) NOT NULL, `modified` VARCHAR(15) NOT NULL, `hash` VARCHAR(32) NOT NULL, `shared` VARCHAR(3) NOT NULL DEFAULT "yes", INDEX (`created`), INDEX (`modified`), INDEX (`shared`), UNIQUE (`hash`)) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;CREATE TABLE `api_keys` ( `id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `key1` VARCHAR(255) NOT NULL, `description` VARCHAR(255) DEFAULT "", `access_level` VARCHAR(2) NOT NULL DEFAULT "1", `enabled` VARCHAR(3) NOT NULL DEFAULT "no", `tags_whitelist` TEXT DEFAULT "", INDEX (`enabled`), UNIQUE (`key1`)) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');break;case 'sqlite':default:$schema=explode(';','CREATE TABLE "bookmarks" ("id" INTEGER PRIMARY KEY AUTOINCREMENT,"url" TEXT NOT NULL,"description" TEXT NOT NULL,"extended" TEXT DEFAULT "","tags" TEXT DEFAULT "","created" TEXT NOT NULL,"modified" TEXT NOT NULL,"hash" TEXT NOT NULL,"shared" TEXT NOT NULL DEFAULT "yes");CREATE INDEX "created" ON "bookmarks" ("created");CREATE INDEX "modified" ON "bookmarks" ("modified");CREATE INDEX "shared" ON "bookmarks" ("shared");CREATE UNIQUE INDEX "hash" ON "bookmarks" ("hash");CREATE TABLE "api_keys" ("id" INTEGER PRIMARY KEY AUTOINCREMENT,"key1" TEXT NOT NULL,"description" TEXT DEFAULT "","access_level" TEXT NOT NULL DEFAULT "1","enabled" TEXT NOT NULL DEFAULT "no","tags_whitelist" TEXT DEFAULT "");CREATE INDEX "enabled" ON "api_keys" ("enabled");CREATE UNIQUE INDEX "key1" ON "api_keys" ("key1");');break;}$schema=array_filter($schema,'trim');$schema=array_filter($schema,'strlen');$schema=array_map(function ($value){return str_replace( ['CREATE TABLE `bookmarks`','CREATE TABLE "bookmarks"','CREATE TABLE `api_keys`','CREATE TABLE "api_keys"'],['CREATE TABLE `'.$this->salad->config->get('db_bookmarks_table_name').'`','CREATE TABLE "'.$this->salad->config->get('db_bookmarks_table_name').'"','CREATE TABLE `'.$this->salad->config->get('db_api_keys_table_name').'`','CREATE TABLE "'.$this->salad->config->get('db_api_keys_table_name').'"'],$value );},$schema);foreach($schema as $query){$this->dbh->exec($query);}} private function tableExists($table){try{$this->dbh->query('SELECT 1 FROM '.$table);}catch (\PDOException $e){return false;}return true;}private function insertExampleBookmarks(){$bookmarks=json_decode('[ { "url": "https://github.com/notwaldorf/dear-sir-or-madam", "description": "notwaldorf/dear-sir-or-madam: 💌 Bookmarklet that ransomifies your internets", "tags": "javascript bookmarklet github color" }, { "url": "http://www.srehttp.org/apps/gif_text/mkgiftxt.htm", "description": "GIF_TEXT: A Graphics Text Generator", "tags": "image generator gif" }, { "url": "https://browsers.evolt.org/", "description": "evolt.org - Browser Archive", "tags": "browsers archive javascript" }, { "url": "https://www.mercurytheatre.info/", "description": "The Mercury Theatre on the Air", "tags": "audio media radio" }, { "url": "color:FFFFFF,81C757,333333,D3D2D2,0000FF,DDDDDD,FF0000", "description": "Salad color scheme", "tags": "color swatches" }, { "url": "https://web.archive.org/web/20050207095344/http://home.earthlink.net/~sarasohn/aboutgs.html", "description": "Father Guido Sarducci", "tags": "media comedy tv" }]',true);$queries=[];$url='';$tags='';foreach($bookmarks as $link){if(!empty($link['post'])){$link=$link['post'];}if(!empty($link['url'])){$url=$link['url'];}elseif(!empty($link['href'])){$url=$link['href'];}if(!empty($link['tag'])){$tags=$link['tag'];}elseif(!empty($link['tags'])){$tags=$link['tags'];}if(!empty($link['time'])){$dt=strtotime($link['time']);}else{$dt=strtotime('now');}$url2=$url;$queries[]=['sql'=>'INSERT INTO '.$this->salad->config->get('db_bookmarks_table_name').' (url,description,extended,tags,created,modified,hash,shared) VALUES (:url,:description,:extended,:tags,:created,:modified,:hash,:shared)','params'=>['url'=>$url2,'description'=>@$link['description'],'extended'=>@$link['extended'],'tags'=>' '.$tags.' ','created'=>$dt,'modified'=>$dt,'hash'=>md5($url),'shared'=>'yes']];}$this->exec($queries);}private function insertExampleAPIKeys(){$queries=[];$queries[]=['sql'=>'INSERT INTO '.$this->salad->config->get('db_api_keys_table_name').' (key1,description,access_level,enabled) VALUES (:key1,:description,:access_level,:enabled)','params'=>['key1'=>'1234','description'=>'key1','access_level'=>'1','enabled'=>'no']];$this->exec($queries);}} class Main{public $config;public $auth;public $db;public $cache;public $request;public $response;public $utils;public $sqlUtils;public $view;public $models;public $ext;public function __construct($api_file_included=false){$this->utils=new \Salad\Utils($this);$this->config=new \Salad\Config($this);$this->auth=new \Salad\Auth($this);$this->db=new \Salad\DB($this);$this->cache=new \Salad\Cache($this);$this->request=new \Salad\Request($this);$this->response=new \Salad\Response($this);$this->sqlUtils=new \Salad\SQLUtils($this);$this->view=new \Salad\View($this);if($api_file_included===true){$this->request->setParam('file_api_included',true);}} public function registerMyObjects($objects=[]){if(!empty($objects['models'])){$this->models=new \stdClass();foreach($objects['models']['classes'] as $modelClass){$class=$objects['models']['namespace'].'\\'.ucfirst($modelClass);$this->models->$modelClass=new $class($this);}} if(!empty($objects['ext'])){$this->ext=new \stdClass();foreach($objects['ext']['classes'] as $extClass){$class=$objects['ext']['namespace'].'\\'.ucfirst($extClass);$this->ext->$extClass=new $class($this);}}}} class Request{protected $salad;public function __construct($salad){$this->salad=$salad;$this->setParams();}private array $params=['uri'=>'/','base_uri'=>'/','format'=>'json','is_cli'=>false,'file_api_included'=>false,'callback'=>'','context'=>''];protected function setParams(){switch(PHP_SAPI){case 'cli':$this->setParam('is_cli',true);$this->setCliParams();break;default:self::setWebParams();break;}$this->setParam('callback',$this->salad->utils->safeForJSONP(''.@$_GET['callback']));$this->setParam('context',$this->salad->utils->safeForJSONP(''.@$_GET['context']));}private function setCliParams(){global $argv;if(count($argv)<2){die("Usage:php [key=value]...\n");}$url=$argv[1];$this->setParam('uri',$url);for ($i=2;$isetParam('base_uri','/');}private function setWebParams(){$uri=rtrim(filter_var(@$_SERVER['REQUEST_URI'],FILTER_SANITIZE_URL),'/').'/';$pattern='/^\/[^\/]*\.php/';$uri=preg_replace($pattern,'',$uri);if(strpos($uri,'all?hashes')===false){$uri=preg_replace('/\?(.*)$/','',$uri);}$uri=rtrim($uri,'/');if(empty($uri)){$uri='/';}$this->setUri($uri,preg_replace("/\/[^\/]+$/",'/',@$_SERVER['SCRIPT_NAME']));}private function setFileIncludedParams($options){preg_match('/(.*)\?(.*)/',$options['uri'],$urlMatches);$this->setUri($urlMatches[1],@$options['base_uri']);parse_str($urlMatches[2],$urlMatches);$_GET=array_merge($_GET,$urlMatches);if(!empty($_GET['format'])){$this->setParam('format',$_GET['format']);}} public function setParam($key,$val){$this->params[$key]=$val;}public function getParam($key,$default=''){return !empty($this->params[$key])?$this->params[$key]:$default;}public function setUri($uri='',$baseUri=''){if(!empty($uri)){$this->setParam('uri',$uri);if(!empty($baseUri)){$this->setParam('base_uri',$baseUri);}}}public function handle($options=[]){if(!empty($options['uri']) && $this->salad->request->getParam('file_api_included')===true){$this->setFileIncludedParams($options);}$router=new \Salad\Router($this->salad);$selectedRoute=$router->findRoute();$this->salad->response->setOutputData(call_user_func($selectedRoute[0],$selectedRoute[1],$selectedRoute[2]));}private string $fullBaseUri='';public function getFullBaseUri(){if(!empty($this->fullBaseUri)){return $this->fullBaseUri;}$fullBaseUri='';$port='';if(!empty($_SERVER['HTTP_HOST'])){switch($_SERVER['SERVER_PORT']){case '80':case '443':break;default:$port=':'.$_SERVER['SERVER_PORT'];break;}$fullBaseUri .= $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].$port;}$fullBaseUri .= $this->getParam('base_uri');$this->fullBaseUri=$fullBaseUri;return $this->fullBaseUri;}} -class Response{private array $outputData=[];private $salad;public function __construct($salad){$this->salad=$salad;}public function setOutputData($outputData){$outputData=$outputData===null?[]:$outputData;$this->outputData=$outputData;}public function getOutput($setHeaders=false){if($setHeaders===true){$this->setHeaders($this->salad->request->getParam('format'));}return $this->generateOutput($this->salad->request->getParam('format'));}public function setHeaders($format){if($this->salad->request->getParam('is_cli')===true || $this->salad->request->getParam('api_self_included')===true){return;}$this->setCorsHeaders();$this->setExtraHeaders();$this->setContentTypeHeader($format);}private function setContentTypeHeader($format){$contentType='';switch($format){case 'xml':$contentType='text/xml;charset=utf-8';break;case 'json':case 'js':$contentType='text/javascript;charset=utf-8';break;case 'css':$contentType='text/css;charset=utf-8';break;case 'webmanifest':$contentType='application/manifest+json;charset=utf-8';break;case 'html':case 'html-partial':$contentType='text/html;charset=utf-8';break;case 'png':$contentType='image/png';break;case 'svg':$contentType='image/svg+xml';break;case 'text':case 'txt':default:$contentType='text/plain;charset=utf-8';break;}header('Content-type:'.$contentType);}private function setCorsHeaders(){if($this->salad->config->get('enable_cors_headers')===true){header('Access-Control-Allow-Origin:*',true);header('Access-Control-Allow-Methods:GET,PUT,POST,DELETE,OPTIONS,post,get',true);header('Access-Control-Allow-Headers:Origin,Content-Type,X-Auth-Token,Authorization,X-Api-Key',true);header("Access-Control-Allow-Credentials",true);}} private function setExtraHeaders(){header('Referrer-Policy:no-referrer',true);header('X-Content-Type-Options:nosniff',true);}private function generateOutput($format){$data='';switch($format){case 'xml':default:$data=$this->generateXML();break;case 'json':$data=$this->generateJSON();$cb=$this->salad->request->getParam('callback');$cnx=$this->salad->request->getParam('context');if(!empty($cb)){$data=$cb.'('.$data;if(!empty($cnx)){$data .= ',"'.$cnx.'"';}$data .= ');';}break;case 'html':$data=$this->generateHTML(true);break;case 'html-partial':$data=$this->generateHTML(false);break;case 'css':case 'js':case 'webmanifest':case 'png':case 'svg':case 'text':case 'txt':$data=$this->generateText();break;}return $data;}private function generateXML(){if(empty($this->outputData['xml'])){$xml='';}else{$xml=$this->outputData['xml'];}$xml=new \SimpleXMLElement($xml);return $xml->asXML();}private function generateHTML($fullHTML=false){if($fullHTML===false){$partialHTML=@$this->outputData['html']['body'];if($this->salad->config->get('minify_html_output')===true){$partialHTML=$this->salad->utils->minifyOutput(['body'=>$partialHTML])['body'];}return $partialHTML;}else{$htmlSkeleton=''.trim(''.@$this->outputData['html']['title']).'';if($this->salad->config->get('minify_html_output')===true){$htmlSkeleton=$this->salad->utils->minifyOutput(['body'=>$htmlSkeleton])['body'];}if(!empty($this->outputData['html']['head'])){if($this->salad->config->get('minify_html_output')===true){$this->outputData['html']['head']=$this->salad->utils->minifyOutput(['head'=>$this->outputData['html']['head']])['head'];}$htmlSkeleton .= $this->outputData['html']['head'];}if(!empty($this->outputData['html']['css'])){$htmlSkeleton .= '';}$htmlSkeleton .= '';if(!empty($this->outputData['html']['body'])){if($this->salad->config->get('minify_html_output')===true){$this->outputData['html']['body']=$this->salad->utils->minifyOutput(['body'=>$this->outputData['html']['body']])['body'];}$htmlSkeleton .= $this->outputData['html']['body'];}if(!empty($this->outputData['html']['js'])){$htmlSkeleton .= '';}$htmlSkeleton .= '';return $htmlSkeleton;}} private function generateText(){if(!empty($this->outputData['bin-data'])){return $this->outputData['bin-data'];}$text=$this->outputData['text'];if($this->salad->config->get('minify_html_output')===true){$text=$this->salad->utils->minifyOutput(['body'=>$text])['body'];}return $text;}private function generateJSON(){return isset($this->outputData['array'])?json_encode($this->outputData['array']):$this->xmlToJson(@$this->outputData['xml']);}private string $nonce='';public function nonce(){if(!empty($this->nonce)){return $this->nonce;}$this->nonce=bin2hex(random_bytes(10));return $this->nonce;}public function debug($obj,$type="pr"){switch($type){case 'pr':default:header('content-type:text/plain;');print_r($obj);break;case 'vd':header('content-type:text/html;');var_dump($obj);break;}die();}public function debugF($context,$data){if(!is_array($data)){$data=[$data];}$data=json_encode($data,JSON_PRETTY_PRINT);$txt="\n".date('g:i:s A',strtotime('now')).":".$data."\n";file_put_contents($this->salad->config->get('cache_path').'/'.$context.'.txt',$txt,FILE_APPEND | LOCK_EX);}public function generateErrorMessage($type,$message=''){$message=!empty($message)?$message:'something went wrong';return $this->generateGenericMessage($type,$message);}public function generateDoneMessage($type,$message=''){$message=!empty($message)?$message:'done';return $this->generateGenericMessage($type,$message,true);}public function generateAuthFailedMessage(){return $this->generateErrorMessage('for:posts','Authentication Failed');}public function generateNotFoundMessage(){return $this->generateErrorMessage('for:posts','404 Not Found');}private function generateGenericMessage($type='for:tags',$message='',$isPositive=false){switch($type){case 'for:posts':$xml='';$array=['result'=>['code'=>$message]];break;case 'for:tags':default:$xml=''.$message.'';$array=['result'=>$message];break;}if($isPositive===true){$html=['title'=>$message,'body'=>$message,];}else{$sadKitty='data:image/jpeg;base64,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';$html=['title'=>$message,'body'=>'
'.$message.'
'.$message.'
','css'=>'body {background-color:#000000;} figure {margin:0;padding:0;text-align:left;} figcaption{ text-align:center;width:191px;color:aqua;}'];}return ['xml'=>$xml,'array'=>$array,'html'=>$html];}private function xmlToJson($xmlContent){if(!$xmlContent){$xml=['result'=>['code'=>'no data for this format']];}else{$xml=new \SimpleXMLElement($xmlContent);}return json_encode($xml);}public function redirect($url){header('Location:'.$url);exit(0);}} +class Response{private array $outputData=[];private $salad;public function __construct($salad){$this->salad=$salad;}public function setOutputData($outputData){$outputData=$outputData===null?[]:$outputData;$this->outputData=$outputData;}public function getOutput($setHeaders=false){if($setHeaders===true){$this->setHeaders($this->salad->request->getParam('format'));}return $this->generateOutput($this->salad->request->getParam('format'));}public function setHeaders($format){if($this->salad->request->getParam('is_cli')===true || $this->salad->request->getParam('api_self_included')===true){return;}$this->setCorsHeaders();$this->setExtraHeaders();$this->setContentTypeHeader($format);}private function setContentTypeHeader($format){$contentType='';switch($format){case 'xml':$contentType='text/xml;charset=utf-8';break;case 'json':case 'js':$contentType='text/javascript;charset=utf-8';break;case 'css':$contentType='text/css;charset=utf-8';break;case 'webmanifest':$contentType='application/manifest+json;charset=utf-8';break;case 'html':case 'html-partial':$contentType='text/html;charset=utf-8';break;case 'png':$contentType='image/png';break;case 'svg':$contentType='image/svg+xml';break;case 'text':case 'txt':default:$contentType='text/plain;charset=utf-8';break;}header('Content-type:'.$contentType);}private function setCorsHeaders(){if($this->salad->config->get('enable_cors_headers')===true){header('Access-Control-Allow-Origin:*',true);header('Access-Control-Allow-Methods:GET,PUT,POST,DELETE,OPTIONS',true);header('Access-Control-Allow-Headers:Origin,Content-Type,X-Auth-Token,Authorization,X-Api-Key',true);}} private function setExtraHeaders(){header('Referrer-Policy:no-referrer',true);header('X-Content-Type-Options:nosniff',true);}private function generateOutput($format){$data='';switch($format){case 'xml':default:$data=$this->generateXML();break;case 'json':$data=$this->generateJSON();$cb=$this->salad->request->getParam('callback');$cnx=$this->salad->request->getParam('context');if(!empty($cb)){$data=$cb.'('.$data;if(!empty($cnx)){$data .= ',"'.$cnx.'"';}$data .= ');';}break;case 'html':$data=$this->generateHTML(true);break;case 'html-partial':$data=$this->generateHTML(false);break;case 'css':case 'js':case 'webmanifest':case 'png':case 'svg':case 'text':case 'txt':$data=$this->generateText();break;}return $data;}private function generateXML(){if(empty($this->outputData['xml'])){$xml='';}else{$xml=$this->outputData['xml'];}$xml=new \SimpleXMLElement($xml);return $xml->asXML();}private function generateHTML($fullHTML=false){if($fullHTML===false){$partialHTML=@$this->outputData['html']['body'];if($this->salad->config->get('minify_html_output')===true){$partialHTML=$this->salad->utils->minifyOutput(['body'=>$partialHTML])['body'];}return $partialHTML;}else{$htmlSkeleton=''.trim(''.@$this->outputData['html']['title']).'';if($this->salad->config->get('minify_html_output')===true){$htmlSkeleton=$this->salad->utils->minifyOutput(['body'=>$htmlSkeleton])['body'];}if(!empty($this->outputData['html']['head'])){if($this->salad->config->get('minify_html_output')===true){$this->outputData['html']['head']=$this->salad->utils->minifyOutput(['head'=>$this->outputData['html']['head']])['head'];}$htmlSkeleton .= $this->outputData['html']['head'];}if(!empty($this->outputData['html']['css'])){$htmlSkeleton .= '';}$htmlSkeleton .= '';if(!empty($this->outputData['html']['body'])){if($this->salad->config->get('minify_html_output')===true){$this->outputData['html']['body']=$this->salad->utils->minifyOutput(['body'=>$this->outputData['html']['body']])['body'];}$htmlSkeleton .= $this->outputData['html']['body'];}if(!empty($this->outputData['html']['js'])){$htmlSkeleton .= '';}$htmlSkeleton .= '';return $htmlSkeleton;}} private function generateText(){if(!empty($this->outputData['bin-data'])){return $this->outputData['bin-data'];}$text=$this->outputData['text'];if($this->salad->config->get('minify_html_output')===true){$text=$this->salad->utils->minifyOutput(['body'=>$text])['body'];}return $text;}private function generateJSON(){return isset($this->outputData['array'])?json_encode($this->outputData['array']):$this->xmlToJson(@$this->outputData['xml']);}private string $nonce='';public function nonce(){if(!empty($this->nonce)){return $this->nonce;}$this->nonce=bin2hex(random_bytes(10));return $this->nonce;}public function debug($obj,$type="pr"){switch($type){case 'pr':default:header('content-type:text/plain;');print_r($obj);break;case 'vd':header('content-type:text/html;');var_dump($obj);break;}die();}public function debugF($context,$data){if(!is_array($data)){$data=[$data];}$data=json_encode($data,JSON_PRETTY_PRINT);$txt="\n".date('g:i:s A',strtotime('now')).":".$data."\n";file_put_contents($this->salad->config->get('cache_path').'/'.$context.'.txt',$txt,FILE_APPEND | LOCK_EX);}public function generateErrorMessage($type,$message=''){$message=!empty($message)?$message:'something went wrong';return $this->generateGenericMessage($type,$message);}public function generateDoneMessage($type,$message=''){$message=!empty($message)?$message:'done';return $this->generateGenericMessage($type,$message,true);}public function generateAuthFailedMessage(){return $this->generateErrorMessage('for:posts','Authentication Failed');}public function generateNotFoundMessage(){return $this->generateErrorMessage('for:posts','404 Not Found');}private function generateGenericMessage($type='for:tags',$message='',$isPositive=false){switch($type){case 'for:posts':$xml='';$array=['result'=>['code'=>$message]];break;case 'for:tags':default:$xml=''.$message.'';$array=['result'=>$message];break;}if($isPositive===true){$html=['title'=>$message,'body'=>$message,];}else{$sadKitty='data:image/jpeg;base64,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';$html=['title'=>$message,'body'=>'
'.$message.'
'.$message.'
','css'=>'body {background-color:#000000;} figure {margin:0;padding:0;text-align:left;} figcaption{ text-align:center;width:191px;color:aqua;}'];}return ['xml'=>$xml,'array'=>$array,'html'=>$html];}private function xmlToJson($xmlContent){if(!$xmlContent){$xml=['result'=>['code'=>'no data for this format']];}else{$xml=new \SimpleXMLElement($xmlContent);}return json_encode($xml);}public function redirect($url){header('Location:'.$url);exit(0);}} class Router{private $salad;public function __construct($salad){$this->salad=$salad;}private function setRouteAliases(){$routeAliases=[];foreach($this->salad->config->get('routes') as $cr){if(!empty($cr['aliases'])){$cr['aliases']=is_array($cr['aliases'])?$cr['aliases']:[$cr['aliases']];foreach($cr['aliases'] as $alias){$routeAliases[]=['uri'=>$alias,'func'=>$cr['func'],'format'=>@$cr['format'],'args'=>@$cr['args']];}}}$this->salad->config->set('routes',array_merge($routeAliases,$this->salad->config->get('routes')));}public function findRoute(){$requestedUri=$this->salad->request->getParam('uri');$baseUri=rtrim($this->salad->request->getParam('base_uri'),'/');if(empty($baseUri)){$baseUri='/';}if($this->salad->request->getParam('base_uri')!=='/'){$requestedUri=str_replace($baseUri,'',$requestedUri);}$requestedUri='/'.trim($requestedUri,'/');$this->setRouteAliases();$currentRoute=[];foreach($this->salad->config->get('routes') as $route){$pattern=$route['uri'];$args=[];if(preg_match("#^$pattern$#",$requestedUri,$matches)){array_shift($matches);$route['args']=!empty($route['args'])?$route['args']:[];$route['args']=array_filter($route['args'],fn ($value)=>$value!==null && $value!=='' && $value!=='undefined');foreach($route['args'] as $key=>$value){if(preg_match('/\$\d/',$value)){$index=intval(str_replace('$','',$value)) - 1;if(preg_match('/\$\d/',$key)){$k=intval(str_replace('$','',$key)) - 1;$args[$matches[$k]]=$this->salad->utils->urldecode($matches[$index]);}else{$args[$key]=$this->salad->utils->urldecode($matches[$index]);}} else{$args[$key]=$this->salad->utils->urldecode($value);}} foreach($_GET as $key=>$value){if(!isset($args[$key]) && !in_array($key,['access_level','access_level_needed','skip_auth','skip_url_check'])){$args[$key]=$this->salad->utils->urldecode($value);}} $currentRoute=[$route['func'],$this->salad,$args];$this->setRouteFormat(@$route['format']);break;}} if(empty($currentRoute)){$r=$this->salad->config->get('route_not_found');$this->setRouteFormat(@$r['format']);$currentRoute=[$r['func'],$this->salad,@$r['args']];}return $currentRoute;}private function setRouteFormat($format=''){if($this->salad->request->getParam('is_cli')===true){if(!empty($_GET['format'])){$this->salad->request->setParam('format',$_GET['format']);}} else{if(!empty($format)){$this->salad->request->setParam('format',$format);}}}} class SQLUtils{private $salad;public function __construct($salad){$this->salad=$salad;}public function buildWhereQueryForTags($tags=''){$where='';$data=[];$selected_tags=[];$tagsWhitelist=$this->salad->request->getParam('tags_whitelist');if(!empty($tags) || !empty($tagsWhitelist)){if(!empty($tagsWhitelist)){$tags .= ' '.$tagsWhitelist;}$tags=str_replace(',',' ',$tags);$tags=str_replace('+',' ',$tags);$tags_arr=array_unique(explode(' ',$tags));$i=0;foreach($tags_arr as $tag){$tag=trim($tag);if(!empty($tag)){$i++;$where .= ' tags LIKE :tag'.$i.' AND ';$data['tag'.$i]='% '.$tag.' %';$selected_tags[]=$tag;}} unset($i,$tag);}$where=rtrim($where,' AND ');return [$where,$data,$selected_tags];}public function buildWhereQueryForSearch($search=''){$where='';$data=[];if(!empty($search)){$search_arr=array_unique(explode(' ',$search));$where .= ' ( ';$search_columns=['url','description','extended'];$i=0;foreach($search_columns as $s_column){$where .= '( ';foreach($search_arr as $s){$i++;$where .= ' '.$s_column.' LIKE :'.$s_column.$i.' AND ';$data[$s_column.$i]='%'.$s.'%';}$where=rtrim($where,' AND ');$where .= ' ) OR ';}$where=rtrim($where,' OR ');$where .= ' ) ';}return [$where,$data];}public function buildWhereQueryForUrl($url){$where='';$data=[];if(!empty($url)){$where .= ' url=:url ';$data['url']=$url;}return [$where,$data];}public function buildWhereQueryForHashes($hashes=''){$where='';$data=[];if(!empty($hashes)){$hashes_arr=array_unique(explode(' ',$hashes));$i=0;foreach($hashes_arr as $hash){$hash=trim($hash);if(!empty($hash)){$i++;$where .= ' hash=:hashes'.$i.' OR ';$data['hashes'.$i]=$hash;}} unset($i,$hash);}$where=rtrim($where,' OR ');return [$where,$data];}public function buildWhereQueryForDate($dt=''){$where='';$data=[];if(!empty($dt)){$dtStart=strtotime(gmdate('d.m.Y 00:00:00',strtotime($dt)));$dtEnd=strtotime(gmdate('d.m.Y 23:59:59',strtotime($dt)));$where .= ' created BETWEEN "'.$dtStart.'" AND "'.$dtEnd.'" ';}return [$where,$data];}} -class Utils{private $salad;public function __construct($salad){$this->salad=$salad;}public function isValidUrl($url){if(!$url){return false;}$path=parse_url($url,PHP_URL_PATH);$encoded_path=array_map('urlencode',explode('/',$path));$url=str_replace($path,implode('/',$encoded_path),$url);return filter_var($url,FILTER_VALIDATE_URL)?true:false;}private function removeJsComments($jsString=''){$jsString=preg_replace('~(?%=&|!])\s*~','$1',$jsString);$jsString=preg_replace('~;+~',';',$jsString);$jsString=preg_replace('~\s+~',' ',$jsString);return $jsString;}private function removeCssComments($cssString=''){$cssString=preg_replace(['/\/\*[\s\S]*?\*\//','/\s*([\{\};:,\>])\s*/','/;}/','/\s+/'],['','$1','}',' '],$cssString);return trim($cssString);}private function removeHtmlComments($htmlString=''){$htmlString=preg_replace('//','',$htmlString);$htmlString=preg_replace('/\s+/',' ',$htmlString);$htmlString=preg_replace('/\s*([<>])\s*/','$1',$htmlString);$htmlString=str_replace(['" />'],['"/>'],$htmlString);return $htmlString;}public function minifyOutput($html){if(!empty($html['body'])){$html['body']=$this->removeHtmlComments($html['body']);}if(!empty($html['css'])){$html['css']=$this->removeCssComments($html['css']);}if(!empty($html['head'])){$html['head']=$this->removeHtmlComments($html['head']);}if(!empty($html['js'])){$html['js']=$this->removeJsComments($html['js']);}return $html;}private array $safeHTMLStrings=[];public function safeForHTML($text=''){if(!empty($this->safeHTMLStrings[$text])){return $this->safeHTMLStrings[$text];}$this->safeHTMLStrings[$text]=htmlspecialchars($text,ENT_QUOTES,'UTF-8');return $this->safeHTMLStrings[$text];}private array $safeJSONPStrings=[];public function safeForJSONP($text=''){if(!empty($this->safeJSONPStrings[$text])){return $this->safeJSONPStrings[$text];}$this->safeJSONPStrings[$text]=preg_replace('/[^a-zA-Z0-9_\.-]/','',$text);return $this->safeJSONPStrings[$text];}public function strEndsWith(string $haystack,string $needle){$needle_len=strlen($needle);return ($needle_len===0 || 0===substr_compare($haystack,$needle,-$needle_len));}public function strStartsWith(string $haystack,string $needle){return strncmp($haystack,$needle,strlen($needle))===0;}private $executionTimes=[];public function startExecTime($key){$this->executionTimes[$key]['start']=microtime(true);}public function endExecTime($key){$executionTime=microtime(true) - $this->executionTimes[$key]['start'];$executionTime=round($executionTime,2);unset($this->executionTimes[$key]);return $executionTime;}public function arrayDiff2D($arr1,$arr2){$arr1Assoc=[];foreach($arr1 as $item){if(isset($item['tag']) && isset($item['count'])){$arr1Assoc[$item['tag']]=$item['count'];}} $arr2Assoc=[];foreach($arr2 as $item){if(isset($item['tag']) && isset($item['count'])){$arr2Assoc[$item['tag']]=$item['count'];}} $diff=[];foreach($arr1Assoc as $tag=>$count){if(!isset($arr2Assoc[$tag])){$diff[]=['tag'=>$tag,'count'=>$count,'status'=>'removed'];}elseif($arr2Assoc[$tag] != $count){$diff[]=['tag'=>$tag,'count'=>$count,'status'=>'modified'];}} foreach($arr2Assoc as $tag=>$count){if(!isset($arr1Assoc[$tag])){$diff[]=['tag'=>$tag,'count'=>$count,'status'=>'added'];}} return $diff;}public function urlDecode($url){$placeholder='__PLUS__';$placeholder2='__ENCODED_AMPERSAND__';$urlWithPlaceholder=str_replace('+',$placeholder,$url);$urlWithPlaceholder=str_replace('%26',$placeholder2,$url);$decodedUrl=urldecode($urlWithPlaceholder);$finalUrl=str_replace($placeholder,'+',$decodedUrl);$finalUrl=str_replace($placeholder2,'%26',$decodedUrl);return $finalUrl;}public function setCacheHeaders($days=1){$daysToCache=($days * 86400);$ts=gmdate("D,d M Y H:i:s",time() + $daysToCache)." GMT";header("Expires:$ts",true);header("Pragma:cache",true);header("Cache-Control:Max-Age=$daysToCache",true);}public function fetch($url,$type){$options=array( 'http'=>array( 'user_agent'=>'Mozilla/5.0 (Windows NT 10.0;Win64;x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/124.0.0.0 Safari/537.36' ),"ssl"=>array( "verify_peer"=>false,"verify_peer_name"=>false,),);$context=stream_context_create($options);$content=file_get_contents($url,false,$context);foreach($http_response_header as $c=>$h){if(stristr($h,'content-encoding') and stristr($h,'gzip')){$content=gzinflate(substr($content,10,-8));}} switch($type){case 'xml':$content=str_replace(["\0"," "],[""," "],$content);$simpleXml=simplexml_load_string($content,"SimpleXMLElement",LIBXML_NOCDATA);return $simpleXml;break;case 'text':default:return $content;break;}} } +class Utils{private $salad;public function __construct($salad){$this->salad=$salad;}public function isValidUrl($url){if(!$url){return false;}$path=parse_url($url,PHP_URL_PATH);$encoded_path=array_map('urlencode',explode('/',$path));$url=str_replace($path,implode('/',$encoded_path),$url);return filter_var($url,FILTER_VALIDATE_URL)?true:false;}private function removeJsComments($jsString=''){$jsString=preg_replace('~(?%=&|!])\s*~','$1',$jsString);$jsString=preg_replace('~;+~',';',$jsString);$jsString=preg_replace('~\s+~',' ',$jsString);return $jsString;}private function removeCssComments($cssString=''){$cssString=preg_replace(['/\/\*[\s\S]*?\*\//','/\s*([\{\};:,\>])\s*/','/;}/','/\s+/'],['','$1','}',' '],$cssString);return trim($cssString);}private function removeHtmlComments($htmlString=''){$htmlString=preg_replace('//','',$htmlString);$htmlString=preg_replace('/\s+/',' ',$htmlString);$htmlString=preg_replace('/\s*([<>])\s*/','$1',$htmlString);$htmlString=str_replace(['" />'],['"/>'],$htmlString);return $htmlString;}public function minifyOutput($html){if(!empty($html['body'])){$html['body']=$this->removeHtmlComments($html['body']);}if(!empty($html['css'])){$html['css']=$this->removeCssComments($html['css']);}if(!empty($html['head'])){$html['head']=$this->removeHtmlComments($html['head']);}if(!empty($html['js'])){$html['js']=$this->removeJsComments($html['js']);}return $html;}private array $safeHTMLStrings=[];public function safeForHTML($text=''){if(!empty($this->safeHTMLStrings[$text])){return $this->safeHTMLStrings[$text];}$this->safeHTMLStrings[$text]=htmlspecialchars($text,ENT_QUOTES,'UTF-8');return $this->safeHTMLStrings[$text];}private array $safeJSONPStrings=[];public function safeForJSONP($text=''){if(!empty($this->safeJSONPStrings[$text])){return $this->safeJSONPStrings[$text];}$this->safeJSONPStrings[$text]=preg_replace('/[^a-zA-Z0-9_\.-]/','',$text);return $this->safeJSONPStrings[$text];}public function strEndsWith(string $haystack,string $needle){$needle_len=strlen($needle);return ($needle_len===0 || 0===substr_compare($haystack,$needle,-$needle_len));}public function strStartsWith(string $haystack,string $needle){return strncmp($haystack,$needle,strlen($needle))===0;}private $executionTimes=[];public function startExecTime($key){$this->executionTimes[$key]['start']=microtime(true);}public function endExecTime($key){$executionTime=microtime(true) - $this->executionTimes[$key]['start'];$executionTime=round($executionTime,2);unset($this->executionTimes[$key]);return $executionTime;}public function arrayDiff2D($arr1,$arr2){$arr1Assoc=[];foreach($arr1 as $item){if(isset($item['tag']) && isset($item['count'])){$arr1Assoc[$item['tag']]=$item['count'];}} $arr2Assoc=[];foreach($arr2 as $item){if(isset($item['tag']) && isset($item['count'])){$arr2Assoc[$item['tag']]=$item['count'];}} $diff=[];foreach($arr1Assoc as $tag=>$count){if(!isset($arr2Assoc[$tag])){$diff[]=['tag'=>$tag,'count'=>$count,'status'=>'removed'];}elseif($arr2Assoc[$tag] != $count){$diff[]=['tag'=>$tag,'count'=>$count,'status'=>'modified'];}} foreach($arr2Assoc as $tag=>$count){if(!isset($arr1Assoc[$tag])){$diff[]=['tag'=>$tag,'count'=>$count,'status'=>'added'];}} return $diff;}public function urlDecode($url){$placeholder='__PLUS__';$placeholder2='__ENCODED_AMPERSAND__';$urlWithPlaceholder=str_replace('+',$placeholder,$url);$urlWithPlaceholder=str_replace('%26',$placeholder2,$url);$decodedUrl=urldecode($urlWithPlaceholder);$finalUrl=str_replace($placeholder,'+',$decodedUrl);$finalUrl=str_replace($placeholder2,'%26',$decodedUrl);return $finalUrl;}public function setCacheHeaders($days=1){$secondsToCache=$days * 24 * 60 * 60;header('Pragma:cache');header('Cache-Control:max-age='.$secondsToCache,true);}public function fetch($url,$type){$options=array( 'http'=>array( 'user_agent'=>'Mozilla/5.0 (Windows NT 10.0;Win64;x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/124.0.0.0 Safari/537.36' ),"ssl"=>array( "verify_peer"=>false,"verify_peer_name"=>false,),);$context=stream_context_create($options);$content=file_get_contents($url,false,$context);foreach($http_response_header as $c=>$h){if(stristr($h,'content-encoding') and stristr($h,'gzip')){$content=gzinflate(substr($content,10,-8));}} switch($type){case 'xml':$content=str_replace(["\0"," "],[""," "],$content);$simpleXml=simplexml_load_string($content,"SimpleXMLElement",LIBXML_NOCDATA);return $simpleXml;break;case 'text':default:return $content;break;}} } class View{private $salad;public string $nonce='';public array $content=['html'=>['css'=>'','js'=>'','title'=>'','head'=>'','body'=>'']];public \stdClass $data;private string $tpl='';public function __construct($salad){$this->salad=$salad;$this->nonce=$this->salad->response->nonce();}public function render($tpl,$data=[]){if(!empty($data)){$this->data=new \stdClass();foreach($data as $dK=>$dV){$this->data->$dK=$dV;}} $this->tpl=$tpl;unset($data);ob_start();include('./themes/'.$this->salad->config->get('ui_theme','ui1').'/'.$this->tpl.'.php');$this->content['html']['body']=ob_get_clean();return $this->content;}public function addCss($file){if(is_file($file)){$file=file_get_contents($file);}$file=str_replace('$___FULL_BASE_URI___$',$this->salad->request->getFullBaseUri(),$file);$this->content['html']['css'] .= $file;}public function addJs($file){if(is_file($file)){$file=file_get_contents($file);}$file=str_replace('$___FULL_BASE_URI___$',$this->salad->request->getFullBaseUri(),$file);$this->content['html']['js'] .= $file;}public function addHtmlTitle($text){$this->content['html']['title']=$text;}public function addHtmlHead($head){if(is_file($head)){$head=file_get_contents($head);}$head=str_replace('$___FULL_BASE_URI___$',$this->salad->request->getFullBaseUri(),$head);$this->content['html']['head']=$head;}} } namespace Salad\API\Controllers{ diff --git a/wasmer.toml b/wasmer.toml new file mode 100644 index 0000000..ee1097e --- /dev/null +++ b/wasmer.toml @@ -0,0 +1,13 @@ +[dependencies] +"php/php" = "=8.3.4" + +[fs] +"/server" = "server" + +[[command]] +name = "run" +module = "php/php:php" +runner = "wasi" +[command.annotations.wasi] +main-args = ["-t", "/server", "-S", "localhost:8080"] +