コミットを比較

...

16 コミット

作成者 SHA1 メッセージ 日付
たかし 4c7393b210 [filepath_by_post_id] にディレクトリーの存在確認を追加 2023-12-07 14:57:15 +00:00
たかし e97f5827a1 Merge branch 'develop' into master 2023-12-07 14:49:52 +00:00
たかし 2d6f04d684 ksort 2023-10-22 06:42:05 +00:00
たかし 3778101278 fix bug user error 2023-10-19 15:52:12 +00:00
たかし 41ce8b4e9f Add post validation 2023-10-03 14:42:11 +00:00
たかし 549af48942 shinjitai 2023-10-01 11:32:13 +00:00
たかし 585037bcbe Add res redirect 2023-10-01 11:28:47 +00:00
たかし 730de9bfbb Add res anchor link 2023-10-01 10:28:58 +00:00
たかし 0cf538a7cb Improve reply UI 2023-09-30 14:16:30 +00:00
たかし 54e62c3bc7 Add nitter-like theme 2023-09-30 08:49:17 +00:00
たかし 4e56eb746f Fix default theme 2023-09-22 13:11:51 +00:00
たかし d831b416d0 Fix username of tl. 2023-09-19 12:23:27 +00:00
たかし f804c23d75 Add special user (tl). 2023-09-19 12:15:08 +00:00
たかし 47f2f0d82c Add deleating future-post. 2023-09-04 12:56:01 +00:00
たかし cdd3156f6b Fix session security issue. 2023-09-04 12:39:31 +00:00
たかし e0d608d46e Add login require message. 2023-09-04 12:32:28 +00:00
17個のファイルの変更447行の追加47行の削除

ファイルの表示

