<?php

final class PhabricatorMetaMTAMailgunReceiveController
  extends PhabricatorMetaMTAController {

  public function shouldRequireLogin() {
    return false;
  }

  private function verifyMessage() {
    $request = $this->getRequest();
    $timestamp = $request->getStr('timestamp');
    $token = $request->getStr('token');
    $sig = $request->getStr('signature');

    // An install may configure multiple Mailgun mailers, and we might receive
    // inbound mail from any of them. Test the signature to see if it matches
    // any configured Mailgun mailer.

    $mailers = PhabricatorMetaMTAMail::newMailers(
      array(
        'inbound' => true,
        'types' => array(
          PhabricatorMailMailgunAdapter::ADAPTERTYPE,
        ),
      ));
    foreach ($mailers as $mailer) {
      $api_key = $mailer->getOption('api-key');
      $hash = hash_hmac('sha256', $timestamp.$token, $api_key);
      if (phutil_hashes_are_identical($sig, $hash)) {
        return true;
      }
    }

    return false;
  }

  public function handleRequest(AphrontRequest $request) {

    // No CSRF for Mailgun.
    $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();

    if (!$this->verifyMessage()) {
      throw new Exception(
        pht('Mail signature is not valid. Check your Mailgun API key.'));
    }

    $raw_headers = $request->getStr('message-headers');
    $raw_dict = array();
    if (strlen($raw_headers)) {
      $raw_headers = phutil_json_decode($raw_headers);
      foreach ($raw_headers as $raw_header) {
        list($name, $value) = $raw_header;
        $raw_dict[$name] = $value;
      }
    }

    $headers = array(
      'to'      => $request->getStr('recipient'),
      'from'    => $request->getStr('from'),
      'subject' => $request->getStr('subject'),
    ) + $raw_dict;

    $received = new PhabricatorMetaMTAReceivedMail();
    $received->setHeaders($headers);
    $received->setBodies(array(
      'text' => $request->getStr('stripped-text'),
      'html' => $request->getStr('stripped-html'),
    ));

    $file_phids = array();
    foreach ($_FILES as $file_raw) {
      try {
        $file = PhabricatorFile::newFromPHPUpload(
          $file_raw,
          array(
            'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
          ));
        $file_phids[] = $file->getPHID();
      } catch (Exception $ex) {
        phlog($ex);
      }
    }
    $received->setAttachments($file_phids);

    try {
      $received->save();
      $received->processReceivedMail();
    } catch (Exception $ex) {
      // We can get exceptions here in two cases.

      // First, saving the message may throw if we have already received a
      // message with the same Message ID. In this case, we're declining to
      // process a duplicate message, so failing silently is correct.

      // Second, processing the message may throw (for example, if it contains
      // an invalid !command). This will generate an email as a side effect,
      // so we don't need to explicitly handle the exception here.

      // In these cases, we want to return HTTP 200. If we do not, MailGun will
      // re-transmit the message later.
      phlog($ex);
    }

    $response = new AphrontWebpageResponse();
    $response->setContent(pht("Got it! Thanks, Mailgun!\n"));
    return $response;
  }

}
