Dotclear

source: inc/public/lib.urlhandlers.php @ 2797:4a5f0d16acd2

Revision 2797:4a5f0d16acd2, 17.2 KB checked in by franck <carnet.franck.paul@…>, 11 years ago (diff)

Add an optional setting (blog pref) to prevent blog from Clickjacking

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

Sites map