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, SingleRangeStaticProducer, MultipleRangeStaticProducer from hydrus.core import HydrusConstants as HC from hydrus.core import HydrusData from hydrus.core import HydrusExceptions from hydrus.core import HydrusGlobals as HG from hydrus.core import HydrusPaths from hydrus.core import HydrusSerialisable from hydrus.core.networking import HydrusServerRequest def GenerateEris( service ): name = service.GetName() service_type = service.GetServiceType() allows_non_local_connections = service.AllowsNonLocalConnections() 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 ) if service_type == HC.CLIENT_API_SERVICE: welcome_text_4 = 'API version ' + str( HC.CLIENT_API_VERSION ) else: welcome_text_4 = 'Network version ' + str( HC.NETWORK_VERSION ) if allows_non_local_connections: welcome_text_5 = 'It responds to requests from any host.' else: welcome_text_5 = 'It only responds to requests from localhost.' return '''''' + name + '''
                         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
''' 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 ): # >::ffff:127.0.0.1 #▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓█████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ #▒▓▓▓▒▒▒▓▓▓▓▓▓▓▒▒▓▓▓▓▒▒░░░░ ░░▒▒▒▒▒▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▒▒▒▓▓▓ #▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▒▒░░ ░░░░░ ░▒▒▓▓▓▓▓▓▓▓▒▓▓▓▓▓▒▓▓▓▒▓▓ #▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒▓▓▓▓▓▒▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▒▒ #▒▒▓▓▓▓▓▓▓▒▒▓▓▒▒▒░░░░░▒▒▒▒▒▒▓▓▓▓█▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▒ #▓▓▓▓▓▓▓▓▓▓▓▒▓▓░ ░░░░▓▓█▓█▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▒▓ #▓▓▓▓▒▓▓▓▓▓▓▓▓░ ░ ▒▓██▓█▓▓▓▓▓▓█▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓ #▓▓▓▓▓▒▓▓▓▓▓█▒ ░ ░░░ ▓████▓▓▓▓▓█▓▒▒▓▓▓▓▓▓▓▒▒▓▓▓▓ #▓▓▓▓▓▓▓▒▓██▓ ░░░░░░░░░░░░ ░ ▒▓█▓▓▓▓▓▓▓█▓▓▒▓▓▓▓▓▒▓▓▓▒▓▓ #▒▓▓▓▓▓▓▓▓██░ ░▒▒▒░▒░░░░░░░░░░░ ░▓█▓▓▓███▓█▓▓▓▒▓▓▓▓▓▓▓▓▒▒ #▒▒▓▓▓▓▓▓▓█▓ ░▒░▒▒▒▒▒░░░░░░░░░░░░ ▒▓█▓█▓███▓▓▓▓▓▒▓▓▓▓▓▓▓▓▒ #▓▓▓▒▓▓▓▓▓█▓░▒▒▒▒▒▒▒░▒░░░▒░░░░░░░░░▒▓▓▓▓▓█▒ ░▒▓▓▓▓▒▓▓▓▓▓▒▓ #▓▓▓▓▒▓▓▓▓█▓░▒▓▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░▒▓▓█▓░░▒▒▓▓▓▓▓▒▓▓▓▓▓▓ #▓▓▓▓▓▒▓▓▓█▓░▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░ ▒██▒▒▒░░▓▓▓▓▓▓▒▒▓▓▓▓ #▓▓▓▓▓▓▓▒▒█▓░░▒▒▒▒▒▒▒▒▒▒▒ ░░░░░░░▒░░ ░█▓ ░▒▒░▓▓▓▓▓▒▓▓▓▒▓▓ #▒▓▓▓▓▓▓▓▓▓▓▒▒▓▒▒▓▓▓▒▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒░░▒█▓ ▒▒ ▒▓▓▓▓▓▓▓▓▓▓▒▓ #▒▒▓▓▓▓▓▓▓▓▓▒▒▓▓█▓▓▓▓▓▒▒▓▓██▓▓▓▒▒▒▒░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▓▒ #▓▓▓▒▓▓▓▓▓░ ▓▒▓▓▓▓▓▓▒▓▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░░░░▒▒ ▒█▓▓▓▓▓▓▓▒▓ #▓▓▓▓▒▓▓▓▓▓▒▓▒▒▒▒▓▓▓▓▒░▒▒░░░▒▒▒░░░░░░░░░░░░░▒ ░▓▓████▓ #▓▓▓▓▓▒▓▓▓▓▓▓▒▒▒▒▒▒▓▓░░░▒▒▒▒▒▒▒░░░░▒▒▒▒░░▒░░░ ▒▒▓ #▓▓▓▓▓▓▓▒▒▓▓▓▓▒▒▒▒▓▓▒░░░░▒▒▒▒▒▓▒▒▒▒▒▒▒▒░░▒▒▒░ ░ #▒▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒░░▒▓▓▓▒░▒▒▒▒▒▒▒▒▒ ▒░ #▒▒▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▓▓▓▒▒▒▒▒▒▒▒▒▒ ░▒░ ░░░░ #▓▓▓▒▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒ ░▓░ ░░░░ #▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▒▒▒▒░▒▒▒▒▒▒▒▒▓▓▒ ░ ░▓▒ ░░░░ #▓▓▓▓▓▒▒▓▓▓▓▓██▓▒░ ▒▓▓▓▓▒▓▒▒▒▒░░▒▒▒▒▓▓▒░░░ ▒▒▓░ ░▒ #▒▓▓▓▓▓▓▓▒▓▓█▓░ ▒▓▓▒▒▒▒░░▒▒▒▒▒▓▓▓▒░░░ ▒▒▒▒░░░░░░░▒▒░ #▒▒▓▓▓▓▓▓█▓▒░ ░ ░ ░▒▓▓▓▒▒▒▒▒▒▓▓█▓▒░░░░ ▒▒▒▒ ░░░▒▒░▒▒░░ #▒▒▒▓▓▓▓█▓░ ░ ░░ ▒░▒▒▒▒▓██████▓░░░░ ▒▒▒▒░ ░ ▒▒░▒▒░░ if self._local_only and client_ip not in ( '127.0.0.1', '::1', '::ffff:127.0.0.1' ): raise HydrusExceptions.InsufficientCredentialsException( '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 _callbackCheckAccountRestrictions( self, request: HydrusServerRequest.HydrusRequest ): return request def _callbackCheckServiceRestrictions( self, request: HydrusServerRequest.HydrusRequest ): self._domain.CheckValid( request.getClientIP() ) self._checkService( request ) self._checkUserAgent( request ) return request def _callbackDoGETJob( self, request: HydrusServerRequest.HydrusRequest ): def wrap_thread_result( response_context ): request.hydrus_response_context = response_context return request if HG.server_profile_mode: d = deferToThread( self._profileJob, self._threadDoGETJob, request ) else: d = deferToThread( self._threadDoGETJob, request ) d.addCallback( wrap_thread_result ) return d def _callbackDoOPTIONSJob( self, request: HydrusServerRequest.HydrusRequest ): def wrap_thread_result( response_context ): request.hydrus_response_context = response_context return request if HG.server_profile_mode: d = deferToThread( self._profileJob, self._threadDoOPTIONSJob, request ) else: d = deferToThread( self._threadDoOPTIONSJob, request ) d.addCallback( wrap_thread_result ) return d def _callbackDoPOSTJob( self, request: HydrusServerRequest.HydrusRequest ): def wrap_thread_result( response_context ): request.hydrus_response_context = response_context return request if HG.server_profile_mode: d = deferToThread( self._profileJob, self._threadDoPOSTJob, request ) else: d = deferToThread( self._threadDoPOSTJob, request ) d.addCallback( wrap_thread_result ) return d def _callbackEstablishAccountFromHeader( self, request: HydrusServerRequest.HydrusRequest ): return request def _callbackEstablishAccountFromArgs( self, request: HydrusServerRequest.HydrusRequest ): return request def _callbackParseGETArgs( self, request: HydrusServerRequest.HydrusRequest ): return request def _callbackParsePOSTArgs( self, request: HydrusServerRequest.HydrusRequest ): return request def _callbackRenderResponseContext( self, request: HydrusServerRequest.HydrusRequest ): self._CleanUpTempFile( request ) if request.channel is None: # Connection was lost, it seems. # no need for request.finish return if request.requestHeaders.hasHeader( 'Origin' ): if self._service.SupportsCORS(): request.setHeader( 'Access-Control-Allow-Origin', '*' ) response_context = request.hydrus_response_context if response_context.HasPath(): path = response_context.GetPath() filesize = os.path.getsize( path ) offset_and_block_size_pairs = self._parseRangeHeader( request, filesize ) else: offset_and_block_size_pairs = [] status_code = response_context.GetStatusCode() if status_code == 200 and response_context.HasPath() and len( offset_and_block_size_pairs ) > 0: status_code = 206 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() filesize = os.path.getsize( path ) mime = response_context.GetMime() content_type = HC.mime_mimetype_string_lookup[ mime ] ( base, filename ) = os.path.split( path ) fileObject = open( path, 'rb' ) content_disposition = 'inline; filename="' + filename + '"' 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', 'max-age={}'.format( 86400 * 365 ) ) if len( offset_and_block_size_pairs ) <= 1: request.setHeader( 'Content-Type', str( content_type ) ) if len( offset_and_block_size_pairs ) == 0: content_length = filesize request.setHeader( 'Content-Length', str( content_length ) ) producer = NoRangeStaticProducer( request, fileObject ) elif len( offset_and_block_size_pairs ) == 1: ( range_start, range_end, offset, block_size ) = offset_and_block_size_pairs[0] header_range_end = filesize - 1 if range_end is None else range_end content_length = block_size request.setHeader( 'Accept-Ranges', 'bytes' ) request.setHeader( 'Content-Range', 'bytes {}-{}/{}'.format( offset, header_range_end, filesize ) ) request.setHeader( 'Content-Length', str( content_length ) ) producer = SingleRangeStaticProducer( request, fileObject, offset, block_size ) else: # hey, what a surprise, an http data transmission standard turned out to be a massive PITA # MultipleRangeStaticProducer is the lad to use, but you have to figure out your own separation bits, which have even more finicky rules. more than I can deal with with the current time I have # if/when you want to do this, check out the FileResource, it does it in its internal gubbins raise HydrusExceptions.RangeNotSatisfiableException( 'Can only support Single Range requests at the moment!' ) producer.start() do_finish = False elif response_context.HasBody(): mime = response_context.GetMime() body_bytes = response_context.GetBodyBytes() content_type = HC.mime_mimetype_string_lookup[ mime ] content_length = len( body_bytes ) content_disposition = 'inline' request.setHeader( 'Content-Type', content_type ) request.setHeader( 'Content-Length', str( content_length ) ) request.setHeader( 'Content-Disposition', content_disposition ) request.write( body_bytes ) else: content_length = 0 if status_code != 204: # 204 is No Content request.setHeader( 'Content-Length', str( content_length ) ) self._reportDataUsed( request, content_length ) self._reportRequestUsed( request ) if do_finish: request.finish() def _checkService( self, request: HydrusServerRequest.HydrusRequest ): return request def _checkUserAgent( self, request: HydrusServerRequest.HydrusRequest ): 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 _profileJob( self, call, request: HydrusServerRequest.HydrusRequest ): HydrusData.Profile( 'client api {}'.format( request.path ), 'request.result_lmao = call( request )', globals(), locals(), min_duration_ms = 3, show_summary = True ) return request.result_lmao def _DecompressionBombsOK( self, request: HydrusServerRequest.HydrusRequest ): return False def _errbackDisconnected( self, failure, request_deferred ): request_deferred.cancel() def _errbackHandleProcessingError( self, failure, request: HydrusServerRequest.HydrusRequest ): try: try: self._CleanUpTempFile( request ) except: pass default_mime = HC.TEXT_HTML default_encoding = str if failure.type == HydrusExceptions.BadRequestException: response_context = ResponseContext( 400, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type in ( HydrusExceptions.MissingCredentialsException, HydrusExceptions.DoesNotSupportCORSException ): response_context = ResponseContext( 401, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.InsufficientCredentialsException: response_context = ResponseContext( 403, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type in ( HydrusExceptions.NotFoundException, HydrusExceptions.DataMissing, HydrusExceptions.FileMissingException ): response_context = ResponseContext( 404, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.ConflictException: response_context = ResponseContext( 409, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.RangeNotSatisfiableException: response_context = ResponseContext( 416, 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 ) ) 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.BandwidthException: response_context = ResponseContext( 509, mime = default_mime, body = default_encoding( failure.value ) ) elif failure.type == HydrusExceptions.ServerException: response_context = ResponseContext( 500, 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 self._callbackRenderResponseContext( request ) except: try: HydrusData.DebugPrint( failure.getTraceback() ) except: pass if hasattr( request, 'channel' ) and request.channel is not None: try: request.setResponseCode( 500 ) except: pass try: request.write( failure.getTraceback() ) except: pass if not request.finished: try: request.finish() except: pass return request def _parseHydrusNetworkAccessKey( self, request, key_required = True ): if not request.requestHeaders.hasHeader( 'Hydrus-Key' ): if key_required: raise HydrusExceptions.MissingCredentialsException( 'No hydrus key header found!' ) else: return None hex_keys = request.requestHeaders.getRawHeaders( 'Hydrus-Key' ) hex_key = hex_keys[0] try: access_key = bytes.fromhex( hex_key ) except: raise HydrusExceptions.BadRequestException( 'Could not parse the hydrus key!' ) return access_key def _parseRangeHeader( self, request, filesize ): offset_and_block_size_pairs = [] if request.requestHeaders.hasHeader( 'Range' ): range_headers = request.requestHeaders.getRawHeaders( 'Range' ) range_header = range_headers[0] if '=' not in range_header: raise HydrusExceptions.BadRequestException( 'Did not understand range header!' ) ( unit_gumpf, range_pairs_string ) = range_header.split( '=', 1 ) if unit_gumpf != 'bytes': raise HydrusExceptions.RangeNotSatisfiableException( 'Do not support anything other than bytes in Range header!' ) range_pair_strings = range_pairs_string.split( ',' ) if True in ( '-' not in range_pair_string for range_pair_string in range_pair_strings ): raise HydrusExceptions.RangeNotSatisfiableException( 'Did not understand the Range header\'s range pair(s)!' ) range_pairs = [ range_pair_string.strip().split( '-' ) for range_pair_string in range_pair_strings ] offset_and_block_size_pairs = [] for ( range_start, range_end ) in range_pairs: if range_start == '': if range_end == '': raise HydrusExceptions.RangeNotSatisfiableException( 'Undefined Range header pair given!' ) range_start = None else: range_start = abs( int( range_start ) ) if range_end == '': range_end = None else: range_end = abs( int( range_end ) ) if range_start is not None and range_end is not None and range_start > range_end: raise HydrusExceptions.RangeNotSatisfiableException( 'The Range header had an invalid pair!' ) if range_start is None: offset = filesize - range_end block_size = range_end elif range_end is None: offset = range_start block_size = filesize - range_start else: if range_start > filesize: offset_and_block_size_pairs = [] break if range_end > filesize: range_end = filesize - 1 offset = range_start block_size = ( range_end + 1 ) - range_start offset_and_block_size_pairs.append( ( range_start, range_end, offset, block_size ) ) return offset_and_block_size_pairs def _reportDataUsed( self, request, num_bytes ): self._service.ReportDataUsed( num_bytes ) HG.controller.ReportDataUsed( num_bytes ) def _reportRequestUsed( self, request: HydrusServerRequest.HydrusRequest ): self._service.ReportRequestUsed() HG.controller.ReportRequestUsed() def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ): raise HydrusExceptions.NotFoundException( 'This service does not support that request!' ) def _threadDoOPTIONSJob( self, request: HydrusServerRequest.HydrusRequest ): allowed_methods = [] if self._threadDoGETJob.__func__ is not HydrusResource._threadDoGETJob: allowed_methods.append( 'GET' ) if self._threadDoPOSTJob.__func__ is not HydrusResource._threadDoPOSTJob: allowed_methods.append( 'POST' ) allowed_methods_string = ', '.join( allowed_methods ) if request.requestHeaders.hasHeader( 'Origin' ): # this is a CORS request if self._service.SupportsCORS(): request.setHeader( 'Access-Control-Allow-Headers', '*' ) request.setHeader( 'Access-Control-Allow-Origin', '*' ) request.setHeader( 'Access-Control-Allow-Methods', allowed_methods_string ) else: # 401 raise HydrusExceptions.DoesNotSupportCORSException( 'This service does not support CORS.' ) else: # regular OPTIONS request request.setHeader( 'Allow', allowed_methods_string ) # 204 No Content response_context = ResponseContext( 200, mime = HC.TEXT_PLAIN ) return response_context def _threadDoPOSTJob( self, request: HydrusServerRequest.HydrusRequest ): raise HydrusExceptions.NotFoundException( 'This service does not support that request!' ) def _CleanUpTempFile( self, request: HydrusServerRequest.HydrusRequest ): 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: HydrusServerRequest.HydrusRequest ): d = defer.Deferred() d.addCallback( self._callbackCheckServiceRestrictions ) d.addCallback( self._callbackEstablishAccountFromHeader ) d.addCallback( self._callbackParseGETArgs ) d.addCallback( self._callbackEstablishAccountFromArgs ) d.addCallback( self._callbackCheckAccountRestrictions ) d.addCallback( self._callbackDoGETJob ) d.addCallback( self._callbackRenderResponseContext ) d.addErrback( self._errbackHandleProcessingError, request ) request.notifyFinish().addErrback( self._errbackDisconnected, d ) reactor.callLater( 0, d.callback, request ) return NOT_DONE_YET def render_OPTIONS( self, request: HydrusServerRequest.HydrusRequest ): d = defer.Deferred() d.addCallback( self._callbackCheckServiceRestrictions ) d.addCallback( self._callbackDoOPTIONSJob ) d.addCallback( self._callbackRenderResponseContext ) d.addErrback( self._errbackHandleProcessingError, request ) request.notifyFinish().addErrback( self._errbackDisconnected, d ) reactor.callLater( 0, d.callback, request ) return NOT_DONE_YET def render_POST( self, request: HydrusServerRequest.HydrusRequest ): d = defer.Deferred() d.addCallback( self._callbackCheckServiceRestrictions ) d.addCallback( self._callbackEstablishAccountFromHeader ) d.addCallback( self._callbackParsePOSTArgs ) d.addCallback( self._callbackEstablishAccountFromArgs ) d.addCallback( self._callbackCheckAccountRestrictions ) d.addCallback( self._callbackDoPOSTJob ) d.addCallback( self._callbackRenderResponseContext ) d.addErrback( self._errbackHandleProcessingError, request ) request.notifyFinish().addErrback( self._errbackDisconnected, d ) reactor.callLater( 0, d.callback, request ) return NOT_DONE_YET class HydrusResourceRobotsTXT( HydrusResource ): def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ): body = '''User-agent: * Disallow: /''' response_context = ResponseContext( 200, mime = HC.TEXT_PLAIN, body = body ) return response_context class HydrusResourceWelcome( HydrusResource ): def _threadDoGETJob( self, request: HydrusServerRequest.HydrusRequest ): body = GenerateEris( self._service ) 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 body is None: body_bytes = None elif isinstance( body, HydrusSerialisable.SerialisableBase ): body_bytes = body.DumpToNetworkBytes() elif isinstance( body, str ): body_bytes = bytes( body, 'utf-8' ) elif isinstance( body, bytes ): body_bytes = body else: raise Exception( 'Was given an incompatible object to respond with: ' + repr( body ) ) if cookies is None: cookies = [] self._status_code = status_code self._mime = mime self._body_bytes = body_bytes self._path = path self._cookies = cookies def GetBodyBytes( self ): return self._body_bytes def GetCookies( self ): return self._cookies 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_bytes is not None def HasPath( self ): return self._path is not None