OSDN Git Service

initial
[charactermanaj/CharacterManaJStorage.git] / cgi-bin / upload.php
1 <?php
2 ////////////////////////////////////
3 // zipファイルのアップロードのCGI
4 ////////////////////////////////////
5
6 ini_set('display_errors', 1);
7 require_once __DIR__ . '/common.php';
8 require_once __DIR__ . '/access_ctrl.php';
9 require_once __DIR__ . '/calc_trip.php';
10 require_once __DIR__ . '/read_catalog.php';
11
12 header("Content-type: application/json; charset=utf-8");
13
14 // パラメータの取り出し
15
16 $title = isset($_POST['title']) ? $_POST['title'] : '';
17 $author = isset($_POST['author']) ? $_POST['author'] : '';
18 $delkey = isset($_POST['delkey']) ? $_POST['delkey'] : '';
19 $license = isset($_POST['license']) ? $_POST['license'] : '';
20 $comment = isset($_POST['comment']) ? $_POST['comment'] : '';
21 $validation = isset($_POST['validation']) ? ($_POST['validation'] == 'true') : false;
22
23 $result = ['result' => 'error'];
24
25 // フォームの入力データが大きすぎればエラーとして何もしない。
26 if (strlen($title) > 5000 || strlen($author) > 500 ||
27     strlen($delkey) > 500 || strlen($license) > 500 ||
28     strlen($comment) > 20000) {
29     echo json_encode($result);
30     exit;
31 }
32
33 $hostaddr = get_hostaddr();
34
35 // 一時ファイルができているか(アップロードされているか)チェック
36 if (isset($_FILES['file'])) {
37     $file = $_FILES['file'];
38     if ($file['error'] == 0) {
39         $ext = pathinfo($file['name'], PATHINFO_EXTENSION);
40         $fsize = $file['size'];
41         if (($ext != 'zip' && $ext != 'cmj') || $fsize < 1000) {
42             // 拡張子がzip, cmjでない、もしくは異常に小さい(1kb以下はありえない)
43             $result = ['result' => 'error', 'message' => '不正なZIPファイルです。'];
44     
45         } else {
46             // zipの中身のチェック
47             $result = check_zip($file['tmp_name'], $validation);
48             if ($result['result'] == 'ok') {
49                 // 一時ファイルから所定フォルダへの退避
50                 $fname = sha1_file($file['tmp_name']) . '.' . $ext;
51                 $destzip = ZIPDIR . $fname;
52                 if (move_uploaded_file($file['tmp_name'], $destzip)) {
53                     // データベースへの登録
54                     $result = append_entry($fname, $fsize, $title, $author, $delkey, $license, $comment, $hostaddr);
55                     if ($result['result'] == 'ok') {
56                         // catalog.txtの解析とデータベースへの登録
57                         $zipid = $result['id'];
58                         search_zip_catalog($destzip, $zipid, $hostaddr);
59                     }
60                 } else {
61                     $result = ['result' => 'error', 'message' => 'ファイルの保存に失敗しました。'];
62                 }
63             }
64         }
65     } else {
66         $result = ['result' => 'error', 'message' => 'ZIPファイルのアップロードでエラーが発生しました。err=' . $file['error']];
67     }
68 } else {
69     $result = ['result' => 'error', 'message' => 'ZIPファイルがアップロードされていません。'];
70 }
71
72 echo json_encode($result);
73
74 /**
75  * ZIPの中身を検証する
76  * 
77  * zipファイルが正しく、png, jpeg, txt以外を含んでいないこと
78  * 各サイズが800kb以下のこと。
79  * 
80  * @param string $zipfile ZIPファイルのパス
81  * @return array 結果を格納したarray
82  */
83 function check_zip($zipfile, $validation) {
84     $error_files = [];
85     $large_files = [];
86
87     $za = new ZipArchive();
88     $res = $za->open($zipfile);
89     if ($res === TRUE) {
90         $filecnt = ['png' => 0, 'txt' => 0, 'jpeg' => 0, 'jpg' => 0,
91             'ini' => 0, 'xml' => 0, 'gif' => 0, 'root_png' => 0];
92         $hasCatalog = false;
93         $hasReadme = false;
94         for ($i = 0; $i < $za->numFiles; $i++) { 
95             $stat = $za->statIndex($i); 
96             $entryname = $stat['name'];
97             $entryname = str_replace('\\', '/', $entryname); // バックスラッシュで入っているzipもあり
98             if (!preg_match('/\/$/', $entryname)) {
99                 // フォルダ以外なら、ファイル名と拡張子、サイズを取り出す
100                 $dirname = pathinfo($entryname, PATHINFO_DIRNAME);
101                 $fname = pathinfo($entryname, PATHINFO_FILENAME);
102                 $ext = strtolower(pathinfo($entryname, PATHINFO_EXTENSION));
103                 $lcname = strtolower(pathinfo($entryname, PATHINFO_BASENAME));
104                 $fsize = $stat['size'];
105                 if ($fname != 'license' && $fname != 'readme' && $fname != 'read me' &&
106                     $lcname != 'thumbs.db' && $lcname != '.ds_store' &&
107                     $ext != 'txt' && $ext != 'ini' && $ext != 'xml' && $ext != 'cpd' &&
108                     $ext != 'png' && $ext != 'jpg' && $ext != 'jpeg' && $ext != 'gif') {
109                     // 対象外のファイル拡張子、またはファイル名
110                     $error_files[] = $entryname;
111                 } else {
112                     // ファイル数のカウント
113                     if ($ext == 'png' && $dirname == '.') {
114                         @$filecnt['root_png']++; // 親ディレクトリ上にあるPNGの数(パーツではない)
115                     } else {
116                         @$filecnt[$ext]++; // @で初回undefuned警告を握りつぶす
117                     }
118
119                     // カタログファイルか?
120                     if ($lcname == 'catalog.txt') {
121                         $hasCatalog = true;
122                     }
123                     // readmeか?
124                     if ($fname == 'readme' || $fname == 'read me') {
125                         $hasReadme = true;
126                     }
127
128                     // ファイルサイズが大きすぎる
129                     if (!(($ext == 'png' && $fsize <= MAX_PNG_SIZE) || 
130                           ($ext != 'xml' && $fsize <= MAX_CONTENT_SIZE))) {
131                         // pngならMAX_PNG_SIZE以下、xml以外ならMAX_CONTENT_SIZE以下のこと。
132                         // そうでなければ大きすぎる.
133                         $large_files[] = $entryname;
134                     }
135
136                     // validationの場合は、隠しファイル, gifもエラーにする
137                     if ($validation) {
138                         if ($lcname == 'thumbs.db' || $lcname == '.ds_store') {
139                             $error_files[] = $entryname;
140                         }
141                         if ($ext == 'gif') {
142                             $error_files[] = $entryname;
143                         }
144                     }
145                 }
146             }
147         }
148         $za->close();
149         if ($validation && (!$hasCatalog || !$hasReadme)) {
150             return ['result' => 'error',
151                 'message' => 'ZIPファイル内にreadme.txt、またはcatalog.txtが含まれていません。',
152                 'error_files' => $error_files, 'large_files' => $large_files];
153         } else if (count($error_files) > 0 || count($large_files) > 0) {
154             return ['result' => 'error',
155                 'message' => 'ZIPファイル内に許可されない、もしくは大きすぎるファイルが含まれています。',
156                 'error_files' => $error_files, 'large_files' => $large_files];
157         } else if ($filecnt['png'] == 0) {
158             return ['result' => 'error',
159                 'message' => 'ZIPファイル内に有効なパーツ画像がありません'];
160         } else if (($filecnt['root_png'] + $filecnt['jpg'] + $filecnt['jpeg'] +
161              $filecnt['gif'] + $filecnt['txt'] + $filecnt['xml']) > 5) {
162             return ['result' => 'error',
163                 'message' => 'ZIPファイル内にパーツ画像以外のファイルが多数あります。'];
164         }
165         return ['result' => 'ok', 'message' => 'valid zip'];
166     }
167     return ['result' => 'error', 'message' => 'ZIPファイルが不正です', 'error_code' => $res];
168 }
169
170 /**
171  * データベースにエントリを追加または更新する
172  * 
173  * @param string $fname ファイル名
174  * @param integer $fsize ファイルサイズ
175  * @param string $title タイトル
176  * @param string $author 作者名
177  * @param string $delkey 削除キー
178  * @param integer $license ライセンスタイプ
179  * @param string $fncommentame コメント
180  * @param string $hostaddr リモートアドレス
181  */
182 function append_entry($fname, $fsize, $title, $author, $delkey, $license, $comment, $hostaddr) {
183     $pdo = create_pdo();
184     
185     // アップロード回数制限中か?
186     $accessCtrl = new AccessCtrl($pdo);
187     if (!$accessCtrl->register($hostaddr) || $accessCtrl->getFailCount($hostaddr) > MAX_FAIL_COUNT) {
188         return [
189             'result' => 'error',
190             'message' => 'アップロード制限中です。しばらくお待ちください。'
191         ];
192     }
193
194     // エントリの確認
195     $fetch = $pdo->prepare('SELECT id, author, delkey FROM ZIP_ENTRIES WHERE fname = ?');
196     $fetch->execute([$fname]);
197     $row = $fetch->fetch();
198     $fetch = null;
199
200     if ($row === FALSE) {
201         // 新規挿入
202         $stmt = $pdo->prepare('INSERT INTO ZIP_ENTRIES(fname, fsize, title, author, delkey,
203             license, comment, hostaddr)
204             VALUES (:fname, :fsize, :title, :author, :delkey, :license, :comment, :hostaddr)');
205         $stmt->execute(['fname' => $fname, 'fsize' => $fsize, 'title' => $title, 'author' => $author,
206             'delkey' => $delkey, 'license' => $license, 'comment' => $comment,
207             'hostaddr' => $hostaddr]);
208         $id = $pdo->lastInsertId();
209         append_log("insert ${id} ${fname} ${fsize} ${author} ${delkey} ${title}");
210         $stmt = null;
211
212     } else {
213         // 更新
214         $id = $row['id'];
215         $prev_author = $row['author'];
216         $prev_delkey = $row['delkey'];
217
218         $passphrase = get_trip_pass($author);
219         $prev_passphrase = get_trip_pass($prev_author);
220         if (($prev_passphrase == '' && $delkey == $prev_delkey) ||
221             ($prev_passphrase != '' && $prev_passphrase == $passphrase)) {
222             // トリップ指定がない場合はdelkeyの一致、トリップの指定がある場合はトリップの一致をもって書き換え可とする
223             append_log("update ${id}");
224             $stmt = $pdo->prepare('UPDATE ZIP_ENTRIES
225                 SET title = :title, author = :author, delkey = :delkey,
226                     license = :license, comment = :comment, hostaddr = :hostaddr
227                 WHERE id = :id');
228             $stmt->execute(['id' => $id, 'title' => $title, 'author' => $author,
229                 'delkey' => $delkey, 'license' => $license, 'comment' => $comment,
230                 'hostaddr' => $hostaddr]);
231             $stmt = null;
232         } else {
233             append_log("unmatch delkey. zipid:${id}");
234             $accessCtrl->failIncrement($hostaddr);
235             $accessCtrl = null;
236             $pdo = null;
237             return ['result' => 'error', 'message' => 'トリップまたはdelkeyが一致しません'];
238         }
239     }
240     $accessCtrl = null;
241     $pdo = null;
242     return ['result' => 'ok', 'id' => $id, 'hostaddr' => $hostaddr];
243 }
244 ?>