ronin-rb/ronin-web-user_agents

View on GitHub
lib/ronin/web/user_agents/chrome.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# frozen_string_literal: true
#
# ronin-web-user_agents - Yet another User-Agent string generator library.
#
# Copyright (c) 2006-2024 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-web-user_agents is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-web-user_agents is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-web-user_agents.  If not, see <https://www.gnu.org/licenses/>
#

require 'ronin/web/user_agents/os/windows'
require 'ronin/web/user_agents/os/mac_os'
require 'ronin/web/user_agents/os/linux'
require 'ronin/web/user_agents/os/android'

module Ronin
  module Web
    module UserAgents
      #
      # Represents every possible Chrome `User-Agent` string.
      #
      module Chrome
        #
        # Builds a new Chrome `User-Agent` string.
        #
        # @param [String] chrome_version
        #   The `Chrome/...` version.
        #
        # @param [:windows, :macos, :linux, :android] os
        #   The Operating System.
        #
        # @param [String, nil] os_version
        #   The Operating System version. Is required if `os:` is `:windows`,
        #   `:macos`, or `:android`.
        #
        # @param [:ubuntu, :fedora, :arch, String, nil] linux_distro
        #   The optional Linux Distro. Only supported if `os:` is `:linux`.
        #
        # @param [:x86_64, :x86, :i686, :aarch64, :arm64, :arm] arch
        #   The hardware architecture. Can be omitted if `os:` is `:android`.
        #
        # @param [String, nil] android_device
        #   The Android device. Only supported if `os:` is `:android`.
        #
        # @return [String]
        #   The new `User-Agent` string.
        #
        # @api public
        #
        def self.build(chrome_version: ,
                       os: ,
                       os_version: nil,
                       linux_distro: nil,
                       arch: nil,
                       android_device: nil)
          case os
          when :windows
            unless os_version
              raise(ArgumentError,"os: :windows also requires an os_version: value")
            end

            build_windows(
              chrome_version:  chrome_version,
              windows_version: os_version,
              arch:            arch
            )
          when :macos
            unless os_version
              raise(ArgumentError,"os: :macos also requires an os_version: value")
            end

            build_macos(
              chrome_version: chrome_version,
              macos_version:  os_version,
              arch:           arch || :intel
            )
          when :linux
            unless arch
              raise(ArgumentError,"os: :linux also requires an arch: value")
            end

            build_linux(
              chrome_version: chrome_version,
              linux_distro:   linux_distro,
              arch:           arch
            )
          when :android
            unless os_version
              raise(ArgumentError,"os: :android also requires an os_version: value")
            end

            build_android(
              chrome_version:  chrome_version,
              android_version: os_version,
              arch:            arch,
              android_device:  android_device
            )
          else
            raise(ArgumentError,"unsupported os: value (#{os.inspect})")
          end
        end

        # Known Chrome versions
        KNOWN_VERSIONS = File.readlines(
          File.join(DATA_DIR,'chrome','versions.txt'), chomp: true
        )

        # Supported Operating Systems.
        SUPPORTED_OSES = [
          :windows,
          :macos,
          :linux,
          :android
        ]

        # Known OS versions grouped by OS.
        KNOWN_OS_VERSIONS = {
          windows: OS::Windows::VERSIONS.keys,
          macos:   OS::MacOS::VERSIONS,
          linux:   [],
          android: OS::Android::VERSIONS
        }

        # Supported architectures grouped by OS.
        SUPPORTED_OS_ARCHES = {
          windows: OS::Windows::ARCHES.keys,
          macos:   OS::MacOS::ARCHES.keys,
          linux:   OS::Linux::ARCHES.keys,
          android: OS::Android::ARCHES.keys
        }

        # Supported Linux Distros.
        SUPPORTED_LINUX_DISTROS = OS::Linux::DISTROS.keys

        #
        # Generates a random Chrome `User-Agent` string.
        #
        # @param [String] chrome_version
        #   The `Chrome/...` version.
        #
        # @param [:windows, :macos, :linux, :android] os
        #   The Operating System.
        #
        # @param [String, nil] os_version
        #   The Operating System version. Is required if `os:` is `:windows`,
        #   `:macos`, or `:android`.
        #
        # @param [:ubuntu, :fedora, :arch, String, nil] linux_distro
        #   The optional Linux Distro. Only supported if `os:` is `:linux`.
        #
        # @param [:x86_64, :x86, :i686, :aarch64, :arm64, :arm] arch
        #   The hardware architecture. Can be omitted if `os:` is `:android`.
        #
        # @param [String, nil] android_device
        #   The Android device. Only supported if `os:` is `:android`.
        #
        # @return [String]
        #   The random `User-Agent` string.
        #
        # @api public
        #
        def self.random(chrome_version: KNOWN_VERSIONS.sample,
                        os:             SUPPORTED_OSES.sample,
                        os_version:     KNOWN_OS_VERSIONS[os].sample,
                        linux_distro:   SUPPORTED_LINUX_DISTROS.sample,
                        arch:           SUPPORTED_OS_ARCHES[os].sample,
                        android_device: OS::Android::DEVICES.sample)
          build(
            chrome_version: chrome_version,
            os: os,
            os_version: os_version,
            linux_distro: linux_distro,
            arch: arch,
            android_device: android_device
          )
        end

        #
        # Builds a Chrome `User-Agent` string for Windows.
        #
        # @param [String] chrome_version
        #   The `Chrome/...` version.
        #
        # @param [String, nil] windows_version
        #   The Windows version.
        #
        # @param [:x86_64, :x86, :i686, :aarch64, :arm64, :arm] arch
        #   The hardware architecture.
        #
        # @return [String]
        #   The Chrome `User-Agent` string for Windows.
        #
        def self.build_windows(chrome_version: , windows_version: , arch: nil)
          windows_version = OS::Windows::VERSIONS.fetch(windows_version,windows_version)
          windows_arch    = OS::Windows::ARCHES.fetch(arch) do
                              raise(ArgumentError,"unknown arch: value (#{arch.inspect})")
                            end

          extensions = "Windows NT #{windows_version}"
          extensions << "; #{windows_arch}" if windows_arch

          return "Mozilla/5.0 (#{extensions}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/#{chrome_version} Safari/537.36"
        end

        #
        # Builds a Chrome `User-Agent` string for macOS.
        #
        # @param [String] chrome_version
        #   The `Chrome/...` version.
        #
        # @param [String, nil] macos_version
        #   The macOS version.
        #
        # @param [:x86_64, :x86, :i686, :aarch64, :arm64, :arm] arch
        #   The hardware architecture.
        #
        # @return [String]
        #   The Chrome `User-Agent` string for macOS.
        #
        def self.build_macos(chrome_version: , macos_version: , arch: :intel)
          macos_version = OS::MacOS::VERSIONS_UNDERSCORED[macos_version]
          macos_arch    = OS::MacOS::ARCHES.fetch(arch) do
                            raise(ArgumentError,"unknown arch: value (#{arch.inspect})")
                          end

          return "Mozilla/5.0 (Macintosh; #{macos_arch} Mac OS X #{macos_version}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/#{chrome_version} Safari/537.36"
        end

        #
        # Builds a Chrome `User-Agent` string for Linux.
        #
        # @param [String] chrome_version
        #   The `Chrome/...` version.
        #
        # @param [:x86_64, :x86, :i686, :aarch64, :arm64] arch
        #   The hardware architecture.
        #
        # @param [:ubuntu, :fedora, :arch, String, nil] linux_distro
        #   The optional Linux Distro.
        #
        # @return [String]
        #   The Chrome `User-Agent` string for Linux.
        #
        def self.build_linux(chrome_version: , arch: , linux_distro: nil)
          linux_distro = OS::Linux::DISTROS.fetch(linux_distro,linux_distro)
          linux_arch   = OS::Linux::ARCHES.fetch(arch) do
                           raise(ArgumentError,"unknown arch: value (#{arch.inspect})")
                         end

          extensions = String.new("X11")
          extensions << "; #{linux_distro}" if linux_distro
          extensions << "; Linux #{linux_arch}"

          return "Mozilla/5.0 (#{extensions}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/#{chrome_version} Safari/537.36"
        end

        #
        # Builds a Chrome `User-Agent` string for Android.
        #
        # @param [String] chrome_version
        #   The `Chrome/...` version.
        #
        # @param [String, nil] android_version
        #   The Android version.
        #
        # @param [:arm, :arm64, nil] arch
        #   The optional hardware architecture.
        #
        # @param [String, nil] android_device
        #   The optional Android device.
        #
        # @return [String]
        #   The Chrome `User-Agent` string for Android.
        #
        def self.build_android(chrome_version: , android_version: , arch: nil, android_device: nil)
          arch = OS::Android::ARCHES.fetch(arch)

          extensions = String.new("Linux")
          extensions << "; #{arch}" if arch
          extensions << "; Android #{android_version}"
          extensions << "; #{android_device}" if android_device

          return "Mozilla/5.0 (#{extensions}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/#{chrome_version} Mobile Safari/537.36"
        end
      end
    end
  end
end