1<?php 2 3/** 4 * DokuWiki WebDAV Plugin: Util Class 5 * 6 * @author Giuseppe Di Terlizzi <giuseppe.diterlizzi@gmail.com> 7 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 8 * @link https://dokuwiki.org/plugin:webdav 9 */ 10 11namespace dokuwiki\plugin\webdav\core; 12 13use Sabre\DAV\Exception\Forbidden; 14 15class Utils 16{ 17 /** 18 * Read from stream 19 * 20 * @param resource $stream resource 21 * 22 * @return string 23 */ 24 public static function streamReader($stream) 25 { 26 $data = ''; 27 28 while (($buf = fread($stream, 8192)) != '') { 29 $data .= $buf; 30 } 31 return $data; 32 } 33 34 /** 35 * Write a file 36 * 37 * @param resource $stream resource 38 * @param string $file file path 39 * 40 * @return void 41 */ 42 public static function streamWriter($stream, $file) 43 { 44 global $conf; 45 46 $fileexists = @file_exists($file); 47 48 io_makeFileDir($file); 49 io_lock($file); 50 51 $fh = @fopen($file, 'wb'); 52 53 if (!$fh) { 54 io_unlock($file); 55 return false; 56 } 57 58 while (($buf = fread($stream, 8192)) != '') { 59 fwrite($fh, $buf); 60 } 61 62 fclose($fh); 63 64 if (!$fileexists && !empty($conf['fperm'])) { 65 chmod($file, $conf['fperm']); 66 } 67 68 io_unlock($file); 69 70 return true; 71 } 72 73 /** 74 * Save Wiki Text 75 * 76 * @throws DAV\Exception\Forbidden 77 * 78 * @param string $id 79 * @param string $text 80 * @param string $mode 81 * 82 * @return void 83 */ 84 public static function saveWikiText($id, $text, $mode = 'edit') 85 { 86 // Add 2 return chars in "create" mode and "zero" byte size 87 if ($mode == 'create' && strlen($text) == 0) { 88 $text = "\n\n"; 89 } 90 91 self::log('debug', 'Save content of {id} page ({size} bytes - {mode} mode)', [ 92 'id' => $id, 93 'size' => strlen($text), 94 'mode' => $mode, 95 ]); 96 97 $auth_permission = AUTH_EDIT; 98 99 if ($mode == 'create') { 100 $auth_permission = AUTH_CREATE; 101 } 102 103 // check ACL permissions 104 if (auth_quickaclcheck($id) < $auth_permission) { 105 throw new Forbidden('Insufficient Permissions'); 106 } 107 108 if (!utf8_check($text)) { 109 throw new Forbidden('Seems not to be valid UTF-8 text'); 110 } 111 112 switch ($mode) { 113 case 'create': 114 $summary = 'Created via WebDAV'; 115 break; 116 case 'delete': 117 $summary = 'Deleted via WebDAV'; 118 break; 119 case 'edit': 120 default: 121 $summary = 'Edited via WebDAV'; 122 break; 123 } 124 125 saveWikiText($id, $text, $summary, false); 126 } 127 128 /** 129 * Search callback 130 * 131 * @param array $data 132 * @param string $base 133 * @param string $file 134 * @param string $type 135 * @param integer $lvl 136 * @param array $opts 137 * 138 * @return array 139 */ 140 public static function searchCallback(&$data, $base, $file, $type, $lvl, $opts = []) 141 { 142 $item = []; 143 144 if (!isset($opts['dir'])) { 145 $opts['dir'] = 'datadir'; 146 } 147 148 $is_dir = ($type == 'd'); 149 $is_mediadir = ($opts['dir'] == 'mediadir'); 150 151 $item['id'] = pathID($file, $is_dir); 152 $item['type'] = $type; 153 $item['dir'] = $opts['dir']; 154 $item['metafile'] = null; 155 $item['metadir'] = null; 156 157 if ($is_dir) { 158 $item['perm'] = auth_quickaclcheck($item['id'] . ':*'); 159 $item['ns'] = $item['id']; 160 $item['mtime'] = filemtime("$base/$file"); 161 $item['dirname'] = $item['ns']; 162 } else { 163 $item['path'] = ($is_mediadir) ? mediaFN($item['id']) : wikiFN($item['id']); 164 $item['mime_type'] = ($is_mediadir) ? mime_content_type($item['path']) : null; 165 $item['perm'] = auth_quickaclcheck($item['id']); 166 $item['ns'] = getNS($item['id']); 167 $item['size'] = filesize($item['path']); 168 $item['mtime'] = filemtime($item['path']); 169 $item['hash'] = sha1_file($item['path']); 170 $item['file'] = basename($file); 171 $item['filename'] = $item['file']; 172 $item['dirname'] = $item['ns']; 173 } 174 175 /** 176 * Use mediameta for fetch original directory and file name: 177 * 178 * <ID>.filename array ( filename => 'Original File Name' ) 179 * <NS>.dirname array ( dirname => 'Original Directory Name' ) 180 */ 181 if ($is_mediadir) { 182 $metafile = mediametaFN($item['id'], '.filename'); 183 $metadir = mediametaFN($item['ns'], '.dirname'); 184 185 if (file_exists($metafile) && $meta = unserialize(io_readFile($metafile, false))) { 186 $item['metafile'] = $metafile; 187 $item['filename'] = $meta['filename']; 188 } 189 190 if (file_exists($metadir) && $meta = unserialize(io_readFile($metadir, false))) { 191 $item['metadir'] = $metadir; 192 $item['dirname'] = $meta['dirname']; 193 } 194 } 195 196 if ($item['perm'] < AUTH_READ) { 197 return false; 198 } 199 200 $data[] = $item; 201 return false; 202 } 203 204 /** 205 * Interpolates context values into the message placeholders. 206 * 207 * @param string $message 208 * @param array $context 209 * 210 * @return string 211 */ 212 public static function interpolate($message, array $context = []) 213 { 214 // build a replacement array with braces around the context keys 215 $replace = []; 216 foreach ($context as $key => $val) { 217 // check that the value can be casted to string 218 if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { 219 $replace['{' . $key . '}'] = $val; 220 } 221 } 222 223 // interpolate replacement values into the message and return 224 return strtr($message, $replace); 225 } 226 227 /** 228 * Log in DokuWiki debug log 229 * 230 * @see dbglog() 231 * 232 * @param string $level 233 * @param string $message 234 * @param array $context 235 */ 236 public static function log($level, $message, $context = []) 237 { 238 // "{category} [{user}] [{ip}] [{level}] {message}" 239 240 dbglog(self::interpolate("{category} {user} [{level}] {message}", [ 241 'category' => 'WebDAV', 242 'user' => (isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : '-'), 243 'ip' => clientIP(), 244 'level' => str_pad(strtoupper($level), 5), 245 'message' => self::interpolate($message, $context), 246 ])); 247 248 if (isset($context['exception'])) { 249 self::log('error', $context['exception']->getMessage()); 250 } 251 } 252} 253