こんにちは、技術猫です。
(初投稿です)
ApacheとPHP(とTailwind CSS)でファイルサーバーを作ってみました。
この記事では、ログイン機能に必要なJSONファイルの読み取りの方法や、
ファイルのアップロード機能の実装方法などを紹介します。
そしておまけで、PHPでのコマンドの実行方法などを紹介します。
コード内のコメントアウトされてる部分を読むと意味がわかるよ!
この記事で紹介するコードの一部は、セキュリティが万全ではない可能性があります。
社外などに公開することは推奨しません!
ソース
作成した全部のソースコードはGitHubのreleaseにてMITライセンスで配布しています。
一応ApacheとPHPがあれば使えます。(テスト環境はmacOS)
ログイン機能
ログイン機能は、最初はパスワードハッシュをPHP内で書いていましたが、
後から出てくる「ユーザー追加機能」という機能が必要なため、
Jsonファイルに保存することになりましたが...
早速間違えた!
プログラミング初心者なので、JSONファイルをPHPで読み込む際に、変な書き方をしていました。
{"users":[
{
"username":"admin",
"password":"$2y$10$3dmC2Lq8r/9B8F83NrLqMulwkwuk.QgJsvQXXumwX83k6mQzmjQP2"
}
]}
<?php
require_once __DIR__ . '/functions.php';
require_unlogined_session();
function get_password($username) {
$json = file_get_contents('http://192.168.1.100/tCloud/users.json');
$datas = json_decode($json, true);
$name = $datas['users']
return $name[$username]['password'];
# users.jsonの中でも、"users"の中にはusernameはなく、"users"の1番目の中にusernameがある。
}
$username = filter_input(INPUT_POST, 'username');
$password = filter_input(INPUT_POST, 'password');
if ($username == "rsa"){
header("Location: rsa.php");
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (password_verify($password,get_password($username))){ # なので、ここでエラー。
session_regenerate_id(true);
$_SESSION['username'] = $username;
header('Location: /tCloud/');
exit;
} else {
http_response_code(403);
}
}
header('Content-Type: text/html; charset=UTF-8');
# この先省略
Microsoft Copilot(Bing)に教えてもらった修正点を使って修正した後はこちら:
<?php
require_once __DIR__ . '/functions.php';
require_unlogined_session();
function get_password($username) {
$json = file_get_contents('http://192.168.1.100/tCloud/users.json');
$datas = json_decode($json, true);
foreach ($datas['users'] as $name) {
if ($name['username'] == $username) {
return $name['password']; #ちゃんと全部読み取ってみて
}
}
return null; # なかったらNull
}
$username = filter_input(INPUT_POST, 'username');
$password = filter_input(INPUT_POST, 'password');
if ($username == "rsa"){
header("Location: rsa.php");
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (password_verify($password,get_password($username))){
session_regenerate_id(true);
$_SESSION['username'] = $username;
header('Location: /tCloud/');
exit;
} else {
http_response_code(403);
}
}
header('Content-Type: text/html; charset=UTF-8');
# この先省略
という感じです。
何番目かを指定する必要があるので、foreachにする必要があることにこの時気がつきました。
ストレージのアップロード機能
アップロードは一つずつ調べて完成しました。
ユーザーごとのストレージがあるのと、共有ドライブがある感じです。
<?php
require_once __DIR__ . '/../functions.php';
require_logined_session();
header('Content-Type: text/html; charset=UTF-8');
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Share File</title>
<link rel="stylesheet" href="/dist/output.css" />
</head>
<body class="text-black bg-white">
<div class="flex flex-col h-screen">
<div class="">
<font color="blue">
<a href="#" onclick="history.back()"> < back </a>
</font>
</div>
<div>
<div class="flex justify-center mt-20">
<div class="w-9/12 border rounded-3xl bg-gray-200 dark:bg-gray-200">
<div class="my-16 text-center">
<img src="/link.png" style="display: block; margin: auto;">
<br><br>
<h2 class="text-xl">Upload</h2>
<font color="orange">
<?php
$temporary_file = $_FILES['user_file_name']['tmp_name']; # 一時ファイル名
$true_file = $_FILES['user_file_name']['name']; # 本来のファイル名
# is_uploaded_fileメソッドで、一時的にアップロードされたファイルが本当にアップロード処理されたかの確認
if (is_uploaded_file($temporary_file)) {
if (move_uploaded_file($temporary_file , $true_file )) {
echo "Upload completed.
The file is located at the following URL:<br>
<a href='http://192.168.1.100:8080/tCloud/files/" . $true_file . "'>http://192.168.1.100:8080/files/" . $true_file . "</a>";
} else {
echo "Upload failed.";
}
} else {
echo "Upload failed.";
}
?>
</font>
<br><br>
<form enctype="multipart/form-data" action="upload.php" method="POST">
<input type="hidden" name="name" value="value" />
<input name="user_file_name" type="file" />
<input type="submit" value="Upload" />
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
おまけ: Admin Console
そしておまけ機能として、Admin Consoleというものを作りました。
ユーザーの追加を管理者ができるようなツールです。
<?php
require_once __DIR__ . '/../functions.php';
require_logined_session();
$username = $_SESSION['username'];
if ($username == "admin"){
} else{
header("Location: /403.php");
exit();
}
header('Content-Type: text/html; charset=UTF-8');
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Add User - tCloud</title>
<link rel="stylesheet" href="/dist/output.css" />
</head>
<body class="text-black bg-white">
<div class="flex flex-col h-screen">
<div class="">
<br>
</div>
<div>
<div class="flex justify-center mt-20">
<div class="text-center">
<img src="/icon-128x128.png" style="display: block; margin: auto;">
<h1 class="text-5xl">tCloud</h1>
Admin Console
<br><br>
<form action="adduser.php" method="POST" class="mt-12 disabled:bg-slate-50 disabled:text-slate-500 disabled:shadow-none invalid:text-pink-600 focus:invalid:ring-pink-500">
<div class="mb-3">
<input
type="text"
placeholder="ID"
name="username"
required
class="w-80 focus:ring-2 focus:ring-blue-500 focus:outline-none appearance-none text-sm text-slate-900 placeholder-slate-400 rounded-md py-2 ring-1 ring-slate-200 shadow-sm"
/>
</div>
<div class="mb-5">
<input
type="text"
placeholder=" PW"
name="password"
minlength="6"
class="w-80 focus:ring-2 focus:ring-blue-500 focus:outline-none appearance-none text-sm text-slate-900 placeholder-slate-400 rounded-md py-2 ring-1 ring-slate-200 shadow-sm"
/>
</div>
<input type="hidden" name="token" value="<?=h(generate_token())?>">
<button type="submit">
Create User
</button>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
処理側は二つあって、
/tCloud/admin/adduser.phpと、
/tCloud/drive/adduser.phpがあります。
やっていることはコマンド実行とファイル作成です。
adminフォルダのadduser.phpは、
<?php
require_once __DIR__ . '/../functions.php';
require_logined_session();
$username = $_SESSION['username'];
if ($username == "admin"){
} else{
header("Location: /403.php");
exit();
}
$user = [
'username' => $_POST['username'],
'password' => password_hash($_POST['password'], PASSWORD_BCRYPT)
];
$json = file_get_contents('http://192.168.1.100/tCloud/users.json'); # jsonを取得
$data = json_decode($json, true); # jsonデータを配列へ
$data['users'][] = $user; # jsonファイルの中の'users'の中を指定
$json = json_encode($data); # 配列をjsonデータへ
file_put_contents('../users.json', $json); # 'users'に追記
header("Location: ../drive/adduser.php?username=" . $_POST['username']);
# driveのadduser.phpに移動
?>
という感じです。
driveのadduser.phpに移動したら、以下のファイルが読み込まれます。
<?php
require_once __DIR__ . '/../functions.php';
require_logined_session();
$username = $_SESSION['username'];
if ($username == "admin"){
} else{
header("Location: /403.php");
exit();
}
exec("mkdir " . $_GET['username']); # ディレクトリ(フォルダ)作成
exec("chmod 777 " . $_GET['username']); # 読み込み・書き込み権限つける
exec("touch " . $_GET['username'] . "/index.php"); # index.php作成
exec("chmod 777 " . $_GET['username'] . "/index.php"); # 読み込み・書き込み権限つける
$file = fopen($_GET['username'] . "/index.php", "w");# ファイルを開く
fwrite($file, '
<?php
require_once __DIR__ . "/../../functions.php";
require_logined_session();
$username = $_SESSION["username"];
if ($username == "' . $_GET['username'] . '"){
} else{
header("Location: /403.php");
exit();
}
header("Content-Type: text/html; charset=UTF-8");
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Your Drive</title>
<link rel="stylesheet" href="/dist/output.css" />
</head>
<body class="text-black bg-white">
<div class="flex flex-col h-screen">
<div class="">
<font color="blue">
<a href="#" onclick="history.back()"> < back </a>
</font>
</div>
<div>
<div class="flex justify-center mt-20">
<div class="w-9/12 border rounded-3xl bg-gray-200 dark:bg-gray-200">
<div class="my-16 text-center">
<img src="/drive.png" style="display: block; margin: auto;">
<br><br>
<h2 class="text-xl">Upload</h2>
<form enctype="multipart/form-data" action="upload.php" method="POST">
<input type="hidden" name="name" value="value" />
<input name="user_file_name" type="file" />
<input type="submit" value="Upload" />
</form>
<br><br>
<h2 class="text-xl">Files</h2>
<?PHP
$dirpath = "./";
$dirlist = dir($dirpath);
while( $filename = $dirlist->read() ){
if( (is_dir($filename) == false) && ($filename!=".." || $filename!= "." ) ){
if ($filename !== "index.php" && $filename !== "upload.php"){
print("<a href=\"/download.php?author=chii&path=" . $filename . "\"><button>".$filename."</button></a><br>\n");
}
}
}
$dirlist->close();
?>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
'); # ファイルに書き込み
fclose($file); # ファイルを閉じる
exec("touch " . $_GET['username'] . "/upload.php"); # upload.phpを作成
exec("chmod 777 " . $_GET['username'] . "/upload.php"); # 読み込み・書き込み権限をつける
$file = fopen($_GET['username'] . "/upload.php", "w"); # upload.phpを開く
fwrite($file, '
<?php
require_once __DIR__ . "/../../functions.php";
require_logined_session();
header("Content-Type: text/html; charset=UTF-8");
$username = $_SESSION["username"];
if ($username == "' . $_GET['username'] . '"){
} else{
header("Location: /403.php");
exit();
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Your Drive</title>
<link rel="stylesheet" href="/dist/output.css" />
</head>
<body class="text-black bg-white">
<div class="flex flex-col h-screen">
<div class="">
<font color="blue">
<a href="#" onclick="history.back()"> < back </a>
</font>
</div>
<div>
<div class="flex justify-center mt-20">
<div class="w-9/12 border rounded-3xl bg-gray-200 dark:bg-gray-200">
<div class="my-16 text-center">
<img src="/drive.png" style="display: block; margin: auto;">
<br><br>
<h2 class="text-xl">Upload</h2>
<font color="orange">
<?php
$temporary_file = $_FILES["user_file_name"]["tmp_name"];
$true_file = $_FILES["user_file_name"]["name"];
if (is_uploaded_file($temporary_file)) {
if (move_uploaded_file($temporary_file , $true_file )) {
echo $true_file . " is Uploaded";
} else {
echo "Upload failed";
}
} else {
echo "Upload failed";
}
var_dump($true_file);
?>
</font>
<br><br>
<form enctype="multipart/form-data" action="upload.php" method="POST">
<input type="hidden" name="name" value="value" />
<input name="user_file_name" type="file" />
<input type="submit" value="Upload" />
</form>
<br><br>
<h2 class="text-xl">Files</h2>
<?PHP
$dirpath = "./";
$dirlist = dir($dirpath);
while( $filename = $dirlist->read() ){
if( (is_dir($filename) == false) && ($filename!=".." || $filename!= "." ) ){
if ($filename !== "index.php" && $filename !== "upload.php"){
print("<a href=\"/download.php?author=chii&path=" . $filename . "\"><button>".$filename."</button></a><br>\n");
}
}
}
$dirlist->close();
?>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
'); # ファイルに書き込み
fclose($file); # ファイルを閉じる
header("Location: ../admin/index.php"); # 最初の画面に戻る
?>
ちょっと作るの難しかったです...
終わりに
どうだったでしょうか。
初投稿なのでまだ書き方など難しいですが、
JSONの読み取り方法など参考になれば幸いです。
GitHubのreleaseでも公開しているのでぜひみてみてください。
見てくれてありがとうございました。
それではまた他の記事で!