Skip to content

Commit

Permalink
Merge with master
Browse files Browse the repository at this point in the history
  • Loading branch information
tyilo committed Mar 29, 2016
2 parents 37694c2 + aee7f78 commit 1fdfb7f
Showing 1 changed file with 68 additions and 165 deletions.
233 changes: 68 additions & 165 deletions submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,18 @@
import optparse
import os
import sys
import itertools
import mimetypes
import random
import string
import re
import webbrowser
import requests
import requests.exceptions


# Python 2/3 compatibility
if sys.version_info[0] >= 3:
import configparser
from urllib.parse import urlencode
from urllib.error import URLError
from urllib.request import Request, build_opener, HTTPCookieProcessor

def form_body(form):
return str(form).encode('utf-8')
else:
# Python 2, import modules with Python 3 names
import ConfigParser as configparser
from urllib import urlencode
from urllib2 import URLError
from urllib2 import Request, build_opener, HTTPCookieProcessor


def form_body(form):
return str(form)

# End Python 2/3 compatibility

Expand Down Expand Up @@ -56,96 +42,6 @@ def form_body(form):
_GUESS_MAINCLASS = set(['Java', 'Python'])


class MultiPartForm(object):
"""MultiPartForm based on code from
http://blog.doughellmann.com/2009/07/pymotw-urllib2-library-for-opening-urls.html
This since the default libraries still lack support for posting
multipart/form-data (which is required to post files in HTTP).
http://bugs.python.org/issue3244
"""

def __init__(self):
self.form_fields = []
self.files = []
self.boundary = ''.join(
random.SystemRandom().choice(string.ascii_letters)
for _ in range(50))
return

def get_content_type(self):
return 'multipart/form-data; boundary=%s' % self.boundary

@staticmethod
def escape_field_name(name):
"""Should escape a field name escaped following RFC 2047 if needed.
Skipped for now as we only call it with hard coded constants.
"""
return name

def add_field(self, name, value):
"""Add a simple field to the form data."""
if value is None:
# Assume the field is empty
value = ""
# ensure value is a string
value = str(value)
self.form_fields.append((name, value))
return

def add_file(self, fieldname, filename, file_handle, mimetype=None):
"""Add a file to be uploaded."""
body = file_handle.read()
if mimetype is None:
mimetype = (mimetypes.guess_type(filename)[0] or
'application/octet-stream')
self.files.append((fieldname, filename, mimetype, body))
return

def make_request(self, url):
body = form_body(self)
request = Request(url, data=body)
request.add_header('Content-type', self.get_content_type())
request.add_header('Content-length', len(body))
return request

def __str__(self):
"""Return a string representing the form data, including attached
files."""
# Build a list of lists, each containing "lines" of the
# request. Each part is separated by a boundary string.
# Once the list is built, return a string where each
# line is separated by '\r\n'.
parts = []
part_boundary = '--' + self.boundary

# Add the form fields
parts.extend([part_boundary,
('Content-Disposition: form-data; name="%s"' %
self.escape_field_name(name)),
'',
value]
for name, value in self.form_fields)

# Add the files to upload
parts.extend([part_boundary,
('Content-Disposition: file; name="%s"; filename="%s"' %
(self.escape_field_name(field_name), filename)),
# FIXME: filename should be escaped using RFC 2231
'Content-Type: %s' % content_type,
'',
body]
for field_name, filename, content_type, body in self.files
)

# Flatten the list and add closing boundary marker,
# then return CR+LF separated data
flattened = list(itertools.chain(*parts))
flattened.append('--' + self.boundary + '--')
flattened.append('')
return '\r\n'.join(flattened)


class ConfigError(Exception):
pass

Expand Down Expand Up @@ -187,25 +83,21 @@ def login(login_url, username, password=None, token=None):
At least one of password or token needs to be provided.
Returns a urllib OpenerDirector object that can be used to access
URLs while logged in.
Returns a requests.Response where the cookies are needed to be able to submit
"""
login_args = {'user': username, 'script': 'true'}
if password:
login_args['password'] = password
if token:
login_args['token'] = token

opener = build_opener(HTTPCookieProcessor())
opener.open(login_url, urlencode(login_args).encode('ascii'))
return opener
return requests.post(login_url, data=login_args)


def login_from_config(cfg):
"""Log in to Kattis using the access information in a kattisrc file
Returns a urllib OpenerDirector object that can be used to access
URLs while logged in.
Returns a requests.Response where the cookies are needed to be able to submit
"""
username = cfg.get('user', 'username')
password = token = None
Expand All @@ -228,36 +120,29 @@ def login_from_config(cfg):
return login(loginurl, username, password, token)


def submit(url_opener, submit_url, problem, language, files,
mainclass=None, tag=None):
def submit(submit_url, cookies, problem, language, files, mainclass='', tag=''):
"""Make a submission.
The url_opener argument is an OpenerDirector object to use (as
returned by the login() function)
"""
if mainclass is None:
mainclass = ""
if tag is None:
tag = ""
form = MultiPartForm()
form.add_field('submit', 'true')
form.add_field('submit_ctr', '2')
form.add_field('language', language)
form.add_field('mainclass', mainclass)
form.add_field('problem', problem)
form.add_field('tag', tag)
form.add_field('script', 'true')
Returns the requests.Result from the submission
"""

