2017-12-26 07:25:29 +00:00
'''
2018-11-04 16:06:24 +00:00
Onionr - P2P Anonymous Storage Network
2018-01-14 08:48:23 +00:00
This file handles all incoming http requests to the client , using Flask
'''
'''
2017-12-26 07:25:29 +00:00
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 / > .
'''
2019-01-20 02:23:26 +00:00
from gevent . pywsgi import WSGIServer , WSGIHandler
from gevent import Timeout
2019-02-08 18:53:28 +00:00
import flask , cgi , uuid
2018-07-27 05:48:22 +00:00
from flask import request , Response , abort , send_from_directory
2019-02-21 20:25:45 +00:00
import sys , random , threading , hmac , base64 , time , os , json , socket
2018-10-27 03:29:25 +00:00
import core
2018-05-19 22:11:51 +00:00
from onionrblockapi import Block
2019-02-21 20:25:45 +00:00
import onionrutils , onionrexceptions , onionrcrypto , blockimporter , onionrevents as events , logger , config
2019-03-02 06:22:59 +00:00
import httpapi
from httpapi import friendsapi , simplecache
2019-02-21 20:25:45 +00:00
import onionr
2018-05-19 22:11:51 +00:00
2019-01-20 02:23:26 +00:00
class FDSafeHandler ( WSGIHandler ) :
2019-02-11 22:36:43 +00:00
''' Our WSGI handler. Doesn ' t do much non-default except timeouts '''
2019-01-20 02:23:26 +00:00
def handle ( self ) :
2019-01-22 20:15:02 +00:00
timeout = Timeout ( 60 , exception = Exception )
2019-01-20 02:23:26 +00:00
timeout . start ( )
try :
WSGIHandler . handle ( self )
except Timeout as ex :
raise
2019-01-08 05:51:39 +00:00
def setBindIP ( filePath ) :
''' Set a random localhost IP to a specified file (intended for private or public API localhost IPs) '''
2019-02-24 00:11:43 +00:00
if config . get ( ' general.random_bind_ip ' , True ) :
hostOctets = [ str ( 127 ) , str ( random . randint ( 0x02 , 0xFF ) ) , str ( random . randint ( 0x02 , 0xFF ) ) , str ( random . randint ( 0x02 , 0xFF ) ) ]
data = ' . ' . join ( hostOctets )
# Try to bind IP. Some platforms like Mac block non normal 127.x.x.x
s = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
try :
s . bind ( ( data , 0 ) )
except OSError :
# if mac/non-bindable, show warning and default to 127.0.0.1
logger . warn ( ' Your platform appears to not support random local host addresses 127.x.x.x. Falling back to 127.0.0.1. ' )
data = ' 127.0.0.1 '
s . close ( )
else :
2019-01-08 07:25:56 +00:00
data = ' 127.0.0.1 '
2019-01-08 05:51:39 +00:00
with open ( filePath , ' w ' ) as bindFile :
bindFile . write ( data )
return data
class PublicAPI :
'''
The new client api server , isolated from the public api
'''
def __init__ ( self , clientAPI ) :
assert isinstance ( clientAPI , API )
app = flask . Flask ( ' PublicAPI ' )
2018-06-14 04:17:58 +00:00
self . i2pEnabled = config . get ( ' i2p.host ' , False )
2018-11-13 17:07:46 +00:00
self . hideBlocks = [ ] # Blocks to be denied sharing
2019-01-08 05:51:39 +00:00
self . host = setBindIP ( clientAPI . _core . publicApiHostFile )
self . torAdder = clientAPI . _core . hsAddress
self . i2pAdder = clientAPI . _core . i2pAddress
self . bindPort = config . get ( ' client.public.port ' )
2019-02-14 23:48:41 +00:00
self . lastRequest = 0
2019-01-08 05:51:39 +00:00
logger . info ( ' Running public api on %s : %s ' % ( self . host , self . bindPort ) )
2017-12-27 01:13:19 +00:00
2017-12-28 00:18:00 +00:00
@app.before_request
2019-01-08 05:51:39 +00:00
def validateRequest ( ) :
''' Validate request has the correct hostname '''
2019-02-12 05:30:56 +00:00
# If high security level, deny requests to public (HS should be disabled anyway for Tor, but might not be for I2P)
2019-01-17 05:31:56 +00:00
if config . get ( ' general.security_level ' , default = 0 ) > 0 :
abort ( 403 )
2019-01-08 05:51:39 +00:00
if type ( self . torAdder ) is None and type ( self . i2pAdder ) is None :
# abort if our hs addresses are not known
abort ( 403 )
if request . host not in ( self . i2pAdder , self . torAdder ) :
2019-02-12 05:30:56 +00:00
# Disallow connection if wrong HTTP hostname, in order to prevent DNS rebinding attacks
2019-01-08 05:51:39 +00:00
abort ( 403 )
2017-12-28 00:18:00 +00:00
2017-12-27 05:00:02 +00:00
@app.after_request
2019-01-08 05:51:39 +00:00
def sendHeaders ( resp ) :
''' Send api, access control headers '''
2019-02-12 05:30:56 +00:00
resp . headers [ ' Date ' ] = ' Thu, 1 Jan 1970 00:00:00 GMT ' # Clock info is probably useful to attackers. Set to unix epoch, since we can't fully remove the header.
# CSP to prevent XSS. Mainly for client side attacks (if hostname protection could somehow be bypassed)
2018-12-09 17:29:39 +00:00
resp . headers [ " Content-Security-Policy " ] = " default-src ' none ' ; script-src ' none ' ; object-src ' none ' ; style-src data: ' unsafe-inline ' ; img-src data:; media-src ' none ' ; frame-src ' none ' ; font-src ' none ' ; connect-src ' none ' "
2019-02-12 05:30:56 +00:00
# Prevent click jacking
2018-01-26 07:22:48 +00:00
resp . headers [ ' X-Frame-Options ' ] = ' deny '
2019-02-12 05:30:56 +00:00
# No sniff is possibly not needed
2018-01-28 01:53:24 +00:00
resp . headers [ ' X-Content-Type-Options ' ] = " nosniff "
2019-02-12 05:30:56 +00:00
# Network API version
2019-01-08 05:51:39 +00:00
resp . headers [ ' X-API ' ] = onionr . API_VERSION
2019-02-12 05:30:56 +00:00
# Close connections to limit FD use
2019-01-18 05:34:13 +00:00
resp . headers [ ' Connection ' ] = " close "
2019-02-14 23:48:41 +00:00
self . lastRequest = clientAPI . _core . _utils . getRoundedEpoch ( roundS = 5 )
2017-12-27 05:00:02 +00:00
return resp
2018-05-02 06:50:29 +00:00
2018-04-25 06:56:40 +00:00
@app.route ( ' / ' )
def banner ( ) :
2019-02-12 05:30:56 +00:00
# Display a bit of information to people who visit a node address in their browser
2018-04-25 06:56:40 +00:00
try :
with open ( ' static-data/index.html ' , ' r ' ) as html :
2018-12-09 17:29:39 +00:00
resp = Response ( html . read ( ) , mimetype = ' text/html ' )
2018-04-25 06:56:40 +00:00
except FileNotFoundError :
resp = Response ( " " )
return resp
2017-12-27 05:00:02 +00:00
2019-01-08 05:51:39 +00:00
@app.route ( ' /getblocklist ' )
def getBlockList ( ) :
2019-02-12 05:30:56 +00:00
# Provide a list of our blocks, with a date offset
2019-01-16 05:57:47 +00:00
dateAdjust = request . args . get ( ' date ' )
bList = clientAPI . _core . getBlockList ( dateRec = dateAdjust )
2019-01-08 05:51:39 +00:00
for b in self . hideBlocks :
if b in bList :
2019-02-12 05:30:56 +00:00
# Don't share blocks we created if they haven't been *uploaded* yet, makes it harder to find who created a block
2019-01-08 05:51:39 +00:00
bList . remove ( b )
return Response ( ' \n ' . join ( bList ) )
@app.route ( ' /getdata/<name> ' )
def getBlockData ( name ) :
2019-02-12 05:30:56 +00:00
# Share data for a block if we have it
2019-01-08 05:51:39 +00:00
resp = ' '
data = name
if clientAPI . _utils . validateHash ( data ) :
if data not in self . hideBlocks :
2019-01-07 05:50:20 +00:00
if data in clientAPI . _core . getBlockList ( ) :
2019-02-12 05:30:56 +00:00
block = clientAPI . getBlockData ( data , raw = True )
try :
block = block . encode ( )
except AttributeError :
abort ( 404 )
2019-02-07 01:03:31 +00:00
block = clientAPI . _core . _utils . strToBytes ( block )
resp = block
#resp = base64.b64encode(block).decode()
2019-01-08 05:51:39 +00:00
if len ( resp ) == 0 :
abort ( 404 )
resp = " "
2019-02-07 01:03:31 +00:00
return Response ( resp , mimetype = ' application/octet-stream ' )
2018-07-23 07:43:10 +00:00
2019-01-08 05:51:39 +00:00
@app.route ( ' /www/<path:path> ' )
def wwwPublic ( path ) :
2019-02-12 05:30:56 +00:00
# A way to share files directly over your .onion
2019-01-08 05:51:39 +00:00
if not config . get ( " www.public.run " , True ) :
abort ( 403 )
return send_from_directory ( config . get ( ' www.public.path ' , ' static-data/www/public/ ' ) , path )
2018-08-08 19:26:02 +00:00
2019-01-08 05:51:39 +00:00
@app.route ( ' /ping ' )
def ping ( ) :
2019-02-12 05:30:56 +00:00
# Endpoint to test if nodes are up
2019-01-08 05:51:39 +00:00
return Response ( " pong! " )
@app.route ( ' /pex ' )
def peerExchange ( ) :
2019-01-11 22:59:21 +00:00
response = ' , ' . join ( clientAPI . _core . listAdders ( recent = 3600 ) )
2019-01-08 05:51:39 +00:00
if len ( response ) == 0 :
2019-01-11 22:59:21 +00:00
response = ' '
2019-01-08 05:51:39 +00:00
return Response ( response )
@app.route ( ' /announce ' , methods = [ ' post ' ] )
2018-08-08 19:26:02 +00:00
def acceptAnnounce ( ) :
resp = ' failure '
powHash = ' '
randomData = ' '
newNode = ' '
2019-01-08 05:51:39 +00:00
ourAdder = clientAPI . _core . hsAddress . encode ( )
2018-08-08 19:26:02 +00:00
try :
newNode = request . form [ ' node ' ] . encode ( )
except KeyError :
2019-02-28 03:02:44 +00:00
logger . warn ( ' No node specified for upload ' )
2018-08-08 19:26:02 +00:00
pass
else :
try :
randomData = request . form [ ' random ' ]
randomData = base64 . b64decode ( randomData )
except KeyError :
logger . warn ( ' No random data specified for upload ' )
else :
2019-01-08 05:51:39 +00:00
nodes = newNode + clientAPI . _core . hsAddress . encode ( )
nodes = clientAPI . _core . _crypto . blake2bHash ( nodes )
powHash = clientAPI . _core . _crypto . blake2bHash ( randomData + nodes )
2018-08-08 19:26:02 +00:00
try :
powHash = powHash . decode ( )
except AttributeError :
pass
if powHash . startswith ( ' 0000 ' ) :
2019-02-12 19:18:08 +00:00
newNode = clientAPI . _core . _utils . bytesToStr ( newNode )
if clientAPI . _core . _utils . validateID ( newNode ) and not newNode in clientAPI . _core . onionrInst . communicatorInst . newPeers :
clientAPI . _core . onionrInst . communicatorInst . newPeers . append ( newNode )
2018-08-08 19:26:02 +00:00
resp = ' Success '
else :
logger . warn ( newNode . decode ( ) + ' failed to meet POW: ' + powHash )
resp = Response ( resp )
2018-09-08 06:45:33 +00:00
return resp
2018-08-08 19:26:02 +00:00
2019-01-08 05:51:39 +00:00
@app.route ( ' /upload ' , methods = [ ' post ' ] )
def upload ( ) :
2019-02-12 05:30:56 +00:00
''' Accept file uploads. In the future this will be done more often than on creation
to speed up block sync
'''
2019-01-08 05:51:39 +00:00
resp = ' failure '
2018-04-19 01:17:47 +00:00
try :
2019-01-08 05:51:39 +00:00
data = request . form [ ' block ' ]
except KeyError :
logger . warn ( ' No block specified for upload ' )
2018-01-10 03:50:38 +00:00
pass
2018-01-27 21:49:48 +00:00
else :
2019-01-08 05:51:39 +00:00
if sys . getsizeof ( data ) < 100000000 :
try :
if blockimporter . importBlockFromData ( data , clientAPI . _core ) :
resp = ' success '
else :
logger . warn ( ' Error encountered importing uploaded block ' )
except onionrexceptions . BlacklistedBlock :
logger . debug ( ' uploaded block is blacklisted ' )
pass
if resp == ' failure ' :
abort ( 400 )
resp = Response ( resp )
2018-01-02 08:43:29 +00:00
return resp
2018-02-04 03:44:29 +00:00
2019-02-12 05:30:56 +00:00
# Set instances, then startup our public api server
2019-01-08 05:51:39 +00:00
clientAPI . setPublicAPIInstance ( self )
while self . torAdder == ' ' :
clientAPI . _core . refreshFirstStartVars ( )
self . torAdder = clientAPI . _core . hsAddress
2019-02-20 23:12:11 +00:00
time . sleep ( 0.1 )
2019-01-20 02:23:26 +00:00
self . httpServer = WSGIServer ( ( self . host , self . bindPort ) , app , log = None , handler_class = FDSafeHandler )
2019-01-08 05:51:39 +00:00
self . httpServer . serve_forever ( )
2018-02-04 03:44:29 +00:00
2019-01-08 05:51:39 +00:00
class API :
'''
Client HTTP api
'''
2018-02-04 03:44:29 +00:00
2019-01-08 05:51:39 +00:00
callbacks = { ' public ' : { } , ' private ' : { } }
2018-12-09 17:29:39 +00:00
2019-01-08 05:51:39 +00:00
def __init__ ( self , onionrInst , debug , API_VERSION ) :
'''
Initialize the api server , preping variables for later use
2017-12-27 01:13:19 +00:00
2019-01-08 05:51:39 +00:00
This initilization defines all of the API entry points and handlers for the endpoints and errors
This also saves the used host ( random localhost IP address ) to the data folder in host . txt
2018-02-04 03:44:29 +00:00
'''
2018-01-26 07:22:48 +00:00
2019-01-08 05:51:39 +00:00
self . debug = debug
2019-01-20 22:54:04 +00:00
self . _core = onionrInst . onionrCore
2019-01-13 22:20:10 +00:00
self . startTime = self . _core . _utils . getEpoch ( )
2019-01-08 05:51:39 +00:00
self . _crypto = onionrcrypto . OnionrCrypto ( self . _core )
self . _utils = onionrutils . OnionrUtils ( self . _core )
app = flask . Flask ( __name__ )
bindPort = int ( config . get ( ' client.client.port ' , 59496 ) )
self . bindPort = bindPort
2018-05-19 21:32:21 +00:00
2019-02-12 05:30:56 +00:00
# Be extremely mindful of this. These are endpoints available without a password
2019-02-21 20:25:45 +00:00
self . whitelistEndpoints = ( ' site ' , ' www ' , ' onionrhome ' , ' board ' , ' boardContent ' , ' sharedContent ' , ' mail ' , ' mailindex ' , ' friends ' , ' friendsindex ' )
2018-12-26 06:14:05 +00:00
2019-01-08 05:51:39 +00:00
self . clientToken = config . get ( ' client.webpassword ' )
self . timeBypassToken = base64 . b16encode ( os . urandom ( 32 ) ) . decode ( )
2018-05-19 21:32:21 +00:00
2019-01-08 05:51:39 +00:00
self . publicAPI = None # gets set when the thread calls our setter... bad hack but kinda necessary with flask
#threading.Thread(target=PublicAPI, args=(self,)).start()
self . host = setBindIP ( self . _core . privateApiHostFile )
logger . info ( ' Running api on %s : %s ' % ( self . host , self . bindPort ) )
self . httpServer = ' '
2019-01-07 05:50:20 +00:00
2019-02-12 05:30:56 +00:00
self . pluginResponses = { } # Responses for plugin endpoints
2019-01-07 05:50:20 +00:00
self . queueResponse = { }
2019-01-08 05:51:39 +00:00
onionrInst . setClientAPIInst ( self )
2019-02-21 20:25:45 +00:00
app . register_blueprint ( friendsapi . friends )
2019-03-02 06:22:59 +00:00
app . register_blueprint ( simplecache . simplecache )
httpapi . load_plugin_blueprints ( app )
2018-07-30 00:37:12 +00:00
2019-01-08 05:51:39 +00:00
@app.before_request
def validateRequest ( ) :
''' Validate request has set password and is the correct hostname '''
2019-02-12 05:30:56 +00:00
# For the purpose of preventing DNS rebinding attacks
2019-01-08 05:51:39 +00:00
if request . host != ' %s : %s ' % ( self . host , self . bindPort ) :
abort ( 403 )
2018-12-26 06:14:05 +00:00
if request . endpoint in self . whitelistEndpoints :
return
2019-01-08 05:51:39 +00:00
try :
if not hmac . compare_digest ( request . headers [ ' token ' ] , self . clientToken ) :
2019-02-22 21:04:03 +00:00
if not hmac . compare_digest ( request . form [ ' token ' ] , self . clientToken ) :
abort ( 403 )
2019-01-08 05:51:39 +00:00
except KeyError :
2019-02-22 21:04:03 +00:00
if not hmac . compare_digest ( request . form [ ' token ' ] , self . clientToken ) :
abort ( 403 )
2018-07-30 00:37:12 +00:00
2019-01-08 05:51:39 +00:00
@app.after_request
def afterReq ( resp ) :
2019-02-12 05:30:56 +00:00
# Security headers
2019-02-07 18:12:04 +00:00
if request . endpoint == ' site ' :
resp . headers [ ' Content-Security-Policy ' ] = " default-src ' none ' ; style-src data: ' unsafe-inline ' ; img-src data: "
else :
resp . headers [ ' Content-Security-Policy ' ] = " default-src ' none ' ; script-src ' self ' ; object-src ' none ' ; style-src ' self ' ; img-src ' self ' ; media-src ' none ' ; frame-src ' none ' ; font-src ' none ' ; connect-src ' self ' "
2019-01-08 05:51:39 +00:00
resp . headers [ ' X-Frame-Options ' ] = ' deny '
resp . headers [ ' X-Content-Type-Options ' ] = " nosniff "
resp . headers [ ' Server ' ] = ' '
resp . headers [ ' Date ' ] = ' Thu, 1 Jan 1970 00:00:00 GMT ' # Clock info is probably useful to attackers. Set to unix epoch.
2019-01-18 05:34:13 +00:00
resp . headers [ ' Connection ' ] = " close "
2019-01-08 05:51:39 +00:00
return resp
2018-07-30 00:37:12 +00:00
2018-12-26 06:14:05 +00:00
@app.route ( ' /board/ ' , endpoint = ' board ' )
def loadBoard ( ) :
return send_from_directory ( ' static-data/www/board/ ' , " index.html " )
2019-01-30 06:10:29 +00:00
@app.route ( ' /mail/<path:path> ' , endpoint = ' mail ' )
def loadMail ( path ) :
2019-02-01 06:38:12 +00:00
return send_from_directory ( ' static-data/www/mail/ ' , path )
2019-01-30 06:10:29 +00:00
@app.route ( ' /mail/ ' , endpoint = ' mailindex ' )
def loadMailIndex ( ) :
return send_from_directory ( ' static-data/www/mail/ ' , ' index.html ' )
2019-02-21 20:25:45 +00:00
@app.route ( ' /friends/<path:path> ' , endpoint = ' friends ' )
def loadContacts ( path ) :
return send_from_directory ( ' static-data/www/friends/ ' , path )
@app.route ( ' /friends/ ' , endpoint = ' friendsindex ' )
def loadContacts ( ) :
return send_from_directory ( ' static-data/www/friends/ ' , ' index.html ' )
2019-01-30 06:10:29 +00:00
2018-12-26 06:14:05 +00:00
@app.route ( ' /board/<path:path> ' , endpoint = ' boardContent ' )
def boardContent ( path ) :
return send_from_directory ( ' static-data/www/board/ ' , path )
2018-12-27 05:27:46 +00:00
@app.route ( ' /shared/<path:path> ' , endpoint = ' sharedContent ' )
def sharedContent ( path ) :
return send_from_directory ( ' static-data/www/shared/ ' , path )
2018-12-26 06:14:05 +00:00
@app.route ( ' /www/<path:path> ' , endpoint = ' www ' )
def wwwPublic ( path ) :
if not config . get ( " www.private.run " , True ) :
abort ( 403 )
return send_from_directory ( config . get ( ' www.private.path ' , ' static-data/www/private/ ' ) , path )
2019-01-07 05:50:20 +00:00
@app.route ( ' /queueResponseAdd/<name> ' , methods = [ ' post ' ] )
def queueResponseAdd ( name ) :
2019-02-12 05:30:56 +00:00
# Responses from the daemon. TODO: change to direct var access instead of http endpoint
2019-01-07 05:50:20 +00:00
self . queueResponse [ name ] = request . form [ ' data ' ]
return Response ( ' success ' )
@app.route ( ' /queueResponse/<name> ' )
def queueResponse ( name ) :
2019-02-12 05:30:56 +00:00
# Fetch a daemon queue response
2019-01-13 22:20:10 +00:00
resp = ' failure '
2019-01-07 05:50:20 +00:00
try :
2019-01-07 21:09:58 +00:00
resp = self . queueResponse [ name ]
2019-01-07 05:50:20 +00:00
except KeyError :
2019-01-07 21:09:58 +00:00
pass
else :
del self . queueResponse [ name ]
2019-01-07 05:50:20 +00:00
return Response ( resp )
2019-01-08 05:51:39 +00:00
@app.route ( ' /ping ' )
def ping ( ) :
2019-02-12 05:30:56 +00:00
# Used to check if client api is working
2019-01-08 05:51:39 +00:00
return Response ( " pong! " )
2018-07-30 00:37:12 +00:00
2018-12-26 06:14:05 +00:00
@app.route ( ' / ' , endpoint = ' onionrhome ' )
2019-01-08 05:51:39 +00:00
def hello ( ) :
2019-02-12 05:30:56 +00:00
# ui home
2019-01-13 22:20:10 +00:00
return send_from_directory ( ' static-data/www/private/ ' , ' index.html ' )
2018-12-26 06:14:05 +00:00
@app.route ( ' /getblocksbytype/<name> ' )
def getBlocksByType ( name ) :
blocks = self . _core . getBlocksByType ( name )
return Response ( ' , ' . join ( blocks ) )
2019-02-04 23:48:21 +00:00
@app.route ( ' /getblockbody/<name> ' )
def getBlockBodyData ( name ) :
2018-12-26 06:14:05 +00:00
resp = ' '
if self . _core . _utils . validateHash ( name ) :
try :
2019-02-04 23:48:21 +00:00
resp = Block ( name , decrypt = True ) . bcontent
#resp = cgi.escape(Block(name, decrypt=True).bcontent, quote=True)
2018-12-26 06:14:05 +00:00
except TypeError :
pass
else :
abort ( 404 )
return Response ( resp )
2019-02-01 06:38:12 +00:00
@app.route ( ' /getblockdata/<name> ' )
def getData ( name ) :
resp = " "
if self . _core . _utils . validateHash ( name ) :
if name in self . _core . getBlockList ( ) :
try :
resp = self . getBlockData ( name , decrypt = True )
except ValueError :
pass
else :
abort ( 404 )
else :
abort ( 404 )
return Response ( resp )
2018-07-30 00:37:12 +00:00
2019-02-04 23:48:21 +00:00
@app.route ( ' /getblockheader/<name> ' )
def getBlockHeader ( name ) :
resp = self . getBlockData ( name , decrypt = True , headerOnly = True )
return Response ( resp )
2019-02-14 23:48:41 +00:00
@app.route ( ' /lastconnect ' )
def lastConnect ( ) :
return Response ( str ( self . publicAPI . lastRequest ) )
2018-12-26 06:14:05 +00:00
@app.route ( ' /site/<name> ' , endpoint = ' site ' )
def site ( name ) :
bHash = name
2019-01-08 05:51:39 +00:00
resp = ' Not Found '
if self . _core . _utils . validateHash ( bHash ) :
2018-12-26 06:14:05 +00:00
try :
resp = Block ( bHash ) . bcontent
2019-02-22 21:04:03 +00:00
except onionrexceptions . NoDataAvailable :
abort ( 404 )
2018-12-26 06:14:05 +00:00
except TypeError :
pass
2019-01-08 05:51:39 +00:00
try :
resp = base64 . b64decode ( resp )
except :
pass
2019-02-22 21:04:03 +00:00
if resp == ' Not Found ' or not resp :
abort ( 404 )
2019-01-08 05:51:39 +00:00
return Response ( resp )
2018-07-30 00:37:12 +00:00
2019-01-08 05:51:39 +00:00
@app.route ( ' /waitforshare/<name> ' , methods = [ ' post ' ] )
2019-02-08 06:19:05 +00:00
def waitforshare ( name ) :
2019-02-12 05:30:56 +00:00
''' Used to prevent the **public** api from sharing blocks we just created '''
2019-01-08 05:51:39 +00:00
assert name . isalnum ( )
if name in self . publicAPI . hideBlocks :
self . publicAPI . hideBlocks . remove ( name )
return Response ( " removed " )
else :
self . publicAPI . hideBlocks . append ( name )
return Response ( " added " )
2018-07-30 00:37:12 +00:00
2019-01-08 05:51:39 +00:00
@app.route ( ' /shutdown ' )
def shutdown ( ) :
try :
self . publicAPI . httpServer . stop ( )
self . httpServer . stop ( )
except AttributeError :
pass
return Response ( " bye " )
2018-07-30 00:37:12 +00:00
2019-01-16 05:57:47 +00:00
@app.route ( ' /shutdownclean ' )
def shutdownClean ( ) :
# good for calling from other clients
self . _core . daemonQueueAdd ( ' shutdown ' )
return Response ( " bye " )
2019-01-13 22:20:10 +00:00
@app.route ( ' /getstats ' )
def getStats ( ) :
2019-02-12 05:30:56 +00:00
# returns node stats
2019-01-20 18:09:53 +00:00
#return Response("disabled")
2019-01-30 06:10:29 +00:00
while True :
try :
return Response ( self . _core . serializer . getStats ( ) )
except AttributeError :
pass
2019-01-13 22:20:10 +00:00
@app.route ( ' /getuptime ' )
def showUptime ( ) :
return Response ( str ( self . getUptime ( ) ) )
2019-02-04 00:31:03 +00:00
@app.route ( ' /getActivePubkey ' )
def getActivePubkey ( ) :
return Response ( self . _core . _crypto . pubKey )
@app.route ( ' /getHumanReadable/<name> ' )
def getHumanReadable ( name ) :
return Response ( self . _core . _utils . getHumanReadableID ( name ) )
2019-02-10 02:21:36 +00:00
@app.route ( ' /insertblock ' , methods = [ ' POST ' ] )
def insertBlock ( ) :
2019-02-10 18:43:45 +00:00
encrypt = False
2019-02-10 02:21:36 +00:00
bData = request . get_json ( force = True )
message = bData [ ' message ' ]
subject = ' temp '
2019-02-10 18:43:45 +00:00
encryptType = ' '
sign = True
meta = { }
to = ' '
try :
if bData [ ' encrypt ' ] :
to = bData [ ' to ' ]
encrypt = True
encryptType = ' asym '
except KeyError :
pass
try :
if not bData [ ' sign ' ] :
sign = False
except KeyError :
pass
try :
bType = bData [ ' type ' ]
except KeyError :
bType = ' bin '
try :
meta = json . loads ( bData [ ' meta ' ] )
except KeyError :
pass
2019-02-11 22:36:43 +00:00
threading . Thread ( target = self . _core . insertBlock , args = ( message , ) , kwargs = { ' header ' : bType , ' encryptType ' : encryptType , ' sign ' : sign , ' asymPeer ' : to , ' meta ' : meta } ) . start ( )
return Response ( ' success ' )
2019-02-08 06:19:05 +00:00
2019-02-10 02:21:36 +00:00
@app.route ( ' /apipoints/<path:subpath> ' , methods = [ ' POST ' , ' GET ' ] )
2019-02-08 06:19:05 +00:00
def pluginEndpoints ( subpath = ' ' ) :
2019-02-12 05:30:56 +00:00
''' Send data to plugins '''
2019-02-08 06:19:05 +00:00
# TODO have a variable for the plugin to set data to that we can use for the response
2019-02-08 18:53:28 +00:00
pluginResponseCode = str ( uuid . uuid4 ( ) )
resp = ' success '
2019-02-10 02:21:36 +00:00
responseTimeout = 20
2019-02-08 18:53:28 +00:00
startTime = self . _core . _utils . getEpoch ( )
2019-02-10 02:21:36 +00:00
postData = { }
if request . method == ' POST ' :
postData = request . form [ ' postData ' ]
2019-02-08 06:19:05 +00:00
if len ( subpath ) > 1 :
data = subpath . split ( ' / ' )
if len ( data ) > 1 :
plName = data [ 0 ]
2019-02-10 02:21:36 +00:00
events . event ( ' pluginRequest ' , { ' name ' : plName , ' path ' : subpath , ' pluginResponse ' : pluginResponseCode , ' postData ' : postData } , onionr = onionrInst )
2019-02-08 18:53:28 +00:00
while True :
try :
resp = self . pluginResponses [ pluginResponseCode ]
except KeyError :
time . sleep ( 0.2 )
if self . _core . _utils . getEpoch ( ) - startTime > responseTimeout :
abort ( 504 )
break
else :
break
2019-02-08 06:19:05 +00:00
else :
abort ( 404 )
2019-02-08 18:53:28 +00:00
return Response ( resp )
2018-12-22 19:02:09 +00:00
2019-01-20 02:23:26 +00:00
self . httpServer = WSGIServer ( ( self . host , bindPort ) , app , log = None , handler_class = FDSafeHandler )
2019-01-08 05:51:39 +00:00
self . httpServer . serve_forever ( )
2018-07-30 00:37:12 +00:00
2019-01-08 05:51:39 +00:00
def setPublicAPIInstance ( self , inst ) :
assert isinstance ( inst , PublicAPI )
self . publicAPI = inst
2018-07-30 00:37:12 +00:00
2019-01-08 05:51:39 +00:00
def validateToken ( self , token ) :
'''
2019-02-12 05:30:56 +00:00
Validate that the client token matches the given token . Used to prevent CSRF and data exfiltration
2019-01-08 05:51:39 +00:00
'''
if len ( self . clientToken ) == 0 :
logger . error ( " client password needs to be set " )
return False
try :
if not hmac . compare_digest ( self . clientToken , token ) :
return False
else :
return True
except TypeError :
return False
2019-01-13 22:20:10 +00:00
def getUptime ( self ) :
2019-01-28 22:49:04 +00:00
while True :
try :
return self . _utils . getEpoch - startTime
except AttributeError :
# Don't error on race condition with startup
2019-02-01 06:38:12 +00:00
pass
2019-02-04 23:48:21 +00:00
def getBlockData ( self , bHash , decrypt = False , raw = False , headerOnly = False ) :
assert self . _core . _utils . validateHash ( bHash )
2019-02-01 06:38:12 +00:00
bl = Block ( bHash , core = self . _core )
if decrypt :
bl . decrypt ( )
if bl . isEncrypted and not bl . decrypted :
raise ValueError
2019-02-02 03:15:28 +00:00
if not raw :
2019-02-04 23:48:21 +00:00
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 :
2019-02-05 23:20:36 +00:00
validSig = False
signer = self . _core . _utils . bytesToStr ( bl . signer )
2019-02-07 01:03:31 +00:00
#print(signer, bl.isSigned(), self._core._utils.validatePubKey(signer), bl.isSigner(signer))
2019-02-05 23:20:36 +00:00
if bl . isSigned ( ) and self . _core . _utils . validatePubKey ( signer ) and bl . isSigner ( signer ) :
validSig = True
bl . bheader [ ' validSig ' ] = validSig
2019-02-04 23:48:21 +00:00
bl . bheader [ ' meta ' ] = ' '
retData = { ' meta ' : bl . bheader , ' metadata ' : bl . bmetadata }
2019-02-02 03:15:28 +00:00
return json . dumps ( retData )
else :
2019-02-02 03:35:18 +00:00
return bl . raw