├── .gitignore
├── .htaccess
├── @import
├── apis
│ ├── challenges
│ │ └── authentication.php
│ ├── error
│ │ └── error.php
│ ├── users
│ │ ├── settings.php
│ │ ├── sign-in.php
│ │ ├── sign-out.php
│ │ ├── sign-up.php
│ │ ├── verify-email.php
│ │ └── verify-name.php
│ └── wechall
│ │ ├── user-score.php
│ │ └── validate-mail.php
├── confs
│ ├── common.php
│ └── init.sql
├── init.php
└── views
│ ├── challenges
│ └── challenges.php
│ ├── common
│ ├── foot.php
│ └── head.php
│ ├── error
│ └── error.php
│ ├── home
│ └── home.php
│ ├── notifications
│ └── notifications.php
│ ├── scoreboard
│ └── scoreboard.php
│ ├── solves
│ └── solves.php
│ └── users
│ ├── profile.php
│ ├── settings.php
│ ├── sign-in.php
│ ├── sign-up.php
│ └── users.php
├── LICENSE
├── README.md
├── assets
├── fonts
│ ├── Play-Regular.ttf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
├── images
│ ├── brand.svg
│ ├── favicon.ico
│ ├── favicon.png
│ ├── forbidden.png
│ └── thumbnail.png
├── scripts
│ ├── bootstrap.min.js
│ ├── challenges.js
│ ├── common.js
│ ├── html5shiv.min.js
│ ├── jquery.min.js
│ ├── popper.min.js
│ ├── respond.min.js
│ └── users.js
└── styles
│ ├── bootstrap.min.css
│ ├── common.css
│ └── font-awesome.min.css
├── index.php
└── robots.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | .*.txt
2 | .*.db
3 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | Options -Indexes
2 | ServerSignature Off
3 |
4 | ErrorDocument 403 /index.php
5 | ErrorDocument 404 /index.php
6 |
7 | AddDefaultCharset utf-8
8 |
9 |
10 | RewriteEngine On
11 | RewriteBase /
12 |
13 | RewriteCond %{REQUEST_FILENAME} !-f
14 | RewriteCond %{REQUEST_FILENAME} !-d
15 | RewriteRule . /index.php [L]
16 |
17 | RewriteRule ^\@import - [F,NC]
18 |
19 |
--------------------------------------------------------------------------------
/@import/apis/challenges/authentication.php:
--------------------------------------------------------------------------------
1 | 'error'];
5 | goto tail;
6 | }
7 |
8 | if(is_use_recaptcha()){
9 | if(!isset($_POST['recaptcha-token']) || !is_valid_recaptcha_token($_POST['recaptcha-token'])){
10 | $res = ['result' => 'invalid_token'];
11 | goto tail;
12 | }
13 | }
14 |
15 | if(!Users::is_signed()){
16 | $res = ['result' => 'unsigned'];
17 | goto tail;
18 | }
19 |
20 | if(!Challenges::is_valid_chal_flag($_POST['flag']) || ($chal = Challenges::get_chal_by_chal_flag($_POST['flag'])) === false){
21 | $res = ['result' => 'invalid_flag'];
22 | goto tail;
23 | }
24 |
25 | if(Challenges::is_solved_chal($chal['chal_no'])){
26 | $res = ['result' => 'already_solved', 'chal_name' => $chal['chal_name'], 'chal_title' => $chal['chal_title']];
27 | goto tail;
28 | }
29 |
30 | if(!Challenges::do_solve_chal($chal['chal_no'], $chal['chal_score'])){
31 | $res = ['result' => 'error'];
32 | goto tail;
33 | }
34 |
35 | $res = ['result' => 'solved', 'chal_name' => $chal['chal_name'], 'chal_title' => $chal['chal_title'], 'chal_score' => $chal['chal_score']];
36 |
37 | tail:
38 | Templater::json($res);
--------------------------------------------------------------------------------
/@import/apis/error/error.php:
--------------------------------------------------------------------------------
1 | 'access_denied'];
8 | break;
9 | case 404:
10 | default:
11 | header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
12 | $res = ['result' => 'page_not_found'];
13 | break;
14 | }
15 |
16 | Templater::json($res);
17 |
--------------------------------------------------------------------------------
/@import/apis/users/settings.php:
--------------------------------------------------------------------------------
1 | 'error'];
4 | goto tail;
5 | }
6 |
7 | if(is_use_recaptcha()){
8 | if(!isset($_POST['recaptcha-token']) || !is_valid_recaptcha_token($_POST['recaptcha-token'])){
9 | $res = ['result' => 'invalid_token'];
10 | goto tail;
11 | }
12 | }
13 |
14 | if(!Users::is_signed()){
15 | $res = ['result' => 'not_signed'];
16 | goto tail;
17 | }
18 |
19 | if(!Users::is_valid_user_name($_POST['name'])){
20 | $res = ['result' => 'invalid_name'];
21 | goto tail;
22 | }
23 |
24 | if(Users::is_exists_user_name($_POST['name'])){
25 | $res = ['result' => 'already_exists_name'];
26 | goto tail;
27 | }
28 |
29 | if(!Users::is_valid_user_email($_POST['email'])){
30 | $res = ['result' => 'invalid_email'];
31 | goto tail;
32 | }
33 |
34 | if(Users::is_exists_user_email($_POST['email'])){
35 | $res = ['result' => 'already_exists_email'];
36 | goto tail;
37 | }
38 |
39 | if(!Users::is_valid_user_password($_POST['password'])){
40 | $res = ['result' => 'invalid_password'];
41 | goto tail;
42 | }
43 |
44 | if(!Users::is_valid_user_comment($_POST['comment'])){
45 | $res = ['result' => 'invalid_comment'];
46 | goto tail;
47 | }
48 |
49 | if(!Users::update_my_user($_POST['name'], $_POST['email'], $_POST['password'], $_POST['comment'])){
50 | $res = ['result' => 'error'];
51 | goto tail;
52 | }
53 |
54 | $res = ['result' => 'valid', 'redirect' => get_user_profile_page_url($_POST['name'])];
55 |
56 | tail:
57 | Templater::json($res);
58 |
--------------------------------------------------------------------------------
/@import/apis/users/sign-in.php:
--------------------------------------------------------------------------------
1 | 'error'];
4 | goto tail;
5 | }
6 |
7 | if(is_use_recaptcha()){
8 | if(!isset($_POST['recaptcha-token']) || !is_valid_recaptcha_token($_POST['recaptcha-token'])){
9 | $res = ['result' => 'invalid_token'];
10 | goto tail;
11 | }
12 | }
13 |
14 | if(Users::is_signed()){
15 | $res = ['result' => 'already_signed'];
16 | goto tail;
17 | }
18 |
19 | if(!Users::is_valid_user_name($_POST['name']) ||
20 | !Users::is_valid_user_password($_POST['password']) ||
21 | !Users::do_sign_in($_POST['name'], $_POST['password'])){
22 | $res = ['result' => 'invalid_account'];
23 | goto tail;
24 | }
25 |
26 | $res = ['result' => 'valid'];
27 |
28 | tail:
29 | Templater::json($res);
30 |
--------------------------------------------------------------------------------
/@import/apis/users/sign-out.php:
--------------------------------------------------------------------------------
1 | 'error'];
4 | goto tail;
5 | }
6 |
7 | if(is_use_recaptcha()){
8 | if(!isset($_POST['recaptcha-token']) || !is_valid_recaptcha_token($_POST['recaptcha-token'])){
9 | $res = ['result' => 'invalid_token'];
10 | goto tail;
11 | }
12 | }
13 |
14 | if(Users::is_signed()){
15 | $res = ['result' => 'already_signed'];
16 | goto tail;
17 | }
18 |
19 | if(!Users::is_valid_user_name($_POST['name'])){
20 | $res = ['result' => 'invalid_name'];
21 | goto tail;
22 | }
23 |
24 | if(Users::is_exists_user_name($_POST['name'])){
25 | $res = ['result' => 'already_exists_name'];
26 | goto tail;
27 | }
28 |
29 | if(!Users::is_valid_user_email($_POST['email'])){
30 | $res = ['result' => 'invalid_email'];
31 | goto tail;
32 | }
33 |
34 | if(Users::is_exists_user_email($_POST['email'])){
35 | $res = ['result' => 'already_exists_email'];
36 | goto tail;
37 | }
38 |
39 | if(!Users::is_valid_user_password($_POST['password'])){
40 | $res = ['result' => 'invalid_password'];
41 | goto tail;
42 | }
43 |
44 | if(!Users::is_valid_user_comment($_POST['comment'])){
45 | $res = ['result' => 'invalid_comment'];
46 | goto tail;
47 | }
48 |
49 | if(!Users::do_sign_up($_POST['name'], $_POST['email'], $_POST['password'], $_POST['comment'])){
50 | $res = ['result' => 'error'];
51 | goto tail;
52 | }
53 |
54 | $res = ['result' => 'valid'];
55 |
56 | tail:
57 | Templater::json($res);
58 |
--------------------------------------------------------------------------------
/@import/apis/users/verify-email.php:
--------------------------------------------------------------------------------
1 | 'error'];
4 | goto tail;
5 | }
6 |
7 | if(!Users::is_valid_user_email($_POST['email'])){
8 | $res = ['result' => 'invalid'];
9 | goto tail;
10 | }
11 |
12 | if(Users::is_exists_user_email($_POST['email'])){
13 | $res = ['result' => 'exists'];
14 | goto tail;
15 | }
16 |
17 | $res = ['result' => 'valid'];
18 |
19 | tail:
20 | Templater::json($res);
21 |
--------------------------------------------------------------------------------
/@import/apis/users/verify-name.php:
--------------------------------------------------------------------------------
1 | 'error'];
4 | goto tail;
5 | }
6 |
7 | if(!Users::is_valid_user_name($_POST['name'])){
8 | $res = ['result' => 'invalid'];
9 | goto tail;
10 | }
11 |
12 | if(Users::is_exists_user_name($_POST['name'])){
13 | $res = ['result' => 'exists'];
14 | goto tail;
15 | }
16 |
17 | $res = ['result' => 'valid'];
18 |
19 | tail:
20 | Templater::json($res);
21 |
--------------------------------------------------------------------------------
/@import/apis/wechall/user-score.php:
--------------------------------------------------------------------------------
1 | 'CanHackMe',
7 | 'description' => 'Have fun challenges!',
8 | 'keyword' => 'CanHackMe, Jeopardy, CTF, Wargame, Hacking, Security, Flag',
9 | 'url' => 'https://canhack.me/',
10 | 'facebook_app_id' => file_get_contents(__DIR__.'/.facebook_app_id.txt'),
11 | 'twitter_account' => file_get_contents(__DIR__.'/.twitter_account.txt'),
12 |
13 | 'use_recaptcha' => false,
14 | 'recaptcha_sitekey' => file_get_contents(__DIR__.'/.recaptcha_sitekey.txt'),
15 | 'recaptcha_secretkey' => file_get_contents(__DIR__.'/.recaptcha_secretkey.txt'),
16 |
17 | 'wechall_authkey' => file_get_contents(__DIR__.'/.wechall_authkey.txt'),
18 | ]);
19 |
20 | define('__AUTHOR__', [
21 | 'name' => 'Safflower',
22 | 'email' => 'plzdonotsay@gmail.com',
23 | 'website' => 'https://safflower.pw/',
24 | ]);
25 |
26 | define('__ADMIN__', [
27 | 'admin',
28 | 'safflower',
29 | ]);
30 |
31 | define('__HASH_SALT__', file_get_contents(__DIR__.'/.hash_salt.txt'));
32 |
--------------------------------------------------------------------------------
/@import/confs/init.sql:
--------------------------------------------------------------------------------
1 |
2 | # users
3 | CREATE TABLE `users` (
4 | `user_no` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
5 | `user_name` TEXT NOT NULL UNIQUE,
6 | `user_email` TEXT NOT NULL UNIQUE,
7 | `user_password` TEXT NOT NULL,
8 | `user_comment` TEXT NOT NULL,
9 | `user_score` INTEGER NOT NULL,
10 | `user_signed_up_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
11 | );
12 |
13 | # challenges
14 | CREATE TABLE `chals` (
15 | `chal_no` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
16 | `chal_name` TEXT NOT NULL UNIQUE,
17 | `chal_title` TEXT NOT NULL UNIQUE,
18 | `chal_contents` TEXT NOT NULL,
19 | `chal_score` INTEGER NOT NULL,
20 | `chal_flag` TEXT NOT NULL UNIQUE,
21 | `chal_tags` TEXT NOT NULL,
22 | `chal_user_no` INTEGER NOT NULL,
23 | `chal_uploaded_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
24 | );
25 |
26 | # solve logs
27 | CREATE TABLE `solvs` (
28 | `solv_no` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
29 | `solv_user_no` INTEGER NOT NULL,
30 | `solv_chal_no` INTEGER NOT NULL,
31 | `solv_solved_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
32 | );
33 |
34 | # notifications
35 | CREATE TABLE `notis` (
36 | `noti_no` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
37 | `noti_contents` TEXT NOT NULL,
38 | `noti_user_no` INTEGER NOT NULL,
39 | `noti_uploaded_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
40 | );
41 |
--------------------------------------------------------------------------------
/@import/init.php:
--------------------------------------------------------------------------------
1 | =') or die('Not supported PHP version (now: '.PHP_VERSION.'). Please install PHP 7.3
.');
3 |
4 | ini_set('display_errors', 'off');
5 |
6 | ini_set('session.name', 'session');
7 | ini_set('session.cookie_httponly', '1');
8 | ini_set('session.sid_length', '50');
9 | ini_set('session.sid_bits_per_character', '6');
10 | session_start();
11 |
12 | define('__CSP_NONCE__', base64_encode(random_bytes(20)));
13 | header('X-Content-Type-Options: nosniff');
14 | header('X-Frame-Options: deny');
15 | header('X-XSS-Protection: 1; mode=block');
16 | header('Content-Security-Policy: base-uri \'self\'; script-src \'nonce-'.__CSP_NONCE__.'\';');
17 |
18 | require __DIR__.'/confs/common.php';
19 |
20 | ######################################################################################################################
21 |
22 | if(__IS_DEBUG__){
23 | error_reporting(E_ALL);
24 | ini_set('display_errors', 'on');
25 | }
26 |
27 | date_default_timezone_set('UTC');
28 |
29 | $need_init = !is_file(__DIR__.'/confs/.common.db');
30 |
31 | $db = new SQLite3(__DIR__.'/confs/.common.db');
32 | $db->createFunction('HASH', function(string $value){
33 | return hash('sha256', $value.__HASH_SALT__);
34 | });
35 |
36 | if($need_init){
37 | if(($init_sql = file_get_contents(__DIR__.'/confs/init.sql')) === false){
38 | die('Can\'t found SQL file for initialize database. (path: /confs/init.sql
)');
39 | }
40 | $db->query($init_sql);
41 | unset($init_sql);
42 | }
43 | unset($need_init);
44 |
45 | Templater::init();
46 | Users::init();
47 | Challenges::init();
48 |
49 | ######################################################################################################################
50 |
51 | function is_use_recaptcha(): bool{
52 | return isset(__SITE__['use_recaptcha'], __SITE__['recaptcha_sitekey'], __SITE__['recaptcha_secretkey']) &&
53 | __SITE__['use_recaptcha'] === true &&
54 | is_string(__SITE__['recaptcha_sitekey']) &&
55 | is_string(__SITE__['recaptcha_secretkey']);
56 | }
57 | function is_valid_recaptcha_token($token): bool{
58 | if(!is_string($token) || !isset($token{0})){
59 | return false;
60 | }
61 | $url = 'https://www.google.com/recaptcha/api/siteverify';
62 | $data = [
63 | 'secret' => __SITE__['recaptcha_secretkey'],
64 | 'response' => $token,
65 | ];
66 | $options = [
67 | 'http' => [
68 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
69 | 'method' => 'POST',
70 | 'content' => http_build_query($data),
71 | ]
72 | ];
73 | $context = stream_context_create($options);
74 | $response = file_get_contents($url, false, $context);
75 | $responseKeys = json_decode($response, true);
76 | return isset($responseKeys['success']) && is_bool($responseKeys['success']) ? $responseKeys['success'] : false;
77 | }
78 | function email_encode(string $email): string{
79 | // html encode some characters (at, dot).
80 | return strtr(htmlentities($email), ['@' => '@', '.' => '.']);
81 | }
82 | function get_challenge_shortcut_page_url(string $chal_name): string{
83 | return '/challenges/@'.urlencode(strtolower($chal_name));
84 | }
85 | function get_challenge_tag_page_url(string $chal_name): string{
86 | return '/challenges/tag/'.urlencode(strtolower($chal_name));
87 | }
88 | function get_user_profile_page_url(string $user_name): string{
89 | return '/users/@'.urlencode(strtolower($user_name));
90 | }
91 | function get_user_profile_image_url(string $user_email, int $size = 64): string{
92 | $token = md5($user_email);
93 | return 'https://www.gravatar.com/avatar/'.$token.'?'.
94 | http_build_query([
95 | 'd' => 'https://github.com/identicons/'.$token.'.png',
96 | 's' => $size,
97 | ]);
98 | }
99 | function is_admin_user_name(string $user_name): bool{
100 | return in_array(strtolower($user_name), __ADMIN__, true);
101 | }
102 | function highlight_keyword(string $content, string $keyword): string{
103 | if(!isset($keyword{0})){
104 | return $content;
105 | }
106 | return preg_replace('/('.preg_quote($keyword, '/').')/i', '$1', $content);
107 | }
108 |
109 | ######################################################################################################################
110 |
111 | class Templater{
112 | private static $url_path;
113 | public static function init(){
114 | self::$url_path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
115 | }
116 | public static function get_url_path(): string{
117 | return self::$url_path;
118 | }
119 | public static function route(string $regex, array $methods, &$args = null): bool{
120 | return in_array($_SERVER['REQUEST_METHOD'], $methods, true) && preg_match($regex, self::get_url_path(), $args);
121 | }
122 | public static function import(string $file, $args = null){
123 | include __DIR__.'/'.$file.'.php';
124 | }
125 | public static function error(int $status = 404){
126 | $_SERVER['REDIRECT_STATUS'] = $status;
127 | if($_SERVER['REQUEST_METHOD'] === 'GET'){
128 | self::import('views/error/error');
129 | }else{
130 | self::import('apis/error/error');
131 | }
132 | die;
133 | }
134 | public static function redirect(string $url){
135 | if(!headers_sent()){
136 | header('Location: '.$url);
137 | }
138 | die('');
139 | }
140 | public static function json(array $data){
141 | if(!headers_sent()){
142 | header('Content-Type: application/json; charset=utf-8');
143 | }
144 | die(json_encode($data));
145 | }
146 | }
147 |
148 | ######################################################################################################################
149 |
150 | class Data{
151 | public static function resource(string $link, bool $is_full_url = false): string{
152 | $parsed_url = parse_url($link);
153 |
154 | // If the link is external URL, return it as it is.
155 | if(isset($parsed_url['scheme']) || isset($parsed_url['host'])) return $link;
156 |
157 | $url_prefix = $is_full_url === true ? rtrim(__SITE__['url'], '/') : '';
158 | $url_path = $parsed_url['path'];
159 | $file_path = realpath($_SERVER['DOCUMENT_ROOT'].'/'.$url_path);
160 | $url_query = $file_path !== false ? '?v='.filemtime($file_path) : '';
161 | return $url_prefix.$url_path.$url_query;
162 | }
163 | public static function markbb(string $value): string{
164 | $value = htmlentities($value);
165 | $value = strtr($value, ["\r" => '', "\n" => '
']);
166 |
167 | $value = preg_replace('#\[b\](.*?)\[/b\]#s', '$1', $value);
168 | $value = preg_replace('#__(.*?)__#s', '$1', $value);
169 | $value = preg_replace('#\*\*(.*?)\*\*#s', '$1', $value);
170 |
171 | $value = preg_replace('#\[u\](.*?)\[/u\]#s', '$1', $value);
172 | $value = preg_replace('#\+\+(.*?)\+\+#s', '$1', $value);
173 |
174 | $value = preg_replace('#\[i\](.*?)\[/i\]#s', '$1', $value);
175 | $value = preg_replace('#\[em\](.*?)\[/em\]#s', '$1', $value);
176 | $value = preg_replace('#\*(.*?)\*#s', '$1', $value);
177 |
178 | $value = preg_replace('#\[s\](.*?)\[/s\]#s', '$1', $value);
179 | $value = preg_replace('#~~(.*?)~~#s', '$1', $value);
180 |
181 | $value = preg_replace('#\[quote\](.*?)\[/quote\]#s', '
$1
', $value);
182 | $value = preg_replace('#\>(.*?)(\r?\n)#s', '$1
$2', $value);
183 |
184 | $value = preg_replace('#\[mark\](.*?)\[/mark\]#s', '$1', $value);
185 | $value = preg_replace('#==(.*?)==#s', '$1', $value);
186 |
187 | $value = preg_replace('#```(.*?)```#s', '$1
', $value);
188 |
189 | $value = preg_replace('#\[code\](.*?)\[/code\]#s', '$1
', $value);
190 | $value = preg_replace('#`(.*?)`#s', '$1
', $value);
191 |
192 | $value = preg_replace('#\[pre\](.*?)\[/pre\]#s', '$1
', $value);
193 |
194 | $value = preg_replace('#\[file\](.*?)\[/file\]#s', '$1', $value);
195 | $value = preg_replace('#\[file=(.*?)\](.*?)\[/file\]#s', '$2', $value);
196 |
197 | $value = preg_replace('#\[img\](.*?)\[/img\]#s', '
', $value);
198 | $value = preg_replace('#\!\[(.*?)\]\((.*?)\)#s', '
', $value);
199 | $value = preg_replace('#\[img=(.*?)\](.*?)\[/img\]#s', '
', $value);
200 |
201 | $value = preg_replace('#\[(.*?)\]\((\/.*?)\)#s', '$1', $value);
202 | $value = preg_replace('#\[(.*?)\]\((.*?)\)#s', '$1', $value);
203 | $value = preg_replace('#\[url=(\/.*?)\](.*?)\[/url\]#s', '$2', $value);
204 | $value = preg_replace('#\[url=(.*?)\](.*?)\[/url\]#s', '$2', $value);
205 |
206 | $value = preg_replace('#\<(\/.*?)\>#s', '$1', $value);
207 | $value = preg_replace('#\<(.*?)\>#s', '$1', $value);
208 | $value = preg_replace('#\[url\](\/.*?)\[/url\]#s', '$1', $value);
209 | $value = preg_replace('#\[url\](.*?)\[/url\]#s', '$1', $value);
210 | return $value;
211 | }
212 | }
213 |
214 | ######################################################################################################################
215 |
216 | class Users{
217 | private static $is_signed;
218 | private static $my_user;
219 | public static function init(){
220 | self::$is_signed = isset($_SESSION['user_no'], $_SESSION['signed_token']) && $_SESSION['signed_token'] === self::get_signed_token();
221 | if(self::$is_signed){
222 | if((self::$my_user = self::get_user_by_user_no($_SESSION['user_no'])) === false){
223 | self::$is_signed = self::$my_user = false;
224 | }
225 | }else{
226 | self::$my_user = false;
227 | }
228 | }
229 | public static function is_signed(): bool{
230 | return self::$is_signed;
231 | }
232 | public static function get_my_user(string $column = '*'){
233 | if(self::$is_signed !== true){
234 | return false;
235 | }
236 | if($column === '*'){
237 | return self::$my_user;
238 | }else if(isset(self::$my_user[$column])){
239 | return self::$my_user[$column];
240 | }else{
241 | return false;
242 | }
243 | }
244 | public static function get_unsigned_token(): string{
245 | return base64_encode(sha1(json_encode([$_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']]), true));
246 | }
247 | public static function get_signed_token(){
248 | return isset($_SESSION['user_no']) ?
249 | base64_encode(sha1(json_encode([$_SESSION['user_no'], $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT'], __HASH_SALT__]), true)) :
250 | false;
251 | }
252 | public static function get_user_by_name(string $user_name, string $column = '*', bool $is_case_sensitive = false){
253 | $where_option = $is_case_sensitive ? '' : 'COLLATE NOCASE';
254 | global $db;
255 | $stmt = $db->prepare("
256 | SELECT
257 | *
258 | FROM
259 | `users`
260 | WHERE
261 | `user_name`=:user_name {$where_option}
262 | LIMIT
263 | 1
264 | ");
265 | $stmt->bindParam(':user_name', $user_name);
266 | $res = $stmt->execute();
267 | if($res === false) return false;
268 | $user = $res->fetchArray(SQLITE3_ASSOC);
269 | if($user === false) return false;
270 |
271 | if($column === '*'){
272 | return $user;
273 | }else if(isset($user[$column])){
274 | return $user[$column];
275 | }else{
276 | return false;
277 | }
278 | }
279 | public static function get_user_by_user_no(int $user_no, string $column = '*'){
280 | global $db;
281 | $stmt = $db->prepare('
282 | SELECT
283 | *
284 | FROM
285 | `users`
286 | WHERE
287 | `user_no`=:user_no
288 | LIMIT
289 | 1
290 | ');
291 | $stmt->bindParam(':user_no', $user_no);
292 | $res = $stmt->execute();
293 | if($res === false) return false;
294 | $user = $res->fetchArray(SQLITE3_ASSOC);
295 | if($user === false) return false;
296 |
297 | if($column === '*'){
298 | return $user;
299 | }else if(isset($user[$column])){
300 | return $user[$column];
301 | }else{
302 | return false;
303 | }
304 | }
305 | public static function get_solv_count_by_user_no(int $user_no){
306 | global $db;
307 | $stmt = $db->prepare("
308 | SELECT
309 | COUNT(*) AS `solv_count`
310 | FROM
311 | `solvs`
312 | WHERE
313 | `solv_user_no`=:user_no
314 | ");
315 | $stmt->bindParam(':user_no', $user_no);
316 | $res = $stmt->execute();
317 | if($res === false) return false;
318 | $solv = $res->fetchArray(SQLITE3_ASSOC);
319 | if($solv === false) return false;
320 | return $solv['solv_count'];
321 | }
322 | public static function get_users(int $limit_start = 0, int $limit_end = 24){
323 | global $db;
324 | $stmt = $db->prepare('
325 | SELECT
326 | *
327 | FROM
328 | `users`
329 | ORDER BY
330 | `user_no` DESC
331 | LIMIT
332 | :limit_start, :limit_end
333 | ');
334 | $stmt->bindParam(':limit_start', $limit_start);
335 | $stmt->bindParam(':limit_end', $limit_end);
336 | $res = $stmt->execute();
337 | if($res === false) return false;
338 | $users = [];
339 | while($user = $res->fetchArray(SQLITE3_ASSOC)) $users[] = $user;
340 | return $users;
341 | }
342 | public static function is_valid_user_name($user_name): bool{
343 | return is_string($user_name) && preg_match('/\A[a-zA-Z0-9_-]{5,20}\z/', $user_name);
344 | }
345 | public static function is_valid_user_url($user_url): bool{
346 | return is_string($user_url) && preg_match('/\A(https?\:\/\/|\/\/).+\z/i', $user_url) && filter_var($user_url, FILTER_VALIDATE_URL);
347 | }
348 | public static function is_valid_user_email($user_email): bool{
349 | return is_string($user_email) && filter_var($user_email, FILTER_VALIDATE_EMAIL);
350 | }
351 | public static function is_valid_user_comment($user_comment): bool{
352 | return is_string($user_comment) && ($len = mb_strlen($user_comment)) !== false && 0 <= $len && $len <= 50;
353 | }
354 | public static function is_valid_user_password($user_password): bool{
355 | return is_string($user_password) && ($len = mb_strlen($user_password)) !== false && 6 <= $len && $len <= 50;
356 | }
357 | public static function is_exists_user_name(string $user_name): bool{
358 | global $db;
359 | $stmt = $db->prepare('
360 | SELECT
361 | 1
362 | FROM
363 | `users`
364 | WHERE
365 | `user_name`=:user_name COLLATE NOCASE AND `user_no`!=:user_no
366 | LIMIT
367 | 1
368 | ');
369 | $stmt->bindParam(':user_name', $user_name);
370 | $stmt->bindValue(':user_no', self::get_my_user('user_no'));
371 | $res = $stmt->execute();
372 | if($res === false) return false;
373 | $user = $res->fetchArray(SQLITE3_ASSOC);
374 | if($user === false) return false;
375 | return true;
376 | }
377 | public static function is_exists_user_email(string $user_email): bool{
378 | global $db;
379 | $stmt = $db->prepare('
380 | SELECT
381 | 1
382 | FROM
383 | `users`
384 | WHERE
385 | `user_email`=:user_email COLLATE NOCASE AND `user_no`!=:user_no
386 | LIMIT
387 | 1
388 | ');
389 | $stmt->bindParam(':user_email', $user_email);
390 | $stmt->bindValue(':user_no', self::get_my_user('user_no'));
391 | $res = $stmt->execute();
392 | if($res === false) return false;
393 | $user = $res->fetchArray(SQLITE3_ASSOC);
394 | if($user === false) return false;
395 | return true;
396 | }
397 | public static function do_sign_in(string $user_name, string $user_password): bool{
398 | global $db;
399 | $stmt = $db->prepare('
400 | SELECT
401 | `user_no`
402 | FROM
403 | `users`
404 | WHERE
405 | `user_name`=:user_name COLLATE NOCASE AND `user_password`=HASH(:user_password)
406 | LIMIT
407 | 1
408 | ');
409 | $stmt->bindParam(':user_name', $user_name);
410 | $stmt->bindParam(':user_password', $user_password);
411 | $res = $stmt->execute();
412 | if($res === false) return false;
413 | $user = $res->fetchArray(SQLITE3_ASSOC);
414 | if($user === false) return false;
415 | if(session_regenerate_id(true) === false) return false;
416 | $_SESSION['user_no'] = $user['user_no'];
417 | $_SESSION['signed_token'] = self::get_signed_token();
418 | self::init();
419 | return true;
420 | }
421 | public static function do_sign_up(string $user_name, string $user_email, string $user_password, string $user_comment): bool{
422 | global $db;
423 | $stmt = $db->prepare('
424 | INSERT INTO
425 | `users`
426 | (
427 | `user_name`, `user_email`, `user_password`, `user_comment`, `user_score`
428 | )
429 | VALUES
430 | (
431 | :user_name, :user_email, HASH(:user_password), :user_comment, 0
432 | )
433 | ');
434 | $stmt->bindParam(':user_name', $user_name);
435 | $stmt->bindParam(':user_email', $user_email);
436 | $stmt->bindParam(':user_password', $user_password);
437 | $stmt->bindParam(':user_comment', $user_comment);
438 | $res = $stmt->execute();
439 | if($res === false) return false;
440 | return true;
441 | }
442 | public static function do_sign_out(){
443 | unset($_SESSION['user_no'], $_SESSION['signed_token']);
444 | }
445 | public static function get_user_count(string $keyword = ''){
446 | global $db;
447 | $stmt = $db->prepare('
448 | SELECT
449 | COUNT(*) AS `count`
450 | FROM
451 | `users`
452 | WHERE
453 | INSTR(LOWER(`user_name`), LOWER(:keyword)) OR INSTR(LOWER(`user_comment`), LOWER(:keyword))
454 | ');
455 | $stmt->bindParam(':keyword', $keyword);
456 | $res = $stmt->execute();
457 | if($res === false) return false;
458 | $user = $res->fetchArray(SQLITE3_ASSOC);
459 | if($user === false) return false;
460 | return $user['count'];
461 | }
462 | public static function update_my_user(string $user_name, string $user_email, string $user_password, string $user_comment): bool{
463 | global $db;
464 | $stmt = $db->prepare('
465 | UPDATE
466 | `users`
467 | SET
468 | `user_name`=:user_name,
469 | `user_email`=:user_email,
470 | `user_password`=HASH(:user_password),
471 | `user_comment`=:user_comment
472 | WHERE
473 | `user_no`=:user_no
474 | ');
475 | $stmt->bindParam(':user_name', $user_name);
476 | $stmt->bindParam(':user_email', $user_email);
477 | $stmt->bindParam(':user_password', $user_password);
478 | $stmt->bindParam(':user_comment', $user_comment);
479 | $stmt->bindValue(':user_no', Users::get_my_user('user_no'));
480 | $res = $stmt->execute();
481 | if($res === false) return false;
482 | self::init();
483 | return true;
484 | }
485 | }
486 |
487 | ######################################################################################################################
488 |
489 | class Challenges{
490 | private static $chal_tags;
491 | public static function init(){
492 | global $db;
493 | $stmt = $db->prepare('
494 | SELECT
495 | GROUP_CONCAT(`chal_tags`, ",") AS `chal_tags`
496 | FROM
497 | `chals`
498 | ');
499 | $res = $stmt->execute();
500 | if($res !== false){
501 | $chal = $res->fetchArray(SQLITE3_ASSOC);
502 | if($chal !== false){
503 | self::$chal_tags = array_unique(explode(',', $chal['chal_tags']));
504 | }else{
505 | self::$chal_tags = false;
506 | }
507 | }else{
508 | self::$chal_tags = false;
509 | }
510 | }
511 | public static function get_chal_tags(){
512 | return self::$chal_tags;
513 | }
514 | public static function is_valid_chal_flag($chal_flag): bool{
515 | return is_string($chal_flag) && preg_match('/\A[a-zA-Z0-9_]+?\{[a-zA-Z0-9_]{10,50}\}\z/', $chal_flag);
516 | }
517 | public static function get_chal_by_chal_flag(string $chal_flag){
518 | global $db;
519 | $stmt = $db->prepare('
520 | SELECT
521 | *
522 | FROM
523 | `chals`
524 | WHERE
525 | `chal_flag`=:chal_flag
526 | LIMIT
527 | 1
528 | ');
529 | $stmt->bindParam(':chal_flag', $chal_flag);
530 | $res = $stmt->execute();
531 | if($res === false) return false;
532 | $chal = $res->fetchArray(SQLITE3_ASSOC);
533 | if($chal === false) return false;
534 | return $chal;
535 | }
536 | public static function get_solved_chals(int $user_no, bool $get_first_solver = false){
537 | global $db;
538 | $select = $get_first_solver ? '(SELECT `u`.`user_no` FROM `solvs`, `users` AS `u` WHERE `solv_chal_no`=`chal_no` AND `solv_user_no`=`u`.`user_no` ORDER BY `solv_no` ASC LIMIT 1) AS `chal_first_solver`,' : '0 AS `chal_first_solver`,';
539 | $stmt = $db->prepare("
540 | SELECT
541 | `chal_name`,
542 | `chal_title`,
543 | `chal_score`,
544 | {$select}
545 | `solv_solved_at` AS `chal_solved_at`
546 | FROM
547 | `solvs`,
548 | `chals`
549 | WHERE
550 | `solv_chal_no`=`chal_no` AND `solv_user_no`=:solv_user_no
551 | ORDER BY
552 | `solv_no` ASC,
553 | `chal_score` ASC,
554 | `chal_no` ASC
555 | ");
556 | $stmt->bindParam(':solv_user_no', $user_no);
557 | $res = $stmt->execute();
558 | if($res === false) return false;
559 | $chals = [];
560 | while($chal = $res->fetchArray(SQLITE3_ASSOC)) $chals[] = $chal;
561 | return $chals;
562 | }
563 | public static function is_solved_chal(int $chal_no): bool{
564 | global $db;
565 | $stmt = $db->prepare('
566 | SELECT
567 | 1
568 | FROM
569 | `solvs`
570 | WHERE
571 | `solv_user_no`=:solv_user_no AND `solv_chal_no`=:solv_chal_no
572 | LIMIT
573 | 1
574 | ');
575 | $stmt->bindValue(':solv_user_no', Users::get_my_user('user_no'));
576 | $stmt->bindParam(':solv_chal_no', $chal_no);
577 | $res = $stmt->execute();
578 | if($res === false) return false;
579 | $chal = $res->fetchArray(SQLITE3_ASSOC);
580 | if($chal === false) return false;
581 | return true;
582 | }
583 | public static function do_solve_chal(int $chal_no, int $chal_score): bool{
584 | global $db;
585 | $stmt = $db->prepare('
586 | INSERT INTO `solvs`
587 | (
588 | `solv_user_no`, `solv_chal_no`
589 | )
590 | VALUES
591 | (
592 | :solv_user_no, :solv_chal_no
593 | )
594 | ');
595 | $stmt->bindValue(':solv_user_no', Users::get_my_user('user_no'));
596 | $stmt->bindParam(':solv_chal_no', $chal_no);
597 | $res = $stmt->execute();
598 | if($res === false) return false;
599 |
600 | $stmt = $db->prepare('
601 | UPDATE
602 | `users`
603 | SET
604 | `user_score`=`user_score`+:score
605 | WHERE
606 | `user_no`=:user_no
607 | ');
608 | $stmt->bindValue(':user_no', Users::get_my_user('user_no'));
609 | $stmt->bindParam(':score', $chal_score);
610 | $res = $stmt->execute();
611 | if($res === false) return false;
612 | return true;
613 | }
614 | public static function get_chal_count_and_score(){
615 | global $db;
616 | $stmt = $db->prepare("
617 | SELECT
618 | COUNT(*) AS `count`,
619 | SUM(`chal_score`) AS `score`
620 | FROM
621 | `chals`
622 | ");
623 | $res = $stmt->execute();
624 | if($res === false) return false;
625 | $solv = $res->fetchArray(SQLITE3_ASSOC);
626 | if($solv === false) return false;
627 | return $solv;
628 | }
629 | public static function get_chals(string $chal_tag = 'all'){
630 | $query_column = Users::is_signed() ? 'EXISTS(SELECT 1 FROM `solvs` WHERE `solv_chal_no`=`chal_no` AND `solv_user_no`='.Users::get_my_user('user_no').' LIMIT 1)' : '0';
631 | $query_where = strcasecmp($chal_tag, 'all') ? 'AND INSTR(","||`chal_tags`||",", ",'.$chal_tag.',")' : '';
632 | global $db;
633 | $stmt = $db->prepare("
634 | SELECT
635 | `chal_name`,
636 | `chal_title`,
637 | `chal_contents`,
638 | `chal_score`,
639 | `chal_tags`,
640 | `chal_uploaded_at`,
641 | `user_name` AS `chal_author`,
642 | {$query_column} AS `chal_is_solved`,
643 | (SELECT COUNT(*) FROM `solvs` WHERE `solv_chal_no`=`chal_no`) AS `chal_solvers`,
644 | (SELECT `u`.`user_name` FROM `solvs`, `users` AS `u` WHERE `solv_chal_no`=`chal_no` AND `solv_user_no`=`u`.`user_no` ORDER BY `solv_no` ASC LIMIT 1) AS `chal_first_solver`
645 | FROM
646 | `chals`,
647 | `users`
648 | WHERE
649 | `chal_user_no`=`user_no`
650 | {$query_where}
651 | ORDER BY
652 | `chal_score` ASC,
653 | `chal_no` ASC
654 | ");
655 | $res = $stmt->execute();
656 | if($res === false) return false;
657 | $chals = [];
658 | while($chal = $res->fetchArray(SQLITE3_ASSOC)) $chals[] = $chal;
659 | return $chals;
660 | }
661 | public static function get_rank_by_user_no(int $user_no){
662 | global $db;
663 | $stmt = $db->prepare('
664 | SELECT
665 | `user_no`
666 | FROM
667 | `users`
668 | ORDER BY
669 | `user_score` DESC,
670 | (SELECT `solv_solved_at` FROM `solvs` WHERE `solv_user_no`=`user_no` ORDER BY `solv_no` DESC LIMIT 1) ASC
671 | ');
672 | $res = $stmt->execute();
673 | if($res === false) return false;
674 | $i = 0;
675 | $ranks = [];
676 | while($rank = $res->fetchArray(SQLITE3_ASSOC)){
677 | ++$i;
678 | if($rank['user_no'] === $user_no) return $i;
679 | }
680 | return false;
681 | }
682 | public static function get_ranks(string $keyword = '', int $limit_start = 0, int $limit_end = 30){
683 | global $db;
684 | $stmt = $db->prepare('
685 | SELECT
686 | `user_no`,
687 | `user_name`,
688 | `user_comment`,
689 | `user_score`,
690 | (SELECT `solv_solved_at` FROM `solvs` WHERE `solv_user_no`=`user_no` ORDER BY `solv_no` DESC LIMIT 1) AS `user_last_solved_at`
691 | FROM
692 | `users`
693 | WHERE
694 | INSTR(LOWER(`user_name`), LOWER(:keyword)) OR INSTR(LOWER(`user_comment`), LOWER(:keyword))
695 | ORDER BY
696 | `user_score` DESC,
697 | `user_last_solved_at` ASC
698 | LIMIT
699 | :limit_start, :limit_end
700 | ');
701 | $stmt->bindParam(':keyword', $keyword);
702 | $stmt->bindParam(':limit_start', $limit_start);
703 | $stmt->bindParam(':limit_end', $limit_end);
704 | $res = $stmt->execute();
705 | if($res === false) return false;
706 | $ranks = [];
707 | while($rank = $res->fetchArray(SQLITE3_ASSOC)) $ranks[] = $rank;
708 | return $ranks;
709 | }
710 | public static function get_solvs(string $keyword = '', int $limit_start = 0, int $limit_end = 30){
711 | global $db;
712 | $stmt = $db->prepare('
713 | SELECT
714 | `solv_no`,
715 | `user_name` AS `solv_user_name`,
716 | `chal_name` AS `solv_chal_name`,
717 | `chal_title` AS `solv_chal_title`,
718 | `chal_score` AS `solv_chal_score`,
719 | `solv_solved_at`
720 | FROM
721 | `solvs`,
722 | `chals`,
723 | `users`
724 | WHERE
725 | `user_no`=`solv_user_no` AND `chal_no`=`solv_chal_no` AND
726 | (INSTR(LOWER(`user_name`), LOWER(:keyword)) OR INSTR(LOWER(`chal_title`), LOWER(:keyword)))
727 | ORDER BY
728 | `solv_no` DESC
729 | LIMIT
730 | :limit_start, :limit_end
731 | ');
732 | $stmt->bindParam(':keyword', $keyword);
733 | $stmt->bindParam(':limit_start', $limit_start);
734 | $stmt->bindParam(':limit_end', $limit_end);
735 | $res = $stmt->execute();
736 | if($res === false) return false;
737 | $solvs = [];
738 | while($solv = $res->fetchArray(SQLITE3_ASSOC)) $solvs[] = $solv;
739 | return $solvs;
740 | }
741 | public static function get_chal_count(){
742 | global $db;
743 | $stmt = $db->prepare('
744 | SELECT
745 | COUNT(*) AS `chal_count`
746 | FROM
747 | `chals`
748 | ');
749 | $res = $stmt->execute();
750 | if($res === false) return false;
751 | $chal = $res->fetchArray(SQLITE3_ASSOC);
752 | if($chal === false) return false;
753 | return $chal['chal_count'];
754 | }
755 | public static function get_solv_count(string $keyword = ''){
756 | global $db;
757 | $stmt = $db->prepare('
758 | SELECT
759 | COUNT(*) AS `solv_count`
760 | FROM
761 | `solvs`,
762 | `chals`,
763 | `users`
764 | WHERE
765 | `user_no`=`solv_user_no` AND `chal_no`=`solv_chal_no` AND
766 | (INSTR(LOWER(`user_name`), LOWER(:keyword)) OR INSTR(LOWER(`chal_title`), LOWER(:keyword)))
767 | ');
768 | $stmt->bindParam(':keyword', $keyword);
769 | $res = $stmt->execute();
770 | if($res === false) return false;
771 | $solv = $res->fetchArray(SQLITE3_ASSOC);
772 | if($solv === false) return false;
773 | return $solv['solv_count'];
774 | }
775 | }
776 |
777 | class Notifications{
778 | public static function get_notis(int $limit_start = 0, int $limit_end = 24){
779 | global $db;
780 | $stmt = $db->prepare('
781 | SELECT
782 | *
783 | FROM
784 | `notis`
785 | ORDER BY
786 | `noti_no` DESC
787 | LIMIT
788 | :limit_start, :limit_end
789 | ');
790 | $stmt->bindParam(':limit_start', $limit_start);
791 | $stmt->bindParam(':limit_end', $limit_end);
792 | $res = $stmt->execute();
793 | if($res === false) return false;
794 | $notis = [];
795 | while($noti = $res->fetchArray(SQLITE3_ASSOC)) $notis[] = $noti;
796 | return $notis;
797 | }
798 |
799 | public static function get_noti_count(){
800 | global $db;
801 | $stmt = $db->prepare('
802 | SELECT
803 | COUNT(*) AS `noti_count`
804 | FROM
805 | `notis`
806 | ');
807 | $res = $stmt->execute();
808 | if($res === false) return false;
809 | $noti = $res->fetchArray(SQLITE3_ASSOC);
810 | if($noti === false) return false;
811 | return $noti['noti_count'];
812 | }
813 | }
--------------------------------------------------------------------------------
/@import/views/challenges/challenges.php:
--------------------------------------------------------------------------------
1 | 'Challenges - '.__SITE__['title'],
21 | 'active' => 'challenges',
22 | 'scripts' => is_use_recaptcha() ? [
23 | '/assets/scripts/challenges.js',
24 | 'https://www.google.com/recaptcha/api.js?render='.urlencode(__SITE__['recaptcha_sitekey']),
25 | ] : [
26 | '/assets/scripts/challenges.js',
27 | ],
28 | ];
29 | $args_foot = [
30 | 'active' => 'challenges',
31 | ];
32 |
33 | if(isset($args['chal_name'])){
34 | foreach($chals as $chal){
35 | if(!strcasecmp($chal['chal_name'], $args['chal_name'])){
36 | $selected_name = $chal['chal_name'];
37 | break;
38 | }
39 | }
40 | if(!isset($selected_name)){
41 | Templater::error(404);
42 | }
43 | }else{
44 | $selected_name = null;
45 | }
46 |
47 | ?>
48 |
49 |
50 |
51 |
52 |
Challenges
53 |
54 | Nothing opened yet.
55 |
56 |
57 |
58 |
Challenges
59 |
60 |
61 |
64 |
70 |
71 |
72 |
73 |
105 |
106 |
107 |
108 |
119 |
120 |
121 |
134 |
= Data::markbb($chal['chal_contents']) ?>
135 |
136 |
137 |
138 |
#= htmlentities($tag) ?>
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | Currently opened = htmlentities(Challenges::get_chal_count()) ?> challenges.
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/@import/views/common/foot.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
37 |
38 |