Refactoring the API with metaclass and decorator

This commit is contained in:
Dmitri Bogomolov 2018-10-22 20:06:12 +03:00
parent 1571176082
commit 45b0659e4c
Signed by untrusted user: g1itch
GPG Key ID: 720A756F18DEED13
1 changed files with 413 additions and 642 deletions

1055
src/api.py
View File

@ -1,12 +1,15 @@
# Copyright (c) 2012-2016 Jonathan Warren
# Copyright (c) 2012-2020 The Bitmessage developers
# pylint: disable=too-many-lines,no-self-use,unused-variable,unused-argument
""" """
This is not what you run to run the Bitmessage API. Instead, enable the API This is not what you run to run the Bitmessage API. Instead, enable the API
( https://bitmessage.org/wiki/API ) and optionally enable daemon mode ( https://bitmessage.org/wiki/API ) and optionally enable daemon mode
( https://bitmessage.org/wiki/Daemon ) then run bitmessagemain.py. ( https://bitmessage.org/wiki/Daemon ) then run bitmessagemain.py.
""" """
# Copyright (c) 2012-2016 Jonathan Warren
# Copyright (c) 2012-2020 The Bitmessage developers
# pylint: disable=too-many-lines,no-self-use,unused-variable,unused-argument
import base64 import base64
import ConfigParser
import errno import errno
import hashlib import hashlib
import json import json
@ -27,7 +30,6 @@ import queues
import shared import shared
import shutdown import shutdown
import state import state
import threads
from addresses import ( from addresses import (
addBMIfNotPresent, addBMIfNotPresent,
calculateInventoryHash, calculateInventoryHash,
@ -44,6 +46,7 @@ from network.threads import StoppableThread
from version import softwareVersion from version import softwareVersion
str_chan = '[chan]' str_chan = '[chan]'
str_broadcast_subscribers = '[Broadcast subscribers]'
class APIError(Exception): class APIError(Exception):
@ -134,15 +137,47 @@ class singleAPI(StoppableThread):
se.serve_forever() se.serve_forever()
class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): class CommandHandler(type):
""" """
This is one of several classes that constitute the API The metaclass for `BMRPCDispatcher` which fills _handlers dict by
methods decorated with @command
"""
def __new__(mcs, name, bases, namespace):
# pylint: disable=protected-access
result = super(CommandHandler, mcs).__new__(
mcs, name, bases, namespace)
result._handlers = {}
for func in namespace.values():
try:
for alias in getattr(func, '_cmd'):
result._handlers[alias] = func
except AttributeError:
pass
return result
This class was written by Vaibhav Bhatia.
Modified by Jonathan Warren (Atheros). class command(object):
http://code.activestate.com/recipes/501148-xmlrpc-serverclient-which-does-cookie-handling-and/ """Decorator for API command method"""
""" def __init__(self, *aliases):
# pylint: disable=too-many-public-methods self.aliases = aliases
def __call__(self, func):
def wrapper(*args):
# return json.dumps(func(*args), indent=4)
return func(*args)
# pylint: disable=protected-access
wrapper._cmd = self.aliases
return wrapper
# This is one of several classes that constitute the API
# This class was written by Vaibhav Bhatia.
# Modified by Jonathan Warren (Atheros).
# Further modified by the Bitmessage developers
# http://code.activestate.com/recipes/501148
class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler, object):
"""The main API handler"""
__metaclass__ = CommandHandler
def do_POST(self): def do_POST(self):
""" """
@ -215,7 +250,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
if 'Authorization' in self.headers: if 'Authorization' in self.headers:
# handle Basic authentication # handle Basic authentication
_, encstr = self.headers.get('Authorization').split() encstr = self.headers.get('Authorization').split()[1]
emailid, password = encstr.decode('base64').split(':') emailid, password = encstr.decode('base64').split(':')
return ( return (
emailid == BMConfigParser().get( emailid == BMConfigParser().get(
@ -281,66 +316,46 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
# Request Handlers # Request Handlers
def HandleListAddresses(self, method): @command('listAddresses', 'listAddresses2')
"""Handle a request to list addresses""" def HandleListAddresses(self):
data = '{"addresses":[' data = []
for addressInKeysFile in BMConfigParser().addresses(): for address in BMConfigParser().addresses():
status, addressVersionNumber, streamNumber, hash01 = decodeAddress( streamNumber = decodeAddress(address)[2]
addressInKeysFile) label = BMConfigParser().get(address, 'label')
if len(data) > 20: if self._method == 'listAddresses2':
data += ','
if BMConfigParser().has_option(addressInKeysFile, 'chan'):
chan = BMConfigParser().getboolean(addressInKeysFile, 'chan')
else:
chan = False
label = BMConfigParser().get(addressInKeysFile, 'label')
if method == 'listAddresses2':
label = base64.b64encode(label) label = base64.b64encode(label)
data += json.dumps({ data.append({
'label': label, 'label': label,
'address': addressInKeysFile, 'address': address,
'stream': streamNumber, 'stream': streamNumber,
'enabled': 'enabled': BMConfigParser().safeGetBoolean(address, 'enabled'),
BMConfigParser().getboolean(addressInKeysFile, 'enabled'), 'chan': BMConfigParser().safeGetBoolean(address, 'chan')
'chan': chan })
}, indent=4, separators=(',', ': ')) return {'addresses': data}
data += ']}'
return data
def HandleListAddressBookEntries(self, params): # the listAddressbook alias should be removed eventually.
"""Handle a request to list address book entries""" @command('listAddressBookEntries', 'listAddressbook')
def HandleListAddressBookEntries(self, label=None):
if len(params) == 1: queryreturn = sqlQuery(
label, = params "SELECT label, address from addressbook WHERE label = ?",
label = self._decode(label, "base64") label
queryreturn = sqlQuery( ) if label else sqlQuery("SELECT label, address from addressbook")
"SELECT label, address from addressbook WHERE label = ?", data = []
label) for label, address in queryreturn:
elif len(params) > 1:
raise APIError(0, "Too many paremeters, max 1")
else:
queryreturn = sqlQuery("SELECT label, address from addressbook")
data = '{"addresses":['
for row in queryreturn:
label, address = row
label = shared.fixPotentiallyInvalidUTF8Data(label) label = shared.fixPotentiallyInvalidUTF8Data(label)
if len(data) > 20: data.append({
data += ','
data += json.dumps({
'label': base64.b64encode(label), 'label': base64.b64encode(label),
'address': address}, indent=4, separators=(',', ': ')) 'address': address
data += ']}' })
return data return {'addresses': data}
def HandleAddAddressBookEntry(self, params): # the addAddressbook alias should be deleted eventually.
"""Handle a request to add an address book entry""" @command('addAddressBookEntry', 'addAddressbook')
def HandleAddAddressBookEntry(self, address, label):
if len(params) != 2:
raise APIError(0, "I need label and address")
address, label = params
label = self._decode(label, "base64") label = self._decode(label, "base64")
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
self._verifyAddress(address) self._verifyAddress(address)
# TODO: add unique together constraint in the table
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT address FROM addressbook WHERE address=?", address) "SELECT address FROM addressbook WHERE address=?", address)
if queryreturn != []: if queryreturn != []:
@ -353,12 +368,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
queues.UISignalQueue.put(('rerenderAddressBook', '')) queues.UISignalQueue.put(('rerenderAddressBook', ''))
return "Added address %s to address book" % address return "Added address %s to address book" % address
def HandleDeleteAddressBookEntry(self, params): # the deleteAddressbook alias should be deleted eventually.
"""Handle a request to delete an address book entry""" @command('deleteAddressBookEntry', 'deleteAddressbook')
def HandleDeleteAddressBookEntry(self, address):
if len(params) != 1:
raise APIError(0, "I need an address")
address, = params
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
self._verifyAddress(address) self._verifyAddress(address)
sqlExecute('DELETE FROM addressbook WHERE address=?', address) sqlExecute('DELETE FROM addressbook WHERE address=?', address)
@ -367,49 +379,35 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
queues.UISignalQueue.put(('rerenderAddressBook', '')) queues.UISignalQueue.put(('rerenderAddressBook', ''))
return "Deleted address book entry for %s if it existed" % address return "Deleted address book entry for %s if it existed" % address
def HandleCreateRandomAddress(self, params): @command('createRandomAddress')
def HandleCreateRandomAddress(
self, label, eighteenByteRipe=False, totalDifficulty=0,
smallMessageDifficulty=0
):
"""Handle a request to create a random address""" """Handle a request to create a random address"""
if not params: nonceTrialsPerByte = BMConfigParser().get(
raise APIError(0, 'I need parameters!') 'bitmessagesettings', 'defaultnoncetrialsperbyte'
) if not totalDifficulty else int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes'
) if not smallMessageDifficulty else int(
defaults.networkDefaultPayloadLengthExtraBytes *
smallMessageDifficulty)
elif len(params) == 1: if not isinstance(eighteenByteRipe, bool):
label, = params raise APIError(
eighteenByteRipe = False 23, 'Bool expected in eighteenByteRipe, saw %s instead' %
nonceTrialsPerByte = BMConfigParser().get( type(eighteenByteRipe))
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 2:
label, eighteenByteRipe = params
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 3:
label, eighteenByteRipe, totalDifficulty = params
nonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 4:
label, eighteenByteRipe, totalDifficulty, \
smallMessageDifficulty = params
nonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = int(
defaults.networkDefaultPayloadLengthExtraBytes *
smallMessageDifficulty)
else:
raise APIError(0, 'Too many parameters!')
label = self._decode(label, "base64") label = self._decode(label, "base64")
try: try:
unicode(label, 'utf-8') unicode(label, 'utf-8')
except BaseException: except UnicodeDecodeError:
raise APIError(17, 'Label is not valid UTF-8 data.') raise APIError(17, 'Label is not valid UTF-8 data.')
queues.apiAddressGeneratorReturnQueue.queue.clear() queues.apiAddressGeneratorReturnQueue.queue.clear()
# FIXME hard coded stream no
streamNumberForAddress = 1 streamNumberForAddress = 1
queues.addressGeneratorQueue.put(( queues.addressGeneratorQueue.put((
'createRandomAddress', 4, streamNumberForAddress, label, 1, "", 'createRandomAddress', 4, streamNumberForAddress, label, 1, "",
@ -417,81 +415,26 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
)) ))
return queues.apiAddressGeneratorReturnQueue.get() return queues.apiAddressGeneratorReturnQueue.get()
def HandleCreateDeterministicAddresses(self, params): @command('createDeterministicAddresses')
def HandleCreateDeterministicAddresses(
self, passphrase, numberOfAddresses=1, addressVersionNumber=0,
streamNumber=0, eighteenByteRipe=False, totalDifficulty=0,
smallMessageDifficulty=0
):
"""Handle a request to create a deterministic address""" """Handle a request to create a deterministic address"""
# pylint: disable=too-many-branches, too-many-statements # pylint: disable=too-many-branches, too-many-statements
if not params: nonceTrialsPerByte = BMConfigParser().get(
raise APIError(0, 'I need parameters!') 'bitmessagesettings', 'defaultnoncetrialsperbyte'
) if not totalDifficulty else int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes'
) if not smallMessageDifficulty else int(
defaults.networkDefaultPayloadLengthExtraBytes *
smallMessageDifficulty)
elif len(params) == 1:
passphrase, = params
numberOfAddresses = 1
addressVersionNumber = 0
streamNumber = 0
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 2:
passphrase, numberOfAddresses = params
addressVersionNumber = 0
streamNumber = 0
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 3:
passphrase, numberOfAddresses, addressVersionNumber = params
streamNumber = 0
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 4:
passphrase, numberOfAddresses, addressVersionNumber, \
streamNumber = params
eighteenByteRipe = False
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 5:
passphrase, numberOfAddresses, addressVersionNumber, \
streamNumber, eighteenByteRipe = params
nonceTrialsPerByte = BMConfigParser().get(
'bitmessagesettings', 'defaultnoncetrialsperbyte')
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 6:
passphrase, numberOfAddresses, addressVersionNumber, \
streamNumber, eighteenByteRipe, totalDifficulty = params
nonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = BMConfigParser().get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
elif len(params) == 7:
passphrase, numberOfAddresses, addressVersionNumber, \
streamNumber, eighteenByteRipe, totalDifficulty, \
smallMessageDifficulty = params
nonceTrialsPerByte = int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte *
totalDifficulty)
payloadLengthExtraBytes = int(
defaults.networkDefaultPayloadLengthExtraBytes *
smallMessageDifficulty)
else:
raise APIError(0, 'Too many parameters!')
if not passphrase: if not passphrase:
raise APIError(1, 'The specified passphrase is blank.') raise APIError(1, 'The specified passphrase is blank.')
if not isinstance(eighteenByteRipe, bool): if not isinstance(eighteenByteRipe, bool):
@ -502,13 +445,13 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
# 0 means "just use the proper addressVersionNumber" # 0 means "just use the proper addressVersionNumber"
if addressVersionNumber == 0: if addressVersionNumber == 0:
addressVersionNumber = 4 addressVersionNumber = 4
if addressVersionNumber != 3 and addressVersionNumber != 4: if addressVersionNumber not in (3, 4):
raise APIError( raise APIError(
2, 'The address version number currently must be 3, 4, or 0' 2, 'The address version number currently must be 3, 4, or 0'
' (which means auto-select). %i isn\'t supported.' % ' (which means auto-select). %i isn\'t supported.' %
addressVersionNumber) addressVersionNumber)
if streamNumber == 0: # 0 means "just use the most available stream" if streamNumber == 0: # 0 means "just use the most available stream"
streamNumber = 1 streamNumber = 1 # FIXME hard coded stream no
if streamNumber != 1: if streamNumber != 1:
raise APIError( raise APIError(
3, 'The stream number must be 1 (or 0 which means' 3, 'The stream number must be 1 (or 0 which means'
@ -533,27 +476,20 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
'unused API address', numberOfAddresses, passphrase, 'unused API address', numberOfAddresses, passphrase,
eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes
)) ))
data = '{"addresses":['
queueReturn = queues.apiAddressGeneratorReturnQueue.get()
for item in queueReturn:
if len(data) > 20:
data += ','
data += "\"" + item + "\""
data += ']}'
return data
def HandleGetDeterministicAddress(self, params): return {'addresses': queues.apiAddressGeneratorReturnQueue.get()}
@command('getDeterministicAddress')
def HandleGetDeterministicAddress(
self, passphrase, addressVersionNumber, streamNumber):
"""Handle a request to get a deterministic address""" """Handle a request to get a deterministic address"""
if len(params) != 3:
raise APIError(0, 'I need exactly 3 parameters.')
passphrase, addressVersionNumber, streamNumber = params
numberOfAddresses = 1 numberOfAddresses = 1
eighteenByteRipe = False eighteenByteRipe = False
if not passphrase: if not passphrase:
raise APIError(1, 'The specified passphrase is blank.') raise APIError(1, 'The specified passphrase is blank.')
passphrase = self._decode(passphrase, "base64") passphrase = self._decode(passphrase, "base64")
if addressVersionNumber != 3 and addressVersionNumber != 4: if addressVersionNumber not in (3, 4):
raise APIError( raise APIError(
2, 'The address version number currently must be 3 or 4. %i' 2, 'The address version number currently must be 3 or 4. %i'
' isn\'t supported.' % addressVersionNumber) ' isn\'t supported.' % addressVersionNumber)
@ -571,16 +507,11 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
)) ))
return queues.apiAddressGeneratorReturnQueue.get() return queues.apiAddressGeneratorReturnQueue.get()
def HandleCreateChan(self, params): @command('createChan')
def HandleCreateChan(self, passphrase):
"""Handle a request to create a chan""" """Handle a request to create a chan"""
if not params:
raise APIError(0, 'I need parameters.')
elif len(params) == 1:
passphrase, = params
passphrase = self._decode(passphrase, "base64") passphrase = self._decode(passphrase, "base64")
if not passphrase: if not passphrase:
raise APIError(1, 'The specified passphrase is blank.') raise APIError(1, 'The specified passphrase is blank.')
# It would be nice to make the label the passphrase but it is # It would be nice to make the label the passphrase but it is
@ -588,7 +519,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
try: try:
unicode(passphrase, 'utf-8') unicode(passphrase, 'utf-8')
label = str_chan + ' ' + passphrase label = str_chan + ' ' + passphrase
except BaseException: except UnicodeDecodeError:
label = str_chan + ' ' + repr(passphrase) label = str_chan + ' ' + repr(passphrase)
addressVersionNumber = 4 addressVersionNumber = 4
@ -601,18 +532,15 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
passphrase, True passphrase, True
)) ))
queueReturn = queues.apiAddressGeneratorReturnQueue.get() queueReturn = queues.apiAddressGeneratorReturnQueue.get()
if not queueReturn: try:
return queueReturn[0]
except IndexError:
raise APIError(24, 'Chan address is already present.') raise APIError(24, 'Chan address is already present.')
address = queueReturn[0]
return address
def HandleJoinChan(self, params): @command('joinChan')
def HandleJoinChan(self, passphrase, suppliedAddress):
"""Handle a request to join a chan""" """Handle a request to join a chan"""
if len(params) < 2:
raise APIError(0, 'I need two parameters.')
elif len(params) == 2:
passphrase, suppliedAddress = params
passphrase = self._decode(passphrase, "base64") passphrase = self._decode(passphrase, "base64")
if not passphrase: if not passphrase:
raise APIError(1, 'The specified passphrase is blank.') raise APIError(1, 'The specified passphrase is blank.')
@ -621,84 +549,73 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
try: try:
unicode(passphrase, 'utf-8') unicode(passphrase, 'utf-8')
label = str_chan + ' ' + passphrase label = str_chan + ' ' + passphrase
except BaseException: except UnicodeDecodeError:
label = str_chan + ' ' + repr(passphrase) label = str_chan + ' ' + repr(passphrase)
status, addressVersionNumber, streamNumber, toRipe = (
self._verifyAddress(suppliedAddress)) self._verifyAddress(suppliedAddress)
suppliedAddress = addBMIfNotPresent(suppliedAddress) suppliedAddress = addBMIfNotPresent(suppliedAddress)
queues.apiAddressGeneratorReturnQueue.queue.clear() queues.apiAddressGeneratorReturnQueue.queue.clear()
queues.addressGeneratorQueue.put(( queues.addressGeneratorQueue.put((
'joinChan', suppliedAddress, label, passphrase, True 'joinChan', suppliedAddress, label, passphrase, True
)) ))
addressGeneratorReturnValue = \ queueReturn = queues.apiAddressGeneratorReturnQueue.get()
queues.apiAddressGeneratorReturnQueue.get() try:
if queueReturn[0] == 'chan name does not match address':
if addressGeneratorReturnValue[0] == \ raise APIError(18, 'Chan name does not match address.')
'chan name does not match address': except IndexError:
raise APIError(18, 'Chan name does not match address.')
if not addressGeneratorReturnValue:
raise APIError(24, 'Chan address is already present.') raise APIError(24, 'Chan address is already present.')
return "success" return "success"
def HandleLeaveChan(self, params): @command('leaveChan')
def HandleLeaveChan(self, address):
"""Handle a request to leave a chan""" """Handle a request to leave a chan"""
self._verifyAddress(address)
if not params:
raise APIError(0, 'I need parameters.')
elif len(params) == 1:
address, = params
status, addressVersionNumber, streamNumber, toRipe = (
self._verifyAddress(address))
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
if not BMConfigParser().has_section(address):
raise APIError(
13, 'Could not find this address in your keys.dat file.')
if not BMConfigParser().safeGetBoolean(address, 'chan'): if not BMConfigParser().safeGetBoolean(address, 'chan'):
raise APIError( raise APIError(
25, 'Specified address is not a chan address.' 25, 'Specified address is not a chan address.'
' Use deleteAddress API call instead.') ' Use deleteAddress API call instead.')
BMConfigParser().remove_section(address) try:
with open(state.appdata + 'keys.dat', 'wb') as configfile: BMConfigParser().remove_section(address)
BMConfigParser().write(configfile) except ConfigParser.NoSectionError:
return 'success'
def HandleDeleteAddress(self, params):
"""Handle a request to delete an address"""
if not params:
raise APIError(0, 'I need parameters.')
elif len(params) == 1:
address, = params
status, addressVersionNumber, streamNumber, toRipe = (
self._verifyAddress(address))
address = addBMIfNotPresent(address)
if not BMConfigParser().has_section(address):
raise APIError( raise APIError(
13, 'Could not find this address in your keys.dat file.') 13, 'Could not find this address in your keys.dat file.')
BMConfigParser().remove_section(address) BMConfigParser().save()
with open(state.appdata + 'keys.dat', 'wb') as configfile: queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
BMConfigParser().write(configfile) queues.UISignalQueue.put(('rerenderMessagelistToLabels', ''))
return "success"
@command('deleteAddress')
def HandleDeleteAddress(self, address):
"""Handle a request to delete an address"""
self._verifyAddress(address)
address = addBMIfNotPresent(address)
try:
BMConfigParser().remove_section(address)
except ConfigParser.NoSectionError:
raise APIError(
13, 'Could not find this address in your keys.dat file.')
BMConfigParser().save()
queues.UISignalQueue.put(('writeNewAddressToTable', ('', '', ''))) queues.UISignalQueue.put(('writeNewAddressToTable', ('', '', '')))
shared.reloadMyAddressHashes() shared.reloadMyAddressHashes()
return 'success' return "success"
def HandleGetAllInboxMessages(self, params): @command('getAllInboxMessages')
def HandleGetAllInboxMessages(self):
"""Handle a request to get all inbox messages""" """Handle a request to get all inbox messages"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, received, message," "SELECT msgid, toaddress, fromaddress, subject, received, message,"
" encodingtype, read FROM inbox where folder='inbox'" " encodingtype, read FROM inbox WHERE folder='inbox'"
" ORDER BY received" " ORDER BY received"
) )
data = '{"inboxMessages":[' data = []
for row in queryreturn: for (msgid, toAddress, fromAddress, subject, received, message,
msgid, toAddress, fromAddress, subject, received, message, \ encodingtype, read) in queryreturn:
encodingtype, read = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject) subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message) message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25: data.append({
data += ','
data += json.dumps({
'msgid': hexlify(msgid), 'msgid': hexlify(msgid),
'toAddress': toAddress, 'toAddress': toAddress,
'fromAddress': fromAddress, 'fromAddress': fromAddress,
@ -706,35 +623,27 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
'message': base64.b64encode(message), 'message': base64.b64encode(message),
'encodingType': encodingtype, 'encodingType': encodingtype,
'receivedTime': received, 'receivedTime': received,
'read': read}, indent=4, separators=(',', ': ')) 'read': read
data += ']}' })
return data return {"inboxMessages": data}
def HandleGetAllInboxMessageIds(self, params): @command('getAllInboxMessageIds', 'getAllInboxMessageIDs')
def HandleGetAllInboxMessageIds(self):
"""Handle a request to get all inbox message IDs""" """Handle a request to get all inbox message IDs"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid FROM inbox where folder='inbox' ORDER BY received") "SELECT msgid FROM inbox where folder='inbox' ORDER BY received")
data = '{"inboxMessageIds":['
for row in queryreturn:
msgid = row[0]
if len(data) > 25:
data += ','
data += json.dumps(
{'msgid': hexlify(msgid)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleGetInboxMessageById(self, params): return {"inboxMessageIds": [
{'msgid': hexlify(msgid)} for msgid, in queryreturn
]}
@command('getInboxMessageById', 'getInboxMessageByID')
def HandleGetInboxMessageById(self, hid, readStatus=None):
"""Handle a request to get an inbox messsage by ID""" """Handle a request to get an inbox messsage by ID"""
if not params: msgid = self._decode(hid, "hex")
raise APIError(0, 'I need parameters!') if readStatus is not None:
elif len(params) == 1:
msgid = self._decode(params[0], "hex")
elif len(params) >= 2:
msgid = self._decode(params[0], "hex")
readStatus = params[1]
if not isinstance(readStatus, bool): if not isinstance(readStatus, bool):
raise APIError( raise APIError(
23, 'Bool expected in readStatus, saw %s instead.' % 23, 'Bool expected in readStatus, saw %s instead.' %
@ -742,34 +651,39 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT read FROM inbox WHERE msgid=?", msgid) "SELECT read FROM inbox WHERE msgid=?", msgid)
# UPDATE is slow, only update if status is different # UPDATE is slow, only update if status is different
if queryreturn != [] and (queryreturn[0][0] == 1) != readStatus: try:
sqlExecute( if (queryreturn[0][0] == 1) != readStatus:
"UPDATE inbox set read = ? WHERE msgid=?", sqlExecute(
readStatus, msgid) "UPDATE inbox set read = ? WHERE msgid=?",
queues.UISignalQueue.put(('changedInboxUnread', None)) readStatus, msgid)
queues.UISignalQueue.put(('changedInboxUnread', None))
except IndexError:
pass
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, received, message," "SELECT toaddress, fromaddress, subject, received, message,"
" encodingtype, read FROM inbox WHERE msgid=?", msgid " encodingtype, read FROM inbox WHERE msgid=?", msgid
) )
data = '{"inboxMessage":[' data = []
for row in queryreturn: try:
msgid, toAddress, fromAddress, subject, received, message, \ (toAddress, fromAddress, subject, received, message, encodingtype,
encodingtype, read = row read) = queryreturn[0]
subject = shared.fixPotentiallyInvalidUTF8Data(subject) except IndexError:
message = shared.fixPotentiallyInvalidUTF8Data(message) return # FIXME inconsistent
data += json.dumps({ subject = shared.fixPotentiallyInvalidUTF8Data(subject)
'msgid': hexlify(msgid), message = shared.fixPotentiallyInvalidUTF8Data(message)
'toAddress': toAddress, return {"inboxMessage": [{
'fromAddress': fromAddress, 'msgid': hid,
'subject': base64.b64encode(subject), 'toAddress': toAddress,
'message': base64.b64encode(message), 'fromAddress': fromAddress,
'encodingType': encodingtype, 'subject': base64.b64encode(subject),
'receivedTime': received, 'message': base64.b64encode(message),
'read': read}, indent=4, separators=(',', ': ')) 'encodingType': encodingtype,
data += ']}' 'receivedTime': received,
return data 'read': read
}]}
def HandleGetAllSentMessages(self, params): @command('getAllSentMessages')
def HandleGetAllSentMessages(self):
"""Handle a request to get all sent messages""" """Handle a request to get all sent messages"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
@ -777,15 +691,12 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
" message, encodingtype, status, ackdata FROM sent" " message, encodingtype, status, ackdata FROM sent"
" WHERE folder='sent' ORDER BY lastactiontime" " WHERE folder='sent' ORDER BY lastactiontime"
) )
data = '{"sentMessages":[' data = []
for row in queryreturn: for (msgid, toAddress, fromAddress, subject, lastactiontime, message,
msgid, toAddress, fromAddress, subject, lastactiontime, message, \ encodingtype, status, ackdata) in queryreturn:
encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject) subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message) message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25: data.append({
data += ','
data += json.dumps({
'msgid': hexlify(msgid), 'msgid': hexlify(msgid),
'toAddress': toAddress, 'toAddress': toAddress,
'fromAddress': fromAddress, 'fromAddress': fromAddress,
@ -794,107 +705,92 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
'encodingType': encodingtype, 'encodingType': encodingtype,
'lastActionTime': lastactiontime, 'lastActionTime': lastactiontime,
'status': status, 'status': status,
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': ')) 'ackData': hexlify(ackdata)
data += ']}' })
return data return {"sentMessages": data}
def HandleGetAllSentMessageIds(self, params): @command('getAllSentMessageIds', 'getAllSentMessageIDs')
def HandleGetAllSentMessageIds(self):
"""Handle a request to get all sent message IDs""" """Handle a request to get all sent message IDs"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid FROM sent where folder='sent'" "SELECT msgid FROM sent WHERE folder='sent'"
" ORDER BY lastactiontime" " ORDER BY lastactiontime"
) )
data = '{"sentMessageIds":[' return {"sentMessageIds": [
for row in queryreturn: {'msgid': hexlify(msgid)} for msgid, in queryreturn
msgid = row[0] ]}
if len(data) > 25:
data += ','
data += json.dumps(
{'msgid': hexlify(msgid)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleInboxMessagesByReceiver(self, params): # after some time getInboxMessagesByAddress should be removed
@command('getInboxMessagesByReceiver', 'getInboxMessagesByAddress')
def HandleInboxMessagesByReceiver(self, toAddress):
"""Handle a request to get inbox messages by receiver""" """Handle a request to get inbox messages by receiver"""
if not params:
raise APIError(0, 'I need parameters!')
toAddress = params[0]
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, received, message," "SELECT msgid, toaddress, fromaddress, subject, received, message,"
" encodingtype FROM inbox WHERE folder='inbox' AND toAddress=?", " encodingtype FROM inbox WHERE folder='inbox' AND toAddress=?",
toAddress) toAddress)
data = '{"inboxMessages":[' data = []
for row in queryreturn: for (msgid, toAddress, fromAddress, subject, received, message,
msgid, toAddress, fromAddress, subject, received, message, \ encodingtype) in queryreturn:
encodingtype = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject) subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message) message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25: data.append({
data += ','
data += json.dumps({
'msgid': hexlify(msgid), 'msgid': hexlify(msgid),
'toAddress': toAddress, 'toAddress': toAddress,
'fromAddress': fromAddress, 'fromAddress': fromAddress,
'subject': base64.b64encode(subject), 'subject': base64.b64encode(subject),
'message': base64.b64encode(message), 'message': base64.b64encode(message),
'encodingType': encodingtype, 'encodingType': encodingtype,
'receivedTime': received}, indent=4, separators=(',', ': ')) 'receivedTime': received
data += ']}' })
return data return {"inboxMessages": data}
def HandleGetSentMessageById(self, params): @command('getSentMessageById', 'getSentMessageByID')
def HandleGetSentMessageById(self, hid):
"""Handle a request to get a sent message by ID""" """Handle a request to get a sent message by ID"""
if not params: msgid = self._decode(hid, "hex")
raise APIError(0, 'I need parameters!')
msgid = self._decode(params[0], "hex")
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime," "SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
" message, encodingtype, status, ackdata FROM sent WHERE msgid=?", " message, encodingtype, status, ackdata FROM sent WHERE msgid=?",
msgid msgid
) )
data = '{"sentMessage":[' try:
for row in queryreturn: (toAddress, fromAddress, subject, lastactiontime, message,
msgid, toAddress, fromAddress, subject, lastactiontime, message, \ encodingtype, status, ackdata) = queryreturn[0]
encodingtype, status, ackdata = row except IndexError:
subject = shared.fixPotentiallyInvalidUTF8Data(subject) return # FIXME inconsistent
message = shared.fixPotentiallyInvalidUTF8Data(message) subject = shared.fixPotentiallyInvalidUTF8Data(subject)
data += json.dumps({ message = shared.fixPotentiallyInvalidUTF8Data(message)
'msgid': hexlify(msgid), return {"sentMessage": [{
'toAddress': toAddress, 'msgid': hid,
'fromAddress': fromAddress, 'toAddress': toAddress,
'subject': base64.b64encode(subject), 'fromAddress': fromAddress,
'message': base64.b64encode(message), 'subject': base64.b64encode(subject),
'encodingType': encodingtype, 'message': base64.b64encode(message),
'lastActionTime': lastactiontime, 'encodingType': encodingtype,
'status': status, 'lastActionTime': lastactiontime,
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': ')) 'status': status,
data += ']}' 'ackData': hexlify(ackdata)
return data }]}
def HandleGetSentMessagesByAddress(self, params): @command('getSentMessagesByAddress', 'getSentMessagesBySender')
def HandleGetSentMessagesByAddress(self, fromAddress):
"""Handle a request to get sent messages by address""" """Handle a request to get sent messages by address"""
if not params:
raise APIError(0, 'I need parameters!')
fromAddress = params[0]
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime," "SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
" message, encodingtype, status, ackdata FROM sent" " message, encodingtype, status, ackdata FROM sent"
" WHERE folder='sent' AND fromAddress=? ORDER BY lastactiontime", " WHERE folder='sent' AND fromAddress=? ORDER BY lastactiontime",
fromAddress fromAddress
) )
data = '{"sentMessages":[' data = []
for row in queryreturn: for (msgid, toAddress, fromAddress, subject, lastactiontime, message,
msgid, toAddress, fromAddress, subject, lastactiontime, message, \ encodingtype, status, ackdata) in queryreturn:
encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject) subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message) message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25: data.append({
data += ','
data += json.dumps({
'msgid': hexlify(msgid), 'msgid': hexlify(msgid),
'toAddress': toAddress, 'toAddress': toAddress,
'fromAddress': fromAddress, 'fromAddress': fromAddress,
@ -903,91 +799,72 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
'encodingType': encodingtype, 'encodingType': encodingtype,
'lastActionTime': lastactiontime, 'lastActionTime': lastactiontime,
'status': status, 'status': status,
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': ')) 'ackData': hexlify(ackdata)
data += ']}' })
return data return {"sentMessages": data}
def HandleGetSentMessagesByAckData(self, params): @command('getSentMessageByAckData')
def HandleGetSentMessagesByAckData(self, ackData):
"""Handle a request to get sent messages by ack data""" """Handle a request to get sent messages by ack data"""
if not params: ackData = self._decode(ackData, "hex")
raise APIError(0, 'I need parameters!')
ackData = self._decode(params[0], "hex")
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime," "SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
" message, encodingtype, status, ackdata FROM sent" " message, encodingtype, status, ackdata FROM sent"
" WHERE ackdata=?", ackData " WHERE ackdata=?", ackData
) )
data = '{"sentMessage":['
for row in queryreturn:
msgid, toAddress, fromAddress, subject, lastactiontime, message, \
encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
data += json.dumps({
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'lastActionTime': lastactiontime,
'status': status,
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleTrashMessage(self, params): try:
(msgid, toAddress, fromAddress, subject, lastactiontime,
message, encodingtype, status, ackdata) = queryreturn[0]
except IndexError:
return # FIXME inconsistent
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
return {"sentMessage": [{
'msgid': hexlify(msgid),
'toAddress': toAddress,
'fromAddress': fromAddress,
'subject': base64.b64encode(subject),
'message': base64.b64encode(message),
'encodingType': encodingtype,
'lastActionTime': lastactiontime,
'status': status,
'ackData': hexlify(ackdata)
}]}
@command('trashMessage')
def HandleTrashMessage(self, msgid):
"""Handle a request to trash a message by ID""" """Handle a request to trash a message by ID"""
msgid = self._decode(msgid, "hex")
if not params:
raise APIError(0, 'I need parameters!')
msgid = self._decode(params[0], "hex")
# Trash if in inbox table # Trash if in inbox table
helper_inbox.trash(msgid) helper_inbox.trash(msgid)
# Trash if in sent table # Trash if in sent table
sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid) sqlExecute("UPDATE sent SET folder='trash' WHERE msgid=?", msgid)
return 'Trashed message (assuming message existed).' return 'Trashed message (assuming message existed).'
def HandleTrashInboxMessage(self, params): @command('trashInboxMessage')
def HandleTrashInboxMessage(self, msgid):
"""Handle a request to trash an inbox message by ID""" """Handle a request to trash an inbox message by ID"""
msgid = self._decode(msgid, "hex")
if not params:
raise APIError(0, 'I need parameters!')
msgid = self._decode(params[0], "hex")
helper_inbox.trash(msgid) helper_inbox.trash(msgid)
return 'Trashed inbox message (assuming message existed).' return 'Trashed inbox message (assuming message existed).'
def HandleTrashSentMessage(self, params): @command('trashSentMessage')
def HandleTrashSentMessage(self, msgid):
"""Handle a request to trash a sent message by ID""" """Handle a request to trash a sent message by ID"""
msgid = self._decode(msgid, "hex")
if not params:
raise APIError(0, 'I need parameters!')
msgid = self._decode(params[0], "hex")
sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid) sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid)
return 'Trashed sent message (assuming message existed).' return 'Trashed sent message (assuming message existed).'
def HandleSendMessage(self, params): # pylint: disable=too-many-locals @command('sendMessage')
def HandleSendMessage(
self, toAddress, fromAddress, subject, message,
encodingType=2, TTL=4 * 24 * 60 * 60
):
"""Handle a request to send a message""" """Handle a request to send a message"""
if not params: if encodingType not in (2, 3):
raise APIError(0, 'I need parameters!')
elif len(params) == 4:
toAddress, fromAddress, subject, message = params
encodingType = 2
TTL = 4 * 24 * 60 * 60
elif len(params) == 5:
toAddress, fromAddress, subject, message, encodingType = params
TTL = 4 * 24 * 60 * 60
elif len(params) == 6:
toAddress, fromAddress, subject, message, encodingType, TTL = \
params
if encodingType not in [2, 3]:
raise APIError(6, 'The encoding type must be 2 or 3.') raise APIError(6, 'The encoding type must be 2 or 3.')
subject = self._decode(subject, "base64") subject = self._decode(subject, "base64")
message = self._decode(message, "base64") message = self._decode(message, "base64")
@ -999,8 +876,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
TTL = 28 * 24 * 60 * 60 TTL = 28 * 24 * 60 * 60
toAddress = addBMIfNotPresent(toAddress) toAddress = addBMIfNotPresent(toAddress)
fromAddress = addBMIfNotPresent(fromAddress) fromAddress = addBMIfNotPresent(fromAddress)
status, addressVersionNumber, streamNumber, toRipe = \ streamNumber, toRipe = self._verifyAddress(toAddress)[2:]
self._verifyAddress(toAddress)
self._verifyAddress(fromAddress) self._verifyAddress(fromAddress)
try: try:
fromAddressEnabled = BMConfigParser().getboolean( fromAddressEnabled = BMConfigParser().getboolean(
@ -1035,34 +911,24 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
toLabel = '' toLabel = ''
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT label FROM addressbook WHERE address=?", toAddress) "SELECT label FROM addressbook WHERE address=?", toAddress)
if queryreturn != []: try:
for row in queryreturn: toLabel, = queryreturn[0][0]
toLabel, = row except IndexError:
pass
queues.UISignalQueue.put(('displayNewSentMessage', ( queues.UISignalQueue.put(('displayNewSentMessage', (
toAddress, toLabel, fromAddress, subject, message, ackdata))) toAddress, toLabel, fromAddress, subject, message, ackdata)))
queues.workerQueue.put(('sendmessage', toAddress)) queues.workerQueue.put(('sendmessage', toAddress))
return hexlify(ackdata) return hexlify(ackdata)
def HandleSendBroadcast(self, params): @command('sendBroadcast')
def HandleSendBroadcast(
self, fromAddress, subject, message, encodingType=2,
TTL=4 * 24 * 60 * 60):
"""Handle a request to send a broadcast message""" """Handle a request to send a broadcast message"""
if not params: if encodingType not in (2, 3):
raise APIError(0, 'I need parameters!')
if len(params) == 3:
fromAddress, subject, message = params
encodingType = 2
TTL = 4 * 24 * 60 * 60
elif len(params) == 4:
fromAddress, subject, message, encodingType = params
TTL = 4 * 24 * 60 * 60
elif len(params) == 5:
fromAddress, subject, message, encodingType, TTL = params
if encodingType not in [2, 3]:
raise APIError(6, 'The encoding type must be 2 or 3.') raise APIError(6, 'The encoding type must be 2 or 3.')
subject = self._decode(subject, "base64") subject = self._decode(subject, "base64")
@ -1082,7 +948,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
13, 'could not find your fromAddress in the keys.dat file.') 13, 'could not find your fromAddress in the keys.dat file.')
streamNumber = decodeAddress(fromAddress)[2] streamNumber = decodeAddress(fromAddress)[2]
ackdata = genAckPayload(streamNumber, 0) ackdata = genAckPayload(streamNumber, 0)
toAddress = '[Broadcast subscribers]' toAddress = str_broadcast_subscribers
ripe = '' ripe = ''
t = ('', t = ('',
@ -1102,55 +968,45 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
TTL) TTL)
helper_sent.insert(t) helper_sent.insert(t)
toLabel = '[Broadcast subscribers]' toLabel = str_broadcast_subscribers
queues.UISignalQueue.put(('displayNewSentMessage', ( queues.UISignalQueue.put(('displayNewSentMessage', (
toAddress, toLabel, fromAddress, subject, message, ackdata))) toAddress, toLabel, fromAddress, subject, message, ackdata)))
queues.workerQueue.put(('sendbroadcast', '')) queues.workerQueue.put(('sendbroadcast', ''))
return hexlify(ackdata) return hexlify(ackdata)
def HandleGetStatus(self, params): @command('getStatus')
def HandleGetStatus(self, ackdata):
"""Handle a request to get the status of a sent message""" """Handle a request to get the status of a sent message"""
if len(params) != 1:
raise APIError(0, 'I need one parameter!')
ackdata, = params
if len(ackdata) < 76: if len(ackdata) < 76:
# The length of ackData should be at least 38 bytes (76 hex digits) # The length of ackData should be at least 38 bytes (76 hex digits)
raise APIError(15, 'Invalid ackData object size.') raise APIError(15, 'Invalid ackData object size.')
ackdata = self._decode(ackdata, "hex") ackdata = self._decode(ackdata, "hex")
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT status FROM sent where ackdata=?", ackdata) "SELECT status FROM sent where ackdata=?", ackdata)
if queryreturn == []: try:
return queryreturn[0][0]
except IndexError:
return 'notfound' return 'notfound'
for row in queryreturn:
status, = row
return status
def HandleAddSubscription(self, params): @command('addSubscription')
def HandleAddSubscription(self, address, label=''):
"""Handle a request to add a subscription""" """Handle a request to add a subscription"""
if not params: if label:
raise APIError(0, 'I need parameters!')
if len(params) == 1:
address, = params
label = ''
if len(params) == 2:
address, label = params
label = self._decode(label, "base64") label = self._decode(label, "base64")
try: try:
unicode(label, 'utf-8') unicode(label, 'utf-8')
except BaseException: except UnicodeDecodeError:
raise APIError(17, 'Label is not valid UTF-8 data.') raise APIError(17, 'Label is not valid UTF-8 data.')
if len(params) > 2:
raise APIError(0, 'I need either 1 or 2 parameters!')
address = addBMIfNotPresent(address)
self._verifyAddress(address) self._verifyAddress(address)
address = addBMIfNotPresent(address)
# First we must check to see if the address is already in the # First we must check to see if the address is already in the
# subscriptions list. # subscriptions list.
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT * FROM subscriptions WHERE address=?", address) "SELECT * FROM subscriptions WHERE address=?", address)
if queryreturn != []: if queryreturn:
raise APIError(16, 'You are already subscribed to that address.') raise APIError(16, 'You are already subscribed to that address.')
sqlExecute( sqlExecute(
"INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) "INSERT INTO subscriptions VALUES (?,?,?)", label, address, True)
@ -1159,36 +1015,37 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
queues.UISignalQueue.put(('rerenderSubscriptions', '')) queues.UISignalQueue.put(('rerenderSubscriptions', ''))
return 'Added subscription.' return 'Added subscription.'
def HandleDeleteSubscription(self, params): @command('deleteSubscription')
def HandleDeleteSubscription(self, address):
"""Handle a request to delete a subscription""" """Handle a request to delete a subscription"""
if len(params) != 1:
raise APIError(0, 'I need 1 parameter!')
address, = params
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
sqlExecute('''DELETE FROM subscriptions WHERE address=?''', address) sqlExecute("DELETE FROM subscriptions WHERE address=?", address)
shared.reloadBroadcastSendersForWhichImWatching() shared.reloadBroadcastSendersForWhichImWatching()
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
queues.UISignalQueue.put(('rerenderSubscriptions', '')) queues.UISignalQueue.put(('rerenderSubscriptions', ''))
return 'Deleted subscription if it existed.' return 'Deleted subscription if it existed.'
def ListSubscriptions(self, params): @command('listSubscriptions')
def ListSubscriptions(self):
"""Handle a request to list susbcriptions""" """Handle a request to list susbcriptions"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT label, address, enabled FROM subscriptions") "SELECT label, address, enabled FROM subscriptions")
data = {'subscriptions': []} data = []
for row in queryreturn: for label, address, enabled in queryreturn:
label, address, enabled = row
label = shared.fixPotentiallyInvalidUTF8Data(label) label = shared.fixPotentiallyInvalidUTF8Data(label)
data['subscriptions'].append({ data.append({
'label': base64.b64encode(label), 'label': base64.b64encode(label),
'address': address, 'address': address,
'enabled': enabled == 1 'enabled': enabled == 1
}) })
return json.dumps(data, indent=4, separators=(',', ': ')) return {'subscriptions': data}
def HandleDisseminatePreEncryptedMsg(self, params): @command('disseminatePreEncryptedMsg')
def HandleDisseminatePreEncryptedMsg(
self, encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte,
requiredPayloadLengthExtraBytes):
"""Handle a request to disseminate an encrypted message""" """Handle a request to disseminate an encrypted message"""
# The device issuing this command to PyBitmessage supplies a msg # The device issuing this command to PyBitmessage supplies a msg
@ -1196,42 +1053,28 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
# to be done. PyBitmessage accepts this msg object and sends it out # to be done. PyBitmessage accepts this msg object and sends it out
# to the rest of the Bitmessage network as if it had generated # to the rest of the Bitmessage network as if it had generated
# the message itself. Please do not yet add this to the api doc. # the message itself. Please do not yet add this to the api doc.
if len(params) != 3:
raise APIError(0, 'I need 3 parameter!')
encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, \
requiredPayloadLengthExtraBytes = params
encryptedPayload = self._decode(encryptedPayload, "hex") encryptedPayload = self._decode(encryptedPayload, "hex")
# Let us do the POW and attach it to the front # Let us do the POW and attach it to the front
target = 2**64 / ( target = 2**64 / ((
( len(encryptedPayload) + requiredPayloadLengthExtraBytes + 8) *
len(encryptedPayload) + requiredPayloadLengthExtraBytes + 8 requiredAverageProofOfWorkNonceTrialsPerByte)
) * requiredAverageProofOfWorkNonceTrialsPerByte logger.info(
'(For msg message via API) Doing proof of work. Total required'
' difficulty: %s\nRequired small message difficulty: %s',
float(requiredAverageProofOfWorkNonceTrialsPerByte) /
defaults.networkDefaultProofOfWorkNonceTrialsPerByte,
float(requiredPayloadLengthExtraBytes) /
defaults.networkDefaultPayloadLengthExtraBytes,
) )
with threads.printLock:
print(
'(For msg message via API) Doing proof of work.'
'Total required difficulty:',
float(
requiredAverageProofOfWorkNonceTrialsPerByte
) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte,
'Required small message difficulty:',
float(
requiredPayloadLengthExtraBytes
) / defaults.networkDefaultPayloadLengthExtraBytes,
)
powStartTime = time.time() powStartTime = time.time()
initialHash = hashlib.sha512(encryptedPayload).digest() initialHash = hashlib.sha512(encryptedPayload).digest()
trialValue, nonce = proofofwork.run(target, initialHash) trialValue, nonce = proofofwork.run(target, initialHash)
with threads.printLock: logger.info(
print '(For msg message via API) Found proof of work', trialValue, 'Nonce:', nonce '(For msg message via API) Found proof of work %s\nNonce: %s\n'
try: 'POW took %s seconds. %s nonce trials per second.',
print( trialValue, nonce, int(time.time() - powStartTime),
'POW took', int(time.time() - powStartTime), nonce / (time.time() - powStartTime)
'seconds.', nonce / (time.time() - powStartTime), )
'nonce trials per second.',
)
except BaseException:
pass
encryptedPayload = pack('>Q', nonce) + encryptedPayload encryptedPayload = pack('>Q', nonce) + encryptedPayload
toStreamNumber = decodeVarint(encryptedPayload[16:26])[0] toStreamNumber = decodeVarint(encryptedPayload[16:26])[0]
inventoryHash = calculateInventoryHash(encryptedPayload) inventoryHash = calculateInventoryHash(encryptedPayload)
@ -1241,21 +1084,21 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
objectType, toStreamNumber, encryptedPayload, objectType, toStreamNumber, encryptedPayload,
int(time.time()) + TTL, '' int(time.time()) + TTL, ''
) )
with threads.printLock: logger.info(
print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', hexlify(inventoryHash) 'Broadcasting inv for msg(API disseminatePreEncryptedMsg'
' command): %s', hexlify(inventoryHash))
queues.invQueue.put((toStreamNumber, inventoryHash)) queues.invQueue.put((toStreamNumber, inventoryHash))
def HandleTrashSentMessageByAckDAta(self, params): @command('trashSentMessageByAckData')
def HandleTrashSentMessageByAckDAta(self, ackdata):
"""Handle a request to trash a sent message by ackdata""" """Handle a request to trash a sent message by ackdata"""
# This API method should only be used when msgid is not available # This API method should only be used when msgid is not available
if not params: ackdata = self._decode(ackdata, "hex")
raise APIError(0, 'I need parameters!')
ackdata = self._decode(params[0], "hex")
sqlExecute("UPDATE sent SET folder='trash' WHERE ackdata=?", ackdata) sqlExecute("UPDATE sent SET folder='trash' WHERE ackdata=?", ackdata)
return 'Trashed sent message (assuming message existed).' return 'Trashed sent message (assuming message existed).'
def HandleDissimatePubKey(self, params): @command('disseminatePubkey')
def HandleDissimatePubKey(self, payload):
"""Handle a request to disseminate a public key""" """Handle a request to disseminate a public key"""
# The device issuing this command to PyBitmessage supplies a pubkey # The device issuing this command to PyBitmessage supplies a pubkey
@ -1263,19 +1106,19 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
# PyBitmessage accepts this pubkey object and sends it out to the rest # PyBitmessage accepts this pubkey object and sends it out to the rest
# of the Bitmessage network as if it had generated the pubkey object # of the Bitmessage network as if it had generated the pubkey object
# itself. Please do not yet add this to the api doc. # itself. Please do not yet add this to the api doc.
if len(params) != 1:
raise APIError(0, 'I need 1 parameter!')
payload, = params
payload = self._decode(payload, "hex") payload = self._decode(payload, "hex")
# Let us do the POW # Let us do the POW
target = 2 ** 64 / (( target = 2 ** 64 / ((
len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8 len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8
) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) ) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)
print '(For pubkey message via API) Doing proof of work...' logger.info('(For pubkey message via API) Doing proof of work...')
initialHash = hashlib.sha512(payload).digest() initialHash = hashlib.sha512(payload).digest()
trialValue, nonce = proofofwork.run(target, initialHash) trialValue, nonce = proofofwork.run(target, initialHash)
print '(For pubkey message via API) Found proof of work', trialValue, 'Nonce:', nonce logger.info(
'(For pubkey message via API) Found proof of work %s Nonce: %s',
trialValue, nonce
)
payload = pack('>Q', nonce) + payload payload = pack('>Q', nonce) + payload
pubkeyReadPosition = 8 # bypass the nonce pubkeyReadPosition = 8 # bypass the nonce
@ -1284,8 +1127,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
pubkeyReadPosition += 8 pubkeyReadPosition += 8
else: else:
pubkeyReadPosition += 4 pubkeyReadPosition += 4
addressVersion, addressVersionLength = decodeVarint( addressVersionLength = decodeVarint(
payload[pubkeyReadPosition:pubkeyReadPosition + 10]) payload[pubkeyReadPosition:pubkeyReadPosition + 10])[1]
pubkeyReadPosition += addressVersionLength pubkeyReadPosition += addressVersionLength
pubkeyStreamNumber = decodeVarint( pubkeyStreamNumber = decodeVarint(
payload[pubkeyReadPosition:pubkeyReadPosition + 10])[0] payload[pubkeyReadPosition:pubkeyReadPosition + 10])[0]
@ -1295,19 +1138,19 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
Inventory()[inventoryHash] = ( Inventory()[inventoryHash] = (
objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, '' objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, ''
) )
with threads.printLock: logger.info(
print 'broadcasting inv within API command disseminatePubkey with hash:', hexlify(inventoryHash) 'broadcasting inv within API command disseminatePubkey with'
' hash: %s', hexlify(inventoryHash))
queues.invQueue.put((pubkeyStreamNumber, inventoryHash)) queues.invQueue.put((pubkeyStreamNumber, inventoryHash))
def HandleGetMessageDataByDestinationHash(self, params): @command(
'getMessageDataByDestinationHash', 'getMessageDataByDestinationTag')
def HandleGetMessageDataByDestinationHash(self, requestedHash):
"""Handle a request to get message data by destination hash""" """Handle a request to get message data by destination hash"""
# Method will eventually be used by a particular Android app to # Method will eventually be used by a particular Android app to
# select relevant messages. Do not yet add this to the api # select relevant messages. Do not yet add this to the api
# doc. # doc.
if len(params) != 1:
raise APIError(0, 'I need 1 parameter!')
requestedHash, = params
if len(requestedHash) != 32: if len(requestedHash) != 32:
raise APIError( raise APIError(
19, 'The length of hash should be 32 bytes (encoded in hex' 19, 'The length of hash should be 32 bytes (encoded in hex'
@ -1321,8 +1164,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
"SELECT hash, payload FROM inventory WHERE tag = ''" "SELECT hash, payload FROM inventory WHERE tag = ''"
" and objecttype = 2") " and objecttype = 2")
with SqlBulkExecute() as sql: with SqlBulkExecute() as sql:
for row in queryreturn: for hash01, payload in queryreturn:
hash01, payload = row
readPosition = 16 # Nonce length + time length readPosition = 16 # Nonce length + time length
# Stream Number length # Stream Number length
readPosition += decodeVarint( readPosition += decodeVarint(
@ -1332,17 +1174,12 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT payload FROM inventory WHERE tag = ?", requestedHash) "SELECT payload FROM inventory WHERE tag = ?", requestedHash)
data = '{"receivedMessageDatas":[' return {"receivedMessageDatas": [
for row in queryreturn: {'data': hexlify(payload)} for payload, in queryreturn
payload, = row ]}
if len(data) > 25:
data += ','
data += json.dumps(
{'data': hexlify(payload)}, indent=4, separators=(',', ': '))
data += ']}'
return data
def HandleClientStatus(self, params): @command('clientStatus')
def HandleClientStatus(self):
"""Handle a request to get the status of the client""" """Handle a request to get the status of the client"""
connections_num = len(network.stats.connectedHostsList()) connections_num = len(network.stats.connectedHostsList())
@ -1352,7 +1189,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
networkStatus = 'connectedAndReceivingIncomingConnections' networkStatus = 'connectedAndReceivingIncomingConnections'
else: else:
networkStatus = 'connectedButHaveNotReceivedIncomingConnections' networkStatus = 'connectedButHaveNotReceivedIncomingConnections'
return json.dumps({ return {
'networkConnections': connections_num, 'networkConnections': connections_num,
'numberOfMessagesProcessed': state.numberOfMessagesProcessed, 'numberOfMessagesProcessed': state.numberOfMessagesProcessed,
'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed, 'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed,
@ -1360,123 +1197,57 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
'networkStatus': networkStatus, 'networkStatus': networkStatus,
'softwareName': 'PyBitmessage', 'softwareName': 'PyBitmessage',
'softwareVersion': softwareVersion 'softwareVersion': softwareVersion
}, indent=4, separators=(',', ': ')) }
def HandleDecodeAddress(self, params): @command('decodeAddress')
def HandleDecodeAddress(self, address):
"""Handle a request to decode an address""" """Handle a request to decode an address"""
# Return a meaningful decoding of an address. # Return a meaningful decoding of an address.
if len(params) != 1:
raise APIError(0, 'I need 1 parameter!')
address, = params
status, addressVersion, streamNumber, ripe = decodeAddress(address) status, addressVersion, streamNumber, ripe = decodeAddress(address)
return json.dumps({ return {
'status': status, 'status': status,
'addressVersion': addressVersion, 'addressVersion': addressVersion,
'streamNumber': streamNumber, 'streamNumber': streamNumber,
'ripe': base64.b64encode(ripe) 'ripe': base64.b64encode(ripe)
}, indent=4, separators=(',', ': ')) }
def HandleHelloWorld(self, params): @command('helloWorld')
def HandleHelloWorld(self, a, b):
"""Test two string params""" """Test two string params"""
a, b = params
return a + '-' + b return a + '-' + b
def HandleAdd(self, params): @command('add')
def HandleAdd(self, a, b):
"""Test two numeric params""" """Test two numeric params"""
a, b = params
return a + b return a + b
def HandleStatusBar(self, params): @command('statusBar')
def HandleStatusBar(self, message):
"""Handle a request to update the status bar""" """Handle a request to update the status bar"""
message, = params
queues.UISignalQueue.put(('updateStatusBar', message)) queues.UISignalQueue.put(('updateStatusBar', message))
def HandleDeleteAndVacuum(self, params): @command('deleteAndVacuum')
def HandleDeleteAndVacuum(self):
"""Handle a request to run the deleteandvacuum stored procedure""" """Handle a request to run the deleteandvacuum stored procedure"""
sqlStoredProcedure('deleteandvacuume')
return 'done'
if not params: @command('shutdown')
sqlStoredProcedure('deleteandvacuume') def HandleShutdown(self):
return 'done'
return None
def HandleShutdown(self, params):
"""Handle a request to shutdown the node""" """Handle a request to shutdown the node"""
# backward compatible trick because False == 0 is True
if not params: state.shutdown = False
# backward compatible trick because False == 0 is True return 'done'
state.shutdown = False
return 'done'
return None
handlers = {}
handlers['helloWorld'] = HandleHelloWorld
handlers['add'] = HandleAdd
handlers['statusBar'] = HandleStatusBar
handlers['listAddresses'] = HandleListAddresses
handlers['listAddressBookEntries'] = HandleListAddressBookEntries
# the listAddressbook alias should be removed eventually.
handlers['listAddressbook'] = HandleListAddressBookEntries
handlers['addAddressBookEntry'] = HandleAddAddressBookEntry
# the addAddressbook alias should be deleted eventually.
handlers['addAddressbook'] = HandleAddAddressBookEntry
handlers['deleteAddressBookEntry'] = HandleDeleteAddressBookEntry
# The deleteAddressbook alias should be deleted eventually.
handlers['deleteAddressbook'] = HandleDeleteAddressBookEntry
handlers['createRandomAddress'] = HandleCreateRandomAddress
handlers['createDeterministicAddresses'] = \
HandleCreateDeterministicAddresses
handlers['getDeterministicAddress'] = HandleGetDeterministicAddress
handlers['createChan'] = HandleCreateChan
handlers['joinChan'] = HandleJoinChan
handlers['leaveChan'] = HandleLeaveChan
handlers['deleteAddress'] = HandleDeleteAddress
handlers['getAllInboxMessages'] = HandleGetAllInboxMessages
handlers['getAllInboxMessageIds'] = HandleGetAllInboxMessageIds
handlers['getAllInboxMessageIDs'] = HandleGetAllInboxMessageIds
handlers['getInboxMessageById'] = HandleGetInboxMessageById
handlers['getInboxMessageByID'] = HandleGetInboxMessageById
handlers['getAllSentMessages'] = HandleGetAllSentMessages
handlers['getAllSentMessageIds'] = HandleGetAllSentMessageIds
handlers['getAllSentMessageIDs'] = HandleGetAllSentMessageIds
handlers['getInboxMessagesByReceiver'] = HandleInboxMessagesByReceiver
# after some time getInboxMessagesByAddress should be removed
handlers['getInboxMessagesByAddress'] = HandleInboxMessagesByReceiver
handlers['getSentMessageById'] = HandleGetSentMessageById
handlers['getSentMessageByID'] = HandleGetSentMessageById
handlers['getSentMessagesByAddress'] = HandleGetSentMessagesByAddress
handlers['getSentMessagesBySender'] = HandleGetSentMessagesByAddress
handlers['getSentMessageByAckData'] = HandleGetSentMessagesByAckData
handlers['trashMessage'] = HandleTrashMessage
handlers['trashInboxMessage'] = HandleTrashInboxMessage
handlers['trashSentMessage'] = HandleTrashSentMessage
handlers['trashSentMessageByAckData'] = HandleTrashSentMessageByAckDAta
handlers['sendMessage'] = HandleSendMessage
handlers['sendBroadcast'] = HandleSendBroadcast
handlers['getStatus'] = HandleGetStatus
handlers['addSubscription'] = HandleAddSubscription
handlers['deleteSubscription'] = HandleDeleteSubscription
handlers['listSubscriptions'] = ListSubscriptions
handlers['disseminatePreEncryptedMsg'] = HandleDisseminatePreEncryptedMsg
handlers['disseminatePubkey'] = HandleDissimatePubKey
handlers['getMessageDataByDestinationHash'] = \
HandleGetMessageDataByDestinationHash
handlers['getMessageDataByDestinationTag'] = \
HandleGetMessageDataByDestinationHash
handlers['clientStatus'] = HandleClientStatus
handlers['decodeAddress'] = HandleDecodeAddress
handlers['deleteAndVacuum'] = HandleDeleteAndVacuum
handlers['shutdown'] = HandleShutdown
def _handle_request(self, method, params): def _handle_request(self, method, params):
if method not in self.handlers: try:
# pylint: disable=attribute-defined-outside-init
self._method = method
return self._handlers[method](self, *params)
except KeyError:
raise APIError(20, 'Invalid method: %s' % method) raise APIError(20, 'Invalid method: %s' % method)
result = self.handlers[method](self, params) finally:
state.last_api_response = time.time() state.last_api_response = time.time()
return result
def _dispatch(self, method, params): def _dispatch(self, method, params):
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init