├── .gitignore
├── .gitlab-ci.yml
├── BUILDING
├── LICENSE
├── README.md
├── data
├── README.md
└── default.rulesets
├── httpseverywhere-0.8.deps
├── libhttpseverywhere.doap
├── meson.build
├── meson_options.txt
├── src
├── context.vala
├── meson.build
├── ruleset.vala
└── update.vala
└── test
├── main.vala
└── meson.build
/.gitignore:
--------------------------------------------------------------------------------
1 | *.c
2 |
3 | build*/
4 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | # This file is a template, and might need editing before it works on your project.
2 | # use the official gcc image, based on debian
3 | # can use verions as well, like gcc:5.2
4 | # see https://hub.docker.com/_/gcc/
5 | image: gcc
6 |
7 | build:
8 | stage: build
9 | # instead of calling g++ directly you can also use some build toolkit like make
10 | # install the necessary build tools when needed
11 | before_script:
12 | - sudo apt update && sudo apt -y install meson valac gobject-introspection valadoc libgirepository1.0-dev libjson-glib-dev libxml2-dev libsoup2.4-dev libgee-0.8-dev libarchive-dev
13 | script:
14 | - mkdir build && cd build && meson .. && ninja
15 | - ./httpseverywhere_test
16 | artifacts:
17 | paths:
18 | - libhttpseverywhere.so
19 | # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time
20 | # cache:
21 | # paths:
22 | # - "*.o"
23 |
24 |
--------------------------------------------------------------------------------
/BUILDING:
--------------------------------------------------------------------------------
1 |
2 |
3 | build dependencies:
4 |
5 | gcc
6 | valac
7 | gobject-introspection
8 | libgirepository1.0-dev
9 | gee
10 | gobject
11 | glib
12 | libxml-2.0
13 |
14 | runtime deps
15 |
16 | gobject
17 | glib
18 | gee
19 | libxml-2.0
20 |
21 |
22 | pitfalls:
23 |
24 | GIR-versions must be exactly X.Y
25 | only major and minor version number
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | libhttpseverywhere
2 | ==================
3 |
4 | [](https://repology.org/metapackage/libhttpseverywhere)
5 |
6 | This library enables you to leverage the power of
7 | [HTTPSEverywhere](https://www.eff.org/https-everywhere) to any desktop-application you want
8 |
9 | HTTPSEverywhere is a browser plugin that comes with a set of rules that you can use to ensure that
10 | you use HTTP instead of HTTPS only when this is absolutely not circumventable.
11 | With libhttpseverywhere you will get a C-bindable, GLib-based library you can
12 | link/bind against in almost all languages
13 |
14 | As a library written in Vala, libhttpseverywhere will support GObject-Introspection. This means
15 | that you can use the lib in many popular languages like e.g. Ruby, Python or Javascript.
16 |
17 | Current Users
18 | -------------
19 |
20 | * [Rainbow Lollipop](http://rainbow-lollipop.de) - An experimental visual history browser.
21 | * [GNOME Web](https://wiki.gnome.org/Apps/Web) - The default Browser of the GNOME Desktop
22 |
23 | Documentation
24 | -------------
25 |
26 | You can either generate the documentation as a devhelp-book for yourself (see Building-section)
27 |
28 | Dependencies
29 | ------------
30 |
31 | The following libraries have to be present for libhttpseverywhere to run:
32 |
33 | * glib-2.0
34 | * gee-0.8
35 | * json-glib-1.0
36 | * libsoup-2.4
37 | * libarchive
38 |
39 | The library names are in pkg-config notation
40 |
41 | Building
42 | --------
43 |
44 | [Meson](http://mesonbuild.com) is used as the buildsystem for libhttpseverywhere. The build dependencies
45 | are the following:
46 |
47 | * c-compiler of your choice
48 | * _valac_ - Vala compiler
49 | * _valadoc_ - Vala documentation tool
50 | * _gobject-introspection_ - GObject introspection compiler
51 | * _libgirepository1.0-dev_ - Dev headers for gobject introspection
52 | * _libjson-glib-dev_ - Dev headers for json-glib
53 | * _libsoup2.4-dev_ - Dev headers for libsoup-2.4
54 | * _libarchive-dev_ - Dev headers for libarchive
55 |
56 | Italics are valid debian package names.
57 |
58 | Clone and build the library as follows:
59 |
60 | ```
61 | $ git clone https://git.gnome.org/browse/libhttpseverywhere
62 | $ cd libhttpseverywhere
63 | $ meson build && cd build
64 | $ ninja
65 | ```
66 |
67 | If you want to build the documentation, make sure you have valadoc available
68 | on your system and call meson like this:
69 |
70 | ```
71 | meson -Denable_valadoc=true build && cd build
72 | ```
73 |
74 | If you desire to install the library, execute:
75 |
76 | ```
77 | # ninja install
78 | ```
79 |
80 |
81 | Running GI
82 | ----------
83 |
84 | libhttpseverywhere supports GObject-Introspection which means you can consume it in various
85 | popular languages including but not limited to: Python, Perl, Lua, JS, PHP.
86 |
87 | Feel free to add examples for your favorite language in an example folder.
88 |
89 | Note: If you installed the library in /usr/local, you have to export the following
90 | environment variables for the examples to work:
91 |
92 | ```
93 | $ export LD_LIBRARY_PATH=/usr/local/lib/x86_64-linux-gnu
94 | $ export GI_TYPELIB_PATH=/usr/local/lib/x86_64-linux-gnu/girepository-1.0/
95 | ```
96 | You will also have to adapt the multiarch-string `x86_64-linux-gnu` if you built the
97 | software for another architecture.
98 |
--------------------------------------------------------------------------------
/data/README.md:
--------------------------------------------------------------------------------
1 | LICENSE
2 | =======
3 |
4 | the rulesets.json comes from HTTPS-Everywhere, extracted from their
5 | release-package. [HTTPS-Everywhere](https://www.eff.org/https-everywhere)
6 |
7 | HTTPS-Everywhere is released under GPL v2+
8 |
--------------------------------------------------------------------------------
/httpseverywhere-0.8.deps:
--------------------------------------------------------------------------------
1 | glib-2.0
2 | json-glib-1.0
3 | libsoup-2.4
4 | gee-0.8
5 | libarchive
6 |
--------------------------------------------------------------------------------
/libhttpseverywhere.doap:
--------------------------------------------------------------------------------
1 |
2 |
7 | libhttpseverywhere
8 | Leverage the power of HTTPS Everywhere
9 | This library enables you to leverage the power of HTTPS Everywhere in any desktop application you want.
10 |
11 | Vala
12 |
13 |
14 |
15 |
16 |
17 |
18 | Daniel Brendle
19 |
20 | elbren
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | #********************************************************************+
2 | # Copyright 2016-2018 Daniel 'grindhold' Brendle
3 | #
4 | # This file is part of libhttpseverywhere.
5 | #
6 | # libhttpseverywhere is free software: you can redistribute it and/or
7 | # modify it under the terms of the GNU Lesser General Public License
8 | # as published by the Free Software Foundation, either
9 | # version 3 of the License, or (at your option) any later
10 | # version.
11 | #
12 | # libhttpseverywhere is distributed in the hope that it will be
13 | # useful, but WITHOUT ANY WARRANTY; without even the implied
14 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 | # PURPOSE. See the GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public
18 | # License along with libhttpseverywhere.
19 | # If not, see http://www.gnu.org/licenses/.
20 | #*********************************************************************
21 |
22 | project('httpseverywhere', 'vala', 'c', meson_version : '>=0.39.1', license: 'LGPL')
23 |
24 | pkgconfig = import('pkgconfig')
25 |
26 | api = '0.8'
27 |
28 | # This isn't libtool. To keep things simple, we can use the same version
29 | # number for the soname as our actual version. But it requires that we
30 | # generally follow libtool semantics: bump the first version whenever
31 | # breaking ABI, bump the second version whenever adding new API, bump
32 | # the third version for every release.
33 | libhttpseverywhere_version = '0.8.3'
34 |
35 | glib = dependency('glib-2.0')
36 | gobject = dependency('gobject-2.0')
37 | json_glib = dependency('json-glib-1.0')
38 | soup = dependency('libsoup-2.4')
39 | gio = dependency('gio-2.0')
40 | gee = dependency('gee-0.8')
41 | archive = dependency('libarchive')
42 |
43 | subdir('src')
44 | subdir('test')
45 |
46 | pkgconfig.generate(libraries : httpseverywhere_lib,
47 | version : libhttpseverywhere_version,
48 | name : 'libhttpseverywhere',
49 | filebase : meson.current_build_dir()+'/httpseverywhere-'+api,
50 | requires : 'glib-2.0 gobject-2.0 gio-2.0 json-glib-1.0 libsoup-2.4 gee-0.8 libarchive',
51 | subdirs: 'httpseverywhere-'+api,
52 | description : 'A library to rewrite HTTP URLs to HTTPS URLs.',
53 | install: true)
54 |
55 | install_data('data/default.rulesets', install_dir: get_option('datadir') + '/libhttpseverywhere')
56 | install_data('httpseverywhere-'+api+'.deps', install_dir: get_option('datadir') + '/vala/vapi')
57 |
--------------------------------------------------------------------------------
/meson_options.txt:
--------------------------------------------------------------------------------
1 | option('enable_valadoc', type: 'boolean', value: false)
2 |
--------------------------------------------------------------------------------
/src/context.vala:
--------------------------------------------------------------------------------
1 | /* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 | /********************************************************************
3 | # Copyright 2015-2018 Daniel 'grindhold' Brendle
4 | #
5 | # This file is part of libhttpseverywhere.
6 | #
7 | # libhttpseverywhere is free software: you can redistribute it and/or
8 | # modify it under the terms of the GNU Lesser General Public License
9 | # as published by the Free Software Foundation, either
10 | # version 3 of the License, or (at your option) any later
11 | # version.
12 | #
13 | # libhttpseverywhere is distributed in the hope that it will be
14 | # useful, but WITHOUT ANY WARRANTY; without even the implied
15 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
16 | # PURPOSE. See the GNU Lesser General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU Lesser General Public
19 | # License along with libhttpseverywhere.
20 | # If not, see http://www.gnu.org/licenses/.
21 | *********************************************************************/
22 |
23 | /**
24 | * A set of classes that enables you to use the HTTPS-Everywhere data
25 | * to convert http-URLs into https-URLs.
26 | */
27 | namespace HTTPSEverywhere {
28 | public errordomain ContextError {
29 | NOT_IMPLEMENTED
30 | }
31 |
32 | private const string rulesets_file = "default.rulesets";
33 |
34 | /**
35 | * The library context object. Most applications will only need to create a
36 | * single context.
37 | */
38 | public class Context : GLib.Object {
39 | private Json.Parser parser;
40 |
41 | private RewriteResult last_rewrite_state;
42 | private Gee.HashMap> targets;
43 | private Gee.HashMap rulesets;
44 |
45 | // Cache for recently used targets
46 | private Gee.ArrayList cache;
47 | private const int CACHE_SIZE = 100;
48 |
49 | // List of RulesetIds that are to be ignored
50 | private Gee.ArrayList ignore_list;
51 |
52 | /**
53 | * Indicates whether the library has been successfully
54 | * initialized. Be careful: this property will become //false//
55 | * at some point if you update the rulesets.
56 | */
57 | public bool initialized { get; private set; default = false; }
58 |
59 | /**
60 | * Different states that express what a rewrite process did to
61 | * a URL
62 | */
63 | public enum RewriteResult {
64 | /**
65 | * The URL has successfully been rewritten to HTTPS
66 | */
67 | OK,
68 | /**
69 | * There was a ruleset for the host but no rule matched
70 | * for the given URL
71 | */
72 | NO_MATCH,
73 | /**
74 | * There is no ruleset for the given host
75 | */
76 | NO_RULESET
77 | }
78 |
79 | /**
80 | * Create a new library context object.
81 | */
82 | public Context() {
83 | }
84 |
85 | /**
86 | * This function initializes HTTPSEverywhere by loading
87 | * the rulesets from the filesystem.
88 | */
89 | public async void init(Cancellable? cancellable = null) throws IOError {
90 | initialized = false;
91 |
92 | targets = new Gee.HashMap>();
93 | rulesets = new Gee.HashMap();
94 | cache = new Gee.ArrayList();
95 |
96 | ignore_list = new Gee.ArrayList();
97 |
98 | var datapaths = new Gee.ArrayList();
99 |
100 | // Specify the paths to search for rules in
101 | datapaths.add(Path.build_filename(Environment.get_user_data_dir(),
102 | "libhttpseverywhere", rulesets_file));
103 | foreach (string dp in Environment.get_system_data_dirs())
104 | datapaths.add(Path.build_filename(dp, "libhttpseverywhere", rulesets_file));
105 |
106 | // local rules in repo dir to test data without installation
107 | // only works if the test executable is loaded from the build/test folder
108 | // that meson generates
109 | if (Environment.get_current_dir().has_suffix("build/test")) {
110 | datapaths.add(Path.build_filename(Environment.get_current_dir(),
111 | "..", "..", "data", rulesets_file));
112 | }
113 |
114 | parser = new Json.Parser();
115 | bool success = false;
116 |
117 | foreach (string dp in datapaths) {
118 | try {
119 | File f = File.new_for_path(dp);
120 | FileInfo fi = f.query_info("standard::*", FileQueryInfoFlags.NONE);
121 | if (fi.get_size() == 0) {
122 | continue;
123 | }
124 | FileInputStream fis = yield f.read_async(Priority.DEFAULT, cancellable);
125 | DataInputStream dis = new DataInputStream(fis);
126 | yield parser.load_from_stream_async(dis, cancellable);
127 | } catch (Error e) {
128 | if (e is IOError.CANCELLED) {
129 | throw (IOError) e;
130 | }
131 | continue;
132 | }
133 | success = true;
134 | break;
135 | }
136 | if (!success) {
137 | string locations = "\n";
138 | foreach (string location in datapaths)
139 | locations += "%s\n".printf(location);
140 | warning("Could not find any suitable database in the following locations:%s",
141 | locations);
142 | return;
143 | }
144 |
145 | load_rulesets();
146 | initialized = true;
147 | }
148 |
149 | /**
150 | * Obtain the RewriteResult for the last rewrite that
151 | * has been done with {@link Context.rewrite}
152 | */
153 | public RewriteResult rewrite_result() {
154 | return last_rewrite_state;
155 | }
156 |
157 | /**
158 | * Takes a url and returns the appropriate
159 | * HTTPS-enabled counterpart if there is any
160 | */
161 | public string rewrite(string url)
162 | requires(initialized) {
163 | string url_copy = url;
164 |
165 | if (!url_copy.has_prefix("http://"))
166 | return url_copy;
167 |
168 | if (url_copy.has_prefix("http://") && !url_copy.has_suffix("/")) {
169 | var rep = url_copy.replace("/","");
170 | if (url_copy.length - rep.length <= 2)
171 | url_copy += "/";
172 | }
173 | Ruleset? rs = null;
174 |
175 | foreach (Target target in this.cache) {
176 | if (target.host in this.ignore_list)
177 | continue;
178 |
179 | if (target.matches(url_copy)) {
180 | foreach (uint ruleset_id in targets.get(target)) {
181 | rs = rulesets.get(ruleset_id);
182 | }
183 | break;
184 | }
185 | }
186 |
187 | if (rs == null) {
188 | foreach (Target target in targets.keys) {
189 | if (target.host in this.ignore_list)
190 | continue;
191 |
192 | if (target.matches(url_copy)) {
193 | foreach (uint ruleset_id in targets.get(target)) {
194 | rs = rulesets.get(ruleset_id);
195 | }
196 | if (cache.size >= Context.CACHE_SIZE)
197 | cache.remove_at(Context.CACHE_SIZE-1);
198 | cache.add(target);
199 | break;
200 | }
201 | }
202 | }
203 |
204 | if (rs == null) {
205 | last_rewrite_state = RewriteResult.NO_RULESET;
206 | return url_copy;
207 | } else {
208 | last_rewrite_state = RewriteResult.NO_MATCH;
209 | string rurl = rs.rewrite(url_copy);
210 | if (url_copy.has_prefix("https://"))
211 | last_rewrite_state = RewriteResult.OK;
212 | return rs.rewrite(rurl);
213 | }
214 | }
215 |
216 | /**
217 | * Returns true when there is a {@link HTTPSEverywhere.Ruleset} for the
218 | * given URL
219 | */
220 | public bool has_https(string url)
221 | requires(initialized) {
222 | foreach (Target target in targets.keys)
223 | if (target.matches(url))
224 | return true;
225 | return false;
226 | }
227 |
228 | /**
229 | * Tells once told the context to ignore the ruleset with the given id
230 | *
231 | * Ruleset IDs are not a valid concept anymore. Do not use this method.
232 | * It will have no effect.
233 | * @since 0.4
234 | * @deprecated 0.6
235 | */
236 | public void ignore_ruleset(uint id) {}
237 |
238 | /**
239 | * Tells this context to check for a previously ignored ruleset again
240 | *
241 | * Ruleset IDs are not a valid concept anymore. Do not use this method.
242 | * It will have no effect.
243 | * @since 0.4
244 | * @deprecated 0.6
245 | */
246 | public void unignore_ruleset(uint id) {
247 | }
248 |
249 | /**
250 | * Tells this context to ignore the given host
251 | * @since 0.4
252 | */
253 | public void ignore_host(string host) {
254 | this.ignore_list.add(host);
255 | }
256 |
257 | /**
258 | * Tells this context to check for a previously ignored host again
259 | * @since 0.4
260 | */
261 | public void unignore_host(string host) {
262 | if (host in this.ignore_list)
263 | this.ignore_list.remove(host);
264 | }
265 |
266 | /**
267 | * Loads a ruleset from the database and stores it in the ram cache
268 | */
269 | private void load_rulesets() {
270 | Json.Node root = parser.get_root();
271 |
272 | if (root.get_node_type() != Json.NodeType.ARRAY) {
273 | warning("Could not parse rulesets: top node must be an array");
274 | }
275 | Json.Array rulesets_arr = root.get_array();
276 | uint id = 0;
277 | rulesets_arr.foreach_element((_,i,e)=>{
278 | try {
279 | var rs = new Ruleset.from_json(e);
280 | rulesets.@set(++id, rs);
281 | foreach (Target target in rs.targets) {
282 | if (this.targets.has_key(target)) {
283 | this.targets.@get(target).add(id);
284 | } else {
285 | var id_list = new Gee.ArrayList();
286 | id_list.add(id);
287 | this.targets.@set(target, id_list);
288 | }
289 | }
290 | } catch (RulesetError e) {
291 | warning("could not parse a ruleset");
292 | }
293 | });
294 | }
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/src/meson.build:
--------------------------------------------------------------------------------
1 | #********************************************************************+
2 | # Copyright 2016-2018 Daniel 'grindhold' Brendle
3 | #
4 | # This file is part of libhttpseverywhere.
5 | #
6 | # libhttpseverywhere is free software: you can redistribute it and/or
7 | # modify it under the terms of the GNU Lesser General Public License
8 | # as published by the Free Software Foundation, either
9 | # version 3 of the License, or (at your option) any later
10 | # version.
11 | #
12 | # libhttpseverywhere is distributed in the hope that it will be
13 | # useful, but WITHOUT ANY WARRANTY; without even the implied
14 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 | # PURPOSE. See the GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public
18 | # License along with libhttpseverywhere.
19 | # If not, see http://www.gnu.org/licenses/.
20 | #*********************************************************************
21 |
22 |
23 | httpseverywhere_lib_source = [
24 | 'context.vala',
25 | 'ruleset.vala',
26 | 'update.vala'
27 | ]
28 |
29 | valagir = 'HTTPSEverywhere-' + api + '.gir'
30 |
31 | httpseverywhere_lib = shared_library('httpseverywhere-'+api, httpseverywhere_lib_source,
32 | dependencies: [glib, gobject, json_glib, soup, gio, gee, archive],
33 | vala_gir: valagir,
34 | vala_header: 'httpseverywhere.h',
35 | install: true,
36 | version: libhttpseverywhere_version)
37 |
38 | g_ir_compiler = find_program('g-ir-compiler')
39 | custom_target('httpseverywhere-typelib',
40 | command: [g_ir_compiler, '--output', '@OUTPUT@', 'src/'+valagir, '--shared-library', 'libhttpseverywhere-' + api + '.so'],
41 | output: 'HTTPSEverywhere-' + api + '.typelib',
42 | depends: httpseverywhere_lib,
43 | install: true,
44 | install_dir: get_option('libdir') + '/girepository-1.0')
45 |
46 | if get_option('enable_valadoc')
47 | valadoc = find_program('valadoc')
48 | custom_target('apidocs',
49 | input: httpseverywhere_lib_source,
50 | command: [valadoc, '-o', 'devhelp/httpseverywhere-'+api, '--doclet', 'devhelp', '@INPUT@',
51 | '--force', '--pkg', 'gee-0.8', '--pkg', 'json-glib-1.0', '--pkg', 'libarchive', '--pkg', 'libsoup-2.4'],
52 | output: 'devhelp',
53 | build_by_default: true)
54 | install_subdir(meson.current_build_dir()+'/../devhelp/httpseverywhere-'+api+'/httpseverywhere-'+api,
55 | install_dir: get_option('datadir')+'/devhelp/books')
56 | endif
57 |
58 | # Create an empty httpseverywhere.h file in the build dir to satisfy
59 | # install_headers that the file exists. Remove when this PR is merged:
60 | # https://github.com/mesonbuild/meson/pull/1469
61 | httpseverywhere_h = meson.current_build_dir()+'/httpseverywhere.h'
62 | run_command ('touch', httpseverywhere_h)
63 | install_headers(httpseverywhere_h, subdir: 'httpseverywhere-'+api)
64 |
65 | # Create an empty httpseverywhere.vapi file in the build dir to satisfy
66 | # install_data that the file exists. Remove when this PR is merged:
67 | # https://github.com/mesonbuild/meson/pull/1469
68 | httpseverywhere_vapi = meson.current_build_dir()+'/httpseverywhere-'+api+'.vapi'
69 | run_command ('touch', httpseverywhere_vapi)
70 | install_data(httpseverywhere_vapi, install_dir: get_option('datadir') + '/vala/vapi')
71 |
72 |
--------------------------------------------------------------------------------
/src/ruleset.vala:
--------------------------------------------------------------------------------
1 | /* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 | /********************************************************************
3 | # Copyright 2015-2018 Daniel 'grindhold' Brendle
4 | #
5 | # This file is part of libhttpseverywhere.
6 | #
7 | # libhttpseverywhere is free software: you can redistribute it and/or
8 | # modify it under the terms of the GNU Lesser General Public License
9 | # as published by the Free Software Foundation, either
10 | # version 3 of the License, or (at your option) any later
11 | # version.
12 | #
13 | # libhttpseverywhere is distributed in the hope that it will be
14 | # useful, but WITHOUT ANY WARRANTY; without even the implied
15 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
16 | # PURPOSE. See the GNU Lesser General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU Lesser General Public
19 | # License along with libhttpseverywhere.
20 | # If not, see http://www.gnu.org/licenses/.
21 | *********************************************************************/
22 |
23 | namespace HTTPSEverywhere {
24 | /**
25 | * Errors that may occur when handling {@link HTTPSEverywhere.Ruleset}s
26 | */
27 | public errordomain RulesetError {
28 | /**
29 | * Gets thrown when a ruleset fails to parse
30 | */
31 | PARSE_ERROR
32 | }
33 |
34 | /**
35 | * This represents the contents of a HTTPSEverywhere ruleset-file
36 | * Rulesets contain a set of target-hosts that the rules apply to
37 | * furthermore it contains a set of regular expressions that determine
38 | * how to convert a HTTP-URL into the corresponding HTTPS-URL
39 | */
40 | public class Ruleset : GLib.Object {
41 | private string name;
42 | private string platform;
43 | private bool default_off;
44 |
45 | private Gee.ArrayList rules;
46 | private Gee.ArrayList exclusions;
47 | private Gee.ArrayList _targets;
48 | // TODO: implement
49 | //private string securecookie;
50 |
51 | /**
52 | * The target-hosts this ruleset applies to
53 | */
54 | public Gee.ArrayList targets {
55 | get {
56 | return this._targets;
57 | }
58 | }
59 |
60 | /**
61 | * Creates an empty Ruleset
62 | */
63 | public Ruleset() {
64 | this.rules = new Gee.ArrayList();
65 | this.exclusions = new Gee.ArrayList();
66 | this._targets = new Gee.ArrayList();
67 | }
68 |
69 | /**
70 | * Creates a Ruleset from a ruleset file
71 | */
72 | public Ruleset.from_json(Json.Node root) throws RulesetError {
73 | this();
74 | var obj = root.get_object();
75 |
76 | // Set the Rulesets attributes
77 | this.name = obj.has_member("name") ? obj.get_string_member("name") : null;
78 | this.default_off = obj.has_member("default_off");
79 | this.platform = obj.has_member("platform") ? obj.get_string_member("platform") : null;
80 |
81 | if (obj.has_member("rule")) {
82 | var rules = obj.get_array_member("rule");
83 | rules.foreach_element((_,i,e)=>{
84 | string? from = null;
85 | string? to = null;
86 | var rule = e.get_object();
87 | from = rule.get_string_member("from");
88 | to = rule.get_string_member("to");
89 | if (from == null || to == null)
90 | warning("Skipped malformed rule");
91 | else
92 | this.add_rule(from, to);
93 | });
94 | }
95 |
96 | if (obj.has_member("target")) {
97 | var targets = obj.get_array_member("target");
98 | targets.foreach_element((_, i, e) => {
99 | if (e.get_node_type() == Json.NodeType.VALUE) {
100 | this.add_target(e.get_string());
101 | }
102 | });
103 | }
104 |
105 | if (obj.has_member("exclusion")) {
106 | var exclusions = obj.get_array_member("exclusion");
107 | exclusions.foreach_element((_, i, e) => {
108 | if (e.get_node_type() == Json.NodeType.VALUE) {
109 | this.add_exclusion(e.get_string());
110 | }
111 | });
112 | }
113 | // TODO: implement securecookies
114 | }
115 |
116 | /**
117 | * Add a rewrite rule to this Ruleset
118 | * the parameters "from" and "to" should be valid regexes
119 | */
120 | public void add_rule(string from, string to) {
121 | this.rules.add(new Rule(from, to));
122 | }
123 |
124 | /**
125 | * Add an exclusion to this Ruleset
126 | */
127 | public void add_exclusion(string exclusion) {
128 | try {
129 | var exc_regex = new Regex(exclusion);
130 | this.exclusions.add(exc_regex);
131 | } catch (GLib.RegexError e) {
132 | warning("Could not add %s to exclusions", exclusion);
133 | }
134 | }
135 |
136 | /**
137 | * Add a target host to this Ruleset
138 | */
139 | public void add_target(string host) {
140 | this._targets.add(new Target(host));
141 | }
142 |
143 | /**
144 | * Rewrite an URL from HTTP to HTTPS
145 | */
146 | public string rewrite(string url) {
147 | // Skip if this rule is inactive
148 | if (this.default_off){
149 | return url;
150 | }
151 |
152 | // Skip if the given url matches any exclusions
153 | foreach (Regex exc in this.exclusions) {
154 | if (exc.match(url, 0))
155 | return url;
156 | }
157 |
158 | // Rewrite the url by the given rules
159 | string u = url;
160 | foreach (Rule rule in this.rules) {
161 | u = rule.rewrite(u);
162 | }
163 | return u;
164 | }
165 | }
166 |
167 | /**
168 | * This class represents a rewrite rule
169 | * Rewrite rules consist of a from-regex and a to-string
170 | */
171 | private class Rule : GLib.Object {
172 | private Regex obsolete_placeholders;
173 | private Regex? from;
174 | private string to = "";
175 |
176 | /**
177 | * Create a new rule from a from-string and a to-string
178 | * the from string should be a valid regex
179 | */
180 | public Rule (string from, string to) {
181 | try {
182 | this.from = new Regex(from);
183 | } catch (GLib.RegexError e) {
184 | warning("Invalid from-regex in rule: %s",from);
185 | this.from = null;
186 | }
187 | this.to = to;
188 | this.obsolete_placeholders = /\$\d/;
189 | }
190 |
191 | /**
192 | * Turns a HTTP-URL into an appropriate HTTPS-URL
193 | */
194 | public string rewrite(string url) {
195 | if (this.from == null)
196 | return url;
197 | MatchInfo info;
198 | if (this.from.match(url, 0, out info)) {
199 | var suffix = url.replace(info.fetch(0), "");
200 | string ret = this.to;
201 | if (info.get_match_count() > 1) {
202 | for (int i = 1; i < info.get_match_count(); i++) {
203 | ret = ret.replace("$%d".printf(i),info.fetch(i));
204 | }
205 | ret += suffix;
206 | }
207 | if (info.get_match_count() == 1) {
208 | ret = url.replace(info.fetch(0), this.to);
209 | // Remove unused $-placeholders
210 | ret = string.joinv("", this.obsolete_placeholders.split(ret));
211 | }
212 | return ret;
213 | } else
214 | return url;
215 | }
216 | }
217 |
218 | /**
219 | * Use Targets to check if a Ruleset applies to an arbitrary URL
220 | */
221 | public class Target : GLib.Object {
222 | public string host {get;set;default="";}
223 | private Regex? wildcardcheck;
224 |
225 | /**
226 | * Defines a new Target. Hosts of targets are only allowed to have one
227 | * asterisk as a wildcard-symbol
228 | */
229 | public Target(string host) {
230 | this.host = host;
231 | if (count_char(host,'*') > 1) {
232 | warning("Ignoring host %s. Contains more than one wildcard.".printf(host));
233 | return;
234 | }
235 | string escaped = Regex.escape_string(host);
236 | escaped = escaped.replace("""\*""", ".*");
237 | try {
238 | this.wildcardcheck = new Regex(escaped);
239 | } catch (GLib.RegexError e) {}
240 | }
241 |
242 | /**
243 | * This method checks if this target is applying to the given url
244 | */
245 | public bool matches(string url) {
246 | if (this.wildcardcheck == null) {
247 | warning("Tried to check invalid host: %s".printf(this.host));
248 | return false;
249 | }
250 | return this.wildcardcheck.match(url);
251 | }
252 | }
253 |
254 | private uint count_char(string s, unichar x) {
255 | uint r = 0;
256 | for (int i = 0; i < s.char_count(); i++) {
257 | if (s.get_char(i) == x)
258 | r++;
259 | }
260 | return r;
261 | }
262 |
263 | }
264 |
--------------------------------------------------------------------------------
/src/update.vala:
--------------------------------------------------------------------------------
1 | /* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 | /********************************************************************
3 | # Copyright 2015-2018 Daniel 'grindhold' Brendle
4 | #
5 | # This file is part of libhttpseverywhere.
6 | #
7 | # libhttpseverywhere is free software: you can redistribute it and/or
8 | # modify it under the terms of the GNU Lesser General Public License
9 | # as published by the Free Software Foundation, either
10 | # version 3 of the License, or (at your option) any later
11 | # version.
12 | #
13 | # libhttpseverywhere is distributed in the hope that it will be
14 | # useful, but WITHOUT ANY WARRANTY; without even the implied
15 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
16 | # PURPOSE. See the GNU Lesser General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU Lesser General Public
19 | # License along with libhttpseverywhere.
20 | # If not, see http://www.gnu.org/licenses/.
21 | *********************************************************************/
22 |
23 | namespace HTTPSEverywhere {
24 | /**
25 | * This enumerates states the update passes
26 | * through. They can be obtained via {@link Updater.update_state}
27 | * And used to show informative messages about progress
28 | * to the user.
29 | */
30 | public enum UpdateState {
31 | FINISHED,
32 | CHECKING_AVAILABILITY,
33 | DOWNLOADING_XPI,
34 | DECOMPRESSING_XPI,
35 | COPYING_RULES
36 | }
37 |
38 | /**
39 | * Errors that may occur during an update process
40 | */
41 | public errordomain UpdateError {
42 | IN_PROGRESS, // Update is already in progress
43 | NO_UPDATE_AVAILABLE,
44 | CANT_REACH_SERVER,
45 | CANT_READ_HTTP_BODY,
46 | CANT_READ_FROM_ARCHIVE,
47 | NO_RULESET_FILE,
48 | WRITE_FAILED
49 | }
50 |
51 | /**
52 | * This class lets the user of this library perform
53 | * an update of the used rule-files.
54 | */
55 | public class Updater : GLib.Object {
56 | /**
57 | * Constants
58 | */
59 | private static string UPDATE_DIR = Path.build_filename(Environment.get_user_data_dir(),
60 | "libhttpseverywhere");
61 | private const string UPDATE_URL = "https://www.eff.org/files/https-everywhere-latest.xpi";
62 | private const string RULESET_PATH = "rules/default.rulesets";
63 | private const string LOCK_NAME = "lock";
64 | private const string ETAG_NAME = "etag";
65 |
66 | /**
67 | * Used to check whether we are already doing an update
68 | */
69 | private bool update_in_progress = false;
70 | private UpdateState _update_state = UpdateState.FINISHED;
71 |
72 | private Context context;
73 |
74 | /**
75 | * Represents the currently processed state of the update
76 | * process. When no update is running, this property is of no use.
77 | */
78 | public UpdateState update_state {
79 | public get {return this._update_state;}
80 | private set {this._update_state = value;}
81 | }
82 |
83 | /**
84 | * Create a new Updater for the given library context.
85 | */
86 | public Updater(Context context) {
87 | this.context = context;
88 | }
89 |
90 | /**
91 | * Writes a file to the disk that inhibits other instances
92 | * of this library from doing updates
93 | *
94 | * If the lockfile has been created longer than 20 minutes
95 | * ago, the program will assume that it is a file from a failed
96 | * attempt to update and continue anyway.
97 | * 40 Minutes is the approximate time a user of a 56k-model will
98 | * need to download the updates.
99 | */
100 | private void lock_update() throws UpdateError {
101 | var file = File.new_for_path(Path.build_filename(UPDATE_DIR, LOCK_NAME));
102 | if (file.query_exists()) {
103 | try {
104 | var info = file.query_info("*", FileQueryInfoFlags.NONE);
105 | DateTime modification_time = new GLib.DateTime.from_timeval_local(info.get_modification_time());
106 | DateTime current_time = new GLib.DateTime.now_local();
107 | if (current_time.difference(modification_time) < 40 * TimeSpan.MINUTE) {
108 | throw new UpdateError.IN_PROGRESS("Update is already in progress");
109 | }
110 | update_in_progress = true;
111 | } catch (Error e ) {
112 | if (e is UpdateError.IN_PROGRESS) {
113 | throw (UpdateError)e;
114 | }
115 | throw new UpdateError.WRITE_FAILED("Error querying lock file: %s".printf(e.message));
116 | }
117 | } else {
118 | try {
119 | file.create(FileCreateFlags.NONE);
120 | update_in_progress = true;
121 | } catch (Error e) {
122 | throw new UpdateError.WRITE_FAILED("Error creating lock file: %s".printf(e.message));
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * Removes the update lock
129 | */
130 | private void unlock_update() {
131 | FileUtils.unlink(Path.build_filename(UPDATE_DIR, LOCK_NAME));
132 | update_in_progress = false;
133 | }
134 |
135 | /**
136 | * Actually executes the update
137 | */
138 | private async void execute_update(Cancellable? cancellable = null) throws UpdateError, IOError {
139 | var session = new Soup.Session();
140 |
141 | // Check if update is necessary
142 | update_state = UpdateState.CHECKING_AVAILABILITY;
143 | try {
144 | string etag;
145 | FileUtils.get_contents(Path.build_filename(UPDATE_DIR, ETAG_NAME), out etag);
146 | var msg = new Soup.Message("HEAD", UPDATE_URL);
147 | try {
148 | yield session.send_async(msg, cancellable);
149 | if (msg.response_headers.get_one("Etag") == etag) {
150 | throw new UpdateError.NO_UPDATE_AVAILABLE("Already the freshest version!");
151 | }
152 | } catch (Error e) {
153 | if (e is IOError.CANCELLED)
154 | throw (IOError) e;
155 | if (e is UpdateError.NO_UPDATE_AVAILABLE)
156 | throw (UpdateError) e;
157 | debug(e.message);
158 | throw new UpdateError.CANT_REACH_SERVER("Couldn't request update from '%s'", UPDATE_URL);
159 | }
160 | } catch (FileError e) {}
161 |
162 | // Download the XPI package
163 | update_state = UpdateState.DOWNLOADING_XPI;
164 | var msg = new Soup.Message("GET", UPDATE_URL);
165 | InputStream stream = null;
166 | try {
167 | stream = yield session.send_async(msg, cancellable);
168 | } catch (Error e) {
169 | if (e is IOError.CANCELLED)
170 | throw (IOError) e;
171 | debug(e.message);
172 | throw new UpdateError.CANT_REACH_SERVER("Couldn't request update from '%s'", UPDATE_URL);
173 | }
174 | // We expect the packed archive to be ~5 MiB big
175 | uint8[] output = new uint8[(5*1024*1024)];
176 | size_t size_read;
177 | try {
178 | yield stream.read_all_async(output, GLib.Priority.DEFAULT, cancellable, out size_read);
179 | } catch (Error e) {
180 | if (e is IOError.CANCELLED)
181 | throw (IOError) e;
182 | throw new UpdateError.CANT_READ_HTTP_BODY(e.message);
183 | }
184 |
185 | // Decompressing the XPI package
186 | // FIXME: Lots of slow sync operations below here, should be made async.
187 | update_state = UpdateState.DECOMPRESSING_XPI;
188 |
189 | Archive.Read zipreader = new Archive.Read();
190 | zipreader.set_format(Archive.Format.ZIP);
191 | #if VALA_0_42
192 | output.length = (int) size_read;
193 | zipreader.open_memory(output);
194 | #else
195 | zipreader.open_memory(output, size_read);
196 | #endif
197 |
198 | string json = "";
199 | unowned Archive.Entry e = null;
200 | bool found_ruleset_file = false;
201 | while (zipreader.next_header(out e) == Archive.Result.OK) {
202 | if (e != null && e.pathname() == Updater.RULESET_PATH) {
203 | found_ruleset_file = true;
204 | uint8[] jsonblock = new uint8[1024*1024];
205 | while (true) {
206 | #if VALA_0_42
207 | var r = zipreader.read_data(jsonblock);
208 | #else
209 | var r = zipreader.read_data(jsonblock, jsonblock.length);
210 | #endif
211 | if (r < 0) {
212 | throw new UpdateError.CANT_READ_FROM_ARCHIVE("Failed reading archive stream");
213 | }
214 | if (r < jsonblock.length && r != 0) {
215 | uint8[] remainder = new uint8[r];
216 | Memory.copy(remainder, jsonblock, r);
217 | json += (string)remainder;
218 | break;
219 | }
220 | json += (string)jsonblock;
221 | }
222 | break; // we dont need to read more files if we have the rulesets
223 | } else
224 | zipreader.read_data_skip();
225 | }
226 |
227 | // Throw an Exception when the expected ruleset file could not be found
228 | if (!found_ruleset_file)
229 | throw new UpdateError.NO_RULESET_FILE("Could not find expected ruleset file in the downloaded archive: %s", Updater.RULESET_PATH);
230 |
231 | // Copying the new Rules-File to the target
232 | update_state = UpdateState.COPYING_RULES;
233 | string rulesets_path = Path.build_filename(UPDATE_DIR, rulesets_file);
234 | try {
235 | FileUtils.set_contents(rulesets_path, json);
236 | } catch (FileError e) {
237 | throw new UpdateError.WRITE_FAILED("Could not write rulesets file at '%s'".printf(rulesets_path));
238 | }
239 |
240 | // Write Etag of update to disk
241 | string etag = msg.response_headers.get_one("Etag");
242 | string etag_path = Path.build_filename(UPDATE_DIR, ETAG_NAME);
243 | try {
244 | FileUtils.set_contents(etag_path, etag);
245 | } catch (FileError e) {
246 | throw new UpdateError.WRITE_FAILED("Could not write etag file at '%s'".printf(etag_path));
247 | }
248 |
249 | update_state = UpdateState.FINISHED;
250 | }
251 |
252 | /**
253 | * This function initializes an update of the used rulefiles
254 | *
255 | * Remember to update the rulesets afterwards via {@link Context.init}
256 | * It will return true on success and false on failure
257 | */
258 | public async void update(Cancellable? cancellable = null) throws UpdateError, IOError {
259 | try {
260 | var f = File.new_for_path(UPDATE_DIR);
261 | f.make_directory_with_parents();
262 | } catch (Error e) {
263 | if (!(e is IOError.EXISTS))
264 | critical("Could not create %s: %s", UPDATE_DIR, e.message);
265 | }
266 |
267 | lock_update();
268 | try {
269 | yield execute_update(cancellable);
270 | } catch (UpdateError e) {
271 | throw e;
272 | } finally {
273 | unlock_update();
274 | }
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/test/main.vala:
--------------------------------------------------------------------------------
1 | /* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 | /********************************************************************
3 | # Copyright 2015-2018 Daniel 'grindhold' Brendle
4 | #
5 | # This file is part of libhttpseverywhere.
6 | #
7 | # libhttpseverywhere is free software: you can redistribute it and/or
8 | # modify it under the terms of the GNU Lesser General Public License
9 | # as published by the Free Software Foundation, either
10 | # version 3 of the License, or (at your option) any later
11 | # version.
12 | #
13 | # libhttpseverywhere is distributed in the hope that it will be
14 | # useful, but WITHOUT ANY WARRANTY; without even the implied
15 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
16 | # PURPOSE. See the GNU Lesser General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU Lesser General Public
19 | # License along with libhttpseverywhere.
20 | # If not, see http://www.gnu.org/licenses/.
21 | *********************************************************************/
22 |
23 | using HTTPSEverywhere;
24 |
25 | namespace HTTPSEverywhereTest {
26 | class Main {
27 | public static int main (string[] args) {
28 | Test.init(ref args);
29 | ContextTest.add_tests();
30 | RulesetTest.add_tests();
31 | UpdaterTest.add_tests();
32 | Test.run();
33 | return 0;
34 | }
35 | }
36 |
37 | class ContextTest {
38 | /* Note that Context tests do not check if a particular URI has been
39 | * rewritten, because we don't want the tests to be dependent on
40 | * changing rulesets.
41 | */
42 | public static void add_tests () {
43 | Test.add_func("/httpseverywhere/context/rewrite", () => {
44 | var context = new Context();
45 | var m = new MainLoop();
46 | context.init.begin(null, (obj, res) => {
47 | try {
48 | context.init.end(res);
49 | var result = context.rewrite("http://example.com");
50 | assert(result == "http://example.com/" || result == "https://example.com/");
51 | assert(context.has_https("http://example.com") == result.has_prefix("https://"));
52 | m.quit();
53 | } catch (Error e) {
54 | GLib.assert_not_reached();
55 | }
56 | });
57 | m.run();
58 | });
59 |
60 | Test.add_func("/httpseverywhere/context/cancel_init", () => {
61 | var loop = new MainLoop();
62 | var context = new Context();
63 | var cancellable = new Cancellable();
64 |
65 | context.init.begin(cancellable, (obj, res) => {
66 | try {
67 | context.init.end(res);
68 | assert_not_reached();
69 | } catch (Error e) {
70 | assert(e is IOError.CANCELLED);
71 | assert(cancellable.is_cancelled());
72 | loop.quit();
73 | }
74 | });
75 |
76 | cancellable.cancel();
77 | loop.run();
78 | });
79 |
80 | Test.add_func("/httpseverywhere/context/rewrite_before_init", () => {
81 | if (Test.subprocess()) {
82 | /* Should emit a critical since init has not been called. */
83 | new Context().rewrite("http://example.com");
84 | }
85 |
86 | Test.trap_subprocess(null, 0, 0);
87 | Test.trap_assert_failed();
88 | Test.trap_assert_stderr("*CRITICAL*");
89 | });
90 | }
91 | }
92 |
93 | class RulesetTest {
94 | public static void add_tests () {
95 | Test.add_func("/httpseverywhere/ruleset/simple", () => {
96 | var from = "^http:";
97 | var to = "https:";
98 | var url = "http://blog.fefe.de";
99 |
100 | var ruleset = new Ruleset();
101 | ruleset.add_rule(from, to);
102 |
103 | assert (ruleset.rewrite(url) == "https://blog.fefe.de");
104 | });
105 |
106 | Test.add_func("/httpseverywhere/ruleset/1group", () => {
107 | var from = "^http://(en|fr)wp\\.org/";
108 | var to = "https://$1.wikipedia.org/wiki/";
109 | var url = "http://enwp.org/Tamale";
110 |
111 | var ruleset = new Ruleset();
112 | ruleset.add_rule(from, to);
113 |
114 | assert (ruleset.rewrite(url) == "https://en.wikipedia.org/wiki/Tamale");
115 |
116 | url = "http://frwp.org/Tamale";
117 | assert (ruleset.rewrite(url) == "https://fr.wikipedia.org/wiki/Tamale");
118 | });
119 |
120 | Test.add_func("/httpseverywhere/ruleset/optional_subdomain", () => {
121 | var from = "^http://(?:www\\.)?filescrunch\\.com/";
122 | var to = "https://filescrunch.com/";
123 | var url = "http://filescrunch.com/nyannyannyannyan";
124 |
125 | var ruleset = new Ruleset();
126 | ruleset.add_rule(from, to);
127 |
128 | assert (ruleset.rewrite(url) == "https://filescrunch.com/nyannyannyannyan");
129 |
130 | url = "http://www.filescrunch.com/nyannyannyannyan";
131 | assert (ruleset.rewrite(url) == "https://filescrunch.com/nyannyannyannyan");
132 | });
133 |
134 | Test.add_func("/httpseverywhere/ruleset/omitted_replace_fields", () => {
135 | var from = "^(http://(www\\.)?|https://)(dl|fsadownload|fsaregistration|ifap|nslds|tcli)\\.ed\\.gov/";
136 | var to = "https://www.$3.ed.gov/";
137 | var url = "http://fsaregistration.ed.gov/";
138 |
139 | var ruleset = new Ruleset();
140 | ruleset.add_rule(from, to);
141 |
142 | assert (ruleset.rewrite(url) == "https://www.fsaregistration.ed.gov/");
143 |
144 | url = "http://www.dl.ed.gov/";
145 | assert (ruleset.rewrite(url) == "https://www.dl.ed.gov/");
146 | });
147 |
148 | Test.add_func("/httpseverywhere/context/ignore", () => {
149 | var context = new Context();
150 | var m = new MainLoop();
151 | context.init.begin(null, (obj, res) => {
152 | try {
153 | context.init.end(res);
154 | var result = context.rewrite("http://forums.lemonde.fr");
155 | assert(result.has_prefix("https://"));
156 | context.ignore_host("forums.lemonde.fr");
157 | result = context.rewrite("http://forums.lemonde.fr");
158 | assert(result.has_prefix("http://"));
159 | context.unignore_host("forums.lemonde.fr");
160 | result = context.rewrite("http://forums.lemonde.fr");
161 | assert(result.has_prefix("https://"));
162 | m.quit();
163 | } catch (Error e) {
164 | GLib.assert_not_reached();
165 | }
166 | });
167 | m.run();
168 | });
169 | }
170 | }
171 |
172 | class UpdaterTest {
173 | public static void add_tests () {
174 | Test.add_func("/httpseverywhere/updater/update", () => {
175 | var context = new Context();
176 | context.init.begin(null, (obj, res) => {
177 | try {
178 | context.init.end(res);
179 | } catch (Error e) {
180 | GLib.assert_not_reached();
181 | }
182 | });
183 | var updater = new Updater(context);
184 | var m = new MainLoop();
185 | updater.update.begin(null, (obj, res) => {
186 | try {
187 | updater.update.end(res);
188 | m.quit();
189 | } catch (UpdateError.NO_UPDATE_AVAILABLE e) {
190 | m.quit();
191 | } catch (Error e) {
192 | GLib.assert_not_reached();
193 | }
194 | });
195 | m.run();
196 | });
197 | }
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/test/meson.build:
--------------------------------------------------------------------------------
1 | #********************************************************************+
2 | # Copyright 2016-2018 Daniel 'grindhold' Brendle
3 | #
4 | # This file is part of libhttpseverywhere.
5 | #
6 | # libhttpseverywhere is free software: you can redistribute it and/or
7 | # modify it under the terms of the GNU Lesser General Public License
8 | # as published by the Free Software Foundation, either
9 | # version 3 of the License, or (at your option) any later
10 | # version.
11 | #
12 | # libhttpseverywhere is distributed in the hope that it will be
13 | # useful, but WITHOUT ANY WARRANTY; without even the implied
14 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 | # PURPOSE. See the GNU Lesser General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public
18 | # License along with libhttpseverywhere.
19 | # If not, see http://www.gnu.org/licenses/.
20 | #*********************************************************************
21 |
22 |
23 | httpseverywhere_test_source = [
24 | 'main.vala'
25 | ]
26 |
27 | httpseverywhere_test = executable('httpseverywhere_test', httpseverywhere_test_source,
28 | dependencies: [glib, gobject, gio, gee, json_glib],
29 | link_with: httpseverywhere_lib,
30 | include_directories: include_directories('../src'))
31 |
--------------------------------------------------------------------------------