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