1254 lines
41 KiB
Python
1254 lines
41 KiB
Python
import BaseHTTPServer
|
|
import ClientConstants as CC
|
|
import ClientFiles
|
|
import Cookie
|
|
import hashlib
|
|
import httplib
|
|
import HydrusAudioHandling
|
|
import HydrusConstants as HC
|
|
import HydrusDocumentHandling
|
|
import HydrusExceptions
|
|
import HydrusFileHandling
|
|
import HydrusFlashHandling
|
|
import HydrusNATPunch
|
|
import HydrusImageHandling
|
|
import os
|
|
import random
|
|
import ServerFiles
|
|
import SocketServer
|
|
import threading
|
|
import time
|
|
import traceback
|
|
import urllib
|
|
import wx
|
|
import yaml
|
|
from twisted.internet import reactor, defer
|
|
from twisted.internet.threads import deferToThread
|
|
from twisted.web.server import Request, Site, NOT_DONE_YET
|
|
from twisted.web.resource import Resource
|
|
from twisted.web.static import File as FileResource, NoRangeStaticProducer
|
|
from twisted.python import log
|
|
|
|
CLIENT_ROOT_MESSAGE = '''<html>
|
|
<head>
|
|
<title>hydrus client</title>
|
|
</head>
|
|
<body>
|
|
<p>This hydrus client uses software version ''' + HC.u( HC.SOFTWARE_VERSION ) + ''' and network version ''' + HC.u( HC.NETWORK_VERSION ) + '''.</p>
|
|
<p>It only serves requests from 127.0.0.1.</p>
|
|
</body>
|
|
</html>'''
|
|
|
|
ROOT_MESSAGE_BEGIN = '''<html>
|
|
<head>
|
|
<title>hydrus service</title>
|
|
</head>
|
|
<body>
|
|
<p>This hydrus service uses software version ''' + HC.u( HC.SOFTWARE_VERSION ) + ''' and network version ''' + HC.u( HC.NETWORK_VERSION ) + '''.</p>
|
|
<p>'''
|
|
|
|
ROOT_MESSAGE_END = '''</p>
|
|
</body>
|
|
</html>'''
|
|
|
|
def ParseFileArguments( path ):
|
|
|
|
HydrusImageHandling.ConvertToPngIfBmp( path )
|
|
|
|
hash = HydrusFileHandling.GetHashFromPath( path )
|
|
|
|
try:
|
|
|
|
( size, mime, width, height, duration, num_frames, num_words ) = HydrusFileHandling.GetFileInfo( path )
|
|
|
|
except HydrusExceptions.SizeException:
|
|
|
|
raise HydrusExceptions.ForbiddenException( 'File is of zero length!' )
|
|
|
|
except HydrusExceptions.MimeException:
|
|
|
|
raise HydrusExceptions.ForbiddenException( 'Filetype is not permitted!' )
|
|
|
|
except Exception as e:
|
|
|
|
raise HydrusExceptions.ForbiddenException( HC.u( e ) )
|
|
|
|
|
|
args = {}
|
|
|
|
args[ 'path' ] = path
|
|
args[ 'hash' ] = hash
|
|
args[ 'size' ] = size
|
|
args[ 'mime' ] = mime
|
|
|
|
if width is not None: args[ 'width' ] = width
|
|
if height is not None: args[ 'height' ] = height
|
|
if duration is not None: args[ 'duration' ] = duration
|
|
if num_frames is not None: args[ 'num_frames' ] = num_frames
|
|
if num_words is not None: args[ 'num_words' ] = num_words
|
|
|
|
if mime in HC.IMAGES:
|
|
|
|
try: thumbnail = HydrusFileHandling.GenerateThumbnail( path )
|
|
except: raise HydrusExceptions.ForbiddenException( 'Could not generate thumbnail from that file.' )
|
|
|
|
args[ 'thumbnail' ] = thumbnail
|
|
|
|
|
|
return args
|
|
|
|
hydrus_favicon = FileResource( HC.STATIC_DIR + os.path.sep + 'hydrus.ico', defaultType = 'image/x-icon' )
|
|
local_booru_css = FileResource( HC.STATIC_DIR + os.path.sep + 'local_booru_style.css', defaultType = 'text/css' )
|
|
|
|
class HydrusDomain( object ):
|
|
|
|
def __init__( self, local_only ):
|
|
|
|
self._local_only = local_only
|
|
|
|
|
|
def CheckValid( self, client_ip ):
|
|
|
|
if self._local_only and client_ip != '127.0.0.1': raise HydrusExceptions.ForbiddenException( 'Only local access allowed!' )
|
|
|
|
|
|
class HydrusResourceWelcome( Resource ):
|
|
|
|
def __init__( self, service_key, service_type, message ):
|
|
|
|
Resource.__init__( self )
|
|
|
|
if service_key == HC.LOCAL_FILE_SERVICE_KEY: body = CLIENT_ROOT_MESSAGE
|
|
else: body = ROOT_MESSAGE_BEGIN + message + ROOT_MESSAGE_END
|
|
|
|
self._body = body.encode( 'utf-8' )
|
|
|
|
self._server_version_string = HC.service_string_lookup[ service_type ] + '/' + str( HC.NETWORK_VERSION )
|
|
|
|
|
|
def render_GET( self, request ):
|
|
|
|
request.setHeader( 'Server', self._server_version_string )
|
|
|
|
return self._body
|
|
|
|
|
|
class HydrusResourceCommand( Resource ):
|
|
|
|
def __init__( self, service_key, service_type, domain ):
|
|
|
|
Resource.__init__( self )
|
|
|
|
self._service_key = service_key
|
|
self._service_type = service_type
|
|
self._domain = domain
|
|
|
|
self._server_version_string = HC.service_string_lookup[ service_type ] + '/' + str( HC.NETWORK_VERSION )
|
|
|
|
|
|
def _callbackCheckRestrictions( self, request ):
|
|
|
|
self._checkUserAgent( request )
|
|
|
|
self._domain.CheckValid( request.getClientIP() )
|
|
|
|
return request
|
|
|
|
|
|
def _callbackParseGETArgs( self, request ):
|
|
|
|
hydrus_args = {}
|
|
|
|
for name in request.args:
|
|
|
|
values = request.args[ name ]
|
|
|
|
value = values[0]
|
|
|
|
if name in ( 'begin', 'expires', 'lifetime', 'num', 'service_type', 'service_port', 'since', 'timespan' ):
|
|
|
|
try: hydrus_args[ name ] = int( value )
|
|
except: raise HydrusExceptions.ForbiddenException( 'I was expecting to parse \'' + name + '\' as an integer, but it failed.' )
|
|
|
|
elif name in ( 'access_key', 'title', 'subject_account_key', 'contact_key', 'hash', 'subject_hash', 'subject_tag', 'message_key', 'share_key' ):
|
|
|
|
try: hydrus_args[ name ] = value.decode( 'hex' )
|
|
except: raise HydrusExceptions.ForbiddenException( 'I was expecting to parse \'' + name + '\' as a hex-encoded string, but it failed.' )
|
|
|
|
|
|
|
|
if 'subject_account_key' in hydrus_args: hydrus_args[ 'subject_identifier' ] = HC.AccountIdentifier( account_key = hydrus_args[ 'subject_account_key' ] )
|
|
elif 'subject_hash' in hydrus_args:
|
|
|
|
if 'subject_tag' in hydrus_args: hydrus_args[ 'subject_identifier' ] = HC.AccountIdentifier( tag = hydrus_args[ 'subject_tag' ], hash = hydrus_args[ 'subject_hash' ] )
|
|
else: hydrus_args[ 'subject_identifier' ] = HC.AccountIdentifier( hash = hydrus_args[ 'subject_hash' ] )
|
|
|
|
|
|
request.hydrus_args = hydrus_args
|
|
|
|
return request
|
|
|
|
|
|
def _callbackParsePOSTArgs( self, request ):
|
|
|
|
request.content.seek( 0 )
|
|
|
|
if not request.requestHeaders.hasHeader( 'Content-Type' ): raise HydrusExceptions.ForbiddenException( 'No Content-Type header found!' )
|
|
|
|
content_types = request.requestHeaders.getRawHeaders( 'Content-Type' )
|
|
|
|
content_type = content_types[0]
|
|
|
|
try: mime = HC.mime_enum_lookup[ content_type ]
|
|
except: raise HydrusExceptions.ForbiddenException( 'Did not recognise Content-Type header!' )
|
|
|
|
if mime == HC.APPLICATION_YAML:
|
|
|
|
yaml_string = request.content.read()
|
|
|
|
request.hydrus_request_data_usage += len( yaml_string )
|
|
|
|
hydrus_args = yaml.safe_load( yaml_string )
|
|
|
|
else:
|
|
|
|
temp_path = HC.GetTempPath()
|
|
|
|
with open( temp_path, 'wb' ) as f:
|
|
|
|
for block in HC.ReadFileLikeAsBlocks( request.content, 65536 ):
|
|
|
|
f.write( block )
|
|
|
|
request.hydrus_request_data_usage += len( block )
|
|
|
|
|
|
|
|
hydrus_args = ParseFileArguments( temp_path )
|
|
|
|
|
|
request.hydrus_args = hydrus_args
|
|
|
|
return request
|
|
|
|
|
|
def _callbackRenderResponseContext( self, request ):
|
|
|
|
response_context = request.hydrus_response_context
|
|
|
|
status_code = response_context.GetStatusCode()
|
|
|
|
request.setResponseCode( status_code )
|
|
|
|
for ( k, v, kwargs ) in response_context.GetCookies(): request.addCookie( k, v, **kwargs )
|
|
|
|
do_finish = True
|
|
|
|
if response_context.HasBody():
|
|
|
|
( mime, body ) = response_context.GetMimeBody()
|
|
|
|
content_type = HC.mime_string_lookup[ mime ]
|
|
|
|
content_length = len( body )
|
|
|
|
request.setHeader( 'Content-Type', content_type )
|
|
request.setHeader( 'Content-Length', str( content_length ) )
|
|
|
|
if type( body ) == unicode: body = body.encode( 'utf-8' )
|
|
|
|
request.write( body )
|
|
|
|
elif response_context.HasPath():
|
|
|
|
path = response_context.GetPath()
|
|
|
|
info = os.lstat( path )
|
|
|
|
size = info[6]
|
|
|
|
if response_context.IsYAML():
|
|
|
|
mime = HC.APPLICATION_YAML
|
|
|
|
content_type = HC.mime_string_lookup[ mime ]
|
|
|
|
else:
|
|
|
|
mime = HydrusFileHandling.GetMime( path )
|
|
|
|
( base, filename ) = os.path.split( path )
|
|
|
|
content_type = HC.mime_string_lookup[ mime ] + '; ' + filename
|
|
|
|
|
|
content_length = size
|
|
|
|
request.setHeader( 'Content-Type', content_type )
|
|
request.setHeader( 'Content-Length', str( content_length ) )
|
|
|
|
request.setHeader( 'Expires', time.strftime( '%a, %d %b %Y %H:%M:%S GMT', time.gmtime( time.time() + 86400 * 365 ) ) )
|
|
request.setHeader( 'Cache-Control', str( 86400 * 365 ) )
|
|
|
|
fileObject = open( path, 'rb' )
|
|
|
|
producer = NoRangeStaticProducer( request, fileObject )
|
|
|
|
producer.start()
|
|
|
|
do_finish = False
|
|
|
|
else:
|
|
|
|
content_length = 0
|
|
|
|
request.setHeader( 'Content-Length', str( content_length ) )
|
|
|
|
|
|
request.hydrus_request_data_usage += content_length
|
|
|
|
self._recordDataUsage( request )
|
|
|
|
if do_finish: request.finish()
|
|
|
|
|
|
def _callbackDoGETJob( self, request ):
|
|
|
|
def wrap_thread_result( response_context ):
|
|
|
|
request.hydrus_response_context = response_context
|
|
|
|
return request
|
|
|
|
|
|
d = deferToThread( self._threadDoGETJob, request )
|
|
|
|
d.addCallback( wrap_thread_result )
|
|
|
|
return d
|
|
|
|
|
|
def _callbackDoPOSTJob( self, request ):
|
|
|
|
def wrap_thread_result( response_context ):
|
|
|
|
request.hydrus_response_context = response_context
|
|
|
|
return request
|
|
|
|
|
|
d = deferToThread( self._threadDoPOSTJob, request )
|
|
|
|
d.addCallback( wrap_thread_result )
|
|
|
|
return d
|
|
|
|
|
|
def _checkUserAgent( self, request ):
|
|
|
|
request.is_hydrus_user_agent = False
|
|
|
|
if request.requestHeaders.hasHeader( 'User-Agent' ):
|
|
|
|
user_agent_texts = request.requestHeaders.getRawHeaders( 'User-Agent' )
|
|
|
|
user_agent_text = user_agent_texts[0]
|
|
|
|
try:
|
|
|
|
user_agents = user_agent_text.split( ' ' )
|
|
|
|
except: return # crazy user agent string, so just assume not a hydrus client
|
|
|
|
for user_agent in user_agents:
|
|
|
|
if '/' in user_agent:
|
|
|
|
( client, network_version ) = user_agent.split( '/', 1 )
|
|
|
|
if client == 'hydrus':
|
|
|
|
request.is_hydrus_user_agent = True
|
|
|
|
network_version = int( network_version )
|
|
|
|
if network_version == HC.NETWORK_VERSION: return
|
|
else:
|
|
|
|
if network_version < HC.NETWORK_VERSION: message = 'Your client is out of date; please download the latest release.'
|
|
else: message = 'This server is out of date; please ask its admin to update to the latest release.'
|
|
|
|
raise HydrusExceptions.NetworkVersionException( 'Network version mismatch! This server\'s network version is ' + HC.u( HC.NETWORK_VERSION ) + ', whereas your client\'s is ' + HC.u( network_version ) + '! ' + message )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _errbackHandleEmergencyError( self, failure, request ):
|
|
|
|
print( failure.getTraceback() )
|
|
|
|
try: request.write( failure.getTraceback() )
|
|
except: pass
|
|
|
|
try: request.finish()
|
|
except: pass
|
|
|
|
|
|
def _errbackHandleProcessingError( self, failure, request ):
|
|
|
|
do_yaml = True
|
|
|
|
try:
|
|
|
|
# the error may have occured before user agent was set up!
|
|
if not request.is_hydrus_user_agent: do_yaml = False
|
|
|
|
except: pass
|
|
|
|
if do_yaml:
|
|
|
|
default_mime = HC.APPLICATION_YAML
|
|
default_encoding = lambda x: yaml.safe_dump( HC.u( x ) )
|
|
|
|
else:
|
|
|
|
default_mime = HC.TEXT_HTML
|
|
default_encoding = lambda x: HC.u( x )
|
|
|
|
|
|
if failure.type == KeyError: response_context = HC.ResponseContext( 403, mime = default_mime, body = default_encoding( 'It appears one or more parameters required for that request were missing:' + os.linesep + failure.getTraceback() ) )
|
|
elif failure.type == HydrusExceptions.PermissionException: response_context = HC.ResponseContext( 401, mime = default_mime, body = default_encoding( failure.value ) )
|
|
elif failure.type == HydrusExceptions.ForbiddenException: response_context = HC.ResponseContext( 403, mime = default_mime, body = default_encoding( failure.value ) )
|
|
elif failure.type == HydrusExceptions.NotFoundException: response_context = HC.ResponseContext( 404, mime = default_mime, body = default_encoding( failure.value ) )
|
|
elif failure.type == HydrusExceptions.NetworkVersionException: response_context = HC.ResponseContext( 426, mime = default_mime, body = default_encoding( failure.value ) )
|
|
elif failure.type == HydrusExceptions.SessionException: response_context = HC.ResponseContext( 403, mime = default_mime, body = default_encoding( failure.value ) )
|
|
else:
|
|
|
|
print( failure.getTraceback() )
|
|
|
|
response_context = HC.ResponseContext( 500, mime = default_mime, body = default_encoding( 'The repository encountered an error it could not handle! Here is a dump of what happened, which will also be written to your client.log file. If it persists, please forward it to hydrus.admin@gmail.com:' + os.linesep * 2 + failure.getTraceback() ) )
|
|
|
|
|
|
request.hydrus_response_context = response_context
|
|
|
|
return request
|
|
|
|
|
|
def _parseAccessKey( self, request ):
|
|
|
|
if not request.requestHeaders.hasHeader( 'Hydrus-Key' ): raise HydrusExceptions.PermissionException( 'No hydrus key header found!' )
|
|
|
|
hex_keys = request.requestHeaders.getRawHeaders( 'Hydrus-Key' )
|
|
|
|
hex_key = hex_keys[0]
|
|
|
|
try: access_key = hex_key.decode( 'hex' )
|
|
except: raise HydrusExceptions.ForbiddenException( 'Could not parse the hydrus key!' )
|
|
|
|
return access_key
|
|
|
|
|
|
def _recordDataUsage( self, request ): pass
|
|
|
|
def _threadDoGETJob( self, request ): raise HydrusExceptions.NotFoundException( 'This service does not support that request!' )
|
|
|
|
def _threadDoPOSTJob( self, request ): raise HydrusExceptions.NotFoundException( 'This service does not support that request!' )
|
|
|
|
def render_GET( self, request ):
|
|
|
|
request.setHeader( 'Server', self._server_version_string )
|
|
|
|
d = defer.Deferred()
|
|
|
|
d.addCallback( self._callbackCheckRestrictions )
|
|
|
|
d.addCallback( self._callbackParseGETArgs )
|
|
|
|
d.addCallback( self._callbackDoGETJob )
|
|
|
|
d.addErrback( self._errbackHandleProcessingError, request )
|
|
|
|
d.addCallback( self._callbackRenderResponseContext )
|
|
|
|
d.addErrback( self._errbackHandleEmergencyError, request )
|
|
|
|
reactor.callLater( 0, d.callback, request )
|
|
|
|
return NOT_DONE_YET
|
|
|
|
|
|
def render_POST( self, request ):
|
|
|
|
request.setHeader( 'Server', self._server_version_string )
|
|
|
|
d = defer.Deferred()
|
|
|
|
d.addCallback( self._callbackCheckRestrictions )
|
|
|
|
d.addCallback( self._callbackParsePOSTArgs )
|
|
|
|
d.addCallback( self._callbackDoPOSTJob )
|
|
|
|
d.addErrback( self._errbackHandleProcessingError, request )
|
|
|
|
d.addCallback( self._callbackRenderResponseContext )
|
|
|
|
d.addErrback( self._errbackHandleEmergencyError, request )
|
|
|
|
reactor.callLater( 0, d.callback, request )
|
|
|
|
return NOT_DONE_YET
|
|
|
|
|
|
class HydrusResourceCommandAccessKey( HydrusResourceCommand ):
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
registration_key = self._parseAccessKey( request )
|
|
|
|
access_key = HC.app.Read( 'access_key', registration_key )
|
|
|
|
body = yaml.safe_dump( { 'access_key' : access_key } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandAccessKeyVerification( HydrusResourceCommand ):
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
access_key = self._parseAccessKey( request )
|
|
|
|
verified = HC.app.Read( 'verify_access_key', self._service_key, access_key )
|
|
|
|
body = yaml.safe_dump( { 'verified' : verified } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandBooru( HydrusResourceCommand ):
|
|
|
|
RECORD_GET_DATA_USAGE = False
|
|
RECORD_POST_DATA_USAGE = False
|
|
|
|
def _recordDataUsage( self, request ):
|
|
|
|
p1 = request.method == 'GET' and self.RECORD_GET_DATA_USAGE
|
|
p2 = request.method == 'POST' and self.RECORD_POST_DATA_USAGE
|
|
|
|
if p1 or p2:
|
|
|
|
num_bytes = request.hydrus_request_data_usage
|
|
|
|
HC.pubsub.pub( 'service_updates_delayed', { HC.LOCAL_BOORU_SERVICE_KEY : [ HC.ServiceUpdate( HC.SERVICE_UPDATE_REQUEST_MADE, num_bytes ) ] } )
|
|
|
|
|
|
|
|
def _callbackCheckRestrictions( self, request ):
|
|
|
|
self._checkUserAgent( request )
|
|
|
|
self._domain.CheckValid( request.getClientIP() )
|
|
|
|
return request
|
|
|
|
|
|
class HydrusResourceCommandBooruGallery( HydrusResourceCommandBooru ):
|
|
|
|
RECORD_GET_DATA_USAGE = True
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
# in future, make this a standard frame with a search key that'll load xml or yaml AJAX stuff
|
|
# with file info included, so the page can sort and whatever
|
|
|
|
share_key = request.hydrus_args[ 'share_key' ]
|
|
|
|
local_booru_manager = HC.app.GetManager( 'local_booru' )
|
|
|
|
local_booru_manager.CheckShareAuthorised( share_key )
|
|
|
|
( name, text, timeout, media_results ) = local_booru_manager.GetGalleryInfo( share_key )
|
|
|
|
body = '''<html>
|
|
<head>'''
|
|
|
|
if name == '': body += '''
|
|
<title>hydrus network local booru share</title>'''
|
|
else: body += '''
|
|
<title>''' + name + '''</title>'''
|
|
|
|
body += '''
|
|
|
|
<link href="hydrus.ico" rel="shortcut icon" />
|
|
<link href="style.css" rel="stylesheet" type="text/css" />'''
|
|
|
|
( thumbnail_width, thumbnail_height ) = HC.options[ 'thumbnail_dimensions' ]
|
|
|
|
body += '''
|
|
<style>
|
|
.thumbnail_container { width: ''' + str( thumbnail_width ) + '''px; height: ''' + str( thumbnail_height ) + '''px; }
|
|
</style>'''
|
|
|
|
body += '''
|
|
</head>
|
|
<body>'''
|
|
|
|
body += '''
|
|
<div class="timeout">This share ''' + HC.ConvertTimestampToPrettyExpires( timeout ) + '''.</div>'''
|
|
|
|
if name != '': body += '''
|
|
<h3>''' + name + '''</h3>'''
|
|
|
|
if text != '':
|
|
|
|
newline = '''</p>
|
|
<p>'''
|
|
|
|
body += '''
|
|
<p>''' + text.replace( os.linesep, newline ).replace( '\n', newline ) + '''</p>'''
|
|
|
|
body+= '''
|
|
<div class="media">'''
|
|
|
|
for media_result in media_results:
|
|
|
|
hash = media_result.GetHash()
|
|
mime = media_result.GetMime()
|
|
|
|
# if mime in flash or pdf or whatever, get other thumbnail
|
|
|
|
body += '''
|
|
<span class="thumbnail">
|
|
<span class="thumbnail_container">
|
|
<a href="page?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''">
|
|
<img src="thumbnail?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''" />
|
|
</a>
|
|
</span>
|
|
</span>'''
|
|
|
|
|
|
body += '''
|
|
</div>
|
|
<div class="footer"><a href="http://hydrusnetwork.github.io/hydrus/">hydrus network</a></div>
|
|
</body>
|
|
</html>'''
|
|
|
|
response_context = HC.ResponseContext( 200, mime = HC.TEXT_HTML, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandBooruFile( HydrusResourceCommandBooru ):
|
|
|
|
RECORD_GET_DATA_USAGE = True
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
share_key = request.hydrus_args[ 'share_key' ]
|
|
hash = request.hydrus_args[ 'hash' ]
|
|
|
|
local_booru_manager = HC.app.GetManager( 'local_booru' )
|
|
|
|
local_booru_manager.CheckFileAuthorised( share_key, hash )
|
|
|
|
path = ClientFiles.GetFilePath( hash )
|
|
|
|
response_context = HC.ResponseContext( 200, path = path )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandBooruPage( HydrusResourceCommandBooru ):
|
|
|
|
RECORD_GET_DATA_USAGE = True
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
share_key = request.hydrus_args[ 'share_key' ]
|
|
hash = request.hydrus_args[ 'hash' ]
|
|
|
|
local_booru_manager = HC.app.GetManager( 'local_booru' )
|
|
|
|
local_booru_manager.CheckFileAuthorised( share_key, hash )
|
|
|
|
( name, text, timeout, media_result ) = local_booru_manager.GetPageInfo( share_key, hash )
|
|
|
|
body = '''<html>
|
|
<head>'''
|
|
|
|
if name == '': body += '''
|
|
<title>hydrus network local booru share</title>'''
|
|
else: body += '''
|
|
<title>''' + name + '''</title>'''
|
|
|
|
body += '''
|
|
|
|
<link href="hydrus.ico" rel="shortcut icon" />
|
|
<link href="style.css" rel="stylesheet" type="text/css" />'''
|
|
|
|
body += '''
|
|
</head>
|
|
<body>'''
|
|
|
|
body += '''
|
|
<div class="timeout">This share ''' + HC.ConvertTimestampToPrettyExpires( timeout ) + '''.</div>'''
|
|
|
|
if name != '': body += '''
|
|
<h3>''' + name + '''</h3>'''
|
|
|
|
if text != '':
|
|
|
|
newline = '''</p>
|
|
<p>'''
|
|
|
|
body += '''
|
|
<p>''' + text.replace( os.linesep, newline ).replace( '\n', newline ) + '''</p>'''
|
|
|
|
body+= '''
|
|
<div class="media">'''
|
|
|
|
mime = media_result.GetMime()
|
|
|
|
if mime in HC.IMAGES:
|
|
|
|
( width, height ) = media_result.GetResolution()
|
|
|
|
body += '''
|
|
<img width="''' + str( width ) + '''" height="''' + str( height ) + '''" src="file?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''" />'''
|
|
|
|
elif mime in HC.VIDEO:
|
|
|
|
( width, height ) = media_result.GetResolution()
|
|
|
|
body += '''
|
|
<video width="''' + str( width ) + '''" height="''' + str( height ) + '''" controls="" loop="" autoplay="" src="file?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''" />
|
|
<p><a href="file?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''">link to ''' + HC.mime_string_lookup[ mime ] + ''' file</a></p>'''
|
|
|
|
elif mime == HC.APPLICATION_FLASH:
|
|
|
|
( width, height ) = media_result.GetResolution()
|
|
|
|
body += '''
|
|
<embed width="''' + str( width ) + '''" height="''' + str( height ) + '''" src="file?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''" />
|
|
<p><a href="file?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''">link to ''' + HC.mime_string_lookup[ mime ] + ''' file</a></p>'''
|
|
|
|
else:
|
|
|
|
body += '''
|
|
<p><a href="file?share_key=''' + share_key.encode( 'hex' ) + '''&hash=''' + hash.encode( 'hex' ) + '''">link to ''' + HC.mime_string_lookup[ mime ] + ''' file</a></p>'''
|
|
|
|
|
|
body += '''
|
|
</div>
|
|
<div class="footer"><a href="http://hydrusnetwork.github.io/hydrus/">hydrus network</a></div>
|
|
</body>
|
|
</html>'''
|
|
|
|
response_context = HC.ResponseContext( 200, mime = HC.TEXT_HTML, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandBooruThumbnail( HydrusResourceCommandBooru ):
|
|
|
|
RECORD_GET_DATA_USAGE = True
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
share_key = request.hydrus_args[ 'share_key' ]
|
|
hash = request.hydrus_args[ 'hash' ]
|
|
|
|
local_booru_manager = HC.app.GetManager( 'local_booru' )
|
|
|
|
local_booru_manager.CheckFileAuthorised( share_key, hash )
|
|
|
|
media_result = local_booru_manager.GetMediaResult( share_key, hash )
|
|
|
|
mime = media_result.GetMime()
|
|
|
|
if mime in HC.MIMES_WITH_THUMBNAILS: path = ClientFiles.GetThumbnailPath( hash, full_size = False )
|
|
elif mime in HC.AUDIO: path = HC.STATIC_DIR + os.path.sep + 'audio_resized.png'
|
|
elif mime in HC.VIDEO: path = HC.STATIC_DIR + os.path.sep + 'video_resized.png'
|
|
elif mime == HC.APPLICATION_FLASH: path = HC.STATIC_DIR + os.path.sep + 'flash_resized.png'
|
|
elif mime == HC.APPLICATION_PDF: path = HC.STATIC_DIR + os.path.sep + 'pdf_resized.png'
|
|
else: path = HC.STATIC_DIR + os.path.sep + 'hydrus_resized.png'
|
|
|
|
response_context = HC.ResponseContext( 200, path = path )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandInit( HydrusResourceCommand ):
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
access_key = HC.app.Read( 'init' )
|
|
|
|
body = yaml.safe_dump( { 'access_key' : access_key } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandLocalFile( HydrusResourceCommand ):
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
hash = request.hydrus_args[ 'hash' ]
|
|
|
|
path = ClientFiles.GetFilePath( hash )
|
|
|
|
response_context = HC.ResponseContext( 200, path = path )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandLocalThumbnail( HydrusResourceCommand ):
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
hash = request.hydrus_args[ 'hash' ]
|
|
|
|
path = ClientFiles.GetThumbnailPath( hash )
|
|
|
|
response_context = HC.ResponseContext( 200, path = path )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandSessionKey( HydrusResourceCommand ):
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
access_key = self._parseAccessKey( request )
|
|
|
|
session_manager = HC.app.GetManager( 'restricted_services_sessions' )
|
|
|
|
( session_key, expires ) = session_manager.AddSession( self._service_key, access_key )
|
|
|
|
now = HC.GetNow()
|
|
|
|
max_age = now - expires
|
|
|
|
cookies = [ ( 'session_key', session_key.encode( 'hex' ), { 'max_age' : max_age, 'path' : '/' } ) ]
|
|
|
|
response_context = HC.ResponseContext( 200, cookies = cookies )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestricted( HydrusResourceCommand ):
|
|
|
|
GET_PERMISSION = HC.GENERAL_ADMIN
|
|
POST_PERMISSION = HC.GENERAL_ADMIN
|
|
RECORD_GET_DATA_USAGE = False
|
|
RECORD_POST_DATA_USAGE = False
|
|
|
|
def _callbackCheckRestrictions( self, request ):
|
|
|
|
self._checkUserAgent( request )
|
|
|
|
self._domain.CheckValid( request.getClientIP() )
|
|
|
|
self._checkSession( request )
|
|
|
|
self._checkPermission( request )
|
|
|
|
return request
|
|
|
|
|
|
def _checkPermission( self, request ):
|
|
|
|
account = request.hydrus_account
|
|
|
|
method = request.method
|
|
|
|
if method == 'GET': permission = self.GET_PERMISSION
|
|
elif method == 'POST': permission = self.POST_PERMISSION
|
|
|
|
if permission is not None: account.CheckPermission( permission )
|
|
|
|
return request
|
|
|
|
|
|
def _checkSession( self, request ):
|
|
|
|
if not request.requestHeaders.hasHeader( 'Cookie' ): raise HydrusExceptions.PermissionException( 'No cookies found!' )
|
|
|
|
cookie_texts = request.requestHeaders.getRawHeaders( 'Cookie' )
|
|
|
|
cookie_text = cookie_texts[0]
|
|
|
|
try:
|
|
|
|
cookies = Cookie.SimpleCookie( cookie_text )
|
|
|
|
if 'session_key' not in cookies: session_key = None
|
|
else: session_key = cookies[ 'session_key' ].value.decode( 'hex' )
|
|
|
|
except: raise Exception( 'Problem parsing cookies!' )
|
|
|
|
session_manager = HC.app.GetManager( 'restricted_services_sessions' )
|
|
|
|
account = session_manager.GetAccount( self._service_key, session_key )
|
|
|
|
request.hydrus_account = account
|
|
|
|
return request
|
|
|
|
|
|
def _recordDataUsage( self, request ):
|
|
|
|
p1 = request.method == 'GET' and self.RECORD_GET_DATA_USAGE
|
|
p2 = request.method == 'POST' and self.RECORD_POST_DATA_USAGE
|
|
|
|
if p1 or p2:
|
|
|
|
account = request.hydrus_account
|
|
|
|
if account is not None:
|
|
|
|
num_bytes = request.hydrus_request_data_usage
|
|
|
|
account.RequestMade( num_bytes )
|
|
|
|
HC.pubsub.pub( 'request_made', ( account.GetAccountKey(), num_bytes ) )
|
|
|
|
|
|
|
|
|
|
class HydrusResourceCommandRestrictedAccount( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = None
|
|
POST_PERMISSION = HC.MANAGE_USERS
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
account = request.hydrus_account
|
|
|
|
body = yaml.safe_dump( { 'account' : account } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
def _threadDoPOSTJob( self, request ):
|
|
|
|
admin_account = request.hydrus_account
|
|
|
|
admin_account_key = admin_account.GetAccountKey()
|
|
|
|
action = request.hydrus_args[ 'action' ]
|
|
|
|
subject_identifiers = request.hydrus_args[ 'subject_identifiers' ]
|
|
|
|
subject_account_keys = { HC.app.Read( 'account_key_from_identifier', self._service_key, subject_identifier ) for subject_identifier in subject_identifiers }
|
|
|
|
kwargs = request.hydrus_args # for things like expires, title, and so on
|
|
|
|
HC.app.Write( 'account', self._service_key, admin_account_key, action, subject_account_keys, kwargs )
|
|
|
|
session_manager = HC.app.GetManager( 'restricted_services_sessions' )
|
|
|
|
session_manager.RefreshAccounts( self._service_key, subject_account_keys )
|
|
|
|
response_context = HC.ResponseContext( 200 )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedAccountInfo( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
subject_identifier = request.hydrus_args[ 'subject_identifier' ]
|
|
|
|
subject_account_key = HC.app.Read( 'account_key_from_identifier', self._service_key, subject_identifier )
|
|
|
|
account_info = HC.app.Read( 'account_info', self._service_key, subject_account_key )
|
|
|
|
body = yaml.safe_dump( { 'account_info' : account_info } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedAccountTypes( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GENERAL_ADMIN
|
|
POST_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
account_types = HC.app.Read( 'account_types', self._service_key )
|
|
|
|
body = yaml.safe_dump( { 'account_types' : account_types } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
def _threadDoPOSTJob( self, request ):
|
|
|
|
edit_log = request.hydrus_args[ 'edit_log' ]
|
|
|
|
HC.app.Write( 'account_types', self._service_key, edit_log )
|
|
|
|
response_context = HC.ResponseContext( 200 )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedBackup( HydrusResourceCommandRestricted ):
|
|
|
|
POST_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoPOSTJob( self, request ):
|
|
|
|
#threading.Thread( target = HC.app.Write, args = ( 'backup', ), name = 'Backup Thread' ).start()
|
|
|
|
HC.app.Write( 'backup' )
|
|
|
|
response_context = HC.ResponseContext( 200 )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedIP( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
hash = request.hydrus_args[ 'hash' ]
|
|
|
|
( ip, timestamp ) = HC.app.Read( 'ip', self._service_key, hash )
|
|
|
|
body = yaml.safe_dump( { 'ip' : ip, 'timestamp' : timestamp } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedNews( HydrusResourceCommandRestricted ):
|
|
|
|
POST_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoPOSTJob( self, request ):
|
|
|
|
news = request.hydrus_args[ 'news' ]
|
|
|
|
HC.app.Write( 'news', self._service_key, news )
|
|
|
|
response_context = HC.ResponseContext( 200 )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedNumPetitions( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.RESOLVE_PETITIONS
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
num_petitions = HC.app.Read( 'num_petitions', self._service_key )
|
|
|
|
body = yaml.safe_dump( { 'num_petitions' : num_petitions } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedPetition( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.RESOLVE_PETITIONS
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
petition = HC.app.Read( 'petition', self._service_key )
|
|
|
|
body = yaml.safe_dump( { 'petition' : petition } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedRegistrationKeys( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
num = request.hydrus_args[ 'num' ]
|
|
title = request.hydrus_args[ 'title' ]
|
|
|
|
if 'lifetime' in request.hydrus_args: lifetime = request.hydrus_args[ 'lifetime' ]
|
|
else: lifetime = None
|
|
|
|
registration_keys = HC.app.Read( 'registration_keys', self._service_key, num, title, lifetime )
|
|
|
|
body = yaml.safe_dump( { 'registration_keys' : registration_keys } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedRepositoryFile( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GET_DATA
|
|
POST_PERMISSION = HC.POST_DATA
|
|
RECORD_GET_DATA_USAGE = True
|
|
RECORD_POST_DATA_USAGE = True
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
hash = request.hydrus_args[ 'hash' ]
|
|
|
|
# don't I need to check that we aren't stealing the file from another service?
|
|
|
|
path = ServerFiles.GetPath( 'file', hash )
|
|
|
|
response_context = HC.ResponseContext( 200, path = path )
|
|
|
|
return response_context
|
|
|
|
|
|
def _threadDoPOSTJob( self, request ):
|
|
|
|
account = request.hydrus_account
|
|
|
|
account_key = account.GetAccountKey()
|
|
|
|
file_dict = request.hydrus_args
|
|
|
|
file_dict[ 'ip' ] = request.getClientIP()
|
|
|
|
HC.app.Write( 'file', self._service_key, account_key, file_dict )
|
|
|
|
response_context = HC.ResponseContext( 200 )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedRepositoryThumbnail( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GET_DATA
|
|
RECORD_GET_DATA_USAGE = True
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
hash = request.hydrus_args[ 'hash' ]
|
|
|
|
# don't I need to check that we aren't stealing the file from another service?
|
|
|
|
path = ServerFiles.GetPath( 'thumbnail', hash )
|
|
|
|
response_context = HC.ResponseContext( 200, path = path )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedServices( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GENERAL_ADMIN
|
|
POST_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoPOSTJob( self, request ):
|
|
|
|
account = request.hydrus_account
|
|
|
|
account_key = account.GetAccountKey()
|
|
|
|
edit_log = request.hydrus_args[ 'edit_log' ]
|
|
|
|
service_keys_to_access_keys = HC.app.Write( 'services', account_key, edit_log )
|
|
|
|
body = yaml.safe_dump( { 'service_keys_to_access_keys' : service_keys_to_access_keys } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedServicesInfo( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GENERAL_ADMIN
|
|
POST_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
services_info = HC.app.Read( 'services_info' )
|
|
|
|
body = yaml.safe_dump( { 'services_info' : services_info } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedStats( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GENERAL_ADMIN
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
stats = HC.app.Read( 'stats', self._service_key )
|
|
|
|
body = yaml.safe_dump( { 'stats' : stats } )
|
|
|
|
response_context = HC.ResponseContext( 200, body = body )
|
|
|
|
return response_context
|
|
|
|
|
|
class HydrusResourceCommandRestrictedUpdate( HydrusResourceCommandRestricted ):
|
|
|
|
GET_PERMISSION = HC.GET_DATA
|
|
POST_PERMISSION = HC.POST_DATA
|
|
RECORD_GET_DATA_USAGE = True
|
|
RECORD_POST_DATA_USAGE = True
|
|
|
|
def _threadDoGETJob( self, request ):
|
|
|
|
begin = request.hydrus_args[ 'begin' ]
|
|
|
|
path = ServerFiles.GetUpdatePath( self._service_key, begin )
|
|
|
|
response_context = HC.ResponseContext( 200, path = path, is_yaml = True )
|
|
|
|
return response_context
|
|
|
|
|
|
def _threadDoPOSTJob( self, request ):
|
|
|
|
account = request.hydrus_account
|
|
|
|
account_key = account.GetAccountKey()
|
|
|
|
update = request.hydrus_args[ 'update' ]
|
|
|
|
HC.app.Write( 'update', self._service_key, account_key, update )
|
|
|
|
response_context = HC.ResponseContext( 200 )
|
|
|
|
return response_context
|
|
|
|
|