i2p: Use augeas for editing the router.config

It's cleaner and less hacky, however we still overwrite the default favs because
they aren't written to the file by i2p until a change is made manually in the
frontend.

We still need to recreate the list of default and add them manually.

Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
LoveIsGrief 2019-04-13 00:00:25 +02:00 committed by Sunil Mohan Adapa
parent c13e9a4227
commit 1c9ad9f953
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
6 changed files with 229 additions and 29 deletions

View File

@ -23,7 +23,7 @@ import argparse
import os
from plinth import action_utils, cfg
from plinth.modules.i2p.helpers import TunnelEditor
from plinth.modules.i2p.helpers import RouterEditor, TunnelEditor
cfg.read()
module_config_path = os.path.join(cfg.config_dir, 'modules-enabled')
@ -87,36 +87,13 @@ def subcommand_add_favorite(arguments):
:param arguments:
:type arguments:
"""
router_config_path = os.path.join(I2P_CONF_DIR, 'router.config')
# Read config
with open(router_config_path) as config_file:
config_lines = config_file.readlines()
found_favorites = False
url = arguments.url
new_favorite = '{name},{description},{url},{icon},'.format(
name=arguments.name, description='', url=arguments.url,
icon='/themes/console/images/eepsite.png')
for i in range(len(config_lines)):
line = config_lines[i]
# Find favorites line
if line.startswith('routerconsole.favorites'):
found_favorites = True
if url in line:
print('URL already in favorites')
exit(0)
# Append favorite
config_lines[i] = line.strip() + new_favorite + '\n'
break
if not found_favorites:
config_lines.append('routerconsole.favorites=' + new_favorite + '\n')
# Update config
with open(router_config_path, mode='w') as config_file:
config_file.writelines(config_lines)
editor = RouterEditor()
editor.read_conf().add_favorite(
arguments.name,
url
).write_conf()
print('Added {} to favorites'.format(url))

View File

@ -59,6 +59,10 @@ proxies_service = None
manual_page = 'I2P'
# TODO: Add all default favorites
# the router.config favorites is empty until a change is made
# in the front-end, but we currently do not have a method of
# doing that, so we need to add the favorites ourselves
additional_favorites = [
('Searx instance', 'http://ransack.i2p'),
('Torrent tracker', 'http://tracker2.postman.i2p'),

View File

@ -17,6 +17,7 @@
Various helpers for the I2P app.
"""
from collections import OrderedDict
import os
import re
@ -25,6 +26,7 @@ import augeas
I2P_CONF_DIR = '/var/lib/i2p/i2p-config'
FILE_TUNNEL_CONF = os.path.join(I2P_CONF_DIR, 'i2ptunnel.config')
TUNNEL_IDX_REGEX = re.compile(r'tunnel.(\d+).name$')
I2P_ROUTER_CONF = os.path.join(I2P_CONF_DIR, 'router.config')
class TunnelEditor():
@ -133,3 +135,93 @@ class TunnelEditor():
def __setitem__(self, tunnel_prop, value):
self.aug.set(self.calc_prop_path(tunnel_prop), value)
class RouterEditor(object):
"""
:type aug: augeas.Augeas
"""
FAVORITE_PROP = 'routerconsole.favorites'
FAVORITE_TUPLE_SIZE = 4
def __init__(self, filename=None):
self.conf_filename = filename or I2P_ROUTER_CONF
self.aug = None
def read_conf(self):
self.aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
self.aug.set('/augeas/load/Properties/lens', 'Properties.lns')
self.aug.set('/augeas/load/Properties/incl[last() + 1]', self.conf_filename)
self.aug.load()
return self
def write_conf(self):
self.aug.save()
return self
@property
def favorite_property(self):
return '/files{filename}/{prop}'.format(filename=self.conf_filename, prop=self.FAVORITE_PROP)
def add_favorite(self, name, url, description='', icon='/themes/console/images/eepsite.png'):
"""
Adds a favorite to the router.config
Favorites are in a single string and separated by ','.
none of the incoming params can therefore use commas.
I2P replaces the commas by dots.
That's ok for the name and description,
but not for the url and icon
:type name: basestring
:type url: basestring
:type description: basestring
:type icon: basestring
"""
if ',' in url:
raise ValueError('URL cannot contain commas')
if ',' in icon:
raise ValueError('Icon cannot contain commas')
name = name.replace(',', '.')
description = description.replace(',', '.')
prop = self.favorite_property
favorites = self.aug.get(prop) or ''
new_favorite = '{name},{description},{url},{icon},'.format(
name=name, description=description, url=url,
icon=icon
)
self.aug.set(prop, favorites + new_favorite)
return self
def get_favorites(self):
favs_string = self.aug.get(self.favorite_property) or ''
favs_split = favs_string.split(',')
# There's a trailing comma --> 1 extra
favs_len = len(favs_split)
if favs_len > 0:
favs_split = favs_split[:-1]
favs_len = len(favs_split)
if favs_len % self.FAVORITE_TUPLE_SIZE:
raise SyntaxError("Invalid number of fields in favorite line")
favs = OrderedDict()
i = 0
while i < favs_len:
next_idx = i + self.FAVORITE_TUPLE_SIZE
t = favs_split[i:next_idx]
name, description, url, icon = t
favs[url] = {
"name": name,
"description": description,
"icon": icon
}
i = next_idx
return favs

View File

