Removed old block system
This commit is contained in:
parent
e99056afac
commit
1d22b43ef9
@ -70,7 +70,6 @@ createdirs.create_dirs()
|
||||
import bigbrother # noqa
|
||||
from onionrcommands import parser # noqa
|
||||
from onionrplugins import onionrevents as events # noqa
|
||||
from onionrblocks.deleteplaintext import delete_plaintext_no_blacklist # noqa
|
||||
|
||||
setup.setup_config()
|
||||
|
||||
@ -84,8 +83,6 @@ if config.get('advanced.security_auditing', True):
|
||||
except onionrexceptions.PythonVersion:
|
||||
pass
|
||||
|
||||
if not config.get('general.store_plaintext_blocks', True):
|
||||
delete_plaintext_no_blacklist()
|
||||
|
||||
setup.setup_default_plugins()
|
||||
|
||||
|
@ -68,7 +68,6 @@ class PrivateAPI:
|
||||
self.httpServer = ''
|
||||
|
||||
self.queueResponse = {}
|
||||
self.get_block_data = httpapi.apiutils.GetBlockData(self)
|
||||
register_private_blueprints.register_private_blueprints(self, app)
|
||||
httpapi.load_plugin_blueprints(app)
|
||||
self.app = app
|
||||
|
@ -6,7 +6,7 @@ from threading import Thread
|
||||
from gevent import sleep
|
||||
|
||||
from httpapi import security, friendsapi, configapi
|
||||
from httpapi import miscclientapi, onionrsitesapi, apiutils
|
||||
from httpapi import miscclientapi, apiutils
|
||||
from httpapi import themeapi
|
||||
from httpapi import fileoffsetreader
|
||||
from httpapi.sse.private import private_sse_blueprint
|
||||
@ -35,8 +35,6 @@ def register_private_blueprints(private_api, app):
|
||||
app.register_blueprint(configapi.config_BP)
|
||||
app.register_blueprint(miscclientapi.endpoints.PrivateEndpoints(
|
||||
private_api).private_endpoints_bp)
|
||||
app.register_blueprint(miscclientapi.motd.bp)
|
||||
app.register_blueprint(onionrsitesapi.site_api)
|
||||
app.register_blueprint(apiutils.shutdown.shutdown_bp)
|
||||
app.register_blueprint(miscclientapi.staticfiles.static_files_bp)
|
||||
app.register_blueprint(themeapi.theme_blueprint)
|
||||
|
@ -1 +1 @@
|
||||
from . import keydb, blockmetadb
|
||||
from . import keydb
|
@ -1,84 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Work with information relating to blocks stored on the node
|
||||
"""
|
||||
import sqlite3
|
||||
|
||||
from etc import onionrvalues
|
||||
from . import expiredblocks, updateblockinfo, add
|
||||
from .. import dbfiles
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
update_block_info = updateblockinfo.update_block_info
|
||||
add_to_block_DB = add.add_to_block_DB
|
||||
|
||||
|
||||
def get_block_list(date_rec=None, unsaved=False):
|
||||
"""Get list of our blocks."""
|
||||
if date_rec is None:
|
||||
date_rec = 0
|
||||
|
||||
conn = sqlite3.connect(
|
||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
|
||||
execute = 'SELECT hash FROM hashes WHERE dateReceived' + \
|
||||
' >= ? ORDER BY dateReceived ASC;'
|
||||
args = (date_rec,)
|
||||
rows = list()
|
||||
for row in c.execute(execute, args):
|
||||
for i in row:
|
||||
rows.append(i)
|
||||
conn.close()
|
||||
return rows
|
||||
|
||||
|
||||
def get_block_date(blockHash):
|
||||
"""Return the date a block was received."""
|
||||
conn = sqlite3.connect(
|
||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
|
||||
execute = 'SELECT dateReceived FROM hashes WHERE hash=?;'
|
||||
args = (blockHash,)
|
||||
for row in c.execute(execute, args):
|
||||
for i in row:
|
||||
return int(i)
|
||||
conn.close()
|
||||
return None
|
||||
|
||||
|
||||
def get_blocks_by_type(blockType, orderDate=True):
|
||||
"""Return a list of blocks by the type."""
|
||||
|
||||
conn = sqlite3.connect(
|
||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
|
||||
if orderDate:
|
||||
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
|
||||
else:
|
||||
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
|
||||
|
||||
args = (blockType,)
|
||||
rows = list()
|
||||
|
||||
for row in c.execute(execute, args):
|
||||
for i in row:
|
||||
rows.append(i)
|
||||
conn.close()
|
||||
return rows
|
||||
|
@ -1,49 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Add an entry to the block metadata database
|
||||
"""
|
||||
import sqlite3
|
||||
import secrets
|
||||
from onionrutils import epoch
|
||||
from onionrblocks import blockmetadata
|
||||
from etc import onionrvalues
|
||||
from .. import dbfiles
|
||||
from onionrexceptions import BlockMetaEntryExists
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def add_to_block_DB(newHash, selfInsert=False, dataSaved=False):
|
||||
"""
|
||||
Add a hash value to the block db
|
||||
|
||||
Should be in hex format!
|
||||
"""
|
||||
|
||||
if blockmetadata.has_block(newHash):
|
||||
raise BlockMetaEntryExists
|
||||
conn = sqlite3.connect(
|
||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
currentTime = epoch.get_epoch() + secrets.randbelow(61)
|
||||
if selfInsert or dataSaved:
|
||||
selfInsert = 1
|
||||
else:
|
||||
selfInsert = 0
|
||||
data = (newHash, currentTime, '', selfInsert)
|
||||
c.execute(
|
||||
'INSERT INTO hashes (hash, dateReceived, dataType, dataSaved) VALUES(?, ?, ?, ?);', data)
|
||||
conn.commit()
|
||||
conn.close()
|
@ -1,41 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Get a list of expired blocks still stored
|
||||
"""
|
||||
import sqlite3
|
||||
from onionrutils import epoch
|
||||
from .. import dbfiles
|
||||
from etc import onionrvalues
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def get_expired_blocks():
|
||||
"""Return a list of expired blocks."""
|
||||
conn = sqlite3.connect(
|
||||
dbfiles.block_meta_db, timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
date = int(epoch.get_epoch())
|
||||
|
||||
compiled = (date,)
|
||||
execute = 'SELECT hash FROM hashes WHERE ' + \
|
||||
'expire <= ? ORDER BY dateReceived;'
|
||||
|
||||
rows = list()
|
||||
for row in c.execute(execute, compiled):
|
||||
for i in row:
|
||||
rows.append(i)
|
||||
conn.close()
|
||||
return rows
|
@ -1,52 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Update block information in the metadata database by a field name
|
||||
"""
|
||||
import sqlite3
|
||||
|
||||
from .. import dbfiles
|
||||
from etc import onionrvalues
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def update_block_info(hash, key, data):
|
||||
"""set info associated with a block
|
||||
|
||||
hash - the hash of a block
|
||||
dateReceived - the date the block was recieved, not necessarily when it was created
|
||||
decrypted - if we can successfully decrypt the block
|
||||
dataType - data type of the block
|
||||
dataFound - if the data has been found for the block
|
||||
dataSaved - if the data has been saved for the block
|
||||
sig - defunct
|
||||
author - defunct
|
||||
dateClaimed - timestamp claimed inside the block, only as trustworthy as the block author is
|
||||
expire - expire date for a block
|
||||
"""
|
||||
if key not in ('dateReceived', 'decrypted', 'dataType', 'dataFound',
|
||||
'dataSaved', 'sig', 'author', 'dateClaimed', 'expire'):
|
||||
raise ValueError('Key must be in the allowed list')
|
||||
|
||||
conn = sqlite3.connect(dbfiles.block_meta_db,
|
||||
timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
args = (data, hash)
|
||||
# Unfortunately, not really possible to prepare this statement
|
||||
c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return True
|
@ -3,8 +3,6 @@ import filepaths
|
||||
home = identifyhome.identify_home()
|
||||
if not home.endswith('/'): home += '/'
|
||||
|
||||
block_meta_db = '%sblock-metadata.db' % (home)
|
||||
block_data_db = '%s/block-data.db' % (filepaths.block_data_location,)
|
||||
address_info_db = '%saddress.db' % (home,)
|
||||
user_id_info_db = '%susers.db' % (home,)
|
||||
forward_keys_db = '%sforward-keys.db' % (home,)
|
||||
|
@ -1,3 +1 @@
|
||||
from . import shutdown, setbindip, getblockdata
|
||||
|
||||
GetBlockData = getblockdata.GetBlockData
|
||||
from . import shutdown, setbindip
|
@ -1,38 +0,0 @@
|
||||
import ujson as json
|
||||
|
||||
from onionrblocks import onionrblockapi
|
||||
from onionrutils import bytesconverter, stringvalidators
|
||||
import onionrexceptions
|
||||
class GetBlockData:
|
||||
def __init__(self, client_api_inst=None):
|
||||
return
|
||||
|
||||
def get_block_data(self, bHash, decrypt=False, raw=False, headerOnly=False):
|
||||
if not stringvalidators.validate_hash(bHash):
|
||||
raise onionrexceptions.InvalidHexHash(
|
||||
"block hash not valid hash format")
|
||||
bl = onionrblockapi.Block(bHash)
|
||||
if decrypt:
|
||||
bl.decrypt()
|
||||
if bl.isEncrypted and not bl.decrypted:
|
||||
raise ValueError
|
||||
|
||||
if not raw:
|
||||
if not headerOnly:
|
||||
retData = {'meta':bl.bheader, 'metadata': bl.bmetadata, 'content': bl.bcontent}
|
||||
for x in list(retData.keys()):
|
||||
try:
|
||||
retData[x] = retData[x].decode()
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
validSig = False
|
||||
signer = bytesconverter.bytes_to_str(bl.signer)
|
||||
if bl.isSigned() and stringvalidators.validate_pub_key(signer) and bl.isSigner(signer):
|
||||
validSig = True
|
||||
bl.bheader['validSig'] = validSig
|
||||
bl.bheader['meta'] = ''
|
||||
retData = {'meta': bl.bheader, 'metadata': bl.bmetadata}
|
||||
return json.dumps(retData)
|
||||
else:
|
||||
return bl.raw
|
@ -1 +1 @@
|
||||
from . import staticfiles, endpoints, motd
|
||||
from . import staticfiles, endpoints
|
@ -78,10 +78,6 @@ class PrivateEndpoints:
|
||||
subprocess.Popen([SCRIPT_NAME, 'restart'])
|
||||
return Response("bye")
|
||||
|
||||
@private_endpoints_bp.route('/gethidden')
|
||||
def get_hidden_blocks():
|
||||
return Response('\n'.join(client_api.publicAPI.hideBlocks))
|
||||
|
||||
|
||||
@private_endpoints_bp.route('/getuptime')
|
||||
def show_uptime():
|
||||
|
@ -1,27 +0,0 @@
|
||||
from flask import Blueprint
|
||||
from flask import Response
|
||||
import unpaddedbase32
|
||||
|
||||
from coredb import blockmetadb
|
||||
import onionrblocks
|
||||
from etc import onionrvalues
|
||||
import config
|
||||
from onionrutils import bytesconverter
|
||||
|
||||
bp = Blueprint('motd', __name__)
|
||||
|
||||
signer = config.get("motd.motd_key", onionrvalues.MOTD_SIGN_KEY)
|
||||
|
||||
@bp.route('/getmotd')
|
||||
def get_motd()->Response:
|
||||
motds = blockmetadb.get_blocks_by_type("motd")
|
||||
newest_time = 0
|
||||
message = "No MOTD currently present."
|
||||
for x in motds:
|
||||
bl = onionrblocks.onionrblockapi.Block(x)
|
||||
if not bl.verifySig() or bl.signer != bytesconverter.bytes_to_str(unpaddedbase32.repad(bytesconverter.str_to_bytes(signer))): continue
|
||||
if not bl.isSigner(signer): continue
|
||||
if bl.claimedTime > newest_time:
|
||||
newest_time = bl.claimedTime
|
||||
message = bl.bcontent
|
||||
return Response(message, headers={"Content-Type": "text/plain"})
|
@ -1,94 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
view and interact with onionr sites
|
||||
"""
|
||||
import base64
|
||||
import binascii
|
||||
import mimetypes
|
||||
|
||||
import unpaddedbase32
|
||||
|
||||
from flask import Blueprint, Response, request, abort
|
||||
|
||||
from onionrblocks import onionrblockapi
|
||||
import onionrexceptions
|
||||
from onionrutils import stringvalidators
|
||||
from onionrutils import mnemonickeys
|
||||
from . import sitefiles
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
site_api = Blueprint('siteapi', __name__)
|
||||
|
||||
@site_api.route('/site/<name>/', endpoint='site')
|
||||
def site(name: str)->Response:
|
||||
"""Accept a site 'name', if pubkey then show multi-page site, if hash show single page site"""
|
||||
resp: str = 'Not Found'
|
||||
mime_type = 'text/html'
|
||||
|
||||
# If necessary convert the name to base32 from mnemonic
|
||||
if mnemonickeys.DELIMITER in name:
|
||||
name = mnemonickeys.get_base32(name)
|
||||
|
||||
# Now make sure the key is regardless a valid base32 format ed25519 key (readding padding if necessary)
|
||||
if stringvalidators.validate_pub_key(name):
|
||||
name = unpaddedbase32.repad(name)
|
||||
resp = sitefiles.get_file(name, 'index.html')
|
||||
|
||||
elif stringvalidators.validate_hash(name):
|
||||
try:
|
||||
resp = onionrblockapi.Block(name).bcontent
|
||||
except onionrexceptions.NoDataAvailable:
|
||||
abort(404)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
resp = base64.b64decode(resp)
|
||||
except binascii.Error:
|
||||
pass
|
||||
if resp == 'Not Found' or not resp:
|
||||
abort(404)
|
||||
return Response(resp)
|
||||
|
||||
@site_api.route('/site/<name>/<path:file>', endpoint='siteFile')
|
||||
def site_file(name: str, file: str)->Response:
|
||||
"""Accept a site 'name', if pubkey then show multi-page site, if hash show single page site"""
|
||||
resp: str = 'Not Found'
|
||||
mime_type = mimetypes.MimeTypes().guess_type(file)[0]
|
||||
|
||||
# If necessary convert the name to base32 from mnemonic
|
||||
if mnemonickeys.DELIMITER in name:
|
||||
name = mnemonickeys.get_base32(name)
|
||||
|
||||
# Now make sure the key is regardless a valid base32 format ed25519 key (readding padding if necessary)
|
||||
if stringvalidators.validate_pub_key(name):
|
||||
name = unpaddedbase32.repad(name)
|
||||
resp = sitefiles.get_file(name, file)
|
||||
|
||||
elif stringvalidators.validate_hash(name):
|
||||
try:
|
||||
resp = onionrblockapi.Block(name).bcontent
|
||||
except onionrexceptions.NoDataAvailable:
|
||||
abort(404)
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
resp = base64.b64decode(resp)
|
||||
except binascii.Error:
|
||||
pass
|
||||
if resp == 'Not Found' or not resp:
|
||||
abort(404)
|
||||
return Response(resp, mimetype=mime_type)
|
@ -1,49 +0,0 @@
|
||||
"""
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
view and interact with onionr sites
|
||||
"""
|
||||
|
||||
from typing import Union
|
||||
|
||||
import onionrexceptions
|
||||
from onionrutils import mnemonickeys
|
||||
from onionrutils import stringvalidators
|
||||
from coredb import blockmetadb
|
||||
from onionrblocks.onionrblockapi import Block
|
||||
from onionrtypes import BlockHash
|
||||
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def find_site(user_id: str) -> Union[BlockHash, None]:
|
||||
"""Returns block hash str for latest block for a site by a given user id"""
|
||||
# If mnemonic delim in key, convert to base32 version
|
||||
if mnemonickeys.DELIMITER in user_id:
|
||||
user_id = mnemonickeys.get_base32(user_id)
|
||||
|
||||
if not stringvalidators.validate_pub_key(user_id):
|
||||
raise onionrexceptions.InvalidPubkey
|
||||
|
||||
found_site = None
|
||||
sites = blockmetadb.get_blocks_by_type('osite')
|
||||
|
||||
# Find site by searching all site blocks. eww O(N) ☹️, TODO: event based
|
||||
for site in sites:
|
||||
site = Block(site)
|
||||
if site.isSigner(user_id) and site.verifySig():
|
||||
found_site = site.hash
|
||||
return found_site
|
@ -1,79 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Read onionr site files
|
||||
"""
|
||||
from typing import Union, Tuple
|
||||
import tarfile
|
||||
import io
|
||||
import os
|
||||
|
||||
import unpaddedbase32
|
||||
|
||||
from coredb import blockmetadb
|
||||
from onionrblocks import onionrblockapi
|
||||
from onionrblocks import insert
|
||||
|
||||
# Import types. Just for type hiting
|
||||
from onionrtypes import UserID, DeterministicKeyPassphrase, BlockHash
|
||||
|
||||
from onionrcrypto import generate_deterministic
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def find_site_gzip(user_id: str)->tarfile.TarFile:
|
||||
"""Return verified site tar object"""
|
||||
sites = blockmetadb.get_blocks_by_type('osite')
|
||||
user_site = None
|
||||
unpadded_user = user_id
|
||||
user_id = unpaddedbase32.repad(user_id)
|
||||
for site in sites:
|
||||
block = onionrblockapi.Block(site)
|
||||
if block.isSigner(user_id) or block.isSigner(unpadded_user):
|
||||
user_site = block
|
||||
if not user_site is None:
|
||||
return tarfile.open(fileobj=io.BytesIO(user_site.bcontent), mode='r')
|
||||
return None
|
||||
|
||||
|
||||
def get_file(user_id, file)->Union[bytes, None]:
|
||||
"""Get a site file content"""
|
||||
ret_data = ""
|
||||
site = find_site_gzip(user_id)
|
||||
|
||||
if file.endswith('/'):
|
||||
file += 'index.html'
|
||||
if site is None: return None
|
||||
for t_file in site.getmembers():
|
||||
|
||||
if t_file.name.replace('./', '') == file:
|
||||
return site.extractfile(t_file)
|
||||
return None
|
||||
|
||||
|
||||
def create_site(admin_pass: DeterministicKeyPassphrase, directory:str='.')->Tuple[UserID, BlockHash]:
|
||||
public_key, private_key = generate_deterministic(admin_pass)
|
||||
|
||||
raw_tar = io.BytesIO()
|
||||
|
||||
tar = tarfile.open(mode='x:gz', fileobj=raw_tar)
|
||||
tar.add(directory)
|
||||
tar.close()
|
||||
|
||||
raw_tar.seek(0)
|
||||
|
||||
block_hash = insert(raw_tar.read(), header='osite', signing_key=private_key, sign=True)
|
||||
|
||||
return (public_key, block_hash)
|
@ -9,9 +9,6 @@ from gevent import sleep
|
||||
import gevent
|
||||
import ujson
|
||||
|
||||
from onionrblocks.onionrblockapi import Block
|
||||
from coredb.dbfiles import block_meta_db
|
||||
from coredb.blockmetadb import get_block_list
|
||||
from onionrutils.epoch import get_epoch
|
||||
from .. import wrapper
|
||||
"""
|
||||
@ -42,27 +39,3 @@ def stream_hello():
|
||||
sleep(1)
|
||||
return SSEWrapper.handle_sse_request(print_hello)
|
||||
|
||||
|
||||
@private_sse_blueprint.route('/recentblocks')
|
||||
def stream_recent_blocks():
|
||||
def _compile_json(b_list):
|
||||
js = {}
|
||||
block_obj = None
|
||||
for block in b_list:
|
||||
block_obj = Block(block)
|
||||
if block_obj.isEncrypted:
|
||||
js[block] = 'encrypted'
|
||||
else:
|
||||
js[block] = Block(block).btype
|
||||
return ujson.dumps({"blocks": js}, reject_bytes=True)
|
||||
|
||||
def _stream_recent():
|
||||
last_time = Path(block_meta_db).stat().st_ctime
|
||||
while True:
|
||||
if Path(block_meta_db).stat().st_ctime != last_time:
|
||||
last_time = Path(block_meta_db).stat().st_ctime
|
||||
yield "data: " + _compile_json(get_block_list(get_epoch() - 5)) + "\n\n"
|
||||
else:
|
||||
yield "data: none" + "\n\n"
|
||||
sleep(5)
|
||||
return SSEWrapper.handle_sse_request(_stream_recent)
|
||||
|
@ -1,5 +0,0 @@
|
||||
from . import insert
|
||||
from .insert import time_insert
|
||||
from .blocklist import BlockList
|
||||
insert = insert.insert_block
|
||||
time_insert = time_insert
|
@ -1,11 +0,0 @@
|
||||
import ujson
|
||||
import nacl.utils
|
||||
from nacl.public import PrivateKey, SealedBox
|
||||
|
||||
from .blockmetadata import get_block_metadata_from_data
|
||||
|
||||
def block_decrypt(raw_block) -> DecryptedBlock:
|
||||
block_header, user_meta, block_data = get_block_metadata_from_data(
|
||||
raw_block)
|
||||
|
||||
|
@ -1,69 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Import block data and save it
|
||||
"""
|
||||
from onionrexceptions import BlacklistedBlock
|
||||
from onionrexceptions import DiskAllocationReached
|
||||
from onionrexceptions import InvalidProof
|
||||
from onionrexceptions import InvalidMetadata
|
||||
import logger
|
||||
from onionrutils import validatemetadata
|
||||
from onionrutils import bytesconverter
|
||||
from coredb import blockmetadb
|
||||
from onionrblocks import blockmetadata
|
||||
import onionrstorage
|
||||
import onionrcrypto as crypto
|
||||
from . import onionrblacklist
|
||||
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def import_block_from_data(content):
|
||||
blacklist = onionrblacklist.OnionrBlackList()
|
||||
ret_data = False
|
||||
|
||||
content = bytesconverter.str_to_bytes(content)
|
||||
|
||||
data_hash = crypto.hashers.sha3_hash(content)
|
||||
|
||||
if blacklist.inBlacklist(data_hash):
|
||||
raise BlacklistedBlock(f'%s is a blacklisted block {data_hash}')
|
||||
|
||||
# returns tuple(metadata, meta), meta is also in metadata
|
||||
metas = blockmetadata.get_block_metadata_from_data(content)
|
||||
metadata = metas[0]
|
||||
|
||||
# check if metadata is valid
|
||||
if validatemetadata.validate_metadata(metadata, metas[2]):
|
||||
# check if POW is enough/correct
|
||||
if crypto.cryptoutils.verify_POW(content):
|
||||
logger.info(f'Imported block passed proof, saving: {data_hash}.',
|
||||
terminal=True)
|
||||
try:
|
||||
block_hash = onionrstorage.set_data(content)
|
||||
except DiskAllocationReached:
|
||||
logger.warn('Failed to save block due to full disk allocation')
|
||||
raise
|
||||
else:
|
||||
blockmetadb.add_to_block_DB(block_hash, dataSaved=True)
|
||||
# caches block metadata values to block database
|
||||
blockmetadata.process_block_metadata(block_hash)
|
||||
ret_data = block_hash
|
||||
else:
|
||||
raise InvalidProof
|
||||
else:
|
||||
raise InvalidMetadata
|
||||
return ret_data
|
@ -1,60 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Get an auto updating list of blocks
|
||||
"""
|
||||
from threading import Thread
|
||||
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
from utils.identifyhome import identify_home
|
||||
from coredb.dbfiles import block_meta_db
|
||||
from coredb.blockmetadb import get_block_list, get_blocks_by_type
|
||||
from onionrutils.epoch import get_epoch
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
class BlockList:
|
||||
def __init__(self, auto_refresh=True, block_type=''):
|
||||
self.block_type = block_type
|
||||
self.refresh_db()
|
||||
self.check_time = get_epoch()
|
||||
|
||||
class Refresher(FileSystemEventHandler):
|
||||
@staticmethod
|
||||
def on_modified(event):
|
||||
if event.src_path != block_meta_db:
|
||||
return
|
||||
self.refresh_db()
|
||||
if auto_refresh:
|
||||
def auto_refresher():
|
||||
observer = Observer()
|
||||
observer.schedule(
|
||||
Refresher(), identify_home(), recursive=False)
|
||||
observer.start()
|
||||
while observer.is_alive():
|
||||
# call import func with timeout
|
||||
observer.join(120)
|
||||
Thread(target=auto_refresher, daemon=True).start()
|
||||
|
||||
def get(self):
|
||||
return self.block_list
|
||||
|
||||
def refresh_db(self):
|
||||
self.check_time = get_epoch()
|
||||
if not self.block_type:
|
||||
self.block_list = get_block_list()
|
||||
else:
|
||||
self.block_list = get_blocks_by_type(self.block_type)
|
@ -1,24 +0,0 @@
|
||||
'''
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Module to work with block metadata
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
from . import hasblock, fromdata, process
|
||||
has_block = hasblock.has_block
|
||||
process_block_metadata = process.process_block_metadata
|
||||
get_block_metadata_from_data = fromdata.get_block_metadata_from_data
|
@ -1,51 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Return a useful tuple of (metadata (header), meta, and data) by accepting raw block data
|
||||
"""
|
||||
from json import JSONDecodeError
|
||||
import ujson as json
|
||||
|
||||
from onionrutils import bytesconverter
|
||||
import logger
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def get_block_metadata_from_data(block_data):
|
||||
"""
|
||||
accepts block contents as string, returns a tuple of
|
||||
metadata, meta (meta being internal metadata, which will be
|
||||
returned as an encrypted base64 string if it is encrypted, dict if not).
|
||||
"""
|
||||
meta = {}
|
||||
metadata = {}
|
||||
data = block_data
|
||||
try:
|
||||
block_data = block_data.encode()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
metadata = json.loads(bytesconverter.bytes_to_str(block_data[:block_data.find(b'\n')]))
|
||||
except JSONDecodeError:
|
||||
pass
|
||||
except ValueError:
|
||||
logger.warn("Could not get metadata from:", terminal=True)
|
||||
logger.warn(block_data, terminal=True)
|
||||
else:
|
||||
data = block_data[block_data.find(b'\n'):]
|
||||
|
||||
meta = metadata['meta']
|
||||
return (metadata, meta, data)
|
@ -1,43 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Return a bool if a block is in the block metadata db or not
|
||||
"""
|
||||
import sqlite3
|
||||
from coredb import dbfiles
|
||||
import onionrexceptions
|
||||
from onionrutils import stringvalidators
|
||||
from etc import onionrvalues
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def has_block(hash: str) -> bool:
|
||||
"""Check for new block in the block meta db."""
|
||||
conn = sqlite3.connect(
|
||||
dbfiles.block_meta_db,
|
||||
timeout=onionrvalues.DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
if not stringvalidators.validate_hash(hash):
|
||||
raise onionrexceptions.InvalidHexHash("Invalid hash")
|
||||
for result in c.execute("SELECT COUNT() FROM hashes WHERE hash = ?", (hash,)):
|
||||
if result[0] >= 1:
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
else:
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return False
|
||||
return False
|
@ -1,71 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Process block metadata with relevant actions
|
||||
"""
|
||||
from etc import onionrvalues
|
||||
from onionrblocks import onionrblockapi
|
||||
from onionrutils import epoch, bytesconverter
|
||||
from coredb import blockmetadb
|
||||
import logger
|
||||
from onionrplugins import onionrevents
|
||||
import onionrexceptions
|
||||
from onionrusers import onionrusers
|
||||
from onionrutils import updater
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def process_block_metadata(blockHash: str):
|
||||
"""
|
||||
Read metadata from a block and cache it to the block database.
|
||||
|
||||
blockHash -> sha3_256 hex formatted hash of Onionr block
|
||||
"""
|
||||
curTime = epoch.get_rounded_epoch(roundS=60)
|
||||
myBlock = onionrblockapi.Block(blockHash)
|
||||
if myBlock.isEncrypted:
|
||||
myBlock.decrypt()
|
||||
if (myBlock.isEncrypted and myBlock.decrypted) or (not myBlock.isEncrypted):
|
||||
blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks
|
||||
|
||||
signer = bytesconverter.bytes_to_str(myBlock.signer)
|
||||
valid = myBlock.verifySig()
|
||||
if valid:
|
||||
if myBlock.getMetadata('newFSKey') is not None:
|
||||
try:
|
||||
onionrusers.OnionrUser(signer).addForwardKey(myBlock.getMetadata('newFSKey'))
|
||||
except onionrexceptions.InvalidPubkey:
|
||||
logger.warn('%s has invalid forward secrecy key to add: %s' % (signer, myBlock.getMetadata('newFSKey')))
|
||||
|
||||
try:
|
||||
if len(blockType) <= onionrvalues.MAX_BLOCK_TYPE_LENGTH:
|
||||
blockmetadb.update_block_info(blockHash, 'dataType', blockType)
|
||||
except TypeError:
|
||||
logger.warn("Missing block information")
|
||||
pass
|
||||
# Set block expire time if specified
|
||||
try:
|
||||
expireTime = int(myBlock.getHeader('expire'))
|
||||
# test that expire time is an integer of sane length (for epoch)
|
||||
# doesn't matter if its too large because of the min() func below
|
||||
if not len(str(expireTime)) < 20: raise ValueError('timestamp invalid')
|
||||
except (ValueError, TypeError) as e:
|
||||
expireTime = onionrvalues.DEFAULT_EXPIRE + curTime
|
||||
finally:
|
||||
expireTime = min(expireTime, curTime + onionrvalues.DEFAULT_EXPIRE)
|
||||
blockmetadb.update_block_info(blockHash, 'expire', expireTime)
|
||||
|
||||
if blockType == 'update': updater.update_event(myBlock)
|
||||
onionrevents.event('processblocks', data = {'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid})
|
@ -1,35 +0,0 @@
|
||||
"""Onionr - P2P Anonymous Storage Network.
|
||||
|
||||
Delete but do not blacklist plaintext blocks
|
||||
"""
|
||||
from coredb import blockmetadb
|
||||
from onionrstorage.removeblock import remove_block
|
||||
import onionrstorage
|
||||
from .onionrblockapi import Block
|
||||
import onionrexceptions
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def delete_plaintext_no_blacklist():
|
||||
"""Delete, but do not blacklist, plaintext blocks."""
|
||||
|
||||
block_list = blockmetadb.get_block_list()
|
||||
|
||||
for block in block_list:
|
||||
block = Block(hash=block, decrypt=False)
|
||||
if not block.isEncrypted:
|
||||
remove_block(block.hash) # delete metadata entry
|
||||
onionrstorage.deleteBlock(block.hash) # delete block data
|
@ -1,4 +0,0 @@
|
||||
from . import main, timeinsert
|
||||
|
||||
insert_block = main.insert_block
|
||||
time_insert = timeinsert.time_insert
|
@ -1,267 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Create and insert Onionr blocks
|
||||
"""
|
||||
from typing import Union
|
||||
import ujson as json
|
||||
|
||||
from gevent import spawn
|
||||
|
||||
from onionrutils import bytesconverter, epoch
|
||||
import filepaths
|
||||
import onionrstorage
|
||||
from .. import storagecounter
|
||||
from onionrplugins import onionrevents as events
|
||||
from etc import onionrvalues
|
||||
import config
|
||||
import onionrcrypto as crypto
|
||||
import onionrexceptions
|
||||
from onionrusers import onionrusers
|
||||
from onionrutils import localcommand, stringvalidators
|
||||
from .. import blockmetadata
|
||||
import coredb
|
||||
from onionrproofs import subprocesspow
|
||||
import logger
|
||||
from onionrtypes import UserIDSecretKey
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
storage_counter = storagecounter.StorageCounter()
|
||||
|
||||
|
||||
def _check_upload_queue():
|
||||
"""
|
||||
Return the current upload queue len.
|
||||
|
||||
raises OverflowError if max, false if api not running
|
||||
"""
|
||||
max_upload_queue: int = 5000
|
||||
queue = localcommand.local_command('/gethidden', max_wait=10)
|
||||
up_queue = False
|
||||
|
||||
try:
|
||||
up_queue = len(queue.splitlines())
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if up_queue >= max_upload_queue:
|
||||
raise OverflowError
|
||||
return up_queue
|
||||
|
||||
|
||||
def insert_block(data: Union[str, bytes], header: str = 'txt',
|
||||
sign: bool = False, encryptType: str = '', symKey: str = '',
|
||||
asymPeer: str = '', meta: dict = {},
|
||||
expire: Union[int, None] = None, disableForward: bool = False,
|
||||
signing_key: UserIDSecretKey = '') -> Union[str, bool]:
|
||||
"""
|
||||
Create and insert a block into the network.
|
||||
|
||||
encryptType must be specified to encrypt a block
|
||||
if expire is less than date, assumes seconds into future.
|
||||
if not assume exact epoch
|
||||
"""
|
||||
our_private_key = crypto.priv_key
|
||||
our_pub_key = crypto.pub_key
|
||||
|
||||
allocationReachedMessage = 'Cannot insert block, disk allocation reached.'
|
||||
if storage_counter.is_full():
|
||||
logger.error(allocationReachedMessage)
|
||||
raise onionrexceptions.DiskAllocationReached
|
||||
|
||||
if signing_key != '':
|
||||
# if it was specified to use an alternative private key
|
||||
our_private_key = signing_key
|
||||
our_pub_key = bytesconverter.bytes_to_str(
|
||||
crypto.cryptoutils.get_pub_key_from_priv(our_private_key))
|
||||
|
||||
retData = False
|
||||
|
||||
if type(data) is None:
|
||||
raise ValueError('Data cannot be none')
|
||||
|
||||
createTime = epoch.get_epoch()
|
||||
|
||||
dataNonce = bytesconverter.bytes_to_str(crypto.hashers.sha3_hash(data))
|
||||
try:
|
||||
with open(filepaths.data_nonce_file, 'r') as nonces:
|
||||
if dataNonce in nonces:
|
||||
return retData
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
# record nonce
|
||||
with open(filepaths.data_nonce_file, 'a') as nonce_file:
|
||||
nonce_file.write(dataNonce + '\n')
|
||||
|
||||
plaintext = data
|
||||
plaintextMeta = {}
|
||||
plaintextPeer = asymPeer
|
||||
|
||||
retData = ''
|
||||
signature = ''
|
||||
signer = ''
|
||||
metadata = {}
|
||||
|
||||
# metadata is full block metadata
|
||||
# meta is internal, user specified metadata
|
||||
|
||||
# only use header if not set in provided meta
|
||||
|
||||
meta['type'] = str(header)
|
||||
|
||||
if encryptType in ('asym', 'sym'):
|
||||
metadata['encryptType'] = encryptType
|
||||
else:
|
||||
if not config.get('general.store_plaintext_blocks', True):
|
||||
raise onionrexceptions.InvalidMetadata(
|
||||
"Plaintext blocks are disabled, " +
|
||||
"yet a plaintext block was being inserted")
|
||||
if encryptType not in ('', None):
|
||||
raise onionrexceptions.InvalidMetadata(
|
||||
'encryptType must be asym or sym, or blank')
|
||||
|
||||
try:
|
||||
data = data.encode()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if encryptType == 'asym':
|
||||
# Duplicate the time in encrypted messages to help prevent replays
|
||||
meta['rply'] = createTime
|
||||
if sign and asymPeer != our_pub_key:
|
||||
try:
|
||||
forwardEncrypted = onionrusers.OnionrUser(
|
||||
asymPeer).forwardEncrypt(data)
|
||||
data = forwardEncrypted[0]
|
||||
meta['forwardEnc'] = True
|
||||
# Expire time of key. no sense keeping block after that
|
||||
expire = forwardEncrypted[2]
|
||||
except onionrexceptions.InvalidPubkey:
|
||||
pass
|
||||
if not disableForward:
|
||||
fsKey = onionrusers.OnionrUser(asymPeer).generateForwardKey()
|
||||
meta['newFSKey'] = fsKey
|
||||
jsonMeta = json.dumps(meta)
|
||||
plaintextMeta = jsonMeta
|
||||
if sign:
|
||||
signature = crypto.signing.ed_sign(
|
||||
jsonMeta.encode() + data, key=our_private_key, encodeResult=True)
|
||||
signer = our_pub_key
|
||||
|
||||
if len(jsonMeta) > 1000:
|
||||
raise onionrexceptions.InvalidMetadata(
|
||||
'meta in json encoded form must not exceed 1000 bytes')
|
||||
|
||||
# encrypt block metadata/sig/content
|
||||
if encryptType == 'sym':
|
||||
raise NotImplementedError("not yet implemented")
|
||||
elif encryptType == 'asym':
|
||||
if stringvalidators.validate_pub_key(asymPeer):
|
||||
# Encrypt block data with forward secrecy key first, but not meta
|
||||
jsonMeta = json.dumps(meta)
|
||||
jsonMeta = crypto.encryption.pub_key_encrypt(
|
||||
jsonMeta, asymPeer, encodedData=True).decode()
|
||||
data = crypto.encryption.pub_key_encrypt(
|
||||
data, asymPeer, encodedData=False)
|
||||
signature = crypto.encryption.pub_key_encrypt(
|
||||
signature, asymPeer, encodedData=True).decode()
|
||||
signer = crypto.encryption.pub_key_encrypt(
|
||||
signer, asymPeer, encodedData=True).decode()
|
||||
try:
|
||||
onionrusers.OnionrUser(asymPeer, saveUser=True)
|
||||
except ValueError:
|
||||
# if peer is already known
|
||||
pass
|
||||
else:
|
||||
logger.warn(f"{asymPeer} is not a valid key to make a block to")
|
||||
raise onionrexceptions.InvalidPubkey(
|
||||
'tried to make block to invalid key is not a valid base32 encoded ed25519 key')
|
||||
|
||||
# compile metadata
|
||||
metadata['meta'] = jsonMeta
|
||||
if len(signature) > 0: # I don't like not pattern
|
||||
metadata['sig'] = signature
|
||||
metadata['signer'] = signer
|
||||
metadata['time'] = createTime
|
||||
|
||||
# ensure expire is integer and of sane length
|
||||
if type(expire) is not type(None): # noqa
|
||||
if not len(str(int(expire))) < 20:
|
||||
raise ValueError(
|
||||
'expire must be valid int less than 20 digits in length')
|
||||
# if expire is less than date, assume seconds into future
|
||||
if expire < epoch.get_epoch():
|
||||
expire = epoch.get_epoch() + expire
|
||||
metadata['expire'] = expire
|
||||
|
||||
# send block data (and metadata) to POW module to get tokenized block data
|
||||
payload = subprocesspow.SubprocessPOW(data, metadata).start()
|
||||
|
||||
if payload != False: # noqa
|
||||
try:
|
||||
retData = onionrstorage.set_data(payload)
|
||||
except onionrexceptions.DiskAllocationReached:
|
||||
logger.error(allocationReachedMessage)
|
||||
retData = False
|
||||
else:
|
||||
if disableForward:
|
||||
logger.warn(
|
||||
f'{retData} asym encrypted block created w/o ephemerality')
|
||||
"""
|
||||
Tell the api server through localCommand to wait for the daemon to
|
||||
upload this block to make statistical analysis more difficult
|
||||
"""
|
||||
spawn(
|
||||
localcommand.local_command,
|
||||
'/daemon-event/upload_event',
|
||||
post=True,
|
||||
is_json=True,
|
||||
post_data={'block': retData}
|
||||
).get(timeout=5)
|
||||
coredb.blockmetadb.add.add_to_block_DB(
|
||||
retData, selfInsert=True, dataSaved=True)
|
||||
|
||||
if expire is None:
|
||||
coredb.blockmetadb.update_block_info(
|
||||
retData, 'expire',
|
||||
createTime + onionrvalues.DEFAULT_EXPIRE)
|
||||
else:
|
||||
coredb.blockmetadb.update_block_info(retData, 'expire', expire)
|
||||
|
||||
blockmetadata.process_block_metadata(retData)
|
||||
|
||||
if retData != False: # noqa
|
||||
if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS:
|
||||
events.event('insertdeniable',
|
||||
{'content': plaintext, 'meta': plaintextMeta,
|
||||
'hash': retData,
|
||||
'peer': bytesconverter.bytes_to_str(asymPeer)},
|
||||
threaded=True)
|
||||
else:
|
||||
events.event('insertblock',
|
||||
{'content': plaintext, 'meta': plaintextMeta,
|
||||
'hash': retData,
|
||||
'peer': bytesconverter.bytes_to_str(asymPeer)},
|
||||
threaded=True)
|
||||
|
||||
spawn(
|
||||
localcommand.local_command,
|
||||
'/daemon-event/remove_from_insert_queue_wrapper',
|
||||
post=True,
|
||||
post_data={'block_hash':
|
||||
bytesconverter.bytes_to_str(
|
||||
crypto.hashers.sha3_hash(data))},
|
||||
is_json=True
|
||||
).get(timeout=5)
|
||||
return retData
|
@ -1,51 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Wrapper to insert blocks with variable delay
|
||||
"""
|
||||
from . import main
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def time_insert(*args, **kwargs):
|
||||
"""Block insert wrapper to allow for insertions independent of mixmate.
|
||||
|
||||
Takes exact args as insert_block, with additional keyword:
|
||||
delay=n; where n=seconds to tell initial nodes to delay share for.
|
||||
|
||||
defaults to 0 or previously set value in current block meta
|
||||
"""
|
||||
try:
|
||||
kwargs['meta']
|
||||
except KeyError:
|
||||
kwargs['meta'] = {}
|
||||
|
||||
try:
|
||||
delay = int(kwargs['meta']['dly'])
|
||||
except KeyError:
|
||||
delay = 0
|
||||
try:
|
||||
delay = kwargs['delay']
|
||||
del kwargs['delay']
|
||||
except KeyError:
|
||||
delay = 0
|
||||
|
||||
# Ensure delay >=0
|
||||
if delay < 0:
|
||||
raise ValueError('delay cannot be less than 0')
|
||||
|
||||
kwargs['meta']['dly'] = delay
|
||||
|
||||
return main.insert_block(*args, **kwargs)
|
@ -1,130 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Handle maintenance of a blacklist database, for blocks and peers
|
||||
"""
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
from onionrplugins.onionrevents import event
|
||||
import onionrcrypto
|
||||
from onionrutils import epoch, bytesconverter
|
||||
from coredb import dbfiles
|
||||
from etc.onionrvalues import DATABASE_LOCK_TIMEOUT
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
class OnionrBlackList:
|
||||
def __init__(self):
|
||||
self.blacklistDB = dbfiles.blacklist_db
|
||||
|
||||
if not os.path.exists(dbfiles.blacklist_db):
|
||||
self.generateDB()
|
||||
return
|
||||
|
||||
def inBlacklist(self, data):
|
||||
hashed = bytesconverter.bytes_to_str(
|
||||
onionrcrypto.hashers.sha3_hash(data))
|
||||
retData = False
|
||||
|
||||
if not hashed.isalnum():
|
||||
raise Exception("Hashed data is not alpha numeric")
|
||||
if len(hashed) > 64:
|
||||
raise Exception("Hashed data is too large")
|
||||
|
||||
for i in self._dbExecute(
|
||||
"SELECT * FROM blacklist WHERE hash = ?", (hashed,)):
|
||||
# this only executes if an entry is present by that hash
|
||||
retData = True
|
||||
break
|
||||
|
||||
return retData
|
||||
|
||||
def _dbExecute(self, toExec, params=()):
|
||||
conn = sqlite3.connect(self.blacklistDB, timeout=DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
retData = c.execute(toExec, params)
|
||||
conn.commit()
|
||||
return retData
|
||||
|
||||
def deleteBeforeDate(self, date):
|
||||
# TODO, delete blacklist entries before date
|
||||
return
|
||||
|
||||
def deleteExpired(self, dataType=0):
|
||||
"""Delete expired entries"""
|
||||
deleteList = []
|
||||
curTime = epoch.get_epoch()
|
||||
|
||||
try:
|
||||
int(dataType)
|
||||
except AttributeError:
|
||||
raise TypeError("dataType must be int")
|
||||
|
||||
for i in self._dbExecute(
|
||||
'SELECT * FROM blacklist WHERE dataType = ?', (dataType,)):
|
||||
if i[1] == dataType:
|
||||
if (curTime - i[2]) >= i[3]:
|
||||
deleteList.append(i[0])
|
||||
|
||||
for thing in deleteList:
|
||||
self._dbExecute("DELETE FROM blacklist WHERE hash = ?", (thing,))
|
||||
|
||||
def generateDB(self):
|
||||
return
|
||||
|
||||
def clearDB(self):
|
||||
self._dbExecute("""DELETE FROM blacklist;""")
|
||||
|
||||
def getList(self):
|
||||
data = self._dbExecute('SELECT * FROM blacklist')
|
||||
myList = []
|
||||
for i in data:
|
||||
myList.append(i[0])
|
||||
return myList
|
||||
|
||||
def addToDB(self, data, dataType=0, expire=0):
|
||||
"""Add to the blacklist. Intended to be block hash, block data, peers, or transport addresses
|
||||
0=block
|
||||
1=peer
|
||||
2=pubkey
|
||||
"""
|
||||
|
||||
# we hash the data so we can remove data entirely from our node's disk
|
||||
hashed = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(data))
|
||||
|
||||
event('blacklist_add', data={'data': data, 'hash': hashed})
|
||||
|
||||
if len(hashed) > 64:
|
||||
raise Exception("Hashed data is too large")
|
||||
|
||||
if not hashed.isalnum():
|
||||
raise Exception("Hashed data is not alpha numeric")
|
||||
try:
|
||||
int(dataType)
|
||||
except ValueError:
|
||||
raise Exception("dataType is not int")
|
||||
try:
|
||||
int(expire)
|
||||
except ValueError:
|
||||
raise Exception("expire is not int")
|
||||
if self.inBlacklist(hashed):
|
||||
return
|
||||
insert = (hashed,)
|
||||
blacklistDate = epoch.get_epoch()
|
||||
try:
|
||||
self._dbExecute("INSERT INTO blacklist (hash, dataType, blacklistDate, expire) VALUES(?, ?, ?, ?);", (str(hashed), dataType, blacklistDate, expire))
|
||||
except sqlite3.IntegrityError:
|
||||
pass
|
@ -1,443 +0,0 @@
|
||||
"""Onionr - P2P Anonymous Storage Network.
|
||||
|
||||
OnionrBlocks class for abstraction of blocks
|
||||
"""
|
||||
import datetime
|
||||
import onionrstorage
|
||||
|
||||
import unpaddedbase32
|
||||
import ujson as json
|
||||
import nacl.exceptions
|
||||
|
||||
import logger
|
||||
import onionrexceptions
|
||||
from onionrusers import onionrusers
|
||||
from onionrutils import stringvalidators, epoch
|
||||
from coredb import blockmetadb
|
||||
from onionrutils import bytesconverter
|
||||
import onionrblocks
|
||||
from onionrcrypto import encryption, cryptoutils as cryptoutils, signing
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
class Block:
|
||||
blockCacheOrder = list() # NEVER write your own code that writes to this!
|
||||
blockCache = dict() # should never be accessed directly, look at Block.getCache()
|
||||
|
||||
def __init__(self, hash = None, type = None, content = None, expire=None, decrypt=False, bypassReplayCheck=False):
|
||||
# take from arguments
|
||||
# sometimes people input a bytes object instead of str in `hash`
|
||||
if (not hash is None) and isinstance(hash, bytes):
|
||||
hash = hash.decode()
|
||||
|
||||
self.hash = hash
|
||||
self.btype = type
|
||||
self.bcontent = content
|
||||
self.expire = expire
|
||||
self.bypassReplayCheck = bypassReplayCheck
|
||||
|
||||
# initialize variables
|
||||
self.valid = True
|
||||
self.raw = None
|
||||
self.signed = False
|
||||
self.signature = None
|
||||
self.signedData = None
|
||||
self.blockFile = None
|
||||
self.bheader = {}
|
||||
self.bmetadata = {}
|
||||
self.isEncrypted = False
|
||||
self.decrypted = False
|
||||
self.signer = None
|
||||
self.validSig = False
|
||||
self.autoDecrypt = decrypt
|
||||
self.claimedTime = None
|
||||
|
||||
self.update()
|
||||
|
||||
def decrypt(self, encodedData = True):
|
||||
"""
|
||||
Decrypt a block, loading decrypted data into their vars
|
||||
"""
|
||||
|
||||
if self.decrypted:
|
||||
return True
|
||||
retData = False
|
||||
# decrypt data
|
||||
if self.getHeader('encryptType') == 'asym':
|
||||
try:
|
||||
self.bcontent = encryption.pub_key_decrypt(self.bcontent, encodedData=False)
|
||||
|
||||
bmeta = encryption.pub_key_decrypt(self.bmetadata, encodedData=encodedData)
|
||||
|
||||
try:
|
||||
bmeta = bmeta.decode()
|
||||
except AttributeError:
|
||||
# yet another bytes fix
|
||||
pass
|
||||
self.bmetadata = json.loads(bmeta)
|
||||
self.signature = encryption.pub_key_decrypt(self.signature, encodedData=encodedData)
|
||||
|
||||
self.signer = encryption.pub_key_decrypt(self.signer, encodedData=encodedData)
|
||||
|
||||
self.bheader['signer'] = self.signer.decode()
|
||||
self.signedData = json.dumps(self.bmetadata).encode() + self.bcontent
|
||||
|
||||
if not self.signer is None:
|
||||
if not self.verifySig():
|
||||
raise onionrexceptions.SignatureError("Block has invalid signature")
|
||||
|
||||
# Check for replay attacks
|
||||
try:
|
||||
if epoch.get_epoch() - blockmetadb.get_block_date(self.hash) > 60:
|
||||
if not cryptoutils.replay_validator(self.bmetadata['rply']): raise onionrexceptions.ReplayAttack
|
||||
except (AssertionError, KeyError, TypeError, onionrexceptions.ReplayAttack) as e:
|
||||
if not self.bypassReplayCheck:
|
||||
# Zero out variables to prevent reading of replays
|
||||
self.bmetadata = {}
|
||||
self.signer = ''
|
||||
self.bheader['signer'] = ''
|
||||
self.signedData = ''
|
||||
self.signature = ''
|
||||
raise onionrexceptions.ReplayAttack('Signature is too old. possible replay attack')
|
||||
try:
|
||||
if not self.bmetadata['forwardEnc']: raise KeyError
|
||||
except (AssertionError, KeyError) as e:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
self.bcontent = onionrusers.OnionrUser(self.signer).forwardDecrypt(self.bcontent)
|
||||
except (onionrexceptions.DecryptionError, nacl.exceptions.CryptoError) as e:
|
||||
logger.error(str(e))
|
||||
pass
|
||||
except (nacl.exceptions.CryptoError,) as e:
|
||||
logger.debug(f'Could not decrypt block. encodedData: {encodedData}. Either invalid key or corrupted data ' + str(e))
|
||||
except onionrexceptions.ReplayAttack:
|
||||
logger.warn('%s is possibly a replay attack' % (self.hash,))
|
||||
else:
|
||||
retData = True
|
||||
self.decrypted = True
|
||||
return retData
|
||||
|
||||
def verifySig(self):
|
||||
"""
|
||||
Verify if a block's signature is signed by its claimed signer
|
||||
"""
|
||||
if self.signer is None:
|
||||
return False
|
||||
if signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
|
||||
self.validSig = True
|
||||
else:
|
||||
self.validSig = False
|
||||
return self.validSig
|
||||
|
||||
def update(self, data = None, file = None):
|
||||
"""
|
||||
Loads data from a block in to the current object.
|
||||
|
||||
Inputs:
|
||||
- data (str):
|
||||
- if None: will load from file by hash
|
||||
- else: will load from `data` string
|
||||
- file (str):
|
||||
- if None: will load from file specified in this parameter
|
||||
- else: will load from wherever block is stored by hash
|
||||
|
||||
Outputs:
|
||||
- (bool): indicates whether or not the operation was successful
|
||||
"""
|
||||
try:
|
||||
# import from string
|
||||
blockdata = data
|
||||
|
||||
# import from file
|
||||
if blockdata is None:
|
||||
try:
|
||||
blockdata = onionrstorage.getData(self.getHash())#.decode()
|
||||
except AttributeError:
|
||||
raise onionrexceptions.NoDataAvailable('Block does not exist')
|
||||
else:
|
||||
self.blockFile = None
|
||||
# parse block
|
||||
self.raw = blockdata
|
||||
self.bheader = json.loads(self.getRaw()[:self.getRaw().index(b'\n')])
|
||||
self.bcontent = self.getRaw()[self.getRaw().index(b'\n') + 1:]
|
||||
if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
|
||||
self.bmetadata = self.getHeader('meta', None)
|
||||
self.isEncrypted = True
|
||||
else:
|
||||
self.bmetadata = json.loads(self.getHeader('meta', None))
|
||||
self.btype = self.getMetadata('type', None)
|
||||
self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
|
||||
# TODO: detect if signer is hash of pubkey or not
|
||||
self.signer = self.getHeader('signer', None)
|
||||
self.signature = self.getHeader('sig', None)
|
||||
# signed data is jsonMeta + block content (no linebreak)
|
||||
self.signedData = (None if not self.isSigned() else self.getHeader('meta').encode() + self.getContent())
|
||||
self.date = blockmetadb.get_block_date(self.getHash())
|
||||
self.claimedTime = self.getHeader('time', None)
|
||||
|
||||
if not self.getDate() is None:
|
||||
self.date = datetime.datetime.fromtimestamp(self.getDate())
|
||||
|
||||
self.valid = True
|
||||
|
||||
if self.autoDecrypt:
|
||||
self.decrypt()
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.warn('Failed to parse block %s' % self.getHash(), error = e, timestamp = False)
|
||||
|
||||
|
||||
self.valid = False
|
||||
return False
|
||||
|
||||
|
||||
# getters
|
||||
|
||||
def getExpire(self):
|
||||
"""
|
||||
Returns the expire time for a block
|
||||
|
||||
Outputs:
|
||||
- (int): the expire time for a block, or None
|
||||
"""
|
||||
return self.expire
|
||||
|
||||
def getHash(self):
|
||||
"""
|
||||
Returns the hash of the block if saved to file
|
||||
|
||||
Outputs:
|
||||
- (str): the hash of the block, or None
|
||||
"""
|
||||
|
||||
return self.hash
|
||||
|
||||
def getType(self):
|
||||
"""
|
||||
Returns the type of the block
|
||||
|
||||
Outputs:
|
||||
- (str): the type of the block
|
||||
"""
|
||||
return self.btype
|
||||
|
||||
def getRaw(self):
|
||||
"""
|
||||
Returns the raw contents of the block, if saved to file
|
||||
|
||||
Outputs:
|
||||
- (bytes): the raw contents of the block, or None
|
||||
"""
|
||||
|
||||
return self.raw
|
||||
|
||||
def getHeader(self, key = None, default = None):
|
||||
"""
|
||||
Returns the header information
|
||||
|
||||
Inputs:
|
||||
- key (str): only returns the value of the key in the header
|
||||
|
||||
Outputs:
|
||||
- (dict/str): either the whole header as a dict, or one value
|
||||
"""
|
||||
|
||||
if not key is None:
|
||||
if key in self.getHeader():
|
||||
return self.getHeader()[key]
|
||||
return default
|
||||
return self.bheader
|
||||
|
||||
def getMetadata(self, key = None, default = None):
|
||||
"""
|
||||
Returns the metadata information
|
||||
|
||||
Inputs:
|
||||
- key (str): only returns the value of the key in the metadata
|
||||
|
||||
Outputs:
|
||||
- (dict/str): either the whole metadata as a dict, or one value
|
||||
"""
|
||||
|
||||
if not key is None:
|
||||
if key in self.getMetadata():
|
||||
return self.getMetadata()[key]
|
||||
return default
|
||||
return self.bmetadata
|
||||
|
||||
def getContent(self):
|
||||
"""
|
||||
Returns the contents of the block
|
||||
|
||||
Outputs:
|
||||
- (str): the contents of the block
|
||||
"""
|
||||
|
||||
return self.bcontent
|
||||
|
||||
def getDate(self):
|
||||
"""
|
||||
Returns the date that the block was received, if loaded from file
|
||||
|
||||
Outputs:
|
||||
- (datetime): the date that the block was received
|
||||
"""
|
||||
|
||||
return self.date
|
||||
|
||||
def getBlockFile(self):
|
||||
"""
|
||||
Returns the location of the block file if it is saved
|
||||
|
||||
Outputs:
|
||||
- (str): the location of the block file, or None
|
||||
"""
|
||||
|
||||
return self.blockFile
|
||||
|
||||
def isValid(self):
|
||||
"""
|
||||
Checks if the block is valid
|
||||
|
||||
Outputs:
|
||||
- (bool): whether or not the block is valid
|
||||
"""
|
||||
|
||||
return self.valid
|
||||
|
||||
def isSigned(self):
|
||||
"""
|
||||
Checks if the block was signed
|
||||
|
||||
Outputs:
|
||||
- (bool): whether or not the block is signed
|
||||
"""
|
||||
|
||||
return self.signed
|
||||
|
||||
def getSignature(self):
|
||||
"""
|
||||
Returns the base64-encoded signature
|
||||
|
||||
Outputs:
|
||||
- (str): the signature, or None
|
||||
"""
|
||||
|
||||
return self.signature
|
||||
|
||||
def getSignedData(self):
|
||||
"""
|
||||
Returns the data that was signed
|
||||
|
||||
Outputs:
|
||||
- (str): the data that was signed, or None
|
||||
"""
|
||||
|
||||
return self.signedData
|
||||
|
||||
def isSigner(self, signer, encodedData = True):
|
||||
"""
|
||||
Checks if the block was signed by the signer inputted
|
||||
|
||||
Inputs:
|
||||
- signer (str): the public key of the signer to check against
|
||||
- encodedData (bool): whether or not the `signer` argument is base64 encoded
|
||||
|
||||
Outputs:
|
||||
- (bool): whether or not the signer of the block is the signer inputted
|
||||
"""
|
||||
signer = unpaddedbase32.repad(bytesconverter.str_to_bytes(signer))
|
||||
try:
|
||||
if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)):
|
||||
return False
|
||||
|
||||
return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
|
||||
except:
|
||||
return False
|
||||
|
||||
# setters
|
||||
|
||||
def setType(self, btype):
|
||||
"""
|
||||
Sets the type of the block
|
||||
|
||||
Inputs:
|
||||
- btype (str): the type of block to be set to
|
||||
|
||||
Outputs:
|
||||
- (Block): the Block instance
|
||||
"""
|
||||
|
||||
self.btype = btype
|
||||
return self
|
||||
|
||||
def setMetadata(self, key, val):
|
||||
"""
|
||||
Sets a custom metadata value
|
||||
|
||||
Metadata should not store block-specific data structures.
|
||||
|
||||
Inputs:
|
||||
- key (str): the key
|
||||
- val: the value (type is irrelevant)
|
||||
|
||||
Outputs:
|
||||
- (Block): the Block instance
|
||||
"""
|
||||
|
||||
self.bmetadata[key] = val
|
||||
return self
|
||||
|
||||
def setContent(self, bcontent):
|
||||
"""
|
||||
Sets the contents of the block
|
||||
|
||||
Inputs:
|
||||
- bcontent (str): the contents to be set to
|
||||
|
||||
Outputs:
|
||||
- (Block): the Block instance
|
||||
"""
|
||||
|
||||
self.bcontent = str(bcontent)
|
||||
return self
|
||||
|
||||
# static functions
|
||||
def exists(bHash):
|
||||
"""
|
||||
Checks if a block is saved to file or not
|
||||
|
||||
Inputs:
|
||||
- hash (str/Block):
|
||||
- if (Block): check if this block is saved to file
|
||||
- if (str): check if a block by this hash is in file
|
||||
|
||||
Outputs:
|
||||
- (bool): whether or not the block file exists
|
||||
"""
|
||||
|
||||
# no input data? scrap it.
|
||||
if bHash is None:
|
||||
return False
|
||||
|
||||
if isinstance(bHash, Block):
|
||||
bHash = bHash.getHash()
|
||||
|
||||
ret = isinstance(onionrstorage.getData(bHash), type(None))
|
||||
|
||||
return not ret
|
@ -1,96 +0,0 @@
|
||||
"""
|
||||
Onionr - Private P2P Communication.
|
||||
|
||||
Keep track of how much disk space we're using
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from threading import Thread
|
||||
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
import config
|
||||
from filepaths import usage_file
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
config.reload()
|
||||
|
||||
|
||||
def _read_data_file(f) -> int:
|
||||
amount = 0
|
||||
try:
|
||||
with open(f, 'r') as f:
|
||||
amount = int(f.read())
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except ValueError:
|
||||
pass # Possibly happens when the file is empty
|
||||
return amount
|
||||
|
||||
|
||||
class StorageCounter:
|
||||
def __init__(self):
|
||||
self.data_file = usage_file
|
||||
self.amount: int = None
|
||||
Path(self.data_file).touch()
|
||||
|
||||
def auto_refresher():
|
||||
class Refresher(FileSystemEventHandler):
|
||||
@staticmethod
|
||||
def on_modified(event):
|
||||
self.amount = _read_data_file(self.data_file)
|
||||
observer = Observer()
|
||||
observer.schedule(Refresher(), usage_file)
|
||||
observer.start()
|
||||
while observer.is_alive():
|
||||
# call import func with timeout
|
||||
observer.join(120)
|
||||
Thread(target=auto_refresher, daemon=True).start()
|
||||
self.amount = _read_data_file(self.data_file)
|
||||
|
||||
def is_full(self) -> bool:
|
||||
"""Returns if the allocated disk space is full (this is Onionr config,
|
||||
not true FS capacity)"""
|
||||
ret_data = False
|
||||
if config.get('allocations.disk', 1073741824) <= (self.amount + 1000):
|
||||
ret_data = True
|
||||
return ret_data
|
||||
|
||||
def _update(self, data):
|
||||
with open(self.data_file, 'w') as data_file:
|
||||
data_file.write(str(data))
|
||||
|
||||
def get_percent(self) -> int:
|
||||
"""Return percent (decimal/float) of disk space we're using"""
|
||||
amount = self.amount
|
||||
return round(amount / config.get('allocations.disk', 2000000000), 2)
|
||||
|
||||
def add_bytes(self, amount) -> int:
|
||||
"""Record that we are now using more disk space,
|
||||
unless doing so would exceed configured max"""
|
||||
new_amount = amount + self.amount
|
||||
ret_data = new_amount
|
||||
if new_amount > config.get('allocations.disk', 2000000000):
|
||||
ret_data = 0
|
||||
else:
|
||||
self._update(new_amount)
|
||||
return ret_data
|
||||
|
||||
def remove_bytes(self, amount) -> int:
|
||||
"""Record that we are now using less disk space"""
|
||||
new_amount = self.amount - amount
|
||||
self._update(new_amount)
|
||||
return new_amount
|
@ -1,56 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
This file contains the command for banning blocks from the node
|
||||
"""
|
||||
import sys
|
||||
import logger
|
||||
from onionrutils import stringvalidators
|
||||
from onionrstorage import removeblock
|
||||
from onionrstorage import deleteBlock
|
||||
from onionrblocks import onionrblacklist
|
||||
from utils import reconstructhash
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def ban_block():
|
||||
"""Delete a block, permanently blacklisting it."""
|
||||
blacklist = onionrblacklist.OnionrBlackList()
|
||||
try:
|
||||
ban = sys.argv[2]
|
||||
except IndexError:
|
||||
# Get the hash if its not provided as a CLI argument
|
||||
ban = logger.readline('Enter a block hash:').strip()
|
||||
# Make sure the hash has no truncated zeroes
|
||||
ban = reconstructhash.reconstruct_hash(ban)
|
||||
if stringvalidators.validate_hash(ban):
|
||||
if not blacklist.inBlacklist(ban):
|
||||
try:
|
||||
blacklist.addToDB(ban)
|
||||
removeblock.remove_block(ban)
|
||||
deleteBlock(ban)
|
||||
except Exception as error: # pylint: disable=W0703
|
||||
logger.error('Could not blacklist block',
|
||||
error=error, terminal=True)
|
||||
else:
|
||||
logger.info('Block blacklisted', terminal=True)
|
||||
else:
|
||||
logger.warn('That block is already blacklisted', terminal=True)
|
||||
else:
|
||||
logger.error('Invalid block hash', terminal=True)
|
||||
|
||||
|
||||
ban_block.onionr_help = "<block hash>: " # type: ignore
|
||||
ban_block.onionr_help += "deletes and blacklists a block" # type: ignore
|
@ -35,7 +35,6 @@ from utils.bettersleep import better_sleep
|
||||
from .killdaemon import kill_daemon # noqa
|
||||
from .showlogo import show_logo
|
||||
|
||||
from sneakernet import sneakernet_import_thread
|
||||
from setupkvvars import setup_kv
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
@ -142,9 +141,6 @@ def daemon():
|
||||
events.event('init', threaded=False)
|
||||
events.event('daemon_start')
|
||||
|
||||
if config.get('transports.sneakernet', True):
|
||||
Thread(target=sneakernet_import_thread, daemon=True).start()
|
||||
|
||||
|
||||
better_sleep(5)
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
This file handles the command for exporting blocks to disk
|
||||
"""
|
||||
import sys
|
||||
|
||||
import logger
|
||||
import onionrstorage
|
||||
from utils import createdirs
|
||||
from onionrutils import stringvalidators
|
||||
from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT
|
||||
import filepaths
|
||||
|
||||
import os
|
||||
from coredb import blockmetadb
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def _do_export(b_hash):
|
||||
createdirs.create_dirs()
|
||||
data = onionrstorage.getData(b_hash)
|
||||
with open('%s/%s%s' % (filepaths.export_location,
|
||||
b_hash, BLOCK_EXPORT_FILE_EXT), 'wb') as export:
|
||||
export.write(data)
|
||||
logger.info('Block exported as file', terminal=True)
|
||||
|
||||
|
||||
def export_block(*args):
|
||||
"""Export block based on hash from stdin or argv."""
|
||||
if args:
|
||||
b_hash = args[0]
|
||||
else:
|
||||
try:
|
||||
if not stringvalidators.validate_hash(sys.argv[2]):
|
||||
raise ValueError
|
||||
except (IndexError, ValueError):
|
||||
logger.error('No valid block hash specified.', terminal=True)
|
||||
sys.exit(1)
|
||||
else:
|
||||
b_hash = sys.argv[2]
|
||||
_do_export(b_hash)
|
||||
|
||||
|
||||
export_block.onionr_help = "<block hash>: Export block to " # type: ignore
|
||||
export_block.onionr_help += "a file. Export directory is in " # type: ignore
|
||||
export_block.onionr_help += "Onionr home under block-export" # type: ignore
|
@ -1,106 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
This file handles the commands for adding
|
||||
and getting files from the Onionr network
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import logger
|
||||
from onionrblocks.onionrblockapi import Block
|
||||
import onionrexceptions
|
||||
from onionrutils import stringvalidators
|
||||
from etc import onionrvalues
|
||||
from onionrblocks import insert
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
_ORIG_DIR = onionrvalues.ORIG_RUN_DIR_ENV_VAR
|
||||
|
||||
|
||||
def _get_dir(path: str) -> str:
|
||||
if not os.getenv(_ORIG_DIR) is None:
|
||||
return os.getenv(_ORIG_DIR) + '/' + path # type: ignore
|
||||
else:
|
||||
return path
|
||||
|
||||
|
||||
def add_html(singleBlock=True, blockType='html'):
|
||||
"""Create one-off web page from HTML file, no ext resources."""
|
||||
add_file(blockType=blockType)
|
||||
|
||||
|
||||
add_html.onionr_help = "Adds an HTML file into Onionr. Does " # type: ignore
|
||||
add_html.onionr_help += "not include dependant resources" # type: ignore
|
||||
|
||||
|
||||
def add_file(blockType='bin'):
|
||||
"""Add a file to the onionr network."""
|
||||
if len(sys.argv) >= 3:
|
||||
filename = sys.argv[2]
|
||||
|
||||
if not os.path.exists(_get_dir(filename)):
|
||||
logger.error(
|
||||
'That file does not exist. Improper path (specify full path)?',
|
||||
terminal=True)
|
||||
return
|
||||
logger.info('Adding file, this might take a long time.',
|
||||
terminal=True)
|
||||
try:
|
||||
with open(_get_dir(filename), 'rb') as single_file:
|
||||
blockhash = insert(single_file.read(), header=blockType)
|
||||
if len(blockhash) > 0:
|
||||
logger.info('File %s saved in block %s' %
|
||||
(filename, blockhash), terminal=True)
|
||||
except Exception as err: # pylint: disable=W0703
|
||||
logger.error('Failed to save file in block ' +
|
||||
str(err), timestamp=False, terminal=True)
|
||||
else:
|
||||
logger.error('%s add-file <filename>' %
|
||||
sys.argv[0], timestamp=False, terminal=True)
|
||||
|
||||
|
||||
add_file.onionr_help = "<file path> Add a file into " # type: ignore
|
||||
add_file.onionr_help += "the Onionr network" # type: ignore
|
||||
|
||||
|
||||
def get_file():
|
||||
"""Get a file from onionr blocks."""
|
||||
try:
|
||||
file_name = _get_dir(sys.argv[2])
|
||||
bHash = sys.argv[3]
|
||||
except IndexError:
|
||||
logger.error("Syntax %s %s" % (
|
||||
sys.argv[0], '/path/to/filename <blockhash>'), terminal=True)
|
||||
else:
|
||||
logger.info(file_name, terminal=True)
|
||||
|
||||
if os.path.exists(file_name):
|
||||
logger.error("File already exists", terminal=True)
|
||||
return
|
||||
if not stringvalidators.validate_hash(bHash):
|
||||
logger.error('Block hash is invalid', terminal=True)
|
||||
return
|
||||
|
||||
try:
|
||||
with open(file_name, 'wb') as my_file:
|
||||
my_file.write(Block(bHash).bcontent)
|
||||
except onionrexceptions.NoDataAvailable:
|
||||
logger.error(
|
||||
'That block is not available. Trying again later may work.',
|
||||
terminal=True)
|
||||
|
||||
|
||||
get_file.onionr_help = "<file path> <block hash>: Download " # type: ignore
|
||||
get_file.onionr_help += "a file from the onionr network." # type: ignore
|
@ -1,37 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Dumb listing of Onionr sites
|
||||
"""
|
||||
from coredb.blockmetadb import get_blocks_by_type
|
||||
from onionrblocks.onionrblockapi import Block
|
||||
import logger
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def print_site_list():
|
||||
"""Create a new MOTD message for the Onionr network."""
|
||||
block_list = get_blocks_by_type('osite')
|
||||
if not block_list:
|
||||
logger.info('No sites saved right now', terminal=True)
|
||||
for block in block_list:
|
||||
block = Block(block)
|
||||
if block.isSigned():
|
||||
logger.info(block.signer.replace('=', ''), terminal=True)
|
||||
else:
|
||||
logger.info(block.hash, terminal=True)
|
||||
|
||||
|
||||
print_site_list.onionr_help = "Dumbly list all Onionr sites currently saved" # type: ignore
|
@ -4,10 +4,9 @@ This module defines commands to show stats/details about the local node
|
||||
"""
|
||||
import os
|
||||
import logger
|
||||
from onionrblocks import onionrblacklist
|
||||
from onionrutils import mnemonickeys
|
||||
from utils import sizeutils, getconsolewidth, identifyhome
|
||||
from coredb import blockmetadb, keydb
|
||||
from coredb import keydb
|
||||
import onionrcrypto
|
||||
import config
|
||||
from etc import onionrvalues
|
||||
@ -43,9 +42,8 @@ def show_stats():
|
||||
"""Print/log statistic info about our Onionr install."""
|
||||
try:
|
||||
# define stats messages here
|
||||
totalBlocks = len(blockmetadb.get_block_list())
|
||||
home = identifyhome.identify_home()
|
||||
totalBanned = len(onionrblacklist.OnionrBlackList().getList())
|
||||
|
||||
|
||||
messages = {
|
||||
# info about local client
|
||||
@ -69,9 +67,7 @@ def show_stats():
|
||||
'div2': True,
|
||||
'Enabled Plugins':
|
||||
str(len(config.get('plugins.enabled', list()))) + ' / ' +
|
||||
str(len(os.listdir(home + 'plugins/'))),
|
||||
'Stored Blocks': str(totalBlocks),
|
||||
'Deleted Blocks': str(totalBanned)
|
||||
str(len(os.listdir(home + 'plugins/')))
|
||||
}
|
||||
|
||||
# color configuration
|
||||
|
@ -6,19 +6,14 @@ from typing import Callable
|
||||
|
||||
from .. import onionrstatistics, version, daemonlaunch
|
||||
from .. import openwebinterface
|
||||
from .. import banblocks # Command to blacklist a block by its hash
|
||||
from .. import filecommands # commands to share files with onionr
|
||||
from .. import exportblocks # commands to export blocks
|
||||
from .. import pubkeymanager # commands to add or change id
|
||||
from .. import resetplugins # command to reinstall default plugins
|
||||
from .. import softreset # command to delete onionr blocks
|
||||
from .. import restartonionr # command to restart Onionr
|
||||
from .. import runtimetestcmd # cmd to execute the runtime integration tests
|
||||
from .. import sitecreator # cmd to create multi-page sites
|
||||
from ..listsites import print_site_list # cmd to list list ids
|
||||
|
||||
|
||||
import onionrexceptions
|
||||
from onionrutils import importnewblocks # func to import new blocks
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -42,8 +37,6 @@ def get_arguments() -> dict:
|
||||
dynamically modify them with plugins
|
||||
"""
|
||||
args = {
|
||||
('blacklist', 'blacklist-block', 'remove-block',
|
||||
'removeblock', 'banblock', 'ban-block'): banblocks.ban_block,
|
||||
('details', 'info'): onionrstatistics.show_details,
|
||||
('stats', 'statistics'): onionrstatistics.show_stats,
|
||||
('version',): version.version,
|
||||
@ -53,15 +46,6 @@ def get_arguments() -> dict:
|
||||
('openhome', 'gui', 'openweb',
|
||||
'open-home', 'open-web'): openwebinterface.open_home,
|
||||
('get-url', 'url', 'get-web'): openwebinterface.get_url,
|
||||
('addhtml', 'add-html'): filecommands.add_html,
|
||||
('addsite', 'add-site',
|
||||
'update-site', 'updatesite'): sitecreator.create_multipage_site,
|
||||
('listsites', 'list-sites'): print_site_list,
|
||||
('addfile', 'add-file'): filecommands.add_file,
|
||||
('get-file', 'getfile'): filecommands.get_file,
|
||||
('export-block', 'exportblock'): exportblocks.export_block,
|
||||
('importblocks',
|
||||
'import-blocks', 'import-block'): importnewblocks.import_new_blocks,
|
||||
('addid', 'add-id'): pubkeymanager.add_ID,
|
||||
('changeid', 'change-id'): pubkeymanager.change_ID,
|
||||
('add-vanity', 'addvanity'): pubkeymanager.add_vanity,
|
||||
|
@ -1,72 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Command to create Onionr mutli-page sites
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import getpass
|
||||
|
||||
from niceware import generate_passphrase
|
||||
|
||||
from httpapi import onionrsitesapi
|
||||
import logger
|
||||
from etc import onionrvalues
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def create_multipage_site():
|
||||
"""Command to create mutlipage sites with specified dir and password."""
|
||||
error_encountered = False
|
||||
orig_dir = os.getcwd()
|
||||
try:
|
||||
directory = sys.argv[2]
|
||||
os.chdir(directory)
|
||||
directory = '.'
|
||||
except IndexError:
|
||||
directory = '.'
|
||||
try:
|
||||
passphrase = sys.argv[3]
|
||||
except IndexError:
|
||||
logger.warn('''It is critical that this passphrase is long.
|
||||
If you want to update your site later you must remember the passphrase.''',
|
||||
terminal=True)
|
||||
|
||||
passphrase = "-".join(generate_passphrase(32))
|
||||
print("Site restore phrase:", passphrase)
|
||||
|
||||
if len(passphrase) < onionrvalues.PASSWORD_LENGTH:
|
||||
error_encountered = True
|
||||
logger.error(
|
||||
f'Passphrase must be at least {onionrvalues.PASSWORD_LENGTH}' +
|
||||
' characters.', terminal=True)
|
||||
|
||||
if error_encountered:
|
||||
sys.exit(1)
|
||||
logger.info('Generating site...', terminal=True)
|
||||
results = onionrsitesapi.sitefiles.create_site(
|
||||
passphrase, directory=directory)
|
||||
results = (results[0].replace('=', ''), results[1])
|
||||
logger.info(f'Site address {results[0]}', terminal=True)
|
||||
logger.info(f'Block for this version {results[1]}', terminal=True)
|
||||
os.chdir(orig_dir)
|
||||
|
||||
|
||||
create_multipage_site.onionr_help = "[directory path " # type: ignore
|
||||
create_multipage_site.onionr_help += "(default relative)] " # type: ignore
|
||||
create_multipage_site.onionr_help += "- packages a whole " # type: ignore
|
||||
create_multipage_site.onionr_help += "directory and makes " # type: ignore
|
||||
create_multipage_site.onionr_help += "it available as " # type: ignore
|
||||
create_multipage_site.onionr_help += "an Onionr site." # type: ignore
|
@ -1,7 +1,6 @@
|
||||
from . import safecompare, replayvalidation, verifypow
|
||||
from . import safecompare, replayvalidation
|
||||
from . import getpubfrompriv
|
||||
|
||||
replay_validator = replayvalidation.replay_timestamp_validation
|
||||
safe_compare = safecompare.safe_compare
|
||||
verify_POW = verifypow.verify_POW
|
||||
get_pub_key_from_priv = getpubfrompriv.get_pub_key_from_priv
|
||||
|
@ -1,67 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Proof of work module
|
||||
"""
|
||||
import multiprocessing, time, math, threading, binascii, sys, json
|
||||
import nacl.encoding, nacl.hash, nacl.utils
|
||||
|
||||
import config
|
||||
import logger
|
||||
from onionrblocks import onionrblockapi
|
||||
from onionrutils import bytesconverter
|
||||
from onionrcrypto import hashers
|
||||
|
||||
from .blocknoncestart import BLOCK_NONCE_START_INT
|
||||
from .vdf import create_vdf
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
config.reload()
|
||||
|
||||
|
||||
def getDifficultyForNewBlock(data):
|
||||
"""
|
||||
Get difficulty for block. Accepts size in integer, Block instance, or str/bytes full block contents
|
||||
"""
|
||||
if isinstance(data, onionrblockapi.Block):
|
||||
dataSizeInBytes = len(bytesconverter.str_to_bytes(data.getRaw()))
|
||||
else:
|
||||
dataSizeInBytes = len(bytesconverter.str_to_bytes(data))
|
||||
|
||||
minDifficulty = config.get('general.minimum_send_pow', 4)
|
||||
totalDifficulty = max(minDifficulty, math.floor(dataSizeInBytes / 1000000.0))
|
||||
|
||||
return totalDifficulty
|
||||
|
||||
|
||||
def getHashDifficulty(h: str):
|
||||
"""
|
||||
Return the amount of leading zeroes in a hex hash string (hexHash)
|
||||
"""
|
||||
return len(h) - len(h.lstrip('0'))
|
||||
|
||||
|
||||
def hashMeetsDifficulty(hexHash):
|
||||
"""
|
||||
Return bool for a hash string to see if it meets pow difficulty defined in config
|
||||
"""
|
||||
hashDifficulty = getHashDifficulty(hexHash)
|
||||
|
||||
try:
|
||||
expected = int(config.get('general.minimum_block_pow'))
|
||||
except TypeError:
|
||||
raise ValueError('Missing general.minimum_block_pow config')
|
||||
|
||||
return hashDifficulty >= expected
|
||||
|
@ -1 +0,0 @@
|
||||
BLOCK_NONCE_START_INT = -10000000
|
@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Multiprocess proof of work
|
||||
"""
|
||||
|
||||
import os
|
||||
from multiprocessing import Pipe, Process
|
||||
import threading
|
||||
import time
|
||||
import secrets
|
||||
|
||||
import onionrproofs
|
||||
|
||||
import ujson as json
|
||||
|
||||
import logger
|
||||
from onionrutils import bytesconverter
|
||||
from onionrcrypto.hashers import sha3_hash
|
||||
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
class SubprocessPOW:
|
||||
def __init__(self, data, metadata, subproc_count=None):
|
||||
"""
|
||||
Onionr proof of work using multiple processes
|
||||
Accepts block data, block metadata
|
||||
if subproc_count is not set,
|
||||
os.cpu_count() is used to determine the number of processes
|
||||
|
||||
Due to Python GIL multiprocessing/use of external libraries
|
||||
is necessary to accelerate CPU bound tasks
|
||||
"""
|
||||
# No known benefit to using more processes than there are cores.
|
||||
# Note: os.cpu_count perhaps not always accurate
|
||||
if subproc_count is None:
|
||||
subproc_count = os.cpu_count()
|
||||
self.subproc_count = subproc_count
|
||||
self.result = ''
|
||||
self.shutdown = False
|
||||
self.data = data
|
||||
self.metadata = metadata
|
||||
|
||||
"""dump dict to measure bytes of json metadata
|
||||
Cannot reuse later bc the pow token must be added
|
||||
"""
|
||||
json_metadata = json.dumps(metadata).encode()
|
||||
|
||||
self.data = bytesconverter.str_to_bytes(data)
|
||||
|
||||
compiled_data = bytes(json_metadata + b'\n' + self.data)
|
||||
|
||||
# Calculate difficulty. May use better algorithm in the future.
|
||||
self.difficulty = onionrproofs.getDifficultyForNewBlock(compiled_data)
|
||||
|
||||
logger.info('Computing POW (difficulty: %s)...' % (self.difficulty,))
|
||||
|
||||
self.main_hash = '0' * 64
|
||||
self.puzzle = self.main_hash[0:min(self.difficulty,
|
||||
len(self.main_hash))]
|
||||
self.shutdown = False
|
||||
self.payload = None
|
||||
|
||||
def start(self):
|
||||
"""spawn the multiproc handler threads"""
|
||||
# Create a new thread for each subprocess
|
||||
for _ in range(self.subproc_count): # noqa
|
||||
threading.Thread(target=self._spawn_proc, daemon=True).start()
|
||||
# Monitor the processes for a payload, shut them down when its found
|
||||
while True:
|
||||
if self.payload is None:
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
self.shutdown = True
|
||||
return self.payload
|
||||
|
||||
def _spawn_proc(self):
|
||||
"""Create a child proof of work process
|
||||
wait for data and send shutdown signal when its found"""
|
||||
# The importerror started happening in 3.9.x
|
||||
# not worth fixing because this POW will be replaced by VDF
|
||||
try:
|
||||
parent_conn, child_conn = Pipe()
|
||||
p = Process(target=self.do_pow, args=(child_conn,), daemon=True)
|
||||
p.start()
|
||||
except ImportError:
|
||||
logger.error(
|
||||
"Error in subprocess module when getting new POW " +
|
||||
"pipe.\nThis is related to a problem in 3.9.x", terminal=True)
|
||||
return
|
||||
payload = None
|
||||
try:
|
||||
while True:
|
||||
data = parent_conn.recv()
|
||||
if len(data) >= 1:
|
||||
payload = data
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
p.terminate()
|
||||
self.payload = payload
|
||||
|
||||
def do_pow(self, pipe):
|
||||
"""find partial hash colision generating nonce for a block"""
|
||||
nonce = 0
|
||||
data = self.data
|
||||
metadata = self.metadata
|
||||
metadata['n'] = secrets.randbits(16)
|
||||
puzzle = self.puzzle
|
||||
difficulty = self.difficulty
|
||||
try:
|
||||
while True:
|
||||
#logger.info('still running', terminal=True)
|
||||
# Break if shutdown received
|
||||
try:
|
||||
if pipe.poll() and pipe.recv() == 'shutdown':
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
# Load nonce into block metadata
|
||||
metadata['c'] = nonce
|
||||
# Serialize metadata, combine with block data
|
||||
payload = json.dumps(metadata).encode() + b'\n' + data
|
||||
# Check sha3_256 hash of block, compare to puzzle
|
||||
# Send payload if puzzle finished
|
||||
token = sha3_hash(payload)
|
||||
# ensure token is string
|
||||
token = bytesconverter.bytes_to_str(token)
|
||||
if puzzle == token[0:difficulty]:
|
||||
pipe.send(payload)
|
||||
break
|
||||
nonce += 1
|
||||
except KeyboardInterrupt:
|
||||
pass
|
@ -1,43 +0,0 @@
|
||||
import multiprocessing
|
||||
|
||||
import mimcvdf
|
||||
|
||||
|
||||
def _wrap_vdf_create(queue, block_data_bytes, rounds):
|
||||
queue.put(mimcvdf.vdf_create(block_data_bytes, rounds))
|
||||
|
||||
|
||||
def _wrap_vdf_verify(queue, block_data_bytes, block_hash_hex, rounds):
|
||||
queue.put(mimcvdf.vdf_verify(block_data_bytes, block_hash_hex, rounds))
|
||||
|
||||
|
||||
|
||||
def rounds_for_bytes(byte_count: int):
|
||||
return byte_count * 1000
|
||||
|
||||
|
||||
|
||||
def create_vdf(block_data_bytes):
|
||||
rounds = rounds_for_bytes(block_data_bytes)
|
||||
queue = multiprocessing.Queue()
|
||||
vdf_proc = multiprocessing.Process(
|
||||
target=_wrap_vdf_create,
|
||||
args=(queue, block_data_bytes, rounds))
|
||||
vdf_proc.start()
|
||||
vdf_proc.join()
|
||||
return queue.get()
|
||||
|
||||
|
||||
def verify_vdf(block_hash_hex, block_data_bytes):
|
||||
rounds = rounds_for_bytes(block_data_bytes)
|
||||
if rounds < 10 ** 6:
|
||||
# >million rounds it starts to take long enough to warrant a subprocess
|
||||
queue = multiprocessing.Queue()
|
||||
vdf_proc = multiprocessing.Process(
|
||||
target=_wrap_vdf_verify,
|
||||
args=(queue, block_data_bytes, block_hash_hex, rounds))
|
||||
vdf_proc.start()
|
||||
vdf_proc.join()
|
||||
return queue.get()
|
||||
return mimcvdf.vdf_verify(block_data_bytes, block_hash_hex, rounds)
|
||||
|
@ -77,57 +77,6 @@ def createPeerDB():
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def createBlockDB():
|
||||
'''
|
||||
Create a database for blocks
|
||||
|
||||
hash - the hash of a block
|
||||
dateReceived - the date the block was recieved, not necessarily when it was created
|
||||
decrypted - if we can successfully decrypt the block (does not describe its current state)
|
||||
dataType - data type of the block
|
||||
dataFound - if the data has been found for the block
|
||||
dataSaved - if the data has been saved for the block
|
||||
sig - optional signature by the author (not optional if author is specified)
|
||||
author - multi-round partial sha3-256 hash of authors public key
|
||||
dateClaimed - timestamp claimed inside the block, only as trustworthy as the block author is
|
||||
expire int - block expire date in epoch
|
||||
'''
|
||||
if os.path.exists(dbfiles.block_meta_db):
|
||||
raise FileExistsError("Block database already exists")
|
||||
conn = sqlite3.connect(dbfiles.block_meta_db)
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE hashes(
|
||||
hash text not null,
|
||||
dateReceived int,
|
||||
decrypted int,
|
||||
dataType text,
|
||||
dataFound int,
|
||||
dataSaved int,
|
||||
sig text,
|
||||
author text,
|
||||
dateClaimed int,
|
||||
expire int
|
||||
);
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def createBlockDataDB():
|
||||
if os.path.exists(dbfiles.block_data_db):
|
||||
raise FileExistsError("Block data database already exists")
|
||||
else:
|
||||
if not os.path.exists(filepaths.block_data_location):
|
||||
os.mkdir(filepaths.block_data_location)
|
||||
conn = sqlite3.connect(dbfiles.block_data_db)
|
||||
c = conn.cursor()
|
||||
c.execute('''CREATE TABLE blockData(
|
||||
hash text not null,
|
||||
data blob not null
|
||||
);
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def createForwardKeyDB():
|
||||
'''
|
||||
@ -168,5 +117,4 @@ def create_blacklist_db():
|
||||
|
||||
|
||||
create_funcs = [createAddressDB, createPeerDB,
|
||||
createBlockDB, createBlockDataDB,
|
||||
createForwardKeyDB, create_blacklist_db]
|
@ -1,116 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Handle block storage, providing an abstraction for
|
||||
storing blocks between file system and database
|
||||
"""
|
||||
import sys
|
||||
import sqlite3
|
||||
import os
|
||||
from onionrutils import bytesconverter
|
||||
from onionrutils import stringvalidators
|
||||
from coredb import dbfiles
|
||||
from filepaths import block_data_location
|
||||
import onionrexceptions
|
||||
from onionrcrypto import hashers
|
||||
from . import setdata, removeblock
|
||||
from etc.onionrvalues import DATABASE_LOCK_TIMEOUT, BLOCK_EXPORT_FILE_EXT
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
DB_ENTRY_SIZE_LIMIT = 10000 # Will be a config option
|
||||
|
||||
set_data = setdata.set_data
|
||||
|
||||
|
||||
def _dbInsert(block_hash, data):
|
||||
conn = sqlite3.connect(dbfiles.block_data_db,
|
||||
timeout=DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
data = (block_hash, data)
|
||||
c.execute('INSERT INTO blockData (hash, data) VALUES(?, ?);', data)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def _dbFetch(block_hash):
|
||||
conn = sqlite3.connect(dbfiles.block_data_db,
|
||||
timeout=DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
for i in c.execute(
|
||||
'SELECT data from blockData where hash = ?', (block_hash,)):
|
||||
return i[0]
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return None
|
||||
|
||||
|
||||
def deleteBlock(block_hash):
|
||||
# Call removeblock.remove_block to automatically want to remove storage byte count
|
||||
if os.path.exists(f'{block_data_location}/{block_hash}{BLOCK_EXPORT_FILE_EXT}'):
|
||||
os.remove(f'{block_data_location}/{block_hash}{BLOCK_EXPORT_FILE_EXT}')
|
||||
return True
|
||||
conn = sqlite3.connect(dbfiles.block_data_db,
|
||||
timeout=DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
data = (block_hash,)
|
||||
c.execute('DELETE FROM blockData where hash = ?', data)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
|
||||
def store(data, block_hash=''):
|
||||
if not stringvalidators.validate_hash(block_hash):
|
||||
raise ValueError
|
||||
ourHash = hashers.sha3_hash(data)
|
||||
if block_hash != '':
|
||||
if not ourHash == block_hash:
|
||||
raise ValueError('Hash specified does not meet internal hash check')
|
||||
else:
|
||||
block_hash = ourHash
|
||||
|
||||
if DB_ENTRY_SIZE_LIMIT >= sys.getsizeof(data):
|
||||
_dbInsert(block_hash, data)
|
||||
else:
|
||||
with open(
|
||||
f'{block_data_location}/{block_hash}{BLOCK_EXPORT_FILE_EXT}', 'wb') as blck_file:
|
||||
blck_file.write(data)
|
||||
|
||||
|
||||
def getData(bHash):
|
||||
|
||||
if not stringvalidators.validate_hash(bHash):
|
||||
raise ValueError
|
||||
|
||||
bHash = bytesconverter.bytes_to_str(bHash)
|
||||
bHash = bHash.strip()
|
||||
# First check DB for data entry by hash
|
||||
# if no entry, check disk
|
||||
# If no entry in either, raise an exception
|
||||
ret_data = None
|
||||
fileLocation = '%s/%s%s' % (
|
||||
block_data_location,
|
||||
bHash, BLOCK_EXPORT_FILE_EXT)
|
||||
not_found_msg = "Block data not found for: " + str(bHash)
|
||||
if os.path.exists(fileLocation):
|
||||
with open(fileLocation, 'rb') as block:
|
||||
ret_data = block.read()
|
||||
else:
|
||||
ret_data = _dbFetch(bHash)
|
||||
|
||||
if ret_data is None:
|
||||
raise onionrexceptions.NoDataAvailable(not_found_msg)
|
||||
return ret_data
|
@ -1,52 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
remove onionr block from meta db
|
||||
"""
|
||||
import sys
|
||||
import sqlite3
|
||||
|
||||
import onionrexceptions
|
||||
import onionrstorage
|
||||
from onionrutils import stringvalidators
|
||||
from coredb import dbfiles
|
||||
from onionrblocks import storagecounter
|
||||
from etc.onionrvalues import DATABASE_LOCK_TIMEOUT
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
storage_counter = storagecounter.StorageCounter()
|
||||
|
||||
|
||||
def remove_block(block):
|
||||
"""Remove a block from this node.
|
||||
|
||||
(does not automatically blacklist).
|
||||
**You may want blacklist.addToDB(blockHash)
|
||||
"""
|
||||
if stringvalidators.validate_hash(block):
|
||||
try:
|
||||
data_size = sys.getsizeof(onionrstorage.getData(block))
|
||||
except onionrexceptions.NoDataAvailable:
|
||||
data_size = 0
|
||||
conn = sqlite3.connect(
|
||||
dbfiles.block_meta_db, timeout=DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
t = (block,)
|
||||
c.execute('Delete from hashes where hash=?;', t)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
if data_size:
|
||||
storage_counter.remove_bytes(data_size)
|
||||
else:
|
||||
raise onionrexceptions.InvalidHexHash
|
@ -1,70 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Test Onionr as it is running
|
||||
"""
|
||||
import sys
|
||||
import sqlite3
|
||||
|
||||
import onionrstorage
|
||||
import onionrexceptions
|
||||
import onionrcrypto as crypto
|
||||
import filepaths
|
||||
from onionrblocks import storagecounter, blockmetadata
|
||||
from coredb import dbfiles
|
||||
from onionrutils import bytesconverter
|
||||
from etc.onionrvalues import DATABASE_LOCK_TIMEOUT
|
||||
from onionrtypes import BlockHash
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
storage_counter = storagecounter.StorageCounter()
|
||||
|
||||
|
||||
def set_data(data):
|
||||
"""Set the data assciated with a hash."""
|
||||
dataSize = sys.getsizeof(data)
|
||||
nonce_hash = crypto.hashers.sha3_hash(
|
||||
bytesconverter.str_to_bytes(
|
||||
blockmetadata.fromdata.get_block_metadata_from_data(data)[2]))
|
||||
nonce_hash = bytesconverter.bytes_to_str(nonce_hash)
|
||||
|
||||
if not type(data) is bytes:
|
||||
data = data.encode()
|
||||
|
||||
dataHash = crypto.hashers.sha3_hash(data)
|
||||
|
||||
if type(dataHash) is bytes:
|
||||
dataHash = dataHash.decode()
|
||||
try:
|
||||
onionrstorage.getData(dataHash)
|
||||
except onionrexceptions.NoDataAvailable:
|
||||
if storage_counter.add_bytes(dataSize):
|
||||
onionrstorage.store(data, block_hash=dataHash)
|
||||
conn = sqlite3.connect(
|
||||
dbfiles.block_meta_db, timeout=DATABASE_LOCK_TIMEOUT)
|
||||
c = conn.cursor()
|
||||
c.execute(
|
||||
"UPDATE hashes SET dataSaved=1 WHERE hash = ?;",
|
||||
(dataHash,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
with open(filepaths.data_nonce_file, 'a') as nonceFile:
|
||||
nonceFile.write(nonce_hash + '\n')
|
||||
else:
|
||||
raise onionrexceptions.DiskAllocationReached
|
||||
else:
|
||||
raise onionrexceptions.DataExists(
|
||||
"Data is already set for " + dataHash)
|
||||
|
||||
return dataHash
|
@ -1,60 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
import new blocks from disk, providing transport agnosticism
|
||||
"""
|
||||
import glob
|
||||
|
||||
import logger
|
||||
from onionrblocks import blockmetadata
|
||||
from coredb import blockmetadb
|
||||
import filepaths
|
||||
import onionrcrypto as crypto
|
||||
from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def import_new_blocks(scanDir=''):
|
||||
"""Scan for new blocks ON THE DISK and import them"""
|
||||
blockList = blockmetadb.get_block_list()
|
||||
exist = False
|
||||
if scanDir == '':
|
||||
scanDir = filepaths.block_data_location
|
||||
if not scanDir.endswith('/'):
|
||||
scanDir += '/'
|
||||
for block in glob.glob(scanDir + "*%s" % (BLOCK_EXPORT_FILE_EXT,)):
|
||||
if block.replace(scanDir, '').replace(BLOCK_EXPORT_FILE_EXT, '') \
|
||||
not in blockList:
|
||||
exist = True
|
||||
logger.info('Found new block on dist %s' % block, terminal=True)
|
||||
with open(block, 'rb') as newBlock:
|
||||
block = block.replace(scanDir, '').replace(
|
||||
BLOCK_EXPORT_FILE_EXT, '')
|
||||
if crypto.hashers.sha3_hash(newBlock.read()) == block.replace(
|
||||
BLOCK_EXPORT_FILE_EXT, ''):
|
||||
blockmetadb.add_to_block_DB(block.replace(
|
||||
BLOCK_EXPORT_FILE_EXT, ''), dataSaved=True)
|
||||
logger.info('Imported block %s' % block, terminal=True)
|
||||
blockmetadata.process_block_metadata(block)
|
||||
else:
|
||||
logger.warn('Failed to verify hash for %s' % block,
|
||||
terminal=True)
|
||||
if not exist:
|
||||
logger.info('No blocks found to import', terminal=True)
|
||||
|
||||
|
||||
import_new_blocks.onionr_help = \
|
||||
f"Scan the Onionr data directory under {filepaths.block_data_location}" + \
|
||||
"for new block files (.db not supported) to import"
|
@ -8,12 +8,9 @@ from secrets import SystemRandom
|
||||
import logger
|
||||
from onionrutils import epoch
|
||||
|
||||
from . import uicheck, inserttest, stresstest
|
||||
from . import uicheck
|
||||
from .webpasstest import webpass_test
|
||||
from .osver import test_os_ver_endpoint
|
||||
from .clearnettor import test_clearnet_tor_request
|
||||
from .housekeeping import test_inserted_housekeeping
|
||||
from .sneakernettest import test_sneakernet_import
|
||||
from .dnsrebindingtest import test_dns_rebinding
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
@ -31,14 +28,8 @@ from .dnsrebindingtest import test_dns_rebinding
|
||||
"""
|
||||
|
||||
RUN_TESTS = [uicheck.check_ui,
|
||||
inserttest.insert_bin_test,
|
||||
stresstest.stress_test_block_insert,
|
||||
webpass_test,
|
||||
test_os_ver_endpoint,
|
||||
test_clearnet_tor_request,
|
||||
test_inserted_housekeeping,
|
||||
sneakernettest.test_sneakernet_import,
|
||||
test_dns_rebinding
|
||||
]
|
||||
|
||||
SUCCESS_FILE = os.path.dirname(os.path.realpath(__file__)) + '/../../tests/runtime-result.txt'
|
||||
|
@ -1,58 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Ensure that clearnet cannot be reached
|
||||
"""
|
||||
from onionrutils.basicrequests import do_get_request
|
||||
from onionrutils import localcommand
|
||||
import logger
|
||||
import config
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
def test_clearnet_tor_request(testmanager):
|
||||
"""Ensure that Tor cannot request clearnet address.
|
||||
|
||||
Does not run if Tor is being reused
|
||||
"""
|
||||
|
||||
config.reload()
|
||||
leak_result = ""
|
||||
|
||||
if config.get('tor.use_existing_tor', False):
|
||||
logger.warn(
|
||||
"Can't ensure Tor reqs to clearnet won't happen when reusing Tor")
|
||||
return
|
||||
|
||||
|
||||
socks_port = localcommand.local_command('/gettorsocks')
|
||||
|
||||
# Don't worry, this request isn't meant to go through,
|
||||
# but if it did it would be through Tor
|
||||
|
||||
try:
|
||||
leak_result: str = do_get_request(
|
||||
'https://example.com/notvalidpage',
|
||||
port=socks_port, ignoreAPI=True).lower()
|
||||
except AttributeError:
|
||||
leak_result = ""
|
||||
except Exception as e:
|
||||
logger.warn(str(e))
|
||||
try:
|
||||
if 'example' in leak_result:
|
||||
logger.error('Tor was able to request a clearnet site')
|
||||
raise ValueError('Tor was able to request a clearnet site')
|
||||
except TypeError:
|
||||
pass
|
@ -1,25 +0,0 @@
|
||||
import os
|
||||
|
||||
from gevent import sleep
|
||||
|
||||
from onionrblocks import insert
|
||||
import logger
|
||||
from coredb.blockmetadb import get_block_list
|
||||
from onionrutils import epoch
|
||||
|
||||
|
||||
def test_inserted_housekeeping(testmanager):
|
||||
"""Tests that inserted blocks are proprely deleted"""
|
||||
bl = insert('testdata', expire=12)
|
||||
wait_seconds = 132 # Wait two minutes plus expire time
|
||||
count = 0
|
||||
if bl in get_block_list():
|
||||
while count < wait_seconds:
|
||||
if bl in get_block_list():
|
||||
sleep(0.8)
|
||||
count += 1
|
||||
else:
|
||||
return
|
||||
raise ValueError('Inserted block with expiry not erased')
|
||||
else:
|
||||
raise ValueError('Inserted block in expiry test not present in list')
|
@ -1,20 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
import onionrblocks
|
||||
import logger
|
||||
import coredb
|
||||
|
||||
def _check_remote_node(testmanager):
|
||||
return
|
||||
|
||||
def insert_bin_test(testmanager):
|
||||
data = os.urandom(32)
|
||||
b_hash = onionrblocks.insert(data)
|
||||
time.sleep(0.3)
|
||||
if b_hash not in testmanager._too_many.get_by_string("PublicAPI").hideBlocks:
|
||||
raise ValueError("Block not hidden")
|
||||
|
||||
if b_hash not in coredb.blockmetadb.get_block_list():
|
||||
logger.error(str(b_hash) + 'is not in bl')
|
||||
raise ValueError
|
@ -1,27 +0,0 @@
|
||||
import os
|
||||
from shutil import move
|
||||
|
||||
from onionrblocks import insert
|
||||
from onionrstorage import deleteBlock
|
||||
from onionrcommands.exportblocks import export_block
|
||||
from filepaths import export_location, block_data_location, data_nonce_file
|
||||
from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT
|
||||
from onionrstorage.removeblock import remove_block
|
||||
from onionrstorage import deleteBlock
|
||||
from coredb.blockmetadb import get_block_list
|
||||
from utils import bettersleep
|
||||
from gevent import sleep
|
||||
|
||||
def test_sneakernet_import(test_manager):
|
||||
in_db = lambda b: b in get_block_list()
|
||||
bl = insert(os.urandom(10))
|
||||
assert in_db(bl)
|
||||
export_block(bl)
|
||||
assert os.path.exists(export_location + bl + BLOCK_EXPORT_FILE_EXT)
|
||||
remove_block(bl)
|
||||
deleteBlock(bl)
|
||||
assert not in_db(bl)
|
||||
os.remove(data_nonce_file)
|
||||
move(export_location + bl + BLOCK_EXPORT_FILE_EXT, block_data_location)
|
||||
sleep(1)
|
||||
assert in_db(bl)
|
@ -1,17 +0,0 @@
|
||||
import os
|
||||
|
||||
import onionrblocks
|
||||
import logger
|
||||
import coredb
|
||||
from onionrutils import epoch
|
||||
|
||||
def stress_test_block_insert(testmanager):
|
||||
return
|
||||
start = epoch.get_epoch()
|
||||
count = 100
|
||||
max_insert_speed = 120
|
||||
for x in range(count): onionrblocks.insert(os.urandom(32))
|
||||
speed = epoch.get_epoch() - start
|
||||
if speed < max_insert_speed:
|
||||
raise ValueError(f'{count} blocks inserted too fast, {max_insert_speed}, got {speed}')
|
||||
logger.info(f'runtest stress block insertion: {count} blocks inserted in {speed}s')
|
@ -1,68 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Detect new block files in a given directory
|
||||
"""
|
||||
import os
|
||||
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
import config
|
||||
from filepaths import block_data_location
|
||||
from etc.onionrvalues import BLOCK_EXPORT_FILE_EXT
|
||||
from onionrblocks.blockimporter import import_block_from_data
|
||||
import onionrexceptions
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
watch_paths = config.get('transports.sneakernet.paths', list([]))
|
||||
if block_data_location not in watch_paths:
|
||||
watch_paths.append(block_data_location)
|
||||
|
||||
|
||||
class _Importer(FileSystemEventHandler):
|
||||
@staticmethod
|
||||
def on_created(event):
|
||||
if not event.src_path.endswith(BLOCK_EXPORT_FILE_EXT):
|
||||
return
|
||||
try:
|
||||
with open(event.src_path, 'rb') as block_file:
|
||||
block_data = block_file.read()
|
||||
except FileNotFoundError:
|
||||
return
|
||||
os.remove(event.src_path)
|
||||
try:
|
||||
import_block_from_data(block_data)
|
||||
except( # noqa
|
||||
onionrexceptions.DataExists,
|
||||
onionrexceptions.BlockMetaEntryExists,
|
||||
onionrexceptions.InvalidMetadata) as _:
|
||||
return
|
||||
if block_data_location in event.src_path:
|
||||
try:
|
||||
os.remove(event.src_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def sneakernet_import_thread():
|
||||
"""Add block data dir & confed paths to fs observer to watch for new bls"""
|
||||
observer = Observer()
|
||||
for path in watch_paths:
|
||||
observer.schedule(_Importer(), path, recursive=True)
|
||||
observer.start()
|
||||
while observer.is_alive():
|
||||
# call import func with timeout
|
||||
observer.join(60)
|
@ -1,123 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
This file primarily serves to allow specific fetching of circles board messages
|
||||
"""
|
||||
import operator
|
||||
import os
|
||||
|
||||
import ujson as json
|
||||
|
||||
from flask import Response, Blueprint
|
||||
from flask import send_from_directory
|
||||
from deadsimplekv import DeadSimpleKV
|
||||
|
||||
from utils import identifyhome
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
flask_blueprint = Blueprint('circles', __name__)
|
||||
|
||||
root = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
with open(
|
||||
os.path.dirname(
|
||||
os.path.realpath(__file__)) + '/info.json', 'r') as info_file:
|
||||
data = info_file.read().strip()
|
||||
version = json.loads(data)['version']
|
||||
|
||||
BOARD_CACHE_FILE = identifyhome.identify_home() + '/board-index.cache.json'
|
||||
|
||||
read_only_cache = DeadSimpleKV(
|
||||
BOARD_CACHE_FILE,
|
||||
flush_on_exit=False,
|
||||
refresh_seconds=30)
|
||||
|
||||
@flask_blueprint.route('/board/<path:path>', endpoint='circlesstatic')
|
||||
def load_mail(path):
|
||||
return send_from_directory(root + '/web/', path)
|
||||
|
||||
|
||||
@flask_blueprint.route('/board/', endpoint='circlesindex')
|
||||
def load_mail_index():
|
||||
return send_from_directory(root + '/web/', 'index.html')
|
||||
|
||||
|
||||
@flask_blueprint.route('/circles/getpostsbyboard/<board>')
|
||||
def get_post_by_board(board):
|
||||
board_cache = DeadSimpleKV(
|
||||
BOARD_CACHE_FILE,
|
||||
flush_on_exit=False)
|
||||
board_cache.refresh()
|
||||
posts = board_cache.get(board)
|
||||
if posts is None:
|
||||
posts = ''
|
||||
else:
|
||||
posts = ','.join(posts)
|
||||
return Response(posts)
|
||||
|
||||
|
||||
@flask_blueprint.route('/circles/getpostsbyboard/<board>/<offset>')
|
||||
def get_post_by_board_with_offset(board, offset):
|
||||
offset = int(offset)
|
||||
OFFSET_COUNT = 10
|
||||
board_cache = DeadSimpleKV(
|
||||
BOARD_CACHE_FILE,
|
||||
flush_on_exit=False)
|
||||
board_cache.refresh()
|
||||
posts = board_cache.get(board)
|
||||
if posts is None:
|
||||
posts = ''
|
||||
else:
|
||||
posts.reverse()
|
||||
posts = ','.join(posts[offset:offset + OFFSET_COUNT])
|
||||
return Response(posts)
|
||||
|
||||
|
||||
@flask_blueprint.route('/circles/version')
|
||||
def get_version():
|
||||
return Response(version)
|
||||
|
||||
|
||||
@flask_blueprint.route('/circles/removefromcache/<board>/<name>',
|
||||
methods=['POST'])
|
||||
def remove_from_cache(board, name):
|
||||
board_cache = DeadSimpleKV(BOARD_CACHE_FILE,
|
||||
flush_on_exit=False)
|
||||
board_cache.refresh()
|
||||
posts = board_cache.get(board)
|
||||
try:
|
||||
posts.remove(name)
|
||||
except ValueError:
|
||||
pass
|
||||
board_cache.put(board, posts)
|
||||
return Response('success')
|
||||
|
||||
|
||||
@flask_blueprint.route('/circles/getpopular/<count>')
|
||||
def get_popular(count):
|
||||
read_only_cache.refresh()
|
||||
boards = json.loads(read_only_cache.get_raw_json())
|
||||
for board in boards:
|
||||
boards[board] = len(boards[board])
|
||||
|
||||
|
||||
top_boards = sorted(boards.items(), key=operator.itemgetter(1), reverse=True)[:int(count)]
|
||||
|
||||
only_board_names = []
|
||||
for b in top_boards:
|
||||
only_board_names.append(b[0])
|
||||
|
||||
return Response(','.join(only_board_names), content_type='text/csv')
|
@ -1,4 +0,0 @@
|
||||
{ "name": "circles",
|
||||
"version": "1.0.0",
|
||||
"author": "onionr"
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
This default plugin handles "flow" messages
|
||||
(global chatroom style communication)
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import deadsimplekv as simplekv
|
||||
from utils import identifyhome, reconstructhash
|
||||
from coredb import blockmetadb
|
||||
import threading
|
||||
import time
|
||||
import locale
|
||||
from onionrblocks.onionrblockapi import Block
|
||||
import logger
|
||||
import onionrblocks
|
||||
from onionrutils import escapeansi, epoch, bytesconverter
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
||||
# import after path insert
|
||||
import flowapi # noqa
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
flask_blueprint = flowapi.flask_blueprint
|
||||
security_whitelist = ['circles.circlesstatic', 'circles.circlesindex']
|
||||
|
||||
plugin_name = 'circles'
|
||||
PLUGIN_VERSION = '0.1.0'
|
||||
|
||||
EXPIRE_TIME = 43200
|
||||
|
||||
class OnionrFlow:
|
||||
def __init__(self):
|
||||
self.alreadyOutputed = []
|
||||
self.flowRunning = False
|
||||
self.channel = ""
|
||||
return
|
||||
|
||||
def start(self):
|
||||
logger.warn(
|
||||
"Please note: everything said here is public, " +
|
||||
"even if a random channel name is used.", terminal=True)
|
||||
message = ""
|
||||
self.flowRunning = True
|
||||
try:
|
||||
self.channel = logger.readline(
|
||||
"Enter a channel name or none for default:").strip()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
self.flowRunning = False
|
||||
newThread = threading.Thread(target=self.showOutput, daemon=True)
|
||||
newThread.start()
|
||||
while self.flowRunning:
|
||||
if self.channel == "":
|
||||
self.channel = "global"
|
||||
try:
|
||||
message = logger.readline(f'\nInsert message into {plugin_name}:').strip().replace(
|
||||
'\n', '\\n').replace('\r', '\\r')
|
||||
except EOFError:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
self.flowRunning = False
|
||||
else:
|
||||
if message == "q":
|
||||
self.flowRunning = False
|
||||
expireTime = epoch.get_epoch() + EXPIRE_TIME
|
||||
if len(message) > 0:
|
||||
logger.info('Inserting message as block...', terminal=True)
|
||||
onionrblocks.insert(message, header='brd',
|
||||
expire=expireTime,
|
||||
meta = {
|
||||
'ch': self.channel})
|
||||
|
||||
logger.info(f"{plugin_name} is exiting, goodbye", terminal=True)
|
||||
return
|
||||
|
||||
def showOutput(self):
|
||||
while isinstance(self.channel, type(None)) and self.flowRunning:
|
||||
time.sleep(1)
|
||||
try:
|
||||
while self.flowRunning:
|
||||
for block in blockmetadb.get_blocks_by_type('brd'):
|
||||
if block in self.alreadyOutputed:
|
||||
continue
|
||||
block = Block(block)
|
||||
b_hash = bytesconverter.bytes_to_str(block.getHash())
|
||||
if block.getMetadata('ch') != self.channel:
|
||||
continue
|
||||
if not self.flowRunning:
|
||||
break
|
||||
logger.info('\n------------------------',
|
||||
prompt=False, terminal=True)
|
||||
content = block.getContent()
|
||||
# Escape new lines, remove trailing whitespace, and escape ansi sequences
|
||||
content = escapeansi.escape_ANSI(content.replace(
|
||||
b'\n', b'\\n').replace(b'\r', b'\\r').strip().decode('utf-8'))
|
||||
logger.info(block.getDate().strftime(
|
||||
"%m/%d %H:%M") + ' - ' +
|
||||
logger.colors.reset + content,
|
||||
prompt=False, terminal=True)
|
||||
self.alreadyOutputed.append(b_hash)
|
||||
time.sleep(5)
|
||||
except KeyboardInterrupt:
|
||||
self.flowRunning = False
|
||||
|
||||
|
||||
def on_circles_cmd(api, data=None):
|
||||
OnionrFlow().start()
|
||||
|
||||
|
||||
def on_circlesend_cmd(api, data=None):
|
||||
err_msg = "Second arg is board name, third is quoted message"
|
||||
try:
|
||||
sys.argv[2]
|
||||
except IndexError:
|
||||
logger.error(err_msg, terminal=True)
|
||||
try:
|
||||
sys.argv[3]
|
||||
except IndexError:
|
||||
logger.error(err_msg, terminal=True)
|
||||
|
||||
bl = onionrblocks.insert(sys.argv[3], header='brd',
|
||||
expire=(EXPIRE_TIME + epoch.get_epoch()),
|
||||
meta={'ch': sys.argv[2]})
|
||||
print(bl)
|
||||
|
||||
|
||||
|
||||
def on_softreset(api, data=None):
|
||||
try:
|
||||
os.remove(identifyhome.identify_home() + '/board-index.cache.json')
|
||||
logger.info('Cleared Circles board cache')
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def on_processblocks(api, data=None):
|
||||
metadata = data['block'].bmetadata # Get the block metadata
|
||||
if data['type'] != 'brd':
|
||||
return
|
||||
|
||||
b_hash = reconstructhash.deconstruct_hash(
|
||||
data['block'].hash) # Get the 0-truncated block hash
|
||||
board_cache = simplekv.DeadSimpleKV(identifyhome.identify_home(
|
||||
) + '/board-index.cache.json', flush_on_exit=False) # get the board index cache
|
||||
board_cache.refresh()
|
||||
# Validate the channel name is sane for caching
|
||||
try:
|
||||
ch = metadata['ch']
|
||||
except KeyError:
|
||||
ch = 'global'
|
||||
ch_len = len(ch)
|
||||
if ch_len == 0:
|
||||
ch = 'global'
|
||||
elif ch_len > 12:
|
||||
return
|
||||
|
||||
existing_posts = board_cache.get(ch)
|
||||
if existing_posts is None:
|
||||
existing_posts = []
|
||||
existing_posts.append(data['block'].hash)
|
||||
board_cache.put(ch, existing_posts)
|
||||
board_cache.flush()
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Auto refresh board posts
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
var checkbox = document.getElementById('refreshCheckbox')
|
||||
function autoRefresh(){
|
||||
if (! checkbox.checked || document.hidden){return}
|
||||
getBlocks()
|
||||
}
|
||||
|
||||
function setupInterval(){
|
||||
if (checkbox.checked){
|
||||
refreshInterval = setInterval(autoRefresh, 3000)
|
||||
autoRefresh()
|
||||
return
|
||||
}
|
||||
clearInterval(refreshInterval)
|
||||
}
|
||||
|
||||
var refreshInterval = setInterval(autoRefresh, 3000)
|
||||
setupInterval()
|
||||
|
||||
checkbox.onchange = function(){setupInterval}
|
||||
|
||||
|
||||
document.addEventListener("visibilitychange", function() {
|
||||
if (document.visibilityState === 'visible') {
|
||||
autoRefresh()
|
||||
}
|
||||
})
|
@ -1,265 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
This file handles the boards/circles interface
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
requested = []
|
||||
newPostForm = document.getElementById('addMsg')
|
||||
firstLoad = true
|
||||
lastLoadedBoard = 'global'
|
||||
loadingMessage = document.getElementById('loadingBoard')
|
||||
loadedAny = false
|
||||
loadingTimeout = 8000
|
||||
|
||||
let toggleLoadingMessage = function(){
|
||||
switch (loadingMessage.style.display){
|
||||
case "inline-block":
|
||||
loadingMessage.style.display = "none"
|
||||
break;
|
||||
default:
|
||||
loadingMessage.style.display = "initial"
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fetch('/circles/version', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((ver) => ver.text())
|
||||
.then(function(ver) {
|
||||
document.getElementById('circlesVersion').innerText = ver
|
||||
})
|
||||
|
||||
function appendMessages(msg, blockHash, beforeHash, channel) {
|
||||
if (channel !== document.getElementById('feedIDInput').value) return // ignore if channel name isn't matching
|
||||
if (msg.length == 0) return // ignore empty messages
|
||||
|
||||
var humanDate = new Date(0)
|
||||
var msgDate = msg['meta']['time']
|
||||
var feed = document.getElementById("feed")
|
||||
var beforeEl = null
|
||||
|
||||
if (msgDate === undefined){
|
||||
msgDate = 'unknown'
|
||||
} else {
|
||||
humanDate.setUTCSeconds(msgDate)
|
||||
msgDate = humanDate.toLocaleString("en-US", {timeZone: "Etc/GMT"})
|
||||
}
|
||||
|
||||
var el = document.createElement('div')
|
||||
el.innerText = msg['content']
|
||||
|
||||
if (beforeHash !== null) {
|
||||
for (i = 0; i < feed.children.length; i++) {
|
||||
if (feed.children[i].getAttribute('data-bl') === beforeHash) {
|
||||
beforeEl = feed.children[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Template Test */
|
||||
// Test to see if the browser supports the HTML template element by checking
|
||||
// for the presence of the template element's content attribute.
|
||||
if ('content' in document.createElement('template')) {
|
||||
|
||||
// Instantiate the table with the existing HTML tbody
|
||||
// and the row with the template
|
||||
var template = document.getElementById('cMsgTemplate')
|
||||
|
||||
// Clone the new row and insert it into the table
|
||||
var clone = document.importNode(template.content, true)
|
||||
var div = clone.querySelectorAll("div")
|
||||
var identicon = clone.querySelectorAll("img")
|
||||
|
||||
div[0].classList.add('entry')
|
||||
div[0].setAttribute('timestamp', msg['meta']['time'])
|
||||
|
||||
div[0].setAttribute('data-bl', blockHash)
|
||||
div[2].textContent = msg['content']
|
||||
if (typeof msg['meta']['signer'] != 'undefined' && msg['meta']['signer'].length > 0){
|
||||
div[3].textContent = msg['meta']['signer'].substr(0, 5)
|
||||
setHumanReadableIDOnPost(div[3], msg['meta']['signer'])
|
||||
div[3].onclick = function(){
|
||||
navigator.clipboard.writeText(div[3].title).then(function() {
|
||||
PNotify.notice("Copied poster identity to clipboard")
|
||||
})
|
||||
}
|
||||
div[3].title = msg['meta']['signer']
|
||||
userIcon(msg['meta']['signer']).then(function(data){
|
||||
identicon[0].src = "data:image/svg+xml;base64," + data
|
||||
})
|
||||
}
|
||||
else{
|
||||
identicon[0].remove()
|
||||
}
|
||||
div[4].textContent = msgDate
|
||||
|
||||
loadingMessage.style.display = "none"
|
||||
loadedAny = true
|
||||
if (firstLoad){
|
||||
//feed.appendChild(clone)
|
||||
feed.prepend(clone)
|
||||
firstLoad = false
|
||||
}
|
||||
else{
|
||||
if (beforeEl === null){
|
||||
feed.prepend(clone)
|
||||
}
|
||||
else{
|
||||
beforeEl.insertAdjacentElement("beforebegin", clone.children[0])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBlocks(){
|
||||
var feed = document.getElementById("feed")
|
||||
var ch = document.getElementById('feedIDInput').value
|
||||
if (lastLoadedBoard !== ch){
|
||||
requested = []
|
||||
|
||||
toggleLoadingMessage()
|
||||
loadedAny = false
|
||||
|
||||
while (feed.firstChild) feed.removeChild(feed.firstChild); // remove all messages from feed
|
||||
|
||||
setTimeout(function(){
|
||||
if (! loadedAny && ch == document.getElementById('feedIDInput').value){
|
||||
PNotify.notice("There are no posts for " + ch + ". You can be the first!")
|
||||
}
|
||||
}, loadingTimeout)
|
||||
}
|
||||
|
||||
lastLoadedBoard = ch
|
||||
if (document.getElementById('none') !== null){
|
||||
document.getElementById('none').remove();
|
||||
|
||||
}
|
||||
|
||||
fetch('/circles/getpostsbyboard/' + ch, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text())
|
||||
.then(function(feedText) {
|
||||
var blockList = feedText.split(',')
|
||||
|
||||
for (i = 0; i < blockList.length; i++){
|
||||
blockList[i] = "0".repeat(64 - blockList[i].length) + blockList[i] // pad hash with zeroes
|
||||
|
||||
if (! requested.includes(blockList[i])){
|
||||
if (blockList[i].length == 0) continue
|
||||
else requested.push(blockList[i])
|
||||
loadMessage(blockList[i], blockList, i, ch);
|
||||
}
|
||||
}
|
||||
sortEntries()
|
||||
})
|
||||
}
|
||||
|
||||
function loadMessage(blockHash, blockList, count, channel){
|
||||
if (blockHash == '0000000000000000000000000000000000000000000000000000000000000000'){
|
||||
return
|
||||
}
|
||||
fetch('/getblockdata/' + blockHash, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"token": webpass
|
||||
}}).then(function(response) {
|
||||
if (!response.ok) {
|
||||
let on404 = function() {
|
||||
if (response.status == 404){
|
||||
fetch('/circles/removefromcache/' + channel + '/' + blockHash, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}
|
||||
})
|
||||
}
|
||||
else{
|
||||
console.log(error)
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
response.json().then(function(data){
|
||||
let before = blockList[count - 1]
|
||||
let delay = 2000
|
||||
if (typeof before == "undefined"){
|
||||
before = null
|
||||
} else {
|
||||
let existing = document.getElementsByClassName('cMsgBox')
|
||||
for (x = 0; x < existing.length; x++){
|
||||
if (existing[x].getAttribute('data-bl') === before){
|
||||
delay = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(function(){appendMessages(data, blockHash, before, channel)}, delay)
|
||||
})
|
||||
return response;
|
||||
})
|
||||
}
|
||||
|
||||
document.getElementById('refreshFeed').onclick = function() {
|
||||
getBlocks()
|
||||
}
|
||||
|
||||
newPostForm.onsubmit = function(){
|
||||
var message = document.getElementById('newMsgText').value
|
||||
var channel = document.getElementById('feedIDInput').value
|
||||
var meta = {'ch': channel}
|
||||
let doSign = document.getElementById('postAnon').checked
|
||||
var postData = {'message': message, 'sign': doSign, 'type': 'brd', 'encrypt': false, 'meta': JSON.stringify(meta)}
|
||||
postData = JSON.stringify(postData)
|
||||
newPostForm.style.display = 'none'
|
||||
fetch('/insertblock', {
|
||||
method: 'POST',
|
||||
body: postData,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}
|
||||
})
|
||||
.then((resp) => resp.text())
|
||||
.then(function(data) {
|
||||
newPostForm.style.display = 'block'
|
||||
if (data == 'failure due to duplicate insert'){
|
||||
PNotify.error({
|
||||
text: "This message is already queued"
|
||||
})
|
||||
return
|
||||
}
|
||||
PNotify.success({
|
||||
text: "Message queued for posting"
|
||||
})
|
||||
setTimeout(function(){getBlocks()}, 500)
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
resetCirclePickers = function(){
|
||||
document.getElementById('recommendedBoards').value = ""
|
||||
document.getElementById('popularBoards').value = ""
|
||||
}
|
||||
|
||||
document.getElementById('feedIDInput').onchange = resetCirclePickers
|
||||
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Handle default board picker
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
recommendedIDs = document.getElementById('recommendedBoards')
|
||||
|
||||
recommendedIDs.onchange = function(){
|
||||
document.getElementById('feedIDInput').value = recommendedIDs.value
|
||||
getBlocks()
|
||||
resetCirclePickers()
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
detect for Circles if plaintext insert/storage is enabled
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
plaintext_enabled = null
|
||||
|
||||
fetch('/config/get/general.store_plaintext_blocks', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text())
|
||||
.then(function(data) {
|
||||
plaintext_enabled = true
|
||||
if (data == "false"){
|
||||
plaintext_enabled = false
|
||||
PNotify.error({
|
||||
text: "Plaintext storage is disabled. You will not be able to see new posts or make posts yourself"
|
||||
})
|
||||
}
|
||||
})
|
@ -1,196 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<!--Mobile responsive-->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>
|
||||
Circles
|
||||
</title>
|
||||
<link rel="shortcut icon" type="image/ico" href="/shared/images/favicon.ico">
|
||||
<link rel="stylesheet" href="/shared/main/PNotifyBrightTheme.css">
|
||||
<link rel="stylesheet" href="/shared/fontawesome-free-5.10.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="/gettheme">
|
||||
<link rel="stylesheet" href="theme.css">
|
||||
<script defer src="/shared/base32.js"></script>
|
||||
<script defer src="/shared/identicon.js"></script>
|
||||
<script defer src="/shared/node_modules/pnotify/dist/iife/PNotify.js"></script>
|
||||
<script defer src="/shared/node_modules/pnotify/dist/iife/PNotifyButtons.js"></script>
|
||||
<script defer src="/shared/useridenticons.js"></script>
|
||||
<script defer src="/shared/misc.js"></script>
|
||||
<script defer src="/shared/navbar.js"></script>
|
||||
<script defer src="/shared/main/apicheck.js"></script>
|
||||
<script defer src="detect-plaintext-storage.js"></script>
|
||||
<script defer src="sethumanreadable.js"></script>
|
||||
<script defer src="default-circle-picker.js"></script>
|
||||
<script defer src="sort-posts.js"></script>
|
||||
<script defer src="board.js"></script>
|
||||
<script defer src="autorefresh.js"></script>
|
||||
<script defer src="popular.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item idLink" href="/">
|
||||
<img src="/shared/images/favicon.ico" class="navbarLogo">
|
||||
</a>
|
||||
|
||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||
data-target="navbarBasic">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbarBasic" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item idLink" href="/mail/">Mail</a>
|
||||
<a class="navbar-item idLink" href="/friends/">Friends</a>
|
||||
<a class="navbar-item idLink" href="/board/">Circles</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!--Hero (Dark Section)-->
|
||||
<section class="hero is-small is-dark">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h1 class="title">
|
||||
Circles <span class="is-pulled-right">v<span id='circlesVersion'></span></span>
|
||||
</h1>
|
||||
<h2 class="subtitle">
|
||||
Anonymous message boards
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<br>
|
||||
|
||||
<!--Start Content-->
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<!--Add Friend-->
|
||||
<div class="column is-one-third">
|
||||
<div class="card">
|
||||
<form method='POST' action='/' id='addMsg'>
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">
|
||||
Post message
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<textarea id='newMsgText' class="textarea" name='newMsgText' rows=10 cols=50 required
|
||||
minlength="2"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a class="card-footer-item">
|
||||
<input class='button is-primary' type='submit' value='Post'>
|
||||
</a>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!--Feed-->
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">
|
||||
Feed
|
||||
</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content">
|
||||
<div class="field">
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<a class="button is-static">Circle Name</a>
|
||||
</p>
|
||||
<p class="control is-expanded">
|
||||
<input id="feedIDInput" class="input" placeholder="Board name" value="global">
|
||||
</p>
|
||||
<p class="control">
|
||||
<a class="button is-success" id="refreshFeed">Refresh Feed</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="columns">
|
||||
<div class="column is-2">
|
||||
<div class="control">
|
||||
<label for="recommendedBoards" class="label">Default Circles:</label>
|
||||
<div class="select">
|
||||
<select id="recommendedBoards">
|
||||
<option value=""></option>
|
||||
<option value="global">Global</option>
|
||||
<option value="onionr">Onionr</option>
|
||||
<option value="games">Games</option>
|
||||
<option value="politics">Politics</option>
|
||||
<option value="tech">Tech</option>
|
||||
<option value="random">Random</option>
|
||||
<option value="privacy">Privacy</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="control">
|
||||
<label for="popularBoards" class="label">Popular Circles:</label>
|
||||
<div class="select">
|
||||
<select id="popularBoards">
|
||||
<option value=""> </option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="control">
|
||||
<br>
|
||||
Note: All posts in Circles are publicly accessible.
|
||||
</p>
|
||||
<input type="checkbox" class="checkbox" id="refreshCheckbox" checked>
|
||||
<label for="refreshCheckbox">Auto refresh feed</label>
|
||||
<br>
|
||||
<input type="checkbox" class="checkbox" id="postAnon" checked>
|
||||
<label for="postAnon">Sign posts</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<span id='loadingBoard'><i class="fas fa-yin-yang fa-spin"></i></span>
|
||||
<div id='feed'>
|
||||
<span id='none'>None yet, try refreshing 😃</span>
|
||||
<!--Message Items are appended here based on template-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Template markup for Circle message-->
|
||||
<template id="cMsgTemplate">
|
||||
<div class="box cMsgBox">
|
||||
<div class="columns">
|
||||
<div class="column cMsg">
|
||||
Message
|
||||
</div>
|
||||
<div class="column cAuthor is-narrow"></div>
|
||||
<img class="identicon image is-48x48" alt="user icon" src="/shared/images/anon.svg">
|
||||
<div class="column is-narrow cMsgDate">
|
||||
Date
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Load popular boards and show them in the UI. Handle selections of popular boards.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
fetch('/circles/getpopular/8', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((popular) => popular.text())
|
||||
.then(function(popular) {
|
||||
var popularSelect = document.getElementById('popularBoards')
|
||||
let boards = popular.split(',')
|
||||
for (board of boards){
|
||||
let newOption = document.createElement('option')
|
||||
if (board == ""){continue}
|
||||
newOption.value = board
|
||||
newOption.innerText = board.charAt(0).toUpperCase() + board.slice(1)
|
||||
console.debug(board)
|
||||
popularSelect.appendChild(newOption)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('popularBoards').onchange = function(){
|
||||
document.getElementById('feedIDInput').value = document.getElementById('popularBoards').value
|
||||
getBlocks()
|
||||
resetCirclePickers()
|
||||
}
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Set human readable public keys onto post author elements
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
humanReadableKeys = {}
|
||||
|
||||
function setHumanReadableIDOnPost(el, key){
|
||||
if (typeof humanReadableKeys[key] == "undefined"){
|
||||
fetch('/getHumanReadable/' + key, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text()) // Transform the data into json
|
||||
.then(function(data) {
|
||||
if (data.includes('HTML')){
|
||||
return
|
||||
}
|
||||
humanReadableKeys[key] = data
|
||||
setHumanReadableIDOnPost(el, key)
|
||||
})
|
||||
return
|
||||
}
|
||||
el.innerText = humanReadableKeys[key].split('-').slice(0, 3).join(' ')
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Sort post entries
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function sortEntries() {
|
||||
var entries = document.getElementsByClassName('entry')
|
||||
|
||||
if (entries.length > 1) {
|
||||
const sortBy = 'timestamp'
|
||||
const parent = entries[0].parentNode
|
||||
|
||||
const sorted = Array.from(entries).sort((a, b) => b.getAttribute(sortBy) - a.getAttribute(sortBy))
|
||||
sorted.forEach(element => parent.appendChild(element))
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
|
||||
.cMsg{
|
||||
word-wrap:break-word;
|
||||
word-break:break-word;
|
||||
white-space: pre-wrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body{
|
||||
background-color: #212224;
|
||||
color: white;
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
This file primarily serves to allow specific fetching of circles board messages
|
||||
"""
|
||||
from flask import Response, Blueprint, g
|
||||
|
||||
from deadsimplekv import DeadSimpleKV
|
||||
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
flask_blueprint = Blueprint('debugAPI', __name__)
|
||||
|
||||
|
||||
@flask_blueprint.route('/debug/dump_shared_state')
|
||||
def get_shared_state() -> Response:
|
||||
"""Return somewhat human-readable dump of shared toomanyobjects."""
|
||||
resp = ""
|
||||
for i in g.too_many.objects.keys:
|
||||
resp += i + dir(g.too_many.objects.keys[i]) + "\n"
|
||||
return Response(resp)
|
||||
|
||||
|
||||
@flask_blueprint.route('/debug/dump_shared_vars')
|
||||
def get_shared_vars() -> Response:
|
||||
"""Return somewhat human-readable dump of pseudo globals (DeadSimpleKV)."""
|
||||
kv: DeadSimpleKV = g.too_many.get(DeadSimpleKV)
|
||||
resp = ""
|
||||
for i in kv.keys:
|
||||
resp += i + dir(g.too_many.objects.keys[i]) + "\n"
|
||||
return Response(resp)
|
||||
|
@ -1,4 +0,0 @@
|
||||
{ "name": "debug",
|
||||
"version": "0.0.0",
|
||||
"author": "onionr"
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Client HTTP API debug endpoints
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import locale
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
||||
# import after path insert
|
||||
import debugapi # noqa
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
flask_blueprint = debugapi.flask_blueprint
|
||||
|
||||
plugin_name = 'debuginfo'
|
||||
PLUGIN_VERSION = '0.0.0'
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"name" : "encrypt",
|
||||
"version" : "1.0",
|
||||
"author" : "onionr"
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
'''
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
This default plugin allows users to encrypt/decrypt messages without using blocks
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
# Imports some useful libraries
|
||||
import logger, config, threading, time, datetime, sys
|
||||
|
||||
import ujson as json
|
||||
from nacl.exceptions import TypeError as NaclTypeError
|
||||
|
||||
from onionrutils import stringvalidators, bytesconverter
|
||||
from onionrcrypto import encryption, keypair, signing, getourkeypair
|
||||
import onionrexceptions, onionrusers
|
||||
|
||||
import locale
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
import binascii
|
||||
|
||||
plugin_name = 'encrypt'
|
||||
|
||||
class PlainEncryption:
|
||||
def __init__(self, api):
|
||||
self.api = api
|
||||
return
|
||||
def encrypt(self):
|
||||
# peer, data
|
||||
plaintext = ""
|
||||
encrypted = ""
|
||||
# detect if signing is enabled
|
||||
sign = True
|
||||
try:
|
||||
if sys.argv[3].lower() == 'false':
|
||||
sign = False
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if not stringvalidators.validate_pub_key(sys.argv[2]):
|
||||
raise onionrexceptions.InvalidPubkey
|
||||
except (ValueError, IndexError) as e:
|
||||
logger.error("Peer public key not specified", terminal=True)
|
||||
except onionrexceptions.InvalidPubkey:
|
||||
logger.error("Invalid public key", terminal=True)
|
||||
else:
|
||||
pubkey = sys.argv[2]
|
||||
# Encrypt if public key is valid
|
||||
logger.info("Please enter your message (ctrl-d or -q to stop):", terminal=True)
|
||||
try:
|
||||
for line in sys.stdin:
|
||||
if line == '-q\n':
|
||||
break
|
||||
plaintext += line
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
# Build Message to encrypt
|
||||
data = {}
|
||||
myPub = keypair[0]
|
||||
if sign:
|
||||
data['sig'] = signing.ed_sign(plaintext, key=keypair[1], encodeResult=True)
|
||||
data['sig'] = bytesconverter.bytes_to_str(data['sig'])
|
||||
data['signer'] = myPub
|
||||
data['data'] = plaintext
|
||||
data = json.dumps(data)
|
||||
plaintext = data
|
||||
encrypted = encryption.pub_key_encrypt(plaintext, pubkey, encodedData=True)
|
||||
encrypted = bytesconverter.bytes_to_str(encrypted)
|
||||
logger.info('Encrypted Message: \n\nONIONR ENCRYPTED DATA %s END ENCRYPTED DATA' % (encrypted,), terminal=True)
|
||||
|
||||
def decrypt(self):
|
||||
plaintext = ""
|
||||
data = ""
|
||||
logger.info("Please enter your message (ctrl-d or -q to stop):", terminal=True)
|
||||
keypair = getourkeypair.get_keypair()
|
||||
try:
|
||||
for line in sys.stdin:
|
||||
if line == '-q\n':
|
||||
break
|
||||
data += line
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
if len(data) <= 1:
|
||||
return
|
||||
encrypted = data.replace('ONIONR ENCRYPTED DATA ', '').replace('END ENCRYPTED DATA', '')
|
||||
myPub = keypair[0]
|
||||
decrypted = encryption.pub_key_decrypt(encrypted, privkey=keypair[1], encodedData=True)
|
||||
if decrypted == False:
|
||||
logger.error("Decryption failed", terminal=True)
|
||||
else:
|
||||
data = json.loads(decrypted)
|
||||
logger.info('Decrypted Message: \n\n%s' % data['data'], terminal=True)
|
||||
try:
|
||||
logger.info("Signing public key: %s" % (data['signer'],), terminal=True)
|
||||
if not signing.ed_verify(data['data'], data['signer'], data['sig']): raise ValueError
|
||||
except (ValueError, KeyError) as e:
|
||||
logger.warn("WARNING: THIS MESSAGE HAS A MISSING OR INVALID SIGNATURE", terminal=True)
|
||||
else:
|
||||
logger.info("Message has good signature.", terminal=True)
|
||||
return
|
||||
|
||||
def on_decrypt_cmd(api, data=None):
|
||||
try:
|
||||
PlainEncryption(api).decrypt()
|
||||
except binascii.Error:
|
||||
logger.error("Invalid ciphertext padding", terminal=True)
|
||||
except NaclTypeError:
|
||||
logger.error("Ciphertext too short.", terminal=True)
|
||||
|
||||
def on_encrypt_cmd(api, data=None):
|
||||
PlainEncryption(api).encrypt()
|
||||
|
||||
on_encrypt_cmd.onionr_help = """encrypt <user_key>\nEncrypt text data to an Onionr user key. Similar to PGP"""
|
||||
on_decrypt_cmd.onionr_help = """decrypt\nDecrypt text data with your Onionr key. Similar to PGP"""
|
||||
ONIONR_COMMANDS = ['encrypt', 'decrypt']
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"name" : "pms",
|
||||
"version" : "0.1.2",
|
||||
"author" : "onionr"
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Load the user's inbox and return it as a list
|
||||
"""
|
||||
from onionrblocks import onionrblockapi
|
||||
from coredb import blockmetadb
|
||||
from utils import reconstructhash, identifyhome
|
||||
import deadsimplekv as simplekv
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
inbox_cache = {}
|
||||
def load_inbox():
|
||||
inbox_list = []
|
||||
deleted = simplekv.DeadSimpleKV(identifyhome.identify_home() + '/mailcache.dat').get('deleted_mail')
|
||||
if deleted is None:
|
||||
deleted = []
|
||||
|
||||
for blockHash in blockmetadb.get_blocks_by_type('pm'):
|
||||
if blockHash in deleted:
|
||||
continue
|
||||
if blockHash in inbox_cache:
|
||||
inbox_list.append(blockHash)
|
||||
continue
|
||||
block = onionrblockapi.Block(blockHash)
|
||||
block.decrypt()
|
||||
if block.decrypted and reconstructhash.deconstruct_hash(blockHash):
|
||||
inbox_cache[blockHash] = True
|
||||
inbox_list.append(blockHash)
|
||||
return inbox_list
|
@ -1,97 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
HTTP endpoints for mail plugin
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
import ujson as json
|
||||
from flask import Response, request, redirect, Blueprint, abort
|
||||
from flask import send_from_directory
|
||||
import deadsimplekv as simplekv
|
||||
|
||||
from httpapi.sse.wrapper import SSEWrapper
|
||||
from onionrusers import contactmanager
|
||||
from onionrutils import stringvalidators
|
||||
from utils import reconstructhash, identifyhome
|
||||
from utils.bettersleep import better_sleep as sleep
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
||||
import loadinbox
|
||||
import sentboxdb
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
flask_blueprint = Blueprint('mail', __name__)
|
||||
kv = simplekv.DeadSimpleKV(identifyhome.identify_home() + '/mailcache.dat')
|
||||
root = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
sse_wrapper = SSEWrapper()
|
||||
|
||||
@flask_blueprint.route('/mail/<path:path>', endpoint='mailstatic')
|
||||
def load_mail(path):
|
||||
return send_from_directory(root + '/web/', path)
|
||||
|
||||
|
||||
@flask_blueprint.route('/mail/', endpoint='mailindex')
|
||||
def load_mail_index():
|
||||
return send_from_directory(root + '/web/', 'index.html')
|
||||
|
||||
|
||||
@flask_blueprint.route('/mail/ping')
|
||||
def mail_ping():
|
||||
return 'pong!'
|
||||
|
||||
@flask_blueprint.route('/mail/deletemsg/<block>', methods=['POST'])
|
||||
def mail_delete(block):
|
||||
if not stringvalidators.validate_hash(block):
|
||||
abort(504)
|
||||
block = reconstructhash.deconstruct_hash(block)
|
||||
existing = kv.get('deleted_mail')
|
||||
if existing is None:
|
||||
existing = []
|
||||
if block not in existing:
|
||||
existing.append(block)
|
||||
kv.put('deleted_mail', existing)
|
||||
kv.flush()
|
||||
return 'success'
|
||||
|
||||
@flask_blueprint.route('/mail/getinbox')
|
||||
def list_inbox():
|
||||
return ','.join(loadinbox.load_inbox())
|
||||
|
||||
|
||||
@flask_blueprint.route('/mail/streaminbox')
|
||||
def stream_inbox():
|
||||
def _stream():
|
||||
while True:
|
||||
yield "data: " + ','.join(loadinbox.load_inbox()) + "\n\n"
|
||||
sleep(1)
|
||||
return sse_wrapper.handle_sse_request(_stream)
|
||||
|
||||
|
||||
@flask_blueprint.route('/mail/getsentbox')
|
||||
def list_sentbox():
|
||||
kv.refresh()
|
||||
sentbox_list = sentboxdb.SentBox().listSent()
|
||||
list_copy = list(sentbox_list)
|
||||
deleted = kv.get('deleted_mail')
|
||||
if deleted is None:
|
||||
deleted = []
|
||||
for sent in list_copy:
|
||||
if sent['hash'] in deleted:
|
||||
sentbox_list.remove(sent)
|
||||
continue
|
||||
sent['name'] = contactmanager.ContactManager(sent['peer'], saveUser=False).get_info('name')
|
||||
return json.dumps(sentbox_list)
|
@ -1,87 +0,0 @@
|
||||
"""Onionr - Private P2P Communication.
|
||||
|
||||
Private messages in an email like fashion
|
||||
"""
|
||||
import locale
|
||||
import sys
|
||||
import os
|
||||
|
||||
import ujson as json
|
||||
|
||||
from onionrusers import contactmanager
|
||||
from utils import reconstructhash
|
||||
from onionrutils import bytesconverter
|
||||
import config
|
||||
import notifier
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
plugin_name = 'pms'
|
||||
PLUGIN_VERSION = '0.1.2'
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
||||
import sentboxdb, mailapi, loadinbox # import after path insert
|
||||
from onblacklist import on_blacklist_add
|
||||
|
||||
flask_blueprint = mailapi.flask_blueprint
|
||||
security_whitelist = ['mail.mailstatic', 'mail.mailindex']
|
||||
|
||||
|
||||
def add_deleted(keyStore, b_hash):
|
||||
existing = keyStore.get('deleted_mail')
|
||||
bHash = reconstructhash.reconstruct_hash(b_hash)
|
||||
if existing is None:
|
||||
existing = []
|
||||
else:
|
||||
if bHash in existing:
|
||||
return
|
||||
keyStore.put('deleted_mail', existing.append(b_hash))
|
||||
|
||||
|
||||
def on_insertblock(api, data={}):
|
||||
meta = json.loads(data['meta'])
|
||||
if meta['type'] == 'pm':
|
||||
sentboxTools = sentboxdb.SentBox()
|
||||
sentboxTools.addToSent(data['hash'], data['peer'], data['content'], meta['subject'])
|
||||
|
||||
|
||||
def on_processblocks(api, data=None):
|
||||
if data['type'] != 'pm':
|
||||
return
|
||||
notification_func = notifier.notify
|
||||
data['block'].decrypt()
|
||||
metadata = data['block'].bmetadata
|
||||
|
||||
signer = bytesconverter.bytes_to_str(data['block'].signer)
|
||||
user = contactmanager.ContactManager(signer, saveUser=False)
|
||||
name = user.get_info("name")
|
||||
if name != 'anonymous' and name != None:
|
||||
signer = name.title()
|
||||
else:
|
||||
signer = signer[:5]
|
||||
|
||||
|
||||
if data['block'].decrypted:
|
||||
config.reload()
|
||||
|
||||
if config.get('mail.notificationSound', True):
|
||||
notification_func = notifier.notification_with_sound
|
||||
if config.get('mail.notificationSetting', True):
|
||||
if not config.get('mail.strangersNotification', True):
|
||||
if not user.isFriend():
|
||||
return
|
||||
notification_func(title="Onionr Mail - New Message", message="From: %s\n\nSubject: %s" % (signer, metadata['subject']))
|
@ -1,12 +0,0 @@
|
||||
from threading import Thread
|
||||
|
||||
from onionrutils import localcommand
|
||||
|
||||
|
||||
def on_blacklist_add(api, data=None):
|
||||
blacklisted_data = data['data']
|
||||
|
||||
def remove():
|
||||
localcommand.local_command(f'/mail/deletemsg/{blacklisted_data}', post=True)
|
||||
|
||||
Thread(target=remove).start()
|
@ -1,79 +0,0 @@
|
||||
"""
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
This file handles the sentbox for the mail plugin
|
||||
"""
|
||||
import sqlite3
|
||||
import os
|
||||
from onionrutils import epoch
|
||||
from utils import identifyhome, reconstructhash
|
||||
"""
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
class SentBox:
|
||||
def __init__(self):
|
||||
self.dbLocation = identifyhome.identify_home() + '/sentbox.db'
|
||||
if not os.path.exists(self.dbLocation):
|
||||
self.createDB()
|
||||
return
|
||||
|
||||
def connect(self):
|
||||
self.conn = sqlite3.connect(self.dbLocation, timeout=30)
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
def close(self):
|
||||
self.conn.close()
|
||||
|
||||
def createDB(self):
|
||||
conn = sqlite3.connect(self.dbLocation)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""CREATE TABLE sent(
|
||||
hash id not null,
|
||||
peer text not null,
|
||||
message text not null,
|
||||
subject text not null,
|
||||
date int not null
|
||||
);
|
||||
""")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return
|
||||
|
||||
def listSent(self):
|
||||
self.connect()
|
||||
retData = []
|
||||
for entry in self.cursor.execute('SELECT * FROM sent;'):
|
||||
retData.append({'hash': entry[0], 'peer': entry[1], 'message': entry[2], 'subject': entry[3], 'date': entry[4]})
|
||||
self.close()
|
||||
return retData
|
||||
|
||||
def addToSent(self, blockID, peer, message, subject=''):
|
||||
blockID = reconstructhash.deconstruct_hash(blockID)
|
||||
self.connect()
|
||||
args = (blockID, peer, message, subject, epoch.get_epoch())
|
||||
self.cursor.execute('INSERT INTO sent VALUES(?, ?, ?, ?, ?)', args)
|
||||
self.conn.commit()
|
||||
self.close()
|
||||
return
|
||||
|
||||
def removeSent(self, blockID):
|
||||
blockID = reconstructhash.deconstruct_hash(blockID)
|
||||
self.connect()
|
||||
args = (blockID,)
|
||||
self.cursor.execute('DELETE FROM sent where hash=?', args)
|
||||
self.conn.commit()
|
||||
self.close()
|
||||
return
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Settings modal closing
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
document.getElementById('closeSettingsModalButton').onclick = function(){
|
||||
document.getElementById('settingsModal').classList.remove('is-active')
|
||||
setActiveTab('inbox')
|
||||
}
|
||||
|
||||
document.querySelector("#settingsModal .modal-background").onclick = function(){
|
||||
document.getElementById('settingsModal').classList.remove('is-active')
|
||||
setActiveTab('inbox')
|
||||
}
|
@ -1,262 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>
|
||||
Onionr Mail
|
||||
</title>
|
||||
<link rel="shortcut icon" type="image/ico" href="/shared/images/favicon.ico">
|
||||
<link rel="stylesheet" href="/shared/fontawesome-free-5.10.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="/shared/main/PNotifyBrightTheme.css">
|
||||
<link rel="stylesheet" href="/gettheme">
|
||||
<link rel="stylesheet" href="/shared/node_modules/bulma-switch/dist/css/bulma-switch.min.css">
|
||||
<link rel="stylesheet" href="/mail/mail.css">
|
||||
<script defer src="/shared/node_modules/pnotify/dist/iife/PNotify.js"></script>
|
||||
<script defer src="/shared/node_modules/pnotify/dist/iife/PNotifyButtons.js"></script>
|
||||
<script defer src="/shared/eventsource.js"></script>
|
||||
<script defer src="/shared/main/apicheck.js"></script>
|
||||
<script defer src="/shared/misc.js"></script>
|
||||
<script defer src="/mail/sethumanreadable.js"></script>
|
||||
<script defer src="/mail/loadsettings.js"></script>
|
||||
<script defer src="/mail/mail.js"></script>
|
||||
<script defer src="/mail/sendmail.js"></script>
|
||||
<script defer src="/mail/closesettings.js"></script>
|
||||
<script defer src="/mail/settings.js"></script>
|
||||
<script defer src="/shared/navbar.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="navbar is-dark" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item idLink" href="/">
|
||||
<img src="/shared/images/favicon.ico" class="navbarLogo">
|
||||
</a>
|
||||
|
||||
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false"
|
||||
data-target="navbarBasic">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbarBasic" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item idLink" href="/mail/">Mail</a>
|
||||
<a class="navbar-item idLink" href="/friends/">Friends</a>
|
||||
<a class="navbar-item idLink" href="/board/">Circles</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!--Hero (Dark Section)-->
|
||||
<section class="hero is-small is-dark">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h1 class="title">
|
||||
Mail
|
||||
</h1>
|
||||
<h2 class="subtitle">
|
||||
Private and safe messages
|
||||
</h2>
|
||||
</div>
|
||||
<div class="column is-7">
|
||||
<div class="field">
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<a class="button is-static">
|
||||
<i class="fas fa-fingerprint"></i>
|
||||
</a>
|
||||
</p>
|
||||
<p class="control is-expanded">
|
||||
<input id="myPub" class="input myPub" type="text" readonly>
|
||||
</p>
|
||||
<p class="control">
|
||||
<a id="myPubCopy" class="button is-primary"><i class="fas fa-copy"></i></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<br>
|
||||
<div id="messageDisplay" class="overlay">
|
||||
<div class="overlayContent">
|
||||
<span class="closeOverlay" overlay="messageDisplay"></span>
|
||||
<div>
|
||||
From: <input type="text" id="fromUser" readonly> Signature: <span id="sigValid"></span> <span
|
||||
id="addUnknownContact"><button class="button is-primary">Add to Contacts</button></span>
|
||||
</div>
|
||||
<div class="break-up">
|
||||
Subject: <span id="subjectView"></span>
|
||||
</div>
|
||||
<div>
|
||||
<button id="replyBtn" class="button is-primary break-up">Reply</button>
|
||||
</div>
|
||||
<div id="signatureValidity"></div>
|
||||
<div id="threadDisplay" class="pre messageContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sentboxDisplay" class="overlay">
|
||||
<div class="overlayContent">
|
||||
<span class="closeOverlay" overlay="sentboxDisplay"></span>
|
||||
To: <input id="toID" readonly type="text">
|
||||
<div id="sentboxDisplayText" class="pre messageContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="settingsModal" class="modal">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<button id="closeSettingsModalButton" class="closeSettingsModal delete" aria-label="close"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div>Ask senders to use forward-secrecy</div>
|
||||
<small>Turn off if using 1 ID on more than 1 device, or have Onionr data erasure on exit enabled.</small>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
<input id="forwardSecrecySetting" type="checkbox"
|
||||
class="switch is-rounded is-danger">
|
||||
<label for="forwardSecrecySetting"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div>Message padding</div>
|
||||
<small>Improves privacy slightly for a small performance hit.</small>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
<input id="messagePaddingSetting" type="checkbox"
|
||||
class="switch is-rounded is-warning">
|
||||
<label for="messagePaddingSetting"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
Inbox notifications
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
<input id="notificationSetting" type="checkbox"
|
||||
class="switch is-rounded" checked>
|
||||
<label for="notificationSetting"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns notificationSetting">
|
||||
<div class="column">
|
||||
Notifications for stranger's messages
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
<input id="strangersNotification" type="checkbox"
|
||||
class="switch is-rounded" checked>
|
||||
<label for="strangersNotification"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns notificationSetting">
|
||||
<div class="column">
|
||||
Notification sound
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<div class="field">
|
||||
<input id="notificationSound" type="checkbox"
|
||||
class="switch is-rounded" checked>
|
||||
<label for="notificationSound"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Signature
|
||||
<textarea id="mailSignatureSetting" class="textarea" placeholder="Signature to add to every message" rows="5"></textarea>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<!--Start of content-->
|
||||
<div class="container">
|
||||
<div class="tabs" id="tabBtns">
|
||||
<ul>
|
||||
<li class="is-active">
|
||||
<a>
|
||||
<span class="icon">
|
||||
<i class="fa fa-envelope"></i>
|
||||
</span>
|
||||
<span id="inboxTab">Inbox</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>
|
||||
<span class="icon">
|
||||
<i class="fa fa-paper-plane"></i>
|
||||
</span>
|
||||
<span>Sent</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>
|
||||
<span class="icon">
|
||||
<i class="fa fa-pen"></i>
|
||||
</span>
|
||||
<span>Compose</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>
|
||||
<span class="icon">
|
||||
<i class="fa fa-cog"></i>
|
||||
</span>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content" id="noInbox">No messages to show ¯\_(ツ)_/¯</div>
|
||||
<div class="content">
|
||||
<div class="mailPing">
|
||||
API server either shutdown, has disabled mail, or has experienced a bug.
|
||||
</div>
|
||||
<div id="threads" class="threads">
|
||||
<div id="threadPlaceholder">Nothing here yet 😞</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sendMessage">
|
||||
<div class="container">
|
||||
<div class="field">
|
||||
<label><i class="fas fa-user"></i> Select friend: <select id="friendSelect"></select></label>
|
||||
</div>
|
||||
<form method="post" action="" id="sendForm" enctype="application/x-www-form-urlencoded">
|
||||
<div class="field">
|
||||
To: <input id="draftID" type="text" name="to" placeholder="pubkey or select above" autocomplete="off" required>
|
||||
</div>
|
||||
Subject: <input name="subject" id="draftSubject" maxlength="25" type="text"
|
||||
placeholder="message subject" autocomplete="off">
|
||||
<div class="field">
|
||||
<textarea name="message" class="textarea" placeholder="type your message..." id="draftText" required></textarea>
|
||||
</div>
|
||||
<input type="submit" value="Send" class="button is-primary successBtn">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="infoOverlay" class="overlay">
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -1,29 +0,0 @@
|
||||
mailSettings = {}
|
||||
|
||||
fetch('/config/get/mail', {
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.json())
|
||||
.then(function(settings) {
|
||||
mailSettings = settings || {}
|
||||
if (mailSettings.default_forward_secrecy === false){
|
||||
document.getElementById('forwardSecrecySetting').checked = false
|
||||
}
|
||||
if (mailSettings.use_padding === false){
|
||||
document.getElementById('messagePaddingSetting').checked = false
|
||||
}
|
||||
if (mailSettings.notificationSetting === false){
|
||||
document.getElementById('notificationSetting').checked = false
|
||||
}
|
||||
if (mailSettings.notificationSound === false){
|
||||
document.getElementById('notificationSound').checked = false
|
||||
}
|
||||
if (typeof mailSettings.signature != undefined && mailSettings.signature != null && mailSettings.signature != ""){
|
||||
document.getElementById('mailSignatureSetting').value = mailSettings.signature
|
||||
}
|
||||
if (mailSettings.strangersNotification == false){
|
||||
document.getElementById('strangersNotification').checked = false
|
||||
}
|
||||
})
|
@ -1,60 +0,0 @@
|
||||
.threadEntry button{
|
||||
margin-right: 1%;
|
||||
}
|
||||
.threadEntry span, .sentboxList span{
|
||||
padding-left: 1%;
|
||||
}
|
||||
.threadEntry, .sentboxList{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overlayContent{
|
||||
background-color: lightgray;
|
||||
border: 3px solid black;
|
||||
border-radius: 3px;
|
||||
color: black;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
min-height: 100%;
|
||||
padding: 1em;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
#draftText{
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
display: block;
|
||||
width: 50%;
|
||||
height: 75%;
|
||||
min-width: 2%;
|
||||
min-height: 5%;
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.sentboxList{
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.messageContent{
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
#tabBtns ul .icon {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
#settingsModal .modal-card-body{
|
||||
font-size: 150%;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
#settingsModal textarea{
|
||||
resize: none;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#settingsModal small{
|
||||
font-size: 0.5em;
|
||||
}
|
@ -1,467 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
This file handles the mail interface
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pms = ''
|
||||
sentbox = ''
|
||||
threadPart = document.getElementById('threads')
|
||||
threadPlaceholder = document.getElementById('threadPlaceholder')
|
||||
tabBtns = document.getElementById('tabBtns')
|
||||
threadContent = {}
|
||||
replyBtn = document.getElementById('replyBtn')
|
||||
addUnknownContact = document.getElementById('addUnknownContact')
|
||||
noInbox = document.getElementById('noInbox')
|
||||
humanReadableCache = {}
|
||||
|
||||
function stripEndZeroes(str){
|
||||
str = str.split("")
|
||||
let zeroCount = 0
|
||||
for (x = str.length - 1; x != 0; x--){
|
||||
if (str[x] == "0"){
|
||||
zeroCount += 1
|
||||
}
|
||||
else{
|
||||
break
|
||||
}
|
||||
}
|
||||
str.splice(str.length - zeroCount, zeroCount)
|
||||
str = str.join("")
|
||||
return str
|
||||
}
|
||||
|
||||
async function addContact(pubkey, friendName){
|
||||
fetch('/friends/add/' + pubkey, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"token": webpass
|
||||
}}).then(function(data) {
|
||||
if (friendName.trim().length > 0){
|
||||
post_to_url('/friends/setinfo/' + pubkey + '/name', {'data': friendName, 'token': webpass})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function openReply(bHash, quote, subject){
|
||||
var inbox = document.getElementsByClassName('threadEntry')
|
||||
var entry = ''
|
||||
var friendName = ''
|
||||
var key = ''
|
||||
for(var i = 0; i < inbox.length; i++) {
|
||||
if (inbox[i].getAttribute('data-hash') === bHash){
|
||||
entry = inbox[i]
|
||||
}
|
||||
}
|
||||
if (entry.getAttribute('data-nameset') == 'true'){
|
||||
document.getElementById('friendSelect').value = document.getElementById('fromUser').value
|
||||
}
|
||||
key = entry.getAttribute('data-pubkey')
|
||||
document.getElementById('draftID').value = key
|
||||
document.getElementById('draftSubject').value = 'RE: ' + subject
|
||||
|
||||
// Add quoted reply
|
||||
var splitQuotes = quote.split('\n')
|
||||
for (var x = 0; x < splitQuotes.length; x++){
|
||||
splitQuotes[x] = '> ' + splitQuotes[x]
|
||||
}
|
||||
|
||||
if (typeof humanReadableCache[key] != 'undefined'){
|
||||
document.getElementById('draftID').value = humanReadableCache[key]
|
||||
quote = '\n' + humanReadableCache[key].split('-').slice(0,3).join('-') + ' wrote:\n' + splitQuotes.join('\n')
|
||||
}
|
||||
else{
|
||||
quote = '\n' + key.substring(0, 12) + ' wrote:' + '\n' + splitQuotes.join('\n')
|
||||
}
|
||||
document.getElementById('draftText').value = quote
|
||||
setActiveTab('compose')
|
||||
}
|
||||
|
||||
function openThread(bHash, sender, date, sigBool, pubkey, subjectLine){
|
||||
addUnknownContact.style.display = 'none'
|
||||
var messageDisplay = document.getElementById('threadDisplay')
|
||||
|
||||
fetch('/getblockbody/' + bHash, {
|
||||
"method": "get",
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text())
|
||||
.then(function(resp) {
|
||||
document.getElementById('fromUser').value = sender || 'Anonymous'
|
||||
document.getElementById('fromUser').value = pubkey || ''
|
||||
document.getElementById('subjectView').innerText = subjectLine
|
||||
|
||||
resp = stripEndZeroes(resp)
|
||||
|
||||
messageDisplay.innerText = resp
|
||||
var sigEl = document.getElementById('sigValid')
|
||||
var sigMsg = 'signature'
|
||||
|
||||
// show add unknown contact button if peer is unknown
|
||||
if (sender !== myPub && sigBool){
|
||||
addUnknownContact.style.display = 'inline'
|
||||
}
|
||||
|
||||
if (sigBool){
|
||||
sigMsg = 'Good ' + sigMsg
|
||||
sigEl.classList.remove('danger')
|
||||
}
|
||||
else{
|
||||
sigMsg = 'Bad/no ' + sigMsg + ' (message could be impersonating someone)'
|
||||
sigEl.classList.add('danger')
|
||||
replyBtn.style.display = 'none'
|
||||
}
|
||||
sigEl.innerText = sigMsg
|
||||
overlay('messageDisplay')
|
||||
replyBtn.onclick = function(){
|
||||
document.getElementById('messageDisplay').style.visibility = 'hidden'
|
||||
openReply(bHash, messageDisplay.innerText, subjectLine)
|
||||
}
|
||||
addUnknownContact.onclick = function(){
|
||||
var friendName = prompt("Enter an alias for this contact:")
|
||||
if (friendName === null || friendName.length == 0){
|
||||
return
|
||||
}
|
||||
addContact(pubkey, friendName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setActiveTab(tabName){
|
||||
threadPart.innerHTML = ""
|
||||
noInbox.style.display = 'none'
|
||||
window.inboxActive = false
|
||||
document.getElementById('sendMessage').classList.add('is-hidden')
|
||||
switch(tabName){
|
||||
case 'inbox':
|
||||
window.inboxActive = true
|
||||
refreshPms()
|
||||
getInbox()
|
||||
break
|
||||
case 'sent':
|
||||
getSentbox()
|
||||
break
|
||||
case 'compose':
|
||||
document.getElementById('sendMessage').classList.remove('is-hidden')
|
||||
break
|
||||
case 'settings':
|
||||
document.getElementById('settingsModal').classList.add('is-active')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function deleteMessage(bHash){
|
||||
fetch('/mail/deletemsg/' + bHash, {
|
||||
"method": "post",
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
}
|
||||
|
||||
function mailPing(){
|
||||
fetch('/mail/ping', {
|
||||
"method": "get",
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then(function(resp) {
|
||||
var pings = document.getElementsByClassName('mailPing')
|
||||
if (resp.ok){
|
||||
for (var i=0; i < pings.length; i++){
|
||||
pings[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
else{
|
||||
for (var i=0; i < pings.length; i++){
|
||||
pings[i].style.display = 'block';
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadInboxEntries(bHash){
|
||||
fetch('/getblockheader/' + bHash, {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.json()) // Transform the data into json
|
||||
.then(function(resp) {
|
||||
//console.log(resp)
|
||||
var entry = document.createElement('div')
|
||||
var bHashDisplay = document.createElement('span')
|
||||
//var senderInput = document.createElement('input')
|
||||
var senderInput = document.createElement('span')
|
||||
var subjectLine = document.createElement('span')
|
||||
var dateStr = document.createElement('span')
|
||||
var validSig = document.createElement('span')
|
||||
var deleteBtn = document.createElement('button')
|
||||
var humanDate = new Date(0)
|
||||
var metadata = resp['metadata']
|
||||
humanDate.setUTCSeconds(resp['meta']['time'])
|
||||
humanDate = humanDate.toString()
|
||||
validSig.style.display = 'none'
|
||||
|
||||
if (typeof resp['meta']['signer'] != 'undefined' && resp['meta']['signer'] != ''){
|
||||
fetch('/friends/getinfo/' + resp['meta']['signer'] + '/name', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then(function(resp2){
|
||||
if (!resp2.ok){
|
||||
setHumanReadableValue(senderInput, resp['meta']['signer'])
|
||||
entry.setAttribute('data-nameSet', false)
|
||||
}
|
||||
else{
|
||||
resp2.text().then(function(resp2){
|
||||
loadHumanReadableToCache(resp['meta']['signer'])
|
||||
senderInput.innerText = resp2
|
||||
entry.setAttribute('data-nameSet', true)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
else{
|
||||
senderInput.innerText = 'Anonymous'
|
||||
entry.setAttribute('data-nameSet', false)
|
||||
}
|
||||
if (! resp['meta']['validSig']){
|
||||
validSig.style.display = 'inline'
|
||||
validSig.innerText = 'Signature Validity: Bad'
|
||||
validSig.style.color = 'red'
|
||||
}
|
||||
//bHashDisplay.innerText = bHash.substring(0, 10)
|
||||
entry.setAttribute('data-hash', bHash)
|
||||
entry.setAttribute('data-pubkey', resp['meta']['signer'])
|
||||
senderInput.readOnly = true
|
||||
dateStr.innerText = humanDate.substring(0, humanDate.indexOf('('))
|
||||
deleteBtn.classList.add('delete', 'deleteBtn')
|
||||
if (metadata['subject'] === undefined || metadata['subject'] === null) {
|
||||
subjectLine.innerText = '()'
|
||||
}
|
||||
else{
|
||||
subjectLine.innerText = '(' + metadata['subject'] + ')'
|
||||
}
|
||||
//entry.innerHTML = 'sender ' + resp['meta']['signer'] + ' - ' + resp['meta']['time']
|
||||
threadPart.appendChild(entry)
|
||||
entry.appendChild(deleteBtn)
|
||||
entry.appendChild(bHashDisplay)
|
||||
entry.appendChild(senderInput)
|
||||
entry.appendChild(subjectLine)
|
||||
entry.appendChild(dateStr)
|
||||
entry.appendChild(validSig)
|
||||
entry.classList.add('threadEntry')
|
||||
|
||||
entry.onclick = function(event){
|
||||
if (event.target.classList.contains('deleteBtn')){
|
||||
return
|
||||
}
|
||||
openThread(entry.getAttribute('data-hash'), senderInput.innerText, dateStr.innerText, resp['meta']['validSig'], entry.getAttribute('data-pubkey'), subjectLine.innerText)
|
||||
}
|
||||
|
||||
deleteBtn.onclick = function(){
|
||||
entry.parentNode.removeChild(entry);
|
||||
deleteMessage(entry.getAttribute('data-hash'))
|
||||
}
|
||||
|
||||
}.bind(bHash))
|
||||
}
|
||||
|
||||
function getInbox(){
|
||||
if (! window.inboxActive){
|
||||
return
|
||||
}
|
||||
var els = document.getElementsByClassName('threadEntry')
|
||||
var showed = false
|
||||
for(var i = 0; i < pms.length; i++) {
|
||||
var add = true
|
||||
if (pms[i].trim().length == 0){
|
||||
noInbox.style.display = 'block'
|
||||
continue
|
||||
}
|
||||
else{
|
||||
threadPlaceholder.style.display = 'none'
|
||||
showed = true
|
||||
}
|
||||
for (var x = 0; x < els.length; x++){
|
||||
if (pms[i] === els[x].getAttribute('data-hash')){
|
||||
add = false
|
||||
}
|
||||
}
|
||||
if (add && window.inboxActive) {
|
||||
loadInboxEntries(pms[i])
|
||||
}
|
||||
}
|
||||
if (! showed){
|
||||
threadPlaceholder.style.display = 'block'
|
||||
}
|
||||
}
|
||||
|
||||
function getSentbox(){
|
||||
fetch('/mail/getsentbox', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.json()) // Transform the data into json
|
||||
.then(function(resp) {
|
||||
var keys = [];
|
||||
var entry = document.createElement('div')
|
||||
for(var k in resp) keys.push(k);
|
||||
if (keys.length == 0){
|
||||
threadPart.innerHTML = "nothing to show here yet."
|
||||
}
|
||||
for (var i = keys.length - 1; i > -1; i--) (function(i, resp){
|
||||
var entry = document.createElement('div')
|
||||
var toLabel = document.createElement('span')
|
||||
toLabel.innerText = 'To: '
|
||||
var toEl = document.createElement('span')
|
||||
toEl.classList.add('toElement')
|
||||
var sentDate = document.createElement('span')
|
||||
var humanDate = new Date(0)
|
||||
humanDate.setUTCSeconds(resp[i]['date'])
|
||||
humanDate = humanDate.toString()
|
||||
var preview = document.createElement('span')
|
||||
var deleteBtn = document.createElement('button')
|
||||
var message = resp[i]['message']
|
||||
deleteBtn.classList.add('deleteBtn', 'delete')
|
||||
|
||||
sentDate.innerText = humanDate.substring(0, humanDate.indexOf('('))
|
||||
if (resp[i]['name'] == null || resp[i]['name'].toLowerCase() == 'anonymous'){
|
||||
toEl.innerText = resp[i]['peer']
|
||||
setHumanReadableValue(toEl, resp[i]['peer'])
|
||||
}
|
||||
else{
|
||||
toEl.innerText = resp[i]['name']
|
||||
}
|
||||
preview.innerText = '(' + resp[i]['subject'] + ')'
|
||||
entry.classList.add('sentboxList')
|
||||
entry.setAttribute('data-hash', resp[i]['hash'])
|
||||
entry.appendChild(deleteBtn)
|
||||
entry.appendChild(toLabel)
|
||||
entry.appendChild(toEl)
|
||||
entry.appendChild(preview)
|
||||
entry.appendChild(sentDate)
|
||||
|
||||
threadPart.appendChild(entry)
|
||||
|
||||
entry.onclick = function(e){
|
||||
if (e.target.classList.contains('deleteBtn')){
|
||||
deleteMessage(e.target.parentNode.getAttribute('data-hash'))
|
||||
e.target.parentNode.parentNode.removeChild(e.target.parentNode)
|
||||
return
|
||||
}
|
||||
showSentboxWindow(toEl.innerText, message)
|
||||
}
|
||||
})(i, resp)
|
||||
threadPart.appendChild(entry)
|
||||
}.bind(threadPart))
|
||||
}
|
||||
|
||||
function showSentboxWindow(to, content){
|
||||
content = stripEndZeroes(content)
|
||||
document.getElementById('toID').value = to
|
||||
document.getElementById('sentboxDisplayText').innerText = content
|
||||
overlay('sentboxDisplay')
|
||||
}
|
||||
|
||||
function refreshPms(callNext){
|
||||
if (! window.inboxActive){
|
||||
return
|
||||
}
|
||||
if (document.hidden){
|
||||
return
|
||||
}
|
||||
fetch('/mail/getinbox', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text())
|
||||
.then(function(data) {
|
||||
pms = data.split(',')
|
||||
if (pms.length > 0){
|
||||
noInbox.style.display = 'none'
|
||||
}
|
||||
if (callNext){
|
||||
getInbox()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
tabBtns.onclick = function(event){
|
||||
var children = tabBtns.children[0].children
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var btn = children[i]
|
||||
btn.classList.remove('is-active')
|
||||
}
|
||||
event.target.parentElement.parentElement.classList.add('is-active')
|
||||
setActiveTab(event.target.innerText.toLowerCase())
|
||||
}
|
||||
|
||||
for (var i = 0; i < document.getElementsByClassName('refresh').length; i++){
|
||||
document.getElementsByClassName('refresh')[i].style.float = 'right'
|
||||
}
|
||||
|
||||
fetch('/friends/list', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.json()) // Transform the data into json
|
||||
.then(function(resp) {
|
||||
var friendSelectParent = document.getElementById('friendSelect')
|
||||
var keys = [];
|
||||
for(var k in resp) keys.push(k);
|
||||
|
||||
friendSelectParent.appendChild(document.createElement('option'))
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var option = document.createElement("option")
|
||||
var name = resp[keys[i]]['name'] || ""
|
||||
option.value = keys[i]
|
||||
if (name.length == 0){
|
||||
option.text = keys[i]
|
||||
}
|
||||
else{
|
||||
option.text = name
|
||||
}
|
||||
friendSelectParent.appendChild(option)
|
||||
}
|
||||
})
|
||||
setActiveTab('inbox')
|
||||
|
||||
setInterval(function(){mailPing()}, 10000)
|
||||
mailPing()
|
||||
window.inboxInterval = setInterval(function(){refreshPms(true)}, 3000)
|
||||
refreshPms(true)
|
||||
|
||||
document.addEventListener("visibilitychange", function() {
|
||||
if (document.visibilityState === 'visible') {
|
||||
refreshPms()
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
let mailStream = function(){
|
||||
var streamSource = new EventSourcePolyfill('/mail/streaminbox', {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}
|
||||
})
|
||||
streamSource.onmessage = function(e){
|
||||
console.debug(e.data)
|
||||
}
|
||||
}
|
||||
mailStream()
|
||||
*/
|
@ -1,109 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
This file handles the mail interface
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var sendbutton = document.getElementById('sendMail')
|
||||
messageContent = document.getElementById('draftText')
|
||||
to = document.getElementById('draftID')
|
||||
subject = document.getElementById('draftSubject')
|
||||
friendPicker = document.getElementById('friendSelect')
|
||||
|
||||
function utf8Length(s) {
|
||||
var size = encodeURIComponent(s).match(/%[89ABab]/g);
|
||||
return s.length + (size ? size.length : 0);
|
||||
}
|
||||
|
||||
function padString(string_data, round_nearest_byte_exponent = 3){
|
||||
if (utf8Length(string_data) === 0){
|
||||
string_data += '0'
|
||||
}
|
||||
let round_size = 10 ** round_nearest_byte_exponent
|
||||
while (utf8Length(string_data) % round_size > 0){
|
||||
string_data += '0'
|
||||
}
|
||||
return string_data
|
||||
}
|
||||
|
||||
function sendMail(toData, message, subject){
|
||||
let meta = {'subject': subject}
|
||||
|
||||
if (document.getElementById('messagePaddingSetting').checked){
|
||||
message = padString(message)
|
||||
}
|
||||
|
||||
if (document.getElementById('mailSignatureSetting').value !== false){
|
||||
message += "\n"
|
||||
message += document.getElementById('mailSignatureSetting').value
|
||||
}
|
||||
|
||||
postData = {'message': message, 'to': toData, 'type': 'pm', 'encrypt': true, 'meta': JSON.stringify(meta)}
|
||||
postData.forward = document.getElementById('forwardSecrecySetting').checked
|
||||
postData = JSON.stringify(postData)
|
||||
sendForm.style.display = 'none'
|
||||
|
||||
fetch('/insertblock', {
|
||||
method: 'POST',
|
||||
body: postData,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}}).then(function(resp){
|
||||
if (!resp.ok){
|
||||
PNotify.error({
|
||||
text: 'Malformed input'
|
||||
})
|
||||
sendForm.style.display = 'block'
|
||||
throw new Error("Malformed input in sendmail")
|
||||
}
|
||||
return resp
|
||||
})
|
||||
.then((resp) => resp.text()) // Transform the data into text
|
||||
.then(function(data) {
|
||||
sendForm.style.display = 'block'
|
||||
PNotify.success({
|
||||
text: 'Queued for sending!',
|
||||
delay: 3500,
|
||||
mouseReset: false
|
||||
})
|
||||
to.value = subject.value = messageContent.value = ""
|
||||
friendPicker.value = ""
|
||||
subject.value = ""
|
||||
})
|
||||
}
|
||||
|
||||
var friendPicker = document.getElementById('friendSelect')
|
||||
friendPicker.onchange = function(){
|
||||
to.value = friendPicker.value
|
||||
}
|
||||
|
||||
sendForm.onsubmit = function(){
|
||||
let getInstances = function(string, word) {
|
||||
return string.split(word).length - 1;
|
||||
}
|
||||
|
||||
if(getInstances(to.value, '-') != 15){
|
||||
if (to.value.length != 56 && to.value.length != 52){
|
||||
PNotify.error({
|
||||
text: 'User ID is not valid'
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
sendMail(to.value, messageContent.value, subject.value)
|
||||
return false
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Load human readable public keys into a cache
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
function loadHumanReadableToCache(key){
|
||||
fetch('/getHumanReadable/' + key, {
|
||||
headers: {
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text())
|
||||
.then(function(resp) {
|
||||
humanReadableCache[key] = resp
|
||||
})
|
||||
}
|
||||
|
||||
function setHumanReadableValue(el, key){
|
||||
if (typeof humanReadableCache[key] != 'undefined'){
|
||||
el.innerText = humanReadableCache[key].split('-').slice(0,3).join('-')
|
||||
return
|
||||
}
|
||||
else{
|
||||
loadHumanReadableToCache(key)
|
||||
setTimeout(function(){setHumanReadableValue(el, key)}, 100)
|
||||
return
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
Onionr - Private P2P Communication
|
||||
|
||||
Handle mail settings
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
var notificationSetting = document.getElementById('notificationSetting')
|
||||
var friendOnlyNotification = document.getElementById('strangersNotification')
|
||||
var notificationSound = document.getElementById('notificationSound')
|
||||
var sigSetting = document.getElementById('mailSignatureSetting')
|
||||
|
||||
document.getElementById('forwardSecrecySetting').onchange = function(e){
|
||||
postData = JSON.stringify({"default_forward_secrecy": e.target.checked})
|
||||
fetch('/config/set/mail', {
|
||||
method: 'POST',
|
||||
body: postData,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}})
|
||||
.then((resp) => resp.text())
|
||||
.then(function(data) {
|
||||
mailSettings['forwardSecrecy'] = document.getElementById('forwardSecrecySetting').checked
|
||||
PNotify.success({
|
||||
text: 'Successfully toggled default forward secrecy'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
notificationSound.onchange = function(e){
|
||||
var postData = JSON.stringify({"notificationSound": e.target.checked})
|
||||
fetch('/config/set/mail', {
|
||||
method: 'POST',
|
||||
body: postData,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}})
|
||||
.then(function(data) {
|
||||
mailSettings['notificationSound'] = notificationSound.checked
|
||||
PNotify.success({
|
||||
text: 'Successfully notification sound'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
friendOnlyNotification.onchange = function(e){
|
||||
var postData = JSON.stringify({"strangersNotification": e.target.checked})
|
||||
fetch('/config/set/mail', {
|
||||
method: 'POST',
|
||||
body: postData,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}})
|
||||
.then(function(data) {
|
||||
mailSettings['strangersNotification'] = friendOnlyNotification.checked
|
||||
PNotify.success({
|
||||
text: 'Successfully toggled notifications from strangers'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
notificationSetting.onchange = function(e){
|
||||
var notificationSettings = document.getElementsByClassName('notificationSetting')
|
||||
if (e.target.checked){
|
||||
for (i = 0; i < notificationSettings.length; i++){
|
||||
notificationSettings[i].style.display = "flex"
|
||||
}
|
||||
}
|
||||
else{
|
||||
for (i = 0; i < notificationSettings.length; i++){
|
||||
notificationSettings[i].style.display = "none"
|
||||
}
|
||||
}
|
||||
var postData = JSON.stringify({"notificationSetting": e.target.checked})
|
||||
fetch('/config/set/mail', {
|
||||
method: 'POST',
|
||||
body: postData,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}})
|
||||
.then(function(data) {
|
||||
mailSettings['notificationSetting'] = notificationSetting.checked
|
||||
PNotify.success({
|
||||
text: 'Successfully toggled default mail notifications'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
sigSetting.onchange = function(){
|
||||
var postData = JSON.stringify({"signature": sigSetting.value})
|
||||
fetch('/config/set/mail', {
|
||||
method: 'POST',
|
||||
body: postData,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"token": webpass
|
||||
}})
|
||||
.then(function(data) {
|
||||
mailSettings['signature'] = sigSetting.value
|
||||
PNotify.success({
|
||||
text: 'Set mail signature'
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user