tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

image.py (3937B)


      1 import os, sys, array, math
      2 
      3 from io import BytesIO
      4 
      5 from wptserve.utils import isomorphic_decode
      6 
      7 import importlib
      8 subresource = importlib.import_module("common.security-features.subresource.subresource")
      9 
     10 class Image:
     11    """This class partially implements the interface of the PIL.Image.Image.
     12       One day in the future WPT might support the PIL module or another imaging
     13       library, so this hacky BMP implementation will no longer be required.
     14    """
     15    def __init__(self, width, height):
     16        self.width = width
     17        self.height = height
     18        self.img = bytearray([0 for i in range(3 * width * height)])
     19 
     20    @staticmethod
     21    def new(mode, size, color=0):
     22        return Image(size[0], size[1])
     23 
     24    def _int_to_bytes(self, number):
     25        packed_bytes = [0, 0, 0, 0]
     26        for i in range(4):
     27            packed_bytes[i] = number & 0xFF
     28            number >>= 8
     29 
     30        return packed_bytes
     31 
     32    def putdata(self, color_data):
     33        for y in range(self.height):
     34            for x in range(self.width):
     35                i = x + y * self.width
     36                if i > len(color_data) - 1:
     37                    return
     38 
     39                self.img[i * 3: i * 3 + 3] = color_data[i][::-1]
     40 
     41    def save(self, f, type):
     42        assert type == "BMP"
     43        # 54 bytes of preambule + image color data.
     44        filesize = 54 + 3 * self.width * self.height
     45        # 14 bytes of header.
     46        bmpfileheader = bytearray([ord('B'), ord('M')] + self._int_to_bytes(filesize) +
     47                                  [0, 0, 0, 0, 54, 0, 0, 0])
     48        # 40 bytes of info.
     49        bmpinfoheader = bytearray([40, 0, 0, 0] +
     50                                  self._int_to_bytes(self.width) +
     51                                  self._int_to_bytes(self.height) +
     52                                  [1, 0, 24] + (25 * [0]))
     53 
     54        padlength = (4 - (self.width * 3) % 4) % 4
     55        bmppad = bytearray([0, 0, 0])
     56        padding = bmppad[0 : padlength]
     57 
     58        f.write(bmpfileheader)
     59        f.write(bmpinfoheader)
     60 
     61        for i in range(self.height):
     62            offset = self.width * (self.height - i - 1) * 3
     63            f.write(self.img[offset : offset + 3 * self.width])
     64            f.write(padding)
     65 
     66 def encode_string_as_bmp_image(string_data):
     67    data_bytes = array.array("B", string_data.encode("utf-8"))
     68 
     69    num_bytes = len(data_bytes)
     70 
     71    # Encode data bytes to color data (RGB), one bit per channel.
     72    # This is to avoid errors due to different color spaces used in decoding.
     73    color_data = []
     74    for byte in data_bytes:
     75        p = [int(x) * 255 for x in '{0:08b}'.format(byte)]
     76        color_data.append((p[0], p[1], p[2]))
     77        color_data.append((p[3], p[4], p[5]))
     78        color_data.append((p[6], p[7], 0))
     79 
     80    # Render image.
     81    num_pixels = len(color_data)
     82    sqrt = int(math.ceil(math.sqrt(num_pixels)))
     83    img = Image.new("RGB", (sqrt, sqrt), "black")
     84    img.putdata(color_data)
     85 
     86    # Flush image to string.
     87    f = BytesIO()
     88    img.save(f, "BMP")
     89    f.seek(0)
     90 
     91    return f.read()
     92 
     93 def generate_payload(request, server_data):
     94    data = (u'{"headers": %(headers)s}') % server_data
     95    if b"id" in request.GET:
     96        request.server.stash.put(request.GET[b"id"], data)
     97    data = encode_string_as_bmp_image(data)
     98    return data
     99 
    100 def generate_report_headers_payload(request, server_data):
    101    stashed_data = request.server.stash.take(request.GET[b"id"])
    102    return stashed_data
    103 
    104 def main(request, response):
    105    handler = lambda data: generate_payload(request, data)
    106    content_type = b'image/bmp'
    107 
    108    if b"report-headers" in request.GET:
    109        handler = lambda data: generate_report_headers_payload(request, data)
    110        content_type = b'application/json'
    111 
    112    subresource.respond(request,
    113                        response,
    114                        payload_generator = handler,
    115                        content_type = content_type,
    116                        access_control_allow_origin = b"*")