query.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <?php
  2. declare(strict_types=1);
  3. require(__DIR__ . '/../../constants.php');
  4. require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader
  5. Minz_Request::init();
  6. $token = Minz_Request::paramString('t');
  7. if (!ctype_alnum($token)) {
  8. header('HTTP/1.1 422 Unprocessable Entity');
  9. header('Content-Type: text/plain; charset=UTF-8');
  10. die('Invalid token `t`!' . $token);
  11. }
  12. $format = Minz_Request::paramString('f');
  13. if (!in_array($format, ['atom', 'html', 'opml', 'rss'], true)) {
  14. header('HTTP/1.1 422 Unprocessable Entity');
  15. header('Content-Type: text/plain; charset=UTF-8');
  16. die('Invalid format `f`!');
  17. }
  18. $user = Minz_Request::paramString('user');
  19. if (!FreshRSS_user_Controller::checkUsername($user)) {
  20. header('HTTP/1.1 422 Unprocessable Entity');
  21. header('Content-Type: text/plain; charset=UTF-8');
  22. die('Invalid user!');
  23. }
  24. Minz_Session::init('FreshRSS', true);
  25. FreshRSS_Context::initSystem();
  26. if (!FreshRSS_Context::hasSystemConf() || !FreshRSS_Context::systemConf()->api_enabled) {
  27. header('HTTP/1.1 503 Service Unavailable');
  28. header('Content-Type: text/plain; charset=UTF-8');
  29. die('Service Unavailable!');
  30. }
  31. FreshRSS_Context::initUser($user);
  32. if (!FreshRSS_Context::hasUserConf()) {
  33. usleep(rand(100, 10000)); //Primitive mitigation of scanning for users
  34. header('HTTP/1.1 404 Not Found');
  35. header('Content-Type: text/plain; charset=UTF-8');
  36. die('User not found!');
  37. } else {
  38. usleep(rand(20, 200));
  39. }
  40. if (!file_exists(DATA_PATH . '/no-cache.txt')) {
  41. require(LIB_PATH . '/http-conditional.php');
  42. // TODO: Consider taking advantage of $feedMode, only for monotonous queries {all, categories, feeds} and not dynamic ones {read/unread, favourites, user labels}
  43. if (httpConditional(FreshRSS_UserDAO::mtime($user) ?: time(), 0, 0, false, PHP_COMPRESSION, false)) {
  44. exit(); //No need to send anything
  45. }
  46. }
  47. Minz_Translate::init(FreshRSS_Context::userConf()->language);
  48. Minz_ExtensionManager::init();
  49. Minz_ExtensionManager::enableByList(FreshRSS_Context::userConf()->extensions_enabled, 'user');
  50. $query = null;
  51. $userSearch = null;
  52. foreach (FreshRSS_Context::userConf()->queries as $raw_query) {
  53. if (!empty($raw_query['token']) && $raw_query['token'] === $token) {
  54. switch ($format) {
  55. case 'atom':
  56. case 'html':
  57. case 'rss':
  58. if (empty($raw_query['shareRss'])) {
  59. continue 2;
  60. }
  61. break;
  62. case 'opml':
  63. if (empty($raw_query['shareOpml'])) {
  64. continue 2;
  65. }
  66. break;
  67. default:
  68. continue 2;
  69. }
  70. $query = new FreshRSS_UserQuery($raw_query, FreshRSS_Context::categories(), FreshRSS_Context::labels());
  71. Minz_Request::_param('get', $query->getGet());
  72. if (Minz_Request::paramString('order') === '') {
  73. Minz_Request::_param('order', $query->getOrder());
  74. }
  75. Minz_Request::_param('state', $query->getState());
  76. $search = $query->getSearch()->getRawInput();
  77. // Note: we disallow references to user queries in public user search to avoid sniffing internal user queries
  78. $userSearch = new FreshRSS_BooleanSearch(Minz_Request::paramString('search'), 0, 'AND', false);
  79. if ($userSearch->getRawInput() !== '') {
  80. if ($search === '') {
  81. $search = $userSearch->getRawInput();
  82. } else {
  83. $search .= ' (' . $userSearch->getRawInput() . ')';
  84. }
  85. }
  86. Minz_Request::_param('search', $search);
  87. break;
  88. }
  89. }
  90. if ($query === null || $userSearch === null) {
  91. usleep(rand(100, 10000));
  92. header('HTTP/1.1 404 Not Found');
  93. header('Content-Type: text/plain; charset=UTF-8');
  94. die('User query not found!');
  95. }
  96. $view = new FreshRSS_View();
  97. try {
  98. FreshRSS_Context::updateUsingRequest(false);
  99. Minz_Request::_param('search', $userSearch->getRawInput()); // Restore user search
  100. $view->entries = FreshRSS_index_Controller::listEntriesByContext();
  101. } catch (Minz_Exception $e) {
  102. Minz_Error::error(400, 'Bad user query!');
  103. die();
  104. }
  105. $get = FreshRSS_Context::currentGet(true);
  106. $type = (string)$get[0];
  107. $id = (int)$get[1];
  108. switch ($type) {
  109. case 'c': // Category
  110. $cat = FreshRSS_Context::categories()[$id] ?? null;
  111. if ($cat === null) {
  112. Minz_Error::error(404, "Category {$id} not found!");
  113. die();
  114. }
  115. $view->categories = [ $cat->id() => $cat ];
  116. break;
  117. case 'f': // Feed
  118. $feed = FreshRSS_Category::findFeed(FreshRSS_Context::categories(), $id);
  119. if ($feed === null) {
  120. Minz_Error::error(404, "Feed {$id} not found!");
  121. die();
  122. }
  123. $view->feeds = [ $feed->id() => $feed ];
  124. $view->categories = [];
  125. break;
  126. default:
  127. $view->categories = FreshRSS_Context::categories();
  128. break;
  129. }
  130. $view->disable_aside = true;
  131. $view->excludeMutedFeeds = true;
  132. $view->internal_rendering = true;
  133. $view->userQuery = $query;
  134. $view->html_url = $query->sharedUrlHtml();
  135. $view->rss_url = $query->sharedUrlRss();
  136. $view->rss_title = $query->getName();
  137. $view->image_url = $query->getImageUrl();
  138. $view->description = $query->getDescription() ?: _t('index.feed.rss_of', $view->rss_title);
  139. if ($query->getName() != '') {
  140. FreshRSS_View::_title($query->getName());
  141. }
  142. FreshRSS_Context::systemConf()->allow_anonymous = true;
  143. if (in_array($format, ['rss', 'atom'], true)) {
  144. header('Content-Type: application/rss+xml; charset=utf-8');
  145. $view->_layout(null);
  146. $view->_path('index/rss.phtml');
  147. } elseif ($format === 'opml') {
  148. if (!$query->safeForOpml()) {
  149. Minz_Error::error(404, 'OPML not allowed for this user query!');
  150. die();
  151. }
  152. header('Content-Type: application/xml; charset=utf-8');
  153. $view->_layout(null);
  154. $view->_path('index/opml.phtml');
  155. } else {
  156. $view->_layout('layout');
  157. $view->_path('index/html.phtml');
  158. }
  159. $view->build();