1<?php
2/**
3 * Description of IJR_Server
4 *
5 * @author  Andreas Gohr <andi@splitbrain.org>
6 * @author Gina Haeussge <osd@foosel.net>
7 * @author Michael Klier <chi@chimeric.de>
8 * @author Michael Hamann <michael@content-space.de>
9 * @author Magnus Wolf <mwolf2706@googlemail.com>
10 */
11
12if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../../');
13
14// fix when '<?xml' isn't on the very first line
15if(isset($HTTP_RAW_POST_DATA)) $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
16
17/**
18 * Increased whenever the API is changed
19 */
20define('DOKU_JSONRPC_API_VERSION',2);
21
22require_once(DOKU_INC.'inc/init.php');
23require_once(DOKU_INC.'inc/common.php');
24require_once(DOKU_INC.'inc/auth.php');
25require_once(DOKU_INC.'inc/pluginutils.php');
26session_write_close();  //close session
27
28if(plugin_isdisabled('jsonrpc'))
29{
30    die('JSON-RPC server not enabled');
31}
32
33require_once('./IJR_Message.php');
34require_once('./IJR_Date.php');
35require_once('./IJR_IntrospectionServer.php');
36require_once('./IJR_CallbackDefines.php');
37
38
39
40class dokuwiki_jsonrpc_server extends IJR_IntrospectionServer {
41    var $methods       = array();
42    var $public_methods = array();
43    private $callbackMethods;
44    private $config;
45
46    function checkAuth(){
47        global $conf;
48        global $USERINFO;
49
50        $this->config = $conf['plugin']['jsonrpc'];
51
52        if($this->config['allow_all'] == 1)
53        {
54            return true;
55        }
56
57        $user   = $_SERVER['REMOTE_USER'];
58        $allowed_users = explode(';',$this->config['allowed']);
59        $allowed_users = array_map('trim', $allowed_users);
60        $allowed_users = array_unique($allowed_users);
61
62        if(in_array($user,$allowed_users))
63        {
64            return true;
65        }
66        return false;
67    }
68
69    function addCallback($method, $callback, $args, $help, $public=false){
70        if($public) $this->public_methods[] = $method;
71        return parent::addCallback($method, $callback, $args, $help);
72    }
73
74
75    function call($methodname, $args){
76        if(!in_array($methodname,$this->public_methods) && !$this->checkAuth()){
77            return new IJR_Error(-32603, 'server error. not authorized to call method "'.$methodname.'".');
78        }
79        return parent::call($methodname, $args);
80    }
81
82
83    function dokuwiki_jsonrpc_server(){
84        $callbackDef = new IJR_CallbackDefines();
85        $this->callbackMethods = $callbackDef->getWikiMethods();
86
87        $this->IJR_IntrospectionServer();
88
89        foreach($this->callbackMethods as $key)
90        {
91            $this->addCallback($key['method'], $key['callback'], $key['args'], $key['help'], $key['public']);
92        }
93        trigger_event('JSONRPC_CALLBACK_REGISTER', $this);
94
95        $this->serve();
96    }
97
98    public function rawPage($id,$rev=''){
99        if(auth_quickaclcheck($id) < AUTH_READ){
100            return new IJR_Error(1, 'You are not allowed to read this page');
101        }
102        $text = rawWiki($id,$rev);
103        if(!$text) {
104            $data = array($id);
105            return trigger_event('HTML_PAGE_FROMTEMPLATE',$data,'pageTemplate',true);
106        } else {
107            return $text;
108        }
109    }
110
111    public function getTitle()
112    {
113        global $conf;
114        return $conf['title'];
115    }
116
117    public function appendPage($page, $text, $opt)
118    {
119       $page_cont = $this->rawPage($page);
120       $page_cont = $page_cont."\n".$text;
121       return saveWikiText($page, $tmp, $opt);
122    }
123
124    public function getAttachment($id){
125        $id = cleanID($id);
126        if (auth_quickaclcheck(getNS($id).':*') < AUTH_READ)
127            return new IJR_Error(1, 'You are not allowed to read this file');
128
129        $file = mediaFN($id);
130        if (!@ file_exists($file))
131            return new IJR_Error(1, 'The requested file does not exist');
132
133        $data = io_readFile($file, false);
134        $base64 = base64_encode($data);
135        return $base64;
136    }
137
138    public function getAttachmentInfo($id){
139        $id = cleanID($id);
140        $info = array(
141            'lastModified' => 0,
142            'size' => 0,
143        );
144
145        $file = mediaFN($id);
146        if ((auth_quickaclcheck(getNS($id).':*') >= AUTH_READ) && file_exists($file)){
147            $info['lastModified'] = new IJR_Date(filemtime($file));
148            $info['size'] = filesize($file);
149        }
150
151        return $info;
152    }
153
154    public function htmlPage($id,$rev=''){
155        if(auth_quickaclcheck($id) < AUTH_READ){
156            return new IJR_Error(1, 'You are not allowed to read this page');
157        }
158        return p_wiki_xhtml($id,$rev,false);
159    }
160
161    public function htmlPagePart($id,$rev='',$maxHeader=3,$maxItems=3){
162        if(auth_quickaclcheck($id) < AUTH_READ){
163            return new IJR_Error(1, 'You are not allowed to read this page');
164        }
165        $title = '';
166        $cfg = array('maxHeader'=>$maxHeader,'maxItems'=>$maxItems);
167        return p_wiki_xhtml_summary_ext($id,$title,$rev,true,$cfg);
168    }
169
170    public function listPages(){
171        global $conf;
172
173        $list  = array();
174        $pages = file($conf['indexdir'] . '/page.idx');
175        $pages = array_filter($pages, 'isVisiblePage');
176
177        foreach(array_keys($pages) as $idx) {
178            if(page_exists($pages[$idx])) {
179                $perm = auth_quickaclcheck($pages[$idx]);
180                if($perm >= AUTH_READ) {
181                    $page = array();
182                    $page['id'] = trim($pages[$idx]);
183                    $page['perms'] = $perm;
184                    $page['size'] = @filesize(wikiFN($pages[$idx]));
185                    $page['lastModified'] = new IJR_Date(@filemtime(wikiFN($pages[$idx])));
186                    $list[] = $page;
187                }
188            }
189        }
190
191        return $list;
192    }
193
194    public function readNamespace($ns,$opts){
195        global $conf;
196
197        if(!is_array($opts)) $opts=array();
198
199        $ns = cleanID($ns);
200        $dir = utf8_encodeFN(str_replace(':', '/', $ns));
201        $data = array();
202        require_once(DOKU_INC.'inc/search.php');
203        $opts['skipacl'] = 0; // no ACL skipping for XMLRPC
204        search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
205        return $data;
206    }
207
208    public function listAttachments($ns, $options = array()) {
209        global $conf;
210        global $lang;
211
212        $ns = cleanID($ns);
213        if (!is_array($options)) $options = array();
214        $options['skipacl'] = 0; // no ACL skipping for XMLRPC
215
216        if(auth_quickaclcheck($ns.':*') >= AUTH_READ) {
217            $dir = utf8_encodeFN(str_replace(':', '/', $ns));
218
219            $data = array();
220            require_once(DOKU_INC.'inc/search.php');
221            search($data, $conf['mediadir'], 'search_media', $options, $dir);
222            $len = count($data);
223
224            if(!$len) return array();
225
226            for($i=0; $i<$len; $i++) {
227                unset($data[$i]['meta']);
228                $data[$i]['lastModified'] = new IJR_Date($data[$i]['mtime']);
229            }
230
231            return $data;
232        } else {
233            return new IJR_Error(1, 'You are not allowed to list media files.');
234        }
235    }
236
237    public function search($searchString)
238    {
239        require_once(DOKU_INC.'inc/html.php');
240        require_once(DOKU_INC.'inc/search.php');
241        require_once(DOKU_INC.'inc/fulltext.php');
242        require_once(DOKU_INC.'inc/pageutils.php');
243
244        $data = array();
245        $result = array();
246
247        $searchStr = cleanID($searchString);
248        $data = ft_pageLookup($searchStr);
249        foreach($data as $id)
250        {
251            $ns = getNS($id);
252            if($ns){
253                $name = shorten(noNS($id), ' ('.$ns.')',30);
254            }else{
255                $name = $id;
256            }
257            $result[] = $id;
258        }
259
260        $data = ft_pageSearch($searchString, $regex);
261        if(count($data))
262        {
263            foreach($data as $id => $cnt)
264            {
265                $result[] = $id;
266            }
267        }
268        return $result;
269    }
270
271    public function listBackLinks($id){
272        require_once(DOKU_INC.'inc/fulltext.php');
273        return ft_backlinks($id);
274    }
275
276    public function pageInfo($id,$rev=''){
277        if(auth_quickaclcheck($id) < AUTH_READ){
278            return new IJR_Error(1, 'You are not allowed to read this page');
279        }
280        $file = wikiFN($id,$rev);
281        $time = @filemtime($file);
282        if(!$time){
283            return new IJR_Error(10, 'The requested page does not exist');
284        }
285
286        $info = getRevisionInfo($id, $time, 1024);
287
288        $data = array(
289            'name'         => $id,
290            'lastModified' => new IJR_Date($time),
291            'author'       => (($info['user']) ? $info['user'] : $info['ip']),
292            'version'      => $time
293        );
294
295        return ($data);
296    }
297
298    public function putPage($id, $text, $params) {
299        global $TEXT;
300        global $lang;
301        global $conf;
302
303        $id    = cleanID($id);
304        $TEXT  = cleanText($text);
305        $sum   = $params['sum'];
306        $minor = $params['minor'];
307
308        if(empty($id))
309            return new IJR_Error(1, 'Empty page ID');
310
311        if(!page_exists($id) && trim($TEXT) == '' ) {
312            return new IJR_ERROR(1, 'Refusing to write an empty new wiki page');
313        }
314
315        if(auth_quickaclcheck($id) < AUTH_EDIT)
316            return new IJR_Error(1, 'You are not allowed to edit this page');
317
318        if(checklock($id))
319            return new IJR_Error(1, 'The page is currently locked');
320
321        if(checkwordblock())
322            return new IJR_Error(1, 'Positive wordblock check');
323
324        if(!page_exists($id) && empty($sum)) {
325            $sum = $lang['created'];
326        }
327
328        if(page_exists($id) && empty($TEXT) && empty($sum)) {
329            $sum = $lang['deleted'];
330        }
331
332        lock($id);
333
334        saveWikiText($id,$TEXT,$sum,$minor);
335
336        unlock($id);
337
338        // run the indexer if page wasn't indexed yet
339        if(!@file_exists(metaFN($id, '.indexed'))) {
340            // try to aquire a lock
341            $lock = $conf['lockdir'].'/_indexer.lock';
342            while(!@mkdir($lock,$conf['dmode'])){
343                usleep(50);
344                if(time()-@filemtime($lock) > 60*5){
345                    // looks like a stale lock - remove it
346                    @rmdir($lock);
347                }else{
348                    return false;
349                }
350            }
351            if(isset($conf['dperm']) && $conf['dperm']) chmod($lock, $conf['dperm']);
352
353            require_once(DOKU_INC.'inc/indexer.php');
354
355            if(!defined('INDEXER_VERSION')){
356                define('INDEXER_VERSION', 2);
357            }
358            // do the work
359            idx_addPage($id);
360
361            io_saveFile(metaFN($id,'.indexed'),INDEXER_VERSION);
362            @rmdir($lock);
363        }
364
365        return 0;
366    }
367
368    public function putAttachment($id, $file, $params) {
369        global $conf;
370        global $lang;
371
372        $auth = auth_quickaclcheck(getNS($id).':*');
373        if($auth >= AUTH_UPLOAD) {
374            if(!isset($id)) {
375                return new IJR_ERROR(1, 'Filename not given.');
376            }
377
378            $ftmp = $conf['tmpdir'] . '/' . $id;
379
380            // save temporary file
381            @unlink($ftmp);
382            $buff = base64_decode($file);
383            io_saveFile($ftmp, $buff);
384
385            // get filename
386            list($iext, $imime,$dl) = mimetype($id);
387            $id = cleanID($id);
388            $fn = mediaFN($id);
389
390            // get filetype regexp
391            $types = array_keys(getMimeTypes());
392            $types = array_map(create_function('$q','return preg_quote($q,"/");'),$types);
393            $regex = join('|',$types);
394
395            // because a temp file was created already
396            if(preg_match('/\.('.$regex.')$/i',$fn)) {
397                //check for overwrite
398                $overwrite = @file_exists($fn);
399                if($overwrite && (!$params['ow'] || $auth < AUTH_DELETE)) {
400                    return new IJR_ERROR(1, $lang['uploadexist'].'1');
401                }
402                // check for valid content
403                @require_once(DOKU_INC.'inc/media.php');
404                $ok = media_contentcheck($ftmp, $imime);
405                if($ok == -1) {
406                    return new IJR_ERROR(1, sprintf($lang['uploadexist'].'2', ".$iext"));
407                } elseif($ok == -2) {
408                    return new IJR_ERROR(1, $lang['uploadspam']);
409                } elseif($ok == -3) {
410                    return new IJR_ERROR(1, $lang['uploadxss']);
411                }
412
413                // prepare event data
414                $data[0] = $ftmp;
415                $data[1] = $fn;
416                $data[2] = $id;
417                $data[3] = $imime;
418                $data[4] = $overwrite;
419
420                // trigger event
421                require_once(DOKU_INC.'inc/events.php');
422                return trigger_event('MEDIA_UPLOAD_FINISH', $data, array($this, '_media_upload_action'), true);
423
424            } else {
425                return new IJR_ERROR(1, $lang['uploadwrong']);
426            }
427        } else {
428            return new IJR_ERROR(1, "You don't have permissions to upload files.");
429        }
430    }
431
432    public function deleteAttachment($id){
433        $auth = auth_quickaclcheck(getNS($id).':*');
434        if($auth < AUTH_DELETE) return new IJR_ERROR(1, "You don't have permissions to delete files.");
435        global $conf;
436        global $lang;
437
438        // check for references if needed
439        $mediareferences = array();
440        if($conf['refcheck']){
441            require_once(DOKU_INC.'inc/fulltext.php');
442            $mediareferences = ft_mediause($id,$conf['refshow']);
443        }
444
445        if(!count($mediareferences)){
446            $file = mediaFN($id);
447            if(@unlink($file)){
448                require_once(DOKU_INC.'inc/changelog.php');
449                addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE);
450                io_sweepNS($id,'mediadir');
451                return 0;
452            }
453            //something went wrong
454               return new IJR_ERROR(1, 'Could not delete file');
455        } else {
456            return new IJR_ERROR(1, 'File is still referenced');
457        }
458    }
459
460    public function _media_upload_action($data) {
461        global $conf;
462
463        if(is_array($data) && count($data)===5) {
464            io_createNamespace($data[2], 'media');
465            if(rename($data[0], $data[1])) {
466                chmod($data[1], $conf['fmode']);
467                media_notify($data[2], $data[1], $data[3]);
468                // add a log entry to the media changelog
469                require_once(DOKU_INC.'inc/changelog.php');
470                if ($data[4]) {
471                    addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_EDIT);
472                } else {
473                    addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_CREATE);
474                }
475                return $data[2];
476            } else {
477                return new IJR_ERROR(1, 'Upload failed.');
478            }
479        } else {
480            return new IJR_ERROR(1, 'Upload failed.');
481        }
482    }
483
484    public function aclCheck($id) {
485        return auth_quickaclcheck($id);
486    }
487
488    public function listLinks($id) {
489        if(auth_quickaclcheck($id) < AUTH_READ){
490            return new IJR_Error(1, 'You are not allowed to read this page');
491        }
492        $links = array();
493
494        // resolve page instructions
495        $ins   = p_cached_instructions(wikiFN(cleanID($id)));
496
497        // instantiate new Renderer - needed for interwiki links
498        include(DOKU_INC.'inc/parser/xhtml.php');
499        $Renderer = new Doku_Renderer_xhtml();
500        $Renderer->interwiki = getInterwiki();
501
502        // parse parse instructions
503        foreach($ins as $in) {
504            $link = array();
505            switch($in[0]) {
506                case 'internallink':
507                    $link['type'] = 'local';
508                    $link['page'] = $in[1][0];
509                    $link['href'] = wl($in[1][0]);
510                    array_push($links,$link);
511                    break;
512                case 'externallink':
513                    $link['type'] = 'extern';
514                    $link['page'] = $in[1][0];
515                    $link['href'] = $in[1][0];
516                    array_push($links,$link);
517                    break;
518                case 'interwikilink':
519                    $url = $Renderer->_resolveInterWiki($in[1][2],$in[1][3]);
520                    $link['type'] = 'extern';
521                    $link['page'] = $url;
522                    $link['href'] = $url;
523                    array_push($links,$link);
524                    break;
525            }
526        }
527
528        return ($links);
529    }
530
531    public function getRecentChanges($timestamp) {
532        if(strlen($timestamp) != 10)
533            return new IJR_Error(20, 'The provided value is not a valid timestamp');
534
535        require_once(DOKU_INC.'inc/changelog.php');
536        require_once(DOKU_INC.'inc/pageutils.php');
537
538        $recents = getRecentsSince($timestamp);
539
540        $changes = array();
541
542        foreach ($recents as $recent) {
543            $change = array();
544            $change['name']         = $recent['id'];
545            $change['lastModified'] = new IJR_Date($recent['date']);
546            $change['author']       = $recent['user'];
547            $change['version']      = $recent['date'];
548            $change['perms']        = $recent['perms'];
549            $change['size']         = @filesize(wikiFN($recent['id']));
550            array_push($changes, $change);
551        }
552
553        if (!empty($changes)) {
554            return $changes;
555        } else {
556            // in case we still have nothing at this point
557            return new IJR_Error(30, 'There are no changes in the specified timeframe');
558        }
559    }
560
561    public function getRecentMediaChanges($timestamp) {
562        if(strlen($timestamp) != 10)
563            return new IJR_Error(20, 'The provided value is not a valid timestamp');
564
565        require_once(DOKU_INC.'inc/changelog.php');
566        require_once(DOKU_INC.'inc/pageutils.php');
567
568        $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES);
569
570        $changes = array();
571
572        foreach ($recents as $recent) {
573            $change = array();
574            $change['name']         = $recent['id'];
575            $change['lastModified'] = new IJR_Date($recent['date']);
576            $change['author']       = $recent['user'];
577            $change['version']      = $recent['date'];
578            $change['perms']        = $recent['perms'];
579            $change['size']         = @filesize(mediaFN($recent['id']));
580            array_push($changes, $change);
581        }
582
583        if (!empty($changes)) {
584            return $changes;
585        } else {
586            // in case we still have nothing at this point
587            return new IJR_Error(30, 'There are no changes in the specified timeframe');
588        }
589    }
590
591    public function pageVersions($id, $first,$num=null) {
592        global $conf;
593
594        $versions = array();
595
596        if(empty($id))
597            return new IJR_Error(1, 'Empty page ID');
598
599        require_once(DOKU_INC.'inc/changelog.php');
600
601        if(is_null($num)){
602            $num = $conf['recent'];
603        }
604
605        $revisions = getRevisions($id, $first, $num+1);
606
607        if(count($revisions)==0 && $first!=0) {
608            $first=0;
609            $revisions = getRevisions($id, $first, $num+1);
610        }
611
612        if(count($revisions)>0 && $first==0) {
613            array_unshift($revisions, '');  // include current revision
614            array_pop($revisions);          // remove extra log entry
615        }
616
617        $hasNext = false;
618        if(count($revisions)>$num) {
619            $hasNext = true;
620            array_pop($revisions); // remove extra log entry
621        }
622
623        if(!empty($revisions)) {
624            foreach($revisions as $rev) {
625                $file = wikiFN($id,$rev);
626                $time = @filemtime($file);
627                // we check if the page actually exists, if this is not the
628                // case this can lead to less pages being returned than
629                // specified via $conf['recent']
630                if($time){
631                    $info = getRevisionInfo($id, $time, 1024);
632                    if(!empty($info)) {
633                        $data['user'] = $info['user'];
634                        $data['ip']   = $info['ip'];
635                        $data['type'] = $info['type'];
636                        $data['sum']  = $info['sum'];
637                        $data['modified'] = new IJR_Date($info['date']);
638                        $data['version'] = $info['date'];
639                        array_push($versions, $data);
640                    }
641                }
642            }
643            return $versions;
644        } else {
645            return array();
646        }
647    }
648
649    public function setLocks($set){
650        $locked     = array();
651        $lockfail   = array();
652        $unlocked   = array();
653        $unlockfail = array();
654
655        foreach($set['lock'] as $id){
656            if(checklock($id)){
657                $lockfail[] = $id;
658            }else{
659                lock($id);
660                $locked[] = $id;
661            }
662        }
663
664        foreach($set['unlock'] as $id){
665            if(unlock($id)){
666                $unlocked[] = $id;
667            }else{
668                $unlockfail[] = $id;
669            }
670        }
671
672        return array(
673            'locked'     => $locked,
674            'lockfail'   => $lockfail,
675            'unlocked'   => $unlocked,
676            'unlockfail' => $unlockfail,
677        );
678    }
679
680    public function login($user,$pass){
681        global $conf;
682        global $auth;
683        if(!$conf['useacl'])
684        {
685            return 0;
686        }
687        if(!$auth)
688        {
689            return 0;
690        }
691        if($auth->canDo('external'))
692        {
693            return $auth->trustExternal($user,$pass,false);
694        }
695        else
696        {
697            return auth_login($user,$pass,false,true);
698        }
699    }
700}
701
702$server = new dokuwiki_jsonrpc_server();