7 * LICENSE: This source file is licensed under the terms of the GNU General Public License.
9 * @package Magic3 Framework
10 * @author 平田直毅(Naoki Hirata) <naoki@aplo.co.jp>
11 * @copyright Copyright 2006-2009 Magic3 Project.
12 * @license http://www.gnu.org/copyleft/gpl.html GPL License
13 * @version SVN: $Id: dump.inc.php 1601 2009-03-21 05:51:06Z fishbone $
14 * @link http://www.magic3.org
16 // Remote dump / restore plugin
17 // Originated as tarfile.inc.php by teanan / Interfair Laboratory 2004.
19 /////////////////////////////////////////////////
22 // Allow using resture function
23 define('PLUGIN_DUMP_ALLOW_RESTORE', FALSE); // FALSE, TRUE
25 // ページ名をディレクトリ構造に変換する際の文字コード (for mbstring)
26 define('PLUGIN_DUMP_FILENAME_ENCORDING', 'SJIS');
29 define('PLUGIN_DUMP_MAX_FILESIZE', 1024); // Kbyte
31 /////////////////////////////////////////////////
35 define('PLUGIN_DUMP_DUMP', 'dump'); // Dump & download
36 define('PLUGIN_DUMP_RESTORE', 'restore'); // Upload & restore
40 // DATA_DIR (wiki/*.txt)
41 $_STORAGE['DATA_DIR']['add_filter'] = '^[0-9A-F]+\.txt';
42 $_STORAGE['DATA_DIR']['extract_filter'] = '^' . preg_quote(DATA_DIR, '/') . '((?:[0-9A-F])+)(\.txt){0,1}';
44 // UPLOAD_DIR (attach/*)
45 $_STORAGE['UPLOAD_DIR']['add_filter'] = '^[0-9A-F_]+';
46 $_STORAGE['UPLOAD_DIR']['extract_filter'] = '^' . preg_quote(UPLOAD_DIR, '/') . '((?:[0-9A-F]{2})+)_((?:[0-9A-F])+)';
48 // BACKUP_DIR (backup/*.gz)
49 $_STORAGE['BACKUP_DIR']['add_filter'] = '^[0-9A-F]+\.gz';
50 $_STORAGE['BACKUP_DIR']['extract_filter'] = '^' . preg_quote(BACKUP_DIR, '/') . '((?:[0-9A-F])+)(\.gz){0,1}';
53 /////////////////////////////////////////////////
55 function plugin_dump_action()
59 if (PKWK_READONLY) die_message('PKWK_READONLY prohibits this');
61 $pass = isset($_POST['pass']) ? $_POST['pass'] : NULL;
62 $act = isset($vars['act']) ? $vars['act'] : NULL;
67 if (! pkwk_login($pass)) {
68 $body = "<p><strong>パスワードが違います。</strong></p>\n";
71 case PLUGIN_DUMP_DUMP:
72 $body = plugin_dump_download();
74 case PLUGIN_DUMP_RESTORE:
75 $retcode = plugin_dump_upload();
76 if ($retcode['code'] == TRUE) {
77 $msg = 'アップロードが完了しました';
79 $msg = 'アップロードに失敗しました';
81 $body .= $retcode['msg'];
82 return array('msg' => $msg, 'body' => $body);
89 $body .= plugin_dump_disp_form();
92 if (PLUGIN_DUMP_ALLOW_RESTORE) {
93 $msg = 'dump & restore';
98 return array('msg' => $msg, 'body' => $body);
101 /////////////////////////////////////////////////
103 function plugin_dump_download()
105 global $vars, $_STORAGE;
108 $arc_kind = ($vars['pcmd'] == 'tar') ? 'tar' : 'tgz';
111 $namedecode = isset($vars['namedecode']) ? TRUE : FALSE;
114 $bk_wiki = isset($vars['bk_wiki']) ? TRUE : FALSE;
115 $bk_attach = isset($vars['bk_attach']) ? TRUE : FALSE;
116 $bk_backup = isset($vars['bk_backup']) ? TRUE : FALSE;
120 $tar->create(CACHE_DIR, $arc_kind) or
121 die_message('テンポラリファイルの生成に失敗しました。');
123 if ($bk_wiki) $filecount += $tar->add_dir(DATA_DIR, $_STORAGE['DATA_DIR']['add_filter'], $namedecode);
124 if ($bk_attach) $filecount += $tar->add_dir(UPLOAD_DIR, $_STORAGE['UPLOAD_DIR']['add_filter'], $namedecode);
125 if ($bk_backup) $filecount += $tar->add_dir(BACKUP_DIR, $_STORAGE['BACKUP_DIR']['add_filter'], $namedecode);
129 if ($filecount === 0) {
130 @unlink($tar->filename);
131 return '<p><strong>ファイルがみつかりませんでした。</strong></p>';
134 download_tarfile($tar->filename, $arc_kind);
135 @unlink($tar->filename);
140 /////////////////////////////////////////////////
142 function plugin_dump_upload()
144 global $vars, $_STORAGE;
146 if (! PLUGIN_DUMP_ALLOW_RESTORE)
147 return array('code' => FALSE , 'msg' => 'Restoring function is not allowed');
149 $filename = $_FILES['upload_file']['name'];
152 if(! preg_match('/(\.tar|\.tar.gz|\.tgz)$/', $filename, $matches)){
153 die_message('Invalid file suffix');
155 $matches[1] = strtolower($matches[1]);
156 switch ($matches[1]) {
157 case '.tar': $arc_kind = 'tar'; break;
158 case '.tgz': $arc_kind = 'tar'; break;
159 case '.tar.gz': $arc_kind = 'tgz'; break;
160 default: die_message('Invalid file suffix: ' . $matches[1]); }
163 if ($_FILES['upload_file']['size'] > PLUGIN_DUMP_MAX_FILESIZE * 1024)
164 die_message('Max file size exceeded: ' . PLUGIN_DUMP_MAX_FILESIZE . 'KB');
166 // Create a temporary tar file
167 $uploadfile = tempnam(realpath(CACHE_DIR), 'tarlib_uploaded_');
169 if(! move_uploaded_file($_FILES['upload_file']['tmp_name'], $uploadfile) ||
170 ! $tar->open($uploadfile, $arc_kind)) {
171 @unlink($uploadfile);
172 die_message('ファイルがみつかりませんでした。');
175 $pattern = "(({$_STORAGE['DATA_DIR']['extract_filter']})|" .
176 "({$_STORAGE['UPLOAD_DIR']['extract_filter']})|" .
177 "({$_STORAGE['BACKUP_DIR']['extract_filter']}))";
178 $files = $tar->extract($pattern);
180 @unlink($uploadfile);
181 return array('code' => FALSE, 'msg' => '<p>展開できるファイルがありませんでした。</p>');
184 $msg = '<p><strong>展開したファイル一覧</strong><ul>';
185 foreach($files as $name) {
186 $msg .= "<li>$name</li>\n";
191 @unlink($uploadfile);
193 return array('code' => TRUE, 'msg' => $msg);
196 /////////////////////////////////////////////////
198 function download_tarfile($tempnam, $arc_kind)
200 $size = filesize($tempnam);
202 $filename = strftime('tar%Y%m%d', time());
203 if ($arc_kind == 'tgz') {
204 $filename .= '.tar.gz';
209 pkwk_common_headers();
210 header('Content-Disposition: attachment; filename="' . $filename . '"');
211 header('Content-Length: ' . $size);
212 header('Content-Type: application/octet-stream');
213 header('Pragma: no-cache');
217 /////////////////////////////////////////////////
219 function plugin_dump_disp_form()
221 global $script, $defaultpage;
223 $act_down = PLUGIN_DUMP_DUMP;
224 $act_up = PLUGIN_DUMP_RESTORE;
225 $maxsize = PLUGIN_DUMP_MAX_FILESIZE;
231 <form action="$script" method="post" class="form">
233 <input type="hidden" name="cmd" value="dump" />
234 <input type="hidden" name="page" value="$defaultpage" />
235 <input type="hidden" name="act" value="$act_down" />
237 <p><strong>アーカイブの形式</strong>
239 <input type="radio" name="pcmd" id="_p_dump_tgz" value="tgz" checked="checked" />
240 <label for="_p_dump_tgz">~.tar.gz 形式</label><br />
241 <input type="radio" name="pcmd" id="_p_dump_tar" value="tar" />
242 <label for="_p_dump_tar">~.tar 形式</label>
244 <p><strong>バックアップディレクトリ</strong>
246 <input type="checkbox" name="bk_wiki" id="_p_dump_d_wiki" checked="checked" />
247 <label for="_p_dump_d_wiki">wiki</label><br />
248 <input type="checkbox" name="bk_attach" id="_p_dump_d_attach" />
249 <label for="_p_dump_d_attach">attach</label><br />
250 <input type="checkbox" name="bk_backup" id="_p_dump_d_backup" />
251 <label for="_p_dump_d_backup">backup</label><br />
253 <p><strong>オプション</strong>
255 <input type="checkbox" name="namedecode" id="_p_dump_namedecode" />
256 <label for="_p_dump_namedecode">エンコードされているページ名をディレクトリ階層つきのファイルにデコード
257 (※リストアに使うことはできなくなります。また、一部の文字は '_' に置換されます)</label><br />
259 <p><label for="_p_dump_adminpass_dump"><strong>管理者パスワード</strong></label>
260 <input type="password" name="pass" id="_p_dump_adminpass_dump" size="12" />
261 <input type="submit" name="ok" class="button" value="OK" />
267 if(PLUGIN_DUMP_ALLOW_RESTORE) {
269 <h3>データのリストア (*.tar, *.tar.gz)</h3>
270 <form enctype="multipart/form-data" action="$script" method="post" class="form">
272 <input type="hidden" name="cmd" value="dump" />
273 <input type="hidden" name="page" value="$defaultpage" />
274 <input type="hidden" name="act" value="$act_up" />
275 <p><strong>[重要] 同じ名前のデータファイルは上書きされますので、十分ご注意ください。</strong></p>
276 <p><span class="small">
277 アップロード可能な最大ファイルサイズは、$maxsize KByte までです。<br />
279 <label for="_p_dump_upload_file">ファイル:</label>
280 <input type="file" name="upload_file" id="_p_dump_upload_file" size="40" />
282 <p><label for="_p_dump_adminpass_restore"><strong>管理者パスワード</strong></label>
283 <input type="password" name="pass" id="_p_dump_adminpass_restore" size="12" />
284 <input type="submit" name="ok" class="button" value="OK" />
294 /////////////////////////////////////////////////
295 // tarlib: a class library for tar file creation and expansion
297 // Tar related definition
298 define('TARLIB_HDR_LEN', 512); // ヘッダの大きさ
299 define('TARLIB_BLK_LEN', 512); // 単位ブロック長さ
300 define('TARLIB_HDR_NAME_OFFSET', 0); // ファイル名のオフセット
301 define('TARLIB_HDR_NAME_LEN', 100); // ファイル名の最大長さ
302 define('TARLIB_HDR_MODE_OFFSET', 100); // modeへのオフセット
303 define('TARLIB_HDR_UID_OFFSET', 108); // uidへのオフセット
304 define('TARLIB_HDR_GID_OFFSET', 116); // gidへのオフセット
305 define('TARLIB_HDR_SIZE_OFFSET', 124); // サイズへのオフセット
306 define('TARLIB_HDR_SIZE_LEN', 12); // サイズの長さ
307 define('TARLIB_HDR_MTIME_OFFSET', 136); // 最終更新時刻のオフセット
308 define('TARLIB_HDR_MTIME_LEN', 12); // 最終更新時刻の長さ
309 define('TARLIB_HDR_CHKSUM_OFFSET', 148); // チェックサムのオフセット
310 define('TARLIB_HDR_CHKSUM_LEN', 8); // チェックサムの長さ
311 define('TARLIB_HDR_TYPE_OFFSET', 156); // ファイルタイプへのオフセット
314 define('TARLIB_STATUS_INIT', 0); // 初期状態
315 define('TARLIB_STATUS_OPEN', 10); // 読み取り
316 define('TARLIB_STATUS_CREATE', 20); // 書き込み
318 define('TARLIB_DATA_MODE', '100666 '); // ファイルパーミッション
319 define('TARLIB_DATA_UGID', '000000 '); // uid / gid
320 define('TARLIB_DATA_CHKBLANKS', ' ');
322 // GNU拡張仕様(ロングファイル名対応)
323 define('TARLIB_DATA_LONGLINK', '././@LongLink');
326 define('TARLIB_HDR_FILE', '0');
327 define('TARLIB_HDR_LINK', 'L');
329 // Kind of the archive
330 define('TARLIB_KIND_TGZ', 0);
331 define('TARLIB_KIND_TAR', 1);
343 $this->filename = '';
345 $this->status = TARLIB_STATUS_INIT;
346 $this->arc_kind = TARLIB_KIND_TGZ;
349 ////////////////////////////////////////////////////////////
351 // 引数 : tarファイルを作成するパス
352 // 返り値: TRUE .. 成功 , FALSE .. 失敗
353 ////////////////////////////////////////////////////////////
354 function create($tempdir, $kind = 'tgz')
356 $tempnam = tempnam(realpath($tempdir), 'tarlib_create_');
357 if ($tempnam === FALSE) return FALSE;
359 if ($kind == 'tgz') {
360 $this->arc_kind = TARLIB_KIND_TGZ;
361 $this->fp = gzopen($tempnam, 'wb');
363 $this->arc_kind = TARLIB_KIND_TAR;
364 $this->fp = @fopen($tempnam, 'wb');
367 if ($this->fp === FALSE) {
371 $this->filename = $tempnam;
372 $this->dummydata = join('', array_fill(0, TARLIB_BLK_LEN, "\0"));
373 $this->status = TARLIB_STATUS_CREATE;
379 ////////////////////////////////////////////////////////////
380 // 関数 : tarファイルにディレクトリを追加する
381 // 引数 : $dir .. ディレクトリ名
382 // $mask .. 追加するファイル(正規表現)
383 // $decode .. ページ名の変換をするか
385 ////////////////////////////////////////////////////////////
386 function add_dir($dir, $mask, $decode = FALSE)
390 if ($this->status != TARLIB_STATUS_CREATE)
391 return ''; // File is not created
395 // 指定されたパスのファイルのリストを取得する
396 $dp = @opendir($dir);
398 @unlink($this->filename);
399 die_message($dir . ' is not found or not readable.');
402 while ($filename = readdir($dp)) {
403 if (preg_match("/$mask/", $filename))
404 $files[] = $dir . $filename;
411 foreach($files as $name)
413 // Tarに格納するファイル名をdecode
414 if ($decode === FALSE) {
417 $dirname = dirname(trim($name)) . '/';
418 $filename = basename(trim($name));
419 if (preg_match("/^((?:[0-9A-F]{2})+)_((?:[0-9A-F]{2})+)/", $filename, $matches)) {
421 $filename = decode($matches[1]) . '/' . decode($matches[2]);
423 $pattern = '^((?:[0-9A-F]{2})+)((\.txt|\.gz)*)$';
424 if (preg_match("/$pattern/", $filename, $matches)) {
425 $filename = decode($matches[1]) . $matches[2];
428 $filename = str_replace(':', '_', $filename);
429 $filename = str_replace('\\', '_', $filename);
432 $filename = $dirname . $filename;
434 if (function_exists('mb_convert_encoding'))
435 $filename = mb_convert_encoding($filename, PLUGIN_DUMP_FILENAME_ENCORDING);
439 $mtime = filemtime($name);
442 if (strlen($filename) > TARLIB_HDR_NAME_LEN) {
444 $size = strlen($filename);
446 $tar_data = $this->_make_header(TARLIB_DATA_LONGLINK, $size, $mtime, TARLIB_HDR_LINK);
448 $this->_write_data(join('', $tar_data), $filename, $size);
452 $size = filesize($name);
453 if ($size === FALSE) {
454 @unlink($this->filename);
455 die_message($name . ' is not found or not readable.');
459 $tar_data = $this->_make_header($filename, $size, $mtime, TARLIB_HDR_FILE);
462 $fpr = @fopen($name , 'rb');
463 flock($fpr, LOCK_SH);
464 $data = fread($fpr, $size);
465 flock($fpr, LOCK_UN);
469 $this->_write_data(join('', $tar_data), $data, $size);
475 ////////////////////////////////////////////////////////////
476 // 関数 : tarのヘッダ情報を生成する (add)
477 // 引数 : $filename .. ファイル名
480 // $typeflag .. TypeFlag (file/link)
482 ////////////////////////////////////////////////////////////
483 function _make_header($filename, $size, $mtime, $typeflag)
485 $tar_data = array_fill(0, TARLIB_HDR_LEN, "\0");
488 for($i = 0; $i < strlen($filename); $i++ ) {
489 if ($i < TARLIB_HDR_NAME_LEN) {
490 $tar_data[$i + TARLIB_HDR_NAME_OFFSET] = $filename{$i};
497 $modeid = TARLIB_DATA_MODE;
498 for($i = 0; $i < strlen($modeid); $i++ ) {
499 $tar_data[$i + TARLIB_HDR_MODE_OFFSET] = $modeid{$i};
503 $ugid = TARLIB_DATA_UGID;
504 for($i = 0; $i < strlen($ugid); $i++ ) {
505 $tar_data[$i + TARLIB_HDR_UID_OFFSET] = $ugid{$i};
506 $tar_data[$i + TARLIB_HDR_GID_OFFSET] = $ugid{$i};
510 $strsize = sprintf('%11o', $size);
511 for($i = 0; $i < strlen($strsize); $i++ ) {
512 $tar_data[$i + TARLIB_HDR_SIZE_OFFSET] = $strsize{$i};
516 $strmtime = sprintf('%o', $mtime);
517 for($i = 0; $i < strlen($strmtime); $i++ ) {
518 $tar_data[$i + TARLIB_HDR_MTIME_OFFSET] = $strmtime{$i};
522 $chkblanks = TARLIB_DATA_CHKBLANKS;
523 for($i = 0; $i < strlen($chkblanks); $i++ ) {
524 $tar_data[$i + TARLIB_HDR_CHKSUM_OFFSET] = $chkblanks{$i};
528 $tar_data[TARLIB_HDR_TYPE_OFFSET] = $typeflag;
532 for($i = 0; $i < TARLIB_BLK_LEN; $i++ ) {
533 $sum += 0xff & ord($tar_data[$i]);
535 $strchksum = sprintf('%7o',$sum);
536 for($i = 0; $i < strlen($strchksum); $i++ ) {
537 $tar_data[$i + TARLIB_HDR_CHKSUM_OFFSET] = $strchksum{$i};
543 ////////////////////////////////////////////////////////////
544 // 関数 : tarデータのファイル出力 (add)
545 // 引数 : $header .. tarヘッダ情報
549 ////////////////////////////////////////////////////////////
550 function _write_data($header, $body, $size)
552 $fixsize = ceil($size / TARLIB_BLK_LEN) * TARLIB_BLK_LEN - $size;
554 if ($this->arc_kind == TARLIB_KIND_TGZ) {
555 gzwrite($this->fp, $header, TARLIB_HDR_LEN); // Header
556 gzwrite($this->fp, $body, $size); // Body
557 gzwrite($this->fp, $this->dummydata, $fixsize); // Padding
559 fwrite($this->fp, $header, TARLIB_HDR_LEN); // Header
560 fwrite($this->fp, $body, $size); // Body
561 fwrite($this->fp, $this->dummydata, $fixsize); // Padding
565 ////////////////////////////////////////////////////////////
568 // 返り値: TRUE .. 成功 , FALSE .. 失敗
569 ////////////////////////////////////////////////////////////
570 function open($name = '', $kind = 'tgz')
572 if (! PLUGIN_DUMP_ALLOW_RESTORE) return FALSE; // Not allowed
574 if ($name != '') $this->filename = $name;
576 if ($kind == 'tgz') {
577 $this->arc_kind = TARLIB_KIND_TGZ;
578 $this->fp = gzopen($this->filename, 'rb');
580 $this->arc_kind = TARLIB_KIND_TAR;
581 $this->fp = fopen($this->filename, 'rb');
584 if ($this->fp === FALSE) {
585 return FALSE; // No such file
587 $this->status = TARLIB_STATUS_OPEN;
593 ////////////////////////////////////////////////////////////
594 // 関数 : 指定したディレクトリにtarファイルを展開する
595 // 引数 : 展開するファイルパターン(正規表現)
597 // 補足 : ARAIさんのattachプラグインパッチを参考にしました
598 ////////////////////////////////////////////////////////////
599 function extract($pattern)
601 if ($this->status != TARLIB_STATUS_OPEN) return ''; // Not opened
607 $buff = fread($this->fp, TARLIB_HDR_LEN);
608 if (strlen($buff) != TARLIB_HDR_LEN) break;
612 if ($longname != '') {
613 $name = $longname; // LongLink対応
616 for ($i = 0; $i < TARLIB_HDR_NAME_LEN; $i++ ) {
617 if ($buff{$i + TARLIB_HDR_NAME_OFFSET} != "\0") {
618 $name .= $buff{$i + TARLIB_HDR_NAME_OFFSET};
626 if ($name == '') break; // 展開終了
628 // チェックサムを取得しつつ、ブランクに置換していく
630 $chkblanks = TARLIB_DATA_CHKBLANKS;
631 for ($i = 0; $i < TARLIB_HDR_CHKSUM_LEN; $i++ ) {
632 $checksum .= $buff{$i + TARLIB_HDR_CHKSUM_OFFSET};
633 $buff{$i + TARLIB_HDR_CHKSUM_OFFSET} = $chkblanks{$i};
635 list($checksum) = sscanf('0' . trim($checksum), '%i');
639 for($i = 0; $i < TARLIB_BLK_LEN; $i++ ) {
640 $sum += 0xff & ord($buff{$i});
642 if ($sum != $checksum) break; // Error
646 for ($i = 0; $i < TARLIB_HDR_SIZE_LEN; $i++ ) {
647 $size .= $buff{$i + TARLIB_HDR_SIZE_OFFSET};
649 list($size) = sscanf('0' . trim($size), '%i');
652 // データブロックは512byteでパディングされている
653 $pdsz = ceil($size / TARLIB_BLK_LEN) * TARLIB_BLK_LEN;
657 for ($i = 0; $i < TARLIB_HDR_MTIME_LEN; $i++ ) {
658 $strmtime .= $buff{$i + TARLIB_HDR_MTIME_OFFSET};
660 list($mtime) = sscanf('0' . trim($strmtime), '%i');
663 // $type = $buff{TARLIB_HDR_TYPE_OFFSET};
665 if ($name == TARLIB_DATA_LONGLINK) {
667 $buff = fread($this->fp, $pdsz);
668 $longname = substr($buff, 0, $size);
669 } else if (preg_match("/$pattern/", $name) ) {
670 // } else if ($type == 0 && preg_match("/$pattern/", $name) ) {
671 $buff = fread($this->fp, $pdsz);
673 // 既に同じファイルがある場合は上書きされる
674 $fpw = @fopen($name, 'wb');
675 if ($fpw !== FALSE) {
676 flock($fpw, LOCK_EX);
677 fwrite($fpw, $buff, $size);
679 @touch($name, $mtime);
680 flock($fpw, LOCK_UN);
687 @fseek($this->fp, $pdsz, SEEK_CUR);
693 ////////////////////////////////////////////////////////////
697 ////////////////////////////////////////////////////////////
700 if ($this->status == TARLIB_STATUS_CREATE) {
702 if ($this->arc_kind == TARLIB_KIND_TGZ) {
704 gzwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
705 gzwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
709 fwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
710 fwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
713 } else if ($this->status == TARLIB_STATUS_OPEN) {
714 if ($this->arc_kind == TARLIB_KIND_TGZ) {
721 $this->status = TARLIB_STATUS_INIT;