Verified Commit 87bf1eef authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3401255 by znerol, smustgrave, borisson_, phenaproxima, penyaskito,...

Issue #3401255 by znerol, smustgrave, borisson_, phenaproxima, penyaskito, kunal.sachdev:  Tighten config validation schema of system.mail mailer_dsn
parent 257fa315
Loading
Loading
Loading
Loading
Loading
+142 −0
Changes for core/config/schema/core.data_types.schema.yml: 142 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -1010,3 +1010,145 @@ entity_reference_selection.default:
# providing a specific schema.
entity_reference_selection.default:*:
  type: entity_reference_selection.default

# Schema for the configuration of mailer transport DSN.
mailer_dsn:
  type: mapping
  label: 'Symfony mailer transport DSN'
  mapping:
    scheme:
      type: string
      label: 'Scheme'
      constraints:
        NotBlank:
          message: 'The mailer DSN must contain a scheme.'
        Regex:
          # https://datatracker.ietf.org/doc/html/rfc3986#appendix-A
          pattern: '/^[a-z][a-z0-9+\-\.]*$/i'
          message: 'The mailer DSN scheme must start with a letter followed by zero or more letters, numbers, plus (+), minus (-) or periods (.)'
    host:
      type: string
      label: 'Host'
      constraints:
        NotBlank:
          message: 'The mailer DSN must contain a host (use "default" by default).'
        UriHost:
          message: 'The mailer DSN host should conform to RFC 3986 URI host component.'
    user:
      type: string
      nullable: true
      label: 'User'
    password:
      type: string
      nullable: true
      label: 'Password'
    port:
      type: integer
      nullable: true
      label: 'Port'
      constraints:
        Range:
          min: 0
          max: 65535
          notInRangeMessage: 'The mailer DSN port must be between 0 and 65535.'
    options:
      type: mailer_dsn.options.[%parent.scheme]
      label: 'Options'

mailer_dsn.options.*:
  type: sequence
  label: 'Options'
  sequence:
    type: string
    label: Option
    constraints:
      NotNull: []

mailer_dsn.options.null:
  type: mapping
  label: 'Null transport options'
  mapping: {}

mailer_dsn.options.native:
  type: mailer_dsn.options.null


mailer_dsn.options.sendmail:
  type: mapping
  label: 'Sendmail transport options'
  mapping:
    command:
      type: string
      nullable: true
      label: 'Command to be executed by sendmail transport'
      constraints:
        Regex:
          # Forbid any kind of control character.
          # @see https://stackoverflow.com/a/66587087
          pattern: '/([^\PC])/u'
          match: false
          message: 'The command option is not allowed to span multiple lines or contain control characters.'

mailer_dsn.options.sendmail+smtp:
  type: mailer_dsn.options.sendmail
  label: 'Sendmail transport options'

mailer_dsn.options.smtp:
  type: mapping
  label: 'SMTP options'
  mapping:
    verify_peer:
      type: boolean
      nullable: true
      label: 'TLS Peer Verification (defaults to true)'
    peer_fingerprint:
      type: string
      nullable: true
      label: 'TLS Peer Fingerprint (no default)'
      constraints:
        Regex:
          pattern: '/^[a-fA-F0-9]+$/'
          message: 'The peer_fingerprint option requires an md5, sha1 or sha256 certificate fingerprint in hex with all separators (colons) removed.'
    local_domain:
      type: string
      nullable: true
      label: 'Domain name or IP address that represents the identity of the client when establishing the SMTP session (defaults to 127.0.0.1)'
      constraints:
        Regex:
          # Forbid any kind of control character.
          # @see https://stackoverflow.com/a/66587087
          pattern: '/([^\PC])/u'
          match: false
          message: 'The local_domain is not allowed to span multiple lines or contain control characters.'
    restart_threshold:
      type: integer
      nullable: true
      label: 'Maximum number of messages to send before re-starting the transport (defaults to 100 messages)'
      constraints:
        Range:
          min: 0
    restart_threshold_sleep:
      type: float
      nullable: true
      label: 'Number of seconds to sleep between stopping and re-starting the transport (defaults to no delay)'
      constraints:
        Range:
          min: 0
    ping_threshold:
      type: integer
      nullable: true
      label: 'The minimum number of seconds between two messages required to ping the server (defaults to 100 seconds)'
      constraints:
        Range:
          min: 0
    max_per_second:
      type: integer
      nullable: true
      label: 'The number of messages to send per second (defaults to no limit)'
      constraints:
        Range:
          min: 0

mailer_dsn.options.smtps:
  type: mailer_dsn.options.smtp
  label: 'Secure SMTP options'
+27 −0
Changes for core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UriHostConstraint.php: 27 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Validation\Plugin\Validation\Constraint;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Validation\Attribute\Constraint;
use Symfony\Component\Validator\Constraint as SymfonyConstraint;

/**
 * Checks if a string conforms to the RFC 3986 host component.
 */
#[Constraint(
  id: 'UriHost',
  label: new TranslatableMarkup('URI host', [], ['context' => 'Validation']),
)]
class UriHostConstraint extends SymfonyConstraint {

  /**
   * The error message if validation fails.
   *
   * @var string
   */
  public string $message = 'This value should conform to RFC 3986 URI host component.';

}
+53 −0
Changes for core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UriHostConstraintValidator.php: 53 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Validation\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

/**
 * Validates if a string conforms to the RFC 3986 host component.
 */
class UriHostConstraintValidator extends ConstraintValidator {

  /**
   * {@inheritdoc}
   */
  public function validate($value, Constraint $constraint): void {
    assert($constraint instanceof UriHostConstraint);

    if ($value === NULL || $value === '') {
      return;
    }

    if (!is_string($value)) {
      throw new UnexpectedTypeException($value, 'string');
    }

    if (!$this->isValid($value)) {
      $this->context->addViolation($constraint->message);
    }
  }

  /**
   * Return TRUE if value is a valid hostname or IP address literal.
   */
  protected function isValid(string $value): bool {
    if (filter_var($value, \FILTER_VALIDATE_DOMAIN, \FILTER_FLAG_HOSTNAME) !== FALSE) {
      return TRUE;
    }

    if (str_starts_with($value, '[') && str_ends_with($value, ']')) {
      $address = substr($value, 1, strlen($value) - 2);
      if (filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== FALSE) {
        return TRUE;
      }
    }

    return FALSE;
  }

}
+1 −0
Changes for core/misc/cspell/dictionary.txt: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -608,6 +608,7 @@ skiptags
slatkin
smacss
smalldatetime
smtps
somecompany
sortablejs
specialchars
+1 −0
Changes for core/modules/config/tests/config_test/config/install/config_test.validation.yml: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -7,3 +7,4 @@ giraffe:
  hum2: hum2
uuid: '7C30C50E-641A-4E34-A7F1-46BCFB9BE5A3'
string__not_blank: 'this is a label'
host: 'localhost'
Loading