diff --git a/tcllib/requests/__init__.py b/tcllib/requests/__init__.py index ed3b81e..8e0148e 100644 --- a/tcllib/requests/__init__.py +++ b/tcllib/requests/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from .checkrequest import CheckRequest +from .downloadrequest import DownloadRequest from .runner import * from .serverselector import * from .dumpmgr import write_info_if_dumps_found diff --git a/tcllib/requests/downloadrequest.py b/tcllib/requests/downloadrequest.py new file mode 100644 index 0000000..345c247 --- /dev/null +++ b/tcllib/requests/downloadrequest.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +import binascii +import hashlib +import random +import time +import zlib +from collections import OrderedDict +from math import floor +from .. import devices +from .tclrequest import TclRequest +from .tclresult import DownloadResult + + +VDKEY_B64Z = b"eJwdjwEOwDAIAr8kKFr//7HhmqXp8AIIDrYAgg8byiUXrwRJRXja+d6iNxu0AhUooDCN9rd6rDLxmGIakUVWo3IGCTRWqCAt6X4jGEIUAxgN0eYWnp+LkpHQAg/PsO90ELsy0Npm/n2HbtPndFgGEV31R9OmT4O4nrddjc3Qt6nWscx7e+WRHq5UnOudtjw5skuV09pFhvmqnOEIs4ljPeel1wfLYUF4\n" + + +def get_salt(): + """Generate cryptographic salt.""" + millis = floor(time.time() * 1000) + tail = "{:06d}".format(random.randint(0, 999999)) + return "{}{}".format(str(millis), tail) + +def get_vk2(params_dict, cltp): + """Generate salted hash of API parameters.""" + params_dict["cltp"] = cltp + query = "" + for key, val in params_dict.items(): + if query: + query += "&" + query += key + "=" + str(val) + vdk = zlib.decompress(binascii.a2b_base64(VDKEY_B64Z)) + query += vdk.decode("utf-8") + engine = hashlib.sha1() + engine.update(bytes(query, "utf-8")) + hexhash = engine.hexdigest() + return hexhash + +class DownloadRequest(TclRequest): + def __init__(self, device: devices.Device, tvver: str, fw_id: str): + super().__init__() + self.uri = "/download_request.php" + self.method = "POST" + self.device = device + self.tvver = tvver + self.fw_id = fw_id + + def get_headers(self): + return {"User-Agent": self.device.ua} + + def get_params(self): + params = OrderedDict() + params["id"] = self.device.imei + params["salt"] = get_salt() + params["curef"] = self.device.curef + params["fv"] = self.device.fwver + params["tv"] = self.tvver + params["type"] = self.device.type + params["fw_id"] = self.fw_id + params["mode"] = self.device.mode + params["vk"] = get_vk2(params, self.device.cltp) + params["cltp"] = self.device.cltp + params["cktp"] = self.device.cktp + params["rtd"] = self.device.rtd + if self.device.mode == self.device.MODE_STATES["FULL"]: + params["foot"] = 1 + params["chnl"] = self.device.chnl + return params + + def is_done(self, http_status: int, contents: str) -> bool: + if http_status == 200: + self.response = contents + self.result = DownloadResult(contents) + self.success = True + return True + elif http_status not in [500, 502, 503]: + # Errors OTHER than 500, 502 or 503 are probably + # errors where we don't need to retry + self.error = "HTTP {}".format(http_status) + self.success = False + return True + return False diff --git a/tcllib/requests/tclresult.py b/tcllib/requests/tclresult.py index 3fe7806..90735d9 100644 --- a/tcllib/requests/tclresult.py +++ b/tcllib/requests/tclresult.py @@ -34,3 +34,21 @@ class CheckResult(TclResult): self.filename = fileinfo.find("FILENAME").text self.filesize = fileinfo.find("SIZE").text self.filehash = fileinfo.find("CHECKSUM").text + +class DownloadResult(TclResult): + def __init__(self, xml: str): + super().__init__(xml) + root = ElementTree.fromstring(xml) + file = root.find("FILE_LIST").find("FILE") + self.fileid = file.find("FILE_ID").text + self.fileurl = file.find("DOWNLOAD_URL").text + s3_fileurl_node = file.find("S3_DOWNLOAD_URL") + self.s3_fileurl = None + if s3_fileurl_node: + self.s3_fileurl = s3_fileurl_node.text + slave_list = root.find("SLAVE_LIST").findall("SLAVE") + enc_list = root.find("SLAVE_LIST").findall("ENCRYPT_SLAVE") + s3_slave_list = root.find("SLAVE_LIST").findall("S3_SLAVE") + self.slaves = [s.text for s in slave_list] + self.encslaves = [s.text for s in enc_list] + self.s3_slaves = [s.text for s in s3_slave_list]