import HydrusConstants as HC import HydrusExceptions import HydrusFileHandling import HydrusImageHandling import HydrusNetwork import HydrusPaths import HydrusSerialisable import os import time import traceback from twisted.internet import reactor, defer from twisted.internet.threads import deferToThread from twisted.web.server import NOT_DONE_YET from twisted.web.resource import Resource from twisted.web.static import File as FileResource, NoRangeStaticProducer import HydrusData import HydrusGlobals as HG def GenerateEris( service, domain ): name = service.GetName() service_type = service.GetServiceType() welcome_text_1 = 'This is ' + name + ',' welcome_text_2 = 'a ' + HC.service_string_lookup[ service_type ] + '.' welcome_text_3 = 'Software version ' + str( HC.SOFTWARE_VERSION ) welcome_text_4 = 'Network version ' + str( HC.NETWORK_VERSION ) if domain.IsLocal(): welcome_text_5 = 'It only responds to requests from localhost.' else: welcome_text_5 = 'It responds to requests from any host.' return '''
8888 8888888 888888888888888888888888 8888:::8888888888888888888888888 8888::::::8888888888888888888888888888 88::::::::888:::8888888888888888888888888 88888888::::8:::::::::::88888888888888888888 888 8::888888::::::::::::::::::88888888888 888 88::::88888888::::m::::::::::88888888888 8 888888888888888888:M:::::::::::8888888888888 88888888888888888888::::::::::::M88888888888888 8888888888888888888888:::::::::M8888888888888888 8888888888888888888888:::::::M888888888888888888 8888888888888888::88888::::::M88888888888888888888 88888888888888888:::88888:::::M888888888888888 8888 88888888888888888:::88888::::M::;o*M*o;888888888 88 88888888888888888:::8888:::::M:::::::::::88888888 8 88888888888888888::::88::::::M:;:::::::::::888888888 8888888888888888888:::8::::::M::aAa::::::::M8888888888 8 88 8888888888::88::::8::::M:::::::::::::888888888888888 8888 88 88888888888:::8:::::::::M::::::::::;::88:88888888888888888 8 8888888888888:::::::::::M::"@@@@@@@"::::8w8888888888888888 88888888888:888::::::::::M:::::"@a@":::::M8i888888888888888 8888888888::::88:::::::::M88:::::::::::::M88z88888888888888888 8888888888:::::8:::::::::M88888:::::::::MM888!888888888888888888 888888888:::::8:::::::::M8888888MAmmmAMVMM888*88888888 88888888 888888 M:::::::::::::::M888888888:::::::MM88888888888888 8888888 8888 M::::::::::::::M88888888888::::::MM888888888888888 88888 888 M:::::::::::::M8888888888888M:::::mM888888888888888 8888 888 M::::::::::::M8888:888888888888::::m::Mm88888 888888 8888 88 M::::::::::::8888:88888888888888888::::::Mm8 88888 888 88 M::::::::::8888M::88888::888888888888:::::::Mm88888 88 8 MM::::::::8888M:::8888:::::888888888888::::::::Mm8 4 ''' + welcome_text_1 + ''' 8M:::::::8888M:::::888:::::::88:::8888888::::::::Mm 2 ''' + welcome_text_2 + ''' 88MM:::::8888M:::::::88::::::::8:::::888888:::M:::::M 8888M:::::888MM::::::::8:::::::::::M::::8888::::M::::M ''' + welcome_text_3 + ''' 88888M:::::88:M::::::::::8:::::::::::M:::8888::::::M::M ''' + welcome_text_4 + ''' 88 888MM:::888:M:::::::::::::::::::::::M:8888:::::::::M: 8 88888M:::88::M:::::::::::::::::::::::MM:88::::::::::::M ''' + welcome_text_5 + ''' 88888M:::88::M::::::::::*88*::::::::::M:88::::::::::::::M 888888M:::88::M:::::::::88@@88:::::::::M::88::::::::::::::M 888888MM::88::MM::::::::88@@88:::::::::M:::8::::::::::::::*8 88888 M:::8::MM:::::::::*88*::::::::::M:::::::::::::::::88@@ 8888 MM::::::MM:::::::::::::::::::::MM:::::::::::::::::88@@ 888 M:::::::MM:::::::::::::::::::MM::M::::::::::::::::*8 888 MM:::::::MMM::::::::::::::::MM:::MM:::::::::::::::M 88 M::::::::MMMM:::::::::::MMMM:::::MM::::::::::::MM 88 MM:::::::::MMMMMMMMMMMMMMM::::::::MMM::::::::MMM 88 MM::::::::::::MMMMMMM::::::::::::::MMMMMMMMMM 88 8MM::::::::::::::::::::::::::::::::::MMMMMM 8 88MM::::::::::::::::::::::M:::M::::::::MM 888MM::::::::::::::::::MM::::::MM::::::MM 88888MM:::::::::::::::MMM:::::::mM:::::MM 888888MM:::::::::::::MMM:::::::::MMM:::M 88888888MM:::::::::::MMM:::::::::::MM:::M 88 8888888M:::::::::MMM::::::::::::::M:::M 8 888888 M:::::::MM:::::::::::::::::M:::M: 888888 M::::::M:::::::::::::::::::M:::MM 888888 M:::::M::::::::::::::::::::::::M:M 888888 M:::::M:::::::::@::::::::::::::M::M 88888 M::::::::::::::@@:::::::::::::::M::M 88888 M::::::::::::::@@@::::::::::::::::M::M 88888 M:::::::::::::::@@::::::::::::::::::M::M 88888 M:::::m::::::::::@::::::::::Mm:::::::M:::M 8888 M:::::M:::::::::::::::::::::::MM:::::::M:::M 8888 M:::::M:::::::::::::::::::::::MMM::::::::M:::M 888 M:::::Mm::::::::::::::::::::::MMM:::::::::M::::M 8888 MM::::Mm:::::::::::::::::::::MMMM:::::::::m::m:::M 888 M:::::M::::::::::::::::::::MMM::::::::::::M::mm:::M 8888 MM:::::::::::::::::::::::::MM:::::::::::::mM::MM:::M: M:::::::::::::::::::::::::M:::::::::::::::mM::MM:::Mm MM::::::m:::::::::::::::::::::::::::::::::::M::MM:::MM M::::::::M:::::::::::::::::::::::::::::::::::M::M:::MM MM:::::::::M:::::::::::::M:::::::::::::::::::::M:M:::MM M:::::::::::M88:::::::::M:::::::::::::::::::::::MM::MMM M::::::::::::8888888888M::::::::::::::::::::::::MM::MM M:::::::::::::88888888M:::::::::::::::::::::::::M::MM M::::::::::::::888888M:::::::::::::::::::::::::M::MM M:::::::::::::::88888M:::::::::::::::::::::::::M:MM M:::::::::::::::::88M::::::::::::::::::::::::::MMM M:::::::::::::::::::M::::::::::::::::::::::::::MMM MM:::::::::::::::::M::::::::::::::::::::::::::MMM M:::::::::::::::::M::::::::::::::::::::::::::MMM MM:::::::::::::::M::::::::::::::::::::::::::MMM M:::::::::::::::M:::::::::::::::::::::::::MMM MM:::::::::::::M:::::::::::::::::::::::::MMM M:::::::::::::M::::::::::::::::::::::::MMM MM:::::::::::M::::::::::::::::::::::::MMM M:::::::::::M:::::::::::::::::::::::MMM MM:::::::::M:::::::::::::::::::::::MMM M:::::::::M::::::::::::::::::::::MMM MM:::::::M::::::::::::::::::::::MMM MM::::::M:::::::::::::::::::::MMM MM:::::M:::::::::::::::::::::MMM MM::::M::::::::::::::::::::MMM MM:::M::::::::::::::::::::MMM MM::M:::::::::::::::::::MMM MM:M:::::::::::::::::::MMM MMM::::::::::::::::::MMM MM::::::::::::::::::MMM M:::::::::::::::::MMM MM::::::::::::::::MMM MM:::::::::::::::MMM MM::::M:::::::::MMM: mMM::::MM:::::::MMMM MMM:::::::::::MMM:M mMM:::M:::::::M:M:M MM::MMMM:::::::M:M MM::MMM::::::::M:M mMM::MM::::::::M:M MM::MM:::::::::M:M MM::MM::::::::::M:m MM:::M:::::::::::MM MMM:::::::::::::::M: MMM:::::::::::::::M: MMM::::::::::::::::M MMM::::::::::::::::M MMM::::::::::::::::Mm MM::::::::::::::::MM MMM:::::::::::::::MM MMM:::::::::::::::MM MMM:::::::::::::::MM MMM:::::::::::::::MM MM::::::::::::::MMM MMM:::::::::::::MM MMM:::::::::::::MM MMM::::::::::::MM MM::::::::::::MM MM::::::::::::MM MM:::::::::::MM MMM::::::::::MM MMM::::::::::MM MM:::::::::MM MMM::::::::MM MMM::::::::MM MM::::::::MM MMM::::::MM MMM::::::MM MM::::::MM MM::::::MM MM:::::MM MM:::::MM: MM:::::M:M MM:::::M:M :M::::::M: M:M:::::::M M:::M::::::M M::::M::::::M M:::::M:::::::M M::::::MM:::::::M M:::::::M::::::::M M;:;::::M:::::::::M M:m:;:::M::::::::::M MM:m:m::M::::::::;:M MM:m::MM:::::::;:;M MM::MMM::::::;:m:M MMMM MM::::m:m:MM MM::::m:MM MM::::MM MM::MM MMMM''' 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( HydrusData.ToUnicode( 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.MIMES_WITH_THUMBNAILS: try: thumbnail = HydrusFileHandling.GenerateThumbnail( path ) except Exception as e: tb = traceback.format_exc() raise HydrusExceptions.ForbiddenException( 'Could not generate thumbnail from that file:' + os.linesep + tb ) args[ 'thumbnail' ] = thumbnail return args hydrus_favicon = FileResource( os.path.join( HC.STATIC_DIR, 'hydrus.ico' ), defaultType = 'image/x-icon' ) 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!' ) def IsLocal( self ): return self._local_only class HydrusResource( Resource ): def __init__( self, service, domain ): Resource.__init__( self ) self._service = service self._service_key = self._service.GetServiceKey() self._domain = domain service_type = self._service.GetServiceType() self._server_version_string = HC.service_string_lookup[ service_type ] + '/' + str( HC.NETWORK_VERSION ) def _callbackCheckRestrictions( self, request ): self._domain.CheckValid( request.getClientIP() ) self._checkService( request ) self._checkUserAgent( request ) return request def _checkService( self, request ): if HG.server_busy: raise HydrusExceptions.ServerBusyException( 'This server is busy, please try again later.' ) return request 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 ' + str( HC.NETWORK_VERSION ) + ', whereas your client\'s is ' + str( network_version ) + '! ' + message ) def _callbackParseGETArgs( self, request ): hydrus_args = HydrusNetwork.ParseGETArgs( request.args ) request.hydrus_args = hydrus_args return request def _callbackParsePOSTArgs( self, request ): request.content.seek( 0 ) if not request.requestHeaders.hasHeader( 'Content-Type' ): hydrus_args = {} else: 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!' ) total_bytes_read = 0 if mime == HC.APPLICATION_JSON: json_string = request.content.read() total_bytes_read += len( json_string ) hydrus_args = HydrusNetwork.ParseBodyString( json_string ) else: ( os_file_handle, temp_path ) = HydrusPaths.GetTempPath() request.temp_file_info = ( os_file_handle, temp_path ) with open( temp_path, 'wb' ) as f: for block in HydrusPaths.ReadFileLikeAsBlocks( request.content ): f.write( block ) total_bytes_read += len( block ) hydrus_args = ParseFileArguments( temp_path ) self._reportDataUsed( request, total_bytes_read ) request.hydrus_args = hydrus_args return request def _callbackRenderResponseContext( self, request ): self._CleanUpTempFile( 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.HasPath(): path = response_context.GetPath() size = os.path.getsize( path ) mime = response_context.GetMime() if mime == HC.APPLICATION_UNKNOWN: mime = HydrusFileHandling.GetMime( path ) content_type = HC.mime_string_lookup[ mime ] content_length = size ( base, filename ) = os.path.split( path ) content_disposition = 'inline; filename="' + filename + '"' # can't be unicode! request.setHeader( 'Content-Type', str( content_type ) ) request.setHeader( 'Content-Length', str( content_length ) ) request.setHeader( 'Content-Disposition', str( content_disposition ) ) 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 elif response_context.HasBody(): mime = response_context.GetMime() body = response_context.GetBody() content_type = HC.mime_string_lookup[ mime ] content_length = len( body ) content_disposition = 'inline' request.setHeader( 'Content-Type', content_type ) request.setHeader( 'Content-Length', str( content_length ) ) request.setHeader( 'Content-Disposition', content_disposition ) request.write( HydrusData.ToByteString( body ) ) else: content_length = 0 request.setHeader( 'Content-Length', str( content_length ) ) self._reportDataUsed( request, content_length ) self._reportRequestUsed( 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 _errbackDisconnected( self, failure, request_deferred ): request_deferred.cancel() def _errbackHandleEmergencyError( self, failure, request ): try: self._CleanUpTempFile( request ) except: pass try: HydrusData.DebugPrint( failure.getTraceback() ) except: pass try: request.write( failure.getTraceback() ) except: pass try: request.finish() except: pass def _errbackHandleProcessingError( self, failure, request ): self._CleanUpTempFile( request ) default_mime = HC.TEXT_HTML default_encoding = HydrusData.ToByteString if failure.type == KeyError: response_context = ResponseContext( 400, 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.BandwidthException: response_context = ResponseContext( 509, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.PermissionException: response_context = ResponseContext( 401, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.ForbiddenException: response_context = ResponseContext( 403, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type in ( HydrusExceptions.NotFoundException, HydrusExceptions.DataMissing ): response_context = ResponseContext( 404, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.NetworkVersionException: response_context = ResponseContext( 426, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.ServerBusyException: response_context = ResponseContext( 503, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.SessionException: response_context = ResponseContext( 419, mime = default_mime, body = default_encoding( failure.value ) ) else: HydrusData.DebugPrint( failure.getTraceback() ) response_context = 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 _reportDataUsed( self, request, num_bytes ): self._service.ReportDataUsed( num_bytes ) HG.controller.ReportDataUsed( num_bytes ) def _reportRequestUsed( self, request ): self._service.ReportRequestUsed() HG.controller.ReportRequestUsed() 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 _CleanUpTempFile( self, request ): if hasattr( request, 'temp_file_info' ): ( os_file_handle, temp_path ) = request.temp_file_info HydrusPaths.CleanUpTempPath( os_file_handle, temp_path ) del request.temp_file_info 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 ) request.notifyFinish().addErrback( self._errbackDisconnected, d ) 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 ) request.notifyFinish().addErrback( self._errbackDisconnected, d ) return NOT_DONE_YET class HydrusResourceRobotsTXT( HydrusResource ): def _threadDoGETJob( self, request ): body = '''User-agent: * Disallow: /''' response_context = ResponseContext( 200, mime = HC.TEXT_PLAIN, body = body ) return response_context class HydrusResourceWelcome( HydrusResource ): def _threadDoGETJob( self, request ): body = GenerateEris( self._service, self._domain ) response_context = ResponseContext( 200, mime = HC.TEXT_HTML, body = body ) return response_context class ResponseContext( object ): def __init__( self, status_code, mime = HC.APPLICATION_JSON, body = None, path = None, cookies = None ): if isinstance( body, HydrusSerialisable.SerialisableBase ): body = body.DumpToNetworkString() if cookies is None: cookies = [] self._status_code = status_code self._mime = mime self._body = body self._path = path self._cookies = cookies def GetBody( self ): return self._body def GetCookies( self ): return self._cookies def GetLength( self ): return len( self._body ) def GetMime( self ): return self._mime def GetPath( self ): return self._path def GetStatusCode( self ): return self._status_code def HasBody( self ): return self._body is not None def HasPath( self ): return self._path is not None