Dotclear

source: inc/public/lib.urlhandlers.php @ 2919:230eb29a531e

Revision 2919:230eb29a531e, 17.5 KB checked in by Dsls, 11 years ago (diff)

disable clickjacking in preview when clickjacking protection is not enabled, addresses #2049

Line 
1<?php
2# -- BEGIN LICENSE BLOCK ---------------------------------------
3#
4# This file is part of Dotclear 2.
5#
6# Copyright (c) 2003-2013 Olivier Meunier & Association Dotclear
7# Licensed under the GPL version 2.0 license.
8# See LICENSE file or
9# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10#
11# -- END LICENSE BLOCK -----------------------------------------
12if (!defined('DC_RC_PATH')) { return; }
13
14class dcUrlHandlers extends urlHandler
15{
16     public $args;
17
18     public function getURLFor($type,$value='') {
19          $core =& $GLOBALS['core'];
20          $url = $core->callBehavior("publicGetURLFor",$type,$value);
21          if (!$url) {
22               $url = $this->getBase($type);
23               if ($value) {
24                    if ($url) {
25                         $url .= '/';
26                    }
27                    $url .= $value;
28               }
29          }
30          return $url;
31     }
32
33     public function register($type,$url,$representation,$handler)
34     {
35          $core =& $GLOBALS['core'];
36          $t = new ArrayObject(array($type,$url,$representation,$handler));
37          $core->callBehavior("publicRegisterURL",$t);
38          parent::register($t[0],$t[1],$t[2],$t[3]);
39     }
40
41     public static function p404()
42     {
43          throw new Exception ("Page not found",404);
44     }
45
46     public static function default404($args,$type,$e)
47     {
48          if ($e->getCode() != 404) {
49               throw $e;
50          }
51          $_ctx =& $GLOBALS['_ctx'];
52          $core = $GLOBALS['core'];
53
54          header('Content-Type: text/html; charset=UTF-8');
55          http::head(404,'Not Found');
56          $core->url->type = '404';
57          $_ctx->current_tpl = '404.html';
58          $_ctx->content_type = 'text/html';
59
60          echo $core->tpl->getData($_ctx->current_tpl);
61
62          # --BEHAVIOR-- publicAfterDocument
63          $core->callBehavior('publicAfterDocument',$core);
64          exit;
65     }
66
67     protected static function getPageNumber(&$args)
68     {
69          if (preg_match('#(^|/)page/([0-9]+)$#',$args,$m)) {
70               $n = (integer) $m[2];
71               if ($n > 0) {
72                    $args = preg_replace('#(^|/)page/([0-9]+)$#','',$args);
73                    return $n;
74               }
75          }
76
77          return false;
78     }
79
80     protected static function serveDocument($tpl,$content_type='text/html',$http_cache=true,$http_etag=true)
81     {
82          $_ctx =& $GLOBALS['_ctx'];
83          $core =& $GLOBALS['core'];
84
85          if ($_ctx->nb_entry_per_page === null) {
86               $_ctx->nb_entry_per_page = $core->blog->settings->system->nb_post_per_page;
87          }
88          if ($_ctx->nb_entry_first_page === null) {
89               $_ctx->nb_entry_first_page = $_ctx->nb_entry_per_page;
90          }
91
92          $tpl_file = $core->tpl->getFilePath($tpl);
93
94          if (!$tpl_file) {
95               throw new Exception('Unable to find template ');
96          }
97
98          $result = new ArrayObject;
99
100          $_ctx->current_tpl = $tpl;
101          $_ctx->content_type = $content_type;
102          $_ctx->http_cache = $http_cache;
103          $_ctx->http_etag = $http_etag;
104          $core->callBehavior('urlHandlerBeforeGetData',$_ctx);
105
106          if ($_ctx->http_cache) {
107               $GLOBALS['mod_files'][] = $tpl_file;
108               http::cache($GLOBALS['mod_files'],$GLOBALS['mod_ts']);
109          }
110
111          header('Content-Type: '.$_ctx->content_type.'; charset=UTF-8');
112          if ($core->blog->settings->system->prevents_clickjacking) {
113               if ($_ctx->exists('xframeoption')) {
114                    $url = parse_url($_ctx->xframeoption);
115                    header(sprintf('X-Frame-Options: %s', is_array($url)?("ALLOW-FROM ".$url['scheme'].'://'.$url['host']):'SAMEORIGIN'));
116               } else {
117                    // Prevents Clickjacking as far as possible
118                    header('X-Frame-Options: SAMEORIGIN'); // FF 3.6.9+ Chrome 4.1+ IE 8+ Safari 4+ Opera 10.5+
119               }
120          }
121
122          $result['content'] = $core->tpl->getData($_ctx->current_tpl);
123          $result['content_type'] = $_ctx->content_type;
124          $result['tpl'] = $_ctx->current_tpl;
125          $result['blogupddt'] = $core->blog->upddt;
126
127          # --BEHAVIOR-- urlHandlerServeDocument
128          $core->callBehavior('urlHandlerServeDocument',$result);
129
130          if ($_ctx->http_cache && $_ctx->http_etag) {
131               http::etag($result['content'],http::getSelfURI());
132          }
133          echo $result['content'];
134     }
135
136     public function getDocument()
137     {
138          $core =& $GLOBALS['core'];
139
140          $type = $args = '';
141
142          if ($this->mode == 'path_info')
143          {
144               $part = substr($_SERVER['PATH_INFO'],1);
145          }
146          else
147          {
148               $part = '';
149
150               $qs = $this->parseQueryString();
151
152               # Recreates some _GET and _REQUEST pairs
153               if (!empty($qs))
154               {
155                    foreach ($_GET as $k => $v) {
156                         if (isset($_REQUEST[$k])) {
157                              unset($_REQUEST[$k]);
158                         }
159                    }
160                    $_GET = $qs;
161                    $_REQUEST = array_merge($qs,$_REQUEST);
162
163                    list($k,$v) = each($qs);
164                    if ($v === null) {
165                         $part = $k;
166                         unset($_GET[$k]);
167                         unset($_REQUEST[$k]);
168                    }
169               }
170          }
171
172          $_SERVER['URL_REQUEST_PART'] = $part;
173
174          $this->getArgs($part,$type,$this->args);
175
176          # --BEHAVIOR-- urlHandlerGetArgsDocument
177          $core->callBehavior('urlHandlerGetArgsDocument',$this);
178
179          if (!$type)
180          {
181               $this->type = 'default';
182               $this->callDefaultHandler($this->args);
183          }
184          else
185          {
186               $this->type = $type;
187               $this->callHandler($type,$this->args);
188          }
189     }
190
191     public static function home($args)
192     {
193          $n = self::getPageNumber($args);
194
195          if ($args && !$n)
196          {
197               # "Then specified URL went unrecognized by all URL handlers and
198               # defaults to the home page, but is not a page number.
199               self::p404();
200          }
201          else
202          {
203               $_ctx =& $GLOBALS['_ctx'];
204               $core =& $GLOBALS['core'];
205
206               if ($n) {
207                    $GLOBALS['_page_number'] = $n;
208                    $core->url->type = $n > 1 ? 'default-page' : 'default';
209               }
210
211               if (empty($_GET['q'])) {
212                    if ($core->blog->settings->system->nb_post_for_home !== null) {
213                         $_ctx->nb_entry_first_page = $core->blog->settings->system->nb_post_for_home;
214                    }
215                    self::serveDocument('home.html');
216                    $core->blog->publishScheduledEntries();
217               } else {
218                    self::search();
219               }
220          }
221     }
222
223     public static function search()
224     {
225          $_ctx =& $GLOBALS['_ctx'];
226          $core =& $GLOBALS['core'];
227
228          $core->url->type='search';
229
230          $GLOBALS['_search'] = !empty($_GET['q']) ? rawurldecode($_GET['q']) : '';
231          if ($GLOBALS['_search']) {
232               $params = new ArrayObject(array('search' => $GLOBALS['_search']));
233               $core->callBehavior('publicBeforeSearchCount',$params);
234               $GLOBALS['_search_count'] = $core->blog->getPosts($params,true)->f(0);
235          }
236
237          self::serveDocument('search.html');
238     }
239
240     public static function lang($args)
241     {
242          $_ctx =& $GLOBALS['_ctx'];
243          $core =& $GLOBALS['core'];
244
245          $n = self::getPageNumber($args);
246          $params = new ArrayObject(array(
247               'lang' => $args));
248
249          $core->callBehavior('publicLangBeforeGetLangs',$params,$args);
250
251          $_ctx->langs = $core->blog->getLangs($params);
252
253          if ($_ctx->langs->isEmpty()) {
254               # The specified language does not exist.
255               self::p404();
256          }
257          else
258          {
259               if ($n) {
260                    $GLOBALS['_page_number'] = $n;
261               }
262               $_ctx->cur_lang = $args;
263               self::home(null);
264          }
265     }
266
267     public static function category($args)
268     {
269          $_ctx =& $GLOBALS['_ctx'];
270          $core =& $GLOBALS['core'];
271
272          $n = self::getPageNumber($args);
273
274          if ($args == '' && !$n) {
275               # No category was specified.
276               self::p404();
277          }
278          else
279          {
280               $params = new ArrayObject(array(
281                    'cat_url' => $args,
282                    'post_type' => 'post',
283                    'without_empty' => false));
284
285               $core->callBehavior('publicCategoryBeforeGetCategories',$params,$args);
286
287               $_ctx->categories = $core->blog->getCategories($params);
288
289               if ($_ctx->categories->isEmpty()) {
290                    # The specified category does no exist.
291                    self::p404();
292               }
293               else
294               {
295                    if ($n) {
296                         $GLOBALS['_page_number'] = $n;
297                    }
298                    self::serveDocument('category.html');
299               }
300          }
301     }
302
303     public static function archive($args)
304     {
305          $_ctx =& $GLOBALS['_ctx'];
306          $core =& $GLOBALS['core'];
307
308          $year = $month = $cat_url = null;
309          # Nothing or year and month
310          if ($args == '')
311          {
312               self::serveDocument('archive.html');
313          }
314          elseif (preg_match('|^/([0-9]{4})/([0-9]{2})$|',$args,$m))
315          {
316               $params = new ArrayObject(array(
317                    'year' => $m[1],
318                    'month' => $m[2],
319                    'type' => 'month'));
320
321               $core->callBehavior('publicArchiveBeforeGetDates',$params,$args);
322
323               $_ctx->archives = $core->blog->getDates($params);
324
325               if ($_ctx->archives->isEmpty()) {
326                    # There is no entries for the specified period.
327                    self::p404();
328               }
329               else
330               {
331                    self::serveDocument('archive_month.html');
332               }
333          }
334          else {
335               # The specified URL is not a date.
336               self::p404();
337          }
338     }
339
340     public static function post($args)
341     {
342          if ($args == '') {
343               # No entry was specified.
344               self::p404();
345          }
346          else
347          {
348               $_ctx =& $GLOBALS['_ctx'];
349               $core =& $GLOBALS['core'];
350
351               $core->blog->withoutPassword(false);
352
353               $params = new ArrayObject(array(
354                    'post_url' => $args));
355
356               $core->callBehavior('publicPostBeforeGetPosts',$params,$args);
357
358               $_ctx->posts = $core->blog->getPosts($params);
359
360               $_ctx->comment_preview = new ArrayObject();
361               $_ctx->comment_preview['content'] = '';
362               $_ctx->comment_preview['rawcontent'] = '';
363               $_ctx->comment_preview['name'] = '';
364               $_ctx->comment_preview['mail'] = '';
365               $_ctx->comment_preview['site'] = '';
366               $_ctx->comment_preview['preview'] = false;
367               $_ctx->comment_preview['remember'] = false;
368
369               $core->blog->withoutPassword(true);
370
371               if ($_ctx->posts->isEmpty())
372               {
373                    # The specified entry does not exist.
374                    self::p404();
375               }
376               else
377               {
378                    $post_id = $_ctx->posts->post_id;
379                    $post_password = $_ctx->posts->post_password;
380
381                    # Password protected entry
382                    if ($post_password != '' && !$_ctx->preview)
383                    {
384                         # Get passwords cookie
385                         if (isset($_COOKIE['dc_passwd'])) {
386                              $pwd_cookie = json_decode($_COOKIE['dc_passwd']);
387                              if ($pwd_cookie === NULL) {
388                                   $pwd_cookie = array();
389                              } else {
390                                   $pwd_cookie = (array) $pwd_cookie;
391                              }
392                         } else {
393                              $pwd_cookie = array();
394                         }
395
396                         # Check for match
397                         # Note: We must prefix post_id key with '#'' in pwd_cookie array in order to avoid integer conversion
398                         # because MyArray["12345"] is treated as MyArray[12345]
399                         if ((!empty($_POST['password']) && $_POST['password'] == $post_password)
400                         || (isset($pwd_cookie['#'.$post_id]) && $pwd_cookie['#'.$post_id] == $post_password))
401                         {
402                              $pwd_cookie['#'.$post_id] = $post_password;
403                              setcookie('dc_passwd',json_encode($pwd_cookie),0,'/');
404                         }
405                         else
406                         {
407                              self::serveDocument('password-form.html','text/html',false);
408                              return;
409                         }
410                    }
411
412                    $post_comment =
413                         isset($_POST['c_name']) && isset($_POST['c_mail']) &&
414                         isset($_POST['c_site']) && isset($_POST['c_content']) &&
415                         $_ctx->posts->commentsActive();
416
417                    # Posting a comment
418                    if ($post_comment)
419                    {
420                         # Spam trap
421                         if (!empty($_POST['f_mail'])) {
422                              http::head(412,'Precondition Failed');
423                              header('Content-Type: text/plain');
424                              echo "So Long, and Thanks For All the Fish";
425                              # Exits immediately the application to preserve the server.
426                              exit;
427                         }
428
429                         $name = $_POST['c_name'];
430                         $mail = $_POST['c_mail'];
431                         $site = $_POST['c_site'];
432                         $content = $_POST['c_content'];
433                         $preview = !empty($_POST['preview']);
434
435                         if ($content != '')
436                         {
437                              # --BEHAVIOR-- publicBeforeCommentTransform
438                              $buffer = $core->callBehavior('publicBeforeCommentTransform',$content);
439                              if ($buffer != '') {
440                                   $content = $buffer;
441                              } else {
442                                   if ($core->blog->settings->system->wiki_comments) {
443                                        $core->initWikiComment();
444                                   } else {
445                                        $core->initWikiSimpleComment();
446                                   }
447                                   $content = $core->wikiTransform($content);
448                              }
449                              $content = $core->HTMLfilter($content);
450                         }
451
452                         $_ctx->comment_preview['content'] = $content;
453                         $_ctx->comment_preview['rawcontent'] = $_POST['c_content'];
454                         $_ctx->comment_preview['name'] = $name;
455                         $_ctx->comment_preview['mail'] = $mail;
456                         $_ctx->comment_preview['site'] = $site;
457
458                         if ($preview)
459                         {
460                              # --BEHAVIOR-- publicBeforeCommentPreview
461                              $core->callBehavior('publicBeforeCommentPreview',$_ctx->comment_preview);
462
463                              $_ctx->comment_preview['preview'] = true;
464                         }
465                         else
466                         {
467                              # Post the comment
468                              $cur = $core->con->openCursor($core->prefix.'comment');
469                              $cur->comment_author = $name;
470                              $cur->comment_site = html::clean($site);
471                              $cur->comment_email = html::clean($mail);
472                              $cur->comment_content = $content;
473                              $cur->post_id = $_ctx->posts->post_id;
474                              $cur->comment_status = $core->blog->settings->system->comments_pub ? 1 : -1;
475                              $cur->comment_ip = http::realIP();
476
477                              $redir = $_ctx->posts->getURL();
478                              $redir .= $core->blog->settings->system->url_scan == 'query_string' ? '&' : '?';
479
480                              try
481                              {
482                                   if (!text::isEmail($cur->comment_email)) {
483                                        throw new Exception(__('You must provide a valid email address.'));
484                                   }
485
486                                   # --BEHAVIOR-- publicBeforeCommentCreate
487                                   $core->callBehavior('publicBeforeCommentCreate',$cur);
488                                   if ($cur->post_id) {
489                                        $comment_id = $core->blog->addComment($cur);
490
491                                        # --BEHAVIOR-- publicAfterCommentCreate
492                                        $core->callBehavior('publicAfterCommentCreate',$cur,$comment_id);
493                                   }
494
495                                   if ($cur->comment_status == 1) {
496                                        $redir_arg = 'pub=1';
497                                   } else {
498                                        $redir_arg = 'pub=0';
499                                   }
500
501                                   header('Location: '.$redir.$redir_arg);
502                              }
503                              catch (Exception $e)
504                              {
505                                   $_ctx->form_error = $e->getMessage();
506                                   $_ctx->form_error;
507                              }
508                         }
509                    }
510
511                    # The entry
512                    if ($_ctx->posts->trackbacksActive()) {
513                         header('X-Pingback: '.$core->blog->url.$core->url->getURLFor("xmlrpc",$core->blog->id));
514                    }
515                    self::serveDocument('post.html');
516               }
517          }
518     }
519
520     public static function preview($args)
521     {
522          $core = $GLOBALS['core'];
523          $_ctx = $GLOBALS['_ctx'];
524
525          if (!preg_match('#^(.+?)/([0-9a-z]{40})/(.+?)$#',$args,$m)) {
526               # The specified Preview URL is malformed.
527               self::p404();
528          }
529          else
530          {
531               $user_id = $m[1];
532               $user_key = $m[2];
533               $post_url = $m[3];
534               if (!$core->auth->checkUser($user_id,null,$user_key)) {
535                    # The user has no access to the entry.
536                    self::p404();
537               }
538               else
539               {
540                    $_ctx->preview = true;
541                    if (defined ("DC_ADMIN_URL")) {
542                         $_ctx->xframeoption=DC_ADMIN_URL;
543                    }
544                    self::post($post_url);
545               }
546          }
547     }
548
549     public static function feed($args)
550     {
551          $type = null;
552          $comments = false;
553          $cat_url = false;
554          $post_id = null;
555          $subtitle = '';
556
557          $mime = 'application/xml';
558
559          $_ctx =& $GLOBALS['_ctx'];
560          $core =& $GLOBALS['core'];
561
562          if (preg_match('!^([a-z]{2}(-[a-z]{2})?)/(.*)$!',$args,$m)) {
563               $params = new ArrayObject(array('lang' => $m[1]));
564
565               $args = $m[3];
566
567               $core->callBehavior('publicFeedBeforeGetLangs',$params,$args);
568
569               $_ctx->langs = $core->blog->getLangs($params);
570
571               if ($_ctx->langs->isEmpty()) {
572                    # The specified language does not exist.
573                    self::p404();
574                    return;
575               } else {
576                    $_ctx->cur_lang = $m[1];
577               }
578          }
579
580          if (preg_match('#^rss2/xslt$#',$args,$m))
581          {
582               # RSS XSLT stylesheet
583               self::serveDocument('rss2.xsl','text/xml');
584               return;
585          }
586          elseif (preg_match('#^(atom|rss2)/comments/([0-9]+)$#',$args,$m))
587          {
588               # Post comments feed
589               $type = $m[1];
590               $comments = true;
591               $post_id = (integer) $m[2];
592          }
593          elseif (preg_match('#^(?:category/(.+)/)?(atom|rss2)(/comments)?$#',$args,$m))
594          {
595               # All posts or comments feed
596               $type = $m[2];
597               $comments = !empty($m[3]);
598               if (!empty($m[1])) {
599                    $cat_url = $m[1];
600               }
601          }
602          else
603          {
604               # The specified Feed URL is malformed.
605               self::p404();
606               return;
607          }
608
609          if ($cat_url)
610          {
611               $params = new ArrayObject(array(
612                    'cat_url' => $cat_url,
613                    'post_type' => 'post'));
614
615               $core->callBehavior('publicFeedBeforeGetCategories',$params,$args);
616
617               $_ctx->categories = $core->blog->getCategories($params);
618
619               if ($_ctx->categories->isEmpty()) {
620                    # The specified category does no exist.
621                    self::p404();
622                    return;
623               }
624
625               $subtitle = ' - '.$_ctx->categories->cat_title;
626          }
627          elseif ($post_id)
628          {
629               $params = new ArrayObject(array(
630                    'post_id' => $post_id,
631                    'post_type' => ''));
632
633               $core->callBehavior('publicFeedBeforeGetPosts',$params,$args);
634
635               $_ctx->posts = $core->blog->getPosts($params);
636
637               if ($_ctx->posts->isEmpty()) {
638                    # The specified post does not exist.
639                    self::p404();
640                    return;
641               }
642
643               $subtitle = ' - '.$_ctx->posts->post_title;
644          }
645
646          $tpl = $type;
647          if ($comments) {
648               $tpl .= '-comments';
649               $_ctx->nb_comment_per_page = $core->blog->settings->system->nb_comment_per_feed;
650          } else {
651               $_ctx->nb_entry_per_page = $core->blog->settings->system->nb_post_per_feed;
652               $_ctx->short_feed_items = $core->blog->settings->system->short_feed_items;
653          }
654          $tpl .= '.xml';
655
656          if ($type == 'atom') {
657               $mime = 'application/atom+xml';
658          }
659
660          $_ctx->feed_subtitle = $subtitle;
661
662          header('X-Robots-Tag: '.context::robotsPolicy($core->blog->settings->system->robots_policy,''));
663          self::serveDocument($tpl,$mime);
664          if (!$comments && !$cat_url) {
665               $core->blog->publishScheduledEntries();
666          }
667     }
668
669     public static function trackback($args)
670     {
671          if (!preg_match('/^[0-9]+$/',$args)) {
672               # The specified trackback URL is not an number
673               self::p404();
674          } else {
675               $tb = new dcTrackback($GLOBALS['core']);
676               $tb->receive($args);
677          }
678     }
679
680     public static function rsd($args)
681     {
682          $core =& $GLOBALS['core'];
683          http::cache($GLOBALS['mod_files'],$GLOBALS['mod_ts']);
684
685          header('Content-Type: text/xml; charset=UTF-8');
686          echo
687          '<?xml version="1.0" encoding="UTF-8"?>'."\n".
688          '<rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">'."\n".
689          "<service>\n".
690          "  <engineName>Dotclear</engineName>\n".
691          "  <engineLink>http://www.dotclear.org/</engineLink>\n".
692          '  <homePageLink>'.html::escapeHTML($core->blog->url)."</homePageLink>\n";
693
694          if ($core->blog->settings->system->enable_xmlrpc)
695          {
696               $u = sprintf(DC_XMLRPC_URL,$core->blog->url,$core->blog->id);
697
698               echo
699               "  <apis>\n".
700               '    <api name="WordPress" blogID="1" preferred="true" apiLink="'.$u.'"/>'."\n".
701               '    <api name="Movable Type" blogID="1" preferred="false" apiLink="'.$u.'"/>'."\n".
702               '    <api name="MetaWeblog" blogID="1" preferred="false" apiLink="'.$u.'"/>'."\n".
703               '    <api name="Blogger" blogID="1" preferred="false" apiLink="'.$u.'"/>'."\n".
704               "  </apis>\n";
705          }
706
707          echo
708          "</service>\n".
709          "</rsd>\n";
710     }
711
712     public static function xmlrpc($args)
713     {
714          $core =& $GLOBALS['core'];
715          $blog_id = preg_replace('#^([^/]*).*#','$1',$args);
716          $server = new dcXmlRpc($core,$blog_id);
717          $server->serve();
718     }
719}
Note: See TracBrowser for help on using the repository browser.

Sites map