2 //ini_set('display_errors', 'On');
4 ini_set('log_errors', 'On');
5 ini_set('error_log', 'errors/'.pathinfo(__FILE__, PATHINFO_FILENAME).'.log');
7 require_once "db_common.inc";
8 require_once "dump_file.inc";
9 require_once "feed_maker.inc";
11 // 登録が成功しない場合、HTTPレスポンスコード 400 Bad Request を返す
12 http_response_code(400);
15 * 送信されてきたスコアのマルチバイト文字エンコーディングを取得する
17 * Content-Typeヘッダからエンコーディングを取得する。
18 * 同時に変愚蛮怒が送ってきているはずのContent-Typeヘッダと一致しているかチェックする。
20 * @return string|false 送られてきたスコアの、PHPのマルチバイト文字処理における文字エンコーディングを表す文字列
23 function get_mb_encoding(){
24 $content_type = filter_input(INPUT_SERVER, 'CONTENT_TYPE');
25 if ($content_type == NULL) return FALSE;
27 $s = explode(';', $content_type);
30 $s[0] !== "text/plain" ||
31 strpos(trim($s[1]), "charset=") !== 0) return FALSE;
33 $c = explode('=', $s[1]);
35 switch (strtolower(trim($c[1]))) {
51 * 受信したスコアデータを、キャラクタ情報部分・キャラクタダンプ部分・スクリーンショット部分の3つに分割する
52 * それぞれの部分は1行毎の文字列の配列とする
54 * @param string $recv_contents 受信したスコアデータ
55 * @return array|false キャラクタ情報・キャラクタダンプ・スクリーンショットのそれぞれの部分のデータの配列
58 function split_recv_contents($recv_contents)
60 $recv_lines = explode("\n", $recv_contents);
62 $dump_info_end_line = array_search('-----charcter dump-----', $recv_lines);
63 $dump_txt_end_line = array_search('-----screen shot-----', $recv_lines);
65 if ($dump_info_end_line === FALSE) return FALSE;
67 $info_lines = array_slice($recv_lines, 0, $dump_info_end_line);
68 $dump_lines = array_slice(
70 $dump_info_end_line + 1,
71 $dump_txt_end_line ? $dump_txt_end_line - $dump_info_end_line - 1: NULL
73 $screen_lines = $dump_txt_end_line ?
76 $dump_txt_end_line + 1,
80 return [$info_lines, $dump_lines, $screen_lines];
87 * スコアデータのキャラクタ情報をパースし、情報名=>値の連想配列を得る
88 * ex) name: Hoge を ['name' => 'Hoge'] のようにする
90 * @param string $info_liens 受信したスコアデータのキャラクタ情報(1要素1行の配列)
91 * @return array|false キャラクタ情報・キャラクタダンプ・スクリーンショットのそれぞれの部分のデータの配列
94 function parse_character_info($info_lines)
97 foreach ($info_lines as $l) {
98 $splitpos = strpos($l, ':');
99 if ($splitpos !== FALSE) {
100 $key = substr($l, 0, $splitpos);
101 $val = substr($l, $splitpos + 1);
102 $info[$key] = trim($val);
111 * キャラクタ情報からDBへのスコア登録用パラメータを生成する
113 * @param array $info キャラクタ情報の連想配列
114 * @return array DBへのスコア登録用パラメータ('character_info'と'realm_info'の連想配列)
116 function create_db_insert_score_data($info)
118 $character_info_array = [
119 'version' => $info['version'],
120 'score' => $info['score'],
121 'name' => $info['name'],
122 'race' => $info['race'],
123 'class' => $info['class'],
124 'personality' => $info['seikaku'],
125 'sex' => $info['sex'],
126 'level' => $info['level'],
127 'depth' => $info['depth'],
128 'maxlv' => $info['maxlv'],
129 'maxdp' => $info['maxdp'],
131 'turns' => $info['turns'],
132 'winner' => $info['killer'] == 'ripe' || $info['killer'] == 'Seppuku',
133 'killer' => $info['killer'],
136 $realm_info_array = array_values(
138 [$info['realm1'], $info['realm2']],
139 function($v) {return $v !== '魔法なし';})
143 'character_info' => $character_info_array,
144 'realm_info' => $realm_info_array,
150 * スクリーンダンプのバリデーションを行う
152 * スクリプト実行などの悪意を持ったスクリーンダンプを登録できないよう、
153 * 使用可能なタグをhtml,body,pre,fontに制限する
155 * @param array $screen_dump_lines スクリーンダンプの文字列の配列
156 * @return バリデーションに成功したらTRUE、失敗したらFALSE
158 function validate_screen_dump($screen_dump_lines)
160 if ($screen_dump_lines === FALSE) {
164 $allow_tags = ['html', 'body', 'pre', 'font'];
167 foreach ($screen_dump_lines as $line) {
168 if (preg_match_all("|</?([^>\s]+)(\s*[^>]+)?>|", $line, $matches, PREG_SET_ORDER) > 0) {
169 $invalid_tag_matches = array_filter($matches, function($m) use($allow_tags) {
170 return !in_array($m[1], $allow_tags);
172 if (count($invalid_tag_matches) > 0) {
182 $recv_encoding = get_mb_encoding();
183 if ($recv_encoding === FALSE) {
187 $recv_contents = file_get_contents('php://input');
188 if (strlen($recv_contents) !== filter_input(INPUT_SERVER, 'CONTENT_LENGTH', FILTER_VALIDATE_INT)) {
192 $recv_contents = mb_convert_encoding($recv_contents, "UTF-8", $recv_encoding);
194 $split_contents = split_recv_contents($recv_contents);
195 if ($split_contents === FALSE) {
199 $char_info = parse_character_info($split_contents[0]);
202 $score_id = $db->register_new_score(create_db_insert_score_data($char_info));
204 if ($score_id === FALSE) {
208 // 登録成功、HTTPレスポンスコード 200 OK を返す
209 http_response_code(200);
211 $dumpfile = new DumpFile($score_id);
212 $dumpfile->save('dumps', 'txt', $split_contents[1]);
213 if (validate_screen_dump($split_contents[2])) {
214 $dumpfile->save('screens', 'html', $split_contents[2]);
216 $dumpfile->save('screens', 'html.bad', $split_contents[2]);
219 $dead_place = $dumpfile->get_dead_place();
220 $db->update_dead_place($score_id, $dead_place);
222 exec("nohup python tools/tweet_score.py -c tools/tweet_score.cfg -l tweet_score.log -s {$score_id} > /dev/null &");
224 $feed_maker = new FeedMaker($db);
225 $feed_maker->make_atom_feed("feed/newcome-atom.xml");