├── .gitignore ├── README.md ├── composer.json └── src └── CTFDBBuilder ├── Adapters ├── BaseAdapter.php ├── MysqlAdapter.php ├── PgsqlAdapter.php └── SqliteAdapter.php ├── Connection.php └── QueryBuilder.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | vendor/ 4 | composer.lock 5 | composer.phar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DB Builder for CTFer 2 | 3 | 一个无任何防护的PHP数据库Builder,支持Mysql/Postgresql/Sqlite。 4 | 5 | ## Install 6 | 7 | ``` 8 | composer require phith0n/ctfdbbuilder:dev-master 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```php 14 | 'mysql', // Db driver 19 | 'host' => 'localhost', 20 | 'database' => 'your-database', 21 | 'username' => 'root', 22 | 'password' => 'your-password', 23 | 'charset' => 'utf8mb4', // Optional 24 | 'options' => [ // PDO constructor options, optional 25 | \PDO::ATTR_TIMEOUT => 5, 26 | \PDO::ATTR_EMULATE_PREPARES => false, 27 | ], 28 | ]); 29 | $builder = $connect->getBuilder(); 30 | ``` 31 | 32 | ### Select (SQL injection) 33 | 34 | 35 | ```php 36 | table('articles')->where('id', '=', $_GET['id'])->first(); 38 | ``` 39 | 40 | ```php 41 | table('users')->where('age', '>', $_GET['age'])->first(); 43 | ``` 44 | 45 | ```php 46 | table('users')->orderBy('age', 'desc')->get(); 48 | ``` 49 | 50 | ```php 51 | table('users')->select('COUNT() AS `cnt`')->first(); 53 | ``` 54 | 55 | ```php 56 | table('users')->where('username', $_POST['username'])->where('password', md5($_POST['password']))->first(); 58 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phith0n/ctfdbbuilder", 3 | "description": "a database query builder for CTFer", 4 | "type": "library", 5 | "homepage": "https://github.com/phith0n/ctfdbbuilder", 6 | "keywords": [ 7 | "ctf", 8 | "query builder", 9 | "sql", 10 | "database", 11 | "mysql", 12 | "postgresql", 13 | "sqlite" 14 | ], 15 | "require": { 16 | "php": ">=7.0", 17 | "pimple/pimple": "^3.2" 18 | }, 19 | "license": "MIT", 20 | "authors": [ 21 | { 22 | "name": "phith0n", 23 | "email": "root@leavesongs.com" 24 | } 25 | ], 26 | "autoload": { 27 | "psr-4": {"CTFDBBuilder\\": "src/CTFDBBuilder"} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/CTFDBBuilder/Adapters/BaseAdapter.php: -------------------------------------------------------------------------------- 1 | connect($config); 32 | 33 | $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 34 | } 35 | 36 | /** 37 | * connect to database 38 | * 39 | * @param array $config 40 | * @return null 41 | */ 42 | abstract protected function connect(array $config); 43 | 44 | /** 45 | * execute a sql query string 46 | * 47 | * @param $sql 48 | * @return \PDOStatement 49 | */ 50 | public function query($sql) 51 | { 52 | return $this->pdo->query($sql); 53 | } 54 | 55 | public function escape($string) 56 | { 57 | return $this->quote . $string . $this->quote; 58 | } 59 | 60 | public function escapeField($field) 61 | { 62 | return $this->sanitizer . $field . $this->sanitizer; 63 | } 64 | } -------------------------------------------------------------------------------- /src/CTFDBBuilder/Adapters/MysqlAdapter.php: -------------------------------------------------------------------------------- 1 | pdo = new \PDO($connectionString, $config['username'], $config['password'], $config['options']); 35 | 36 | if (isset($config['charset'])) { 37 | $this->pdo->prepare("SET NAMES '{$config['charset']}'")->execute(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/CTFDBBuilder/Adapters/PgsqlAdapter.php: -------------------------------------------------------------------------------- 1 | pdo = new \PDO($connectionString, $config['username'], $config['password'], $config['options']); 27 | 28 | if (isset($config['charset'])) { 29 | $this->pdo->prepare("SET NAMES '{$config['charset']}'")->execute(); 30 | } 31 | 32 | if (isset($config['schema'])) { 33 | $this->pdo->prepare("SET search_path TO '{$config['schema']}'")->execute(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/CTFDBBuilder/Adapters/SqliteAdapter.php: -------------------------------------------------------------------------------- 1 | pdo = new \PDO($connectionString, null, null, $config['options']); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CTFDBBuilder/Connection.php: -------------------------------------------------------------------------------- 1 | container = $container ? $container : new Container(); 34 | 35 | $this->connect($adapter, $adapterConfig); 36 | } 37 | 38 | protected function connect($adapter, $config) 39 | { 40 | $this->container['adapter'] = function (Container $c) use ($adapter, $config) { 41 | $adapter = '\\CTFDBBuilder\\Adapters\\' . ucfirst(strtolower($adapter)) . 'Adapter'; 42 | return new $adapter($config); 43 | }; 44 | 45 | $this->container['builder'] = $this->container->factory(function(Container $c) { 46 | return new QueryBuilder($this); 47 | }); 48 | } 49 | 50 | public function getAdapter() 51 | { 52 | return $this->container['adapter']; 53 | } 54 | 55 | public function getBuilder() 56 | { 57 | return $this->container['builder']; 58 | } 59 | 60 | static public function newBuilder(...$args) 61 | { 62 | if (!self::$storedConnection) { 63 | self::$storedConnection = new self(...$args); 64 | } 65 | 66 | return self::$storedConnection->getBuilder(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/CTFDBBuilder/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | connect = $connect; 33 | $this->adapter = $this->connect->getAdapter(); 34 | } 35 | 36 | protected function addStatement($key, $value, $overwrite = false) 37 | { 38 | $value = array($value); 39 | 40 | if ($overwrite || !array_key_exists($key, $this->statements)) { 41 | $this->statements[$key] = $value; 42 | } else { 43 | $this->statements[$key] = array_merge($this->statements[$key], $value); 44 | } 45 | } 46 | 47 | private function _default_state_callback($state) 48 | { 49 | return " $state[0] "; 50 | } 51 | 52 | protected function getStatement($key, Callable $callback = null) 53 | { 54 | if (!$callback) { 55 | $callback = [$this, '_default_state_callback']; 56 | } 57 | 58 | if (array_key_exists($key, $this->statements) && $this->statements[$key]) { 59 | return call_user_func($callback, $this->statements[$key]); 60 | } else { 61 | return ''; 62 | } 63 | } 64 | 65 | protected function prepareString($value) 66 | { 67 | if (is_null($value)) { 68 | return 'null'; 69 | } elseif (is_string($value)) { 70 | return $this->adapter->escape($value); 71 | } else { 72 | return strval($value); 73 | } 74 | } 75 | 76 | protected function escapeField($field) 77 | { 78 | if (preg_match('#\s|`|"|\'|\*|\(|\)#', $field)) { 79 | return $field; 80 | } else { 81 | return $this->adapter->escapeField($field); 82 | } 83 | } 84 | 85 | public function where(...$args) 86 | { 87 | $this->addStatement('where', $this->Q(...$args)); 88 | return $this; 89 | } 90 | 91 | public function select($fields) 92 | { 93 | $this->addStatement('select', $this->escapeField($fields), true); 94 | return $this; 95 | } 96 | 97 | public function from($table) 98 | { 99 | return $this->table($table); 100 | } 101 | 102 | public function table($table) 103 | { 104 | $this->addStatement('table', $this->escapeField($table), true); 105 | return $this; 106 | } 107 | 108 | public function join($sql) 109 | { 110 | $this->addStatement('join', $this->escapeField($sql), true); 111 | return $this; 112 | } 113 | 114 | public function groupBy($field) 115 | { 116 | $this->addStatement('groupBy', $this->escapeField($field)); 117 | return $this; 118 | } 119 | 120 | public function having($sql) 121 | { 122 | $this->addStatement('having', $this->escapeField($sql), true); 123 | return $this; 124 | } 125 | 126 | public function limit($number) 127 | { 128 | $this->addStatement('limit', $number, true); 129 | return $this; 130 | } 131 | 132 | public function offset($number) 133 | { 134 | $this->addStatement('offset', $number, true); 135 | return $this; 136 | } 137 | 138 | public function orderBy($field, $direction = null) 139 | { 140 | if (func_num_args() == 1) { 141 | $this->addStatement('orderBy', $this->escapeField($field)); 142 | } else { 143 | $this->addStatement('orderBy', "{$this->escapeField($field)} {$direction}"); 144 | } 145 | return $this; 146 | } 147 | 148 | public function set($key, $value = null) 149 | { 150 | if (is_array($key)) { 151 | foreach ($key as $_k => $_v) { 152 | $this->addStatement('set', [$this->escapeField($_k), $this->prepareString($_v)]); 153 | } 154 | } else { 155 | $this->addStatement('set', [$this->escapeField($key), $this->prepareString($value)]); 156 | } 157 | return $this; 158 | } 159 | 160 | public function query($sql) 161 | { 162 | return $this->execute($sql)->fetchAll(\PDO::FETCH_ASSOC); 163 | } 164 | 165 | public function execute($sql) 166 | { 167 | return $this->adapter->query($sql); 168 | } 169 | 170 | public function first() 171 | { 172 | $records = $this->limit(1)->get(); 173 | return empty($records) ? null : $records[0]; 174 | } 175 | 176 | public function get() 177 | { 178 | if (empty($this->statements['select'])) { 179 | $this->addStatement('select', '*', true); 180 | } 181 | 182 | $sql = "SELECT {$this->getStatement('select')} FROM {$this->getStatement('table')} {$this->getStatement('join')} "; 183 | 184 | $sql .= $this->getStatement('where', function ($state) { 185 | return ' WHERE ' . implode(" AND ", $state) . ' '; 186 | }); 187 | 188 | $sql .= $this->getStatement('groupBy', function($state) { 189 | return ' GROUP BY ' . implode(', ', $state) . ' '; 190 | }); 191 | $sql .= $this->getStatement('having'); 192 | $sql .= $this->getStatement('orderBy', function ($state) { 193 | return ' ORDER BY ' . implode(", ", $state) . ' '; 194 | }); 195 | 196 | $sql .= $this->getStatement('limit', function($state) { 197 | return " LIMIT {$state[0]} "; 198 | }); 199 | $sql .= $this->getStatement('offset', function($state) { 200 | return " OFFSET {$state[0]} "; 201 | }); 202 | 203 | return $this->query($sql); 204 | } 205 | 206 | public function update() 207 | { 208 | $sql = $this->getStatement('table', function ($state) { 209 | return "UPDATE {$state[0]} SET "; 210 | }); 211 | $sql .= $this->getStatement('set', function ($state) { 212 | $set = []; 213 | foreach ($state as $line) { 214 | $set[] = "{$line[0]} = {$line[1]}"; 215 | } 216 | 217 | return ' ' . implode(', ', $set) . ' '; 218 | }); 219 | $sql .= $this->getStatement('where', function ($state) { 220 | return ' WHERE ' . implode(" AND ", $state) . ' '; 221 | }); 222 | $sql .= $this->getStatement('limit', function($state) { 223 | return " LIMIT {$state[0]} "; 224 | }); 225 | 226 | return $this->execute($sql); 227 | } 228 | 229 | public function insert() 230 | { 231 | $sql = $this->getStatement('table', function ($state) { 232 | return "INSERT INTO {$state[0]} "; 233 | }); 234 | $sql .= $this->getStatement('set', function ($state) { 235 | $keys = []; 236 | $values = []; 237 | foreach ($state as $line) { 238 | $keys[] = $line[0]; 239 | $values[] = $line[1]; 240 | } 241 | 242 | return ' (' . implode(', ', $keys) . ') VALUES (' . implode(', ', $values) . ') '; 243 | }); 244 | 245 | return $this->execute($sql); 246 | } 247 | 248 | public function delete() 249 | { 250 | $sql = $this->getStatement('table', function ($state) { 251 | return "DELETE FROM {$state[0]} "; 252 | }); 253 | $sql .= $this->getStatement('where', function ($state) { 254 | return ' WHERE ' . implode(" AND ", $state) . ' '; 255 | }); 256 | return $this->execute($sql); 257 | } 258 | 259 | public function Q(...$args) 260 | { 261 | $argc = count($args); 262 | if($argc == 2) { 263 | return "{$this->escapeField($args[0])} = {$this->adapter->escape($args[1])}"; 264 | } elseif($argc == 3) { 265 | return "{$this->escapeField($args[0])} {$args[1]} {$this->adapter->escape($args[2])}"; 266 | } else { 267 | return $args[0]; 268 | } 269 | } 270 | } --------------------------------------------------------------------------------