Programatically show page not found exception in Drupal 8

By Arun AK, 7 January, 2018

During development it is important to make sure not exploiting any kind of security issues. Here we are going to see how can secure server details without showing to public users while handling an exception.

In this example we have a custom menu callback which will process the argument which we are passing with the url. We will pass media id(mid) with the url and it will force prompt download corresponding the media file.

Define custom menu callback: custom_module.routing.yml

custom_module.file_download:
  path: '/download/file/{fid}'
  defaults:
    _controller: '\Drupal\custom_module\Controller\CustomFileDownload::download'
  requirements:
    _permission: 'access content'

As our custom call back refering a method inside controller define downloadFile method in CustomFileDownload.php:

<?php

namespace Drupal\custom_module\Controller;

use Drupal\Core\Config\ConfigFactory;
use Drupal\user\PrivateTempStoreFactory;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * CustomFileDownload.
 */

class CustomFileDownload extends ControllerBase {

  /**
   * Entity Type Manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManager
   */

  protected $entityTypeManager;

  /**
   * User Private Tempstore.
   *
   * @var \Drupal\user\PrivateTempStoreFactory
   */

  protected $userPrivateTempstore;

  /**
   * Class constructor.
   *
   * @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
   *   A EntityTypeManager Object.
   * @param \Drupal\user\PrivateTempStoreFactory $userPrivateTempstore
   *   A PrivateTempStoreFactory Object.
   * @param \Drupal\Core\Config\ConfigFactory $configFactory
   *   A ConfigFactory Object.
   */

  public function __construct(EntityTypeManager $entityTypeManager, PrivateTempStoreFactory $userPrivateTempstore, ConfigFactory $configFactory) {
    $this->entityTypeManager = $entityTypeManager;
    $this->userPrivateTempstore = $userPrivateTempstore;
    $this->configFactory = $configFactory;
  }

  /**
   * {@inheritdoc}
   */

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('user.private_tempstore'),
      $container->get('config.factory')
    );
  }

  /**
   * Display the markup.
   */

  public function download($mid) {
    $temp = base64_decode($mid);
    if (!preg_match("@^http@i", $temp)) {
      $media = $this->entityTypeManager->getStorage('media')->load($mid);
        $media = $media->field_document->entity->getFileUri();
    }
    else {
      $media = base64_decode($mid);
    }
    header('Content-Type: ' . mime_content_type($media));
    header("Content-disposition: attachment; filename=\"" . basename($media) . "\"");
    readfile($media);

    die();

  }

}

In this if we pass the media id like below:

http://www.example.com/download/1

It will prompt download if given media id is valid.

Suppose if some trying to access this url by passing a wrong value as media id it will throw error like below:
Exception Error

To avoid this kind of issues, we can add proper validation and can show custom error page to the user.

  /**
   * Display the markup.
   */

  public function download($mid) {
    $temp = base64_decode($mid);
    if (!preg_match("@^http@i", $temp)) {
      $media = $this->entityTypeManager->getStorage('media')->load($mid);
          if(is_object($media)) {
        $media = $media->field_document->entity->getFileUri();
          }
          else {
                throw new NotFoundHttpException();
          }
    }
    else {
      $media = base64_decode($mid);
    }
    header('Content-Type: ' . mime_content_type($media));
    header("Content-disposition: attachment; filename=\"" . basename($media) . "\"");
    readfile($media);

    die();

  }

In the above code we have added if condition to check whether that media is exist or not. If media id is not valid or media doesn't exist we will throw page not found exception as response.

As we are using NotFoundHttpException class to show the page not found exception we need to add following name space at top of the class:

use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

NotFoundHttpException class help us to show a page not found error message instead of reveals full controller file path, if given media id is not valid or media does not exist.