Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 1 | # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import cookielib |
| 6 | import getpass |
| 7 | import imghdr |
| 8 | import os |
| 9 | import os.path |
Dennis Kempin | 13d948e | 2014-04-18 11:23:32 -0700 | [diff] [blame] | 10 | import re |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 11 | import subprocess |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 12 | import sys |
| 13 | import urllib |
| 14 | import urllib2 |
| 15 | |
| 16 | # path to current script directory |
| 17 | script_dir = os.path.dirname(os.path.realpath(__file__)) |
| 18 | cache_dir = os.path.realpath(os.path.join(script_dir, "..", "cache")) |
| 19 | if not os.path.exists(cache_dir): |
| 20 | os.mkdir(cache_dir) |
| 21 | |
| 22 | # path to the cookies file used for storing the login cookies. |
| 23 | cookies_file = os.path.join(cache_dir, "cookies") |
| 24 | |
| 25 | # path to folder where downloaded reports are cached |
| 26 | log_cache_dir = os.path.join(cache_dir, "reports") |
| 27 | if not os.path.exists(log_cache_dir): |
| 28 | os.mkdir(log_cache_dir) |
| 29 | |
| 30 | class FeedbackDownloader(): |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 31 | STUBBY_CMD = 'stubby --security_protocol=loas --binary_output=0 \ |
| 32 | call blade:feedback-export-api-prod \ |
| 33 | ReportService.Get \'resource_id: "{}"\'' |
| 34 | BINARY_DATA_FILTER = '| grep -A 2 \'name: "system_logs.zip"\' | tail -n 1 \ |
| 35 | | sed \'s/.*data: "\(.*\)"\s*/\\1/\'' |
| 36 | SCREENSHOT_FILTER = '| grep -A 2 \'screenshot <\' | tail -n 1 \ |
| 37 | | sed \'s/.*content: "\(.*\)"\s*/\\1/\'' |
| 38 | |
Dennis Kempin | 13d948e | 2014-04-18 11:23:32 -0700 | [diff] [blame] | 39 | def __init__(self, force_login=False): |
| 40 | if force_login and os.path.exists(cookies_file): |
| 41 | os.remove(cookies_file) |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 42 | self._SetupCookies() |
| 43 | |
| 44 | def _SetupCookies(self): |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 45 | # setup cookies file and url opener |
| 46 | self.cookies = cookielib.MozillaCookieJar(cookies_file) |
| 47 | if os.path.exists(cookies_file): |
| 48 | self.cookies.load() |
| 49 | cookie_processor = urllib2.HTTPCookieProcessor(self.cookies) |
| 50 | self.opener = urllib2.build_opener(cookie_processor) |
| 51 | self._Login() |
| 52 | |
| 53 | def _Login(self): |
| 54 | username = getpass.getuser() |
| 55 | |
| 56 | # check if already logged in |
Dennis Kempin | 13d948e | 2014-04-18 11:23:32 -0700 | [diff] [blame] | 57 | url = "https://feedback.corp.google.com" |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 58 | data = self.opener.open(url).read() |
Dennis Kempin | 13d948e | 2014-04-18 11:23:32 -0700 | [diff] [blame] | 59 | if "loginForm" not in data: |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 60 | return |
| 61 | |
| 62 | # ask for credentials |
| 63 | print "Login to corp.google.com:" |
| 64 | password = getpass.getpass() |
| 65 | sys.stdout.write("OTP: ") |
| 66 | otp = sys.stdin.readline().strip() |
| 67 | |
Dennis Kempin | 13d948e | 2014-04-18 11:23:32 -0700 | [diff] [blame] | 68 | values = { |
| 69 | 'u': username, |
| 70 | 'pw': password, |
| 71 | 'otp': otp |
| 72 | } |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 73 | |
Dennis Kempin | 13d948e | 2014-04-18 11:23:32 -0700 | [diff] [blame] | 74 | # extract hidden form values |
| 75 | regex = ("<input type=\"hidden\" id=\"([^\"]*)\" " + |
| 76 | "name=\"([^\"]*)\" value=\"([^\"]*)\"/>") |
| 77 | for match in re.finditer(regex, data): |
| 78 | values[match.group(2)] = match.group(3) |
| 79 | |
| 80 | # execute login by posting userdata to login.corp.google.com |
| 81 | query = urllib.urlencode(values) |
| 82 | url = ("https://login.corp.google.com/login") |
| 83 | result = self.opener.open(url, query).read() |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 84 | # check if the result displays error |
| 85 | if "error" in result >= 0: |
| 86 | print "Login failed" |
| 87 | exit(-1) |
| 88 | |
| 89 | # login was successful. save cookies to file to be reused later. |
| 90 | self.cookies.save() |
| 91 | |
| 92 | def DownloadFile(self, url): |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 93 | self._SetupCookies() |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 94 | try: |
| 95 | return self.opener.open(url).read() |
| 96 | except urllib2.URLError: |
| 97 | return None |
| 98 | |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 99 | def _OctetStreamToBinary(self, octetStream): |
| 100 | """ The zip files are returned in an octet-stream format that must |
| 101 | be decoded back into a binary. This function scans through the stream |
| 102 | and unescapes the special characters |
| 103 | """ |
| 104 | binary = '' |
| 105 | i = 0 |
| 106 | while i < len(octetStream): |
| 107 | if ord(octetStream[i]) is ord('\\'): |
| 108 | if re.match('\d\d\d', octetStream[i + 1:i + 4]): |
| 109 | binary += chr(int(octetStream[i + 1:i + 4], 8)) |
| 110 | i += 4 |
| 111 | else: |
| 112 | binary += octetStream[i:i + 2].decode("string-escape") |
| 113 | i += 2 |
| 114 | else: |
| 115 | binary += octetStream[i] |
| 116 | i += 1 |
| 117 | return binary |
| 118 | |
| 119 | def _DownloadAttachedFile(self, id, filter_cmd): |
| 120 | cmd = FeedbackDownloader.STUBBY_CMD.format(id) + filter_cmd |
| 121 | print cmd |
| 122 | process = subprocess.Popen(cmd, shell=True, |
| 123 | stdout=subprocess.PIPE, |
| 124 | stderr=subprocess.PIPE) |
| 125 | |
| 126 | output, errors = process.communicate() |
| 127 | errorcode = process.returncode |
| 128 | if errorcode != 0: |
| 129 | print "An error (%d) occurred while downloading the logs" % errorcode |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 130 | return None |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 131 | |
| 132 | return self._OctetStreamToBinary(output) |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 133 | |
| 134 | def DownloadSystemLog(self, id): |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 135 | report = self._DownloadAttachedFile(id, |
| 136 | FeedbackDownloader.BINARY_DATA_FILTER) |
Dennis Kempin | c6c981d | 2014-04-18 11:49:16 -0700 | [diff] [blame] | 137 | |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 138 | if not report or (report[0:2] != "BZ" and report[0:2] != "PK"): |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 139 | print "Report does not seem to include include log files..." |
| 140 | print "Do you need to run 'prodaccess' perhaps?" |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 141 | return None |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 142 | |
Dennis Kempin | cee3761 | 2013-06-14 10:40:41 -0700 | [diff] [blame] | 143 | return report |
| 144 | |
| 145 | def DownloadScreenshot(self, id): |
Charlie Mooney | d30322b | 2014-09-04 15:02:29 -0700 | [diff] [blame] | 146 | return self._DownloadAttachedFile(id, FeedbackDownloader.SCREENSHOT_FILTER) |