Skip to content

Commit

Permalink
http basic auth support
Browse files Browse the repository at this point in the history
  • Loading branch information
maaaaz committed Jun 28, 2015
1 parent 6d70381 commit a62da96
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 31 deletions.
17 changes: 12 additions & 5 deletions README.md
Expand Up @@ -12,7 +12,7 @@ Features
* Cookie and custom HTTP header definition support
* Multiprocessing and killing of unresponding processes after a user-definable timeout
* Accepts several format as input target
* Maps useful options of phantomjs such as ignoring ssl error, proxy definition and proxy authentication
* Maps useful options of phantomjs such as ignoring ssl error, proxy definition and proxy authentication, HTTP Basic Authentication

Usage
-----
Expand All @@ -38,6 +38,12 @@ Options:
-o OUTPUT_DIRECTORY, --output-directory=OUTPUT_DIRECTORY
<OUTPUT_DIRECTORY> (optional): screenshots output
directory (default './screenshots/')
-u HTTP_USERNAME, --http-username=HTTP_USERNAME
<HTTP_USERNAME> (optional): Specify a username for
HTTP Basic Authentication.
-b HTTP_PASSWORD, --http-password=HTTP_PASSWORD
<HTTP_PASSWORD> (optional): Specify a password for
HTTP Basic Authentication.
-P PROXY, --proxy=PROXY
<PROXY> (optional): Specify a proxy. Ex: -P
http://proxy.company.com:8080
Expand All @@ -60,9 +66,9 @@ Options:
-w WORKERS, --workers=WORKERS
<WORKERS> (optional): number of parallel execution
workers (default 2)
-l LOG_LEVEL, --log-level=LOG_LEVEL
<LOG_LEVEL> (optional): verbosity level { DEBUG, INFO,
WARN, ERROR, CRITICAL } (default ERROR)
-v, --verbosity <VERBOSITY> (optional): verbosity level, repeat it to
increase the level { -v INFO, -vv DEBUG } (default
verbosity ERROR)
```

### Examples
Expand All @@ -87,7 +93,7 @@ webscreenshot.py version 1.0
Increasing verbosity level execution
-----------------------------------
$ python webscreenshot.py -i list.txt -l INFO
$ python webscreenshot.py -i list.txt -v
webscreenshot.py version 1.1
[INFO][General] 'http://google.fr' has been formatted as 'http://google.fr:80' with supplied overriding options
Expand Down Expand Up @@ -119,6 +125,7 @@ Requirements

Changelog
---------
* version 1.7 - 06/28/2015: HTTP basic authentication support + loglevel option changed to verbosity
* version 1.6 - 04/23/2015: Transparent background fix
* version 1.5 - 01/11/2015: Cookie and custom HTTP header support
* version 1.4 - 10/12/2014: url-to-image phantomjs script integration + few bugs corrected
Expand Down
40 changes: 35 additions & 5 deletions webscreenshot.js
Expand Up @@ -18,12 +18,13 @@
# along with webscreenshot. If not, see <http://www.gnu.org/licenses/>.
***/

var Page = (function(custom_headers) {
var Page = (function(custom_headers, http_username, http_password) {
var opts = {
width: 1200,
height: 800,
ajaxTimeout: 400,
maxTimeout: 800
maxTimeout: 800,
httpAuthErrorCode: 2
};

var requestCount = 0;
Expand All @@ -35,7 +36,10 @@ var Page = (function(custom_headers) {
width: opts.width,
height: opts.height
};

page.settings.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36';
page.settings.userName = http_username;
page.settings.password = http_password;

page.customHeaders = custom_headers;

Expand All @@ -51,6 +55,10 @@ var Page = (function(custom_headers) {
};

page.onResourceReceived = function(response) {
if (response.stage && response.stage == 'end' && response.status == '401') {
page.failReason = '401';
}

if (!response.stage || response.stage === 'end') {
requestCount -= 1;
if (requestCount === 0) {
Expand All @@ -66,7 +74,13 @@ var Page = (function(custom_headers) {

page.open(url, function(status) {
if (status !== "success") {
phantom.exit(1);
if (page.failReason && page.failReason == '401') {
// Specific 401 HTTP code hint
phantom.exit(opts.httpAuthErrorCode);
} else {
// All other failures
phantom.exit(1);
}
} else {
forceRenderTimeout = setTimeout(renderAndExit, opts.maxTimeout);
}
Expand Down Expand Up @@ -95,6 +109,12 @@ function main() {
var p_outfile = new RegExp('output_file=(.*)');
var p_header = new RegExp('header=(.*)');

var p_http_username = new RegExp('http_username=(.*)');
var http_username = '';

var p_http_password = new RegExp('http_password=(.*)');
var http_password = '';

var temp_custom_headers = {
// Nullify Accept-Encoding header to disable compression (https://github.com/ariya/phantomjs/issues/10930)
'Accept-Encoding': ' '
Expand All @@ -111,6 +131,16 @@ function main() {
var output_file = p_outfile.exec(system.args[i])[1];
}

if (p_http_username.test(system.args[i]) === true)
{
http_username = p_http_username.exec(system.args[i])[1];
}

if (p_http_password.test(system.args[i]) === true)
{
http_password = p_http_password.exec(system.args[i])[1];
}

if (p_header.test(system.args[i]) === true)
{
var header = p_header.exec(system.args[i]);
Expand All @@ -124,13 +154,13 @@ function main() {
}

if (typeof(URL) === 'undefined' || URL.length == 0 || typeof(output_file) === 'undefined' || output_file.length == 0) {
console.log("Usage: phantomjs [options] webscreenshot.js url_capture=<URL> output_file=<output_file.png> [header=<custom header>]");
console.log("Usage: phantomjs [options] webscreenshot.js url_capture=<URL> output_file=<output_file.png> [header=<custom header> http_username=<HTTP basic auth username> http_password=<HTTP basic auth password>]");
console.log('Please specify an URL to capture and an output png filename !');

phantom.exit(1);
}
else {
var page = Page(temp_custom_headers);
var page = Page(temp_custom_headers, http_username, http_password);
page.render(URL, output_file);
}
}
Expand Down
55 changes: 34 additions & 21 deletions webscreenshot.py
Expand Up @@ -38,20 +38,22 @@
# Options definition
option_0 = { 'name' : ('-i', '--input-file'), 'help' : '<INPUT_FILE>: text file containing the target list. Ex: list.txt', 'nargs' : 1}
option_1 = { 'name' : ('-o', '--output-directory'), 'help' : '<OUTPUT_DIRECTORY> (optional): screenshots output directory (default \'./screenshots/\')', 'nargs' : 1}
option_2 = { 'name' : ('-P', '--proxy'), 'help' : '<PROXY> (optional): Specify a proxy. Ex: -P http://proxy.company.com:8080'}
option_3 = { 'name' : ('-A', '--proxy-auth'), 'help' : '<PROXY_AUTH> (optional): Provides authentication information for the proxy. Ex: -A user:password'}
option_4 = { 'name' : ('-p', '--port'), 'help' : '<PORT> (optional): use the specified port for each target in the input list. Ex: -p 80', 'nargs' : 1}
option_5 = { 'name' : ('-s', '--ssl'), 'help' : '<SSL> (optional): enforce ssl for every connection', 'action' : 'store_true', 'default' : 'False'}
option_6 = { 'name' : ('-t', '--timeout'), 'help' : '<TIMEOUT> (optional): phantomjs execution timeout in seconds (default 30 sec)', 'default' : '30', 'nargs' : 1}
option_7 = { 'name' : ('-c', '--cookie'), 'help' : '<COOKIE_STRING> (optional): cookie string to add. Ex: -c "JSESSIONID=1234; YOLO=SWAG"', 'nargs' : 1}
option_8 = { 'name' : ('-a', '--header'), 'help' : '<HEADER> (optional): custom or additional header. Repeat this option for every header. Ex: -a "Host: localhost" -a "Foo: bar"', 'action' : 'append'}
option_9 = { 'name' : ('-w', '--workers'), 'help' : '<WORKERS> (optional): number of parallel execution workers (default 2)', 'default' : 2, 'nargs' : 1}
option_10 = { 'name' : ('-l', '--log-level'), 'help' : '<LOG_LEVEL> (optional): verbosity level { DEBUG, INFO, WARN, ERROR, CRITICAL } (default ERROR)', 'default' : 'ERROR', 'nargs' : 1 }
option_2 = { 'name' : ('-u', '--http-username'), 'help' : '<HTTP_USERNAME> (optional): Specify a username for HTTP Basic Authentication.'}
option_3 = { 'name' : ('-b', '--http-password'), 'help' : '<HTTP_PASSWORD> (optional): Specify a password for HTTP Basic Authentication.'}
option_4 = { 'name' : ('-P', '--proxy'), 'help' : '<PROXY> (optional): Specify a proxy. Ex: -P http://proxy.company.com:8080'}
option_5 = { 'name' : ('-A', '--proxy-auth'), 'help' : '<PROXY_AUTH> (optional): Provides authentication information for the proxy. Ex: -A user:password'}
option_6 = { 'name' : ('-p', '--port'), 'help' : '<PORT> (optional): use the specified port for each target in the input list. Ex: -p 80', 'nargs' : 1}
option_7 = { 'name' : ('-s', '--ssl'), 'help' : '<SSL> (optional): enforce ssl for every connection', 'action' : 'store_true', 'default' : False}
option_8 = { 'name' : ('-t', '--timeout'), 'help' : '<TIMEOUT> (optional): phantomjs execution timeout in seconds (default 30 sec)', 'default' : 30, 'nargs' : 1}
option_9 = { 'name' : ('-c', '--cookie'), 'help' : '<COOKIE_STRING> (optional): cookie string to add. Ex: -c "JSESSIONID=1234; YOLO=SWAG"', 'nargs' : 1}
option_10 = { 'name' : ('-a', '--header'), 'help' : '<HEADER> (optional): custom or additional header. Repeat this option for every header. Ex: -a "Host: localhost" -a "Foo: bar"', 'action' : 'append'}
option_11 = { 'name' : ('-w', '--workers'), 'help' : '<WORKERS> (optional): number of parallel execution workers (default 2)', 'default' : 2, 'nargs' : 1}
option_12 = { 'name' : ('-v', '--verbosity'), 'help' : '<VERBOSITY> (optional): verbosity level, repeat it to increase the level { -v INFO, -vv DEBUG } (default verbosity ERROR)', 'action' : 'count', 'default': 0}

options_definition = [option_0, option_1, option_2, option_3, option_4, option_5, option_6, option_7, option_8, option_9, option_10]
options_definition = [option_0, option_1, option_2, option_3, option_4, option_5, option_6, option_7, option_8, option_9, option_10, option_11, option_12]

# Script version
VERSION = '1.6'
VERSION = '1.7'

# phantomjs binary, hoping to find it in a $PATH directory
## Be free to change it to your own full-path location
Expand All @@ -60,6 +62,7 @@
SCREENSHOTS_DIRECTORY = os.path.abspath(os.path.join(os.getcwdu(), './screenshots/'))

# Logger definition
LOGLEVELS = {0 : 'ERROR', 1 : 'INFO', 2 : 'DEBUG'}
logger_output = logging.StreamHandler(sys.stdout)
logger_output.setFormatter(logging.Formatter('[%(levelname)s][%(name)s] %(message)s'))

Expand All @@ -69,6 +72,7 @@
# Macros
SHELL_EXECUTION_OK = 0
SHELL_EXECUTION_ERROR = -1
PHANTOMJS_HTTP_AUTH_ERROR_CODE = 2

# Handful patterns
p_ipv4_elementary = '(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})\.(?:[\d]{1,3})'
Expand Down Expand Up @@ -133,11 +137,16 @@ def shell_exec(url, command, options):

retval = p.poll()
if retval != SHELL_EXECUTION_OK:
# Phantomjs general error
logger_url.error("Shell command PID %s returned an abnormal error code: '%s'" % (p.pid,retval))
logger_url.error("Screenshot somehow failed\n")
if retval == PHANTOMJS_HTTP_AUTH_ERROR_CODE:
# HTTP Authentication request
logger_url.error("HTTP Authentication requested, try to pass credentials with -u and -b options")
else:
# Phantomjs general error
logger_url.error("Shell command PID %s returned an abnormal error code: '%s'" % (p.pid,retval))
logger_url.error("Screenshot somehow failed\n")

return SHELL_EXECUTION_ERROR

else:
# Phantomjs ok
logger_url.debug("Shell command PID %s ended normally" % p.pid)
Expand All @@ -151,7 +160,6 @@ def shell_exec(url, command, options):
logger_gen.error('Unknown error: %s, exiting' % e )
return SHELL_EXECUTION_ERROR


def filter_bad_filename_chars(filename):
"""
Filter bad chars for any filename
Expand Down Expand Up @@ -276,8 +284,12 @@ def craft_cmd(url_and_options):
cmd_parameters.append("--proxy-auth %s" % options.proxy_auth) if options.proxy_auth != None else None

cmd_parameters.append('"%s" url_capture="%s" output_file="%s"' % (WEBSCREENSHOT_JS, url, output_filename))

cmd_parameters.append('header="Cookie: %s"' % options.cookie.rstrip(';')) if options.cookie != None else None

cmd_parameters.append('http_username="%s"' % options.http_username) if options.http_username != None else None
cmd_parameters.append('http_password="%s"' % options.http_password) if options.http_password != None else None

if options.header:
for header in options.header:
cmd_parameters.append('header="%s"' % header.rstrip(';'))
Expand Down Expand Up @@ -322,23 +334,24 @@ def main(options, arguments):
"""
Dat main
"""
global VERSION, SCREENSHOTS_DIRECTORY
global VERSION, SCREENSHOTS_DIRECTORY, LOGLEVELS
signal.signal(signal.SIGINT, kill_em_all)

print 'webscreenshot.py version %s\n' % VERSION

try :
options.log_level = LOGLEVELS[options.verbosity]
logger_gen.setLevel(options.log_level)
except :
parser.error("Please specify a valid log level")

if (options.input_file == None):
parser.error('Please specify a valid input file')

if options.output_directory != None:
if (options.output_directory != None):
SCREENSHOTS_DIRECTORY = os.path.abspath(os.path.join(os.getcwdu(), options.output_directory))

logger_gen.debug("Options: %s" % options)
logger_gen.debug("Options: %s\n" % options)
if not os.path.exists(SCREENSHOTS_DIRECTORY):
logger_gen.info("'%s' does not exist, will then be created" % SCREENSHOTS_DIRECTORY)
os.makedirs(SCREENSHOTS_DIRECTORY)
Expand Down

0 comments on commit a62da96

Please sign in to comment.