├── .gitignore ├── LICENSE ├── README.rst ├── scripts ├── db_mapreduce.py ├── db_setup.py ├── fv_dbcontrol.py ├── fv_fileoutput.py ├── fv_load.py ├── fv_scrapy.py └── utils.py └── web ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── app ├── assets │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── d3.v3.min.js │ │ └── dimple.v1.1.5.min.js │ └── stylesheets │ │ ├── application.css │ │ ├── darkstrap.css │ │ ├── font-awesome.css │ │ ├── framework_and_overrides.css.scss │ │ └── overrides.css ├── controllers │ ├── analysis_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── db_controller.rb │ ├── explorer_controller.rb │ └── visitors_controller.rb ├── helpers │ └── application_helper.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ └── concerns │ │ └── .keep └── views │ ├── analysis │ ├── analysis.html.haml │ ├── guid.html.haml │ ├── guids.html.haml │ └── keywords.html.haml │ ├── explorer │ ├── _object.html.haml │ ├── _product.html.haml │ ├── _update.html.haml │ ├── download.html.haml │ ├── explorer.html.haml │ ├── file.html.haml │ ├── firmware.html.haml │ ├── products.html.haml │ ├── raw.html.haml │ └── uefi.html.haml │ ├── layouts │ ├── _messages.html.haml │ ├── _navigation.html.haml │ ├── _navigation_links.html.haml │ └── application.html.haml │ ├── pages │ └── about.html.haml │ └── visitors │ └── new.html.haml ├── config.ru ├── config ├── application.rb ├── application.yml ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── logger_customizations.rb │ ├── mime_types.rb │ ├── secret_token.rb │ ├── session_store.rb │ ├── simple_form.rb │ ├── simple_form_bootstrap.rb │ ├── will_paginate_array.rb │ └── wrap_parameters.rb ├── locales │ ├── en.yml │ └── simple_form.en.yml └── routes.rb ├── db └── seeds.rb ├── lib ├── assets │ └── .keep ├── tasks │ └── .keep └── templates │ └── haml │ └── scaffold │ └── _form.html.haml ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico ├── humans.txt └── robots.txt ├── script └── rails ├── test ├── controllers │ └── .keep ├── fixtures │ └── .keep ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ └── .keep └── test_helper.rb └── vendor └── assets ├── javascripts └── .keep └── stylesheets └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | # bundler state 2 | /.bundle 3 | /vendor/bundle/ 4 | /vendor/ruby/ 5 | 6 | # minimal Rails specific artifacts 7 | db/*.sqlite3 8 | /db/*.sqlite3-journal 9 | /log/* 10 | /tmp/* 11 | 12 | # configuration file introduced in Rails 4.1 13 | /config/secrets.yml 14 | 15 | # various artifacts 16 | **.war 17 | *.rbc 18 | *.sassc 19 | .rspec 20 | .redcar/ 21 | .sass-cache 22 | /config/config.yml 23 | /config/database.yml 24 | /coverage.data 25 | /coverage/ 26 | /db/*.javadb/ 27 | /db/*.sqlite3 28 | /doc/api/ 29 | /doc/app/ 30 | /doc/features.html 31 | /doc/specs.html 32 | /public/cache 33 | /public/stylesheets/compiled 34 | /public/system/* 35 | /spec/tmp/* 36 | /cache 37 | /capybara* 38 | /capybara-*.html 39 | /gems 40 | /specifications 41 | rerun.txt 42 | pickle-email-*.html 43 | .zeus.sock 44 | 45 | # scm revert files 46 | **.orig 47 | 48 | # Netbeans project directory 49 | /nbproject/ 50 | 51 | # RubyMine project files 52 | .idea 53 | /*.tmproj 54 | **.swp 55 | 56 | # Ignore certificates 57 | *.pem 58 | *.cert 59 | 60 | # Ignore OS generated files 61 | .DS_Store* 62 | ehthumbs.db 63 | Icon? 64 | Thumbs.db 65 | 66 | # Ignore python byte/object code 67 | *.py[cod] 68 | 69 | # C extensions 70 | *.so 71 | 72 | # Packages 73 | *.egg 74 | *.egg-info 75 | dist 76 | build 77 | eggs 78 | parts 79 | bin 80 | var 81 | sdist 82 | develop-eggs 83 | .installed.cfg 84 | lib64 85 | __pycache__ 86 | 87 | # Installer logs 88 | pip-log.txt 89 | 90 | # Unit test / coverage reports 91 | .coverage 92 | .tox 93 | nosetests.xml 94 | 95 | # Translations 96 | *.mo 97 | 98 | # Mr Developer 99 | .mr.developer.cfg 100 | .project 101 | .pydevproject 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Teddy Reed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Subzero 2 | ======= 3 | Firmware analysis gone wild. 4 | 5 | This project includes both a web interface and a set of importing and map/reduction scripts used for vulnerability analysis on Firmware Updates (specifically those parsed by uefi-firmware-parser.) The import of firmware is complimented with the descriptions and metadata mined from uefi-spider in JSON form. This web interface will eventually include a submission form used to detect/match unknown updates against the corpus of imported data. 6 | 7 | Installation 8 | ------------ 9 | Subzero provides a ``db_setup.py`` script that creates the RethinkDB database on the `localhost` server. This script can be run multiple time until it completes if it does error initially. It should create the tables used by the application and the required table indexes. 10 | 11 | :: 12 | 13 | $ (cd web && bundle install() 14 | $ (cd scripts && python ./db_setup.py) 15 | 16 | **Requirements** 17 | 18 | - RethinkDB (python rethinkdb) 19 | - ssdeep (pydeep) 20 | - python-magic 21 | - Ruby/Rails (and the associated gems) 22 | - This project consumes output from uefi-spider and uefi-firmware-parser. 23 | 24 | Note: Rethink may need to be compiled from source with a minor change to the max array size limit. With a large number of updates the maximum array size (100k) is quickly exceeded for most of the map/reductions. 25 | 26 | Usage 27 | ----- 28 | 29 | **Subzero application** 30 | :: 31 | 32 | $ cd web && rails s 33 | 34 | **Firmware import** 35 | 36 | The importing process uses 4 steps, and assumes you have downloaded or crawled 37 | firmware update either from vendors or an enterprise: 38 | (1) Importing metadata about the updates; 39 | (2) Parsing and importing a hierarchy of components within a single firmware update; 40 | (3) Comparing product updates and vendor statistics; 41 | (4) Scheduling map/reductions to generate statistics on the firmware corpus. 42 | 43 | Step 2 is quite involved and uses multiple scripts specific to each vendor supported by Subzero. Since each vendor distributes their firmware uniquely, these scripts must preprocess and extract firmware components such as flash descriptors, UEFI Volumes, or other non-monolithic blobs for import. Once this data is isolated 44 | Subzero can use specifications and standards (and a lot of python) to parse each subcomponent and store the binary content and hierarchy of relations (a tree). 45 | 46 | *Example* 47 | :: 48 | 49 | $ cd scripts 50 | $ python ./fv_scrapy.py -t Dell -d /path/to/dell/updates/ 51 | [...] 52 | $ python ./fv_load.py --pfs -i Dell-O990-A05 \ 53 | /path/to/dell/updates/Dell-O990-A05/update.exe.hdr 54 | [...] 55 | 56 | Where this last command is repeated for each firmware update imported. 57 | There are example scripts that automate this importing. 58 | 59 | **Stats Generation** 60 | 61 | Of the previous section, the statistics generation comprises steps 3 and 4. 62 | It's simple to add additional statistics and save them into RethinkDB. 63 | There are special indexes created to assist with rapidly adding additional reductions. 64 | 65 | *Example* 66 | :: 67 | 68 | $ cd scripts 69 | $ python ./fv_dbcontrol.py load_change --vendor Dell 70 | $ python ./fv_dbcontrol.py load_meta --vendor Dell 71 | $ python ./db_mapreduce.py guid_group 72 | 73 | In this example all of Dell's firmware updates will compare their binary changes 74 | time deltas and added or removed firmware features. Then each of the content sections with every Dell firmware will run against magic and optionally have their ASCII strings parsed and stored. Finally every UEFI guid will be mapped and counted. 75 | 76 | **Features** 77 | 78 | - WebUI display of UEFI, Flash, and other firmware formats. 79 | - Graph-views of vendor update frequency, metadata, and firmware changes. 80 | - Vulnerability analysis through a variety of techniques. 81 | - Export and download of firmware components. 82 | 83 | **Supported Vendors** 84 | 85 | Subzero has been tested on BIOS/UEFI/firmware updates from the following vendors. 86 | Not every update for every product will parse, some may required a-prioi decompression 87 | or extraction from the distribution update mechanism (typically a PE). 88 | 89 | - ASRock 90 | - Dell 91 | - Gigabyte 92 | - Intel 93 | - Lenovo 94 | - HP 95 | - MSI 96 | - VMware 97 | 98 | -------------------------------------------------------------------------------- /scripts/db_mapreduce.py: -------------------------------------------------------------------------------- 1 | import argparse, json, os, sys, time 2 | import rethinkdb as r 3 | 4 | class Controller(object): 5 | def command_guid_group(self, db, args): 6 | db.table("stats").get_all("uefi_guid", index= "type").delete().run() 7 | return db.table("objects").get_all("uefi_file", index= "type").group_by("guid", r.count).with_fields("reduction", {"group": "guid"}).map(lambda guid: 8 | { 9 | "key": guid["group"]["guid"], 10 | "date": r.now().to_epoch_time(), 11 | "type": "uefi_guid", 12 | "result": guid["reduction"] 13 | } 14 | ) 15 | pass 16 | 17 | def command_object_group(self, db, args): 18 | db.table("stats").get_all("object_id", index= "type").delete().run() 19 | return db.table("objects").group_by("object_id", r.count).with_fields("reduction", {"group": "object_id"}).map(lambda guid: 20 | { 21 | "key": guid["group"]["object_id"], 22 | "date": r.now().to_epoch_time(), 23 | "type": "object_id", 24 | "result": guid["reduction"] 25 | } 26 | ) 27 | pass 28 | 29 | def command_vendor_object_sum(self, db, args): 30 | db.table("stats").get_all("vendor_object_size", index= "type").delete().run() 31 | return db.table("objects").group_by("vendor", r.sum("size")).with_fields("reduction", {"group": "vendor"}).map(lambda guid: 32 | { 33 | "key": guid["group"]["vendor"], 34 | "date": r.now().to_epoch_time(), 35 | "type": "vendor_object_size", 36 | "result": guid["reduction"] 37 | } 38 | ) 39 | 40 | def command_vendor_content_sum(self, db, args): 41 | db.table("stats").get_all("vendor_content_size", index= "type").delete().run() 42 | return db.table("content").group_by("vendor", r.sum("size")).with_fields("reduction", {"group": "vendor"}).map(lambda guid: 43 | { 44 | "key": guid["group"]["vendor"], 45 | "date": r.now().to_epoch_time(), 46 | "type": "vendor_content_size", 47 | "result": guid["reduction"] 48 | } 49 | ) 50 | 51 | def command_vendor_object_count(self, db, args): 52 | db.table("stats").get_all("vendor_object_count", index= "type").delete().run() 53 | return db.table("objects").group_by("vendor", r.count).with_fields("reduction", {"group": "vendor"}).map(lambda guid: 54 | { 55 | "key": guid["group"]["vendor"], 56 | "date": r.now().to_epoch_time(), 57 | "type": "vendor_object_count", 58 | "result": guid["reduction"] 59 | } 60 | ) 61 | 62 | def command_vendor_content_count(self, db, args): 63 | db.table("stats").get_all("vendor_content_count", index= "type").delete().run() 64 | return db.table("content").group_by("vendor", r.count).with_fields("reduction", {"group": "vendor"}).map(lambda guid: 65 | { 66 | "key": guid["group"]["vendor"], 67 | "date": r.now().to_epoch_time(), 68 | "type": "vendor_content_count", 69 | "result": guid["reduction"] 70 | } 71 | ) 72 | 73 | def command_vendor_update_count(self, db, args): 74 | db.table("stats").get_all("vendor_update_count", index= "type").delete().run() 75 | return db.table("updates").group_by("vendor", r.count).with_fields("reduction", {"group": "vendor"}).map(lambda guid: 76 | { 77 | "key": guid["group"]["vendor"], 78 | "date": r.now().to_epoch_time(), 79 | "type": "vendor_update_count", 80 | "result": guid["reduction"] 81 | } 82 | ) 83 | 84 | 85 | 86 | def main(): 87 | 88 | argparser = argparse.ArgumentParser() 89 | subparsers = argparser.add_subparsers(help='Firmware MapReduce Controls', dest='command') 90 | 91 | parser_guid_group = subparsers.add_parser("guid_group", help= "Groupby UEFI file GUIDs.") 92 | parser_object_group = subparsers.add_parser("object_group", help= "Groupby Object hashs.") 93 | 94 | parser_vendor_object_sum = subparsers.add_parser("vendor_object_sum", help= "Sum objects by vendor.") 95 | parser_vendor_content_sum = subparsers.add_parser("vendor_content_sum", help= "Sum content by vendor.") 96 | 97 | parser_vendor_object_count = subparsers.add_parser("vendor_object_count", help= "Count objects by vendor.") 98 | parser_vendor_content_count = subparsers.add_parser("vendor_content_count", help= "Count content by vendor.") 99 | 100 | parser_vendor_update_count = subparsers.add_parser("vendor_update_count", help= "Count updates by vendor.") 101 | parser_vendor_products_count = subparsers.add_parser("vendor_product_count", help= "Count products by vendor.") 102 | 103 | args = argparser.parse_args() 104 | controller = Controller() 105 | command = "command_%s" % args.command 106 | 107 | r.connect("localhost", 28015).repl() 108 | db = r.db("uefi") 109 | 110 | command_ptr = getattr(controller, command, None) 111 | if command_ptr is not None: 112 | print "Running command (%s)..." % args.command 113 | begin = time.time() 114 | db.table("stats").insert(command_ptr(db, args).limit(99999)).run() 115 | end = time.time() 116 | print "...finished (%d) seconds." % (end-begin) 117 | else: 118 | print "Cannot find command: %s" % command 119 | 120 | if __name__ == '__main__': 121 | main() -------------------------------------------------------------------------------- /scripts/db_setup.py: -------------------------------------------------------------------------------- 1 | import rethinkdb as r 2 | 3 | def db_cmd(cmd): 4 | try: 5 | cmd.run() 6 | except: 7 | pass 8 | pass 9 | 10 | common_guids = { 11 | "7ec6c2b0-3fe3-42a0-16a3-22dd0517c1e8": "PFS_DELL_UEFI_VOLUMES", 12 | "7439ed9e-70d3-4b65-339e-1963a7ad3c37": "PFS_DELL_INTEL_ME" 13 | } 14 | 15 | r.connect("localhost").repl() 16 | 17 | db_cmd(r.db_create("uefi")) 18 | uefi = r.db("uefi") 19 | 20 | #db_cmd(uefi.table_create("files")) 21 | db_cmd(uefi.table_create("content")) 22 | db_cmd(uefi.table_create("updates")) 23 | db_cmd(uefi.table_create("objects")) 24 | db_cmd(uefi.table_create("lookup")) 25 | db_cmd(uefi.table_create("stats")) 26 | 27 | db_cmd(uefi.table("lookup").index_create("guid")) 28 | for guid, name in common_guids.iteritems(): 29 | if uefi.table("lookup").get_all(guid, index= "guid").is_empty().run(): 30 | db_cmd(uefi.table("lookup").insert({"guid": guid})) 31 | db_cmd(uefi.table("lookup").get_all(guid, index= "guid").\ 32 | update({"guid_name": name})) 33 | 34 | db_cmd(uefi.table("updates").index_create("item_id")) 35 | db_cmd(uefi.table("updates").index_create("firmware_id")) 36 | db_cmd(uefi.table("updates").index_create("date")) 37 | db_cmd(uefi.table("updates").index_create("vendor")) 38 | 39 | db_cmd(uefi.table("objects").index_create("firmware_id")) 40 | db_cmd(uefi.table("objects").index_create("object_id")) 41 | db_cmd(uefi.table("objects").index_create("guid")) 42 | db_cmd(uefi.table("objects").index_create("size")) 43 | db_cmd(uefi.table("objects").index_create("vendor")) 44 | db_cmd(uefi.table("objects").index_create("type")) 45 | 46 | db_cmd(uefi.table("content").index_create("firmware_id")) 47 | db_cmd(uefi.table("content").index_create("object_id")) 48 | db_cmd(uefi.table("content").index_create("guid")) 49 | db_cmd(uefi.table("content").index_create("size")) 50 | db_cmd(uefi.table("content").index_create("vendor")) 51 | 52 | db_cmd(uefi.table("stats").index_create("key")) 53 | db_cmd(uefi.table("stats").index_create("type")) 54 | db_cmd(uefi.table("stats").index_create("type_key", 55 | lambda stat: 56 | [stat["type"], stat["key"]] 57 | )) 58 | -------------------------------------------------------------------------------- /scripts/fv_dbcontrol.py: -------------------------------------------------------------------------------- 1 | import argparse, json, os, sys, time 2 | import base64 3 | import copy 4 | import gc 5 | import subprocess 6 | 7 | import hashlib 8 | import pydeep 9 | import magic 10 | import pefile 11 | 12 | import rethinkdb as r 13 | 14 | from utils import red, blue 15 | 16 | # Testing 17 | from fv_fileoutput import file_compare 18 | 19 | def _dump_data(name, data): 20 | try: 21 | with open(name, 'wb') as fh: fh.write(data) 22 | print "Wrote: %s" % (red(name)) 23 | except Exception, e: 24 | print "Error: could not write (%s), (%s)." % (name, str(e)) 25 | 26 | def _object_compare(obj1, obj2): 27 | content1 = base64.b64decode(obj1) 28 | content2 = base64.b64decode(obj2) 29 | min_size = min(len(content1), len(content2)) 30 | max_size = max(len(content1), len(content2)) 31 | change_score = max_size - min_size 32 | for i in xrange(min_size): 33 | if content1[i] != content2[i]: 34 | change_score += 1 35 | return change_score 36 | 37 | class Controller(object): 38 | 39 | def command_list_fv(self, db, args): 40 | ids = db.table("files").pluck("firmware_id").distinct().run() 41 | for _id in ids: 42 | info = db.table("updates").filter({"firmware_id": _id["firmware_id"]}).pluck("date", "machine", "name", "version").run() 43 | print "%s:" % _id["firmware_id"], 44 | for _machine in info: 45 | print "%s, %s, %s, %s" % (_machine["date"], _machine["machine"], _machine["name"], _machine["version"]) 46 | pass 47 | 48 | def command_list_files(self, db, args): 49 | files = db.table("files").filter({"firmware_id": args.fv_id}).pluck("guid", "name", "attrs", "description").order_by(r.row["attrs"]["size"]).run() 50 | for _file in files: print "%s %s %s (%s)" % (_file["guid"], _file["attrs"]["size"], _file["name"], _file["description"]) 51 | pass 52 | 53 | def _compare_children(self, db, list1, list2, save= False): 54 | change_score = 0 55 | added_objects = [] 56 | added_objects_score = 0 57 | 58 | ### Assemble GUID pairs 59 | children1, children2 = {}, {} 60 | child_cursor = db.table("objects").get_all(*(list1 + list2)).\ 61 | pluck("id", "size", "object_id", "guid", "children", "order").\ 62 | order_by("order").order_by("size").run() 63 | 64 | has_child = False 65 | for i, child in enumerate(child_cursor): 66 | if child["id"] == "4ae16769-cef1-44ec-97d7-13d6d59fdd21": 67 | has_child = True 68 | if "guid" not in child: 69 | #print i, child["size"] 70 | child["guid"] = min(len(children1.keys()), len(children2.keys())) 71 | #print child["guid"], child["size"] 72 | #print i, child["size"], child["guid"] 73 | if child["id"] in list1: 74 | if child["guid"] not in children1.keys(): 75 | children1[child["guid"]] = [] 76 | children1[child["guid"]].append(child) 77 | if child["id"] in list2: 78 | if child["guid"] not in children2: 79 | children2[child["guid"]] = [] 80 | children2[child["guid"]].append(child) 81 | 82 | #print children1.keys() 83 | #print children2.keys() 84 | 85 | objects1, objects2 = [], [] 86 | for guid in children2.keys(): 87 | if guid not in children1: 88 | print "added guid %s" % guid 89 | ### This guid/object was added in the update 90 | added_objects += [c["object_id"] for c in children2[guid]] 91 | added_objects_score += sum([int(c["size"]) for c in children2[guid]]) 92 | ### Todo: this does not account for nested children in a new update 93 | continue 94 | for i in xrange(len(children2[guid])): 95 | if "children" in children2[guid][i] and len(children2[guid][i]["children"]) > 0: 96 | ### There are nested children, compare them individually. 97 | if len(children1[guid]) <= i or "children" not in children1[guid][i]: 98 | ### There are less grandchildren in the previous update (for this child guid) 99 | child_ids = db.table("objects").get_all(*children2[guid][i]["children"]).pluck("object_id").run() 100 | nested_change = [ 101 | int(children2[guid][i]["size"]), 102 | [child["object_id"] for child in child_ids], 103 | int(children2[guid][i]["size"]) 104 | ] 105 | else: 106 | #print red("will compare grandchildren lengths %d %d for guid %s, index %d" % ( 107 | # len(children1[guid][i]["children"]), len(children2[guid][i]["children"]), guid, i 108 | # )) 109 | nested_change = self._compare_children(db, children1[guid][i]["children"], children2[guid][i]["children"], save= save) 110 | 111 | change_score += nested_change[0] 112 | added_objects += nested_change[1] 113 | added_objects_score += nested_change[2] 114 | 115 | if save: 116 | db.table("objects").get(children2[guid][i]["id"]).update({ 117 | "load_change": { 118 | "change_score": nested_change[0], 119 | "new_files": nested_change[1], 120 | "new_files_score": nested_change[2] 121 | } 122 | }).run() 123 | 124 | continue 125 | elif len(children1[guid]) <= i: 126 | added_objects.append(children2[guid][i]["object_id"]) 127 | added_objects_score += int(children2[guid][i]["size"]) 128 | change_score += int(children2[guid][i]["size"]) 129 | else: 130 | objects1.append(children1[guid][i]) # ["object_id"] 131 | objects2.append(children2[guid][i]) # ["object_id"] 132 | 133 | ### If there are objects, compare the content 134 | content1, content2 = [], [] 135 | if len(objects1) + len(objects2) > 0: 136 | content_cursor = db.table("content").\ 137 | get_all(*([o["object_id"] for o in objects1] + [o["object_id"] for o in objects2]), 138 | index= "object_id").order_by("size").pluck("object_id", "content", "size", "children").run() 139 | 140 | for content in content_cursor: 141 | if content["object_id"] in [o["object_id"] for o in objects1]: 142 | content1.append(content) 143 | if content["object_id"] in [o["object_id"] for o in objects2]: 144 | content2.append(content) 145 | 146 | #print len(objects1), len(objects2), len(content1), len(content2) 147 | ids1, ids2 = {o["object_id"]: o["id"] for o in objects1}, {o["object_id"]: o["id"] for o in objects2} 148 | for i in xrange(len(content2)): 149 | if len(content1) <= i: 150 | content_change_score = int(content2[i]["size"]) 151 | content_added_objects = [content2[i]["object_id"]] 152 | content_added_objects_score = int(content2[i]["size"]) 153 | else: 154 | change = _object_compare(content1[i]["content"], content2[i]["content"]) 155 | content_added_objects = [] 156 | content_added_objects_score = 0 157 | content_change_score = change 158 | 159 | change_score += content_change_score 160 | added_objects += content_added_objects 161 | added_objects_score += content_added_objects_score 162 | 163 | if save and ("children" not in content2[i] or len(content2[i]["children"]) == 0): 164 | db.table("objects").get(ids2[content2[i]["object_id"]]).update({ 165 | "load_change": { 166 | "change_score": content_change_score, 167 | "new_files": content_added_objects, 168 | "new_files_score": content_added_objects_score 169 | } 170 | }).run() 171 | 172 | #print guid, change_score, len(added_objects) 173 | return (change_score, added_objects, added_objects_score) 174 | pass 175 | 176 | def _compare_firmware(self, db, firmware1, firmware2, save= False): 177 | ### Query firmware objects 178 | if len(firmware1[2]) == 0 or len(firmware2[2]) == 0: 179 | print "Cannot compare versions (%d -> %d) without loaded firmware objects." % (firmware1[0], firmware2[0]) 180 | return 181 | 182 | ### This could be bad without guided-objects 183 | if len(firmware1[2]) != len(firmware2[2]): 184 | print "Firmware object count has changed between versions (%s -> %s)." % (firmware1[0], firmware2[0]) 185 | 186 | change = self._compare_children(db, firmware1[2], firmware2[2], save= True) 187 | 188 | ### Save changes to update 189 | if save: 190 | db.table("updates").get_all(firmware2[1], index= "firmware_id").update({ 191 | "load_change": { 192 | "change_score": change[0], 193 | "new_files": change[1], 194 | "new_files_score": change[2], 195 | "delta": firmware2[4] - firmware1[4] 196 | } 197 | }).run() 198 | db.table("objects").get_all(firmware2[1], index= "object_id").update({ 199 | "load_change": { 200 | "change_score": change[0], 201 | "new_files": change[1], 202 | "new_files_score": change[2], 203 | "delta": firmware2[4] - firmware1[4] 204 | } 205 | }).run() 206 | print "Firmware %s change: %s" % (firmware2[1], str(change)) 207 | pass 208 | 209 | def _load_meta(self, db, _object): 210 | content = base64.b64decode(_object["content"]) 211 | entry = { 212 | "magic": magic.from_buffer(content), 213 | "ssdeep": pydeep.hash_buf(content), 214 | "md5": hashlib.md5(content).hexdigest(), 215 | "sha1": hashlib.sha1(content).hexdigest(), 216 | "sha256": hashlib.sha256(content).hexdigest() 217 | } 218 | 219 | if entry["magic"] == "MS-DOS executable": 220 | ### This is a weak application of magic 221 | try: 222 | pe_data = self._get_pe(content) 223 | for k, v in pe_data.iteritems(): entry[k] = v 224 | except Exception, e: print e; pass 225 | pass 226 | #entry_copy = copy.deepcopy(entry) 227 | #del entry 228 | #del content 229 | #gc.collect() 230 | 231 | db.table("content").get(_object["id"]).update({"load_meta": entry}).run() 232 | print "Loaded meta for object (%s) %s." % (_object["firmware_id"], _object["id"]) 233 | pass 234 | 235 | def _get_pe(self, content): 236 | def section_name(s): return s.Name.replace("\x00", "").strip() 237 | pe_entry = {} 238 | pe = pefile.PE(data= content) 239 | pe_entry["machine_type"] = pe.FILE_HEADER.Machine 240 | pe_entry["compile_time"] = pe.FILE_HEADER.TimeDateStamp 241 | pe_entry["sections"] = [section_name(s) for s in pe.sections if len(section_name(s)) > 0] 242 | pe_entry["linker"] = "%d,%d" % (pe.OPTIONAL_HEADER.MajorLinkerVersion, pe.OPTIONAL_HEADER.MinorLinkerVersion) 243 | pe_entry["os_version"] = "%d,%d" % (pe.OPTIONAL_HEADER.MajorOperatingSystemVersion, pe.OPTIONAL_HEADER.MinorOperatingSystemVersion) 244 | pe_entry["image_version"] = "%d,%d" % (pe.OPTIONAL_HEADER.MajorImageVersion, pe.OPTIONAL_HEADER.MinorImageVersion) 245 | pe_entry["subsystem"] = pe.OPTIONAL_HEADER.Subsystem 246 | pe_entry["subsystem_version"] = "%d,%d" % (pe.OPTIONAL_HEADER.MajorSubsystemVersion, pe.OPTIONAL_HEADER.MinorSubsystemVersion) 247 | del pe 248 | 249 | return pe_entry 250 | pass 251 | 252 | def _load_children(self, db, children): 253 | child_objects = db.table("objects").get_all(*children).pluck("id", "object_id", "load_meta", "children").run() 254 | for child in child_objects: 255 | if "children" in child and len(child["children"]) > 0: 256 | self._load_children(db, child["children"]) 257 | continue 258 | 259 | contents = db.table("content").get_all(child["object_id"], index= "object_id").\ 260 | filter(not r.row.contains("load_meta")).run() 261 | num = 0 262 | for content in contents: 263 | print "%d/??" % (num), 264 | num += 1 265 | self._load_meta(db, content) 266 | break 267 | del contents 268 | pass 269 | 270 | def _get_product_updates(self, db, product): 271 | updates = db.table("updates").order_by("date").filter(lambda update: 272 | update["products"].contains(product) & update.has_fields("firmware_id") 273 | ).map(r.row.merge({ "object_id": r.row["firmware_id"] })).eq_join("object_id", 274 | db.table("objects"), index= "object_id" 275 | ).zip().run() 276 | return updates 277 | pass 278 | 279 | def command_load_meta(self, db, args): 280 | if args.vendor: 281 | vendor_products = [] 282 | products = db.table("updates").order_by("date").filter(lambda update: 283 | update["vendor"].eq(args.vendor) 284 | ).pluck("products").run() 285 | for product_list in products: 286 | for product in product_list["products"]: 287 | if product not in vendor_products: 288 | vendor_products.append(product) 289 | products = vendor_products 290 | ### In an effort to avoid memory exhaustion 291 | for product in products: 292 | print "Recalling load_meta for product %s" % product 293 | subprocess.call("python %s load_meta --product \"%s\"" % (sys.argv[0], product), shell=True) 294 | return 295 | 296 | products = [args.product] 297 | 298 | for product in products: 299 | updates = self._get_product_updates(db, product) 300 | for update in updates: 301 | if "children" not in update or len(update["children"]) == 0: 302 | continue 303 | self._load_children(db, update["children"]) 304 | 305 | def command_load_change(self, db, args): 306 | if args.vendor: 307 | vendor_products = [] 308 | products = db.table("updates").order_by("date").filter(lambda update: 309 | update["vendor"].eq(args.vendor) 310 | ).pluck("products").run() 311 | for product_list in products: 312 | for product in product_list["products"]: 313 | if product not in vendor_products: 314 | vendor_products.append(product) 315 | products = vendor_products 316 | else: 317 | products = [args.product] 318 | 319 | for product in products: 320 | updates = self._get_product_updates(db, product) 321 | firmware_objects = [] 322 | 323 | for update in updates: 324 | firmware_objects.append((update["version"], update["firmware_id"], update["children"], "load_change" in update, update["date"])) 325 | 326 | for i in xrange(len(firmware_objects)-1): 327 | if not args.force and firmware_objects[i+1][3]: 328 | print "Skipping change comparison (%s -> %s), already completed." % (firmware_objects[i][0], firmware_objects[i+1][0]) 329 | continue 330 | self._compare_firmware(db, firmware_objects[i], firmware_objects[i+1], True) 331 | 332 | def _add_lookup(self, db, guid, name, value, force= False): 333 | if db.table("objects").get_all(guid, index= "guid").is_empty().run(): 334 | if force is False: 335 | print "Cannot find any files matching GUID (%s), please use the force option." % guid 336 | return 337 | pass 338 | 339 | if db.table("lookup").get_all(guid, index= "guid").is_empty().run(): 340 | db.table("lookup").insert({ 341 | "guid": guid, 342 | "%s" % name: value 343 | }).run() 344 | print "Added lookup for GUID (%s), with (%s) = (%s)." % (guid, name, value) 345 | else: 346 | db.table("lookup").get_all(guid, index= "guid").update({"%s" % name: value}).run() 347 | print "Updated lookup for GUID (%s), set (%s) = (%s)." % (guid, name, value) 348 | pass 349 | 350 | def command_add_lookup(self, db, args): 351 | self._add_lookup(db, args.guid, args.name, args.value, force= args.force) 352 | 353 | def command_load_guids(self, db, args): 354 | from uefi_firmware.guids import GUID_TABLES 355 | from uefi_firmware.utils import rfguid 356 | for table in GUID_TABLES: 357 | for name, r_guid in table.iteritems(): 358 | #print name, rfguid(r_guid) 359 | self._add_lookup(db, rfguid(r_guid), "guid_name", name, True) 360 | pass 361 | 362 | def parse_extra (parser, namespace): 363 | namespaces = [] 364 | extra = namespace.extra 365 | while extra: 366 | n = parser.parse_args(extra) 367 | extra = n.extra 368 | namespaces.append(n) 369 | 370 | return namespaces 371 | 372 | def main(): 373 | 374 | argparser = argparse.ArgumentParser() 375 | subparsers = argparser.add_subparsers(help='Firmware Controls', dest='command') 376 | 377 | parser_list_fv = subparsers.add_parser("list_fv", help= "List all FV IDs which have files in the DB") 378 | 379 | parser_list_files = subparsers.add_parser("list_files", help= "List all files GUIDs for a given FV ID") 380 | parser_list_files.add_argument("fv_id", help="Firmware ID.") 381 | 382 | '''Simple loading/parsing commands.''' 383 | parser_load_change = subparsers.add_parser("load_change", help= "Load change scores for objects and firmware.") 384 | parser_load_change.add_argument("-f", "--force", action="store_true", default= False, help="Force recalculation.") 385 | group = parser_load_change.add_mutually_exclusive_group(required= True) 386 | group.add_argument("--product", help="Product to load.") 387 | group.add_argument("--vendor", help="Vendor to load.") 388 | 389 | parser_load_meta = subparsers.add_parser("load_meta", help= "Extract meta, hashes for a machine's firmware.") 390 | parser_load_meta.add_argument("-f", "--force", action="store_true", default= False, help="Force recalculation.") 391 | group = parser_load_meta.add_mutually_exclusive_group(required= True) 392 | group.add_argument("--product", help="Product to load.") 393 | group.add_argument("--vendor", help="Vendor to load.") 394 | 395 | parser_add_lookup = subparsers.add_parser("add_lookup", help= "Add metadata about a file GUID.") 396 | parser_add_lookup.add_argument("-f", "--force", default=False, action= "store_true", help= "Force the lookup insert.") 397 | parser_add_lookup.add_argument("guid", help= "File GUID") 398 | parser_add_lookup.add_argument("name", help="Key to add to the GUID.") 399 | parser_add_lookup.add_argument("value", help= "Value") 400 | 401 | parser_load_guids = subparsers.add_parser("load_guids", help= "Read in EFI GUID definitions.") 402 | parser_load_guids.add_argument("-f", "--force", default= False, action= "store_true", help= "Override existing DB GUID definitions.") 403 | 404 | args = argparser.parse_args() 405 | 406 | controller = Controller() 407 | command = "command_%s" % args.command 408 | 409 | r.connect("localhost", 28015).repl() 410 | db = r.db("uefi") 411 | 412 | #objects_table = db.table("objects") 413 | #updates_table = db.table("updates") 414 | #content_table = db.table("content") 415 | #lookup_table = db.table("lookup") 416 | #stats_table = db.table("stats") 417 | 418 | command_ptr = getattr(controller, command, None) 419 | if command_ptr is not None: 420 | command_ptr(db, args) 421 | 422 | 423 | if __name__ == '__main__': 424 | main() 425 | 426 | -------------------------------------------------------------------------------- /scripts/fv_fileoutput.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import hashlib 3 | import base64 4 | import pefile 5 | import os 6 | 7 | from uefi_firmware import * 8 | 9 | def _brute_search(data): 10 | volumes = search_firmware_volumes(data) 11 | files = {} 12 | 13 | for index in volumes: 14 | volume_files = _parse_firmware_volume(data[index-40:], name=index) 15 | for key, value in volume_files.iteritems(): 16 | files[key] = value 17 | return files 18 | pass 19 | 20 | def get_name(_object): 21 | if len(_object["label"]) > 0: return _object["label"] 22 | if "objects" not in _object: return None 23 | #print {key:value for key,value in _object.iteritems() if key != "objects"} 24 | for _sub_object in _object["objects"]: 25 | #print {key:value for key,value in _sub_object.iteritems() if key != "objects"} 26 | if len(_sub_object["label"]) > 0: return _sub_object["label"] 27 | for _sub_object in _object["objects"]: 28 | _name = get_name(_sub_object) 29 | if _name is not None: return _name 30 | return None 31 | 32 | def show_file(_object): 33 | return {key:value for key,value in _object.iteritems() if key not in ["objects", "content"]} 34 | 35 | def show_files(objects): 36 | for _object in objects: 37 | if _object["type"] == "FirmwareFile": 38 | print _object["guid"], get_name(_object), _object["attrs"] 39 | else: 40 | print _object["type"], {key:value for key,value in _object.iteritems() if key != "objects"} 41 | if "objects" in _object: 42 | show_files(_object["objects"]) 43 | 44 | def get_files(objects): 45 | files = {} 46 | for _object in objects: 47 | if _object["type"] == "FirmwareFile": 48 | files[_object["guid"]] = {"name": get_name(_object)} 49 | for key, value in _object["attrs"].iteritems(): 50 | files[_object["guid"]][key] = value 51 | files[_object["guid"]]["objects"] = _object["objects"] 52 | files[_object["guid"]]["content"] = _object["content"] 53 | if "objects" in _object: 54 | for key, value in get_files(_object["objects"]).iteritems(): 55 | files[key] = value 56 | return files 57 | 58 | def _strings(_object, min=10): 59 | import string 60 | result = "" 61 | for c in _object["content"]: 62 | if c == 0x00: continue 63 | if c in string.printable: 64 | result += c 65 | continue 66 | if len(result.strip()) >= min: 67 | yield result 68 | result = "" 69 | 70 | def _find_pe(_object): 71 | if "objects" in _object: 72 | for _sub_object in _object["objects"]: 73 | if _sub_object["type"] not in ["FirmwareFileSystemSection", "CompressedSection"]: 74 | continue 75 | sub_content = _find_pe(_sub_object) 76 | if sub_content is not None: return sub_content 77 | if "attrs" not in _object: return None 78 | if "type_name" not in _object["attrs"]: return None 79 | if _object["attrs"]["type_name"] in ["PE32 image"]: 80 | return _object 81 | return None 82 | 83 | def file_compare(key, file1, file2): 84 | 85 | md5_1 = hashlib.md5(file1["content"]).hexdigest() 86 | md5_2 = hashlib.md5(file2["content"]).hexdigest() 87 | 88 | if md5_1 == md5_2: return 0 89 | 90 | #if md5_1 != md5_2: 91 | # print "%s content is different: %s %s" % (key, md5_1, md5_2) 92 | # print " ", show_file(value) 93 | # print " ", show_file(files_2[key]) 94 | pe_object1 = _find_pe(file1) 95 | pe_object2 = _find_pe(file2) 96 | 97 | 98 | if pe_object1 is None or pe_object2 is None: 99 | if file1["size"] != file2["size"]: 100 | print "%s (%s) sizes are different (no PE): %d %d" % (key, file1["name"], file1["size"], file2["size"]) 101 | return True 102 | print "%s (%s) md5s are different (no PE): %d %d" % (key, file1["name"], file1["size"], file2["size"]) 103 | return True 104 | 105 | try: 106 | pe_object1["content"] = base64.b64decode(pe_object1["content"]) 107 | pe_object2["content"] = base64.b64decode(pe_object2["content"]) 108 | except Exception, e: pass 109 | 110 | try: 111 | pe_1 = pefile.PE(data= pe_object1["content"]) 112 | pe_1_sections = {} 113 | for section in pe_1.sections: 114 | pe_1_sections[section.Name.replace("\x00", "")] = section 115 | 116 | pe_2 = pefile.PE(data= pe_object2["content"]) 117 | pe_2_sections = {} 118 | for section in pe_2.sections: 119 | pe_2_sections[section.Name.replace("\x00", "")] = section 120 | except Exception, e: 121 | print "%s does not contain a DOS header?" % key 122 | return True 123 | 124 | #for section_name in pe_1_sections: 125 | #print (section.Name, hex(section.VirtualAddress), hex(section.Misc_VirtualSize), section.SizeOfRawData) 126 | 127 | #if section_name not in pe_2_sections: 128 | # print "%s does not contain section (%s)." % (key, section_name) 129 | # return True 130 | text_1 = pe_1_sections[".text"].get_data(0) 131 | text_2 = pe_2_sections[".text"].get_data(0) 132 | #pe_2.sections[0].get_data(0) 133 | 134 | if len(text_1) != len(text_2): 135 | print "%s (%s) .text sizes are different: %d %d" % (key, file1["name"], file1["size"], file2["size"]) 136 | return True 137 | 138 | if text_1 != text_2: 139 | byte_mismatch_count = 0 140 | for i in xrange(len(text_1)): 141 | if text_1[i] != text_2[i]: byte_mismatch_count += 1 142 | print "%s (%s) .text section mis-match (%d bytes, %d total)" % (key, file1["name"], byte_mismatch_count, len(text_1)) 143 | #for l in _strings(pe_object1): print l 144 | return True 145 | 146 | #print "%s (%s) some other section is different: %d %d" % (key, file1["name"], file1["size"], file2["size"]) 147 | #return True 148 | 149 | #print section.Name, hex(section.VirtualAddress), hex(section.Misc_VirtualSize), section.SizeOfRawData 150 | #sys.exit(1) 151 | 152 | #pe_2 = pefile.PE(data= pe_object2["content"]) 153 | 154 | pass 155 | 156 | def _parse_firmware_volume(data, name="volume"): 157 | firmware_volume = FirmwareVolume(data, name) 158 | firmware_volume.process() 159 | 160 | objects = firmware_volume.iterate_objects(True) 161 | return get_files(objects) 162 | 163 | 164 | if __name__ == "__main__": 165 | parser = argparse.ArgumentParser() 166 | parser.add_argument("--save-pe", help='Save a PE by canonical name') 167 | parser.add_argument("--save-content", help= "Save entire content") 168 | parser.add_argument("file", help="The file to work on") 169 | parser.add_argument("file2", help="File to compare") 170 | args = parser.parse_args() 171 | 172 | try: 173 | with open(args.file, 'rb') as fh: input_data_1 = fh.read() 174 | except Exception, e: 175 | print "Error: Cannot read file (%s) (%s)." % (args.file, str(e)) 176 | sys.exit(1) 177 | 178 | try: 179 | with open(args.file2, 'rb') as fh: input_data_2 = fh.read() 180 | except Exception, e: 181 | print "Error: Cannot read file (%s) (%s)." % (args.file2, str(e)) 182 | sys.exit(1) 183 | 184 | files_1 = _brute_search(input_data_1) 185 | files_2 = _brute_search(input_data_2) 186 | 187 | for key, value in files_1.iteritems(): 188 | if key not in files_2: 189 | print "%s not in %s" % (key, os.path.basename(args.file)), show_file(value) 190 | for key, value in files_2.iteritems(): 191 | if key not in files_1: 192 | print "%s not in %s" % (key, os.path.basename(args.file2)), show_file(value) 193 | 194 | different_count = 0 195 | for key, value in files_1.iteritems(): 196 | if key not in files_2: continue 197 | if file_compare(key, value, files_2[key]): 198 | different_count += 1 199 | print "%d files, %d different files" % (len(files_1), different_count) 200 | 201 | if args.save_pe is not None: 202 | pe_1 = _find_pe(files_1[args.save_pe]) 203 | pe_2 = _find_pe(files_2[args.save_pe]) 204 | 205 | with open('%s_1' % args.save_pe, 'w') as fh: fh.write(pe_1["content"]) 206 | with open('%s_2' % args.save_pe, 'w') as fh: fh.write(pe_2["content"]) 207 | 208 | if args.save_content is not None: 209 | with open("%s_1" % args.save_content, 'w') as fh: fh.write(files_1[args.save_content]["content"]) 210 | with open("%s_2" % args.save_content, 'w') as fh: fh.write(files_2[args.save_content]["content"]) 211 | 212 | 213 | -------------------------------------------------------------------------------- /scripts/fv_load.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import hashlib 3 | import pefile 4 | import os 5 | import base64 6 | import copy 7 | import sys 8 | 9 | import rethinkdb as r 10 | 11 | from uefi_firmware import * 12 | from uefi_firmware.pfs import PFSFile, PFS_GUIDS 13 | from uefi_firmware.utils import search_firmware_volumes 14 | from uefi_firmware.flash import FlashDescriptor 15 | 16 | def get_file_name(_object): 17 | if len(_object["label"]) > 0: 18 | return _object["label"] 19 | if not "objects" in _object: 20 | return None 21 | for _sub_object in _object["objects"]: 22 | if not isinstance(_sub_object["_self"], uefi.EfiSection): 23 | continue 24 | name = get_file_name(_sub_object) 25 | if name is not None: 26 | return name 27 | return None 28 | 29 | def get_file_description(_object): 30 | if isinstance(_object["_self"], uefi.FreeformGuidSection): 31 | return _object["label"] 32 | if "objects" not in _object: 33 | return None 34 | for _sub_object in _object["objects"]: 35 | if not isinstance(_sub_object["_self"], uefi.EfiSection): 36 | continue 37 | description = get_file_description(_sub_object) 38 | if description is not None: 39 | return description 40 | return None 41 | 42 | def get_files(objects, measured= False): 43 | ### They may be duplicate file-GUIDs in a volume/capsule/etc. 44 | #files = {} 45 | files = [] 46 | for _object in objects: 47 | if _object["type"] == "FirmwareFile": 48 | #files[_object["guid"]] = { 49 | files.append({ 50 | #"name": get_file_name(_object), 51 | #"description": get_file_description(_object), 52 | "attrs": dict(_object["attrs"].items() + { 53 | "name": get_file_name(_object), 54 | "description": get_file_description(_object), 55 | "measured": measured 56 | }.items() 57 | ), 58 | "objects": _object["objects"], 59 | "content": _object["content"], 60 | "guid": _object["guid"], 61 | }) 62 | if "objects" in _object: 63 | if "attrs" in _object["attrs"]: 64 | measured = (_object["attrs"]["attrs"] == uefi.GuidDefinedSection.ATTR_AUTH_STATUS_VALID) 65 | for uefi_file in get_files(_object["objects"], measured= measured): 66 | files.append(uefi_file) 67 | return files 68 | 69 | def _strings(_object, min=10): 70 | import string 71 | result = "" 72 | for c in _object["content"]: 73 | if c == 0x00: continue 74 | if c in string.printable: 75 | result += c 76 | continue 77 | if len(result.strip()) >= min: 78 | yield result 79 | result = "" 80 | 81 | def _load_pe(_object): 82 | _types = {267: "x86", 523: "x86_64"} 83 | 84 | pe_object = _find_pe(_object) 85 | if pe_object is None: 86 | return None 87 | 88 | pe_info = {} 89 | 90 | pe_info["sections"] = {} 91 | pe = pefile.PE(data= pe_object["content"]) 92 | 93 | pe_info["machine_type"] = pe.FILE_HEADER.Machine 94 | pe_info["compile_time"] = pe.FILE_HEADER.TimeDateStamp 95 | pe_info["subsystem"] = pe.OPTIONAL_HEADER.Subsystem 96 | 97 | for section in pe.sections: 98 | #pe_1_sections[section.Name.replace("\x00", "")] = section 99 | pe_info["sections"][section.Name.replace("\x00", "")] = base64.b64encode(section.get_data(0)) 100 | pe_info["sections"][section.Name.replace("\x00", "") + "_md5"] = hashlib.md5(section.get_data(0)).hexdigest() 101 | 102 | def _object_entry(_object): 103 | #return {key: value for key, value in _object.iteritems() if key in ["guid", "type", "attrs", "object_id", "chunks", "other"]} 104 | #print _object["attrs"] 105 | entry = {k: v for k, v in _object.iteritems() if k in ["guid", "type", "attrs", "other"]} 106 | return entry 107 | 108 | def store_content(firmware_id, object_id, content, content_type= "object"): 109 | if not content_table.get_all(object_id, index="object_id").is_empty().run(): 110 | '''If the content entry already exists for this object_id (hash), skip inserting content.''' 111 | print "Skipping object content (%s) (%s), already exists." % (firmware_id, object_id) 112 | return 113 | if args.test: return 114 | content_table.insert({ 115 | "firmware_id": firmware_id, 116 | "object_id": object_id, 117 | "type": content_type, 118 | "size": len(content), 119 | "content": base64.b64encode(content) 120 | }).run() 121 | 122 | def store_object(firmware_id, _object, object_type= "uefi_object"): 123 | if "_self" in _object: 124 | ### If this is an EFI object, it must be a basic object (section object) 125 | if isinstance(_object["_self"], uefi.FirmwareObject) and not isinstance(_object["_self"], uefi.EfiSection): 126 | if not isinstance(_object["_self"], uefi.BaseObject): 127 | print "Object type (%s), cannot be stored." % _object["_self"] 128 | return [] 129 | 130 | '''Store base objects only.''' 131 | object_id = get_firmware_id(_object["content"]) 132 | if "objects" not in _object or len(_object["objects"]) == 0: 133 | entry = _object_entry(_object) 134 | if "guid" in entry and len(entry["guid"]) == 0: 135 | del entry["guid"] 136 | entry["firmware_id"] = firmware_id 137 | entry["object_id"] = object_id 138 | 139 | entry["size"] = len(_object["content"]) 140 | entry["type"] = object_type 141 | #entry["content"] = base64.b64encode(_object["content"]) 142 | 143 | ### Store object content 144 | store_content(firmware_id, object_id, _object["content"]) 145 | 146 | ### Store this entry 147 | if args.test: return [] 148 | keys = get_result_keys(objects_table.insert(entry).run()) 149 | return keys 150 | 151 | children = [] 152 | for _sub_object in _object["objects"]: 153 | key = store_object(firmware_id, _sub_object) 154 | if key is not None: 155 | children += key 156 | return children 157 | 158 | def store_file(firmware_id, uefi_file): 159 | entry = _object_entry(uefi_file) 160 | 161 | ### Used to store the file content 162 | file_id = get_firmware_id(uefi_file["content"]) 163 | ### Do not store file content (yet), may be too much data 164 | 165 | children = [] 166 | for _object in uefi_file["objects"]: 167 | children += store_object(firmware_id, _object) 168 | #print children 169 | 170 | if len(children) == 0: 171 | print "Storing a base UEFI file/object for GUID (%s)." % uefi_file["guid"] 172 | entry["no_children"] = True 173 | store_content(firmware_id, file_id, uefi_file["content"]) 174 | #if object_keys is not None: 175 | #children += object_keys 176 | #return object_keys 177 | 178 | entry["children"] = children 179 | entry["firmware_id"] = firmware_id 180 | entry["object_id"] = file_id 181 | 182 | entry["size"] = len(uefi_file["content"]) 183 | entry["type"] = "uefi_file" 184 | 185 | ### Additional (and optional) UEFI file-only attributes 186 | entry["attrs"] = uefi_file["attrs"] 187 | #entry["attrs"]["name"] = uefi_file["name"] 188 | #entry["attrs"]["description"] = uefi_file["description"] 189 | if args.test: return [] 190 | keys = get_result_keys(objects_table.insert(entry).run()) 191 | print "Stored UEFI file (%s) %s." % (firmware_id, uefi_file["guid"]) 192 | return keys, uefi_file["attrs"]["measured"] 193 | 194 | def load_uefi_volume(firmware_id, data, guid= None, order=None, generate_object_id= False): 195 | object_id = firmware_id #if object_id is None else object_id 196 | firmware_volume = uefi.FirmwareVolume(data) 197 | if not firmware_volume.valid_header: 198 | print "This is not a valid UEFI firmware volume (%s)." % object_id 199 | return None 200 | if not firmware_volume.process(): 201 | print "The UEFI firmware volume (%s) did not parse correctly." % object_id 202 | return None 203 | 204 | if generate_object_id: 205 | object_id = get_firmware_id(data[:firmware_volume.size]) 206 | 207 | if not args.force and not objects_table.get_all(object_id, index="object_id").is_empty().run(): 208 | print "Firmware volume object (%s) exists." % object_id 209 | if args.test: return [] 210 | primary = objects_table.get_all(object_id, index="object_id").limit(1).pluck("id").coerce_to('array').run()[0]["id"] 211 | return [primary] 212 | 213 | ### Store the files 214 | objects = firmware_volume.iterate_objects(True) 215 | files = get_files(objects) 216 | 217 | ### Store the volume object information 218 | child_ids = [] 219 | volume_measured = False 220 | for uefi_file in files: 221 | file_ids, file_measured = store_file(firmware_id, uefi_file) 222 | child_ids += file_ids 223 | volume_measured = volume_measured or file_measured 224 | 225 | entry = { 226 | "firmware_id": firmware_id, 227 | "object_id": object_id, 228 | "children": child_ids, 229 | "type": "uefi_volume", 230 | "measured": volume_measured, 231 | "size": len(data) 232 | ### Todo: store volume-specific attributes 233 | } 234 | 235 | if guid is not None: entry["guid"] = guid 236 | if order is not None: entry["order"] = order 237 | 238 | if args.test: return [] 239 | return get_result_keys(objects_table.insert(entry).run()) 240 | pass 241 | 242 | def load_uefi_capsule(firmware_id, data, guid=None, order=None, object_id= None): 243 | object_id = firmware_id if object_id is None else object_id 244 | capsule = uefi.FirmwareCapsule(data) 245 | if not capsule.valid_header: 246 | print "This is not a valid UEFI firmware capsule (%s)." % object_id 247 | #sys.exit(1) 248 | return None 249 | capsule.process() 250 | #if not capsule.process(): 251 | # return None 252 | 253 | ### Create the parent object 254 | if not args.force and not objects_table.get_all(object_id, index="object_id").is_empty().run(): 255 | print "Firmware capsule object (%s) exists." % object_id 256 | if args.test: return [] 257 | primary = objects_table.get_all(object_id, index="object_id").limit(1).pluck("id").coerce_to('array').run()[0]["id"] 258 | return [primary] 259 | 260 | ### Only handle capsule's filed with firmware volumes? 261 | volume = capsule.capsule_body 262 | objects = volume.iterate_objects(True) 263 | files = get_files(objects) 264 | 265 | if args.test: return 266 | 267 | ### Store the volume object information 268 | child_ids = [] 269 | capsule_measured = False 270 | for uefi_file in files: 271 | file_ids, file_measured = store_file(firmware_id, uefi_file) 272 | child_ids += file_ids 273 | capsule_measured = capsule_measured or file_measured 274 | 275 | entry = { 276 | "firmware_id": firmware_id, 277 | "object_id": object_id, 278 | "children": child_ids, 279 | ### Store size, type, attrs 280 | "type": "uefi_capsule", 281 | "measured": capsule_measured, 282 | "size": len(data) 283 | ### Todo: store capsule-specific attributes 284 | } 285 | 286 | if guid is not None: entry["guid"] = guid 287 | if order is not None: entry["order"] = order 288 | 289 | ### Not storing capsule content (yet), may be too much data. 290 | #return [object_id] 291 | if args.test: return [] 292 | return get_result_keys(objects_table.insert(entry).run()) 293 | pass 294 | 295 | def load_flash(firmware_id, data, save= True): 296 | flash = FlashDescriptor(data) 297 | if not flash.valid_header: 298 | print "This is not a valid Flash Descriptor." 299 | sys.exit(1) 300 | 301 | flash.process() 302 | 303 | if not args.force and not objects_table.get_all(firmware_id, index="object_id").is_empty().run(): 304 | print "Flash object (%s) exists." % firmware_id 305 | return None 306 | 307 | child_ids = [] 308 | for region in flash.regions: 309 | region_info = region.info(include_content= True) 310 | region_id = get_firmware_id(region_info["content"]) 311 | 312 | ### Check if this region exists within this firmware_id 313 | if not objects_table.get_all(firmware_id, index="firmware_id").filter({"object_id": region_id}).is_empty().run(): 314 | print "Skipping Region (%s) region_id (%s), object exists." % (firmware_id, region_id) 315 | if args.test: continue 316 | primary = objects_table.get_all(firmware_id, index="firmware_id").filter({"object_id": region_id}).\ 317 | limit(1).pluck("id").coerce_to('array').run()[0]["id"] 318 | child_ids.append(primary) 319 | continue 320 | 321 | object_keys = [] 322 | #region_info["object_id"] = region_id 323 | if region_info["label"] == "bios": 324 | for i, volume in enumerate(region.sections): 325 | print i 326 | #volume_info = volume.info(include_content= True) 327 | volume_keys = load_uefi_volume(firmware_id, volume._data, 328 | order= i, generate_object_id= True) 329 | if volume_keys is not None: 330 | object_keys += volume_keys 331 | else: 332 | object_keys = store_object(firmware_id, region_info, object_type= "flash_region") 333 | 334 | if object_keys is not None: 335 | child_ids += object_keys 336 | 337 | print "Stored Region (%s) region_id %s." % (firmware_id, region_id) 338 | 339 | if not save: 340 | ### This is a child flash descriptor, do not save an object entry. 341 | return child_ids 342 | 343 | entry = { 344 | "firmware_id": firmware_id, 345 | "object_id": firmware_id, 346 | "children": child_ids, 347 | "type": "flash_descriptor", 348 | "size": len(data) 349 | } 350 | 351 | if args.test: return [] 352 | return get_result_keys(objects_table.insert(entry).run()) 353 | 354 | 355 | def load_pfs(firmware_id, data): 356 | pfs = PFSFile(data) 357 | if not pfs.check_header(): 358 | print "This is not a valid DELL PFS update." 359 | sys.exit(1) 360 | 361 | pfs.process() 362 | 363 | ### Store PFS info 364 | if not args.force and not objects_table.get_all(firmware_id, index="object_id").is_empty().run(): 365 | print "PFS object (%s) exists." % firmware_id 366 | return 367 | 368 | child_ids = [] 369 | for section in pfs.objects: 370 | section_info = section.info(include_content= True) 371 | 372 | if not objects_table.get_all(firmware_id, index="firmware_id").filter({"guid": section_info["guid"]}).is_empty().run(): 373 | print "Skipping PFS (%s) GUID %s, object exists." % (firmware_id, section_info["guid"]) 374 | if args.test: continue 375 | primary = objects_table.get_all(firmware_id, index="firmware_id").filter({"guid": section_info["guid"]}).\ 376 | limit(1).pluck("id").coerce_to('array').run()[0]["id"] 377 | child_ids.append(primary) 378 | continue 379 | 380 | section_id = get_firmware_id(section_info["content"]) 381 | section_info["object_id"] = section_id 382 | 383 | #for chunk in section_info["chunks"]: 384 | # store_content(firmware_id, section_id, chunk, content_type= "pfs_chunk") 385 | 386 | object_keys = [] 387 | if section_info["guid"] == PFS_GUIDS["FIRMWARE_VOLUMES"]: 388 | ### Brute search for volumes here 389 | volumes = search_firmware_volumes(section_info["content"]) 390 | print volumes 391 | for i, index in enumerate(volumes): 392 | volume_keys = load_uefi_volume(firmware_id, section_info["content"][index-40:], 393 | guid= section_info["guid"], order= i, generate_object_id= True) 394 | if volume_keys is not None: 395 | object_keys += volume_keys 396 | else: 397 | object_keys = store_object(firmware_id, section_info, object_type= "pfs_section") 398 | 399 | if object_keys is not None: 400 | child_ids += object_keys 401 | 402 | print "Stored PFS section (%s) GUID %s." % (firmware_id, section_info["guid"]) 403 | 404 | if args.test: return 405 | objects_table.insert({ 406 | "firmware_id": firmware_id, 407 | "object_id": firmware_id, 408 | "children": child_ids, 409 | "type": "dell_pfs", 410 | "size": len(data) 411 | }).run() 412 | pass 413 | 414 | def load_logo(firmware_id, data): 415 | logo_data = data[:0x10000] 416 | data = data[0x10000:] 417 | 418 | logo_object = {"content": logo_data, "type": "hp_logo_data"} 419 | object_keys = store_object(firmware_id, logo_object, object_type= "hp_logo_data") 420 | 421 | #object_keys = [] 422 | volumes = search_firmware_volumes(data) 423 | for i, index in enumerate(volumes): 424 | volume_keys = load_uefi_volume(firmware_id, data[index-40:], 425 | order= i, generate_object_id= True) 426 | if volume_keys is not None: 427 | print "Stored UEFI volume from index 0x%x" % (index-40) 428 | object_keys += volume_keys 429 | 430 | print "Stored HP Logo (%s)." % (firmware_id) 431 | 432 | if args.test: return 433 | objects_table.insert({ 434 | "firmware_id": firmware_id, 435 | "object_id": firmware_id, 436 | "children": object_keys, 437 | "type": "hp_logo", 438 | "size": len(data) + len(logo_data) 439 | }).run() 440 | pass 441 | 442 | def load_asrock(firmware_id, data): 443 | ### Todo: consolidate with load_flash 444 | asrock_data = data[:0x1000] 445 | data = data[0x1000:] 446 | 447 | child_ids = load_flash(firmware_id, data, save= False) 448 | if child_ids is None: 449 | return 450 | 451 | asrock_object = {"content": asrock_data, "type": "asrock_header"} 452 | header_id = store_object(firmware_id, asrock_object, object_type= "asrock_header") 453 | 454 | entry = { 455 | "firmware_id": firmware_id, 456 | "object_id": firmware_id, 457 | "children": header_id + child_ids, 458 | "type": "flash_descriptor", 459 | "size": len(data) + 0x1000 460 | } 461 | 462 | if args.test: return [] 463 | return get_result_keys(objects_table.insert(entry).run()) 464 | pass 465 | 466 | def load_lvfv(firmware_id, data): 467 | ### Todo: store initial chunk of update (may be all 0xFF padding) 468 | 469 | children = [] 470 | objects = uefi.find_volumes(data, process= False) 471 | 472 | for i, firmware_object in enumerate(objects): 473 | if type(firmware_object) == uefi.FirmwareVolume: 474 | children += load_uefi_volume(firmware_id, firmware_object._data, order= i, generate_object_id= True) 475 | else: 476 | ### Todo: store the padding in Lenovo updates (this includes content) 477 | pass 478 | 479 | entry = { 480 | "firmware_id": firmware_id, 481 | "object_id": firmware_id, 482 | "children": children, 483 | "type": "lenovo_update", 484 | "size": len(data) 485 | } 486 | 487 | if args.test: return [] 488 | return get_result_keys(objects_table.insert(entry).run()) 489 | 490 | 491 | def set_update(firmware_id, data, label_type, item_id= None): 492 | ### Set the label for the item 493 | if not args.test and item_id is not None: 494 | db.table("updates").get_all(item_id, index="item_id").update({ 495 | "firmware_id": firmware_id, 496 | "type": label_type 497 | }).run() 498 | print "Updating update %s to firmware ID: %s (%s)." % (item_id, firmware_id, label_type) 499 | 500 | if not args.test and not db.table("updates").get_all(firmware_id, index="firmware_id").is_empty().run(): 501 | ### Add size of the firmware to the updates table 502 | db.table("updates").get_all(firmware_id, index="firmware_id").update({ 503 | "size": len(data) 504 | }).run() 505 | print "Updating size for firmware ID: %s (%s)." % (firmware_id, label_type) 506 | pass 507 | 508 | def get_firmware_id(data): 509 | return hashlib.md5(data).hexdigest() 510 | 511 | def get_result_keys(insert_object): 512 | return insert_object["generated_keys"] 513 | 514 | ITEM_TYPES = { 515 | "capsule": "uefi_capsule", 516 | "pfs": "dell_pfs", 517 | "me": "intel_me", 518 | "bios": "bios_rom", 519 | "volume": "uefi_volume", 520 | "logo": "hp_logo", 521 | "fd": "flash_descriptor", 522 | "lvfv": "lenovo_update" 523 | } 524 | 525 | if __name__ == "__main__": 526 | parser = argparse.ArgumentParser() 527 | parser.add_argument("--pfs", action="store_true", default=False, help="This is a DELL PFS update.") 528 | parser.add_argument("--bios", action="store_true", default=False, help="This is a BIOS ROM.") 529 | parser.add_argument("--me", action="store_true", default=False, help="This is an Intel ME container.") 530 | parser.add_argument("--capsule", action= "store_true", default= False, help= "This is a UEFI firmware capsule.") 531 | parser.add_argument("--logo", action="store_true", default= False, help= "This is an HP (logo) update.") 532 | parser.add_argument("--flash", action="store_true", default= False, help= "This is a flash description file.") 533 | parser.add_argument("--asrock", action="store_true", default= False, help= "This is an ASRock update.") 534 | parser.add_argument("--lvfv", action="store_true", default= False, help="This is a Lenovo update (controller and flash).") 535 | parser.add_argument("-i", "--item", default= None, help= "Set the update with this item_id to the firmware_id.") 536 | parser.add_argument("-f", "--force", default= False, action="store_true", help= "Force the update") 537 | parser.add_argument("-t", "--test", default= False, action="store_true", help= "Test the loading, but do not commit.") 538 | parser.add_argument("-v", "--vendor", default= None, help= "Set the vendor for this load.") 539 | parser.add_argument("file", help="The file to work on") 540 | 541 | args = parser.parse_args() 542 | 543 | try: 544 | with open(args.file, 'rb') as fh: input_data = fh.read() 545 | except Exception, e: 546 | print "Error: Cannot read file (%s) (%s)." % (args.file, str(e)) 547 | sys.exit(1) 548 | 549 | firmware_id = get_firmware_id(input_data) 550 | 551 | r.connect("localhost", 28015).repl() 552 | db = r.db("uefi") 553 | objects_table = db.table("objects") 554 | updates_table = db.table("updates") 555 | content_table = db.table("content") 556 | 557 | label_type = "unknown" 558 | if args.pfs: label_type = ITEM_TYPES["pfs"] 559 | elif args.bios: label_type = ITEM_TYPES["bios"] 560 | elif args.me: label_type = ITEM_TYPES["me"] 561 | elif args.capsule: label_type = ITEM_TYPES["capsule"] 562 | elif args.logo: label_type = ITEM_TYPES["logo"] 563 | elif args.flash: label_type = ITEM_TYPES["fd"] 564 | elif args.asrock: label_type = ITEM_TYPES["fd"] 565 | elif args.lvfv: label_type = ITEM_TYPES["lvfv"] 566 | else: label_type = ITEM_TYPES["volume"] 567 | 568 | set_update(firmware_id, input_data, label_type, item_id= args.item) 569 | 570 | if args.pfs: 571 | load_pfs(firmware_id, input_data) 572 | elif args.capsule: 573 | load_uefi_capsule(firmware_id, input_data) 574 | elif args.logo: 575 | load_logo(firmware_id, input_data) 576 | elif args.flash: 577 | load_flash(firmware_id, input_data) 578 | elif args.asrock: 579 | load_asrock(firmware_id, input_data) 580 | elif args.lvfv: 581 | load_lvfv(firmware_id, input_data) 582 | else: 583 | load_uefi_volume(firmware_id, input_data) 584 | 585 | 586 | 587 | -------------------------------------------------------------------------------- /scripts/fv_scrapy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Read scrapy-generated JSON files specific to each scraped BIOS vendor. 3 | Store results in RethinkDB. 4 | """ 5 | 6 | import os, sys 7 | import argparse 8 | import json 9 | from datetime import datetime 10 | import rethinkdb as r 11 | 12 | ''' 13 | IntelSpider: 14 | { 15 | "binary": "", 16 | "bios_url": "http://downloadmirror.intel.com/15909/eng/EC0072.BIO", 17 | "products": [ 18 | "Intel\u00ae Desktop Board DG35EC" 19 | ], 20 | "attrs": { 21 | "status": "Previously Released", 22 | "name": "BIOS Update [ECG3510M.86A]", 23 | "url": "/Detail_Desc.aspx?agr=Y&DwnldID=15909&ProdId=3598&lang=eng", 24 | "date": "5/13/2008", 25 | "version": "0072", 26 | "item_id": "15909", 27 | "desc": "Three methods for updating your Intel\u00ae Desktop Board\u2019s BIOS version." 28 | }, 29 | "item_id": "15909", 30 | "notes_url": "http://downloadmirror.intel.com/15909/eng/EC_0072_ReleaseNotes2.pdf" 31 | } 32 | 33 | Dell Spider: 34 | { 35 | "previous_versions": [ 36 | [ 37 | "A09", 38 | "http://www.dell.com//support/drivers/us/en/19/DriverDetails?driverId=4RR90", 39 | "10/12/2012 8:56:52 AM", 40 | "4RR90" 41 | ], 42 | ], 43 | "binary": "", 44 | "importance": "Recommended", 45 | "fixes": "* Added support for an option to disable predictive memory failure reporting using the Deployment Toolkit.\n* Added TXT-SX support\n* Increased single-bit error logging threshold\n* Ensure BIOS has enabled processor AES-NI before booting to the operating system.\n* Updated the iDRAC Configuration Utility\n* Updated the embedded 5709 UEFI driver to version 6.0.0\n* Updated MRC\n* Updated Intel(R) Xeon(R) Processor 5600 Series B1 stepping microcode (Patch ID=0x13)\n* Added SR-IOV support\n* Updated the embedded 5709C PXE/iSCSI option ROM to version 6.0.11", 46 | "version": "2.2.10", 47 | "notes_url": "", 48 | "attrs": { 49 | "url": "http://www.dell.com/support/drivers/us/en/19/DriverDetails?driverId=6NP3V", 50 | "release_date": "1/12/2012 (Latest Version)", 51 | "driver_type": "BIOS Updates", 52 | "compatibility": [ 53 | "Enterprise Servers T610", 54 | " Powervault DL2200", 55 | " Enterprise Servers R910" 56 | ] 57 | }, 58 | "item_id": "6NP3V", 59 | "file_names": [ 60 | "T610-020210C.exe", 61 | "PET610_BIOS_WIN_2.2.10.EXE", 62 | "PET610_BIOS_LX_2.2.10.BIN" 63 | ], 64 | "bios_urls": [ 65 | "http://downloads.dell.com/FOLDER45071M/1/T610-020210C.exe", 66 | "http://downloads.dell.com/FOLDER82696M/1/PET610_BIOS_WIN_2.2.10.EXE", 67 | "http://downloads.dell.com/FOLDER71962M/1/PET610_BIOS_LX_2.2.10.BIN" 68 | ] 69 | } 70 | 71 | HP Spider: 72 | "binary": "", 73 | "bios_url": "http://ftp.hp.com/pub/softpaq/sp54501-55000/sp54636.exe", 74 | "importance": "Critical", 75 | "ssm": "YES", 76 | "version": "F.20 PASS1", 77 | "notes_url": "...", 78 | "attrs": { 79 | "url": "...", 80 | "date": "2011-09-09", 81 | "item_id": "ob_102534_1", 82 | "name": "HP Notebook System BIOS Update" 83 | }, 84 | "item_id": "ob_102534_1", 85 | "binary_name": "sp54636.exe", 86 | "compatibility": [ 87 | "HP ProBook 4320s Notebook PC", 88 | "HP ProBook 4321s Notebook PC", 89 | "HP ProBook 4420s Notebook PC", 90 | "HP ProBook 4421s Notebook PC" 91 | ], 92 | "desc": "...", 93 | "previous_versions": [[], []] 94 | } 95 | 96 | 97 | ''' 98 | 99 | CACHE_TIMES = {} 100 | 101 | def load_details(details, file_name= "None"): 102 | global CACHE_TIMES 103 | 104 | if "attrs" not in details: 105 | print "Warning: no attrs in details (%s)." % file_name 106 | update = {"item_id": details["item_id"]} 107 | 108 | if args.type == "HP": 109 | try: 110 | update["products"] = [p.strip() for p in details["compatibility"]] 111 | except: 112 | update["products"] = ["Unknown System"] 113 | update["payload_urls"] = [details["bios_url"]] 114 | update["name"] = details["binary_name"] 115 | update["date"] = int(datetime.strptime(details["attrs"]["date"], "%Y-%m-%d").strftime("%s")) 116 | update["version"] = details["version"] 117 | update["description"] = details["desc"] if "desc" in details else "" 118 | update["notes_url"] = details["notes_url"] 119 | update["details_url"] = details["attrs"]["url"] 120 | 121 | update["attrs"] = {} 122 | update["attrs"]["importance"] = details["importance"] if "importance" in details else "Unknown" 123 | if "ssm" in details: 124 | update["attrs"]["ssm"] = details["ssm"] 125 | update["attrs"]["title"] = details["attrs"]["name"] 126 | 127 | update["previous_versions"] = [] 128 | if "previous_versions" in details: 129 | for version in details["previous_versions"]: 130 | update["previous_versions"].append({ 131 | "version": version[0], 132 | "item_id": version[2], 133 | "details_url": version[1] 134 | }) 135 | update["vendor"] = "HP" 136 | 137 | if args.type == "ASRock": 138 | update["products"] = [details["attrs"]["product"]] 139 | update["payload_urls"] = [details["bios_url"]] 140 | update["name"] = details["binary_name"] 141 | update["date"] = int(datetime.strptime(details["date"], "%m/%d/%Y").strftime("%s")) 142 | update["version"] = details["version"] 143 | update["description"] = details["desc"] 144 | update["notes_url"] = "" 145 | update["details_url"] = details["attrs"]["url"] 146 | update["attrs"] = {} 147 | update["attrs"]["update_type"] = details["bios_type"] 148 | update["attrs"]["chipset"] = details["attrs"]["chipset"] 149 | update["vendor"] = "ASRock" 150 | 151 | if args.type == "Lenovo": 152 | update["products"] = details["products"] 153 | update["payload_urls"] = [details["bios_url"]] 154 | update["name"] = details["binary_name"] 155 | try: 156 | update["date"] = int(datetime.strptime(details["date"], "%Y/%m/%d").strftime("%s")) 157 | except: 158 | update["date"] = 0 159 | update["version"] = details["version"] 160 | update["description"] = details["desc"] 161 | update["notes_url"] = details["notes_url"] 162 | update["details_url"] = details["url"] 163 | update["attrs"] = {} 164 | update["attrs"]["update_type"] = "default" 165 | update["vendor"] = "Lenovo" 166 | 167 | if args.type == "MSI": 168 | update["products"] = [details["attrs"]["title"]] 169 | update["payload_urls"] = [details["bios_url"]] 170 | update["name"] = details["binary_name"] 171 | update["date"] = int(datetime.strptime(details["date"], "%Y-%m-%d").strftime("%s")) 172 | update["version"] = details["version"] 173 | update["description"] = details["desc"] if "desc" in details else "" 174 | update["notes_url"] = "" 175 | update["details_url"] = details["attrs"]["url"] 176 | update["attrs"] = {} 177 | update["attrs"]["importance"] = "default" 178 | update["attrs"]["title"] = details["attrs"]["title"] 179 | update["attrs"]["driver_type"] = details["driver_type"] 180 | update["vendor"] = "MSI" 181 | 182 | if args.type == "Dell": 183 | update["products"] = [p.strip() for p in details["attrs"]["compatibility"]] 184 | update["payload_urls"] = details["bios_urls"] 185 | update["name"] = details["file_names"][0] # Could be improved 186 | ### This is the date, there is a timestamp found in a previous version 187 | update["date"] = int(datetime.strptime(details["attrs"]["release_date"].split("(", 1)[0].strip(), "%m/%d/%Y").strftime("%s")) 188 | update["version"] = details["version"] 189 | update["description"] = details["fixes"] 190 | update["notes_url"] = "" 191 | update["details_url"] = details["attrs"]["url"] 192 | 193 | update["attrs"] = {} 194 | update["attrs"]["importance"] = details["importance"] 195 | update["previous_versions"] = [] 196 | for version in details["previous_versions"]: 197 | update["previous_versions"].append({ 198 | "version": version[0], 199 | "date": int(datetime.strptime(version[2], "%m/%d/%Y %H:%M:%S %p").strftime("%s")), 200 | "item_id": version[3], 201 | "details_url": version[1] 202 | }) 203 | CACHE_TIMES[version[3]] = int(datetime.strptime(version[2], "%m/%d/%Y %H:%M:%S %p").strftime("%s")) 204 | update["vendor"] = "Dell" 205 | pass 206 | 207 | elif args.type == "Intel": 208 | update["products"] = [p.replace(u"Intel\u00ae", "").strip() for p in details["products"]] 209 | ### Only one URL for intel 210 | update["payload_urls"] = [details["bios_url"]] 211 | update["name"] = details["attrs"]["name"] 212 | update["date"] = int(datetime.strptime(details["attrs"]["date"], "%m/%d/%Y").strftime("%s")) 213 | update["version"] = details["attrs"]["version"] 214 | update["description"] = details["attrs"]["desc"] 215 | update["notes_url"] = details["notes_url"] 216 | update["details_url"] = details["attrs"]["url"] 217 | 218 | update["attrs"] = {} 219 | update["attrs"]["status"] = details["attrs"]["status"] 220 | update["vendor"] = "Intel" 221 | 222 | #print json.dumps(update, indent=2) 223 | return update 224 | pass 225 | 226 | def save_update(update): 227 | if not table.filter({"item_id": update["item_id"]}).is_empty(): 228 | print "ItemID: %s is a duplicate, skipping." % update["item_id"] 229 | return 230 | table.insert(update) 231 | pass 232 | 233 | if __name__ == "__main__": 234 | parser = argparse.ArgumentParser() 235 | parser.add_argument("-d", "--directory", action="store_true", default= False, help= "Treat the input as a directory") 236 | parser.add_argument("-t", "--type", default= None, help= "Force parsing a file of a specific ISV, otherwise guess.") 237 | parser.add_argument("file", help="The file to work on") 238 | args = parser.parse_args() 239 | 240 | r.connect("localhost", 28015).repl() 241 | table = r.db("uefi").table("updates") 242 | 243 | all_files = [] 244 | if args.directory: 245 | if not os.path.isdir(args.file): 246 | print "Error: %s is not a directory." % args.file 247 | sys.exit(1) 248 | for root, dirs, files in os.walk(args.file): 249 | for file_name in files: 250 | file_name = os.path.join(root, file_name) 251 | _, extension = os.path.splitext(file_name) 252 | if extension != ".json": 253 | continue 254 | all_files.append(file_name) 255 | else: 256 | all_files.append(args.file) 257 | 258 | updates = [] 259 | for file_name in all_files: 260 | try: 261 | with open(file_name, 'r') as fh: 262 | details = fh.read() 263 | details = json.loads(details) 264 | except Exception, e: 265 | print "Error: cannot load (%s). (%s)" % (file_name, str(e)) 266 | continue 267 | 268 | try: 269 | update = load_details(details, file_name) 270 | except Exception, e: 271 | print "Failed for %s, %s." % (file_name, str(e)) 272 | continue 273 | if not table.get_all(update["item_id"], index="item_id").is_empty().run(): 274 | print "ItemID: %s is a duplicate, skipping." % update["item_id"] 275 | continue 276 | updates.append(update) 277 | pass 278 | 279 | ### Now update the precision using cached times. 280 | if len(CACHE_TIMES) > 0: 281 | for update in updates: 282 | if update["item_id"] in CACHE_TIMES: 283 | update["date"] = CACHE_TIMES[update["item_id"]] 284 | 285 | ### Apply all updates 286 | table.insert(updates).run() 287 | 288 | -------------------------------------------------------------------------------- /scripts/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def blue(msg): 4 | return "\033[1;36m%s\033[1;m" % msg 5 | def red(msg): 6 | return "\033[31m%s\033[1;m" % msg 7 | def green(msg): 8 | return "\033[32m%s\033[1;m" % msg 9 | def purple(msg): 10 | return "\033[1;35m%s\033[1;m" % msg 11 | 12 | def ascii_char(c): 13 | if ord(c) >= 32 and ord(c) <= 126: return c 14 | return '.' 15 | 16 | def hex_dump(data, size= 16): 17 | def print_line(line): 18 | print "%s | %s" % (line.encode("hex"), "".join([ascii_char(c) for c in line])) 19 | pass 20 | 21 | for i in xrange(0, len(data)/size): 22 | data_line = data[i*size:i*size + size] 23 | print_line(data_line) 24 | 25 | if not len(data) % size == 0: 26 | print_line(data[(len(data) % size) * -1:]) -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # bundler state 2 | /.bundle 3 | /vendor/bundle/ 4 | /vendor/ruby/ 5 | 6 | # minimal Rails specific artifacts 7 | db/*.sqlite3 8 | /db/*.sqlite3-journal 9 | /log/* 10 | /tmp/* 11 | 12 | # configuration file introduced in Rails 4.1 13 | /config/secrets.yml 14 | 15 | # various artifacts 16 | **.war 17 | *.rbc 18 | *.sassc 19 | .rspec 20 | .redcar/ 21 | .sass-cache 22 | /coverage.data 23 | /coverage/ 24 | /db/*.javadb/ 25 | /db/*.sqlite3 26 | /doc/api/ 27 | /doc/app/ 28 | /doc/features.html 29 | /doc/specs.html 30 | /public/cache 31 | /public/stylesheets/compiled 32 | /public/system/* 33 | /spec/tmp/* 34 | /cache 35 | /capybara* 36 | /capybara-*.html 37 | /gems 38 | /specifications 39 | rerun.txt 40 | pickle-email-*.html 41 | .zeus.sock 42 | 43 | # scm revert files 44 | **.orig 45 | 46 | # Netbeans project directory 47 | /nbproject/ 48 | 49 | # RubyMine project files 50 | .idea 51 | /*.tmproj 52 | **.swp 53 | 54 | # Ignore OS generated files 55 | .DS_Store* 56 | ehthumbs.db 57 | Icon? 58 | Thumbs.db 59 | -------------------------------------------------------------------------------- /web/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | #ruby '2.0.0' 3 | 4 | gem 'rails', '4.0.2' 5 | gem 'sqlite3' 6 | gem 'sass-rails', '~> 4.0.0' 7 | gem 'uglifier', '>= 1.3.0' 8 | gem 'coffee-rails', '~> 4.0.0' 9 | gem 'jquery-rails' 10 | gem 'turbolinks' 11 | gem 'jbuilder', '~> 1.2' 12 | gem 'bootstrap-sass', '>= 3.0.0.0' 13 | gem 'figaro' 14 | gem 'haml-rails' 15 | gem 'high_voltage' 16 | gem 'simple_form' 17 | gem 'therubyracer' 18 | 19 | group :development do 20 | gem 'better_errors' 21 | gem 'binding_of_caller', :platforms=>[:mri_19, :mri_20, :rbx] 22 | gem 'html2haml' 23 | gem 'quiet_assets' 24 | gem 'rails_layout' 25 | end 26 | 27 | gem 'will_paginate-bootstrap' 28 | 29 | gem 'rethinkdb' 30 | gem 'puma', '2.2.0' 31 | -------------------------------------------------------------------------------- /web/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionmailer (4.0.2) 5 | actionpack (= 4.0.2) 6 | mail (~> 2.5.4) 7 | actionpack (4.0.2) 8 | activesupport (= 4.0.2) 9 | builder (~> 3.1.0) 10 | erubis (~> 2.7.0) 11 | rack (~> 1.5.2) 12 | rack-test (~> 0.6.2) 13 | activemodel (4.0.2) 14 | activesupport (= 4.0.2) 15 | builder (~> 3.1.0) 16 | activerecord (4.0.2) 17 | activemodel (= 4.0.2) 18 | activerecord-deprecated_finders (~> 1.0.2) 19 | activesupport (= 4.0.2) 20 | arel (~> 4.0.0) 21 | activerecord-deprecated_finders (1.0.3) 22 | activesupport (4.0.2) 23 | i18n (~> 0.6, >= 0.6.4) 24 | minitest (~> 4.2) 25 | multi_json (~> 1.3) 26 | thread_safe (~> 0.1) 27 | tzinfo (~> 0.3.37) 28 | arel (4.0.1) 29 | atomic (1.1.14) 30 | better_errors (1.1.0) 31 | coderay (>= 1.0.0) 32 | erubis (>= 2.6.6) 33 | binding_of_caller (0.7.2) 34 | debug_inspector (>= 0.0.1) 35 | bootstrap-sass (3.1.0.0) 36 | sass (~> 3.2) 37 | builder (3.1.4) 38 | coderay (1.1.0) 39 | coffee-rails (4.0.1) 40 | coffee-script (>= 2.2.0) 41 | railties (>= 4.0.0, < 5.0) 42 | coffee-script (2.2.0) 43 | coffee-script-source 44 | execjs 45 | coffee-script-source (1.7.0) 46 | debug_inspector (0.0.2) 47 | erubis (2.7.0) 48 | execjs (2.0.2) 49 | figaro (0.7.0) 50 | bundler (~> 1.0) 51 | rails (>= 3, < 5) 52 | haml (4.1.0.beta.1) 53 | tilt 54 | haml-rails (0.5.3) 55 | actionpack (>= 4.0.1) 56 | activesupport (>= 4.0.1) 57 | haml (>= 3.1, < 5.0) 58 | railties (>= 4.0.1) 59 | high_voltage (2.1.0) 60 | hike (1.2.3) 61 | hpricot (0.8.6) 62 | html2haml (1.0.1) 63 | erubis (~> 2.7.0) 64 | haml (>= 4.0.0.rc.1) 65 | hpricot (~> 0.8.6) 66 | ruby_parser (~> 3.1.1) 67 | i18n (0.6.9) 68 | jbuilder (1.5.3) 69 | activesupport (>= 3.0.0) 70 | multi_json (>= 1.2.0) 71 | jquery-rails (3.1.0) 72 | railties (>= 3.0, < 5.0) 73 | thor (>= 0.14, < 2.0) 74 | json (1.8.1) 75 | libv8 (3.16.14.3) 76 | mail (2.5.4) 77 | mime-types (~> 1.16) 78 | treetop (~> 1.4.8) 79 | mime-types (1.25.1) 80 | minitest (4.7.5) 81 | multi_json (1.8.4) 82 | polyglot (0.3.3) 83 | puma (2.2.0) 84 | rack (>= 1.1, < 2.0) 85 | quiet_assets (1.0.2) 86 | railties (>= 3.1, < 5.0) 87 | rack (1.5.2) 88 | rack-test (0.6.2) 89 | rack (>= 1.0) 90 | rails (4.0.2) 91 | actionmailer (= 4.0.2) 92 | actionpack (= 4.0.2) 93 | activerecord (= 4.0.2) 94 | activesupport (= 4.0.2) 95 | bundler (>= 1.3.0, < 2.0) 96 | railties (= 4.0.2) 97 | sprockets-rails (~> 2.0.0) 98 | rails_layout (1.0.5) 99 | railties (4.0.2) 100 | actionpack (= 4.0.2) 101 | activesupport (= 4.0.2) 102 | rake (>= 0.8.7) 103 | thor (>= 0.18.1, < 2.0) 104 | rake (10.1.1) 105 | ref (1.0.5) 106 | rethinkdb (1.11.0.2) 107 | json 108 | ruby-protocol-buffers 109 | varint 110 | ruby-protocol-buffers (1.5.1) 111 | ruby_parser (3.1.3) 112 | sexp_processor (~> 4.1) 113 | sass (3.2.14) 114 | sass-rails (4.0.1) 115 | railties (>= 4.0.0, < 5.0) 116 | sass (>= 3.1.10) 117 | sprockets-rails (~> 2.0.0) 118 | sexp_processor (4.4.1) 119 | simple_form (3.0.1) 120 | actionpack (>= 4.0.0, < 4.1) 121 | activemodel (>= 4.0.0, < 4.1) 122 | sprockets (2.10.1) 123 | hike (~> 1.2) 124 | multi_json (~> 1.0) 125 | rack (~> 1.0) 126 | tilt (~> 1.1, != 1.3.0) 127 | sprockets-rails (2.0.1) 128 | actionpack (>= 3.0) 129 | activesupport (>= 3.0) 130 | sprockets (~> 2.8) 131 | sqlite3 (1.3.8) 132 | therubyracer (0.12.0) 133 | libv8 (~> 3.16.14.0) 134 | ref 135 | thor (0.18.1) 136 | thread_safe (0.1.3) 137 | atomic 138 | tilt (1.4.1) 139 | treetop (1.4.15) 140 | polyglot 141 | polyglot (>= 0.3.1) 142 | turbolinks (2.2.1) 143 | coffee-rails 144 | tzinfo (0.3.38) 145 | uglifier (2.4.0) 146 | execjs (>= 0.3.0) 147 | json (>= 1.8.0) 148 | varint (0.1.0) 149 | will_paginate (3.0.5) 150 | will_paginate-bootstrap (1.0.0) 151 | will_paginate (>= 3.0.3) 152 | 153 | PLATFORMS 154 | ruby 155 | 156 | DEPENDENCIES 157 | better_errors 158 | binding_of_caller 159 | bootstrap-sass (>= 3.0.0.0) 160 | coffee-rails (~> 4.0.0) 161 | figaro 162 | haml-rails 163 | high_voltage 164 | html2haml 165 | jbuilder (~> 1.2) 166 | jquery-rails 167 | puma (= 2.2.0) 168 | quiet_assets 169 | rails (= 4.0.2) 170 | rails_layout 171 | rethinkdb 172 | sass-rails (~> 4.0.0) 173 | simple_form 174 | sqlite3 175 | therubyracer 176 | turbolinks 177 | uglifier (>= 1.3.0) 178 | will_paginate-bootstrap 179 | -------------------------------------------------------------------------------- /web/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | RailsBootstrap::Application.load_tasks 7 | -------------------------------------------------------------------------------- /web/app/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /web/app/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /web/app/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /web/app/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /web/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/assets/images/.keep -------------------------------------------------------------------------------- /web/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require bootstrap 17 | //= require_tree . 18 | 19 | function generate_freq_bars(selector, data) { 20 | var svg = dimple.newSvg(selector, 1900, 500); 21 | var myChart = new dimple.chart(svg, data); 22 | myChart.setBounds(70, 40, 1590, 320) 23 | var x = myChart.addCategoryAxis("x", "Name"); 24 | var y = myChart.addMeasureAxis("y", "Change"); 25 | var s = myChart.addSeries("Name", dimple.plot.bar); 26 | //var myLegend = myChart.addLegend(1800, 20, 60, 500, "right"); 27 | 28 | s.addEventHandler("mouseover", function(e) { 29 | generic_onHover(e, svg); 30 | }); 31 | s.addEventHandler("mouseleave", function(e) { 32 | generic_onLeave(e, svg); 33 | }); 34 | 35 | myChart.draw(); 36 | s.shapes.each(function(d) { 37 | // Get the shape as a d3 selection 38 | var shape = d3.select(this), 39 | // Get the height and width from the scales 40 | height = myChart.y + myChart.height - y._scale(d.height); 41 | width = d.width + 40; 42 | // Only label bars where the text can fit 43 | if (height >= 8) { 44 | // Add a text label for the value 45 | svg.append("text") 46 | // Position in the centre of the shape (vertical position is 47 | // manually set due to cross-browser problems with baseline) 48 | .attr("x", parseFloat(shape.attr("x")) + width / 2) 49 | .attr("y", parseFloat(shape.attr("y")) - height / 2 + 3.5) 50 | // Centre align 51 | .style("text-anchor", "middle") 52 | .style("font-size", "10px") 53 | .style("font-family", "sans-serif") 54 | // Make it a little transparent to tone down the black 55 | .style("opacity", 0.6) 56 | // Format the number 57 | .text(d3.format("f")(d.yValue) + "h\n" + ""); 58 | console.log(d); 59 | } 60 | }); 61 | } 62 | 63 | function generate_delta_points(selector, data) { 64 | var svg = dimple.newSvg(selector, 1900, 500); 65 | var myChart = new dimple.chart(svg, data); 66 | myChart.setBounds(70, 40, 1590, 320) 67 | 68 | //var x = myChart.addCategoryAxis("x", "Version"); 69 | //var y = myChart.addMeasureAxis("y", "Delta"); 70 | //var z = myChart.addMeasureAxis("z", "Change"); 71 | //myChart.addSeries("Name", dimple.plot.bubble); 72 | //var s = myChart.addSeries("Name", dimple.plot.line); 73 | 74 | var x = myChart.addMeasureAxis("x", "Delta"); 75 | var y = myChart.addMeasureAxis("y", "Change"); 76 | var s = myChart.addSeries(["Name", "Version"], dimple.plot.bubble); 77 | //x.overrideMin = 1; 78 | 79 | myChart.addLegend(180, 10, 360, 20, "right"); 80 | 81 | s.addEventHandler("mouseover", function(e) { 82 | var cx = parseFloat(e.selectedShape.attr("cx")), 83 | cy = parseFloat(e.selectedShape.attr("cy")); 84 | var width = 170, 85 | height = 48, 86 | x = (cx + width + 10 < svg.attr("width") ? 87 | cx + 10 : 88 | cx - width - 20); 89 | y = (cy - height / 2 < 0 ? 90 | 15 : 91 | cy - height / 2); 92 | 93 | popup = svg.append("g"); 94 | svg._popup = popup; 95 | 96 | popup 97 | .append("rect").attr("x", x + 5).attr("y", y - 5).attr("width", 150) 98 | .attr("height", height).attr("rx", 5).attr("ry", 5) 99 | .style("fill", 'white').style("stroke", 'black').style("stroke-width", 1); 100 | 101 | console.log(e); 102 | 103 | popup 104 | .append('text').attr('x', x + 0).attr('y', y + 10) 105 | .append('tspan').attr('x', x + 10).attr('y', y + 8) 106 | .text(e.seriesValue[0] + " " + e.seriesValue[1] + "\n") 107 | .style("font-family", "sans-serif").style("font-size", 10).style("fill", "black") 108 | .append('tspan').attr('x', x + 10).attr('y', y + 22) 109 | .text('Change ' + Math.round(e.yValue * 10) / 10) 110 | .style("font-family", "sans-serif").style("font-size", 10).style("fill", "black") 111 | .append('tspan').attr('x', x + 10).attr('y', y + 36) 112 | .text("Delta " + e.xValue) 113 | .style("font-family", "sans-serif").style("font-size", 10).style("fill", "black") 114 | }); 115 | 116 | s.addEventHandler("mouseleave", function(e) { 117 | generic_onLeave(e, svg); 118 | }); 119 | 120 | myChart.draw(); 121 | } 122 | 123 | function generate_time_bars(selector, data) { 124 | var svg = dimple.newSvg(selector, 1900, 500); 125 | var myChart = new dimple.chart(svg, data); 126 | myChart.setBounds(70, 40, 1590, 320) 127 | var x = myChart.addTimeAxis("x", "Time", "%d %b %Y", "%d %b %Y"); 128 | x.addOrderRule("Time"); 129 | var y = myChart.addMeasureAxis("y", "Change"); 130 | var s = myChart.addSeries(["Name"], dimple.plot.bar); 131 | var myLegend = myChart.addLegend(1800, 20, 60, 500, "right"); 132 | 133 | myChart.draw(); 134 | 135 | myChart.legends = []; 136 | var filterValues = dimple.getUniqueValues(data, "Name"); 137 | myLegend.shapes.selectAll("rect") 138 | .on("click", function (e) { 139 | var hide = false; 140 | var newFilters = []; 141 | filterValues.forEach(function (f) { 142 | if (f === e.aggField.slice(-1)[0]) { 143 | hide = true; 144 | } else { 145 | newFilters.push(f); 146 | } 147 | }); 148 | if (hide) { 149 | d3.select(this).style("opacity", 0.2); 150 | } else { 151 | newFilters.push(e.aggField.slice(-1)[0]); 152 | d3.select(this).style("opacity", 0.8); 153 | } 154 | filterValues = newFilters; 155 | myChart.data = dimple.filterData(data, "Name", filterValues); 156 | myChart.draw(800); 157 | }); 158 | 159 | s.addEventHandler("mouseover", function(e) { 160 | generic_onHover(e, svg); 161 | }); 162 | s.addEventHandler("mouseleave", function(e) { 163 | generic_onLeave(e, svg); 164 | }); 165 | 166 | myChart.draw(); 167 | } 168 | 169 | function generic_onHover(e, svg) { 170 | var cx = parseFloat(e.selectedShape.attr("x")), 171 | cy = parseFloat(e.selectedShape.attr("y")); 172 | var width = 150, 173 | height = 40, 174 | x = (cx + width + 10 < svg.attr("width") ? 175 | cx + 10 : 176 | cx - width - 20); 177 | y = (cy - height / 2 < 0 ? 178 | 15 : 179 | cy - height / 2); 180 | 181 | popup = svg.append("g"); 182 | svg._popup = popup; 183 | 184 | popup 185 | .append("rect").attr("x", x + 5).attr("y", y - 5).attr("width", 150) 186 | .attr("height", height).attr("rx", 5).attr("ry", 5) 187 | .style("fill", 'white').style("stroke", 'black').style("stroke-width", 1); 188 | 189 | popup 190 | .append('text').attr('x', x + 0).attr('y', y + 10) 191 | .append('tspan').attr('x', x + 10).attr('y', y + 8) 192 | .text(e.seriesValue[0] + "\n") 193 | .style("font-family", "sans-serif").style("font-size", 10).style("fill", "black") 194 | .append('tspan').attr('x', x + 10).attr('y', y + 22) 195 | .text('Change ' + Math.round(e.yValue * 10) / 10) 196 | .style("font-family", "sans-serif").style("font-size", 10).style("fill", "black") 197 | } 198 | 199 | function generic_onLeave(e, svg) { 200 | popup = svg._popup; 201 | svg._popup = null; 202 | 203 | if (popup !== null) { 204 | popup.remove(); 205 | } 206 | }; -------------------------------------------------------------------------------- /web/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | 15 | td { 16 | padding-right: 8px !important; 17 | } 18 | 19 | .container { 20 | margin-right: 0 !important; 21 | margin-left: 0 !important; 22 | } 23 | 24 | .guid { 25 | font-family: Monaco, "Liberation Mono", Courier, monospace; 26 | display: inline; 27 | } -------------------------------------------------------------------------------- /web/app/assets/stylesheets/darkstrap.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Darkstrap v0.9.2 3 | * By danneu (http://github.com/danneu/darkstrap) 4 | * Based off Twitter Bootstrap v2.2.2 5 | */ 6 | 7 | tr.warning, 8 | tr.success, 9 | tr.error, 10 | tr.info { 11 | color: #fff; 12 | } 13 | 14 | body { 15 | color: #c6c6c6; 16 | background-color: #2f2f2f; 17 | } 18 | 19 | a:hover { 20 | color: #1ab2ff; 21 | } 22 | 23 | textarea, 24 | input[type="text"], 25 | input[type="password"], 26 | input[type="datetime"], 27 | input[type="datetime-local"], 28 | input[type="date"], 29 | input[type="month"], 30 | input[type="time"], 31 | input[type="week"], 32 | input[type="number"], 33 | input[type="email"], 34 | input[type="url"], 35 | input[type="search"], 36 | input[type="tel"], 37 | input[type="color"], 38 | .uneditable-input { 39 | background-color: #cccccc; 40 | } 41 | select { 42 | background-color: #cccccc; 43 | } 44 | 45 | .uneditable-input, 46 | .uneditable-textarea { 47 | background-color: #c9c9c9; 48 | } 49 | 50 | input:-moz-placeholder, 51 | textarea:-moz-placeholder { 52 | color: #666666; 53 | } 54 | input:-ms-input-placeholder, 55 | textarea:-ms-input-placeholder { 56 | color: #666666; 57 | } 58 | input::-webkit-input-placeholder, 59 | textarea::-webkit-input-placeholder { 60 | color: #666666; 61 | } 62 | 63 | .control-group.warning .input-prepend .add-on, 64 | .control-group.warning .input-append .add-on { 65 | background-color: #faa732; 66 | } 67 | 68 | .control-group.error .input-prepend .add-on, 69 | .control-group.error .input-append .add-on { 70 | background-color: #fc5b5e; 71 | } 72 | 73 | .control-group.success .input-prepend .add-on, 74 | .control-group.success .input-append .add-on { 75 | background-color: #5bb75b; 76 | } 77 | 78 | .control-group.info .input-prepend .add-on, 79 | .control-group.info .input-append .add-on { 80 | background-color: #3a87ad; 81 | } 82 | 83 | .form-actions { 84 | background-color: #444444; 85 | } 86 | .help-block, 87 | .help-inline { 88 | color: #ececec; 89 | } 90 | 91 | .table th, 92 | .table td { 93 | border-top: 1px solid #333; 94 | } 95 | .table tbody + tbody { 96 | border-top: 2px solid #333; 97 | } 98 | .table .table { 99 | background-color: #2f2f2f; 100 | } 101 | 102 | .table-bordered { 103 | border: 1px solid #333; 104 | } 105 | .table-bordered th, 106 | .table-bordered td { 107 | border-left: 1px solid #666666; 108 | } 109 | .table-striped tbody > tr:nth-child(odd) > td, 110 | .table-striped tbody > tr:nth-child(odd) > th { 111 | background-color: #444444; 112 | } 113 | 114 | .table-hover tbody tr:hover td, 115 | .table-hover tbody tr:hover th { 116 | background-color: #222 !important; 117 | } 118 | 119 | tr:hover { 120 | background-color: #222 !important; 121 | } 122 | 123 | .table-bordered { 124 | border: 1px solid #333; } 125 | .table-bordered > thead > tr > th, 126 | .table-bordered > thead > tr > td, 127 | .table-bordered > tbody > tr > th, 128 | .table-bordered > tbody > tr > td, 129 | .table-bordered > tfoot > tr > th, 130 | .table-bordered > tfoot > tr > td { 131 | border: 1px solid #333; } 132 | .table-bordered > thead > tr > th, 133 | .table-bordered > thead > tr > td { 134 | border-bottom-width: 2px; } 135 | 136 | .table tbody tr.success td { 137 | background-color: #5bb75b; 138 | } 139 | .table tbody tr.error td { 140 | background-color: #fc5b5e; 141 | } 142 | .table tbody tr.warning td { 143 | background-color: #faa732; 144 | } 145 | .table tbody tr.info td { 146 | background-color: #3a87ad; 147 | } 148 | 149 | .table-hover tbody tr.success:hover td { 150 | background-color: #4cad4c; 151 | } 152 | .table-hover tbody tr.error:hover td { 153 | background-color: #fc4245; 154 | } 155 | .table-hover tbody tr.warning:hover td { 156 | background-color: #f99c19; 157 | } 158 | .table-hover tbody tr.info:hover td { 159 | background-color: #34789a; 160 | } 161 | 162 | [class^="icon-"], 163 | [class*=" icon-"] { 164 | background-image: url("../img/glyphicons-halflings.png"); 165 | } 166 | 167 | .icon-white, 168 | .nav-pills > .active > a > [class^="icon-"], 169 | .nav-pills > .active > a > [class*=" icon-"], 170 | .nav-list > .active > a > [class^="icon-"], 171 | .nav-list > .active > a > [class*=" icon-"], 172 | .navbar-inverse .nav > .active > a > [class^="icon-"], 173 | .navbar-inverse .nav > .active > a > [class*=" icon-"], 174 | .dropdown-menu > li > a:hover > [class^="icon-"], 175 | .dropdown-menu > li > a:hover > [class*=" icon-"], 176 | .dropdown-menu > .active > a > [class^="icon-"], 177 | .dropdown-menu > .active > a > [class*=" icon-"], 178 | .dropdown-submenu:hover > a > [class^="icon-"], 179 | .dropdown-submenu:hover > a > [class*=" icon-"] { 180 | background-image: url("../img/glyphicons-halflings-white.png"); 181 | } 182 | 183 | 184 | .btn-link:hover { 185 | color: #1ab2ff; 186 | } 187 | 188 | .alert { 189 | background-color: #faa732; 190 | border: 1px solid #fa7d23; 191 | } 192 | 193 | 194 | .alert-success { 195 | background-color: #5bb75b; 196 | border-color: #5cad4c; 197 | } 198 | 199 | 200 | .alert-danger, 201 | .alert-error { 202 | background-color: #fc5b5e; 203 | border-color: #fc4c6d; 204 | } 205 | 206 | .alert-info { 207 | background-color: #3a87ad; 208 | border-color: #318292; 209 | } 210 | 211 | .nav-tabs > .active > a, 212 | .nav-tabs > .active > a:hover { 213 | background-color: #2f2f2f; 214 | } 215 | 216 | .nav .dropdown-toggle:hover .caret { 217 | border-top-color: #1ab2ff; 218 | border-bottom-color: #1ab2ff; 219 | } 220 | 221 | .navbar-inner { 222 | background-color: #363636; 223 | background-image: -moz-linear-gradient(top, #444444, #222222); 224 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222)); 225 | background-image: -webkit-linear-gradient(top, #444444, #222222); 226 | background-image: -o-linear-gradient(top, #444444, #222222); 227 | background-image: linear-gradient(to bottom, #444444, #222222); 228 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF444444', endColorstr='#FF222222', GradientType=0); 229 | border: 1px solid #030303; 230 | } 231 | .navbar .brand { 232 | color: #c6c6c6; 233 | text-shadow: 0 1px 0 #444444; 234 | } 235 | .navbar-text { 236 | color: #c6c6c6; 237 | } 238 | 239 | .navbar-link { 240 | color: #c6c6c6; 241 | } 242 | .navbar-link:hover { 243 | color: white; 244 | } 245 | 246 | .navbar .divider-vertical { 247 | border-left: 1px solid #222222; 248 | border-right: 1px solid #444444; 249 | } 250 | 251 | 252 | .navbar .nav > li > a { 253 | color: #c6c6c6; 254 | text-shadow: 0 1px 0 #444444; 255 | } 256 | 257 | .navbar .nav > li > a:focus, 258 | .navbar .nav > li > a:hover { 259 | color: white; 260 | } 261 | 262 | .navbar .nav > .active > a, 263 | .navbar .nav > .active > a:hover, 264 | .navbar .nav > .active > a:focus { 265 | color: white; 266 | background-color: #151515; 267 | } 268 | 269 | .navbar .btn-navbar { 270 | background-color: #292929; 271 | background-image: -moz-linear-gradient(top, #373737, #151515); 272 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#373737), to(#151515)); 273 | background-image: -webkit-linear-gradient(top, #373737, #151515); 274 | background-image: -o-linear-gradient(top, #373737, #151515); 275 | background-image: linear-gradient(to bottom, #373737, #151515); 276 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF373737', endColorstr='#FF151515', GradientType=0); 277 | border-color: #151515 #151515 black; 278 | *background-color: #151515; 279 | } 280 | .navbar .btn-navbar:hover, .navbar .btn-navbar:active, .navbar .btn-navbar.active, .navbar .btn-navbar.disabled, .navbar .btn-navbar[disabled] { 281 | background-color: #151515; 282 | *background-color: #090909; 283 | } 284 | .navbar .btn-navbar:active, .navbar .btn-navbar.active { 285 | background-color: black \9; 286 | } 287 | 288 | .navbar .nav li.dropdown > a:hover .caret { 289 | border-top-color: white; 290 | border-bottom-color: white; 291 | } 292 | 293 | .navbar .nav li.dropdown.open > .dropdown-toggle, 294 | .navbar .nav li.dropdown.active > .dropdown-toggle, 295 | .navbar .nav li.dropdown.open.active > .dropdown-toggle { 296 | background-color: #151515; 297 | color: white; 298 | } 299 | 300 | .navbar .nav li.dropdown > .dropdown-toggle .caret { 301 | border-top-color: #c6c6c6; 302 | border-bottom-color: #c6c6c6; 303 | } 304 | 305 | .navbar .nav li.dropdown.open > .dropdown-toggle .caret, 306 | .navbar .nav li.dropdown.active > .dropdown-toggle .caret, 307 | .navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { 308 | border-top-color: white; 309 | border-bottom-color: white; 310 | } 311 | 312 | .well { 313 | -webkit-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 314 | -moz-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 315 | box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 316 | background: #202020; 317 | background-color: rgba(0, 0, 0, 0.3); 318 | border: 0; 319 | } 320 | 321 | .darkwell, .breadcrumb, code, pre, select, 322 | input[type="text"], 323 | input[type="password"], 324 | input[type="datetime"], 325 | input[type="datetime-local"], 326 | input[type="date"], 327 | input[type="month"], 328 | input[type="time"], 329 | input[type="week"], 330 | input[type="number"], 331 | input[type="email"], 332 | input[type="url"], 333 | input[type="search"], 334 | input[type="tel"], 335 | input[type="color"], 336 | .uneditable-input, textarea, .hero-unit, .progress { 337 | -webkit-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 338 | -moz-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 339 | box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 340 | background: #202020; 341 | background-color: rgba(0, 0, 0, 0.3); 342 | border: 0; 343 | } 344 | 345 | .breadcrumb { 346 | border: 0; 347 | } 348 | .breadcrumb li { 349 | text-shadow: 0 1px 0 black; 350 | } 351 | 352 | .page-header { 353 | -webkit-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 354 | -moz-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 355 | box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 356 | border-bottom: 1px solid #121212; 357 | } 358 | 359 | h1, h2, h3, h4, h5, h6 { 360 | color: white; 361 | } 362 | 363 | h6 { 364 | color: #999; 365 | } 366 | 367 | blockquote { 368 | border-left-color: #111; 369 | } 370 | blockquote.pull-right { 371 | border-right-color: #111; 372 | } 373 | 374 | hr { 375 | -webkit-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 376 | -moz-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 377 | box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 378 | border-bottom: 1px solid #121212; 379 | border-top: none; 380 | } 381 | 382 | code { 383 | border: none; 384 | padding: 2px 4px; 385 | } 386 | 387 | pre { 388 | border: none; 389 | color: #c6c6c6; 390 | padding: 8px; 391 | } 392 | 393 | legend { 394 | -webkit-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 395 | -moz-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 396 | box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 397 | border-bottom: 1px solid #121212; 398 | color: #fff; 399 | } 400 | 401 | select, 402 | input[type="text"], 403 | input[type="password"], 404 | input[type="datetime"], 405 | input[type="datetime-local"], 406 | input[type="date"], 407 | input[type="month"], 408 | input[type="time"], 409 | input[type="week"], 410 | input[type="number"], 411 | input[type="email"], 412 | input[type="url"], 413 | input[type="search"], 414 | input[type="tel"], 415 | input[type="color"], 416 | .uneditable-input { 417 | color: white; 418 | height: 21px; 419 | } 420 | select:-moz-placeholder, 421 | input[type="text"]:-moz-placeholder, 422 | input[type="password"]:-moz-placeholder, 423 | input[type="datetime"]:-moz-placeholder, 424 | input[type="datetime-local"]:-moz-placeholder, 425 | input[type="date"]:-moz-placeholder, 426 | input[type="month"]:-moz-placeholder, 427 | input[type="time"]:-moz-placeholder, 428 | input[type="week"]:-moz-placeholder, 429 | input[type="number"]:-moz-placeholder, 430 | input[type="email"]:-moz-placeholder, 431 | input[type="url"]:-moz-placeholder, 432 | input[type="search"]:-moz-placeholder, 433 | input[type="tel"]:-moz-placeholder, 434 | input[type="color"]:-moz-placeholder, 435 | .uneditable-input:-moz-placeholder { 436 | color: #666666; 437 | } 438 | select:-ms-input-placeholder, 439 | input[type="text"]:-ms-input-placeholder, 440 | input[type="password"]:-ms-input-placeholder, 441 | input[type="datetime"]:-ms-input-placeholder, 442 | input[type="datetime-local"]:-ms-input-placeholder, 443 | input[type="date"]:-ms-input-placeholder, 444 | input[type="month"]:-ms-input-placeholder, 445 | input[type="time"]:-ms-input-placeholder, 446 | input[type="week"]:-ms-input-placeholder, 447 | input[type="number"]:-ms-input-placeholder, 448 | input[type="email"]:-ms-input-placeholder, 449 | input[type="url"]:-ms-input-placeholder, 450 | input[type="search"]:-ms-input-placeholder, 451 | input[type="tel"]:-ms-input-placeholder, 452 | input[type="color"]:-ms-input-placeholder, 453 | .uneditable-input:-ms-input-placeholder { 454 | color: #666666; 455 | } 456 | select::-webkit-input-placeholder, 457 | input[type="text"]::-webkit-input-placeholder, 458 | input[type="password"]::-webkit-input-placeholder, 459 | input[type="datetime"]::-webkit-input-placeholder, 460 | input[type="datetime-local"]::-webkit-input-placeholder, 461 | input[type="date"]::-webkit-input-placeholder, 462 | input[type="month"]::-webkit-input-placeholder, 463 | input[type="time"]::-webkit-input-placeholder, 464 | input[type="week"]::-webkit-input-placeholder, 465 | input[type="number"]::-webkit-input-placeholder, 466 | input[type="email"]::-webkit-input-placeholder, 467 | input[type="url"]::-webkit-input-placeholder, 468 | input[type="search"]::-webkit-input-placeholder, 469 | input[type="tel"]::-webkit-input-placeholder, 470 | input[type="color"]::-webkit-input-placeholder, 471 | .uneditable-input::-webkit-input-placeholder { 472 | color: #666666; 473 | } 474 | 475 | textarea { 476 | color: white; 477 | } 478 | textarea:-moz-placeholder { 479 | color: #666666; 480 | } 481 | textarea:-ms-input-placeholder { 482 | color: #666666; 483 | } 484 | textarea::-webkit-input-placeholder { 485 | color: #666666; 486 | } 487 | 488 | select { 489 | height: 29px; 490 | } 491 | 492 | .input-prepend .add-on, 493 | .input-append .add-on { 494 | background: #444; 495 | color: #c6c6c6; 496 | border-color: #111; 497 | text-shadow: 0 1px 0 black; 498 | } 499 | 500 | .form-actions { 501 | border-top-color: #222; 502 | } 503 | 504 | .well .form-actions { 505 | border-top-color: #000; 506 | background-color: rgba(0, 0, 0, 0.3); 507 | margin-left: -17px; 508 | margin-right: -17px; 509 | margin-bottom: -17px; 510 | } 511 | 512 | .help-inline, 513 | .help-block { 514 | color: #999; 515 | } 516 | 517 | .control-group.warning input, .control-group.warning select, .control-group.warning textarea { 518 | color: #faa732; 519 | border-color: #faa732; 520 | -webkit-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 521 | -moz-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 522 | box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 523 | background: #202020; 524 | background-color: rgba(0, 0, 0, 0.3); 525 | } 526 | .control-group.warning input:focus, 527 | .control-group.warning select:focus, 528 | .control-group.warning textarea:focus { 529 | border-color: #faa732; 530 | -webkit-box-shadow: 0 0 6px #faa732; 531 | -moz-box-shadow: 0 0 6px #faa732; 532 | box-shadow: 0 0 6px #faa732; 533 | } 534 | .control-group.warning .control-label, 535 | .control-group.warning .help-block, 536 | .control-group.warning .help-inline { 537 | color: #faa732; 538 | } 539 | .control-group.success input, .control-group.success select, .control-group.success textarea { 540 | color: #5bb75b; 541 | border-color: #5bb75b; 542 | -webkit-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 543 | -moz-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 544 | box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 545 | background: #202020; 546 | background-color: rgba(0, 0, 0, 0.3); 547 | } 548 | .control-group.success input:focus, 549 | .control-group.success select:focus, 550 | .control-group.success textarea:focus { 551 | border-color: #5bb75b; 552 | -webkit-box-shadow: 0 0 6px #5bb75b; 553 | -moz-box-shadow: 0 0 6px #5bb75b; 554 | box-shadow: 0 0 6px #5bb75b; 555 | } 556 | .control-group.success .control-label, 557 | .control-group.success .help-block, 558 | .control-group.success .help-inline { 559 | color: #5bb75b; 560 | } 561 | .control-group.error input, .control-group.error select, .control-group.error textarea { 562 | color: #fc5b5e; 563 | border-color: #fc5b5e; 564 | -webkit-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 565 | -moz-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 566 | box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 567 | background: #202020; 568 | background-color: rgba(0, 0, 0, 0.3); 569 | } 570 | .control-group.error input:focus, 571 | .control-group.error select:focus, 572 | .control-group.error textarea:focus { 573 | border-color: #fc5b5e; 574 | -webkit-box-shadow: 0 0 6px #fc5b5e; 575 | -moz-box-shadow: 0 0 6px #fc5b5e; 576 | box-shadow: 0 0 6px #fc5b5e; 577 | } 578 | .control-group.error .control-label, 579 | .control-group.error .help-block, 580 | .control-group.error .help-inline { 581 | color: #fc5b5e; 582 | } 583 | .control-group.info input, .control-group.info select, .control-group.info textarea { 584 | color: #3a87ad; 585 | border-color: #3a87ad; 586 | -webkit-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 587 | -moz-box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 588 | box-shadow: rgba(255, 255, 255, 0.1) 0 1px 0, rgba(0, 0, 0, 0.8) 0 1px 7px 0px inset; 589 | background: #202020; 590 | background-color: rgba(0, 0, 0, 0.3); 591 | } 592 | .control-group.info input:focus, 593 | .control-group.info select:focus, 594 | .control-group.info textarea:focus { 595 | border-color: #3a87ad; 596 | -webkit-box-shadow: 0 0 6px #3a87ad; 597 | -moz-box-shadow: 0 0 6px #3a87ad; 598 | box-shadow: 0 0 6px #3a87ad; 599 | } 600 | .control-group.info .control-label, 601 | .control-group.info .help-block, 602 | .control-group.info .help-inline { 603 | color: #3a87ad; 604 | } 605 | 606 | input:focus:invalid, 607 | textarea:focus:invalid, 608 | select:focus:invalid { 609 | border-color: #fc5b5e; 610 | } 611 | 612 | input:focus:invalid:focus, 613 | textarea:focus:invalid:focus, 614 | select:focus:invalid:focus { 615 | border-color: #fc5b5e; 616 | box-shadow: 0 0 6px #fc5b5e; 617 | } 618 | 619 | .btn-link { 620 | text-shadow: none; 621 | } 622 | 623 | .img-polaroid { 624 | background-color: #111; 625 | background-color: rgba(0, 0, 0, 0.3); 626 | } 627 | 628 | .nav-tabs .open .dropdown-toggle, 629 | .nav-pills .open .dropdown-toggle, 630 | .nav > .open.active > a:hover { 631 | background-color: rgba(0, 0, 0, 0.25); 632 | border-color: transparent transparent #666666 transparent; 633 | } 634 | 635 | .nav > .dropdown.active > a:hover { 636 | color: #fff; 637 | } 638 | 639 | .nav-tabs .active .dropdown-toggle .caret, 640 | .nav-pills .active .dropdown-toggle .caret { 641 | border-top-color: #fff; 642 | } 643 | 644 | .nav-tabs { 645 | border-bottom: 1px solid #666666; 646 | } 647 | .nav-tabs > .active > a, .nav-tabs > .active > a:hover { 648 | background-color: #2f2f2f; 649 | color: #fff; 650 | border-color: #666666 #666666 transparent #666666; 651 | } 652 | .nav-tabs > li > a:hover { 653 | border-color: #2f2f2f #2f2f2f #666666 #2f2f2f; 654 | background-color: rgba(0, 0, 0, 0.25); 655 | color: #00aaff; 656 | } 657 | .nav-tabs.nav-stacked > li > a, .nav-tabs.nav-stacked > li > a:hover { 658 | border-color: #666; 659 | } 660 | 661 | .well > .nav-tabs > .active > a, .well > .nav-tabs > .active > a:hover { 662 | background-color: #202020; 663 | } 664 | 665 | .nav-pills > li > a:hover { 666 | background-color: rgba(0, 0, 0, 0.25); 667 | color: #00aaff; 668 | } 669 | 670 | .nav-list > li > a, 671 | .nav-list .nav-header { 672 | text-shadow: 0 1px 0 black; 673 | } 674 | 675 | .nav-list > li > a:hover { 676 | background-color: rgba(0, 0, 0, 0.25); 677 | color: #00aaff; 678 | } 679 | 680 | .nav-list .active > a:hover { 681 | background-color: #0088cc; 682 | color: white; 683 | } 684 | 685 | .tabs-below .nav-tabs { 686 | border-top: 1px solid #666666; 687 | } 688 | 689 | .tabs-left .nav-tabs { 690 | border-right: 1px solid #666666; 691 | } 692 | 693 | .tabs-right .nav-tabs { 694 | border-left: 1px solid #666666; 695 | } 696 | 697 | .tabs-below .nav-tabs > li > a:hover { 698 | border-top: 1px solid #666666; 699 | } 700 | 701 | .tabs-left .nav-tabs > li > a:hover { 702 | border-color: transparent #666666 transparent transparent; 703 | } 704 | 705 | .tabs-right .nav-tabs > li > a:hover { 706 | border-color: transparent transparent transparent #666666; 707 | } 708 | 709 | .tabs-below .nav-tabs .active > a, 710 | .tabs-below .nav-tabs .active > a:hover { 711 | border-color: transparent #666666 #666666 #666666; 712 | } 713 | 714 | .tabs-left .nav-tabs .active > a, 715 | .tabs-left .nav-tabs .active > a:hover { 716 | border-color: #666666 transparent #666666 #666666; 717 | } 718 | 719 | .tabs-right .nav-tabs .active > a, 720 | .tabs-right .nav-tabs .active > a:hover { 721 | border-color: #666666 #666666 #666666 transparent; 722 | } 723 | 724 | .nav-list > li > a, 725 | .nav-list .nav-header { 726 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5); 727 | } 728 | 729 | .nav-tabs > li > a:hover { 730 | border-color: transparent transparent #666666 transparent; 731 | } 732 | 733 | .nav > .disabled > a:hover { 734 | color: #999; 735 | } 736 | 737 | .nav-list .divider { 738 | background-color: transparent; 739 | -webkit-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 740 | -moz-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 741 | box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; 742 | border-bottom: 1px solid #121212; 743 | } 744 | 745 | .navbar .brand { 746 | text-shadow: 0 1px 0 black; 747 | } 748 | 749 | .navbar .divider-vertical { 750 | border: transparent; 751 | -webkit-box-shadow: rgba(255, 255, 255, 0.07) 1px 0 0; 752 | -moz-box-shadow: rgba(255, 255, 255, 0.07) 1px 0 0; 753 | box-shadow: rgba(255, 255, 255, 0.07) 1px 0 0; 754 | border-right: 1px solid #121212; 755 | } 756 | 757 | .navbar-inverse .brand { 758 | color: #555; 759 | text-shadow: 0 1px 0 white; 760 | } 761 | .navbar-inverse .brand:hover { 762 | color: #555; 763 | } 764 | .navbar-inverse .navbar-inner { 765 | background: #fafafa; 766 | border: 1px solid #030303; 767 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5); 768 | background: -moz-linear-gradient(top, white 0%, #999999 100%); 769 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #999999)); 770 | background: -webkit-linear-gradient(top, white 0%, #999999 100%); 771 | background: -o-linear-gradient(top, white 0%, #999999 100%); 772 | background: -ms-linear-gradient(top, white 0%, #999999 100%); 773 | background: linear-gradient(to bottom, #ffffff 0%, #999999 100%); 774 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#999999',GradientType=0 ); 775 | } 776 | .navbar-inverse .nav > li > a { 777 | color: #555; 778 | } 779 | .navbar-inverse .nav > li > a:hover { 780 | color: #333; 781 | } 782 | .navbar-inverse .nav > .active > a, 783 | .navbar-inverse .nav > .active > a:hover { 784 | background-color: #e5e5e5; 785 | box-shadow: 0 3px 8px rgba(0, 0, 0, 0.125) inset; 786 | color: #555555; 787 | } 788 | .navbar-inverse .nav li.dropdown.open > .dropdown-toggle, 789 | .navbar-inverse .nav li.dropdown.active > .dropdown-toggle, 790 | .navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle { 791 | background-color: #e5e5e5; 792 | color: #555; 793 | } 794 | .navbar-inverse .nav li.dropdown > a:hover .caret { 795 | border-top-color: #555; 796 | color: #555; 797 | } 798 | .navbar-inverse .nav > li > a:focus, 799 | .navbar-inverse .nav > li > a:hover { 800 | background-color: transparent; 801 | color: #333; 802 | } 803 | .navbar-inverse .nav li.dropdown.open > .dropdown-toggle, 804 | .navbar-inverse .nav li.dropdown.active > .dropdown-toggle, 805 | .navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle { 806 | background-color: #e5e5e5; 807 | color: #555; 808 | } 809 | .navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret, 810 | .navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret, 811 | .navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret { 812 | border-bottom-color: #555; 813 | border-top-color: #555; 814 | color: #555; 815 | } 816 | .navbar-inverse .navbar-search .search-query { 817 | box-shadow: 0 3px 8px rgba(0, 0, 0, 0.6) inset; 818 | background-color: white; 819 | color: #333; 820 | } 821 | .navbar-inverse .navbar-search input.search-query:focus { 822 | box-shadow: 0 3px 8px rgba(0, 0, 0, 0.6) inset, 0 0 8px rgba(82, 168, 236, 0.6); 823 | box-shadow: 0 3px 8px rgba(0, 0, 0, 0.6) inset, 0 0 8px rgba(82, 168, 236, 0.9); 824 | padding: 4px 14px; 825 | outline: 0 none; 826 | } 827 | .navbar-inverse .nav li.dropdown > .dropdown-toggle .caret { 828 | border-bottom-color: #555; 829 | border-top-color: #555; 830 | } 831 | .navbar-inverse .nav li.dropdown > a:hover .caret { 832 | border-bottom-color: #333; 833 | border-top-color: #333; 834 | } 835 | .navbar-inverse .navbar-search .search-query:-moz-placeholder { 836 | color: #999; 837 | } 838 | 839 | .pagination ul > li > a, 840 | .pagination ul > li > span { 841 | background: transparent; 842 | border-color: #666; 843 | } 844 | 845 | .pagination ul > li > a:hover, 846 | .pagination ul > .active > a, 847 | .pagination ul > .active > span { 848 | background-color: rgba(0, 0, 0, 0.25); 849 | } 850 | 851 | .pager li > a, .pager li > span { 852 | background-color: transparent; 853 | border-color: #666; 854 | } 855 | 856 | .pager li > a:hover { 857 | background-color: rgba(0, 0, 0, 0.25); 858 | } 859 | 860 | .pager .disabled > a, 861 | .pager .disabled > a:hover, 862 | .pager .disabled > span { 863 | background-color: transparent; 864 | } 865 | 866 | .label, 867 | .badge { 868 | text-shadow: 1px 1px 0 black; 869 | box-shadow: 1px 1px 0 black; 870 | } 871 | 872 | .label-inverse, 873 | .badge-inverse { 874 | background-color: #111; 875 | } 876 | 877 | .hero-unit { 878 | background: #111; 879 | color: #ccc; 880 | } 881 | 882 | .thumbnail { 883 | border-color: #666; 884 | box-shadow: 0 1px 3px black; 885 | } 886 | .thumbnail .caption { 887 | color: #999; 888 | } 889 | 890 | .alert { 891 | color: white; 892 | border-color: #a86404; 893 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); 894 | } 895 | .alert h1, .alert h2, .alert h3, .alert h4, .alert h5, .alert h6 { 896 | color: #c17305; 897 | } 898 | 899 | .alert-error { 900 | border-color: #d40408; 901 | } 902 | .alert-error h1, .alert-error h2, .alert-error h3, .alert-error h4, .alert-error h5, .alert-error h6 { 903 | color: #ed0409; 904 | } 905 | 906 | .alert-success { 907 | border-color: #2d662d; 908 | } 909 | .alert-success h1, .alert-success h2, .alert-success h3, .alert-success h4, .alert-success h5, .alert-success h6 { 910 | color: #347834; 911 | } 912 | 913 | .alert-info { 914 | border-color: #1a3c4e; 915 | } 916 | .alert-info h1, .alert-info h2, .alert-info h3, .alert-info h4, .alert-info h5, .alert-info h6 { 917 | color: #204B61; 918 | } 919 | 920 | select::-webkit-scrollbar { 921 | -webkit-appearance: none; 922 | width: 11px; 923 | } 924 | select::-webkit-scrollbar-thumb { 925 | border-radius: 8px; 926 | border: 2px solid #202020; 927 | background-color: rgba(0, 0, 0, 0.5); 928 | } 929 | 930 | .modal { 931 | background-color: #444; 932 | } 933 | 934 | .modal-header { 935 | border-bottom: 1px solid #222222; 936 | } 937 | 938 | .modal-body p { 939 | color: #c6c6c6; 940 | } 941 | 942 | .modal-footer { 943 | background-color: #373737; 944 | border-top: 1px solid #222222; 945 | -moz-box-shadow: 0 1px 0 #333333 inset; 946 | -webkit-box-shadow: 0 1px 0 #333333 inset; 947 | -o-box-shadow: 0 1px 0 #333333 inset; 948 | box-shadow: 0 1px 0 #333333 inset; 949 | } 950 | 951 | .popover { 952 | background: #444; 953 | border: 1px solid rgba(0, 0, 0, 0.5); 954 | border: 1px solid black; 955 | } 956 | 957 | .popover-title { 958 | background: #373737; 959 | border-bottom-color: #222; 960 | } 961 | 962 | .popover.top .arrow:after { 963 | border-top-color: #444; 964 | } 965 | 966 | .popover.right .arrow:after { 967 | border-right-color: #444; 968 | } 969 | 970 | .popover.bottom .arrow:after { 971 | border-bottom-color: #444; 972 | } 973 | 974 | .popover.left .arrow:after { 975 | border-left-color: #444; 976 | } 977 | -------------------------------------------------------------------------------- /web/app/assets/stylesheets/framework_and_overrides.css.scss: -------------------------------------------------------------------------------- 1 | // import the CSS framework 2 | @import "bootstrap"; 3 | 4 | // make all images responsive by default 5 | img { 6 | @extend .img-responsive; 7 | margin: 0 auto; 8 | } 9 | // override for the 'Home' navigation link 10 | .navbar-brand { 11 | font-size: inherit; 12 | } 13 | 14 | // THESE ARE EXAMPLES YOU CAN MODIFY 15 | // create your own classes 16 | // to make views framework-neutral 17 | .column { 18 | @extend .col-md-6; 19 | @extend .text-center; 20 | } 21 | .form { 22 | @extend .col-md-6; 23 | } 24 | .form-centered { 25 | @extend .col-md-6; 26 | @extend .text-center; 27 | } 28 | .submit { 29 | @extend .btn; 30 | @extend .btn-primary; 31 | @extend .btn-lg; 32 | } 33 | // apply styles to HTML elements 34 | // to make views framework-neutral 35 | main { 36 | @extend .container; 37 | background-color: #eee; 38 | padding-bottom: 80px; 39 | width: 100%; 40 | margin-top: 51px; // accommodate the navbar 41 | } 42 | section { 43 | @extend .row; 44 | margin-top: 20px; 45 | } 46 | -------------------------------------------------------------------------------- /web/app/assets/stylesheets/overrides.css: -------------------------------------------------------------------------------- 1 | 2 | td { 3 | padding-right: 8px !important; 4 | } 5 | 6 | .container { 7 | margin-right: 0 !important; 8 | margin-left: 0 !important; 9 | width: 100% !important; 10 | } 11 | 12 | body { 13 | color: white; 14 | fill: white !important; 15 | } 16 | 17 | main { 18 | background-color: #333; 19 | margin-top: 100px; 20 | } 21 | 22 | line, path { 23 | stroke: white !important; 24 | } 25 | 26 | .navbar { 27 | color: inherit; 28 | background-image: -webkit-gradient(linear, left 0, left 100%, from(#1c62bc), to(#0f5097)); 29 | background-image: -webkit-linear-gradient(top, #1c62bc, 0, #0f5097, 100%); 30 | background-image: -moz-linear-gradient(top, #1c62bc 0, #0f5097 100%); 31 | background-image: linear-gradient(to bottom, #1c62bc 0, #0f5097 100%); 32 | background-repeat: repeat-x; 33 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1c62bc', endColorstr='#ff0f5097', GradientType=0); 34 | background-color: #0f5097; 35 | border-top: 1px solid #0f5097; 36 | border-bottom: 1px solid #1a428a; 37 | } 38 | 39 | .navbar .nav>li>a { 40 | color: #cfe1e8 !important; 41 | text-decoration: none !important; 42 | border-radius: 0 !important; 43 | font-size:16px !important; 44 | } 45 | .navbar .nav>li>a:hover { 46 | background: transparent !important; 47 | color: #fff !important; 48 | border-radius: 0px !important; 49 | } 50 | 51 | .navbar-brand, a.navbar-brand:hover { 52 | color: #fff !important; 53 | font-size: 26px !important; 54 | font-weight: normal !important; 55 | } -------------------------------------------------------------------------------- /web/app/controllers/analysis_controller.rb: -------------------------------------------------------------------------------- 1 | require 'rethinkdb' 2 | 3 | ### Remove later, abstract DB accesses to DbController 4 | include RethinkDB::Shortcuts 5 | 6 | class AnalysisController < DbController 7 | before_filter :db_connect 8 | helper_method :menu_items 9 | 10 | def menu_items 11 | [ 12 | ['GUIDs', "/analysis/guids"], 13 | ["Keywords", "/analysis/keywords"], 14 | ["Vulns", "/analysis/vulnerabilities"], 15 | ["Potential Vulns", "/analysis/similarities"], 16 | ["Capsule Data", "/analysis/capsules"] 17 | ] 18 | end 19 | 20 | def analysis 21 | select_limit = 10 22 | ### Most uses GUIDS 23 | @guids = [] 24 | cursor = @stats_table.get_all("uefi_guid", :index => "type").order_by(r.desc(:result)).limit(select_limit). 25 | map{|doc| 26 | doc.merge({ 27 | "lookup" => @lookup_table.get_all(doc[:key], :index => "guid").coerce_to("array") 28 | })}.run 29 | cursor.each{|guid| @guids.push(flatten_lookup(guid))} 30 | 31 | ### Largest guids 32 | @guids_size = [] 33 | cursor = @objects_table.order_by(:index => r.desc(:size)).has_fields(:guid).pluck(:guid, :size). 34 | limit(select_limit). 35 | map{|doc| 36 | doc.merge({ 37 | "lookup" => @lookup_table.get_all(doc[:guid], :index => "guid").coerce_to("array") 38 | })}.run 39 | cursor.each {|guid| @guids_size.push(flatten_lookup(guid))} 40 | 41 | ### Fastest updates, calculate the change in updates (per-vendor) 42 | cursor = @stats_table.get_all("vendor_update_count", :index => "type"). 43 | map{|doc| 44 | doc.merge({ 45 | "deltas" => @updates_table.get_all(doc[:key], :index => "vendor"). 46 | filter{|sdoc| sdoc[:load_change][:delta] > 0}. 47 | order_by(lambda {|sdoc| sdoc[:load_change][:delta]}).limit(select_limit).coerce_to("array") 48 | }) 49 | }.run 50 | #cursor.each {|vendor| @vendors.push({:name => vendor["key"], :count => vendor["result"]})} 51 | @fast_updates = [] 52 | #cursor = @updates_table.filter{|doc| doc[:load_change][:delta] > 0}. 53 | # order_by(lambda {|doc| doc[:load_change][:delta]}).limit(select_limit).run 54 | #cursor.each do |update| 55 | # if not @fast_updates.has_key?(update["key"]) then @fast_updates[update["key"]] = [] end 56 | # update["deltas"].each {|delta| @fast_updates[update["key"]].push(delta)} 57 | #end 58 | # @fast_updates.push(update)} 59 | cursor.each {|update| update["deltas"].each {|delta| @fast_updates.push(delta)}} 60 | @fast_updates.sort_by! {|update| update["load_change"]["delta"]} 61 | 62 | ### Smallest updates, order by asc load_change->change 63 | cursor = @stats_table.get_all("vendor_update_count", :index => "type"). 64 | map{|doc| 65 | doc.merge({ 66 | "changes" => @updates_table.get_all(doc[:key], :index => "vendor"). 67 | filter{|sdoc| sdoc[:load_change][:change_score] > 0}. 68 | order_by(lambda {|sdoc| sdoc[:load_change][:change_score]}).limit(select_limit).coerce_to("array") 69 | }) 70 | }.run 71 | @small_updates = [] 72 | #cursor = @updates_table.filter{|doc| doc[:load_change][:change_score] > 0}. 73 | # order_by(lambda {|doc| doc[:load_change][:change_score]}).limit(select_limit).run 74 | #cursor.each {|update| @small_updates.push(update)} 75 | cursor.each {|update| update["changes"].each {|change| @small_updates.push(change)}} 76 | @small_updates.sort_by! {|update| update["load_change"]["changes"]} 77 | 78 | ### Most common DXE 79 | @dxes = [] 80 | 81 | ### Most common PEIMs 82 | @peims = [] 83 | 84 | 85 | end 86 | 87 | def keywords 88 | ### Importantce != Optional (Dell) 89 | not_importances = ["Optional", "Recommended"] 90 | @updates = [] 91 | cursor = @updates_table.filter{|update| 92 | update[:attrs][:importance].eq("Urgent") | update[:attrs][:importance].eq("Required") | 93 | update[:attrs][:importance].eq("Critical") 94 | }.order_by(:date).order_by(:vendor).run 95 | cursor.each {|update| 96 | @updates.push(update_dict(update)) 97 | } 98 | puts @updates.length 99 | 100 | ### Trusted-compusing GUIDs 101 | ### (lookup-> important(reason->trusted,security,vulnerable), references, [optional]key) 102 | ### (objects-> actions[trusted]) 103 | 104 | ### Release notes with security, vulnerability, exploit 105 | 106 | end 107 | 108 | def similarities 109 | ### Assembly with function calls (CopyMem) 110 | 111 | end 112 | 113 | def vulnerabilities 114 | ### Updates identified as being a vulnerability fix 115 | ### (update-> patch(notes, references, [optional]key)) 116 | ### (objects-> actions[vulnerable]) 117 | 118 | ### FW PEIMs/DXEs using the network 119 | 120 | ### Changes to high-profile GUIDs (and trusted computing, update-related GUIDs) 121 | 122 | end 123 | 124 | def trusted 125 | ### List guids related to trusted computing/secure boot/key storage 126 | end 127 | 128 | def guids 129 | @guids = [] 130 | cursor = @stats_table.get_all("uefi_guid", :index => "type").map{|doc| 131 | doc.merge({ 132 | "lookup" => @lookup_table.get_all(doc[:key], :index => "guid").coerce_to("array") 133 | }) 134 | }.run 135 | cursor.each do |lookup| 136 | lookup = flatten_lookup(lookup) 137 | @guids.push({ 138 | :guid => lookup["key"], 139 | :name => lookup["guid_name"], 140 | :count => lookup["result"] 141 | }) 142 | end 143 | 144 | @guids.sort! { |guid1, guid2| guid1[:count] <=> guid2[:count]} 145 | @guids.reverse! 146 | puts @guids.length 147 | end 148 | 149 | def guid 150 | @guid = params[:id] 151 | @objects = [] 152 | cursor = object_query(@objects_table.get_all(@guid, :index => "guid"). 153 | ### TESTING 154 | order_by(:size).limit(20) 155 | ).run 156 | cursor.each do |obj| 157 | @objects.push(get_object_info(obj)) 158 | end 159 | 160 | @objects = @objects.paginate(:page => params[:page], :per_page => 30) 161 | 162 | end 163 | 164 | private 165 | def flatten_lookup(obj) 166 | blacklist = ["guid", "id"] 167 | if obj.has_key?("lookup") and obj["lookup"].length > 0 168 | obj["lookup"][0].each do |key, value| 169 | next if blacklist.include?(key) 170 | obj[key] = value 171 | end 172 | end 173 | return obj 174 | end 175 | 176 | end -------------------------------------------------------------------------------- /web/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | helper_method :menu_items 6 | def menu_items 7 | [] 8 | end 9 | 10 | end -------------------------------------------------------------------------------- /web/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /web/app/controllers/db_controller.rb: -------------------------------------------------------------------------------- 1 | require 'rethinkdb' 2 | #require "base64" 3 | 4 | include RethinkDB::Shortcuts 5 | 6 | class DbController < ApplicationController 7 | before_filter :db_connect 8 | 9 | private 10 | def db_connect 11 | r.connect(:host => "localhost").repl 12 | @db = r.db("uefi") 13 | @objects_table = r.db("uefi").table("objects") 14 | @stats_table = r.db("uefi").table("stats") 15 | @updates_table = r.db("uefi").table("updates") 16 | @content_table = r.db("uefi").table("content") 17 | @lookup_table = r.db("uefi").table("lookup") 18 | end 19 | 20 | def update_dict(doc) 21 | return { 22 | :name => doc["name"], 23 | :version => doc["version"], 24 | :date => doc["date"], 25 | :vendor => doc["vendor"], 26 | :item_id => doc["item_id"], 27 | :firmware_id => doc["firmware_id"], 28 | :size => doc["size"], 29 | :status => doc["attrs"]["status"], 30 | :load_change => if doc.has_key?("load_change") then doc["load_change"] else {} end, 31 | :stats => doc["stats"], 32 | 33 | :actions => if doc.has_key?("actions") then doc["actions"] else nil end, 34 | :importance => doc["attrs"]["importance"] 35 | } 36 | end 37 | 38 | def object_query(sequence) 39 | return sequence.map{|doc| r.branch(doc.has_fields([:guid]), doc.merge({ 40 | ### Add in map-reduces 41 | "shared" => @stats_table.get_all(["uefi_guid", doc[:guid]], :index => "type_key").pluck("result").coerce_to('array'), 42 | "matches" => @stats_table.get_all(["object_id", doc[:object_id]], :index => "type_key").pluck("result").coerce_to('array'), 43 | "lookup" => @lookup_table.get_all(doc[:guid], :index => "guid").coerce_to("array") 44 | }), doc.merge({}) 45 | )}.map{|doc|doc.merge({ 46 | "content" => @content_table.get_all(doc["object_id"], :index => "object_id").pluck("attrs", "load_meta").coerce_to("array") 47 | })}.order_by(r.desc(lambda {|doc| doc[:size]})) 48 | end 49 | 50 | def object_stats! (_obj) 51 | 52 | end 53 | 54 | def percent_change (_obj) 55 | size = _obj.has_key?("size") ? _obj["size"] : _obj["attrs"]["size"] 56 | score = _obj["load_change"]["change_score"] 57 | return (score/(size * 1.0))*100 58 | end 59 | 60 | def lookups 61 | if @lookups != nil then return @lookups end 62 | 63 | ### Search for optional lookup values which better describe each file 64 | @lookups = {} 65 | cursor = r.db("uefi").table("lookup").run 66 | cursor.each{ |lookup| @lookups[lookup["guid"]] = lookup } 67 | return @lookups 68 | end 69 | 70 | def add_object_stats! (obj, attrs = true, meta = true) 71 | obj["stats"] = {} 72 | if attrs then obj["stats"] = obj["attrs"] end 73 | if meta and obj.has_key?("content") and obj["content"].length > 0 74 | if obj["content"][0].has_key?("load_meta") 75 | #obj["stats"]["Magic"] = obj["content"][0]["load_meta"]["magic"] 76 | obj["load_meta"] = obj["content"][0]["load_meta"] 77 | #obj["stats"].merge(obj["content"][0]["load_meta"]) 78 | end 79 | end 80 | 81 | if obj.has_key? ("load_change") 82 | if obj["load_change"].has_key? ("change_score") and obj["load_change"]["change_score"] > 0 83 | obj["stats"]["Changed"] = "%d bytes, %.2f%%" % [obj["load_change"]["change_score"], percent_change(obj)] 84 | end 85 | if obj["load_change"].has_key? ("new_file") 86 | obj["stats"]["New File"] = true 87 | end 88 | end 89 | end 90 | 91 | def add_lookups! (_obj) 92 | lookups = lookups() 93 | if lookups.has_key?(_obj["guid"]) 94 | lookups[_obj["guid"]].each do |key, value| 95 | next if ["guid", "id"].include?(key) 96 | _obj[key] = "*%s" % value 97 | end 98 | end 99 | end 100 | 101 | def get_object_info(_obj) 102 | ### Requires: firmware_id, children, attrs 103 | #@firmware_id = obj["firmware_id"] 104 | add_lookups!(_obj) 105 | add_object_stats!(_obj, attrs = false, meta = true) 106 | 107 | ### This is a different type of stats 108 | objects_count = if _obj.has_key?("children") then _obj["children"].length else 0 end 109 | unless objects_count == 0 then _obj["stats"]["Children"] = objects_count end 110 | 111 | ### Handle various lookups data from lookup table 112 | if _obj.has_key?("lookup") and _obj["lookup"].length > 0 113 | if _obj["lookup"][0].has_key?("guid_name") then _obj["guid_name"] = _obj["lookup"][0]["guid_name"] end 114 | if _obj["lookup"][0].has_key?("guid_actions") then _obj["guid_actions"] = _obj["lookup"][0]["guid_actions"] end 115 | end 116 | 117 | unless _obj.has_key?("attrs") then _obj["attrs"] = {} end 118 | if _obj["type"] == "uefi_file" 119 | _obj["info"] = { 120 | #"Attrs" => _obj["attrs"]["attributes"], 121 | "FileType" => _obj["attrs"]["type_name"], 122 | } 123 | 124 | ### Requires a map-reduce 125 | unless _obj["shared"].length == 0 then _obj["stats"]["Shared"] = _obj["shared"][0]["result"] end 126 | unless _obj["matches"].length == 0 then _obj["stats"]["Matches"] = _obj["matches"][0]["result"] end 127 | else 128 | if _obj.has_key?("attrs") and _obj["attrs"].has_key?("type_name") 129 | _obj["info"] = { 130 | "SectionType" => _obj["attrs"]["type_name"] 131 | } 132 | end 133 | end 134 | 135 | return _obj 136 | end 137 | 138 | end 139 | -------------------------------------------------------------------------------- /web/app/controllers/explorer_controller.rb: -------------------------------------------------------------------------------- 1 | require 'rethinkdb' 2 | require "base64" 3 | 4 | ### Remove later, abstract DB accesses to DbController 5 | include RethinkDB::Shortcuts 6 | 7 | class ExplorerController < DbController 8 | before_filter :db_connect 9 | 10 | #def initialize 11 | # @PER_PAGE = 30 12 | #end 13 | 14 | def sort_direction 15 | %w[asc desc].include?(params[:direction]) ? params[:direction] : "asc" 16 | end 17 | 18 | def explorer 19 | ### There is nothing on this page for now 20 | @vendors = [] 21 | cursor = @stats_table.get_all("vendor_update_count", :index => "type").run 22 | cursor.each {|vendor| @vendors.push({:name => vendor["key"], :count => vendor["result"]})} 23 | 24 | end 25 | 26 | def products 27 | @vendor = params[:vendor] 28 | @page_num = if params.has_key?(:page) then params[:page].to_i-1 else 0 end 29 | @products = {} 30 | ### Iterate the updates and count the number per machine 31 | updates = r.db("uefi").table("updates").get_all(@vendor, :index => "vendor"). 32 | order_by(r.desc(:date)). 33 | #pluck("version", "products", "date", "vendor", "item_id", 34 | # "attrs", "name", "firmware_id", "size", "load_change"). 35 | run 36 | ### Do sorting here 37 | 38 | updates.each do |doc| 39 | doc["products"].each do |product| 40 | unless @products.has_key?(product) 41 | @products[product] = [] 42 | end 43 | ### Add the version/date/vendor 44 | add_object_stats!(doc, false) 45 | @products[product].push(update_dict(doc)) 46 | end 47 | end 48 | 49 | @products_keys = @products.keys.paginate(:page => params[:page], :per_page => 60) 50 | ### Leave counting/stats up to the viewer. 51 | end 52 | 53 | def sort_product_column 54 | keys = ["date", "name", "size"] 55 | #Product.column_names.include?(params[:sort]) ? params[:sort] : "name" 56 | end 57 | 58 | def download 59 | @object_id = params[:object_id] 60 | 61 | object = nil 62 | cursor = r.db("uefi").table("content").get_all(@object_id, :index => "object_id").limit(1).run 63 | cursor.each {|obj| object = obj } 64 | if object == nil 65 | return 66 | end 67 | 68 | send_data Base64.decode64(object["content"]), 69 | :filename => "%s-%s.obj" % [object["firmware_id"], object["guid"]], :disposition => 'attachment' 70 | end 71 | 72 | def raw 73 | @firmware_id = params[:firmware_id] 74 | @id = params[:id] 75 | 76 | ### Get Information about object 77 | cursor = r.db("uefi").table("objects").get_all(@firmware_id, :index => "firmware_id"). 78 | filter{|obj| obj["id"].eq(@id)}. 79 | pluck("name", "guid", "description", "attrs", "load_change", "size", "id", "load_meta").limit(1).run 80 | 81 | ### Silly construct 82 | cursor.each{ |obj| @object = obj } 83 | add_object_stats!(@object) 84 | end 85 | 86 | def file 87 | @firmware_id = params[:firmware_id] 88 | @guid = params[:id] 89 | 90 | ### Get Information about File 91 | cursor = r.db("uefi").table("objects").get_all(@firmware_id, :index => "firmware_id"). 92 | filter{|file| file["guid"].eq(@guid) }. 93 | pluck("name", "guid", "description", "attrs", "load_change", "size").limit(1).run 94 | 95 | ### Silly construct 96 | cursor.each{ |file| @file = file } 97 | 98 | ### Stats will display a table of key=>value details 99 | @stats = { 100 | "name" => @file.has_key?("name") ? @file["name"] : "", 101 | "description" => @file.has_key?("description") ? @file["description"] : "", 102 | } 103 | @stats = @stats.deep_merge(@file["attrs"]) 104 | 105 | ### Collect objects within this file 106 | @objects = [] 107 | cursor = r.db("uefi").table("objects").filter{|obj| 108 | (obj["firmware_id"].eq(@firmware_id)) & (obj["guid"].eq(@guid)) 109 | }.pluck("attrs", "load_meta", "load_change", "id"). 110 | order_by(r.desc(lambda {|doc| doc[:attrs][:size]})).run 111 | 112 | cursor.each do |obj| 113 | add_object_stats!(obj) 114 | @objects.push(obj) 115 | end 116 | 117 | ### This applies to objects 118 | if @file.has_key?("load_meta") 119 | @stats.merge(@file["load_meta"]) {|key, a_val, b_val| a_val.merge b_val } 120 | end 121 | 122 | end 123 | 124 | def firmware 125 | @depth = 1 126 | @object_id = params[:id] 127 | @firmware_id = "None" 128 | @firmware_object = {} 129 | 130 | ### Get the base firmware object 131 | ### Todo: pluck from updates: .pluck("date", "attrs", "item_id", "name", "vendor", "version") 132 | cursor = @objects_table.get_all(@object_id, :index => "object_id").eq_join( 133 | 'firmware_id', @updates_table, :index => "firmware_id" 134 | ).order_by(r.desc(lambda {|doc| doc[:size]})).limit(1).run 135 | 136 | cursor.each do |obj| 137 | obj["right"].each do |key, value| 138 | if not ["load_meta", "load_change"].include?(key) then obj["left"][key] = value end 139 | end 140 | puts obj["left"]["firmware_id"] 141 | @firmware_id = obj["left"]["firmware_id"] 142 | @firmware_object = get_object_info(obj["left"]) 143 | end 144 | 145 | ### Keep a hash of child_id -> object id 146 | child_map = {} 147 | child_ids = [] 148 | if @firmware_object.has_key? ("children") 149 | child_ids = @firmware_object["children"].dup 150 | child_ids.each {|id| child_map[id] = @firmware_object} 151 | end 152 | @firmware_object["objects"] = [] 153 | 154 | ### Embedded object may paginate better 155 | if @firmware_object["firmware_id"] != @firmware_object["object_id"] then @depth += 1 end 156 | 157 | ### Get the children objects 158 | depth_index = 0 159 | while child_ids.length > 0 and depth_index < @depth 160 | depth_index += 1 161 | cursor = @objects_table.get_all(*child_ids). 162 | #### TESTING 163 | #limit(20). 164 | #### END TESTING 165 | map{|doc| r.branch(doc.has_fields([:guid]), doc.merge({ 166 | ### Add in map-reduces 167 | "shared" => @stats_table.get_all(["uefi_guid", doc[:guid]], :index => "type_key").pluck("result").coerce_to('array'), 168 | "matches" => @stats_table.get_all(["object_id", doc[:object_id]], :index => "type_key").pluck("result").coerce_to('array'), 169 | "lookup" => @lookup_table.get_all(doc[:guid], :index => "guid").coerce_to("array") 170 | }), doc.merge({}) 171 | )}.map{|doc|doc.merge({ 172 | "content" => @content_table.get_all(doc["object_id"], :index => "object_id").pluck("attrs", "load_meta").coerce_to("array") 173 | })}.order_by(r.desc(lambda {|doc| doc[:size]})).run 174 | 175 | child_ids = [] 176 | cursor.each do |obj| 177 | ### Add this object to it's parent 178 | obj["objects"] = [] 179 | child_map[obj["id"]]["objects"].push(get_object_info(obj)) 180 | #@objects.push(get_object_info(obj)) 181 | if obj.has_key?("children") 182 | obj["children"].each{|id| child_map[id] = obj} 183 | child_ids = child_ids.concat(obj["children"].dup) 184 | end 185 | end 186 | end 187 | 188 | @changed = [] 189 | @objects = @firmware_object["objects"] 190 | @objects.each do |obj| 191 | if obj.has_key?("load_change") and obj["load_change"]["change_score"] > 32 192 | @changed.push(obj) 193 | end 194 | end 195 | 196 | @objects = @objects.paginate(:page => params[:page], :per_page => 30) 197 | 198 | end 199 | 200 | def uefi 201 | @firmware_id = params[:id] 202 | @files = [] 203 | 204 | ### Search for objects, later bind them to each file listed. 205 | objects = {} 206 | cursor = r.db("uefi").table("objects").filter{|obj| obj["firmware_id"].eq(@firmware_id)}. 207 | pluck("attrs", "guid", "load_meta"). 208 | order_by(r.desc(lambda {|doc| doc[:attrs][:size]})).run 209 | cursor.each do |obj| 210 | unless objects.has_key? (obj["guid"]) 211 | objects[obj["guid"]] = [] 212 | end 213 | objects[obj["guid"]].push(obj) 214 | end 215 | 216 | ### Finally, search for files belonging to this firmware_id 217 | cursor = r.db("uefi").table("files").filter{|file| file["firmware_id"].eq(@firmware_id)}. 218 | pluck("name", "guid", "description", "attrs", "load_change", "size"). 219 | order_by(r.desc(lambda {|doc| doc[:attrs][:size]})).run 220 | cursor.each do |file| 221 | if objects.has_key? (file["guid"]) 222 | file["objects"] = objects[file["guid"]] 223 | else 224 | file["objects"] = [] 225 | end 226 | 227 | add_lookups!(file) 228 | ### Add an assortment of stats 229 | add_object_stats!(file, attrs = false, meta = false) 230 | @files.push(file) 231 | end 232 | 233 | end 234 | 235 | def actions 236 | ### Set on an object, and push to the update (use firmware_id as reference) 237 | ### {actions/guid_actions => {vulnerable, trusted, network}} # all booleans (then can look backwards) 238 | ### vulnerable implies this update has fixed a vulnerability, so the most-recent compartively 239 | ### will be marked vulnerable (and cannot be changed) 240 | @name = params[:name] 241 | @value = params[:actions][:value] 242 | @object_id = params[:actions][:id] 243 | @firmware_id = params[:actions][:firmware_id] 244 | 245 | success = true 246 | unless @value == "locked" 247 | new_value = @value == "true" ? "false" : "true" 248 | if @name == "vulnerable" 249 | @objects_table.get(@object_id).update({"actions" => {"vulnerable" => new_value}}).run 250 | cursor = @updates_table.get_all(@firmware_id, :index=> "firmware_id").run 251 | firmware_update = nil 252 | cursor.each{ |update| firmware_update = update} 253 | if firmware_update != nil 254 | actions = firmware_update.has_key?("actions") ? firmware_update["actions"] : {"vulnerable" => []} 255 | if new_value == "true" and not actions["vulnerable"].include?(@object_id) 256 | actions["vulnerable"].push(@object_id) 257 | elsif new_value == "false" and actions["vulnerable"].include?(@object_id) 258 | actions["vulnerable"] = actions["vulnerable"] - [@object_id] 259 | end 260 | @updates_table.get(firmware_update["id"]).update({"actions" => actions}).run 261 | puts actions 262 | end 263 | end 264 | 265 | if @name == "trusted" or @name == "network" 266 | object_data = @objects_table.get(@object_id).run 267 | if object_data.has_key? ("guid") 268 | guid = nil 269 | cursor = @lookup_table.get_all(object_data["guid"], :index => "guid").run 270 | cursor.each {|doc| guid = doc} 271 | if guid != nil 272 | actions = guid.has_key?("actions") ? guid["guid_actions"] : {} 273 | actions[@name] = new_value 274 | @lookup_table.get(guid["id"]).update({"guid_actions" => actions}).run 275 | puts guid["id"] 276 | else 277 | @lookup_table.insert({"guid" => object_data["guid"], "guid_actions" => {@name => new_value}}).run 278 | end 279 | end 280 | #cursor = @lookup_table.get_all() 281 | end 282 | 283 | end 284 | 285 | respond_to do |format| 286 | format.json { render :json => { 287 | :success => success, 288 | :value => new_value, 289 | 290 | :type => @name, 291 | :previous => @value, 292 | :id => @object_id 293 | } } 294 | end 295 | end 296 | 297 | 298 | end -------------------------------------------------------------------------------- /web/app/controllers/visitors_controller.rb: -------------------------------------------------------------------------------- 1 | class VisitorsController < ApplicationController 2 | 3 | def new 4 | end 5 | 6 | end 7 | -------------------------------------------------------------------------------- /web/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /web/app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/mailers/.keep -------------------------------------------------------------------------------- /web/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/models/.keep -------------------------------------------------------------------------------- /web/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/app/models/concerns/.keep -------------------------------------------------------------------------------- /web/app/views/analysis/analysis.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Analysis 3 | 4 | %h3 5 | %div Subzero Analysis 6 | 7 | .container 8 | .row 9 | .col-md-6 10 | %h4 Top UEFI GUIDs (count) 11 | %table 12 | - @guids.each do |guid| 13 | %tr 14 | %td 15 | %b= guid["result"] 16 | %td 17 | .guid= guid["key"] 18 | %div 19 | %small 20 | #{guid["guid_name"]} 21 | .col-md-6 22 | %h4 Top UEFI GUIDs (size) 23 | %table 24 | - @guids_size.each do |guid| 25 | %tr 26 | %td 27 | %b= guid["size"] 28 | %td 29 | .guid 30 | = guid["guid"] 31 | 32 | .row 33 | .col-md-6 34 | %h4 Fastest Updates 35 | %table 36 | - @fast_updates.each do |update| 37 | %tr 38 | %td 39 | = update["vendor"] 40 | %td 41 | %a{ :href => "/explorer/firmware/#{update["firmware_id"]}"} 42 | = update["item_id"] 43 | %td 44 | #{update["load_change"]["delta"]}s 45 | 46 | .col-md-6 47 | %h4 Smallest Updates 48 | %table 49 | - @small_updates.each do |update| 50 | %tr 51 | %td= update["vendor"] 52 | %td 53 | %a{ :href => "/explorer/firmware/#{update["firmware_id"]}"} 54 | = update["item_id"] 55 | %td 56 | #{update["load_change"]["change_score"]} bytes -------------------------------------------------------------------------------- /web/app/views/analysis/guid.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Analysis - GUID 3 | 4 | %h3 5 | Subzero GUID: 6 | %a{ :href => "/analysis/guid/#{@guid}/"} 7 | .guid= @guid 8 | 9 | .container 10 | .row 11 | %table.table.table-bordered.table-striped.table-hover 12 | %thead 13 | %tr 14 | %th ObjectID 15 | %th Type 16 | %th Info 17 | %th Size 18 | %th Stats 19 | %th Actions 20 | %tbody 21 | - @objects.each do |obj| 22 | = render :partial => "explorer/object", :locals => { :obj => obj, :source => false, :independent => true, :base => false } 23 | 24 | = will_paginate @objects -------------------------------------------------------------------------------- /web/app/views/analysis/guids.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Analysis - GUIDs 3 | 4 | %h3 5 | %div Subzero GUID List 6 | 7 | .container 8 | .row 9 | .col-md-12 10 | %table 11 | - guid_index = 0 12 | - @guids.each do |guid| 13 | - guid_index += 1 14 | %tr 15 | %td= guid_index 16 | %td= guid[:count] 17 | %td.guid 18 | %a{ :href => "/analysis/guid/#{guid[:guid]}" } 19 | = guid[:guid] 20 | %td= guid[:name] 21 | -------------------------------------------------------------------------------- /web/app/views/analysis/keywords.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Analysis - Keywords 3 | 4 | %h3 5 | %div Subzero Keyword Analysis 6 | 7 | .container 8 | .row 9 | .col-md-12 10 | %table 11 | - update_index = 0 12 | - @updates.each do |update| 13 | - update_index += 1 14 | %tr 15 | = render :partial => "explorer/update", :locals => { :firmware => update, :last_update => 0 } 16 | -------------------------------------------------------------------------------- /web/app/views/explorer/_object.html.haml: -------------------------------------------------------------------------------- 1 | -# base = (just print the base object, no children) 2 | -# independent = (this is not linked to an explorer view) 3 | -# source = (this is a source object with unlinkable children) 4 | - action_classes = {"true" => "text-warning", "false" => "text-muted", "locked" => "text-danger", "vulnerable" => "text-danger"} 5 | %tr 6 | %td 7 | %div 8 | - if obj.has_key?("measured") and obj["measured"] == true 9 | %i.fa.fa-unlock-alt.text-success 10 | - else 11 | %i.fa.fa-unlock-alt.text-warning 12 | - unless obj.has_key?("guid") 13 | [obj] 14 | - if source 15 | %a{ :href => "/explorer/firmware/#{obj["object_id"]}/" } 16 | - unless obj.has_key?("guid") 17 | .guid= obj["object_id"] 18 | - else 19 | .guid= obj["guid"] 20 | - elsif independent 21 | .guid 22 | %a{ :href => "/explorer/firmware/#{obj["object_id"]}/"} 23 | = obj["object_id"] 24 | %div 25 | %small 26 | FirmwareID: 27 | %a{ :href => "/explorer/firmware/#{obj["firmware_id"]}/"} 28 | = obj["firmware_id"] 29 | - else 30 | .guid= obj["object_id"] 31 | - if obj.has_key?("guid_name") 32 | %div 33 | %small= obj["guid_name"] 34 | %td 35 | - if source == false and obj.has_key?("load_meta") 36 | #{obj["load_meta"]["magic"]} 37 | - else 38 | %small 39 | #{obj["vendor"]} 40 | - if source == true or independent == true 41 | (#{obj["type"]}) 42 | %td 43 | - if obj.has_key?("attrs") 44 | - if obj["attrs"].has_key?("name") 45 | .none= obj["attrs"]["name"] 46 | - if obj["attrs"].has_key?("description") 47 | .none= obj["attrs"]["description"] 48 | - if obj.has_key?("info") 49 | - obj["info"].each do |key, value| 50 | %b= key 51 | %small= value 52 | - if obj.has_key?("load_meta") 53 | - if source == true or independent == true 54 | %div 55 | %small (#{obj["load_meta"]["magic"]}) 56 | -# obj["objects"].each do |sub_obj| 57 | %td #{obj["size"]} 58 | %td 59 | - if obj.has_key? ("stats") 60 | - obj["stats"].each do |key, value| 61 | -# unless key == "Magic" 62 | %b= key 63 | %small= value 64 | - if key == "Changed" 65 | %br 66 | %td 67 | - if obj.has_key?("actions") and obj["actions"].has_key?("vulnerable") 68 | - c = action_classes.has_key?(obj["actions"]["vulnerable"]) ? action_classes[obj["actions"]["vulnerable"]] : "text-danger" 69 | %i.fa.fa-bug.toggle.toggle-vulnerable{ "data-id" => obj["id"], 70 | "class" => c, 71 | "data-type" => "vulnerable", 72 | "data-firmware" => obj["firmware_id"], 73 | "data-value" => obj["actions"]["vulnerable"] } 74 | - else 75 | %i.fa.fa-bug.toggle.toggle-vulnerable{ "data-id" => obj["id"], 76 | "class" => "text-muted", 77 | "data-type" => "vulnerable", 78 | "data-firmware" => obj["firmware_id"], 79 | "data-value" => "false" } 80 | -#= obj 81 | - if obj.has_key?("guid_actions") and obj["guid_actions"].class != String and obj["guid_actions"].has_key?("trusted") 82 | %i.fa.fa-key.toggle.toggle-trusted{ "data-id" => obj["id"], 83 | "class" => action_classes[obj["guid_actions"]["trusted"]], 84 | "data-type" => "trusted", 85 | "data-firmware" => obj["firmware_id"], 86 | "data-value" => obj["guid_actions"]["trusted"] } 87 | - else 88 | %i.fa.fa-key.toggle.toggle-trusted{ "data-id" => obj["id"], 89 | "class" => "text-muted", 90 | "data-type" => "trusted", 91 | "data-firmware" => obj["firmware_id"], 92 | "data-value" => "false" } 93 | 94 | - if obj.has_key?("guid_actions") and obj["guid_actions"].class != String and obj["guid_actions"].has_key?("network") 95 | %i.fa.fa-fighter-jet.toggle.toggle-network{ "data-id" => obj["id"], 96 | "class" => action_classes[obj["guid_actions"]["network"]], 97 | "data-type" => "network", 98 | "data-firmware" => obj["firmware_id"], 99 | "data-value" => obj["guid_actions"]["network"] } 100 | - else 101 | %i.fa.fa-fighter-jet.toggle.toggle-network{ "data-id" => obj["id"], 102 | "class" => "text-muted", 103 | "data-type" => "network", 104 | "data-firmware" => obj["firmware_id"], 105 | "data-value" => "false" } 106 | %a{ :href => "/explorer/download/#{obj["object_id"]}"} 107 | %i.fa.fa-download{ :class => "text-muted" } 108 | 109 | - if obj.has_key?("objects") and base == false 110 | 111 | - obj["objects"].each do |child| 112 | = render("object", :obj => child, :source => false, :independent => independent, :base => false) 113 | 114 | -------------------------------------------------------------------------------- /web/app/views/explorer/_product.html.haml: -------------------------------------------------------------------------------- 1 | 2 | %tr 3 | %td= product_index 4 | %td= updates[0][:vendor] 5 | %td 6 | .pull-left 7 | %a{ "data-toggle" => "collapse", "data-target" => "#product#{product_index}"} 8 | = name 9 | .pull-right 10 | > 11 | %td= updates.length 12 | %td= Time.at(updates[-1][:date]) 13 | %td= Time.at(updates[0][:date]) 14 | %td 15 | = (updates[0][:date] - updates[-1][:date])/(60*60) 16 | hours 17 | %td 18 | %td= updates[0][:item_id] 19 | %td 20 | %tr 21 | %td{ :colspan => "10", :style => "padding: 0px; padding-right: 0px !important" } 22 | .collapse.no-transition{ :id => "product#{product_index}"} 23 | %table.table.table-bordered.tabl-hover{ :style => "margin-bottom: 0px" } 24 | %tbody 25 | - updates.each do |update| 26 | %tr 27 | = render "update", :firmware => update, :last_update => last_update -------------------------------------------------------------------------------- /web/app/views/explorer/_update.html.haml: -------------------------------------------------------------------------------- 1 | - action_classes = {"true" => "text-warning", "false" => "text-muted", "locked" => "text-danger"} 2 | 3 | %tr 4 | %td 5 | %i.fa.fa-unlock-alt.text-warning 6 | = firmware[:importance] 7 | %td= firmware[:item_id] 8 | %td= firmware[:version] 9 | %td= firmware[:name] 10 | %td 11 | %b Size 12 | - if firmware.has_key?(:size) and firmware[:size] != nil 13 | #{firmware[:size]/1024} kB 14 | %td 15 | = Time.at(firmware[:date]) 16 | - if last_update != 0 17 | %small 18 | = (firmware[:date] - last_update)/(60*60)*-1 19 | hours 20 | %td 21 | - if firmware.has_key? (:stats) and firmware[:stats] != nil 22 | - firmware[:stats].each do |key, value| 23 | %b= key 24 | %small= value 25 | - else 26 | None 27 | %td 28 | %a{:href => "/explorer/firmware/#{firmware[:firmware_id]}"} 29 | .guid= firmware[:firmware_id] 30 | %td 31 | - if firmware[:actions] != nil and firmware[:actions].has_key?("vulnerable") and firmware[:actions]["vulnerable"].length > 0 32 | %i.fa.fa-bug.text-warning 33 | - else 34 | %i.fa.fa-bug.text-muted 35 | = firmware[:status] -------------------------------------------------------------------------------- /web/app/views/explorer/download.html.haml: -------------------------------------------------------------------------------- 1 | 2 | Cannot find file. -------------------------------------------------------------------------------- /web/app/views/explorer/explorer.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Explorer 3 | 4 | %h3 5 | %div Subzero Explorer 6 | 7 | .container 8 | .row 9 | .col-md-4 10 | %h4 Products 11 | .explore-link 12 | - @vendors.each do |vendor| 13 | %div 14 | %a{ :href => "/explorer/products/#{vendor[:name]}"} 15 | = vendor[:name] 16 | updates: #{vendor[:count]} 17 | .col-md-4 18 | %h4 Vendors 19 | .explore-link 20 | %a{ :href => "/explorer/vendors"} 21 | Picture Not Found 22 | .col-md-4 23 | %h4 Objects 24 | .explore-link 25 | %a{ :href => "/explorer/objects"} 26 | Picture Not Found -------------------------------------------------------------------------------- /web/app/views/explorer/file.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Explorer - File 3 | 4 | .container 5 | .row 6 | .none 7 | Firmware ID: 8 | = @firmware_id 9 | GUID: 10 | .guid= @guid 11 | 12 | = render "object", :stats => @stats, :objects => @objects -------------------------------------------------------------------------------- /web/app/views/explorer/firmware.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Explorer - Firmware Objects 3 | 4 | %h3 5 | Firmware ID: 6 | %a{ :href => "/explorer/firmware/#{@firmware_id}/"} 7 | .guid= @firmware_id 8 | 9 | .desc 10 | = @firmware_object["description"] 11 | 12 | -#.actions 13 | -# .row 14 | -# This update contains a security patch: 15 | -# %input{:id => "contains_patch"} 16 | 17 | .container 18 | .row 19 | %h4 Changes 20 | .col-md-12 21 | %table.table.table-bordered 22 | - @changed.each do |obj| 23 | = render("object", :obj => obj, :source => true, :independent => false, :base => true) 24 | 25 | .row 26 | %h4 Structure 27 | %table.table.table-bordered.table-striped.table-hover 28 | %thead 29 | %tr 30 | %th ObjectID 31 | %th Type 32 | %th Info 33 | %th Size 34 | %th Stats 35 | %th Actions 36 | %tbody 37 | = render("object", :obj => @firmware_object, :source => true, :independent => true, :base => true) 38 | %tr 39 | %th{ :colspan => "6" } 40 | Children 41 | - @objects.each do |obj| 42 | = render("object", :obj => obj, :source => true, :independent => false, :base => false) 43 | 44 | = will_paginate @objects 45 | 46 | - content_for :javascript do 47 | :javascript 48 | var $classes = {}; 49 | $classes["true"] = "text-warning"; 50 | $classes["false"] = "text-muted"; 51 | $classes["locked"] = "text-danger"; 52 | 53 | function toggle_failed(e) { 54 | var $object = "none"; 55 | } 56 | 57 | function toggle_callback(result) { 58 | console.log(result); 59 | var $object = $('.toggle-' + result["type"] + '[data-id="' + result["id"] + '"]'); 60 | console.log($object); 61 | $object.removeClass($classes[result["previous"]]); 62 | $object.addClass($classes[result["value"]]); 63 | $object.attr("data-value", result["value"]); 64 | } 65 | 66 | $(".toggle").click(function(e) { 67 | var $toggle = $(e.target); 68 | $toggle.attr("disabled", "disabled"); 69 | 70 | var $actions = { 71 | value: $toggle.attr("data-value"), 72 | id: $toggle.attr("data-id"), 73 | type: $toggle.attr("data-type"), 74 | firmware_id: $toggle.attr("data-firmware"), 75 | }; 76 | 77 | $.post( "/explorer/actions/" + $actions["type"], { actions: $actions }, toggle_callback, "json") 78 | .fail(function(e) { 79 | toggle_failed(e); 80 | }); 81 | }); -------------------------------------------------------------------------------- /web/app/views/explorer/products.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Product Explorer 3 | - times_json = "" 4 | - freqs_json = "" 5 | 6 | %noscript 7 | :css 8 | .chart {} 9 | .axis line, .axis path { 10 | shape-rendering: crispEdges; 11 | stroke: black; 12 | fill: none; 13 | } 14 | .circle { 15 | fill: steelblue; 16 | } 17 | 18 | %h3 19 | %div Firmware List: Products 20 | 21 | #time_chart 22 | #freq_chart 23 | #delta_chart 24 | 25 | %div 26 | %table.table.table-bordered.table-striped.table-hover 27 | %thead 28 | %tr 29 | %th # 30 | %th Vendor 31 | %th Name 32 | %th Updates 33 | %th Initial 34 | %th Latest 35 | %th Lifetime 36 | %th Stats 37 | %th Item/FW ID 38 | %th Actions 39 | %tbody 40 | - product_index = (@page_num * 30) 41 | - @products_keys.each do |name| 42 | - updates = @products[name] 43 | - product_index += 1 44 | - last_update = 0 45 | = render "product", :name => name, :updates => updates, :product_index => product_index, :last_update =>last_update 46 | 47 | - updates.each do |update| 48 | - delta = if last_update != 0 then (update[:date] - last_update)/(60*60)*-1 else 0 end 49 | - last_update = update[:date] 50 | - update_formatted = Time.at(update[:date]).strftime("%d %b %Y") 51 | - if update[:load_change].has_key?("change_score") 52 | - times_json += "{\"Name\": \"#{name}\", \"Version\": \"#{update[:version]}\", \"Change\": #{update[:load_change]['change_score']}, \"Time\": \"#{update_formatted}\", \"Delta\": #{delta}}, " 53 | 54 | - freq = updates.map{|x| (x[:date]-updates[-1][:date])/(60*60)}.compact 55 | - freqs_json += "{\"Name\": \"#{name}\", \"Change\": #{(freq.sum/freq.size)/updates.size}, \"Count\": #{updates.size} }, " 56 | 57 | = will_paginate @products_keys 58 | 59 | - content_for :javascript do 60 | :javascript 61 | var update_times = [#{times_json}] 62 | var update_freqs = [#{freqs_json}] 63 | 64 | generate_time_bars('#time_chart', update_times); 65 | generate_freq_bars('#freq_chart', update_freqs); 66 | generate_delta_points('#delta_chart', update_times); 67 | -------------------------------------------------------------------------------- /web/app/views/explorer/raw.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Explorer - Raw 3 | 4 | .container 5 | .row 6 | .none 7 | Firmware ID: 8 | .guid= @firmware_id 9 | ID: 10 | = @id 11 | 12 | = render "object", :objects => [@object] -------------------------------------------------------------------------------- /web/app/views/explorer/uefi.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | Explorer - UEFI Files 3 | 4 | .none 5 | Firmware ID: 6 | = @firmware_id 7 | 8 | %div 9 | %table.table.table-bordered.table-striped.table-hover 10 | %thead 11 | %tr 12 | %th GUID 13 | %th Objects 14 | %th Size 15 | %th Name 16 | %th Stats 17 | %th Actions 18 | %tbody 19 | - @files.each do |file| 20 | %tr 21 | %td 22 | %div 23 | %a{ :href => "/explorer/file/#{@firmware_id}/#{file["guid"]}"} 24 | .guid= file["guid"] 25 | %small (#{file["attrs"]["type_name"]}) 26 | %td 27 | 28 | - file["objects"].each do |obj| 29 | %div 30 | #{obj["attrs"]["type_name"]} #{obj["attrs"]["size"]} 31 | - if obj.has_key? ("load_meta") 32 | %small (#{obj["load_meta"]["magic"]}) 33 | %td #{file["attrs"]["size"]} 34 | %td 35 | .none #{file["name"]} 36 | - if file["description"] != nil and file["description"].length > 0 37 | %small (#{file["description"]}) 38 | %td 39 | - if file.has_key? ("stats") 40 | - file["stats"].each do |key, value| 41 | %b= key 42 | %small= value 43 | %td -------------------------------------------------------------------------------- /web/app/views/layouts/_messages.html.haml: -------------------------------------------------------------------------------- 1 | -# Rails flash messages styled for Bootstrap 3.0 2 | - flash.each do |name, msg| 3 | - if msg.is_a?(String) 4 | %div{:class => "alert alert-#{name == :notice ? "success" : "danger"}"} 5 | %button.close{"aria-hidden" => "true", "data-dismiss" => "alert", :type => "button"} × 6 | = content_tag :div, msg, :id => "flash_#{name}" 7 | -------------------------------------------------------------------------------- /web/app/views/layouts/_navigation.html.haml: -------------------------------------------------------------------------------- 1 | -# navigation styled for Bootstrap 3.0 2 | %nav.navbar.navbar-default.navbar-fixed-top 3 | .container 4 | .navbar-header 5 | %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", :type => "button"} 6 | %span.sr-only Toggle navigation 7 | %span.icon-bar 8 | %span.icon-bar 9 | %span.icon-bar 10 | = link_to 'Home', root_path, class: 'navbar-brand' 11 | .collapse.navbar-collapse 12 | %ul.nav.navbar-nav 13 | = render 'layouts/navigation_links' 14 | .collapse.navbar-collapse 15 | %ul.nav.navbar-nav 16 | - menu_items.each do | link | 17 | %li= link_to link[0], link[1] -------------------------------------------------------------------------------- /web/app/views/layouts/_navigation_links.html.haml: -------------------------------------------------------------------------------- 1 | %li= link_to 'About', page_path('about') 2 | %li= link_to 'Explorer', explorer_path 3 | %li= link_to 'Analysis', analysis_path 4 | %li= link_to 'Submit', submit_path -------------------------------------------------------------------------------- /web/app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{:name => "viewport", :content => "width=device-width, initial-scale=1.0"} 5 | %title= content_for?(:title) ? yield(:title) : 'Rails Bootstrap' 6 | %meta{:name => "description", :content => "#{content_for?(:description) ? yield(:description) : 'Rails Bootstrap'}"} 7 | = stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true 8 | = javascript_include_tag "application", "data-turbolinks-track" => true 9 | = stylesheet_link_tag "overrides", "darkstrap", media: "all" 10 | = csrf_meta_tags 11 | %body 12 | %header 13 | = render 'layouts/navigation' 14 | %main{:role => "main"} 15 | = render 'layouts/messages' 16 | = yield 17 | 18 | = content_for(:javascript) 19 | -------------------------------------------------------------------------------- /web/app/views/pages/about.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :title do 2 | About 3 | %h3 About the Website 4 | %p Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 5 | -------------------------------------------------------------------------------- /web/app/views/visitors/new.html.haml: -------------------------------------------------------------------------------- 1 | %h3 Home 2 | -------------------------------------------------------------------------------- /web/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /web/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(:default, Rails.env) 8 | 9 | module RailsBootstrap 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 20 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 21 | # config.i18n.default_locale = :de 22 | config.assets.paths << Rails.root.join("app", "assets", "fonts") 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /web/config/application.yml: -------------------------------------------------------------------------------- 1 | # Add account credentials and API keys here. 2 | # See http://railsapps.github.io/rails-environment-variables.html 3 | # This file should be listed in .gitignore to keep your settings secret! 4 | # Each entry sets a local environment variable and overrides ENV variables in the Unix shell. 5 | # For example, setting: 6 | # GMAIL_USERNAME: Your_Gmail_Username 7 | # makes 'Your_Gmail_Username' available as ENV["GMAIL_USERNAME"] 8 | # Add application configuration variables here, as shown below. 9 | # 10 | # GMAIL_USERNAME: changeme 11 | # GMAIL_PASSWORD: changeme 12 | -------------------------------------------------------------------------------- /web/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /web/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /web/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | RailsBootstrap::Application.initialize! 6 | -------------------------------------------------------------------------------- /web/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | RailsBootstrap::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | config.log_level = :info 31 | end 32 | -------------------------------------------------------------------------------- /web/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | RailsBootstrap::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both thread web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Generate digests for assets URLs. 33 | config.assets.digest = true 34 | 35 | # Version of your assets, change this if you want to expire all your assets. 36 | config.assets.version = '1.0' 37 | 38 | # Specifies the header that your server uses for sending files. 39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | # config.force_ssl = true 44 | 45 | # Set to :debug to see everything in the log. 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | # config.log_tags = [ :subdomain, :uuid ] 50 | 51 | # Use a different logger for distributed setups. 52 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 53 | 54 | # Use a different cache store in production. 55 | # config.cache_store = :mem_cache_store 56 | 57 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 58 | # config.action_controller.asset_host = "http://assets.example.com" 59 | 60 | # Precompile additional assets. 61 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 62 | # config.assets.precompile += %w( search.js ) 63 | 64 | # Ignore bad email addresses and do not raise email delivery errors. 65 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 66 | # config.action_mailer.raise_delivery_errors = false 67 | 68 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 69 | # the I18n.default_locale when a translation can not be found). 70 | config.i18n.fallbacks = true 71 | 72 | # Send deprecation notices to registered listeners. 73 | config.active_support.deprecation = :notify 74 | 75 | # Disable automatic flushing of the log to improve performance. 76 | # config.autoflush_log = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | end 81 | -------------------------------------------------------------------------------- /web/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | RailsBootstrap::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = "public, max-age=3600" 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | end 37 | -------------------------------------------------------------------------------- /web/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /web/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /web/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /web/config/initializers/logger_customizations.rb: -------------------------------------------------------------------------------- 1 | # config/initializers/logger_customizations.rb 2 | # Production-only monkeypatches to make our logs awesome 3 | 4 | # Monkeypatch round 2 5 | # * add timestamps + loglevel 6 | # * skip "Rendered partial..." lines 7 | class ActiveSupport::Logger::SimpleFormatter 8 | def call(severity, time, progname = nil, msg) 9 | # Skip "Rendered..." messages in production 10 | if msg =~ /Rendered/ 11 | return 12 | end 13 | 14 | "%s\n" % msg 15 | end 16 | end -------------------------------------------------------------------------------- /web/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /web/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | RailsBootstrap::Application.config.secret_key_base = 'CHANGEME_NOT_USED' 13 | -------------------------------------------------------------------------------- /web/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | RailsBootstrap::Application.config.session_store :cookie_store, key: '_rails-bootstrap_session' 4 | -------------------------------------------------------------------------------- /web/config/initializers/simple_form.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in SimpleForm. 2 | SimpleForm.setup do |config| 3 | # Wrappers are used by the form builder to generate a 4 | # complete input. You can remove any component from the 5 | # wrapper, change the order or even add your own to the 6 | # stack. The options given below are used to wrap the 7 | # whole input. 8 | config.wrappers :default, class: :input, 9 | hint_class: :field_with_hint, error_class: :field_with_errors do |b| 10 | ## Extensions enabled by default 11 | # Any of these extensions can be disabled for a 12 | # given input by passing: `f.input EXTENSION_NAME => false`. 13 | # You can make any of these extensions optional by 14 | # renaming `b.use` to `b.optional`. 15 | 16 | # Determines whether to use HTML5 (:email, :url, ...) 17 | # and required attributes 18 | b.use :html5 19 | 20 | # Calculates placeholders automatically from I18n 21 | # You can also pass a string as f.input placeholder: "Placeholder" 22 | b.use :placeholder 23 | 24 | ## Optional extensions 25 | # They are disabled unless you pass `f.input EXTENSION_NAME => :lookup` 26 | # to the input. If so, they will retrieve the values from the model 27 | # if any exists. If you want to enable the lookup for any of those 28 | # extensions by default, you can change `b.optional` to `b.use`. 29 | 30 | # Calculates maxlength from length validations for string inputs 31 | b.optional :maxlength 32 | 33 | # Calculates pattern from format validations for string inputs 34 | b.optional :pattern 35 | 36 | # Calculates min and max from length validations for numeric inputs 37 | b.optional :min_max 38 | 39 | # Calculates readonly automatically from readonly attributes 40 | b.optional :readonly 41 | 42 | ## Inputs 43 | b.use :label_input 44 | b.use :hint, wrap_with: { tag: :span, class: :hint } 45 | b.use :error, wrap_with: { tag: :span, class: :error } 46 | end 47 | 48 | # The default wrapper to be used by the FormBuilder. 49 | config.default_wrapper = :default 50 | 51 | # Define the way to render check boxes / radio buttons with labels. 52 | # Defaults to :nested for bootstrap config. 53 | # inline: input + label 54 | # nested: label > input 55 | config.boolean_style = :nested 56 | 57 | # Default class for buttons 58 | config.button_class = 'btn' 59 | 60 | # Method used to tidy up errors. Specify any Rails Array method. 61 | # :first lists the first message for each field. 62 | # Use :to_sentence to list all errors for each field. 63 | # config.error_method = :first 64 | 65 | # Default tag used for error notification helper. 66 | config.error_notification_tag = :div 67 | 68 | # CSS class to add for error notification helper. 69 | config.error_notification_class = 'alert alert-error' 70 | 71 | # ID to add for error notification helper. 72 | # config.error_notification_id = nil 73 | 74 | # Series of attempts to detect a default label method for collection. 75 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] 76 | 77 | # Series of attempts to detect a default value method for collection. 78 | # config.collection_value_methods = [ :id, :to_s ] 79 | 80 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. 81 | # config.collection_wrapper_tag = nil 82 | 83 | # You can define the class to use on all collection wrappers. Defaulting to none. 84 | # config.collection_wrapper_class = nil 85 | 86 | # You can wrap each item in a collection of radio/check boxes with a tag, 87 | # defaulting to :span. Please note that when using :boolean_style = :nested, 88 | # SimpleForm will force this option to be a label. 89 | # config.item_wrapper_tag = :span 90 | 91 | # You can define a class to use in all item wrappers. Defaulting to none. 92 | # config.item_wrapper_class = nil 93 | 94 | # How the label text should be generated altogether with the required text. 95 | # config.label_text = lambda { |label, required| "#{required} #{label}" } 96 | 97 | # You can define the class to use on all labels. Default is nil. 98 | config.label_class = 'control-label' 99 | 100 | # You can define the class to use on all forms. Default is simple_form. 101 | # config.form_class = :simple_form 102 | 103 | # You can define which elements should obtain additional classes 104 | # config.generate_additional_classes_for = [:wrapper, :label, :input] 105 | 106 | # Whether attributes are required by default (or not). Default is true. 107 | # config.required_by_default = true 108 | 109 | # Tell browsers whether to use the native HTML5 validations (novalidate form option). 110 | # These validations are enabled in SimpleForm's internal config but disabled by default 111 | # in this configuration, which is recommended due to some quirks from different browsers. 112 | # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, 113 | # change this configuration to true. 114 | config.browser_validations = false 115 | 116 | # Collection of methods to detect if a file type was given. 117 | # config.file_methods = [ :mounted_as, :file?, :public_filename ] 118 | 119 | # Custom mappings for input types. This should be a hash containing a regexp 120 | # to match as key, and the input type that will be used when the field name 121 | # matches the regexp as value. 122 | # config.input_mappings = { /count/ => :integer } 123 | 124 | # Custom wrappers for input types. This should be a hash containing an input 125 | # type as key and the wrapper that will be used for all inputs with specified type. 126 | # config.wrapper_mappings = { string: :prepend } 127 | 128 | # Default priority for time_zone inputs. 129 | # config.time_zone_priority = nil 130 | 131 | # Default priority for country inputs. 132 | # config.country_priority = nil 133 | 134 | # When false, do not use translations for labels. 135 | # config.translate_labels = true 136 | 137 | # Automatically discover new inputs in Rails' autoload path. 138 | # config.inputs_discovery = true 139 | 140 | # Cache SimpleForm inputs discovery 141 | # config.cache_discovery = !Rails.env.development? 142 | 143 | # Default class for inputs 144 | # config.input_class = nil 145 | end 146 | -------------------------------------------------------------------------------- /web/config/initializers/simple_form_bootstrap.rb: -------------------------------------------------------------------------------- 1 | # Use this setup block to configure all options available in SimpleForm. 2 | SimpleForm.setup do |config| 3 | config.wrappers :bootstrap, tag: 'div', class: 'control-group', error_class: 'error' do |b| 4 | b.use :html5 5 | b.use :placeholder 6 | b.use :label 7 | b.wrapper tag: 'div', class: 'controls' do |ba| 8 | ba.use :input 9 | ba.use :error, wrap_with: { tag: 'span', class: 'help-inline' } 10 | ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } 11 | end 12 | end 13 | 14 | config.wrappers :prepend, tag: 'div', class: "control-group", error_class: 'error' do |b| 15 | b.use :html5 16 | b.use :placeholder 17 | b.use :label 18 | b.wrapper tag: 'div', class: 'controls' do |input| 19 | input.wrapper tag: 'div', class: 'input-prepend' do |prepend| 20 | prepend.use :input 21 | end 22 | input.use :hint, wrap_with: { tag: 'span', class: 'help-block' } 23 | input.use :error, wrap_with: { tag: 'span', class: 'help-inline' } 24 | end 25 | end 26 | 27 | config.wrappers :append, tag: 'div', class: "control-group", error_class: 'error' do |b| 28 | b.use :html5 29 | b.use :placeholder 30 | b.use :label 31 | b.wrapper tag: 'div', class: 'controls' do |input| 32 | input.wrapper tag: 'div', class: 'input-append' do |append| 33 | append.use :input 34 | end 35 | input.use :hint, wrap_with: { tag: 'span', class: 'help-block' } 36 | input.use :error, wrap_with: { tag: 'span', class: 'help-inline' } 37 | end 38 | end 39 | 40 | # Wrappers for forms and inputs using the Twitter Bootstrap toolkit. 41 | # Check the Bootstrap docs (http://twitter.github.com/bootstrap) 42 | # to learn about the different styles for forms and inputs, 43 | # buttons and other elements. 44 | config.default_wrapper = :bootstrap 45 | end 46 | -------------------------------------------------------------------------------- /web/config/initializers/will_paginate_array.rb: -------------------------------------------------------------------------------- 1 | require 'will_paginate/array' 2 | -------------------------------------------------------------------------------- /web/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /web/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /web/config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Labels and hints examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | 27 | -------------------------------------------------------------------------------- /web/config/routes.rb: -------------------------------------------------------------------------------- 1 | RailsBootstrap::Application.routes.draw do 2 | ### Maybe have graphs here? 3 | root :to => 'explorer#search' 4 | 5 | ### List of all machines or graphs? 6 | get 'explorer' => "explorer#explorer" 7 | 8 | ### Machines/ISV/OEM views 9 | get 'explorer/products/:vendor' => "explorer#products" 10 | get 'explorer/vendors' => "explorer#vendors" 11 | get 'explorer/objects' => "explorer#objects" 12 | get 'explorer/vendor/:name' => "explorer#vendor" 13 | get 'explorer/machine/:id' => "explorer#machine" 14 | 15 | ### Object views 16 | get 'explorer/uefi/:id' => "explorer#uefi" 17 | get 'explorer/firmware/:id' => "explorer#firmware" 18 | 19 | ### Downloads 20 | get 'explorer/file/:firmware_id/:id' => "explorer#file" 21 | get 'explorer/raw/:firmware_id/:id' => "explorer#raw" 22 | get 'explorer/download/:object_id' => "explorer#download" 23 | 24 | ### Analysis 25 | get 'analysis' => "analysis#analysis" 26 | get 'analysis/guids' => "analysis#guids" 27 | get 'analysis/guid/:id' => "analysis#guid" 28 | get 'analysis/keywords' => "analysis#keywords" 29 | get 'analysis/vulnerabilities' => "analysis#vulnerabilities" 30 | get 'analysis/trusted' => "analysis#trusted" 31 | get 'analysis/similarities' => "analysis#similarities" 32 | 33 | ### Malware/Submit 34 | get 'submit' => "submit#submit" 35 | 36 | ### AJAX actions 37 | post 'explorer/actions/:name' => "explorer#actions" 38 | end 39 | -------------------------------------------------------------------------------- /web/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | # Environment variables (ENV['...']) can be set in the file config/application.yml. 9 | # See http://railsapps.github.io/rails-environment-variables.html 10 | -------------------------------------------------------------------------------- /web/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/lib/assets/.keep -------------------------------------------------------------------------------- /web/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/lib/tasks/.keep -------------------------------------------------------------------------------- /web/lib/templates/haml/scaffold/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for(@<%= singular_table_name %>) do |f| 2 | = f.error_notification 3 | 4 | .form-inputs 5 | <%- attributes.each do |attribute| -%> 6 | = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> 7 | <%- end -%> 8 | 9 | .form-actions 10 | = f.button :submit 11 | -------------------------------------------------------------------------------- /web/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The page you were looking for doesn't exist.