if len(files) > 0:
for filename in files:
form.add_file('sub_file[]', os.path.basename(filename),
open(filename))
data = {'submit': 'true',
'submit_ctr': 2,
'language': language,
'mainclass': mainclass,
'problem': problem,
'tag': tag,
'script': 'true'}

request = form.make_request(submit_url)
sub_files = []
for f in files:
with open(f) as sub_file:
sub_files.append(('sub_file[]', (os.path.basename(f), sub_file.read(), 'application/octet-stream')))

return url_opener.open(request).read().decode('utf-8').replace("<br />",
"\n")
return requests.post(submit_url, data=data, files=sub_files, cookies=cookies)


def confirm_or_die(problem, language, files, mainclass, tag):
Expand Down Expand Up @@ -298,17 +183,23 @@ def main():
help='''Sets language to LANGUAGE.
Overrides default guess (based on suffix of first filename)''', default=None)
opt.add_option('-t', '--tag', dest='tag', metavar='TAG',
help=optparse.SUPPRESS_HELP, default="")
help=optparse.SUPPRESS_HELP, default='')
opt.add_option('-f', '--force', dest='force',
help='Force, no confirmation prompt before submission',
action="store_true", default=False)
action='store_true', default=False)

opts, args = opt.parse_args()

if len(args) == 0:
opt.print_help()
sys.exit(1)

try:
cfg = get_config()
except ConfigError as exc:
print(exc)
sys.exit(1)

problem, ext = os.path.splitext(os.path.basename(args[0]))
language = _LANGUAGE_GUESS.get(ext, None)
mainclass = problem if language in _GUESS_MAINCLASS else None
Expand All @@ -320,6 +211,18 @@ def main():
mainclass = opts.mainclass
if opts.language:
language = opts.language
else:
if language == 'Python':
try:
pyver = cfg.get('defaults', 'python-version')
if not pyver in ['2', '3']:
print('python-version in .kattisrc must be 2 or 3')
sys.exit(1)
elif pyver == '3':
language = 'Python 3'
except Exception:
pass


if language is None:
print('''\
Expand All @@ -335,23 +238,22 @@ def main():
seen.add(arg)

try:
cfg = get_config()
opener = login_from_config(cfg)
login_reply = login_from_config(cfg)
except ConfigError as exc:
print(exc)
sys.exit(1)
except URLError as exc:
if hasattr(exc, 'code'):
print('Login failed.')
if exc.code == 403:
print('Incorrect username or password/token (403)')
elif exc.code == 404:
print("Incorrect login URL (404)")
else:
print(exc)
except requests.exceptions.RequestException as err:
print('Login connection failed:', err)
sys.exit(1)

if not login_reply.status_code == 200:
print('Login failed.')
if login_reply.status_code == 403:
print('Incorrect username or password/token (403)')
elif login_reply.status_code == 404:
print('Incorrect login URL (404)')
else:
print('Failed to connect to Kattis server.')
print('Reason: ', exc.reason)
print('Status code:', login_reply.status_code)
sys.exit(1)

submit_url = get_url(cfg, 'submissionurl', 'submit')
Expand All @@ -360,25 +262,26 @@ def main():
confirm_or_die(problem, language, files, mainclass, tag)

try:
result = submit(opener, submit_url, problem, language, files, mainclass, tag)
except URLError as exc:
if hasattr(exc, 'code'):
print('Submission failed.')
if exc.code == 403:
print('Access denied (403)')
elif exc.code == 404:
print('Incorrect submit URL (404)')
else:
print(exc)
result = submit(submit_url, login_reply.cookies, problem, language, files, mainclass, tag)
except requests.exceptions.RequestException as err:
print('Submit connection failed:', err)
sys.exit(1)

if result.status_code != 200:
print('Submission failed.')
if result.status_code == 403:
print('Access denied (403)')
elif result.status_code == 404:
print('Incorrect submit URL (404)')
else:
print('Failed to connect to Kattis server.')
print('Reason: ', exc.reason)
print('Status code:', login_reply.status_code)
sys.exit(1)

print(result)
plain_result = result.content.decode('utf-8').replace('<br />', '\n')
print(plain_result)

try:
open_submission(result, cfg)
open_submission(plain_result, cfg)
except configparser.NoOptionError:
pass

Expand Down

0 comments on commit 1fdfb7f

Please sign in to comment.