bibis/data.php

199 行
4.6 KiB
PHP

<?php
// Data controller
// Post
require_once(__DIR__ . '/data-post.php');
// User
// ユーザー情報は USER_TSV (data/user.tsv) に保存される。
// 内容はタブ区切り・ヘッダー行は無い。
// {ID}<TAB>{名前}<TAB>{パスワードハッシュ値}<TAB>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);
$prev = null;
foreach ($rows as $row) {
$user = parse_user_row($row);
if ($user['id'] <= '') { continue; }
$user['hash_password'] = ''; // HIDE
$users_cache[$user['id']] = $user;
$user = null;
}
$rows = null;
$row = null;
return $users_cache;
}
function update_user($user) {
// 注意:ここを変更したら、必ず、ユーザーが消えない事をテストすること。
$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];
$array = null;
}
$output .= "{$id}\t{$username}\t{$hash_password}\t{$trip}" . PHP_EOL;
$id = null;
$username = null;
$hash_password = null;
$trip = null;
$changed = true;
}
$rows = null;
$row = null;
if (!$changed) { return ['ユーザーIDが不正。']; }
if (
!ftruncate($fp, 0)
|| !rewind($fp)
|| !fwrite($fp, $output)
|| !fflush($fp)
) { return ['ファイルの書き込みに失敗。']; }
@flock($fp, LOCK_UN);
@fclose($fp);
@chmod(USERS_TSV, 600);
return [];
}
function add_user($user) {
$id = $user['id'];
$username = $user['username'];
$password = $user['password'];
$match = find_user_row($id);
if (isset($match)) { return ['ID が既存ユーザーと重複。']; }
$match = null;
$hash_password = hash_password($password);
$record = "{$id}\t{$username}\t{$hash_password}\t" . PHP_EOL;
$result = file_put_contents(USERS_TSV, $record, FILE_APPEND | LOCK_EX);
if ($result === false) { return ['ファイルの書き込みに失敗。']; }
@chmod(USERS_TSV, 600);
return [];
}
// Profile (bio)
// プロフィールは PROFILE_DIR (data/profile) 配下に {ID}.txt のファイル名で保存される。
// 先頭に「-」だけの行があり、それより後の行がプロフィール本文になる。
// 将来的に「-」より前の行にユーザーごとの設定値などを記録する予定。
function load_profile($id) {
$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 ['ファイルの書き込みに失敗。']; }
@chmod(PROFILE_DIR . $id . '.txt', 600);
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;
}