From bed2dfe579cf667ea774ae918ee445d5712454c7 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 7 Oct 2024 13:42:16 +0530 Subject: [PATCH 1/7] feat: add gzip support, send UA containing version --- browserstack/local.py | 1 + browserstack/local_binary.py | 57 +++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/browserstack/local.py b/browserstack/local.py index d7e1618..65c9549 100644 --- a/browserstack/local.py +++ b/browserstack/local.py @@ -15,6 +15,7 @@ def __init__(self, key=None, binary_path=None, **kwargs): self.key = os.environ['BROWSERSTACK_ACCESS_KEY'] if 'BROWSERSTACK_ACCESS_KEY' in os.environ else key self.options = kwargs self.local_logfile_path = os.path.join(os.getcwd(), 'local.log') + LocalBinary.set_version(self.get_package_version()) def __xstr(self, key, value): if key is None: diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 3f87ead..fad6fbc 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -1,12 +1,21 @@ import platform, os, sys, stat, tempfile, re, subprocess from browserstack.bserrors import BrowserStackLocalError +import gzip try: - from urllib.request import urlopen + import urllib.request + + def urlopen(url, headers=None): + return urllib.request.urlopen(urllib.request.Request(url, headers=headers)) except ImportError: - from urllib2 import urlopen + import urllib2 + + def urlopen(url, headers=None): + return urllib2.urlopen(urllib2.Request(url, headers=headers)) class LocalBinary: + _version = None + def __init__(self): is_64bits = sys.maxsize > 2**32 self.is_windows = False @@ -32,11 +41,13 @@ def __init__(self): ] self.path_index = 0 + @staticmethod + def set_version(version): + LocalBinary._version = version + def is_alpine(self): - grepOutput = subprocess.run("grep -w NAME /etc/os-release", capture_output=True, shell=True) - if grepOutput.stdout.decode('utf-8').find('Alpine') > -1: - return True - return False + response = subprocess.check_output(["grep", "-w", "NAME", "/etc/os-release"]) + return response.decode('utf-8').find('Alpine') > -1 def __make_path(self, dest_path): try: @@ -57,11 +68,20 @@ def __available_dir(self): raise BrowserStackLocalError('Error trying to download BrowserStack Local binary') def download(self, chunk_size=8192, progress_hook=None): - response = urlopen(self.http_path) + headers = { + 'User-Agent': '/'.join(('browserstack-local-python', LocalBinary._version)), + 'Accept-Encoding': 'gzip, *', + } + + if sys.version_info < (3, 2): + # lack of support for gzip decoding for stream, response is expected to have a tell() method + headers.pop('Accept-Encoding', None) + + response = urlopen(self.http_path, headers=headers) try: - total_size = int(response.info().getheader('Content-Length').strip()) + total_size = int(response.info().get('Content-Length', '').strip() or '0') except: - total_size = int(response.info().get_all('Content-Length')[0].strip()) + total_size = int(response.info().get_all('Content-Length')[0].strip() or '0') bytes_so_far = 0 dest_parent_dir = self.__available_dir() @@ -69,21 +89,36 @@ def download(self, chunk_size=8192, progress_hook=None): if self.is_windows: dest_binary_name += '.exe' + content_encoding = response.info().get('Content-Encoding', '') + gzip_file = gzip.GzipFile(fileobj=response, mode='rb') if content_encoding.lower() == 'gzip' else None + + def read_chunk(chunk_size): + if gzip_file: + return gzip_file.read(chunk_size) + else: + return response.read(chunk_size) + with open(os.path.join(dest_parent_dir, dest_binary_name), 'wb') as local_file: while True: - chunk = response.read(chunk_size) + chunk = read_chunk(chunk_size) bytes_so_far += len(chunk) if not chunk: break - if progress_hook: + if total_size > 0 and progress_hook: progress_hook(bytes_so_far, chunk_size, total_size) try: local_file.write(chunk) except: return self.download(chunk_size, progress_hook) + + if gzip_file: + gzip_file.close() + + if callable(getattr(response, 'close', None)): + response.close() final_path = os.path.join(dest_parent_dir, dest_binary_name) st = os.stat(final_path) From ac36e9e13e5b9f45551c815738bdb40bf04d97f4 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 7 Oct 2024 13:46:59 +0530 Subject: [PATCH 2/7] chore: apply sourceURL override, log line for debugging --- browserstack/local_binary.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index fad6fbc..77f878a 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -20,19 +20,23 @@ def __init__(self): is_64bits = sys.maxsize > 2**32 self.is_windows = False osname = platform.system() + source_url = "https://www.browserstack.com/local-testing/downloads/binaries/" + if os.environ.get('BROWSERSTACK_LOCAL_BIN_URL'): + source_url = os.environ.get('BROWSERSTACK_LOCAL_BIN_URL') + if osname == 'Darwin': - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-darwin-x64" + self.http_path = source_url + "BrowserStackLocal-darwin-x64" elif osname == 'Linux': if self.is_alpine(): - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-alpine" + self.http_path = source_url + "BrowserStackLocal-alpine" else: if is_64bits: - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-linux-x64" + self.http_path = source_url + "BrowserStackLocal-linux-x64" else: - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-linux-ia32" + self.http_path = source_url + "BrowserStackLocal-linux-ia32" else: self.is_windows = True - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal.exe" + self.http_path = source_url + "BrowserStackLocal.exe" self.ordered_paths = [ os.path.join(os.path.expanduser('~'), '.browserstack'), @@ -92,6 +96,9 @@ def download(self, chunk_size=8192, progress_hook=None): content_encoding = response.info().get('Content-Encoding', '') gzip_file = gzip.GzipFile(fileobj=response, mode='rb') if content_encoding.lower() == 'gzip' else None + if os.getenv('BROWSERSTACK_LOCAL_DEBUG_GZIP') and gzip_file: + print('using gzip in ' + headers['User-Agent']) + def read_chunk(chunk_size): if gzip_file: return gzip_file.read(chunk_size) From 29d9194a5a8dc2a28d08c66a7f5da880019f8298 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 15:40:18 +0530 Subject: [PATCH 3/7] update: remove URL flag --- browserstack/local_binary.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 77f878a..870501b 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -21,8 +21,6 @@ def __init__(self): self.is_windows = False osname = platform.system() source_url = "https://www.browserstack.com/local-testing/downloads/binaries/" - if os.environ.get('BROWSERSTACK_LOCAL_BIN_URL'): - source_url = os.environ.get('BROWSERSTACK_LOCAL_BIN_URL') if osname == 'Darwin': self.http_path = source_url + "BrowserStackLocal-darwin-x64" From fd3d938e016c28d4523d2c7adf4ea56caaf4296f Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 16:21:26 +0530 Subject: [PATCH 4/7] chore: remove refactoring --- browserstack/local_binary.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 870501b..7a13d85 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -3,15 +3,9 @@ import gzip try: - import urllib.request - - def urlopen(url, headers=None): - return urllib.request.urlopen(urllib.request.Request(url, headers=headers)) + from urllib.request import urlopen except ImportError: - import urllib2 - - def urlopen(url, headers=None): - return urllib2.urlopen(urllib2.Request(url, headers=headers)) + from urllib2 import urlopen class LocalBinary: _version = None From de1af8f822e52f0f02cd9ae1e15cf77ad24bcabb Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 16:22:49 +0530 Subject: [PATCH 5/7] chore: remove refactoring --- browserstack/local_binary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 7a13d85..b39b4c4 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -3,9 +3,9 @@ import gzip try: - from urllib.request import urlopen + from urllib.request import urlopen except ImportError: - from urllib2 import urlopen + from urllib2 import urlopen class LocalBinary: _version = None From e5f72a570887e020f6579b5c3eee33e1973e8985 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 16:31:38 +0530 Subject: [PATCH 6/7] update: add request object --- browserstack/local_binary.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index b39b4c4..72d1402 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -3,9 +3,9 @@ import gzip try: - from urllib.request import urlopen + from urllib.request import urlopen, Request except ImportError: - from urllib2 import urlopen + from urllib2 import urlopen, Request class LocalBinary: _version = None @@ -73,7 +73,7 @@ def download(self, chunk_size=8192, progress_hook=None): # lack of support for gzip decoding for stream, response is expected to have a tell() method headers.pop('Accept-Encoding', None) - response = urlopen(self.http_path, headers=headers) + response = urlopen(Request(self.http_path, headers=headers)) try: total_size = int(response.info().get('Content-Length', '').strip() or '0') except: From e3362ff8b2c6e74938df53fc58c15c713937c4ba Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 18:18:28 +0530 Subject: [PATCH 7/7] fix: tests --- tests/test_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_local.py b/tests/test_local.py index 11d6a02..d5d4e46 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -70,7 +70,7 @@ def test_proxy(self): self.assertIn('-proxyHost', self.local._generate_cmd()) self.assertIn('localhost', self.local._generate_cmd()) self.assertIn('-proxyPort', self.local._generate_cmd()) - self.assertIn(2000, self.local._generate_cmd()) + self.assertIn('2000', self.local._generate_cmd()) self.assertIn('-proxyUser', self.local._generate_cmd()) self.assertIn('hello', self.local._generate_cmd()) self.assertIn('-proxyPass', self.local._generate_cmd())