PageRenderTime 21ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/cuckoo/reporting/singlefile.py

https://github.com/cuckoobox/cuckoo
Python | 199 lines | 164 code | 25 blank | 10 comment | 10 complexity | 54b5d0e7c89e79499b2abea74493ee61 MD5 | raw file
  1. # Copyright (C) 2012-2013 Claudio Guarnieri.
  2. # Copyright (C) 2014-2018 Cuckoo Foundation.
  3. # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
  4. # See the file 'docs/LICENSE' for copying permission.
  5. import base64
  6. import codecs
  7. import datetime
  8. import glob
  9. import io
  10. import jinja2
  11. import logging
  12. import os
  13. import random
  14. from cuckoo.common.abstracts import Report
  15. from cuckoo.common.exceptions import CuckooReportError
  16. from cuckoo.misc import cwd
  17. logging.getLogger("weasyprint").setLevel(logging.ERROR)
  18. class SingleFile(Report):
  19. """Store report in a single-file HTML and/or PDF format."""
  20. fonts = [{
  21. "family": "Roboto",
  22. "weight": 400,
  23. "style": "normal",
  24. "file": {
  25. "src": "Roboto-Regular-webfont.woff",
  26. "format": "woff",
  27. },
  28. }, {
  29. "family": "Roboto",
  30. "weight": 400,
  31. "style": "italic",
  32. "file": {
  33. "src": "Roboto-Italic-webfont.woff",
  34. "format": "woff",
  35. },
  36. }, {
  37. "family": "Roboto",
  38. "weight": 700,
  39. "style": "normal",
  40. "file": {
  41. "src": "Roboto-Bold-webfont.woff",
  42. "format": "woff",
  43. },
  44. }, {
  45. "family": "Roboto",
  46. "weight": 500,
  47. "style": "normal",
  48. "file": {
  49. "src": "Roboto-Medium-webfont.woff",
  50. "format": "woff",
  51. },
  52. }, {
  53. "family": "FontAwesome",
  54. "weight": "normal",
  55. "style": "normal",
  56. "file": {
  57. "src": "fontawesome-webfont.woff2",
  58. "format": "woff2",
  59. },
  60. }]
  61. mime_types = {
  62. "svg": "image/svg+xml",
  63. "ttf": "application/x-font-ttf",
  64. "otf": "application/x-font-opentype",
  65. "woff": "application/font-woff",
  66. "woff2": "application/font-woff2",
  67. "eot": "application/vnd.ms-fontobject",
  68. "sfnt": "application/font-sfnt",
  69. "png": "image/png",
  70. "gif": "image/gif",
  71. "jpg": "image/jpeg",
  72. }
  73. path_base = cwd("html", private=True)
  74. def run(self, results):
  75. report = self.generate_jinja2_template(results)
  76. if self.options.get("html"):
  77. report_path = os.path.join(self.reports_path, "report.html")
  78. codecs.open(report_path, "wb", encoding="utf-8").write(report)
  79. if self.options.get("pdf"):
  80. try:
  81. import weasyprint
  82. except ImportError:
  83. raise CuckooReportError(
  84. "The weasyprint library hasn't been installed on your "
  85. "Operating System and as such we can't generate a PDF "
  86. "report for you. You can install 'weasyprint' manually "
  87. "by running 'pip install weasyprint' or by compiling and "
  88. "installing package yourself."
  89. )
  90. report_path = os.path.join(self.reports_path, "report.pdf")
  91. f = weasyprint.HTML(io.BytesIO(report.encode("utf8")))
  92. f.write_pdf(report_path)
  93. def generate_jinja2_template(self, results):
  94. template = open(cwd("html", "report.html", private=True), "rb").read()
  95. env = jinja2.environment.Environment(
  96. autoescape=True,
  97. loader=jinja2.loaders.FileSystemLoader(self.path_base),
  98. trim_blocks=False, lstrip_blocks=True
  99. )
  100. return env.from_string(template).render(
  101. task=self.task, filename=os.path.basename(self.task["target"]),
  102. results=results, date=datetime.datetime.now(),
  103. images=self.combine_images(), css=self.combine_css(),
  104. fonts=self.index_fonts(), scripts=self.combine_js(),
  105. screenshots=self.combine_screenshots(results),
  106. )
  107. def combine_css(self):
  108. """Scan the static/css/ directory and concatenate stylesheets"""
  109. css_includes = []
  110. for filepath in glob.glob("%s/static/css/*.css" % self.path_base):
  111. css_includes.append(open(filepath, "rb").read().decode("utf8"))
  112. return "\n".join(css_includes)
  113. def combine_js(self):
  114. """Scan the static/js/ directory and concatenate js files"""
  115. js_includes = []
  116. # Note: jquery-2.2.4.min.js must be the first file.
  117. filepaths = sorted(glob.glob("%s/static/js/*.js" % self.path_base))
  118. for filepath in filepaths:
  119. js_includes.append(
  120. open(filepath, "rb").read().strip().decode("utf8")
  121. )
  122. return "\n".join(js_includes)
  123. def index_fonts(self):
  124. fonts = []
  125. for font in self.fonts:
  126. filepath = os.path.join(
  127. self.path_base, "static", "fonts", font["file"]["src"]
  128. )
  129. fonts.append({
  130. "family": font["family"],
  131. "weight": font["weight"],
  132. "style": font["style"],
  133. "url": self.css_inline_font(
  134. font["file"]["format"],
  135. base64.b64encode(open(filepath, "rb").read())
  136. )
  137. })
  138. return fonts
  139. def combine_screenshots(self, results, num=4, shuffle=True):
  140. screenshots = results.get("screenshots", [])
  141. # Select N random screenshots.
  142. shots = range(len(screenshots))
  143. if shuffle:
  144. random.shuffle(shots)
  145. shot_includes = []
  146. for idx in shots[:num]:
  147. filepath = screenshots[idx]["path"]
  148. shot_includes.append({
  149. "selector": "shot-%d" % idx,
  150. "name": os.path.basename(filepath),
  151. "css": self.css_inline_image(
  152. "shot-%d" % idx, "jpg",
  153. base64.b64encode(open(filepath, "rb").read())
  154. ),
  155. })
  156. return shot_includes
  157. def combine_images(self):
  158. """Create a CSS string representation of all files in static/img/."""
  159. img_includes = []
  160. for imgpath in glob.glob("%s/static/img/*.png" % self.path_base):
  161. name, ext = os.path.splitext(os.path.basename(imgpath))
  162. img_includes.append(self.css_inline_image(
  163. name, ext.lstrip("."),
  164. base64.b64encode(open(imgpath, "rb").read())
  165. ))
  166. return "\n".join(img_includes)
  167. def css_inline_image(self, name, extension, content):
  168. return "div.img-%s{background: url(data:%s;base64,%s);}" % (
  169. name, self.mime_types[extension], content
  170. )
  171. def css_inline_font(self, extension, content):
  172. return "url(data:%s;charset=utf-8;base64,%s) format('%s')" % (
  173. self.mime_types[extension], content, extension
  174. )