# -*- coding: binary -*-

module Msf
module Exploit::Remote::HTTP::Drupal

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super

    register_options([
      OptString.new('TARGETURI', [true, 'Path to Drupal install', '/'])
    ])
  end

  def setup
    super

    # Ensure we don't hit a redirect (e.g., /drupal -> /drupal/)
    # XXX: Naughty datastore modification instead of send_request_cgi!
    datastore['TARGETURI'] = normalize_uri(datastore['TARGETURI'], '/')
  end

  # Determine Drupal version
  #
  # @return [Rex::Version] Version as Rex::Version
  def drupal_version
    res = send_request_cgi(
      'method' => 'GET',
      'uri'    => normalize_uri(target_uri.path)
    )

    return unless res && res.code == 200

    # Check for an X-Generator header
    version = version_match(res.headers['X-Generator'])

    return version if version

    # Check for a <meta> tag
    generator = res.get_html_document.at(
      '//meta[@name = "Generator"]/@content'
    )

    return unless generator

    version_match(generator.value)
  end

  # Return CHANGELOG.txt
  #
  # @param version [Rex::Version] Rex::Version or version string
  # @return [String] CHANGELOG.txt as a string
  def drupal_changelog(version)
    return unless version && Rex::Version.correct?(version)

    uri = Rex::Version.new(version) < Rex::Version.new('8') ?
          normalize_uri(target_uri.path, 'CHANGELOG.txt') :
          normalize_uri(target_uri.path, 'core/CHANGELOG.txt')

    res = send_request_cgi(
      'method' => 'GET',
      'uri'    => uri
    )

    return unless res && res.code == 200

    res.body
  end

  # Check CHANGELOG.txt for patch level
  #
  # @param changelog [String] CHANGELOG.txt to search
  # @param patch [String] Patch to check for (example: SA-CORE-2019-003)
  # @return [Boolean, nil] Whether or not the patch was found or unknown
  def drupal_patch(changelog, patch)
    return unless changelog && patch

    # HACK: Patch level removed since undetermined 8.x release
    if changelog.include?('For a full list of fixes in the latest release')
      return nil
    elsif changelog.include?(patch)
      return true
    end

    false
  end

  # Match a Drupal version
  #
  # @param string [String] String to match against
  # @return [Rex::Version] Version as Rex::Version
  def version_match(string)
    return unless string

    # Perl devs love me; Ruby devs hate me
    string =~ /^Drupal ([\d.]+)/

    return unless $1 && Rex::Version.correct?($1)

    Rex::Version.new($1)
  end

end
end
