Files
2025-10-20 02:12:11 +11:00

747 lines
25 KiB
Ruby
Executable File

#!/usr/bin/env ruby
#
# .$$$ $. .$$$ $.
# $$$$ $$. .$$$ $$$ .$$$$$$. .$$$$$$$$$$. $$$$ $$. .$$$$$$$. .$$$$$$.
# $ $$ $$$ $ $$ $$$ $ $$$$$$. $$$$$ $$$$$$ $ $$ $$$ $ $$ $$ $ $$$$$$.
# $ `$ $$$ $ `$ $$$ $ `$ $$$ $$' $ `$ `$$ $ `$ $$$ $ `$ $ `$ $$$'
# $. $ $$$ $. $$$$$$ $. $$$$$$ `$ $. $ :' $. $ $$$ $. $$$$ $. $$$$$.
# $::$ . $$$ $::$ $$$ $::$ $$$ $::$ $::$ . $$$ $::$ $::$ $$$$
# $;;$ $$$ $$$ $;;$ $$$ $;;$ $$$ $;;$ $;;$ $$$ $$$ $;;$ $;;$ $$$$
# $$$$$$ $$$$$ $$$$ $$$ $$$$ $$$ $$$$ $$$$$$ $$$$$ $$$$$$$$$ $$$$$$$$$'
#
#
# WhatWeb - Next generation web scanner.
# Developed by Andrew Horton (urbanadventurer) and Brendan Coles (bcoles)
#
# Homepage: https://morningstarsecurity.com/research/whatweb
#
# Copyright 2009 to 2025 Andrew Horton and Brendan Coles
#
# This file is part of WhatWeb.
#
# WhatWeb is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# WhatWeb 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with WhatWeb. If not, see <http://www.gnu.org/licenses/>.
$LOAD_PATH.unshift(File.join(File.expand_path(__dir__)), '.')
# Performance profiling infrastructure (Phase 1)
if ENV['WHATWEB_PROFILE']
begin
require 'ruby-prof'
RubyProf.start
at_exit do
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
if ENV['WHATWEB_PROFILE_FILE']
File.open(ENV['WHATWEB_PROFILE_FILE'], 'w') { |f| printer.print(f) }
STDERR.puts "Profile saved to #{ENV['WHATWEB_PROFILE_FILE']}"
else
printer.print(STDERR)
end
end
STDERR.puts "Ruby profiling enabled"
rescue LoadError
STDERR.puts "Warning: ruby-prof gem not available. Install with: gem install ruby-prof"
end
end
require 'lib/whatweb'
#
# Command-line usage information (full)
#
def usage_full
puts blue(WhatWeb::BANNER)
puts(<<-EOT)
WhatWeb - Next generation web scanner version #{WhatWeb::VERSION}.
Developed by Andrew Horton (urbanadventurer) and Brendan Coles (bcoles).
Homepage: https://morningstarsecurity.com/research/whatweb
Usage: whatweb [options] <URLs>
TARGET SELECTION:
<TARGETs>\t\t\tEnter URLs, hostnames, IP addresses, filenames or
\t\t\t\tIP ranges in CIDR, x.x.x-x, or x.x.x.x-x.x.x.x
\t\t\t\tformat.
--input-file=FILE, -i\t\tRead targets from a file. You can pipe
\t\t\t\thostnames or URLs directly with -i /dev/stdin.
TARGET MODIFICATION:
--url-prefix\t\t\tAdd a prefix to target URLs.
--url-suffix\t\t\tAdd a suffix to target URLs.
--url-pattern\t\t\tInsert the targets into a URL.
\t\t\t\te.g. example.com/%insert%/robots.txt
AGGRESSION:
The aggression level controls the trade-off between speed/stealth and
reliability.
--aggression, -a=LEVEL\tSet the aggression level. Default: 1.
1. Stealthy\t\t\tMakes one HTTP request per target and also
\t\t\t\tfollows redirects.
3. Aggressive\t\t\tIf a level 1 plugin is matched, additional
\t\t\t\trequests will be made.
4. Heavy\t\t\tMakes a lot of HTTP requests per target. URLs
\t\t\t\tfrom all plugins are attempted.
HTTP OPTIONS:
--user-agent, -U=AGENT\tIdentify as AGENT instead of WhatWeb/#{WhatWeb::VERSION}.
--header, -H\t\t\tAdd an HTTP header. eg "Foo:Bar". Specifying a
\t\t\t\tdefault header will replace it. Specifying an
\t\t\t\tempty value, e.g. "User-Agent:" will remove it.
--follow-redirect=WHEN\tControl when to follow redirects. WHEN may be
\t\t\t\t`never', `http-only', `meta-only', `same-site',
\t\t\t\tor `always'. Default: #{$FOLLOW_REDIRECT}.
--max-redirects=NUM\t\tMaximum number of redirects. Default: 10.
AUTHENTICATION:
--user, -u=<user:password>\tHTTP basic authentication.
--cookie, -c=COOKIES\t\tUse cookies, e.g. 'name=value; name2=value2'.
--cookie-jar=FILE\t\tRead cookies from a file and save cookies to the
\t\t\t\tsame file. Creates the file if it doesn't exist.
--no-cookies\t\t\tDisable automatic cookie handling (improves performance with high thread counts).
PROXY:
--proxy\t\t\t<hostname[:port]> Set proxy hostname and port.
\t\t\t\tDefault: #{$PROXY_PORT}.
--proxy-user\t\t\t<username:password> Set proxy user and password.
PLUGINS:
--list-plugins, -l\t\tList all plugins.
--info-plugins, -I=[SEARCH]\tList all plugins with detailed information.
\t\t\t\tOptionally search with keywords in a comma
\t\t\t\tdelimited list.
--search-plugins=STRING\tSearch plugins for a keyword.
--plugins, -p=LIST\t\tSelect plugins. LIST is a comma delimited set
\t\t\t\tof selected plugins. Default is all.
\t\t\t\tEach element can be a directory, file or plugin
\t\t\t\tname and can optionally have a modifier, +/-.
\t\t\t\tExamples: +/tmp/moo.rb,+/tmp/foo.rb
\t\t\t\ttitle,md5,+./plugins-disabled/
\t\t\t\t./plugins-disabled,-md5
\t\t\t\t-p + is a shortcut for -p +plugins-disabled.
--grep, -g=STRING|REGEXP\tSearch for STRING or a Regular Expression. Shows
\t\t\t\tonly the results that match.
\t\t\t\tExamples: --grep "hello"
\t\t\t\t--grep "/he[l]*o/"
--custom-plugin=DEFINITION\tDefine a custom plugin named Custom-Plugin,
\t\t\t\tExamples: ":text=>'powered by abc'"
\t\t\t\t":version=>/powered[ ]?by ab[0-9]/"
\t\t\t\t":ghdb=>'intitle:abc \\"powered by abc\\"'"
\t\t\t\t":md5=>'8666257030b94d3bdb46e05945f60b42'"
\t\t\t\t"{:text=>'powered by abc'}"
--dorks=PLUGIN\t\tList Google dorks for the selected plugin.
OUTPUT:
--verbose, -v\t\t\tVerbose output includes plugin descriptions.
\t\t\t\tUse twice for debugging.
--colour,--color=WHEN\t\tcontrol whether colour is used. WHEN may be
\t\t\t\t`never', `always', or `auto'.
--quiet, -q\t\t\tDo not display brief logging to STDOUT.
--no-errors\t\t\tSuppress error messages.
LOGGING:
--log-brief=FILE\t\tLog brief, one-line output.
--log-verbose=FILE\t\tLog verbose output.
--log-errors=FILE\t\tLog errors.
--log-xml=FILE\t\tLog XML format.
--log-json=FILE\t\tLog JSON format.
--log-sql=FILE\t\tLog SQL INSERT statements.
--log-sql-create=FILE\t\tCreate SQL database tables.
--log-json-verbose=FILE\tLog JSON Verbose format.
--log-magictree=FILE\t\tLog MagicTree XML format.
--log-object=FILE\t\tLog Ruby object inspection format.
--log-mongo-database\t\tName of the MongoDB database.
--log-mongo-collection\tName of the MongoDB collection.
\t\t\t\tDefault: whatweb.
--log-mongo-host\t\tMongoDB hostname or IP address.
\t\t\t\tDefault: 0.0.0.0.
--log-mongo-username\t\tMongoDB username. Default: nil.
--log-mongo-password\t\tMongoDB password. Default: nil.
--log-elastic-index\t\tName of the index to store results. Default: whatweb
--log-elastic-host\t\tHost:port of the elastic http interface. Default: 127.0.0.1:9200
PERFORMANCE & STABILITY:
--max-threads, -t\t\tNumber of simultaneous threads. Default: 25.
--open-timeout\t\tTime in seconds. Default: #{$HTTP_OPEN_TIMEOUT}.
--read-timeout\t\tTime in seconds. Default: #{$HTTP_READ_TIMEOUT}.
--wait=SECONDS\t\tWait SECONDS between connections.
\t\t\t\tThis is useful when using a single thread.
--output-sync\t\t\tForce immediate output flushing for real-time
\t\t\t\tmonitoring (slower with high thread counts).
--output-buffer-size=SIZE\tSet output buffer size. 0=unbuffered,
\t\t\t\tdefault=auto based on thread count.
HELP & MISCELLANEOUS:
--short-help\t\t\tShort usage help.
--help, -h\t\t\tComplete usage help.
--debug\t\t\tRaise errors in plugins.
--version\t\t\tDisplay version information.
EXAMPLE USAGE:
* Scan example.com.
./whatweb example.com
* Scan reddit.com slashdot.org with verbose plugin descriptions.
./whatweb -v reddit.com slashdot.org
* An aggressive scan of wired.com detects the exact version of WordPress.
./whatweb -a 3 www.wired.com
* Scan the local network quickly and suppress errors.
whatweb --no-errors 192.168.0.0/24
* Scan the local network for https websites.
whatweb --no-errors --url-prefix https:// 192.168.0.0/24
* Scan for crossdomain policies in the Alexa Top 1000.
./whatweb -i plugin-development/alexa-top-100.txt \\
--url-suffix /crossdomain.xml -p crossdomain_xml
EOT
=begin
suggestions = ''
suggestions << "To enable MongoDB logging install the mongo gem.\n" unless gem_available?('mongo')
suggestions << "To enable character set detection and MongoDB logging install the rchardet gem.\n" unless gem_available?('rchardet')
unless suggestions.empty?
puts
puts 'OPTIONAL DEPENDENCIES'
puts '-' * 80
puts suggestions
puts
end
=end
end
#
# Command-line usage information (short)
#
def usage_short
puts blue(WhatWeb::BANNER)
puts(<<-EOT)
WhatWeb - Next generation web scanner version #{WhatWeb::VERSION}.
Developed by Andrew Horton (urbanadventurer) and Brendan Coles (bcoles)
Homepage: https://morningstarsecurity.com/research/whatweb
Usage: whatweb [options] <URLs>
<TARGETs>\t\t\tEnter URLs, hostnames, IP addresses, filenames or
\t\t\t\tIP ranges in CIDR, x.x.x-x, or x.x.x.x-x.x.x.x
\t\t\t\tformat.
--input-file=FILE, -i\t\tRead targets from a file.
--aggression, -a=LEVEL\tSet the aggression level. Default: 1.
1. Stealthy\t\t\tMakes one HTTP request per target and also
\t\t\t\tfollows redirects.
3. Aggressive\t\t\tIf a level 1 plugin is matched, additional
\t\t\t\trequests will be made.
--list-plugins, -l\t\tList all plugins.
--info-plugins, -I=[SEARCH]\tList all plugins with detailed information.
\t\t\t\tOptionally search with a keyword.
--verbose, -v\t\t\tVerbose output includes plugin descriptions.
Note: This is the short usage help. For the complete usage help use -h or --help.
EOT
end
if ARGV.empty? # faster usage info
usage_short
exit
end
### logging
$semaphore = Mutex.new
# this is noisy in debug
def $semaphore.reentrant_synchronize(&work)
# This test is noisy in debug, shame there isn't a good way to test for mutex ownership
$semaphore.lock
$semaphore.unlock
rescue ThreadError
raise if $WWDEBUG
work.call
else
$semaphore.synchronize(&work)
end
# Load message handling functions
require_relative 'lib/messages'
#
# Check for cookie performance warnings based on thread count
#
def check_cookie_performance_warning(max_threads)
return if $NO_COOKIES
case max_threads
when 0..50
# No warning - should work fine
when 51..100
notice "💡 Notice: With #{max_threads} threads, consider --no-cookies if scan performance is slow"
when 101..200
warning "⚠️ Performance Warning:"
warning " High thread count (#{max_threads}) may cause cookie jar bottlenecks"
warning " For optimal performance: whatweb --no-cookies [your other options]"
when 201..Float::INFINITY
error "🚨 Performance Alert:"
error " Very high thread count (#{max_threads}) will cause severe cookie contention"
error " STRONGLY RECOMMENDED: Add --no-cookies to prevent major slowdowns"
error " Command: whatweb --max-threads #{max_threads} --no-cookies [other options]"
end
end
#
# Default options
#
# Plugins
plugin_selection = nil
use_custom_plugin = false
use_custom_grep_plugin = false
# Targets
input_file = nil
# Output
logging_list = []
mongo = {}
mongo[:use_mongo_log] = false
elastic = {}
elastic[:use_elastic_log] = false
# Target modification
url_prefix = ''
url_suffix = ''
url_pattern = nil
# HTTP connection
max_threads = 25
max_redirects = 10
# Cookie jar file path
cookie_jar_file = nil
# optional arguments work badly with URLs
opts = GetoptLong.new(
['-h', '--help', GetoptLong::NO_ARGUMENT],
['--short-help', GetoptLong::NO_ARGUMENT],
['-v', '--verbose', GetoptLong::NO_ARGUMENT],
['-l', '--list-plugins', GetoptLong::NO_ARGUMENT],
['-p', '--plugins', GetoptLong::REQUIRED_ARGUMENT],
['-I', '--info-plugins', '--search-plugins', GetoptLong::OPTIONAL_ARGUMENT],
['--dorks', GetoptLong::REQUIRED_ARGUMENT],
['--colour', '--color', GetoptLong::REQUIRED_ARGUMENT],
['--log-object', GetoptLong::REQUIRED_ARGUMENT],
['--log-brief', GetoptLong::REQUIRED_ARGUMENT],
['--log-xml', GetoptLong::REQUIRED_ARGUMENT],
['--log-json', GetoptLong::REQUIRED_ARGUMENT],
['--log-json-verbose', GetoptLong::REQUIRED_ARGUMENT],
['--log-magictree', GetoptLong::REQUIRED_ARGUMENT],
['--log-verbose', GetoptLong::REQUIRED_ARGUMENT],
['--log-mongo-collection', GetoptLong::REQUIRED_ARGUMENT],
['--log-mongo-host', GetoptLong::REQUIRED_ARGUMENT],
['--log-mongo-database', GetoptLong::REQUIRED_ARGUMENT],
['--log-mongo-username', GetoptLong::REQUIRED_ARGUMENT],
['--log-mongo-password', GetoptLong::REQUIRED_ARGUMENT],
['--log-elastic-index', GetoptLong::REQUIRED_ARGUMENT],
['--log-elastic-host', GetoptLong::REQUIRED_ARGUMENT],
['--log-sql', GetoptLong::REQUIRED_ARGUMENT],
['--log-sql-create', GetoptLong::REQUIRED_ARGUMENT],
['--log-errors', GetoptLong::REQUIRED_ARGUMENT],
['--no-errors', GetoptLong::NO_ARGUMENT],
['-i', '--input-file', GetoptLong::REQUIRED_ARGUMENT],
['-U', '--user-agent', GetoptLong::REQUIRED_ARGUMENT],
['-a', '--aggression', GetoptLong::REQUIRED_ARGUMENT],
['-t', '--max-threads', GetoptLong::REQUIRED_ARGUMENT],
['--follow-redirect', GetoptLong::REQUIRED_ARGUMENT],
['--max-redirects', GetoptLong::REQUIRED_ARGUMENT],
['--proxy', GetoptLong::REQUIRED_ARGUMENT],
['--proxy-user', GetoptLong::REQUIRED_ARGUMENT],
['--url-prefix', GetoptLong::REQUIRED_ARGUMENT],
['--url-suffix', GetoptLong::REQUIRED_ARGUMENT],
['--url-pattern', GetoptLong::REQUIRED_ARGUMENT],
['--custom-plugin', GetoptLong::REQUIRED_ARGUMENT],
['-g', '--grep', GetoptLong::REQUIRED_ARGUMENT],
['--open-timeout', GetoptLong::REQUIRED_ARGUMENT],
['--read-timeout', GetoptLong::REQUIRED_ARGUMENT],
['--header', '-H', GetoptLong::REQUIRED_ARGUMENT],
['--cookie', '-c', GetoptLong::REQUIRED_ARGUMENT],
['--cookie-jar', GetoptLong::REQUIRED_ARGUMENT],
['--no-cookies', GetoptLong::NO_ARGUMENT],
['--user', '-u', GetoptLong::REQUIRED_ARGUMENT],
['--wait', GetoptLong::REQUIRED_ARGUMENT],
['--debug', GetoptLong::NO_ARGUMENT],
['--version', GetoptLong::NO_ARGUMENT],
['-q', '--quiet', GetoptLong::NO_ARGUMENT],
['--output-sync', GetoptLong::NO_ARGUMENT],
['--output-buffer-size', GetoptLong::REQUIRED_ARGUMENT]
)
begin
opts.each do |opt, arg|
case opt
when '-i', '--input-file'
input_file = arg
when '-l', '--list-plugins'
PluginSupport.load_plugins
PluginSupport.plugin_list
exit
when '-p', '--plugins'
plugin_selection = arg
when '-I', '--info-plugins'
PluginSupport.load_plugins
PluginSupport.plugin_info(arg.split(','))
exit
when '--dorks'
PluginSupport.load_plugins
PluginSupport.plugin_dorks(arg)
exit
when '--color', '--colour'
$use_colour = 'always' unless arg # no argument
case arg.downcase
when 'auto'
$use_colour = 'auto'
when 'always'
$use_colour = 'always'
when 'never'
$use_colour = false
else
raise('--colour argument not recognized')
end
when '--log-object'
logging_list << LoggingObject.new(arg)
when '--log-brief'
logging_list << LoggingBrief.new(arg)
when '--log-xml'
logging_list << LoggingXML.new(arg)
when '--log-magictree'
logging_list << LoggingMagicTreeXML.new(arg)
when '--log-verbose'
logging_list << LoggingVerbose.new(arg)
when '--log-sql'
logging_list << LoggingSQL.new(arg)
when '--log-sql-create'
PluginSupport.load_plugins('+')
# delete the file if it already exists
begin
File.delete(arg)
rescue
warning("File #{arg} already exists and cannot be removed")
end
LoggingSQL.new(arg).create_tables
notice("SQL CREATE statements written to #{arg}")
exit
when '--log-json'
logging_list << LoggingJSON.new(arg)
when '--log-json-verbose'
logging_list << LoggingJSONVerbose.new(arg)
when '--log-mongo-collection'
unless defined?(Mongo) && defined?(CharDet)
raise "Mongo logging requires the following gems to be installed.\n - mongo\n - rchardet\n"
end
mongo[:collection] = arg
mongo[:use_mongo_log] = true
when '--log-mongo-host'
unless defined?(Mongo) && defined?(CharDet)
raise "Mongo logging requires the following gems to be installed.\n - mongo\n - rchardet\n"
end
mongo[:host] = arg
mongo[:use_mongo_log] = true
when '--log-mongo-database'
unless defined?(Mongo) && defined?(CharDet)
raise "Mongo logging requires the following gems to be installed.\n - mongo\n - rchardet\n"
end
mongo[:database] = arg
mongo[:use_mongo_log] = true
when '--log-mongo-username'
unless defined?(Mongo) && defined?(CharDet)
raise "Mongo logging requires the following gems to be installed.\n - mongo\n - rchardet\n"
end
mongo[:username] = arg
mongo[:use_mongo_log] = true
when '--log-mongo-password'
unless defined?(Mongo) && defined?(CharDet)
raise "Mongo logging requires the following gems to be installed.\n - mongo\n - rchardet\n"
end
mongo[:password] = arg
mongo[:use_mongo_log] = true
when '--log-elastic-index'
elastic[:index] = arg
elastic[:use_elastic_log] = true
when '--log-elastic-host'
elastic[:host] = arg
elastic[:use_elastic_log] = true
when '--log-errors'
$LOG_ERRORS = LoggingErrors.new(arg)
when '--no-errors'
$NO_ERRORS = true
when '-U', '--user-agent'
$USER_AGENT = arg
when '-t', '--max-threads'
max_threads = arg.to_i
when '-a', '--aggression'
raise "Agression level must be 1,3, or 4. #{arg} is invalid." unless [1, 3, 4].include? arg.to_i
$AGGRESSION = arg.to_i
when '--proxy'
$USE_PROXY = true
$PROXY_HOST = arg.to_s.split(':')[0]
$PROXY_PORT = arg.to_s.split(':')[1].to_i if arg.to_s.include?(':')
when '--proxy-user'
$PROXY_USER = arg.to_s.split(':')[0]
$PROXY_PASS = arg.to_s.scan(/^[^:]*:(.+)/).to_s if arg =~ /^[^:]*:(.+)/
when '-q', '--quiet'
$QUIET = true
when '--url-prefix'
url_prefix = arg
when '--url-suffix'
url_suffix = arg
when '--url-pattern'
url_pattern = arg
when '--custom-plugin'
use_custom_plugin = true if PluginSupport.custom_plugin(arg)
when '--grep', '-g'
use_custom_grep_plugin = true if PluginSupport.custom_plugin(arg, 'grep')
when '--follow-redirect'
raise('Invalid --follow-redirect parameter.') unless %w[never http-only meta-only same-site always].include?(arg.downcase)
$FOLLOW_REDIRECT = arg.downcase
when '--max-redirects'
max_redirects = arg.to_i
when '--open-timeout'
$HTTP_OPEN_TIMEOUT = arg.to_i
when '--read-timeout'
$HTTP_READ_TIMEOUT = arg.to_i
when '--wait'
$WAIT = arg.to_i
when '-H', '--header'
begin
x = arg.scan(/([^:]+):(.*)/).flatten
raise if x.empty?
$CUSTOM_HEADERS[x.first] = x.last
rescue
raise('Invalid --header parameter.')
end
when '-c', '--cookie'
begin
raise if arg.empty?
$CUSTOM_HEADERS['Cookie'] = arg
rescue
raise('Cookie require a parameter, e.g. name=value; name2=value2')
end
when '--cookie-jar'
cookie_jar_file = arg
begin
if File.readable?(arg)
# Check if this is legacy format (single domain) for backward compatibility
lines = File.readlines(arg).map(&:strip)
# If it's NOT the new domain-aware format, load as legacy cookies
unless lines.any? { |line| line.start_with?('# Domain:') }
# Legacy format - load into $CUSTOM_HEADERS for backward compatibility
cookie_lines = lines.delete_if { |line| line =~ /^$/ }
unless cookie_lines.empty?
$CUSTOM_HEADERS['Cookie'] = cookie_lines.map { |line| line.sub(/([^;])$/, '\1;') }.join(' ')
end
end
# Note: Domain-aware format is handled automatically by SimpleCookieJar.load_from_file
else
# File doesn't exist yet - that's OK, we'll create it when saving
# Just store the path for later use
end
rescue
raise("Could not read cookies from #{arg}.")
end
when '--no-cookies'
$NO_COOKIES = true
when '-u', '--user'
unless arg.include?(':')
raise("Incorrect credentials format.")
end
$BASIC_AUTH_USER, $BASIC_AUTH_PASS = arg.split(':', 2)
when '--debug'
$WWDEBUG = true
when '--short-help'
usage_short
exit
when '-h', '--help'
usage_full
exit
when '-v', '--verbose'
$verbose = $verbose + 1
when '--version'
puts "WhatWeb version #{WhatWeb::VERSION} ( https://morningstarsecurity.com/research/whatweb/ )"
exit
when '--output-sync'
$OUTPUT_SYNC = true
when '--output-buffer-size'
$OUTPUT_BUFFER_SIZE = arg.to_i
end
end
rescue Errno::EPIPE
exit
rescue StandardError, GetoptLong::Error => e
# Disable colours in Windows environments for errors in usage
$use_colour = false if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
error("Error in processing commandline options - #{e}")
error e.backtrace if $WWDEBUG
exit
end
# sanity check # Disable colours in Windows environments when set to auto
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
$use_colour = false unless $use_colour == 'always'
end
### PLUGINS
plugin_selection += ',+Custom-Plugin' if use_custom_plugin && plugin_selection
plugin_selection += ',+Grep' if use_custom_grep_plugin && plugin_selection
plugins = PluginSupport.load_plugins(plugin_selection)
# load all the plugins
# sanity check # no plugins?
if plugins.empty?
error 'No plugins selected, exiting.'
exit 1
end
# optimise plugins
PluginSupport.precompile_regular_expressions
# Store thread count globally for logging classes (before creating loggers)
$THREADS = max_threads
# Configure smart defaults for output performance based on thread count
unless defined?($OUTPUT_SYNC)
$OUTPUT_SYNC = (max_threads <= 5) # Sync for low thread counts
end
unless defined?($OUTPUT_BUFFER_SIZE)
$OUTPUT_BUFFER_SIZE = case max_threads
when 1..5 then 1 # No buffering for low threads
when 6..50 then 10 # Small buffer for medium (includes default 25)
else 25 # Larger buffer for high threads (51+)
end
end
### OUTPUT
logging_list << LoggingBrief.new(STDOUT, sync: $OUTPUT_SYNC) unless $QUIET || $verbose > 0 # by default logging brief
logging_list << LoggingObject.new if $verbose > 1 # full logging if -vv
logging_list << LoggingVerbose.new(STDOUT, sync: $OUTPUT_SYNC) if $verbose > 0 # full logging if -v
## logging dependencies
if mongo[:use_mongo_log]
unless plugins.map { |a, _b| a }.include?('Charset')
error('MongoDB logging requires the Charset plugin to be activated. The Charset plugin is the slowest whatweb plugin, it not included by default, and resides in the plugins-disabled folder. Use ./whatweb -p +./plugins-disabled/charset.rb to enable it.')
exit 1
end
logging_list << LoggingMongo.new(mongo)
end
logging_list << LoggingElastic.new(elastic) if elastic[:use_elastic_log]
## Headers
$CUSTOM_HEADERS['User-Agent'] = $USER_AGENT unless $CUSTOM_HEADERS['User-Agent']
$CUSTOM_HEADERS.delete_if { |_k, v| v == '' }
## Cookie Jar Initialization
unless $NO_COOKIES
require_relative 'lib/simple_cookie_jar'
$COOKIE_JAR = SimpleCookieJar.new(max_domains: 10_000, cookie_jar_file: cookie_jar_file)
# Warn about performance implications
check_cookie_performance_warning(max_threads)
end
urls = ARGV
# Initialize Scanner
begin
scanner = WhatWeb::Scan.new(
# Targets
urls,
input_file: input_file,
# HTTP connection
max_threads: max_threads,
# Target modification
url_prefix: url_prefix,
url_suffix: url_suffix,
url_pattern: url_pattern
)
rescue SystemExit, Interrupt
error 'Interrupted. Exiting...'
exit 1
rescue => e
error(e.message)
error(e.backtrace.join("\n")) if $WWDEBUG
exit 1
end
# run scan
scanner.scan do |target|
# TODO: change so we can call it without Target object and with just Headers, HTML, etc.
# note this modifies target with redirection info
result = WhatWeb::Parser.run_plugins(target, plugins, scanner: scanner)
# check for redirection
WhatWeb::Redirect.new(target, scanner, max_redirects)
WhatWeb::Parser.parse(
target,
result,
logging_list: logging_list,
grep_plugin: use_custom_grep_plugin
)
end
# Save cookies to jar file if specified
if defined?($COOKIE_JAR) && $COOKIE_JAR
$COOKIE_JAR.save_to_file
end
# close down logging and plugins
logging_list.each(&:close)
Plugin.shutdown_all
# Display cookie jar statistics if enabled and cookies were used
if defined?($COOKIE_JAR) && $COOKIE_JAR && (!defined?($NO_COOKIES) || !$NO_COOKIES) && $verbose && $verbose > 1
stats = $COOKIE_JAR.stats
notice "Cookie jar statistics:"
notice " Domains: #{stats[:domains]}/#{stats[:max_domains]}"
notice " Requests processed: #{stats[:requests_processed]}"
notice " Memory estimate: #{stats[:memory_estimate_kb]}KB"
# Show actual cookies in debug mode
$COOKIE_JAR.display_cookies if $verbose > 2
end
# pp $PLUGIN_TIMES.sort_by {|x,y|y }