├── .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 | [![Packaging status](https://repology.org/badge/vertical-allrepos/libhttpseverywhere.svg)](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 | --------------------------------------------------------------------------------