2 // PukiWiki - Yet another WikiWikiWeb clone
5 // 2004-2017 PukiWiki Development Team
6 // 2004 teanan / Interfair Laboratory
7 // License: GPL v2 or (at your option) any later version
9 // Remote dump / restore plugin
10 // Originated as tarfile.inc.php by teanan / Interfair Laboratory 2004.
12 /////////////////////////////////////////////////
15 // Allow using resture function
16 define('PLUGIN_DUMP_ALLOW_RESTORE', FALSE); // FALSE, TRUE
18 // ページ名をディレクトリ構造に変換する際の文字コード (for mbstring)
19 define('PLUGIN_DUMP_FILENAME_ENCORDING', 'SJIS');
22 define('PLUGIN_DUMP_MAX_FILESIZE', 1024); // Kbyte
24 /////////////////////////////////////////////////
28 define('PLUGIN_DUMP_DUMP', 'dump'); // Dump & download
29 define('PLUGIN_DUMP_RESTORE', 'restore'); // Upload & restore
33 // DATA_DIR (wiki/*.txt)
34 $_STORAGE['DATA_DIR']['add_filter'] = '^[0-9A-F]+\.txt';
35 $_STORAGE['DATA_DIR']['extract_filter'] = '^' . preg_quote(DATA_DIR, '/') . '((?:[0-9A-F])+)(\.txt){0,1}';
37 // UPLOAD_DIR (attach/*)
38 $_STORAGE['UPLOAD_DIR']['add_filter'] = '^[0-9A-F_]+';
39 $_STORAGE['UPLOAD_DIR']['extract_filter'] = '^' . preg_quote(UPLOAD_DIR, '/') . '((?:[0-9A-F]{2})+)_((?:[0-9A-F])+)';
41 // BACKUP_DIR (backup/*.gz)
42 $_STORAGE['BACKUP_DIR']['add_filter'] = '^[0-9A-F]+\.gz';
43 $_STORAGE['BACKUP_DIR']['extract_filter'] = '^' . preg_quote(BACKUP_DIR, '/') . '((?:[0-9A-F])+)(\.gz){0,1}';
46 /////////////////////////////////////////////////
48 function plugin_dump_action()
52 if (PKWK_READONLY) die_message('PKWK_READONLY prohibits this');
54 $pass = isset($_POST['pass']) ? $_POST['pass'] : NULL;
55 $act = isset($vars['act']) ? $vars['act'] : NULL;
60 if (! pkwk_login($pass)) {
61 $body = "<p><strong>パスワードが違います。</strong></p>\n";
64 case PLUGIN_DUMP_DUMP:
65 $body = plugin_dump_download();
67 case PLUGIN_DUMP_RESTORE:
68 $retcode = plugin_dump_upload();
69 if ($retcode['code'] == TRUE) {
70 $msg = 'アップロードが完了しました';
72 $msg = 'アップロードに失敗しました';
74 $body .= $retcode['msg'];
75 return array('msg' => $msg, 'body' => $body);
82 $body .= plugin_dump_disp_form();
85 if (PLUGIN_DUMP_ALLOW_RESTORE) {
86 $msg = 'dump & restore';
91 return array('msg' => $msg, 'body' => $body);
94 /////////////////////////////////////////////////
96 function plugin_dump_download()
98 global $vars, $_STORAGE;
101 $arc_kind = ($vars['pcmd'] == 'tar') ? 'tar' : 'tgz';
104 $namedecode = isset($vars['namedecode']) ? TRUE : FALSE;
107 $bk_wiki = isset($vars['bk_wiki']) ? TRUE : FALSE;
108 $bk_attach = isset($vars['bk_attach']) ? TRUE : FALSE;
109 $bk_backup = isset($vars['bk_backup']) ? TRUE : FALSE;
113 $tar->create(CACHE_DIR, $arc_kind) or
114 die_message('テンポラリファイルの生成に失敗しました。');
116 if ($bk_wiki) $filecount += $tar->add_dir(DATA_DIR, $_STORAGE['DATA_DIR']['add_filter'], $namedecode);
117 if ($bk_attach) $filecount += $tar->add_dir(UPLOAD_DIR, $_STORAGE['UPLOAD_DIR']['add_filter'], $namedecode);
118 if ($bk_backup) $filecount += $tar->add_dir(BACKUP_DIR, $_STORAGE['BACKUP_DIR']['add_filter'], $namedecode);
122 if ($filecount === 0) {
123 @unlink($tar->filename);
124 return '<p><strong>ファイルがみつかりませんでした。</strong></p>';
127 download_tarfile($tar->filename, $arc_kind);
128 @unlink($tar->filename);
133 /////////////////////////////////////////////////
135 function plugin_dump_upload()
137 global $vars, $_STORAGE;
139 if (! PLUGIN_DUMP_ALLOW_RESTORE)
140 return array('code' => FALSE , 'msg' => 'Restoring function is not allowed');
142 $filename = $_FILES['upload_file']['name'];
145 if(! preg_match('/(\.tar|\.tar.gz|\.tgz)$/', $filename, $matches)){
146 die_message('Invalid file suffix');
148 $matches[1] = strtolower($matches[1]);
149 switch ($matches[1]) {
150 case '.tar': $arc_kind = 'tar'; break;
151 case '.tgz': $arc_kind = 'tar'; break;
152 case '.tar.gz': $arc_kind = 'tgz'; break;
153 default: die_message('Invalid file suffix: ' . $matches[1]); }
156 if ($_FILES['upload_file']['size'] > PLUGIN_DUMP_MAX_FILESIZE * 1024)
157 die_message('Max file size exceeded: ' . PLUGIN_DUMP_MAX_FILESIZE . 'KB');
159 // Create a temporary tar file
160 $uploadfile = tempnam(realpath(CACHE_DIR), 'tarlib_uploaded_');
162 if(! move_uploaded_file($_FILES['upload_file']['tmp_name'], $uploadfile) ||
163 ! $tar->open($uploadfile, $arc_kind)) {
164 @unlink($uploadfile);
165 die_message('ファイルがみつかりませんでした。');
168 $pattern = "(({$_STORAGE['DATA_DIR']['extract_filter']})|" .
169 "({$_STORAGE['UPLOAD_DIR']['extract_filter']})|" .
170 "({$_STORAGE['BACKUP_DIR']['extract_filter']}))";
171 $files = $tar->extract($pattern);
173 @unlink($uploadfile);
174 return array('code' => FALSE, 'msg' => '<p>展開できるファイルがありませんでした。</p>');
177 $msg = '<p><strong>展開したファイル一覧</strong><ul>';
178 foreach($files as $name) {
179 $msg .= "<li>$name</li>\n";
184 @unlink($uploadfile);
186 return array('code' => TRUE, 'msg' => $msg);
189 /////////////////////////////////////////////////
191 function download_tarfile($tempnam, $arc_kind)
193 $size = filesize($tempnam);
195 $filename = strftime('tar%Y%m%d', time());
196 if ($arc_kind == 'tgz') {
197 $filename .= '.tar.gz';
202 pkwk_common_headers();
203 header('Content-Disposition: attachment; filename="' . $filename . '"');
204 header('Content-Length: ' . $size);
205 header('Content-Type: application/octet-stream');
206 header('Pragma: no-cache');
207 // Disable output bufferring
208 while (ob_get_level()) {
215 /////////////////////////////////////////////////
217 function plugin_dump_disp_form()
221 $script = get_base_uri();
222 $act_down = PLUGIN_DUMP_DUMP;
223 $act_up = PLUGIN_DUMP_RESTORE;
224 $maxsize = PLUGIN_DUMP_MAX_FILESIZE;
230 <form action="$script" method="post">
232 <input type="hidden" name="cmd" value="dump" />
233 <input type="hidden" name="page" value="$defaultpage" />
234 <input type="hidden" name="act" value="$act_down" />
236 <p><strong>アーカイブの形式</strong>
238 <input type="radio" name="pcmd" id="_p_dump_tgz" value="tgz" checked="checked" />
239 <label for="_p_dump_tgz"> ~.tar.gz 形式</label><br />
240 <input type="radio" name="pcmd" id="_p_dump_tar" value="tar" />
241 <label for="_p_dump_tar">~.tar 形式</label>
243 <p><strong>バックアップディレクトリ</strong>
245 <input type="checkbox" name="bk_wiki" id="_p_dump_d_wiki" checked="checked" />
246 <label for="_p_dump_d_wiki">wiki</label><br />
247 <input type="checkbox" name="bk_attach" id="_p_dump_d_attach" />
248 <label for="_p_dump_d_attach">attach</label><br />
249 <input type="checkbox" name="bk_backup" id="_p_dump_d_backup" />
250 <label for="_p_dump_d_backup">backup</label><br />
252 <p><strong>オプション</strong>
254 <input type="checkbox" name="namedecode" id="_p_dump_namedecode" />
255 <label for="_p_dump_namedecode">エンコードされているページ名をディレクトリ階層つきのファイルにデコード
256 (※リストアに使うことはできなくなります。また、一部の文字は '_' に置換されます)</label><br />
258 <p><label for="_p_dump_adminpass_dump"><strong>管理者パスワード</strong></label>
259 <input type="password" name="pass" id="_p_dump_adminpass_dump" size="12" />
260 <input type="submit" name="ok" value="OK" />
266 if(PLUGIN_DUMP_ALLOW_RESTORE) {
268 <h3>データのリストア (*.tar, *.tar.gz)</h3>
269 <form enctype="multipart/form-data" action="$script" method="post">
271 <input type="hidden" name="cmd" value="dump" />
272 <input type="hidden" name="page" value="$defaultpage" />
273 <input type="hidden" name="act" value="$act_up" />
274 <p><strong>[重要] 同じ名前のデータファイルは上書きされますので、十分ご注意ください。</strong></p>
275 <p><span class="small">
276 アップロード可能な最大ファイルサイズは、$maxsize KByte までです。<br />
278 <label for="_p_dump_upload_file">ファイル:</label>
279 <input type="file" name="upload_file" id="_p_dump_upload_file" size="40" />
281 <p><label for="_p_dump_adminpass_restore"><strong>管理者パスワード</strong></label>
282 <input type="password" name="pass" id="_p_dump_adminpass_restore" size="12" />
283 <input type="submit" name="ok" value="OK" />
293 /////////////////////////////////////////////////
294 // tarlib: a class library for tar file creation and expansion
296 // Tar related definition
297 define('TARLIB_HDR_LEN', 512); // ヘッダの大きさ
298 define('TARLIB_BLK_LEN', 512); // 単位ブロック長さ
299 define('TARLIB_HDR_NAME_OFFSET', 0); // ファイル名のオフセット
300 define('TARLIB_HDR_NAME_LEN', 100); // ファイル名の最大長さ
301 define('TARLIB_HDR_MODE_OFFSET', 100); // modeへのオフセット
302 define('TARLIB_HDR_UID_OFFSET', 108); // uidへのオフセット
303 define('TARLIB_HDR_GID_OFFSET', 116); // gidへのオフセット
304 define('TARLIB_HDR_SIZE_OFFSET', 124); // サイズへのオフセット
305 define('TARLIB_HDR_SIZE_LEN', 12); // サイズの長さ
306 define('TARLIB_HDR_MTIME_OFFSET', 136); // 最終更新時刻のオフセット
307 define('TARLIB_HDR_MTIME_LEN', 12); // 最終更新時刻の長さ
308 define('TARLIB_HDR_CHKSUM_OFFSET', 148); // チェックサムのオフセット
309 define('TARLIB_HDR_CHKSUM_LEN', 8); // チェックサムの長さ
310 define('TARLIB_HDR_TYPE_OFFSET', 156); // ファイルタイプへのオフセット
313 define('TARLIB_STATUS_INIT', 0); // 初期状態
314 define('TARLIB_STATUS_OPEN', 10); // 読み取り
315 define('TARLIB_STATUS_CREATE', 20); // 書き込み
317 define('TARLIB_DATA_MODE', '100666 '); // ファイルパーミッション
318 define('TARLIB_DATA_UGID', '000000 '); // uid / gid
319 define('TARLIB_DATA_CHKBLANKS', ' ');
321 // GNU拡張仕様(ロングファイル名対応)
322 define('TARLIB_DATA_LONGLINK', '././@LongLink');
325 define('TARLIB_HDR_FILE', '0');
326 define('TARLIB_HDR_LINK', 'L');
328 // Kind of the archive
329 define('TARLIB_KIND_TGZ', 0);
330 define('TARLIB_KIND_TAR', 1);
341 $this->__construct();
343 function __construct() {
344 $this->filename = '';
346 $this->status = TARLIB_STATUS_INIT;
347 $this->arc_kind = TARLIB_KIND_TGZ;
350 ////////////////////////////////////////////////////////////
352 // 引数 : tarファイルを作成するパス
353 // 返り値: TRUE .. 成功 , FALSE .. 失敗
354 ////////////////////////////////////////////////////////////
355 function create($tempdir, $kind = 'tgz')
357 $tempnam = tempnam(realpath($tempdir), 'tarlib_create_');
358 if ($tempnam === FALSE) return FALSE;
360 if ($kind == 'tgz') {
361 $this->arc_kind = TARLIB_KIND_TGZ;
362 $this->fp = gzopen($tempnam, 'wb');
364 $this->arc_kind = TARLIB_KIND_TAR;
365 $this->fp = @fopen($tempnam, 'wb');
368 if ($this->fp === FALSE) {
372 $this->filename = $tempnam;
373 $this->dummydata = join('', array_fill(0, TARLIB_BLK_LEN, "\0"));
374 $this->status = TARLIB_STATUS_CREATE;
380 ////////////////////////////////////////////////////////////
381 // 関数 : tarファイルにディレクトリを追加する
382 // 引数 : $dir .. ディレクトリ名
383 // $mask .. 追加するファイル(正規表現)
384 // $decode .. ページ名の変換をするか
386 ////////////////////////////////////////////////////////////
387 function add_dir($dir, $mask, $decode = FALSE)
391 if ($this->status != TARLIB_STATUS_CREATE)
392 return ''; // File is not created
396 // 指定されたパスのファイルのリストを取得する
397 $dp = @opendir($dir);
399 @unlink($this->filename);
400 die_message($dir . ' is not found or not readable.');
402 while (($filename = readdir($dp)) !== FALSE) {
403 if (preg_match('/' . $mask . '/', $filename)) {
404 $files[] = $dir . $filename;
409 sort($files, SORT_STRING);
412 foreach($files as $name)
414 // Tarに格納するファイル名をdecode
415 if ($decode === FALSE) {
418 $dirname = dirname(trim($name)) . '/';
419 $filename = basename(trim($name));
420 if (preg_match("/^((?:[0-9A-F]{2})+)_((?:[0-9A-F]{2})+)/", $filename, $matches)) {
422 $filename = decode($matches[1]) . '/' . decode($matches[2]);
424 $pattern = '^((?:[0-9A-F]{2})+)((\.txt|\.gz)*)$';
425 if (preg_match("/$pattern/", $filename, $matches)) {
426 $filename = decode($matches[1]) . $matches[2];
429 $filename = str_replace(':', '_', $filename);
430 $filename = str_replace('\\', '_', $filename);
433 $filename = $dirname . $filename;
435 if (function_exists('mb_convert_encoding'))
436 $filename = mb_convert_encoding($filename, PLUGIN_DUMP_FILENAME_ENCORDING);
440 $mtime = filemtime($name);
443 if (strlen($filename) > TARLIB_HDR_NAME_LEN) {
445 $size = strlen($filename);
447 $tar_data = $this->_make_header(TARLIB_DATA_LONGLINK, $size, $mtime, TARLIB_HDR_LINK);
449 $this->_write_data(join('', $tar_data), $filename, $size);
453 $size = filesize($name);
454 if ($size === FALSE) {
455 @unlink($this->filename);
456 die_message($name . ' is not found or not readable.');
460 $tar_data = $this->_make_header($filename, $size, $mtime, TARLIB_HDR_FILE);
463 $fpr = @fopen($name , 'rb');
464 flock($fpr, LOCK_SH);
465 $data = fread($fpr, $size);
466 flock($fpr, LOCK_UN);
470 $this->_write_data(join('', $tar_data), $data, $size);
476 ////////////////////////////////////////////////////////////
477 // 関数 : tarのヘッダ情報を生成する (add)
478 // 引数 : $filename .. ファイル名
481 // $typeflag .. TypeFlag (file/link)
483 ////////////////////////////////////////////////////////////
484 function _make_header($filename, $size, $mtime, $typeflag)
486 $tar_data = array_fill(0, TARLIB_HDR_LEN, "\0");
489 for($i = 0; $i < strlen($filename); $i++ ) {
490 if ($i < TARLIB_HDR_NAME_LEN) {
491 $tar_data[$i + TARLIB_HDR_NAME_OFFSET] = $filename{$i};
498 $modeid = TARLIB_DATA_MODE;
499 for($i = 0; $i < strlen($modeid); $i++ ) {
500 $tar_data[$i + TARLIB_HDR_MODE_OFFSET] = $modeid{$i};
504 $ugid = TARLIB_DATA_UGID;
505 for($i = 0; $i < strlen($ugid); $i++ ) {
506 $tar_data[$i + TARLIB_HDR_UID_OFFSET] = $ugid{$i};
507 $tar_data[$i + TARLIB_HDR_GID_OFFSET] = $ugid{$i};
511 $strsize = sprintf('%11o', $size);
512 for($i = 0; $i < strlen($strsize); $i++ ) {
513 $tar_data[$i + TARLIB_HDR_SIZE_OFFSET] = $strsize{$i};
517 $strmtime = sprintf('%o', $mtime);
518 for($i = 0; $i < strlen($strmtime); $i++ ) {
519 $tar_data[$i + TARLIB_HDR_MTIME_OFFSET] = $strmtime{$i};
523 $chkblanks = TARLIB_DATA_CHKBLANKS;
524 for($i = 0; $i < strlen($chkblanks); $i++ ) {
525 $tar_data[$i + TARLIB_HDR_CHKSUM_OFFSET] = $chkblanks{$i};
529 $tar_data[TARLIB_HDR_TYPE_OFFSET] = $typeflag;
533 for($i = 0; $i < TARLIB_BLK_LEN; $i++ ) {
534 $sum += 0xff & ord($tar_data[$i]);
536 $strchksum = sprintf('%7o',$sum);
537 for($i = 0; $i < strlen($strchksum); $i++ ) {
538 $tar_data[$i + TARLIB_HDR_CHKSUM_OFFSET] = $strchksum{$i};
544 ////////////////////////////////////////////////////////////
545 // 関数 : tarデータのファイル出力 (add)
546 // 引数 : $header .. tarヘッダ情報
550 ////////////////////////////////////////////////////////////
551 function _write_data($header, $body, $size)
553 $fixsize = ceil($size / TARLIB_BLK_LEN) * TARLIB_BLK_LEN - $size;
555 if ($this->arc_kind == TARLIB_KIND_TGZ) {
556 gzwrite($this->fp, $header, TARLIB_HDR_LEN); // Header
557 gzwrite($this->fp, $body, $size); // Body
558 gzwrite($this->fp, $this->dummydata, $fixsize); // Padding
560 fwrite($this->fp, $header, TARLIB_HDR_LEN); // Header
561 fwrite($this->fp, $body, $size); // Body
562 fwrite($this->fp, $this->dummydata, $fixsize); // Padding
566 ////////////////////////////////////////////////////////////
569 // 返り値: TRUE .. 成功 , FALSE .. 失敗
570 ////////////////////////////////////////////////////////////
571 function open($name = '', $kind = 'tgz')
573 if (! PLUGIN_DUMP_ALLOW_RESTORE) return FALSE; // Not allowed
575 if ($name != '') $this->filename = $name;
577 if ($kind == 'tgz') {
578 $this->arc_kind = TARLIB_KIND_TGZ;
579 $this->fp = gzopen($this->filename, 'rb');
581 $this->arc_kind = TARLIB_KIND_TAR;
582 $this->fp = fopen($this->filename, 'rb');
585 if ($this->fp === FALSE) {
586 return FALSE; // No such file
588 $this->status = TARLIB_STATUS_OPEN;
594 ////////////////////////////////////////////////////////////
595 // 関数 : 指定したディレクトリにtarファイルを展開する
596 // 引数 : 展開するファイルパターン(正規表現)
598 // 補足 : ARAIさんのattachプラグインパッチを参考にしました
599 ////////////////////////////////////////////////////////////
600 function extract($pattern)
602 if ($this->status != TARLIB_STATUS_OPEN) return ''; // Not opened
608 $buff = fread($this->fp, TARLIB_HDR_LEN);
609 if (strlen($buff) != TARLIB_HDR_LEN) break;
613 if ($longname != '') {
614 $name = $longname; // LongLink対応
617 for ($i = 0; $i < TARLIB_HDR_NAME_LEN; $i++ ) {
618 if ($buff{$i + TARLIB_HDR_NAME_OFFSET} != "\0") {
619 $name .= $buff{$i + TARLIB_HDR_NAME_OFFSET};
627 if ($name == '') break; // 展開終了
629 // チェックサムを取得しつつ、ブランクに置換していく
631 $chkblanks = TARLIB_DATA_CHKBLANKS;
632 for ($i = 0; $i < TARLIB_HDR_CHKSUM_LEN; $i++ ) {
633 $checksum .= $buff{$i + TARLIB_HDR_CHKSUM_OFFSET};
634 $buff{$i + TARLIB_HDR_CHKSUM_OFFSET} = $chkblanks{$i};
636 list($checksum) = sscanf('0' . trim($checksum), '%i');
640 for($i = 0; $i < TARLIB_BLK_LEN; $i++ ) {
641 $sum += 0xff & ord($buff{$i});
643 if ($sum != $checksum) break; // Error
647 for ($i = 0; $i < TARLIB_HDR_SIZE_LEN; $i++ ) {
648 $size .= $buff{$i + TARLIB_HDR_SIZE_OFFSET};
650 list($size) = sscanf('0' . trim($size), '%i');
653 // データブロックは512byteでパディングされている
654 $pdsz = ceil($size / TARLIB_BLK_LEN) * TARLIB_BLK_LEN;
658 for ($i = 0; $i < TARLIB_HDR_MTIME_LEN; $i++ ) {
659 $strmtime .= $buff{$i + TARLIB_HDR_MTIME_OFFSET};
661 list($mtime) = sscanf('0' . trim($strmtime), '%i');
664 // $type = $buff{TARLIB_HDR_TYPE_OFFSET};
666 if ($name == TARLIB_DATA_LONGLINK) {
668 $buff = fread($this->fp, $pdsz);
669 $longname = substr($buff, 0, $size);
670 } else if (preg_match("/$pattern/", $name) ) {
671 // } else if ($type == 0 && preg_match("/$pattern/", $name) ) {
672 $buff = fread($this->fp, $pdsz);
674 // 既に同じファイルがある場合は上書きされる
675 $fpw = @fopen($name, 'wb');
676 if ($fpw !== FALSE) {
677 flock($fpw, LOCK_EX);
678 fwrite($fpw, $buff, $size);
680 @touch($name, $mtime);
681 flock($fpw, LOCK_UN);
688 @fseek($this->fp, $pdsz, SEEK_CUR);
694 ////////////////////////////////////////////////////////////
698 ////////////////////////////////////////////////////////////
701 if ($this->status == TARLIB_STATUS_CREATE) {
703 if ($this->arc_kind == TARLIB_KIND_TGZ) {
705 gzwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
706 gzwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
710 fwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
711 fwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
714 } else if ($this->status == TARLIB_STATUS_OPEN) {
715 if ($this->arc_kind == TARLIB_KIND_TGZ) {
722 $this->status = TARLIB_STATUS_INIT;