@ -0,0 +1,18 @@
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from pathlib import Path
DATA_DIR = Path(__file__).parent / 'data'

View File

@ -0,0 +1,23 @@
# NOTE: This I2P config file must use UTF-8 encoding
i2np.lastIPChange=1555091394049
i2np.ntcp2.iv=i3yLM2tPW6QH5h4YYZ8EWQ==
i2np.ntcp2.sp=j1K18jMDa5SPH23R2cHMJ-dUyPdo~uooZp6Uz06qP0k=
i2np.udp.internalPort=18778
i2np.udp.port=18778
jbigi.lastProcessor=Haswell Celeron/Pentium w/ AVX model 60/64
router.blocklistVersion=1523108115000
router.firstInstalled=1555091372597
router.firstVersion=0.9.38
router.inboundPool.randomKey=c1BGKzCBpxYfsH0AiEMIS39zYWzyBuWO9lYTqeA91dk=
router.outboundPool.randomKey=Of0jkHuGeUAHr~NoIAQbY930fbYacb4NyX3CjbVKKFI=
router.passwordManager.migrated=true
router.previousVersion=0.9.38
router.startup.jetty9.migrated=true
router.updateDisabled=true
router.updateLastInstalled=1555091372597
routerconsole.country=
routerconsole.favorites=anoncoin.i2p,The Anoncoin project,http://anoncoin.i2p/,/themes/console/images/anoncoin_32.png,Dev Builds,Development builds of I2P,http://bobthebuilder.i2p/,/themes/console/images/script_gear.png,Dev Forum,Development forum,http://zzz.i2p/,/themes/console/images/group_gear.png,echelon.i2p,I2P Applications,http://echelon.i2p/,/themes/console/images/box_open.png,exchanged.i2p,Anonymous cryptocurrency exchange,http://exchanged.i2p/,/themes/console/images/exchanged.png,I2P Bug Reports,Bug tracker,http://trac.i2p2.i2p/report/1,/themes/console/images/bug.png,I2P FAQ,Frequently Asked Questions,http://i2p-projekt.i2p/faq,/themes/console/images/question.png,I2P Forum,Community forum,http://i2pforum.i2p/,/themes/console/images/group.png,I2P Plugins,Add-on directory,http://i2pwiki.i2p/index.php?title=Plugins,/themes/console/images/info/plugin_link.png,I2P Technical Docs,Technical documentation,http://i2p-projekt.i2p/how,/themes/console/images/education.png,I2P Wiki,Anonymous wiki - share the knowledge,http://i2pwiki.i2p/,/themes/console/images/i2pwiki_logo.png,Planet I2P,I2P News,http://planet.i2p/,/themes/console/images/world.png,PrivateBin,Encrypted I2P Pastebin,http://paste.crypthost.i2p/,/themes/console/images/paste_plain.png,Project Website,I2P home page,http://i2p-projekt.i2p/,/themes/console/images/info_rhombus.png,stats.i2p,I2P Network Statistics,http://stats.i2p/cgi-bin/dashboard.cgi,/themes/console/images/chart_line.png,The Tin Hat,Privacy guides and tutorials,http://secure.thetinhat.i2p/,/themes/console/images/thetinhat.png,Trac Wiki,,http://trac.i2p2.i2p/,/themes/console/images/billiard_marker.png,
routerconsole.lang=en
routerconsole.newsLastChecked=1555093599462
routerconsole.newsLastUpdated=1553195540000
routerconsole.welcomeWizardComplete=true

View File

@ -0,0 +1,86 @@
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import unittest
from plinth.modules.i2p.helpers import RouterEditor
from plinth.modules.i2p.tests import DATA_DIR
ROUTER_CONF_PATH = str(DATA_DIR / 'router.config')
class RouterEditingTests(unittest.TestCase):
def setUp(self):
self.editor = RouterEditor(ROUTER_CONF_PATH)
def test_count_favs(self):
self.editor.read_conf()
favs = self.editor.get_favorites()
self.assertEqual(len(favs.keys()), 17)
def test_add_normal_favorite(self):
self.editor.read_conf()
name = 'Somewhere'
url = 'http://somewhere-again.i2p'
description = "Just somewhere else"
self.editor.add_favorite(
name, url, description
)
favs = self.editor.get_favorites()
self.assertIn(url, favs)
favorite = favs[url]
self.assertEqual(favorite['name'], name)
self.assertEqual(favorite['description'], description)
self.assertEqual(len(favs), 18)
def test_add_favorite_with_comma(self):
self.editor.read_conf()
name = 'Name,with,comma'
expected_name = name.replace(',', '.')
url = 'http://url-without-comma.i2p'
description = "Another,comma,to,play,with"
expected_description = description.replace(',', '.')
self.editor.add_favorite(
name, url, description
)
favs = self.editor.get_favorites()
self.assertIn(url, favs)
favorite = favs[url]
self.assertEqual(favorite['name'], expected_name)
self.assertEqual(favorite['description'], expected_description)
self.assertEqual(len(favs), 18)
def test_add_fav_to_empty_config(self):
self.editor.conf_filename = '/tmp/inexistent.conf'
self.editor.read_conf()
self.assertEqual(len(self.editor.get_favorites()), 0)
name = 'Test Favorite'
url = 'http://test-fav.i2p'
self.editor.add_favorite(
name, url
)
self.assertEqual(len(self.editor.get_favorites()), 1)
if __name__ == '__main__':
unittest.main()