{名前}{パスワードハッシュ値}2chトリップ $users_cache = null; function load_users() { global $users_cache; if (isset($users_cache)) { return $users_cache; } $users_cache = []; $prev = file_get_contents(USERS_TSV) ?? ''; $rows = explode(PHP_EOL, $prev); unset($prev); foreach ($rows as $row) { $user = parse_user_row($row); if ($user['id'] == '') { continue; } $user['hash_password'] = ''; // HIDE $users_cache[$user['id']] = $user; unset($user); } unset($rows); return $users_cache; } function update_user($user) { // 注意:ここを変更したら、必ず、ユーザーが消えない事をテストすること。 if (!$user) { return ['ユーザーが不正。']; } if (!isset($user['id'])) { return ['ユーザーIDが不正。']; } $id = $user['id']; $username = $user['username']; $password = $user['password']; $trip = $user['trip'] ?? ''; $fp = fopen(USERS_TSV, 'r+'); if ($fp === false || !flock($fp, LOCK_EX)) { return ['ファイルの取得に失敗。']; } $prev = fread($fp, filesize(USERS_TSV)); if ($prev === false) { $prev = ''; } $rows = explode(PHP_EOL, $prev); $prev = null; $output = ''; $search_id = $id . "\t"; $changed = false; foreach ($rows as $row) { if ($row === '') { continue; } if (stripos($row, $search_id) !== 0) { $output .= $row . PHP_EOL; continue; } if ($password != '') { $hash_password = hash_password($password); } else { $array = explode("\t", $row); $hash_password = $array[2]; unset($array); } $output .= "{$id}\t{$username}\t{$hash_password}\t{$trip}" . PHP_EOL; unset($id); unset($username); unset($hash_password); unset($trip); $changed = true; } unset($rows); if ( !ftruncate($fp, 0) || !rewind($fp) || !fwrite($fp, $output) || !fflush($fp) ) { return ['ファイルの書き込みに失敗。']; } flock($fp, LOCK_UN); fclose($fp); return []; } function add_user($user) { if (!file_exists(USERS_TSV)) { die(USERS_TSV.': ファイルを見つけられません。'); } $id = $user['id']; $username = $user['username']; $password = $user['password']; $match = find_user_row($id); if (isset($match)) { return ['ID が既存ユーザーと重複。']; } unset($match); $hash_password = hash_password($password); $record = "{$id}\t{$username}\t{$hash_password}\t" . PHP_EOL; $result = false; if (is_writable(USERS_TSV)) { $result = file_put_contents(USERS_TSV, $record, FILE_APPEND | LOCK_EX); } if ($result === false) { return ['ファイルの書き込みに失敗。']; } chmod(USERS_TSV, 644); return []; } // Profile (bio) // プロフィールは PROFILE_DIR (data/profile) 配下に {ID}.txt のファイル名で保存される。 // 先頭に「-」だけの行があり、それより後の行がプロフィール本文になる。 // 将来的に「-」より前の行にユーザーごとの設定値などを記録する予定。 function load_profile($id) { if (!file_exists(PROFILE_DIR)) { if (!mkdir_p(PROFILE_DIR, 755)) die(PROFILE_DIR.'を作成に失敗。'); } if (!file_exists(PROFILE_DIR.$id.'.txt')) { touch(PROFILE_DIR.$id.'.txt', time()); } $profile = ''; if (is_writable(PROFILE_DIR.$id.'.txt')) { $profile = file_get_contents(PROFILE_DIR . $id . '.txt') ?? ''; } if ($profile == '') { return null; } $split = preg_split('/^-$/m', $profile, 2); $bio = mbtrim($split[1] ?? ''); return ['bio' => $bio]; } function save_profile($id, $profile) { $bio = $profile['bio'] ?? ''; if (!mkdir_p(PROFILE_DIR)) { return ['フォルダーの作成に失敗。']; } $result = file_put_contents(PROFILE_DIR . $id . '.txt', '-' . PHP_EOL . $bio . PHP_EOL); if ($result === false) { return ['ファイルの書き込みに失敗。']; } return []; } // Auth function auth_user($id, $password) { $id = trim($id); $match = find_user_row($id); if (!isset($match)) { return false; } $user = parse_user_row($match); if (hash_password($password) !== $user['hash_password']) { return false; } return $user; } // Util function find_user_row($id) { $users = file_get_contents(USERS_TSV) ?? ''; $rows = explode(PHP_EOL, $users); $search_id = $id . "\t"; foreach ($rows as $row) { if (stripos($row, $search_id) === 0) { return $row; } } return null; } function parse_user_row($row) { $array = explode("\t", $row); $id = $array[0] ?? ''; $username_raw = $array[1] ?? ''; $hash_password = $array[2] ?? ''; $trip = $array[3] ?? ''; $username = $username_raw . $trip; $array = []; return compact('id', 'username_raw', 'hash_password', 'trip', 'username'); } function hash_password($password) { $i = 0; $hash = $password; do { $hash = hash('sha512', $hash . PASSWORD_SOLT); $i++; } while ($i < PASSWORD_ITER); return $hash; }