from . import HydrusConstants as HC from . import HydrusExceptions from . import HydrusFileHandling from . import HydrusImageHandling from . import HydrusNetworking from . import HydrusPaths from . 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 from . import HydrusData from . import HydrusGlobals as HG 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
''' def ParseFileArguments( path, decompression_bombs_ok = False ): HydrusImageHandling.ConvertToPngIfBmp( path ) hash = HydrusFileHandling.GetHashFromPath( path ) try: mime = HydrusFileHandling.GetMime( path ) if mime in HC.DECOMPRESSION_BOMB_IMAGES and not decompression_bombs_ok: if HydrusImageHandling.IsDecompressionBomb( path ): raise HydrusExceptions.InsufficientCredentialsException( 'File seemed to be a Decompression Bomb!' ) ( size, mime, width, height, duration, num_frames, has_audio, num_words ) = HydrusFileHandling.GetFileInfo( path, mime ) except Exception as e: raise HydrusExceptions.BadRequestException( 'File ' + hash.hex() + ' could not parse: ' + str( e ) ) args = HydrusNetworking.ParsedRequestArguments() 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 args[ 'has_audio' ] = has_audio if num_words is not None: args[ 'num_words' ] = num_words if mime in HC.MIMES_WITH_THUMBNAILS: try: bounding_dimensions = HC.SERVER_THUMBNAIL_DIMENSIONS target_resolution = HydrusImageHandling.GetThumbnailResolution( ( width, height ), bounding_dimensions ) thumbnail_bytes = HydrusFileHandling.GenerateThumbnailBytes( path, target_resolution, mime, duration, num_frames ) except Exception as e: tb = traceback.format_exc() raise HydrusExceptions.BadRequestException( 'Could not generate thumbnail from that file:' + os.linesep + tb ) args[ 'thumbnail' ] = thumbnail_bytes 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.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 ): return request def _callbackCheckServiceRestrictions( self, request ): self._domain.CheckValid( request.getClientIP() ) self._checkService( request ) self._checkUserAgent( request ) return request def _callbackEstablishAccountFromHeader( self, request ): return request def _callbackEstablishAccountFromArgs( self, request ): return request def _callbackParseGETArgs( self, request ): return request def _callbackParsePOSTArgs( self, 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 _callbackRenderResponseContext( self, request ): 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 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() content_type = HC.mime_string_lookup[ mime ] content_length = size ( base, filename ) = os.path.split( path ) content_disposition = 'inline; filename="' + filename + '"' 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', 'max-age={}'.format( 86400 * 365 ) ) fileObject = open( path, 'rb' ) producer = NoRangeStaticProducer( request, fileObject ) producer.start() do_finish = False elif response_context.HasBody(): mime = response_context.GetMime() body_bytes = response_context.GetBodyBytes() content_type = HC.mime_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 _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 _callbackDoOPTIONSJob( self, request ): def wrap_thread_result( response_context ): request.hydrus_response_context = response_context return request d = deferToThread( self._threadDoOPTIONSJob, 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 _DecompressionBombsOK( self, request ): return False 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 if 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 def _errbackHandleProcessingError( self, failure, request ): self._CleanUpTempFile( request ) 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.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 return request def _parseHydrusNetworkAccessKey( self, request ): if not request.requestHeaders.hasHeader( 'Hydrus-Key' ): raise HydrusExceptions.MissingCredentialsException( 'No hydrus key header found!' ) hex_keys = request.requestHeaders.getRawHeaders( 'Hydrus-Key' ) hex_key = hex_keys[0] try: access_key = bytes.fromhex( hex_key ) except: raise HydrusExceptions.InsufficientCredentialsException( '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 _threadDoOPTIONSJob( self, request ): 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', 'Hydrus-Client-API-Access-Key' ) 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 ): 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._callbackCheckServiceRestrictions ) d.addCallback( self._callbackEstablishAccountFromHeader ) d.addCallback( self._callbackParseGETArgs ) d.addCallback( self._callbackEstablishAccountFromArgs ) d.addCallback( self._callbackCheckAccountRestrictions ) d.addCallback( self._callbackDoGETJob ) d.addErrback( self._errbackHandleProcessingError, request ) d.addCallback( self._callbackRenderResponseContext ) d.addErrback( self._errbackHandleEmergencyError, request ) request.notifyFinish().addErrback( self._errbackDisconnected, d ) reactor.callLater( 0, d.callback, request ) return NOT_DONE_YET def render_OPTIONS( self, request ): request.setHeader( 'Server', self._server_version_string ) d = defer.Deferred() d.addCallback( self._callbackCheckServiceRestrictions ) d.addCallback( self._callbackDoOPTIONSJob ) d.addErrback( self._errbackHandleProcessingError, request ) d.addCallback( self._callbackRenderResponseContext ) d.addErrback( self._errbackHandleEmergencyError, request ) request.notifyFinish().addErrback( self._errbackDisconnected, d ) 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._callbackCheckServiceRestrictions ) d.addCallback( self._callbackEstablishAccountFromHeader ) d.addCallback( self._callbackParsePOSTArgs ) d.addCallback( self._callbackEstablishAccountFromArgs ) d.addCallback( self._callbackCheckAccountRestrictions ) d.addCallback( self._callbackDoPOSTJob ) d.addErrback( self._errbackHandleProcessingError, request ) d.addCallback( self._callbackRenderResponseContext ) d.addErrback( self._errbackHandleEmergencyError, request ) request.notifyFinish().addErrback( self._errbackDisconnected, d ) reactor.callLater( 0, d.callback, request ) 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 ) 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