| 30 | |
| 31 | |
| 32 | class UpdateHTTPRequestHandler(http.server.BaseHTTPRequestHandler): |
| 33 | def __init__(self, catalog_dir, *args): |
| 34 | self.catalog_dir = catalog_dir |
| 35 | http.server.BaseHTTPRequestHandler.__init__(self, *args) |
| 36 | |
| 37 | def do_GET(self): |
| 38 | request_path = urllib.parse.urlparse(self.path) |
| 39 | if request_path.path == '/_catalog': |
| 40 | err = '' |
| 41 | query = urllib.parse.urlparse(self.path).query |
| 42 | try: |
| 43 | op = urllib.parse.parse_qs(query)['op'][0] |
| 44 | if op == 'list': |
| 45 | try: |
| 46 | path = urllib.parse.parse_qs(query)['path'][0] |
| 47 | except KeyError: |
| 48 | path = '.' |
| 49 | full_path = os.path.join(self.catalog_dir, path) |
| 50 | if os.path.commonprefix((os.path.realpath(full_path), self.catalog_dir)) == self.catalog_dir: |
| 51 | self.__send_dir(full_path) |
| 52 | result = True |
| 53 | else: |
| 54 | err = 'Path traversal detected' |
| 55 | result = False |
| 56 | else: |
| 57 | err = '{0} unknown operation'.format(op) |
| 58 | result = False |
| 59 | except KeyError: |
| 60 | err = '{0} invaid catalog request'.format(self.path) |
| 61 | result = False |
| 62 | if not result: |
| 63 | logger.info(err) |
| 64 | self.send_response(http.HTTPStatus.FORBIDDEN, err) |
| 65 | self.end_headers() |
| 66 | else: |
| 67 | self.__send_file(self.path) |
| 68 | |
| 69 | def __check_header(self): |
| 70 | ex_headers_templ = ['x-*-STA-MAC', 'x-*-AP-MAC', 'x-*-FREE-SPACE', 'x-*-SKETCH-SIZE', 'x-*-SKETCH-MD5', 'x-*-CHIP-SIZE', 'x-*-SDK-VERSION'] |
| 71 | ex_headers = [] |
| 72 | ua = re.match('(ESP8266|ESP32)-http-Update', self.headers.get('User-Agent')) |
| 73 | if ua: |
| 74 | arch = ua.group().split('-')[0] |
| 75 | ex_headers = list(map(lambda x: x.replace('*', arch), ex_headers_templ)) |
| 76 | else: |
| 77 | logger.info('User-Agent {0} is not HTTPUpdate'.format(ua)) |
| 78 | return False |
| 79 | for ex_header in ex_headers: |
| 80 | if ex_header not in self.headers: |
| 81 | logger.info('Missing header {0} to identify a legitimate request'.format(ex_header)) |
| 82 | return False |
| 83 | return True |
| 84 | |
| 85 | def __send_file(self, path): |
| 86 | if not self.__check_header(): |
| 87 | self.send_response(http.HTTPStatus.FORBIDDEN, 'The request available only from ESP8266 or ESP32 http updater.') |
| 88 | self.end_headers() |
| 89 | return |