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()
25 $content_type = filter_input(INPUT_SERVER, 'CONTENT_TYPE');
26 if ($content_type == null) {
30 $s = explode(';', $content_type);
33 $s[0] !== "text/plain" ||
34 strpos(trim($s[1]), "charset=") !== 0) {
38 $c = explode('=', $s[1]);
40 switch (strtolower(trim($c[1]))) {
56 * 受信したスコアデータを、キャラクタ情報部分・キャラクタダンプ部分・スクリーンショット部分の3つに分割する
57 * それぞれの部分は1行毎の文字列の配列とする
59 * @param string $recv_contents 受信したスコアデータ
60 * @return array|false キャラクタ情報・キャラクタダンプ・スクリーンショットのそれぞれの部分のデータの配列
63 function split_recv_contents($recv_contents)
65 $recv_lines = explode("\n", $recv_contents);
67 $dump_info_end_line = array_search('-----charcter dump-----', $recv_lines);
68 $dump_txt_end_line = array_search('-----screen shot-----', $recv_lines);
70 if ($dump_info_end_line === false) {
74 $info_lines = array_slice($recv_lines, 0, $dump_info_end_line);
75 $dump_lines = array_slice(
77 $dump_info_end_line + 1,
78 $dump_txt_end_line ? $dump_txt_end_line - $dump_info_end_line - 1: null
80 $screen_lines = $dump_txt_end_line ?
83 $dump_txt_end_line + 1,
87 return [$info_lines, $dump_lines, $screen_lines];
94 * スコアデータのキャラクタ情報をパースし、情報名=>値の連想配列を得る
95 * ex) name: Hoge を ['name' => 'Hoge'] のようにする
97 * @param string $info_liens 受信したスコアデータのキャラクタ情報(1要素1行の配列)
98 * @return array|false キャラクタ情報・キャラクタダンプ・スクリーンショットのそれぞれの部分のデータの配列
101 function parse_character_info($info_lines)
104 foreach ($info_lines as $l) {
105 $splitpos = strpos($l, ':');
106 if ($splitpos !== false) {
107 $key = substr($l, 0, $splitpos);
108 $val = substr($l, $splitpos + 1);
109 $info[$key] = trim($val);
118 * キャラクタ情報からDBへのスコア登録用パラメータを生成する
120 * @param array $info キャラクタ情報の連想配列
121 * @return array DBへのスコア登録用パラメータ('character_info'と'realm_info'の連想配列)
123 function create_db_insert_score_data($info)
125 $character_info_array = [
126 'version' => $info['version'],
127 'score' => $info['score'],
128 'name' => $info['name'],
129 'race' => $info['race'],
130 'class' => $info['class'],
131 'personality' => $info['seikaku'],
132 'sex' => $info['sex'],
133 'level' => $info['level'],
134 'depth' => $info['depth'],
135 'maxlv' => $info['maxlv'],
136 'maxdp' => $info['maxdp'],
138 'turns' => $info['turns'],
139 'winner' => $info['killer'] == 'ripe' || $info['killer'] == 'Seppuku',
140 'killer' => $info['killer'],
143 $realm_info_array = array_values(
145 [$info['realm1'], $info['realm2']],
147 return $v !== '魔法なし';
153 'character_info' => $character_info_array,
154 'realm_info' => $realm_info_array,
160 * スクリーンダンプのバリデーションを行う
162 * スクリプト実行などの悪意を持ったスクリーンダンプを登録できないよう、
163 * 使用可能なタグをhtml,body,pre,fontに制限する
165 * @param array $screen_dump_lines スクリーンダンプの文字列の配列
166 * @return バリデーションに成功したらTRUE、失敗したらFALSE
168 function validate_screen_dump($screen_dump_lines)
170 if ($screen_dump_lines === false) {
174 $allow_tags = ['html', 'body', 'pre', 'font'];
177 foreach ($screen_dump_lines as $line) {
178 if (preg_match_all("|</?([^>\s]+)(\s*[^>]+)?>|", $line, $matches, PREG_SET_ORDER) > 0) {
179 $invalid_tag_matches = array_filter(
181 function ($m) use ($allow_tags) {
182 return !in_array($m[1], $allow_tags);
185 if (count($invalid_tag_matches) > 0) {
195 $recv_encoding = get_mb_encoding();
196 if ($recv_encoding === false) {
200 $recv_contents = file_get_contents('php://input');
201 if (strlen($recv_contents) !== filter_input(INPUT_SERVER, 'CONTENT_LENGTH', FILTER_VALIDATE_INT)) {
205 $recv_contents = mb_convert_encoding($recv_contents, "UTF-8", $recv_encoding);
207 $split_contents = split_recv_contents($recv_contents);
208 if ($split_contents === false) {
212 $char_info = parse_character_info($split_contents[0]);
215 $score_id = $db->register_new_score(create_db_insert_score_data($char_info));
217 if ($score_id === false) {
221 // 登録成功、HTTPレスポンスコード 200 OK を返す
222 http_response_code(200);
224 $dumpfile = new DumpFile($score_id);
225 $dumpfile->save('dumps', 'txt', $split_contents[1]);
226 if (validate_screen_dump($split_contents[2])) {
227 $dumpfile->save('screens', 'html', $split_contents[2]);
229 $dumpfile->save('screens', 'html.bad', $split_contents[2]);
232 $dead_place = $dumpfile->get_dead_place();
233 $db->update_dead_place($score_id, $dead_place);
235 exec("nohup python tools/tweet_score.py -c tools/tweet_score.cfg -l tweet_score.log -s {$score_id} > /dev/null &");
237 $feed_maker = new FeedMaker($db);
238 $feed_maker->make_atom_feed("feed/newcome-atom.xml");