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();