iOS web browser with a focus on security and privacy
1#!/usr/bin/ruby
2#
3# Endless
4# Copyright (c) 2014-2015 joshua stein <jcs@jcs.org>
5#
6# See LICENSE file for redistribution terms.
7#
8
9require "active_support/core_ext/hash/conversions"
10require "plist"
11require "json"
12require "net/https"
13require "uri"
14
15HTTPS_E_TARGETS_PLIST = "Endless/Resources/https-everywhere_targets.plist"
16HTTPS_E_RULES_PLIST = "Endless/Resources/https-everywhere_rules.plist"
17
18# in b64 for some reason
19HSTS_PRELOAD_LIST = "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.json?format=TEXT"
20HSTS_PRELOAD_HOSTS_PLIST = "Endless/Resources/hsts_preload.plist"
21
22FORCE = (ARGV[0].to_s == "-f")
23
24# convert all HTTPS Everywhere XML rule files into one big rules hash and write
25# it out as a plist, as well as a standalone hash of target URLs -> rule names
26# to another plist
27def convert_https_e
28 https_e_git_commit = `cd https-everywhere && git show -s`.split("\n")[0].
29 gsub(/^commit /, "")[0, 12]
30
31 if File.exists?(HTTPS_E_TARGETS_PLIST)
32 if m = File.open(HTTPS_E_TARGETS_PLIST).gets.to_s.match(/Everywhere (.+) - /)
33 if (m[1] == https_e_git_commit) && !FORCE
34 return
35 end
36 end
37 end
38
39 rules = {}
40 targets = {}
41
42 Dir.glob(File.dirname(__FILE__) +
43 "/https-everywhere/src/chrome/content/rules/*.xml").each do |f|
44 hash = Hash.from_xml(File.read(f))
45
46 raise "no ruleset" if !hash["ruleset"]
47
48 if hash["ruleset"]["default_off"] ||
49 hash["ruleset"]["platform"] == "mixedcontent"
50 next # XXX: should we store these?
51 end
52
53 raise "conflict on #{f}" if rules[hash["ruleset"]["name"]]
54
55 # validate regexps
56 begin
57 r = hash["ruleset"]["rule"]
58 r = [ r ] if !r.is_a?(Array)
59 r.each do |h|
60 Regexp.compile(h["from"])
61 end
62
63 if r = hash["ruleset"]["securecookie"]
64 r = [ r ] if !r.is_a?(Array)
65 r.each do |h|
66 Regexp.compile(h["host"])
67 Regexp.compile(h["name"])
68 end
69 end
70 rescue => e
71 STDERR.puts "error in #{f}: #{e} (#{hash.inspect})"
72 exit 1
73 end
74
75 rules[hash["ruleset"]["name"]] = hash
76
77 hash["ruleset"]["target"].each do |target|
78 if !target.is_a?(Hash)
79 # why do some of these get converted into an array?
80 if target.length != 2 || target[0] != "host"
81 puts f
82 raise target.inspect
83 end
84
85 target = { target[0] => target[1] }
86 end
87
88 if targets[target["host"][1]]
89 raise "rules already exist for #{target["host"]}"
90 end
91
92 targets[target["host"]] = hash["ruleset"]["name"]
93 end
94 end
95
96 File.write(HTTPS_E_TARGETS_PLIST,
97 "<!-- generated from HTTPS Everywhere #{https_e_git_commit} - do not " +
98 "directly edit this file -->\n" +
99 targets.to_plist)
100
101 File.write(HTTPS_E_RULES_PLIST,
102 "<!-- generated from HTTPS Everywhere #{https_e_git_commit} - do not " +
103 "directly edit this file -->\n" +
104 rules.to_plist)
105end
106
107def convert_hsts_preload
108 domains = {}
109
110 json = JSON.parse(Net::HTTP.get(URI(HSTS_PRELOAD_LIST)).unpack("m0").first)
111 json["entries"].each do |entry|
112 domains[entry["name"]] = {
113 "include_subdomains" => !!entry["include_subdomains"]
114 }
115 end
116
117 File.write(HSTS_PRELOAD_HOSTS_PLIST,
118 "<!-- generated from #{HSTS_PRELOAD_LIST} - do not directly edit this " +
119 "file -->\n" +
120 domains.to_plist)
121end
122
123convert_https_e
124convert_hsts_preload