Guest User

Untitled

a guest
Jul 16th, 2018
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.29 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. """
  4. Functions for dealing with tasks in the uwsgi spooler.
  5. """
  6.  
  7. import sys
  8. import os
  9. import logging
  10. from collections import OrderedDict
  11.  
  12. logging.basicConfig(
  13. level=logging.INFO,
  14. format='[%(asctime)s] - [%(levelname)s]: %(message)s'
  15. )
  16. logger = logging.getLogger('uwsgi_spooler_modifier')
  17.  
  18.  
  19. class Task(object):
  20. """
  21. an object representing our task/spooler file.
  22. """
  23. @staticmethod
  24. def calculate_content_length(content_list):
  25. # length of the content
  26. # = number of bytes required to represent the size of the item.
  27. # (so any item with a length < 256 should be 1 byte)
  28. # + 1 NULL byte (\x00)
  29. # + number of bytes required to represent the item.
  30. content_size = [
  31. # because we need to convert the bit_length to a byte_length.
  32. ((len(x).bit_length() + 7) // 8) +
  33. # NULL byte
  34. 1 +
  35. # length of the item.
  36. len(x)
  37. for x in content_list
  38. ]
  39. return sum(content_size)
  40.  
  41. @classmethod
  42. def init_from_file(cls, file_name):
  43. binary_content = b''
  44. with open(file_name, 'rb') as f:
  45. binary_content = f.read()
  46.  
  47. content_list = binary_content.partition(b'\x00')
  48.  
  49. # Structure of the file header is:
  50. # file_header[0] == 17 (if it's a uwsgi spooler file)
  51. # file_header[1-n] == size of the file in bytes
  52. # file_header[n+1] == null byte (that we split on, so not actually in the
  53. # file_header object).
  54. # These two values are excluded from our size.
  55. file_header = content_list[0]
  56. if (int(file_header[0]) != 17):
  57. # This is not a uwsgi spooler file.
  58. raise Exception('This file is not a uwsgi spooler file')
  59. # convert size to an int;
  60. # the spooler files I've been looking at are all little-endian.
  61. file_size = int.from_bytes(file_header[1:], byteorder='little')
  62.  
  63. # This is a string of length|null|key|length|null|value|... sets.
  64. # This block converts the contents of the file into a list.
  65. bytes_list = list(content_list[2])
  66. content_list = []
  67. str_length = 0
  68. buffered_bytes = b''
  69. for b in bytes_list:
  70. b_byte = b.to_bytes(1, byteorder='little')
  71. if b_byte != b'\x00' and not str_length:
  72. # set up the length of the string
  73. buffered_bytes += b_byte
  74. elif b != b'\x00' and str_length:
  75. # we're dealing with a key or value
  76. buffered_bytes += b_byte
  77. str_length -= 1
  78. if not str_length:
  79. # We've got all of the key/value; add it to the list and
  80. # then flush the buffer.
  81. content_list.append(buffered_bytes)
  82. buffered_bytes = b''
  83. elif b_byte == b'\x00':
  84. # we've hit the null byte, setup the string length and then flush
  85. # the buffer.
  86. str_length = int.from_bytes(buffered_bytes, byteorder='little')
  87. buffered_bytes = b''
  88.  
  89. # convert our list into key-value pairs.
  90. file_contents = OrderedDict()
  91. for i, item in enumerate(content_list):
  92. if i % 2 == 0:
  93. file_contents[item] = None
  94. elif i % 2 == 1:
  95. file_contents[content_list[i - 1]] = item
  96.  
  97. content_length = Task.calculate_content_length(content_list)
  98.  
  99. # Sanity check.
  100. # Does the header length match our content length?
  101. if (
  102. file_size != content_length
  103. ):
  104. raise Exception(
  105. 'The file is malformed. \n' +
  106. 'The size of the file does not match the content length.\n' +
  107. '[Header Size: {}] != [Content Size: {}]'.format(
  108. file_size,
  109. content_length
  110. )
  111. )
  112. logger.debug(
  113. (
  114. file_size,
  115. file_contents,
  116. content_length
  117. )
  118. )
  119. # Return an instance of the Task class.
  120. return cls(file_contents, file_size)
  121.  
  122. # Instance functions
  123. def __init__(self, content_dict, file_size):
  124. self.content_dict = content_dict
  125. self.file_size = file_size
  126.  
  127. def update(self, key, value):
  128. # Make sure we've got bytes objects
  129. if not isinstance(key, bytes):
  130. key = key.encode('utf8')
  131. if not isinstance(value, bytes):
  132. if not isinstance(value, str):
  133. value = str(value)
  134. value = value.encode('utf8')
  135.  
  136. # Will raise an exception if key doesn't exist.
  137. self.content_dict[key]
  138. # set the value
  139. self.content_dict[key] = value
  140. content_list = []
  141. for x, y in self.content_dict.items():
  142. content_list.append(x)
  143. content_list.append(y)
  144. self.file_size = Task.calculate_content_length(content_list)
  145. return value
  146.  
  147. def write_to_file(self, file_name):
  148. # initialize the buffer to our uwsgi spooler byte.
  149. buffer_str = b'\x11'
  150. # we need to then append the length of the content to the buffer
  151. # Sanity check the length before appending
  152. content_list = []
  153. for key, value in self.content_dict.items():
  154. content_list.append(key)
  155. content_list.append(value)
  156. expected_size = Task.calculate_content_length(content_list)
  157. if expected_size != self.file_size:
  158. raise Exception(
  159. "sizes don't match; Somethings gone wrong!" +
  160. "[{}] != [{}]".format(expected_size, self.file_size)
  161. )
  162. # Add the size
  163. buffer_str += self.file_size.to_bytes(
  164. ((self.file_size.bit_length() + 7) // 8),
  165. byteorder='little'
  166. )
  167. # Add the null byte;
  168. buffer_str += b'\x00'
  169. # iterate through our dictionary, for each key=value write them to the
  170. # buffer.
  171. for key, value in self.content_dict.items():
  172. # key size
  173. buffer_str += len(key).to_bytes(
  174. ((len(key).bit_length() + 7) // 8),
  175. byteorder='little'
  176. )
  177. # Null Byte
  178. buffer_str += b'\x00'
  179. # key
  180. buffer_str += key
  181. # value size
  182. buffer_str += len(value).to_bytes(
  183. ((len(value).bit_length() + 7) // 8),
  184. byteorder='little'
  185. )
  186. # Null byte
  187. buffer_str += b'\x00'
  188. # value
  189. buffer_str += value
  190.  
  191. # Write
  192. logger.debug('Writing [{}] to {}'.format(buffer_str, file_name))
  193. with open(file_name, 'wb') as f:
  194. f.write(buffer_str)
  195. return
  196.  
  197.  
  198. def main(argv):
  199. file_name = None
  200. updates = {}
  201. for i, arg in enumerate(argv):
  202. if arg == '--filename':
  203. file_name = argv[i + 1]
  204. elif arg == '--update':
  205. update_item = argv[i + 1]
  206. if '=' not in update_item:
  207. raise Exception('update must be key=value')
  208. update_item = update_item.split('=')
  209. updates[update_item[0].strip()] = update_item[1].strip()
  210. elif arg == '-v' or arg == '--verbose':
  211. logger.setLevel(logging.DEBUG)
  212. elif arg == '-h' or arg == '--help':
  213. usage()
  214. return False
  215. continue
  216.  
  217. if not file_name:
  218. raise Exception("No filename specified")
  219. if not os.path.exists(file_name):
  220. raise Exception('{} does not exist'.format(file_name))
  221.  
  222. res = Task.init_from_file(file_name)
  223. if not res:
  224. return False
  225. if updates:
  226. for key, value in updates.items():
  227. res.update(key, value)
  228. logger.debug('Content After Update: \n{}'.format(res.content_dict))
  229.  
  230. res.write_to_file(file_name + '.update')
  231. return True
  232.  
  233.  
  234. def usage(error_str=None):
  235. print(
  236. '''
  237. --filename [filename]
  238. The path/name of the spooler file to edit.
  239. --update key=value
  240. The key/value pair in the spooler file to change. This key can be
  241. passed multiple times in order to update multiple values.
  242. -v | --verbose
  243. Turn on DEBUG output.
  244. -h | --help
  245. Print this help text.
  246. '''
  247. )
  248.  
  249.  
  250. if __name__ == '__main__':
  251. main(sys.argv)
Add Comment
Please, Sign In to add comment