"""
Original code by Bryce Boe: http://www.bryceboe.com/2010/09/01/submitting-binaries-to-virustotal/

Modified by Elias Bachaalany <elias at hex-rays.com>

"""
from __future__ import print_function

import hashlib, httplib, mimetypes, os, pprint, simplejson, sys, urlparse

# -----------------------------------------------------------------------
DEFAULT_TYPE = 'application/octet-stream'
FILE_REPORT_URL = 'https://www.virustotal.com/api/get_file_report.json'
SCAN_URL = 'https://www.virustotal.com/api/scan_file.json'
API_KEY = "" # Put API key here. Register an account in VT Community


# -----------------------------------------------------------------------
# The following function is modified from the snippet at:
# http://code.activestate.com/recipes/146306/
def _encode_multipart_formdata(fields, files=()):
    """
    fields is a dictionary of name to value for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be
    uploaded as files.
    Return (content_type, body) ready for httplib.HTTP instance
    """
    BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
    CRLF = '\r\n'
    L = []
    for key, value in fields.items():
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"' % key)
        L.append('')
        L.append(value)
    for (key, filename, value) in files:
        L.append('--' + BOUNDARY)
        L.append('Content-Disposition: form-data; name="%s"; filename="%s"' %
                 (key, filename))
        content_type = mimetypes.guess_type(filename)[0] or DEFAULT_TYPE
        L.append('Content-Type: %s' % content_type)
        L.append('')
        L.append(value)
    L.append('--' + BOUNDARY + '--')
    L.append('')
    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
    return content_type, body


# -----------------------------------------------------------------------
def _post_multipart(url, fields, files=()):
    """
    url is the full to send the post request to.
    fields is a dictionary of name to value for regular form fields.
    files is a sequence of (name, filename, value) elements for data to be
    uploaded as files.
    Return body of http response.
    """
    content_type, data = _encode_multipart_formdata(fields, files)
    url_parts = urlparse.urlparse(url)
    if url_parts.scheme == 'http':
        h = httplib.HTTPConnection(url_parts.netloc)
    elif url_parts.scheme == 'https':
        h = httplib.HTTPSConnection(url_parts.netloc)
    else:
        raise Exception('Unsupported URL scheme')
    path = urlparse.urlunparse(('', '') + url_parts[2:])
    h.request('POST', path, data, {'content-type':content_type})
    return h.getresponse().read()


# -----------------------------------------------------------------------
def set_apikey(key, dbg = False):
    """
    Set the VT API key
    """
    global API_KEY
    API_KEY = key
    if dbg:
        httplib.HTTPConnection.debuglevel = 1



# -----------------------------------------------------------------------
def scan_file(filename):
    """
    Uploads a file for scanning.

    :param filename: The filename to upload

    :returns: - None if upload failed
             - scan_id value if upload succeeds
             - raises an exception on IO failures
    """
    files = [('file', filename, open(filename, 'rb').read())]
    json = _post_multipart(SCAN_URL, {'key':API_KEY}, files)
    data = simplejson.loads(json)
    return str(data['scan_id']) if data['result'] == 1 else None


# -----------------------------------------------------------------------
def get_file_md5_hash(filename):
    f = open(filename, 'rb')
    r = hashlib.md5(f.read()).hexdigest()
    f.close()
    return r


# -----------------------------------------------------------------------
def get_file_report(filename=None, md5sum=None):
    """
    Returns an report for a file or md5su.

    :param filename: File name to get report. The file is used just
                     to compute its MD5Sum
    :param md5sum: MD5sum string (in case filename was not passed)

    :returns: A dictionary if report exists: key=scanner, value=reported name
    :returns: None: if file was not previously analyzed
    """
    if filename is None and md5sum is None:
        raise Exception('Either filename or md5sum should be passed!')

    # Filename passed? Compute its MD5
    if filename:
        global LAST_FILE_HASH
        LAST_FILE_HASH = md5sum = get_file_md5_hash(filename)

    # Form the request
    json = _post_multipart(FILE_REPORT_URL, {'resource':md5sum, 'key':API_KEY})
    data = simplejson.loads(json)
    if data['result'] != 1:
        # No results
        return None
    else:
        # date, result_dict = data['report']
        return data['report'][1]


# -----------------------------------------------------------------------
def pretty_print(obj):
    pprint.pprint(obj)


# -----------------------------------------------------------------------
if __name__ == '__main__':
    if len(sys.argv) != 2:
        print('Usage: %s filename' % sys.argv[0])
        sys.exit(1)

    filename = sys.argv[1]
    if not os.path.isfile(filename):
        print('%s is not a valid file' % filename)
        sys.exit(1)

    get_file_report(filename=filename)
