2 // $Id: dump.inc.php,v 1.41 2007/11/03 15:17:52 henoheno Exp $
4 // 2004-2007 PukiWiki Developers Team
5 // 2004 teanan / Interfair Laboratory
6 // License: GPL v2 or (at your option) any later version
8 // Remote dump / restore plugin
9 // Originated as tarfile.inc.php by teanan / Interfair Laboratory 2004.
11 /////////////////////////////////////////////////
14 // Allow using resture function
15 define('PLUGIN_DUMP_ALLOW_RESTORE', FALSE); // FALSE, TRUE
17 // ページ名をディレクトリ構造に変換する際の文字コード (for mbstring)
18 define('PLUGIN_DUMP_FILENAME_ENCORDING', 'SJIS');
21 define('PLUGIN_DUMP_MAX_FILESIZE', 1024); // Kbyte
23 /////////////////////////////////////////////////
27 define('PLUGIN_DUMP_DUMP', 'dump'); // Dump & download
28 define('PLUGIN_DUMP_RESTORE', 'restore'); // Upload & restore
32 // DATA_DIR (wiki/*.txt)
33 $_STORAGE['DATA_DIR']['add_filter'] = '^[0-9A-F]+\.txt';
34 $_STORAGE['DATA_DIR']['extract_filter'] = '^' . preg_quote(DATA_DIR, '/') . '((?:[0-9A-F])+)(\.txt){0,1}';
36 // UPLOAD_DIR (attach/*)
37 $_STORAGE['UPLOAD_DIR']['add_filter'] = '^[0-9A-F_]+';
38 $_STORAGE['UPLOAD_DIR']['extract_filter'] = '^' . preg_quote(UPLOAD_DIR, '/') . '((?:[0-9A-F]{2})+)_((?:[0-9A-F])+)';
40 // BACKUP_DIR (backup/*.gz)
41 $_STORAGE['BACKUP_DIR']['add_filter'] = '^[0-9A-F]+\.gz';
42 $_STORAGE['BACKUP_DIR']['extract_filter'] = '^' . preg_quote(BACKUP_DIR, '/') . '((?:[0-9A-F])+)(\.gz){0,1}';
45 /////////////////////////////////////////////////
47 function plugin_dump_action()
51 if (PKWK_READONLY) die_message('PKWK_READONLY prohibits this');
53 $pass = isset($_POST['pass']) ? $_POST['pass'] : NULL;
54 $act = isset($vars['act']) ? $vars['act'] : NULL;
59 if (! pkwk_login($pass)) {
60 $body = "<p><strong>パスワードが違います。</strong></p>\n";
63 case PLUGIN_DUMP_DUMP:
64 $body = plugin_dump_download();
66 case PLUGIN_DUMP_RESTORE:
67 $retcode = plugin_dump_upload();
68 if ($retcode['code'] == TRUE) {
69 $msg = 'アップロードが完了しました';
71 $msg = 'アップロードに失敗しました';
73 $body .= $retcode['msg'];
74 return array('msg' => $msg, 'body' => $body);
81 $body .= plugin_dump_disp_form();
84 if (PLUGIN_DUMP_ALLOW_RESTORE) {
85 $msg = 'dump & restore';
90 return array('msg' => $msg, 'body' => $body);
93 /////////////////////////////////////////////////
95 function plugin_dump_download()
97 global $vars, $_STORAGE;
100 $arc_kind = ($vars['pcmd'] == 'tar') ? 'tar' : 'tgz';
103 $namedecode = isset($vars['namedecode']) ? TRUE : FALSE;
106 $bk_wiki = isset($vars['bk_wiki']) ? TRUE : FALSE;
107 $bk_attach = isset($vars['bk_attach']) ? TRUE : FALSE;
108 $bk_backup = isset($vars['bk_backup']) ? TRUE : FALSE;
112 $tar->create(CACHE_DIR, $arc_kind) or
113 die_message('テンポラリファイルの生成に失敗しました。');
115 if ($bk_wiki) $filecount += $tar->add_dir(DATA_DIR, $_STORAGE['DATA_DIR']['add_filter'], $namedecode);
116 if ($bk_attach) $filecount += $tar->add_dir(UPLOAD_DIR, $_STORAGE['UPLOAD_DIR']['add_filter'], $namedecode);
117 if ($bk_backup) $filecount += $tar->add_dir(BACKUP_DIR, $_STORAGE['BACKUP_DIR']['add_filter'], $namedecode);
121 if ($filecount === 0) {
122 @unlink($tar->filename);
123 return '<p><strong>ファイルがみつかりませんでした。</strong></p>';
126 download_tarfile($tar->filename, $arc_kind);
127 @unlink($tar->filename);
132 /////////////////////////////////////////////////
134 function plugin_dump_upload()
136 global $vars, $_STORAGE;
138 if (! PLUGIN_DUMP_ALLOW_RESTORE)
139 return array('code' => FALSE , 'msg' => 'Restoring function is not allowed');
141 $filename = $_FILES['upload_file']['name'];
144 if(! preg_match('/(\.tar|\.tar.gz|\.tgz)$/', $filename, $matches)){
145 die_message('Invalid file suffix');
147 $matches[1] = strtolower($matches[1]);
148 switch ($matches[1]) {
149 case '.tar': $arc_kind = 'tar'; break;
150 case '.tgz': $arc_kind = 'tar'; break;
151 case '.tar.gz': $arc_kind = 'tgz'; break;
152 default: die_message('Invalid file suffix: ' . $matches[1]); }
155 if ($_FILES['upload_file']['size'] > PLUGIN_DUMP_MAX_FILESIZE * 1024)
156 die_message('Max file size exceeded: ' . PLUGIN_DUMP_MAX_FILESIZE . 'KB');
158 // Create a temporary tar file
159 $uploadfile = tempnam(realpath(CACHE_DIR), 'tarlib_uploaded_');
161 if(! move_uploaded_file($_FILES['upload_file']['tmp_name'], $uploadfile) ||
162 ! $tar->open($uploadfile, $arc_kind)) {
163 @unlink($uploadfile);
164 die_message('ファイルがみつかりませんでした。');
167 $pattern = "(({$_STORAGE['DATA_DIR']['extract_filter']})|" .
168 "({$_STORAGE['UPLOAD_DIR']['extract_filter']})|" .
169 "({$_STORAGE['BACKUP_DIR']['extract_filter']}))";
170 $files = $tar->extract($pattern);
172 @unlink($uploadfile);
173 return array('code' => FALSE, 'msg' => '<p>展開できるファイルがありませんでした。</p>');
176 $msg = '<p><strong>展開したファイル一覧</strong><ul>';
177 foreach($files as $name) {
178 $msg .= "<li>$name</li>\n";
183 @unlink($uploadfile);
185 return array('code' => TRUE, 'msg' => $msg);
188 /////////////////////////////////////////////////
190 function download_tarfile($tempnam, $arc_kind)
192 $size = filesize($tempnam);
194 $filename = strftime('tar%Y%m%d', time());
195 if ($arc_kind == 'tgz') {
196 $filename .= '.tar.gz';
201 pkwk_common_headers();
202 header('Content-Disposition: attachment; filename="' . $filename . '"');
203 header('Content-Length: ' . $size);
204 header('Content-Type: application/octet-stream');
205 header('Pragma: no-cache');
209 /////////////////////////////////////////////////
211 function plugin_dump_disp_form()
213 global $script, $defaultpage;
215 $act_down = PLUGIN_DUMP_DUMP;
216 $act_up = PLUGIN_DUMP_RESTORE;
217 $maxsize = PLUGIN_DUMP_MAX_FILESIZE;
223 <form action="$script" method="post">
225 <input type="hidden" name="cmd" value="dump" />
226 <input type="hidden" name="page" value="$defaultpage" />
227 <input type="hidden" name="act" value="$act_down" />
229 <p><strong>アーカイブの形式</strong>
231 <input type="radio" name="pcmd" id="_p_dump_tgz" value="tgz" checked="checked" />
232 <label for="_p_dump_tgz"> ~.tar.gz 形式</label><br />
233 <input type="radio" name="pcmd" id="_p_dump_tar" value="tar" />
234 <label for="_p_dump_tar">~.tar 形式</label>
236 <p><strong>バックアップディレクトリ</strong>
238 <input type="checkbox" name="bk_wiki" id="_p_dump_d_wiki" checked="checked" />
239 <label for="_p_dump_d_wiki">wiki</label><br />
240 <input type="checkbox" name="bk_attach" id="_p_dump_d_attach" />
241 <label for="_p_dump_d_attach">attach</label><br />
242 <input type="checkbox" name="bk_backup" id="_p_dump_d_backup" />
243 <label for="_p_dump_d_backup">backup</label><br />
245 <p><strong>オプション</strong>
247 <input type="checkbox" name="namedecode" id="_p_dump_namedecode" />
248 <label for="_p_dump_namedecode">エンコードされているページ名をディレクトリ階層つきのファイルにデコード
249 (※リストアに使うことはできなくなります。また、一部の文字は '_' に置換されます)</label><br />
251 <p><label for="_p_dump_adminpass_dump"><strong>管理者パスワード</strong></label>
252 <input type="password" name="pass" id="_p_dump_adminpass_dump" size="12" />
253 <input type="submit" name="ok" value="OK" />
259 if(PLUGIN_DUMP_ALLOW_RESTORE) {
261 <h3>データのリストア (*.tar, *.tar.gz)</h3>
262 <form enctype="multipart/form-data" action="$script" method="post">
264 <input type="hidden" name="cmd" value="dump" />
265 <input type="hidden" name="page" value="$defaultpage" />
266 <input type="hidden" name="act" value="$act_up" />
267 <p><strong>[重要] 同じ名前のデータファイルは上書きされますので、十分ご注意ください。</strong></p>
268 <p><span class="small">
269 アップロード可能な最大ファイルサイズは、$maxsize KByte までです。<br />
271 <label for="_p_dump_upload_file">ファイル:</label>
272 <input type="file" name="upload_file" id="_p_dump_upload_file" size="40" />
274 <p><label for="_p_dump_adminpass_restore"><strong>管理者パスワード</strong></label>
275 <input type="password" name="pass" id="_p_dump_adminpass_restore" size="12" />
276 <input type="submit" name="ok" value="OK" />
286 /////////////////////////////////////////////////
287 // tarlib: a class library for tar file creation and expansion
289 // Tar related definition
290 define('TARLIB_HDR_LEN', 512); // ヘッダの大きさ
291 define('TARLIB_BLK_LEN', 512); // 単位ブロック長さ
292 define('TARLIB_HDR_NAME_OFFSET', 0); // ファイル名のオフセット
293 define('TARLIB_HDR_NAME_LEN', 100); // ファイル名の最大長さ
294 define('TARLIB_HDR_MODE_OFFSET', 100); // modeへのオフセット
295 define('TARLIB_HDR_UID_OFFSET', 108); // uidへのオフセット
296 define('TARLIB_HDR_GID_OFFSET', 116); // gidへのオフセット
297 define('TARLIB_HDR_SIZE_OFFSET', 124); // サイズへのオフセット
298 define('TARLIB_HDR_SIZE_LEN', 12); // サイズの長さ
299 define('TARLIB_HDR_MTIME_OFFSET', 136); // 最終更新時刻のオフセット
300 define('TARLIB_HDR_MTIME_LEN', 12); // 最終更新時刻の長さ
301 define('TARLIB_HDR_CHKSUM_OFFSET', 148); // チェックサムのオフセット
302 define('TARLIB_HDR_CHKSUM_LEN', 8); // チェックサムの長さ
303 define('TARLIB_HDR_TYPE_OFFSET', 156); // ファイルタイプへのオフセット
306 define('TARLIB_STATUS_INIT', 0); // 初期状態
307 define('TARLIB_STATUS_OPEN', 10); // 読み取り
308 define('TARLIB_STATUS_CREATE', 20); // 書き込み
310 define('TARLIB_DATA_MODE', '100666 '); // ファイルパーミッション
311 define('TARLIB_DATA_UGID', '000000 '); // uid / gid
312 define('TARLIB_DATA_CHKBLANKS', ' ');
314 // GNU拡張仕様(ロングファイル名対応)
315 define('TARLIB_DATA_LONGLINK', '././@LongLink');
318 define('TARLIB_HDR_FILE', '0');
319 define('TARLIB_HDR_LINK', 'L');
321 // Kind of the archive
322 define('TARLIB_KIND_TGZ', 0);
323 define('TARLIB_KIND_TAR', 1);
335 $this->filename = '';
337 $this->status = TARLIB_STATUS_INIT;
338 $this->arc_kind = TARLIB_KIND_TGZ;
341 ////////////////////////////////////////////////////////////
343 // 引数 : tarファイルを作成するパス
344 // 返り値: TRUE .. 成功 , FALSE .. 失敗
345 ////////////////////////////////////////////////////////////
346 function create($tempdir, $kind = 'tgz')
348 $tempnam = tempnam(realpath($tempdir), 'tarlib_create_');
349 if ($tempnam === FALSE) return FALSE;
351 if ($kind == 'tgz') {
352 $this->arc_kind = TARLIB_KIND_TGZ;
353 $this->fp = gzopen($tempnam, 'wb');
355 $this->arc_kind = TARLIB_KIND_TAR;
356 $this->fp = @fopen($tempnam, 'wb');
359 if ($this->fp === FALSE) {
363 $this->filename = $tempnam;
364 $this->dummydata = join('', array_fill(0, TARLIB_BLK_LEN, "\0"));
365 $this->status = TARLIB_STATUS_CREATE;
371 ////////////////////////////////////////////////////////////
372 // 関数 : tarファイルにディレクトリを追加する
373 // 引数 : $dir .. ディレクトリ名
374 // $mask .. 追加するファイル(正規表現)
375 // $decode .. ページ名の変換をするか
377 ////////////////////////////////////////////////////////////
378 function add_dir($dir, $mask, $decode = FALSE)
382 if ($this->status != TARLIB_STATUS_CREATE)
383 return ''; // File is not created
387 // 指定されたパスのファイルのリストを取得する
388 $dp = @opendir($dir);
390 @unlink($this->filename);
391 die_message($dir . ' is not found or not readable.');
393 while (($filename = readdir($dp)) !== FALSE) {
394 if (preg_match('/' . $mask . '/', $filename)) {
395 $files[] = $dir . $filename;
400 sort($files, SORT_STRING);
403 foreach($files as $name)
405 // Tarに格納するファイル名をdecode
406 if ($decode === FALSE) {
409 $dirname = dirname(trim($name)) . '/';
410 $filename = basename(trim($name));
411 if (preg_match("/^((?:[0-9A-F]{2})+)_((?:[0-9A-F]{2})+)/", $filename, $matches)) {
413 $filename = decode($matches[1]) . '/' . decode($matches[2]);
415 $pattern = '^((?:[0-9A-F]{2})+)((\.txt|\.gz)*)$';
416 if (preg_match("/$pattern/", $filename, $matches)) {
417 $filename = decode($matches[1]) . $matches[2];
420 $filename = str_replace(':', '_', $filename);
421 $filename = str_replace('\\', '_', $filename);
424 $filename = $dirname . $filename;
426 if (function_exists('mb_convert_encoding'))
427 $filename = mb_convert_encoding($filename, PLUGIN_DUMP_FILENAME_ENCORDING);
431 $mtime = filemtime($name);
434 if (strlen($filename) > TARLIB_HDR_NAME_LEN) {
436 $size = strlen($filename);
438 $tar_data = $this->_make_header(TARLIB_DATA_LONGLINK, $size, $mtime, TARLIB_HDR_LINK);
440 $this->_write_data(join('', $tar_data), $filename, $size);
444 $size = filesize($name);
445 if ($size === FALSE) {
446 @unlink($this->filename);
447 die_message($name . ' is not found or not readable.');
451 $tar_data = $this->_make_header($filename, $size, $mtime, TARLIB_HDR_FILE);
454 $fpr = @fopen($name , 'rb');
455 flock($fpr, LOCK_SH);
456 $data = fread($fpr, $size);
457 flock($fpr, LOCK_UN);
461 $this->_write_data(join('', $tar_data), $data, $size);
467 ////////////////////////////////////////////////////////////
468 // 関数 : tarのヘッダ情報を生成する (add)
469 // 引数 : $filename .. ファイル名
472 // $typeflag .. TypeFlag (file/link)
474 ////////////////////////////////////////////////////////////
475 function _make_header($filename, $size, $mtime, $typeflag)
477 $tar_data = array_fill(0, TARLIB_HDR_LEN, "\0");
480 for($i = 0; $i < strlen($filename); $i++ ) {
481 if ($i < TARLIB_HDR_NAME_LEN) {
482 $tar_data[$i + TARLIB_HDR_NAME_OFFSET] = $filename{$i};
489 $modeid = TARLIB_DATA_MODE;
490 for($i = 0; $i < strlen($modeid); $i++ ) {
491 $tar_data[$i + TARLIB_HDR_MODE_OFFSET] = $modeid{$i};
495 $ugid = TARLIB_DATA_UGID;
496 for($i = 0; $i < strlen($ugid); $i++ ) {
497 $tar_data[$i + TARLIB_HDR_UID_OFFSET] = $ugid{$i};
498 $tar_data[$i + TARLIB_HDR_GID_OFFSET] = $ugid{$i};
502 $strsize = sprintf('%11o', $size);
503 for($i = 0; $i < strlen($strsize); $i++ ) {
504 $tar_data[$i + TARLIB_HDR_SIZE_OFFSET] = $strsize{$i};
508 $strmtime = sprintf('%o', $mtime);
509 for($i = 0; $i < strlen($strmtime); $i++ ) {
510 $tar_data[$i + TARLIB_HDR_MTIME_OFFSET] = $strmtime{$i};
514 $chkblanks = TARLIB_DATA_CHKBLANKS;
515 for($i = 0; $i < strlen($chkblanks); $i++ ) {
516 $tar_data[$i + TARLIB_HDR_CHKSUM_OFFSET] = $chkblanks{$i};
520 $tar_data[TARLIB_HDR_TYPE_OFFSET] = $typeflag;
524 for($i = 0; $i < TARLIB_BLK_LEN; $i++ ) {
525 $sum += 0xff & ord($tar_data[$i]);
527 $strchksum = sprintf('%7o',$sum);
528 for($i = 0; $i < strlen($strchksum); $i++ ) {
529 $tar_data[$i + TARLIB_HDR_CHKSUM_OFFSET] = $strchksum{$i};
535 ////////////////////////////////////////////////////////////
536 // 関数 : tarデータのファイル出力 (add)
537 // 引数 : $header .. tarヘッダ情報
541 ////////////////////////////////////////////////////////////
542 function _write_data($header, $body, $size)
544 $fixsize = ceil($size / TARLIB_BLK_LEN) * TARLIB_BLK_LEN - $size;
546 if ($this->arc_kind == TARLIB_KIND_TGZ) {
547 gzwrite($this->fp, $header, TARLIB_HDR_LEN); // Header
548 gzwrite($this->fp, $body, $size); // Body
549 gzwrite($this->fp, $this->dummydata, $fixsize); // Padding
551 fwrite($this->fp, $header, TARLIB_HDR_LEN); // Header
552 fwrite($this->fp, $body, $size); // Body
553 fwrite($this->fp, $this->dummydata, $fixsize); // Padding
557 ////////////////////////////////////////////////////////////
560 // 返り値: TRUE .. 成功 , FALSE .. 失敗
561 ////////////////////////////////////////////////////////////
562 function open($name = '', $kind = 'tgz')
564 if (! PLUGIN_DUMP_ALLOW_RESTORE) return FALSE; // Not allowed
566 if ($name != '') $this->filename = $name;
568 if ($kind == 'tgz') {
569 $this->arc_kind = TARLIB_KIND_TGZ;
570 $this->fp = gzopen($this->filename, 'rb');
572 $this->arc_kind = TARLIB_KIND_TAR;
573 $this->fp = fopen($this->filename, 'rb');
576 if ($this->fp === FALSE) {
577 return FALSE; // No such file
579 $this->status = TARLIB_STATUS_OPEN;
585 ////////////////////////////////////////////////////////////
586 // 関数 : 指定したディレクトリにtarファイルを展開する
587 // 引数 : 展開するファイルパターン(正規表現)
589 // 補足 : ARAIさんのattachプラグインパッチを参考にしました
590 ////////////////////////////////////////////////////////////
591 function extract($pattern)
593 if ($this->status != TARLIB_STATUS_OPEN) return ''; // Not opened
599 $buff = fread($this->fp, TARLIB_HDR_LEN);
600 if (strlen($buff) != TARLIB_HDR_LEN) break;
604 if ($longname != '') {
605 $name = $longname; // LongLink対応
608 for ($i = 0; $i < TARLIB_HDR_NAME_LEN; $i++ ) {
609 if ($buff{$i + TARLIB_HDR_NAME_OFFSET} != "\0") {
610 $name .= $buff{$i + TARLIB_HDR_NAME_OFFSET};
618 if ($name == '') break; // 展開終了
620 // チェックサムを取得しつつ、ブランクに置換していく
622 $chkblanks = TARLIB_DATA_CHKBLANKS;
623 for ($i = 0; $i < TARLIB_HDR_CHKSUM_LEN; $i++ ) {
624 $checksum .= $buff{$i + TARLIB_HDR_CHKSUM_OFFSET};
625 $buff{$i + TARLIB_HDR_CHKSUM_OFFSET} = $chkblanks{$i};
627 list($checksum) = sscanf('0' . trim($checksum), '%i');
631 for($i = 0; $i < TARLIB_BLK_LEN; $i++ ) {
632 $sum += 0xff & ord($buff{$i});
634 if ($sum != $checksum) break; // Error
638 for ($i = 0; $i < TARLIB_HDR_SIZE_LEN; $i++ ) {
639 $size .= $buff{$i + TARLIB_HDR_SIZE_OFFSET};
641 list($size) = sscanf('0' . trim($size), '%i');
644 // データブロックは512byteでパディングされている
645 $pdsz = ceil($size / TARLIB_BLK_LEN) * TARLIB_BLK_LEN;
649 for ($i = 0; $i < TARLIB_HDR_MTIME_LEN; $i++ ) {
650 $strmtime .= $buff{$i + TARLIB_HDR_MTIME_OFFSET};
652 list($mtime) = sscanf('0' . trim($strmtime), '%i');
655 // $type = $buff{TARLIB_HDR_TYPE_OFFSET};
657 if ($name == TARLIB_DATA_LONGLINK) {
659 $buff = fread($this->fp, $pdsz);
660 $longname = substr($buff, 0, $size);
661 } else if (preg_match("/$pattern/", $name) ) {
662 // } else if ($type == 0 && preg_match("/$pattern/", $name) ) {
663 $buff = fread($this->fp, $pdsz);
665 // 既に同じファイルがある場合は上書きされる
666 $fpw = @fopen($name, 'wb');
667 if ($fpw !== FALSE) {
668 flock($fpw, LOCK_EX);
669 fwrite($fpw, $buff, $size);
671 @touch($name, $mtime);
672 flock($fpw, LOCK_UN);
679 @fseek($this->fp, $pdsz, SEEK_CUR);
685 ////////////////////////////////////////////////////////////
689 ////////////////////////////////////////////////////////////
692 if ($this->status == TARLIB_STATUS_CREATE) {
694 if ($this->arc_kind == TARLIB_KIND_TGZ) {
696 gzwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
697 gzwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
701 fwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
702 fwrite($this->fp, $this->dummydata, TARLIB_HDR_LEN);
705 } else if ($this->status == TARLIB_STATUS_OPEN) {
706 if ($this->arc_kind == TARLIB_KIND_TGZ) {
713 $this->status = TARLIB_STATUS_INIT;