Link.FYI

Pastebin

Create New My Pastes

Code (PHP) pasted on 2017-10-28, 01:56 Raw Source

  1. <?php
  2. namespace TSHW\Paste;
  3.  
  4. use League\OAuth2\Client\Provider\AbstractProvider;
  5. use League\Plates\Engine;
  6.  
  7. class App {
  8.  
  9.     /** @var Engine */
  10.     private $tpl;
  11.  
  12.     /** @var string */
  13.     private $dataDir;
  14.  
  15.     /** @var AbstractProvider */
  16.     private $oauthProvider;
  17.  
  18.     /** @var \GeSHi */
  19.     private $highlighter;
  20.  
  21.     private $popularLanguages = [
  22.         'php', 'java', 'scala', 'javascript', 'xml', 'text', 'python'
  23.     ];
  24.  
  25.     private $ignoredLanguages = [
  26.         'text'
  27.     ];
  28.  
  29.     private $userWhitelist = [
  30.         'Tar-Minyatur',
  31.         'chani'
  32.     ];
  33.  
  34.     /**
  35.      * App constructor.
  36.      * @param Engine $tpl Template engine (Plates)
  37.      * @param string $dataDir Directory where pastes are stored
  38.      * @param AbstractProvider $oauthProvider
  39.      */
  40.     public function __construct(Engine $tpl, string $dataDir, AbstractProvider $oauthProvider) {
  41.         $this->tpl = $tpl;
  42.         $this->dataDir = rtrim($dataDir, '/') . '/';
  43.         $this->oauthProvider = $oauthProvider;
  44.         $this->highlighter = new \GeSHi();
  45.         $this->highlighter->enable_classes(true);
  46.         $this->highlighter->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
  47.         $this->highlighter->set_line_style('background: #eaeaea; padding-left: 0.4em');
  48.         $this->highlighter->set_header_type(GESHI_HEADER_NONE);
  49.         $this->highlighter->set_overall_class('highlighted_code');
  50.     }
  51.  
  52.     public function handleRequest() {
  53.         session_start();
  54.  
  55.         if (array_key_exists('username', $_SESSION)) {
  56.             $this->tpl->addData(['user' => $_SESSION['username']]);
  57.         }
  58.  
  59.         $this->handleFormActions();
  60.  
  61.         $url = $_SERVER['REQUEST_URI'];
  62.         if (strpos($url, '?') !== false) {
  63.             $url = substr($url, 0, strpos($url, '?'));
  64.         }
  65.         if (preg_match('_^/([a-z0-9]{8,12})(/raw)?$_', $url, $matches)) {
  66.             $this->showPaste($matches[1], $matches[2] === '/raw');
  67.         } else if ($url === '/p/github-login') {
  68.             $this->handleOauthLogin();
  69.         } else if ($url === '/p/logout') {
  70.             unset($_SESSION['username']);
  71.             $this->redirect('/');
  72.         } else if ($url === '/') {
  73.             $this->enforceOauthLogin();
  74.             $this->showIndex();
  75.         } else {
  76.             $this->showError('Oh damn, this page does not exist. :-(');
  77.         }
  78.     }
  79.  
  80.     private function redirect(string $path) {
  81.         http_response_code(302);
  82.         $url = sprintf('%s://%s%s', (!empty($_SERVER['HTTPS']) ? 'https' : 'http'), $_SERVER['HTTP_HOST'], $path);
  83.         header('Location: ' . $url);
  84.         die('You will be redirected to ' . $url);
  85.     }
  86.  
  87.     private function showError(string $message) {
  88.         http_response_code(400);
  89.         die($this->tpl->render('error', ['message' => $message]));
  90.     }
  91.  
  92.     private function handleFormActions() {
  93.         if (array_key_exists('action', $_POST)) {
  94.             switch ($_POST['action']) {
  95.                 case "create":
  96.                     $pasteId = $this->createNewPaste($_POST);
  97.                     $this->redirect('/' . $pasteId);
  98.                     break;
  99.             }
  100.         }
  101.     }
  102.  
  103.     private function createNewPaste(array $params) {
  104.         if (!array_key_exists('code', $params)) {
  105.             $this->showError('Something went completely wrong. This should not have happened. :-( Please try again.');
  106.         }
  107.         $language = array_key_exists('language', $params) ? $params['language'] : 'text';
  108.         $pasteId = $this->getNewPasteId();
  109.         if (is_null($pasteId)) {
  110.             $this->showError('For some unclear reason I could not generate a new ID for you. :-( Please try again.');
  111.         }
  112.         $content = [
  113.             'language' => $language,
  114.             'created' => date('c'),
  115.             'creator' => $_SESSION['username'],
  116.             'code' => $params['code'],
  117.         ];
  118.         if (file_put_contents($this->dataDir . $pasteId, json_encode($content)) === false) {
  119.             $this->showError('Your paste could not be created. Sorry about that. :-( Please try again.');
  120.         }
  121.         return $pasteId;
  122.     }
  123.  
  124.     private function getNewPasteId() {
  125.         $attempts = 3;
  126.         $pasteId = null;
  127.         do {
  128.             $attempts -= 1;
  129.             $length = mt_rand(8, 12);
  130.             $hash = sha1(microtime());
  131.             $newId = substr($hash, 0, $length);
  132.             if (!file_exists($this->dataDir . $newId)) {
  133.                 $pasteId = $newId;
  134.             }
  135.         } while (is_null($pasteId) && ($attempts > 0));
  136.         return $pasteId;
  137.     }
  138.  
  139.     private function showPaste(string $pasteId, bool $showRaw = false) {
  140.         $filename = $this->dataDir . $pasteId;
  141.         if (!file_exists($filename)) {
  142.             $this->showError('Oh no...looks like this paste is gone or never existed. :-(');
  143.         }
  144.         $content = json_decode(file_get_contents($filename));
  145.         if (is_null($content)) {
  146.             $this->showError('Oh no...looks like this paste a little more than I can chew right now. :-(');
  147.         }
  148.         if ($showRaw) {
  149.             header("Content-Type: text/plain");
  150.             die($content->code);
  151.         }
  152.         $this->highlighter->set_source($content->code);
  153.         $this->highlighter->set_language($content->language);
  154.         if ($this->highlighter->error() !== false) {
  155.             $this->showError("This is embarrassing. I failed to render the paste you wanted to open. Sorry. :-(");
  156.         }
  157.         echo $this->tpl->render('paste', [
  158.             'pasteId' => $pasteId,
  159.             'source' => $this->highlighter->parse_code(),
  160.             'header' => 'Code (' . $this->highlighter->get_language_name() . ') pasted on ' . date('Y-m-d, H:i', strtotime($content->created)),
  161.             'stylesheet' => $this->highlighter->get_stylesheet()]);
  162.     }
  163.  
  164.     private function showIndex() {
  165.         $languages = [
  166.             'popular' => [],
  167.             'others' => []
  168.         ];
  169.         foreach (glob(BASE_PATH . '/vendor/geshi/geshi/src/geshi/*.php') as $file) {
  170.             $language = basename($file, '.php');
  171.             if (in_array($language, $this->ignoredLanguages)) continue;
  172.             include $file;
  173.             $languageName = $language_data['LANG_NAME'];
  174.             if (in_array($language, $this->popularLanguages)) {
  175.                 $languages['popular'][$language] = $languageName;
  176.             } else {
  177.                 $languages['others'][$language] = $languageName;
  178.             }
  179.         }
  180.         asort($languages['popular']);
  181.         asort($languages['others']);
  182.         echo $this->tpl->render('index', ['languages' => $languages]);
  183.     }
  184.  
  185.     private function handleOauthLogin() {
  186.         if (!isset($_GET['code'])) {
  187.             // If we don't have an authorization code then get one
  188.             $authUrl = $this->oauthProvider->getAuthorizationUrl();
  189.             $_SESSION['oauth2state'] = $this->oauthProvider->getState();
  190.             header('Location: ' . $authUrl);
  191.             die();
  192.         } elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
  193.             unset($_SESSION['oauth2state']);
  194.             $this->showError('Uh! Something went wrong with the authorization. I aborted this for security reasons, but please feel free to try again!');
  195.         } else {
  196.             // Try to get an access token (using the authorization code grant)
  197.             try {
  198.                 $token = $this->oauthProvider->getAccessToken('authorization_code', [
  199.                     'code' => $_GET['code']
  200.                 ]);
  201.                 // We got an access token, let's now get the user's details
  202.                 $user = $this->oauthProvider->getResourceOwner($token);
  203.                 if (!in_array($user->getNickname(), $this->userWhitelist)) {
  204.                     $this->showError('Sorry, your username (' . $user->getNickname() . ') is not whitelisted and therefore I cannot allow you to create Pastes. :-(');
  205.                 }
  206.                 $_SESSION['username'] = $user->getNickname();
  207.                 $this->redirect('/');
  208.             } catch (\Exception $e) {
  209.                 // Failed to get user details
  210.                 $this->showError('Oh no! Something is wrong with your authorization!');
  211.             }
  212.         }
  213.     }
  214.  
  215.     private function enforceOauthLogin() {
  216.         if (!array_key_exists('username', $_SESSION)) {
  217.             echo $this->tpl->render('login_required');
  218.             die();
  219.         }
  220.     }
  221.  
  222.  
  223. }