54 |

You may have mistyped the address or the page may have moved.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /web/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The change you wanted was rejected.

54 |

Maybe you tried to change something you didn't have access to.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /web/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

We're sorry, but something went wrong.

54 |
55 |

If you are the application owner check the logs for more information.

56 | 57 | 58 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/humans.txt: -------------------------------------------------------------------------------- 1 | /* the humans responsible & colophon */ 2 | /* humanstxt.org */ 3 | 4 | 5 | /* TEAM */ 6 | : 7 | Site: 8 | Twitter: 9 | Location: 10 | 11 | /* THANKS */ 12 | Daniel Kehoe (@rails_apps) for the RailsApps project 13 | 14 | /* SITE */ 15 | Standards: HTML5, CSS3 16 | Components: jQuery 17 | Software: Ruby on Rails 18 | 19 | /* GENERATED BY */ 20 | RailsApps application template: http://railsapps.github.io/ 21 | -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | Disallow: / 6 | -------------------------------------------------------------------------------- /web/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | 7 | ### Replace WebRick with Puma 8 | require 'rack/handler' 9 | Rack::Handler::WEBrick = Rack::Handler.get(:puma) 10 | 11 | require 'rails/commands' 12 | -------------------------------------------------------------------------------- /web/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/test/controllers/.keep -------------------------------------------------------------------------------- /web/test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/test/fixtures/.keep -------------------------------------------------------------------------------- /web/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/test/helpers/.keep -------------------------------------------------------------------------------- /web/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/test/integration/.keep -------------------------------------------------------------------------------- /web/test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/test/mailers/.keep -------------------------------------------------------------------------------- /web/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/test/models/.keep -------------------------------------------------------------------------------- /web/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | ActiveRecord::Migration.check_pending! 7 | 8 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 9 | # 10 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 11 | # -- they do not yet inherit this setting 12 | fixtures :all 13 | 14 | # Add more helper methods to be used by all tests here... 15 | end 16 | -------------------------------------------------------------------------------- /web/vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /web/vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theopolis/subzero/12c706c6da28736e7d80a2097b92e2b78d8bad01/web/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------