@ -16,6 +16,7 @@ define('SESSION_NAME', 'bibis');
date_default_timezone_set('UTC');
ini_set('zlib.output_compression', 1);
ini_set('session.use_strict_mode', 1);
if (OPEN_BASEDIR != '') {
ini_set('open_basedir', OPEN_BASEDIR);
@ -108,6 +109,14 @@ function output_csrf_token_hidden() {
// String
function anchor_to_link($s, $thread_id) {
return preg_replace(
'/>>([1-9]+[0-9]{0,10})/',
'<a href="' . sitebase('post/?id=') . $thread_id . '&amp;res=\1">&gt;&gt;\1</a>',
$s
);
}
function url_to_link($s) {
return preg_replace(
'/((http|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:\/~+#-]*[\w@?^=%&amp;\/~+#-])?)/',
@ -183,9 +192,13 @@ function get_style_css() {
// Limit and permission
function is_logged_in() {
return isset($_SESSION['user']);
}
function can_post() {
if (!post_limited()) { return false; }
if (!ENABLE_GUEST && !isset($_SESSION['user'])) { return false; }
if (!ENABLE_GUEST && !is_logged_in()) { return false; }
return true;
}

ファイルの表示

@ -116,6 +116,29 @@ function get_post_metadata($file, $deleted = false) {
return compact('id', 'is_guest', 'userid', 'detail_url', 'delete_url', 'user_url', 'datetime', 'time', 'thread_id', 'thread_title', 'thread_url', 'is_mine', 'is_future');
}
function load_res_num($id) {
$filepath = filepath_by_post_id($id);
if ($filepath === null) { return 1; }
$file = basename($filepath);
$meta = get_post_metadata($file);
$thread_id = $meta['thread_id'];
$meta = null;
if ($thread_id <= '') { return 1; }
$pattern = "2*Z*.th{$thread_id}.txt";
$list = glob(POST_DIR . $pattern);
$search = ".id{$id}.";
$i = 0;
foreach ($list as $i => $filepath) {
if (strpos(basename($filepath), $search) > 0) {
return $i + 2;
}
}
return 1; // fallback
}
function load_post($filepath) {
$files = null;
@ -123,7 +146,6 @@ function load_post($filepath) {
$deleted = $detail['deleted'];
$title = $detail['title'];
$body_raw = $detail['body'];
$body = url_to_link(nl2br(htmlspecialchars($body_raw), false));
$attachment_id = $detail['attachment_id'];
$detail = null;
@ -140,6 +162,16 @@ function load_post($filepath) {
$username = $users[$userid]['username'] ?? 'UNKNOWN';
}
$body = url_to_link(
nl2br(
anchor_to_link(
htmlspecialchars($body_raw),
$metadata['thread_id'] ?? $metadata['id']
),
false
)
);
$attachments = [];
if ($attachment_id != '') {
$attachments[] = [
@ -155,17 +187,8 @@ function load_post($filepath) {
}
function load_post_by_id($id) {
if (!file_exists(POST_DIR)) {
die(POST_DIR.': ディレクトリは存在しません。');
}
$files = glob(POST_DIR . '2*Z*.id' . $id . '.*.txt');
if (sizeof($files) !== 1) {
$files = null;
return null;
}
$filepath = $files[0];
$filepath = filepath_by_post_id($id);
if ($filepath === null) { return null; }
return load_post($filepath);
}
@ -174,6 +197,7 @@ function search_post($options = []) {
die(POST_DIR.': ディレクトリは存在しません。');
}
$includes_future = $options['includes_future'] ?? false;
$pagesize = $options['pagesize'] ?? 0;
$has_paging = $pagesize > 0;
$thread = false;
@ -185,7 +209,7 @@ function search_post($options = []) {
$options = null;
$pattern = '2*Z*.txt';
if ($key === 'userid' && $value != '') {
if ($key === 'userid' && $value != '' && $value !== 'tl') {
$pattern = "2*Z*.us{$value}.*.txt";
}
elseif ($key == 'thread' && $value >= 0) {
@ -199,14 +223,16 @@ function search_post($options = []) {
$now = date('Y-m-d\\TH:i:s\\Z');
$files = glob(POST_DIR . $pattern);
$files = array_filter(
$files,
function ($v) use($now) {
$datetime = substr(basename($v), 0, 20);
return $datetime <= $now;
},
ARRAY_FILTER_USE_BOTH
);
if (!$includes_future) {
$files = array_filter(
$files,
function ($v) use($now) {
$datetime = substr(basename($v), 0, 20);
return $datetime <= $now;
},
ARRAY_FILTER_USE_BOTH
);
}
if (!$thread) {
$files = array_reverse($files);
}
@ -338,7 +364,7 @@ function save_uploaded_image($key, $attachment_id) {
return [];
}
function delete_post($id) {
function delete_post($id, $hard = false) {
$files = glob(POST_DIR . '2*Z*.id' . $id . '.*.txt');
if (sizeof($files) !== 1) { return ['書き込みのファイルが存在しない。']; }
@ -357,7 +383,11 @@ function delete_post($id) {
}
}
$result = file_put_contents($filepath, '', LOCK_EX);
if ($hard) {
$result = unlink($filepath);
} else {
$result = file_put_contents($filepath, '', LOCK_EX);
}
if ($result === false) { return ['書き込みの削除に失敗。']; }
return [];
@ -502,6 +532,18 @@ function count_post_today() {
return $size;
}
function filepath_by_post_id($id) {
if (!file_exists(POST_DIR)) {
die(POST_DIR.': ディレクトリは存在しません。');
}
$files = glob(POST_DIR . '2*Z*.id' . $id . '.*.txt');
if (sizeof($files) !== 1) {
return null;
}
return $files[0];
}
function load_post_title_by_id($id) {
if (!file_exists(POST_DIR)) {
die(POST_DIR.': ディレクトリは存在しません。');

ファイルの表示

@ -36,6 +36,12 @@ function load_users() {
return $users_cache;
}
function load_users_with_special() {
$users = load_users();
$users['tl'] = ['id' => 'tl', 'username_raw' => SITENAME, 'username' => SITENAME, 'trip' => ''];
return $users;
}
function update_user($user) {
// 注意:ここを変更したら、必ず、ユーザーが消えない事をテストすること。
@ -129,6 +135,7 @@ function add_user($user) {
// 将来的に「-」より前の行にユーザーごとの設定値などを記録する予定。
function load_profile($id) {
if ($id === 'tl') { return ['bio' => '']; }
if (!file_exists(PROFILE_DIR)) {
if (!mkdir_p(PROFILE_DIR, 755)) die(PROFILE_DIR.'を作成に失敗。');
}
@ -140,7 +147,6 @@ function load_profile($id) {
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] ?? '');

2
public/.well-known/index.php ノーマルファイル
ファイルの表示

@ -0,0 +1,2 @@
<?php
require_once __DIR__ . '/../../activitypub/webfinger.php';

ファイルの表示

@ -25,6 +25,7 @@ function do_post() {
$user = auth_user($id, $password);
if (!$user) { return on_error(400, ['ID またはパスワードが不一致。']); }
session_regenerate_id(true);
$_SESSION['user'] = $user;
http_response_code(301);
header('Location: ' . sitebase());

ファイルの表示

@ -32,7 +32,7 @@ function do_post() {
// 削除済み投稿はuseridが空になるからここにくる。
if ($post['userid'] !== ($_SESSION['user']['id'] ?? '')) { return on_error(403, ['権限無し。']); }
$errors = delete_post($id);
$errors = delete_post($id, $post['is_future'] ?? false);
if ($errors) { return on_error('500', $errors); }
$_SESSION['messages'] = ['書き込みを削除しました。'];

ファイルの表示

@ -18,6 +18,32 @@ function do_get() {
$post = load_post_by_id($id);
if (!$post) { return on_error(400, ['書き込みが存在しない']); }
if (isset($_GET['res'])) {
// TODO:to data-post (or common?)
$res = $_GET['res'] ?? '';
if ((int)$res <= 0 || (((int)$res) . '') !== $res) { return on_error(400, ['返信番号が不正']); }
$redirect_id = null;
if ($res === '1') {
$redirect_id = $id;
}
elseif ((int)$res >= 2) {
$result = search_post(['key' => 'thread', 'value' => $id]);
foreach ($result['post_list'] as $i => $res_post) {
if ($i + 2 === (int)$res) {
$redirect_id = $res_post['id'];
break;
}
}
$result = null;
$res_post = null;
}
if ($redirect_id === null) { return on_error(404, ['返信が存在しない']); }
http_response_code(301);
header('Location: ' . sitebase('post/?id=' . $redirect_id));
return;
}
$view['post'] = $post;
$thread_id = $post['thread_id'];
@ -32,6 +58,7 @@ function do_get() {
$view['total'] = $total;
$view['reply_list'] = $result['post_list'];
}
$view['res_num'] = load_res_num($id);
$view['thread_size_over'] = ($total + 1 >= THREAD_SIZE);
$result = null;
$view['form'] = ['thread_id' => $is_reply ? $thread_id : $id];
@ -74,6 +101,9 @@ function do_post() {
$body = sanitize_post_body($body);
$error_body = validate_post_body($body);
}
if (!$error_body && $thread_id !== NULL && !(ENABLE_IMAGE && $has_file)) {
$error_body = validate_post_body_with_anchor($body);
}
$errors = array_merge($error_title, $error_body);
if ($errors) { return on_error(400, $errors); }

ファイルの表示

@ -52,6 +52,7 @@ function do_post() {
if ($errors) { return on_error(500, $errors); }
$username_raw = $username;
session_regenerate_id(true);
$_SESSION['user'] = compact('id', 'username', 'username_raw');
$_SESSION['messages'] = ['登録完了。よろしくね。'];
http_response_code(301);

ファイルの表示

@ -100,7 +100,6 @@ form ul li
label
{
display: block;
font-size: 80%;
}
button, textarea, input
@ -113,12 +112,12 @@ button, textarea, input
textarea, input
{
background: #2b2b2b;
background: #12110c;
box-sizing: border-box;
border-color: #000;
border-color: #4b4b4b;
color: #ccc;
width: 100%;
padding: 0 0.25em;
padding: 0.25em;
}
input[type='checkbox']
@ -130,15 +129,15 @@ input[type='checkbox']
input[type='file']
{
background: none;
background: #12110c;
color: #ccc;
font-size: 70%;
padding: 0.25em;
padding: 0.4em;
}
input[type='file']:hover
{
background: #4d4d4d;
background: #2b2b2b;
color: #ccc;
}

282
public/theme/nitter.css ノーマルファイル
ファイルの表示

@ -0,0 +1,282 @@
/** theme: nitter-linke **/
/** bibis-version: 0.9.3 **/
/*
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without any warranty.
*/
*
{
border: none;
font-size: 100%;
margin: 0;
padding: 0;
vertical-align: top;
}
body
{
background: #0F0F0F;
color: #F8F8F2;
line-height: 1.5;
margin: 1em;
word-break: break-all;
}
h1, h2, h3, h4, h5, h6, dl, ul, ol, p
{
background: #161616;
color: #F8F8F2;
}
h2, dl, ul
{
border-top: thin solid #3E3E35;
}
h1, h2, button, label, b
{
font-weight: 600;
}
h1, h2, ul
{
padding: 0.25em 1em;
}
h1
{
text-align: center;
}
ul
{
list-style: none;
}
dd ul
{
border: thin solid #888889;
margin: 0.5em 0 0;
}
dl
{
display: block; /*dillo*/
padding: 0.5em 1em 0;
}
dt
{
/*color: rgb(205, 205, 200);*/
font-weight: normal;
}
dd
{
clear: both; /* see: .post-time */
padding-bottom: 0.5em;
}
p
{
padding: 0 1em 0.5em;
}
form ul
{
padding-top: 0.5em;
}
form ul li
{
padding: 0 0 0.5em;
}
label
{
display: block;
}
button, textarea, input
{
background: #121212;
color: #F8F8F2;
font-family: inherit;
border: thin solid rgb(153, 69, 62);
font-size: 100%;
line-height: 1.5;
}
button:hover, textarea:hover, input:hover
{
border-color: #FF6360;
}
textarea, input
{
box-sizing: border-box;
width: 100%;
padding: 0.25em;
}
input[type='checkbox']
{
padding: 0;
vertical-align: baseline;
width: auto;
}
input[type='file']
{
background: #12110c;
color: #ccc;
font-size: 70%;
padding: 0.4em;
}
textarea
{
height: 6em;
}
button
{
background: none;
cursor: pointer;
color: #FF6360;
padding: 0.25em 1.5em;
text-align: center;
}
a
{
background: none;
color: #FF6C60;
text-decoration: none;
}
a:hover
{
text-decoration: underline;
}
/* class */
.spooftime-text
{
font-style: italic;
font-weight: 400;
}
:checked ~ .spooftime-text
{
background: #0ff;
color: #000;
font-style: normal;
font-weight: 600;
}
.form-li-submit
{
text-align: right;
}
.post-time
{
float: right;
}
a.post-time
{
color: #FF6C60;
}
.post-time.is-future
{
background: #0ff;
color: #000;
}
.pager
{
height: 3em;
line-height: 2;
padding: 1em;
}
.pager-after a,
.pager-before a
{
display: block;
}
.pager-after
{
float: left;
}
.pager-after a
{
padding: 0.5em 1em;
}
.pager-after a:hover
{
}
.pager-before
{
float: right;
}
.pager-before a
{
background: #222;
color: #FF6C60;
padding: 0.5em 1.5em 0.5em 2em;
}
.pager-before a:hover
{
background: #282828;
color: #FF6C60;
}
/* for modern browsers, and netsurf */
@media (min-width: 880.1px)
{
body
{
left: -125px;
margin: 1em auto;
position: relative;
width: 600px;
}
.menu
{
background: none;
border: none;
color: #ccc;
margin: 0.75em 0 0 600px;
position: fixed;
top: 0;
}
.menu li
{
width: 200px;
}
.menu a
{
display: block;
line-height: 2;
}
}

ファイルの表示

@ -3,7 +3,7 @@ require_once(__DIR__ . '/../../require.php');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$user_list = load_users();
ksort($user_list);
ksort($user_list, SORT_STRING | SORT_FLAG_CASE);
$view['user_list'] = $user_list;
output_html($view, ['header.php', 'user-list.php']);

ファイルの表示

@ -2,10 +2,10 @@
require_once(__DIR__ . '/../../require.php');
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$id = $_GET['id'] ?? '';
if (!strlen($id) || validate_register_id($id)) { return on_error(400, ['不正なリクエスト。']); }
$id = '' . ($_GET['id'] ?? '');
if ($id !== 'tl' && (!strlen($id) || validate_register_id($id))) { return on_error(400, ['不正なリクエスト。']); }
$users = load_users();
$users = load_users_with_special();
if (!isset($users[$id])) { return on_error(400, ['存在しない ID']); }
$view['user'] = $users[$id];
@ -24,6 +24,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
'page' => $page,
'pagesize' => POSTS_PER_PAGE,
'pager_prefix' => "?id={$id}&page=",
'includes_future' => ($id === ($_SESSION['user']['id'] ?? '')),
]);
$view['post_list'] = $result['post_list'];
$view['post_count'] = $result['total'];

ファイルの表示

@ -64,6 +64,13 @@ function validate_post_body($s) {
return [];
}
function validate_post_body_with_anchor($s) {
// アンカーのみ(返信時のデフォルト)は不許可
$s = preg_replace('/^>>[0-9]+/', '', $s);
if (mbtrim($s) === '') { return ['アンカー「>>○○」1つだけの返信は不可。']; }
return [];
}
function sanitize_post_body($s) {
return sanitize_multiline($s);
}

ファイルの表示

@ -1,7 +1,6 @@
<?php
// HTML Header, and global navigation
$view['logged_in'] = isset($_SESSION['user']);
$view['login_user'] = $_SESSION['user'] ?? null;
$view['messages'] = $_SESSION['messages'] ?? null;
$_SESSION['messages'] = null;
@ -12,7 +11,7 @@ $_SESSION['messages'] = null;
<title><?= htmlspecialchars(SITENAME); ?></title><body>
<h1><a href="<?= sitebase() ?>"><?= htmlspecialchars(SITENAME); ?></a></h1>
<ul class="menu">
<?php if ($view['logged_in']): ?>
<?php if (is_logged_in()): ?>
<li><a href="<?= sitebase('user/?id=' . htmlspecialchars(urlencode($view['login_user']['id']))) ?>"><?= '@' . htmlspecialchars($view['login_user']['id']) ?></a>
<li><a href="<?= sitebase() ?>">タイムライン・Timeline</a>
<li><a href="<?= sitebase('thread/') ?>">スレッド一覧・Threads</a>

ファイルの表示

@ -55,12 +55,15 @@ function view_post($post, $options = []) {
$html_attachment_items = join(PHP_EOL, $attachment_items);
?>
<dl<?= ($res_num > 0 ? ' id="N' . $res_num . '"' : '') ?>>
<?php if ($post['is_future'] ?? false): ?>
<dt><strong>【予約投稿】</strong></dt>
<?php endif; ?>
<?php if (!$is_single && $title != ''): ?>
<dt>件名:<b><?= $title ?></b></dt>
<?php endif; ?>
<dt><?= $res_num > 0 ? "{$res_num} " : '' ?><?= $html_user ?> <?= $html_time ?></dt>
<?php if ($link_to_thread && $thread_id != ''): ?>
<dd>RE: <a href="<?= $thread_url ?>"><?= $thread_title ?></a></dd>
<?php if ($link_to_thread && $thread_id != '' && $is_single): ?>
<dd>RE:<a href="<?= $thread_url ?>"><?= $thread_title ?></a></dd>
<?php endif; ?>
<dd><?= $body ?>
<?php if (ENABLE_ATTACHMENT && $html_attachment_items != ''): ?>

ファイルの表示

@ -1,12 +1,20 @@
<?php
// Post form
$value = '';
if (($view['res_num'] ?? 0) >= 2) {
$value = '>>' . $view['res_num'] . PHP_EOL;
}
?>
<?php if (ENABLE_POST): ?>
<?php if (isset($view['form']['thread_id']) && $view['form']['thread_id'] != ''): ?>
<h2 id="REPLY_FORM">返信・Reply</h2>
<?php endif; ?>
<?php if (!can_post()): ?>
<?php if (!post_limited()): ?>
<?php if (!ENABLE_GUEST && !is_logged_in()): ?>
<ul>
<li>書き込みするにはログインしてください。
</ul>
<?php elseif (!post_limited()): ?>
<ul>
<li>書き込み制限中。
</ul>
@ -29,11 +37,11 @@ $view['form'] = $view['form'] ?? [];
?>
<form method="POST" action="<?= sitebase('post/') ?>" enctype="multipart/form-data">
<ul>
<li><?= $view['logged_in'] ? 'ログイン中:<b>' . htmlspecialchars($view['login_user']['username']) . '</b>' : '<b>' . htmlspecialchars(GUESTNAME) . '</b>' ?>
<?php if (!(isset($view['form']['thread_id']) && $view['form']['thread_id'] != '')): ?>
<li><?= is_logged_in() ? 'ログイン中:<b>' . htmlspecialchars($view['login_user']['username']) . '</b>' : '<b>' . htmlspecialchars(GUESTNAME) . '</b>' ?>
<?php if (!(isset($view['form']['thread_id']) && $view['form']['thread_id'] > '')): ?>
<li><label for="TITLE">件名 (省略可)・Title (Optional)</label> <input type="text" id="TITLE" name="title">
<?php endif; ?>
<li><label for="BODY">本文 (500文字以内)・Text (500)</label> <textarea id="BODY" name="body" cols="40" rows="5"></textarea>
<li><label for="BODY">本文 (500文字以内)・Text (500)</label> <textarea id="BODY" name="body" cols="40" rows="5"><?= htmlspecialchars($value) ?></textarea>
<?php if (ENABLE_SPOOF_TIME): ?>
<li><label><input type="checkbox" name="spooftime" value="1"> <b class="spooftime-text">ランダム予約投稿(3H~27H遅らせる)</b></label></li>
<?php endif; ?>

ファイルの表示

@ -3,7 +3,9 @@
require __DIR__ . '/post-common.php';
$post = $view['post'] ?? [];
$title = $post['title'] ?? '';
$title = ($post['title']) ?? '';
$thread_title = ($post['thread_title']) ?? '';
$thread_url = ($post['thread_url']) ?? '';
$reply_list = $view['reply_list'] ?? [];
$total = sizeof($reply_list) + 1;
$total_view = $total > 1 ? " ($total)" : '';
@ -12,9 +14,13 @@ $total_view = $total > 1 ? " ($total)" : '';
<h2>件名:<?= htmlspecialchars("$title{$total_view}") ?></h2>
<?php elseif ($post['thread_id'] == ''): ?>
<h2><?= htmlspecialchars('無題#' . mb_substr($post['id'], 0, 7) . $total_view) ?></h2>
<?php elseif ($thread_title > ''): ?>
<h2>RE:<a href="<?= $thread_url ?>"><?= htmlspecialchars($thread_title) ?></a></h2>
<?php elseif ($post['thread_id'] <= ''): ?>
<h2>RE:<a href="<?= $thread_url ?>"><?= htmlspecialchars('無題#' . mb_substr($post['thread_id'], 0, 7)) ?></a></h2>
<?php endif; ?>
<?php
view_post($post, ['res_num' => $post['thread_id'] != '' ? 0 : 1, 'is_single' => true, 'link_to_thread' => true]);
view_post($post, ['res_num' => $post['thread_id'] != '' ? 0 : 1, 'is_single' => true, 'link_to_thread' => true, 'res_num' => $view['res_num'] ?? 1]);
?>
<?php
foreach ($reply_list as $i => $reply) {