Send export_raw as attachement to avoid IE's content sniffing [security]
[dokuwiki/radio.git] / inc / actions.php
blob4ade9aab20e275bad67e0f82c78d67daca1bad0e
1 <?php
2 /**
3 * DokuWiki Actions
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author Andreas Gohr <andi@splitbrain.org>
7 */
9 if(!defined('DOKU_INC')) die('meh.');
10 require_once(DOKU_INC.'inc/template.php');
13 /**
14 * Call the needed action handlers
16 * @author Andreas Gohr <andi@splitbrain.org>
17 * @triggers ACTION_ACT_PREPROCESS
18 * @triggers ACTION_HEADERS_SEND
20 function act_dispatch(){
21 global $INFO;
22 global $ACT;
23 global $ID;
24 global $QUERY;
25 global $lang;
26 global $conf;
27 global $license;
29 $preact = $ACT;
31 // give plugins an opportunity to process the action
32 $evt = new Doku_Event('ACTION_ACT_PREPROCESS',$ACT);
33 if ($evt->advise_before()) {
35 //sanitize $ACT
36 $ACT = act_clean($ACT);
38 //check if searchword was given - else just show
39 $s = cleanID($QUERY);
40 if($ACT == 'search' && empty($s)){
41 $ACT = 'show';
44 //login stuff
45 if(in_array($ACT,array('login','logout'))){
46 $ACT = act_auth($ACT);
49 //check if user is asking to (un)subscribe a page
50 if($ACT == 'subscribe' || $ACT == 'unsubscribe')
51 $ACT = act_subscription($ACT);
53 //check if user is asking to (un)subscribe a namespace
54 if($ACT == 'subscribens' || $ACT == 'unsubscribens')
55 $ACT = act_subscriptionns($ACT);
57 //check permissions
58 $ACT = act_permcheck($ACT);
60 //register
61 $nil = array();
62 if($ACT == 'register' && $_POST['save'] && register()){
63 $ACT = 'login';
66 if ($ACT == 'resendpwd' && act_resendpwd()) {
67 $ACT = 'login';
70 //update user profile
71 if ($ACT == 'profile') {
72 if(!$_SERVER['REMOTE_USER']) {
73 $ACT = 'login';
74 } else {
75 if(updateprofile()) {
76 msg($lang['profchanged'],1);
77 $ACT = 'show';
82 //revert
83 if($ACT == 'revert'){
84 if(checkSecurityToken()){
85 $ACT = act_revert($ACT);
86 }else{
87 $ACT = 'show';
91 //save
92 if($ACT == 'save'){
93 if(checkSecurityToken()){
94 $ACT = act_save($ACT);
95 }else{
96 $ACT = 'show';
100 //cancel conflicting edit
101 if($ACT == 'cancel')
102 $ACT = 'show';
104 //draft deletion
105 if($ACT == 'draftdel')
106 $ACT = act_draftdel($ACT);
108 //draft saving on preview
109 if($ACT == 'preview')
110 $ACT = act_draftsave($ACT);
112 //edit
113 if(($ACT == 'edit' || $ACT == 'preview') && $INFO['editable']){
114 $ACT = act_edit($ACT);
115 }else{
116 unlock($ID); //try to unlock
119 //handle export
120 if(substr($ACT,0,7) == 'export_')
121 $ACT = act_export($ACT);
123 //display some infos
124 if($ACT == 'check'){
125 check();
126 $ACT = 'show';
129 //handle admin tasks
130 if($ACT == 'admin'){
131 // retrieve admin plugin name from $_REQUEST['page']
132 if (!empty($_REQUEST['page'])) {
133 $pluginlist = plugin_list('admin');
134 if (in_array($_REQUEST['page'], $pluginlist)) {
135 // attempt to load the plugin
136 if ($plugin =& plugin_load('admin',$_REQUEST['page']) !== NULL)
137 $plugin->handle();
142 // check permissions again - the action may have changed
143 $ACT = act_permcheck($ACT);
144 } // end event ACTION_ACT_PREPROCESS default action
145 $evt->advise_after();
146 unset($evt);
148 // when action 'show', the intial not 'show' and POST, do a redirect
149 if($ACT == 'show' && $preact != 'show' && strtolower($_SERVER['REQUEST_METHOD']) == 'post'){
150 act_redirect($ID,$preact);
153 //call template FIXME: all needed vars available?
154 $headers[] = 'Content-Type: text/html; charset=utf-8';
155 trigger_event('ACTION_HEADERS_SEND',$headers,'act_sendheaders');
157 include(template('main.php'));
158 // output for the commands is now handled in inc/templates.php
159 // in function tpl_content()
162 function act_sendheaders($headers) {
163 foreach ($headers as $hdr) header($hdr);
167 * Sanitize the action command
169 * Add all allowed commands here.
171 * @author Andreas Gohr <andi@splitbrain.org>
173 function act_clean($act){
174 global $lang;
175 global $conf;
177 // check if the action was given as array key
178 if(is_array($act)){
179 list($act) = array_keys($act);
182 //remove all bad chars
183 $act = strtolower($act);
184 $act = preg_replace('/[^1-9a-z_]+/','',$act);
186 if($act == 'export_html') $act = 'export_xhtml';
187 if($act == 'export_htmlbody') $act = 'export_xhtmlbody';
189 // check if action is disabled
190 if(!actionOK($act)){
191 msg('Command disabled: '.htmlspecialchars($act),-1);
192 return 'show';
195 //disable all acl related commands if ACL is disabled
196 if(!$conf['useacl'] && in_array($act,array('login','logout','register','admin',
197 'subscribe','unsubscribe','profile','revert',
198 'resendpwd','subscribens','unsubscribens',))){
199 msg('Command unavailable: '.htmlspecialchars($act),-1);
200 return 'show';
203 if(!in_array($act,array('login','logout','register','save','cancel','edit','draft',
204 'preview','search','show','check','index','revisions',
205 'diff','recent','backlink','admin','subscribe','revert',
206 'unsubscribe','profile','resendpwd','recover','wordblock',
207 'draftdel','subscribens','unsubscribens',)) && substr($act,0,7) != 'export_' ) {
208 msg('Command unknown: '.htmlspecialchars($act),-1);
209 return 'show';
211 return $act;
215 * Run permissionchecks
217 * @author Andreas Gohr <andi@splitbrain.org>
219 function act_permcheck($act){
220 global $INFO;
221 global $conf;
223 if(in_array($act,array('save','preview','edit','recover'))){
224 if($INFO['exists']){
225 if($act == 'edit'){
226 //the edit function will check again and do a source show
227 //when no AUTH_EDIT available
228 $permneed = AUTH_READ;
229 }else{
230 $permneed = AUTH_EDIT;
232 }else{
233 $permneed = AUTH_CREATE;
235 }elseif(in_array($act,array('login','search','recent','profile'))){
236 $permneed = AUTH_NONE;
237 }elseif($act == 'revert'){
238 $permneed = AUTH_ADMIN;
239 if($INFO['ismanager']) $permneed = AUTH_EDIT;
240 }elseif($act == 'register'){
241 $permneed = AUTH_NONE;
242 }elseif($act == 'resendpwd'){
243 $permneed = AUTH_NONE;
244 }elseif($act == 'admin'){
245 if($INFO['ismanager']){
246 // if the manager has the needed permissions for a certain admin
247 // action is checked later
248 $permneed = AUTH_READ;
249 }else{
250 $permneed = AUTH_ADMIN;
252 }else{
253 $permneed = AUTH_READ;
255 if($INFO['perm'] >= $permneed) return $act;
257 return 'denied';
261 * Handle 'draftdel'
263 * Deletes the draft for the current page and user
265 function act_draftdel($act){
266 global $INFO;
267 @unlink($INFO['draft']);
268 $INFO['draft'] = null;
269 return 'show';
273 * Saves a draft on preview
275 * @todo this currently duplicates code from ajax.php :-/
277 function act_draftsave($act){
278 global $INFO;
279 global $ID;
280 global $conf;
281 if($conf['usedraft'] && $_POST['wikitext']){
282 $draft = array('id' => $ID,
283 'prefix' => $_POST['prefix'],
284 'text' => $_POST['wikitext'],
285 'suffix' => $_POST['suffix'],
286 'date' => $_POST['date'],
287 'client' => $INFO['client'],
289 $cname = getCacheName($draft['client'].$ID,'.draft');
290 if(io_saveFile($cname,serialize($draft))){
291 $INFO['draft'] = $cname;
294 return $act;
298 * Handle 'save'
300 * Checks for spam and conflicts and saves the page.
301 * Does a redirect to show the page afterwards or
302 * returns a new action.
304 * @author Andreas Gohr <andi@splitbrain.org>
306 function act_save($act){
307 global $ID;
308 global $DATE;
309 global $PRE;
310 global $TEXT;
311 global $SUF;
312 global $SUM;
314 //spam check
315 if(checkwordblock())
316 return 'wordblock';
317 //conflict check //FIXME use INFO
318 if($DATE != 0 && @filemtime(wikiFN($ID)) > $DATE )
319 return 'conflict';
321 //save it
322 saveWikiText($ID,con($PRE,$TEXT,$SUF,1),$SUM,$_REQUEST['minor']); //use pretty mode for con
323 //unlock it
324 unlock($ID);
326 //delete draft
327 act_draftdel($act);
328 session_write_close();
330 // when done, show page
331 return 'show';
335 * Revert to a certain revision
337 * @author Andreas Gohr <andi@splitbrain.org>
339 function act_revert($act){
340 global $ID;
341 global $REV;
342 global $lang;
344 // when no revision is given, delete current one
345 // FIXME this feature is not exposed in the GUI currently
346 $text = '';
347 $sum = $lang['deleted'];
348 if($REV){
349 $text = rawWiki($ID,$REV);
350 if(!$text) return 'show'; //something went wrong
351 $sum = $lang['restored'];
354 // spam check
355 if(checkwordblock($Text))
356 return 'wordblock';
358 saveWikiText($ID,$text,$sum,false);
359 msg($sum,1);
361 //delete any draft
362 act_draftdel($act);
363 session_write_close();
365 // when done, show current page
366 $_SERVER['REQUEST_METHOD'] = 'post'; //should force a redirect
367 $REV = '';
368 return 'show';
372 * Do a redirect after receiving post data
374 * Tries to add the section id as hash mark after section editing
376 function act_redirect($id,$preact){
377 global $PRE;
378 global $TEXT;
379 global $MSG;
381 //are there any undisplayed messages? keep them in session for display
382 //on the next page
383 if(isset($MSG) && count($MSG)){
384 //reopen session, store data and close session again
385 @session_start();
386 $_SESSION[DOKU_COOKIE]['msg'] = $MSG;
387 session_write_close();
390 //get section name when coming from section edit
391 if($PRE && preg_match('/^\s*==+([^=\n]+)/',$TEXT,$match)){
392 $check = false;
393 $title = sectionID($match[0],$check);
396 $opts = array(
397 'id' => $id,
398 'fragment' => $title,
399 'preact' => $preact
401 trigger_event('ACTION_SHOW_REDIRECT',$opts,'act_redirect_execute');
404 function act_redirect_execute($opts){
405 $go = wl($opts['id'],'',true);
406 if($opts['fragment']) $go .= '#'.$opts['fragment'];
408 //show it
409 send_redirect($go);
413 * Handle 'login', 'logout'
415 * @author Andreas Gohr <andi@splitbrain.org>
417 function act_auth($act){
418 global $ID;
419 global $INFO;
421 //already logged in?
422 if($_SERVER['REMOTE_USER'] && $act=='login'){
423 return 'show';
426 //handle logout
427 if($act=='logout'){
428 $lockedby = checklock($ID); //page still locked?
429 if($lockedby == $_SERVER['REMOTE_USER'])
430 unlock($ID); //try to unlock
432 // do the logout stuff
433 auth_logoff();
435 // rebuild info array
436 $INFO = pageinfo();
438 act_redirect($ID,'login');
441 return $act;
445 * Handle 'edit', 'preview'
447 * @author Andreas Gohr <andi@splitbrain.org>
449 function act_edit($act){
450 global $ID;
451 global $INFO;
453 //check if locked by anyone - if not lock for my self
454 $lockedby = checklock($ID);
455 if($lockedby) return 'locked';
457 lock($ID);
458 return $act;
462 * Export a wiki page for various formats
464 * Triggers ACTION_EXPORT_POSTPROCESS
466 * Event data:
467 * data['id'] -- page id
468 * data['mode'] -- requested export mode
469 * data['headers'] -- export headers
470 * data['output'] -- export output
472 * @author Andreas Gohr <andi@splitbrain.org>
473 * @author Michael Klier <chi@chimeric.de>
475 function act_export($act){
476 global $ID;
477 global $REV;
478 global $conf;
479 global $lang;
481 $pre = '';
482 $post = '';
483 $output = '';
484 $headers = array();
486 // search engines: never cache exported docs! (Google only currently)
487 $headers['X-Robots-Tag'] = 'noindex';
489 $mode = substr($act,7);
490 switch($mode) {
491 case 'raw':
492 $headers['Content-Type'] = 'text/plain; charset=utf-8';
493 $headers['Content-Disposition'] = 'attachment; filename='.noNS($ID).'.txt';
494 $output = rawWiki($ID,$REV);
495 break;
496 case 'xhtml':
497 $pre .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' . DOKU_LF;
498 $pre .= ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' . DOKU_LF;
499 $pre .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="'.$conf['lang'].'"' . DOKU_LF;
500 $pre .= ' lang="'.$conf['lang'].'" dir="'.$lang['direction'].'">' . DOKU_LF;
501 $pre .= '<head>' . DOKU_LF;
502 $pre .= ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . DOKU_LF;
503 $pre .= ' <title>'.$ID.'</title>' . DOKU_LF;
505 // get metaheaders
506 ob_start();
507 tpl_metaheaders();
508 $pre .= ob_get_clean();
510 $pre .= '</head>' . DOKU_LF;
511 $pre .= '<body>' . DOKU_LF;
512 $pre .= '<div class="dokuwiki export">' . DOKU_LF;
514 // get toc
515 $pre .= tpl_toc(true);
517 $headers['Content-Type'] = 'text/html; charset=utf-8';
518 $output = p_wiki_xhtml($ID,$REV,false);
520 $post .= '</div>' . DOKU_LF;
521 $post .= '</body>' . DOKU_LF;
522 $post .= '</html>' . DOKU_LF;
523 break;
524 case 'xhtmlbody':
525 $headers['Content-Type'] = 'text/html; charset=utf-8';
526 $output = p_wiki_xhtml($ID,$REV,false);
527 break;
528 default:
529 $output = p_cached_output(wikiFN($ID,$REV), $mode);
530 $headers = p_get_metadata($ID,"format $mode");
531 break;
534 // prepare event data
535 $data = array();
536 $data['id'] = $ID;
537 $data['mode'] = $mode;
538 $data['headers'] = $headers;
539 $data['output'] =& $output;
541 trigger_event('ACTION_EXPORT_POSTPROCESS', $data);
543 if(!empty($data['output'])){
544 if(is_array($data['headers'])) foreach($data['headers'] as $key => $val){
545 header("$key: $val");
547 print $pre.$data['output'].$post;
548 exit;
550 return 'show';
554 * Handle page 'subscribe', 'unsubscribe'
556 * @author Steven Danz <steven-danz@kc.rr.com>
557 * @todo localize
559 function act_subscription($act){
560 global $ID;
561 global $INFO;
562 global $lang;
564 $file=metaFN($ID,'.mlist');
565 if ($act=='subscribe' && !$INFO['subscribed']){
566 if ($INFO['userinfo']['mail']){
567 if (io_saveFile($file,$_SERVER['REMOTE_USER']."\n",true)) {
568 $INFO['subscribed'] = true;
569 msg(sprintf($lang[$act.'_success'], $INFO['userinfo']['name'], $ID),1);
570 } else {
571 msg(sprintf($lang[$act.'_error'], $INFO['userinfo']['name'], $ID),1);
573 } else {
574 msg($lang['subscribe_noaddress']);
576 } elseif ($act=='unsubscribe' && $INFO['subscribed']){
577 if (io_deleteFromFile($file,$_SERVER['REMOTE_USER']."\n")) {
578 $INFO['subscribed'] = false;
579 msg(sprintf($lang[$act.'_success'], $INFO['userinfo']['name'], $ID),1);
580 } else {
581 msg(sprintf($lang[$act.'_error'], $INFO['userinfo']['name'], $ID),1);
585 return 'show';
589 * Handle namespace 'subscribe', 'unsubscribe'
592 function act_subscriptionns($act){
593 global $ID;
594 global $INFO;
595 global $lang;
597 if(!getNS($ID)) {
598 $file = metaFN(getNS($ID),'.mlist');
599 $ns = "root";
600 } else {
601 $file = metaFN(getNS($ID),'/.mlist');
602 $ns = getNS($ID);
605 // reuse strings used to display the status of the subscribe action
606 $act_msg = rtrim($act, 'ns');
608 if ($act=='subscribens' && !$INFO['subscribedns']){
609 if ($INFO['userinfo']['mail']){
610 if (io_saveFile($file,$_SERVER['REMOTE_USER']."\n",true)) {
611 $INFO['subscribedns'] = true;
612 msg(sprintf($lang[$act_msg.'_success'], $INFO['userinfo']['name'], $ns),1);
613 } else {
614 msg(sprintf($lang[$act_msg.'_error'], $INFO['userinfo']['name'], $ns),1);
616 } else {
617 msg($lang['subscribe_noaddress']);
619 } elseif ($act=='unsubscribens' && $INFO['subscribedns']){
620 if (io_deleteFromFile($file,$_SERVER['REMOTE_USER']."\n")) {
621 $INFO['subscribedns'] = false;
622 msg(sprintf($lang[$act_msg.'_success'], $INFO['userinfo']['name'], $ns),1);
623 } else {
624 msg(sprintf($lang[$act_msg.'_error'], $INFO['userinfo']['name'], $ns),1);
628 return 'show';
631 //Setup VIM: ex: et ts=2 enc=utf-8 :