2022-03-23 20:57:10 +00:00
import collections
2021-04-07 21:26:45 +00:00
import json
import os
import traceback
2023-01-25 22:59:39 +00:00
import typing
2021-04-07 21:26:45 +00:00
import urllib
2022-03-07 02:46:01 +00:00
CBOR_AVAILABLE = False
try :
import cbor2
import base64
CBOR_AVAILABLE = True
except :
pass
2021-04-07 21:26:45 +00:00
from hydrus . core import HydrusConstants as HC
from hydrus . core import HydrusExceptions
from hydrus . core import HydrusFileHandling
from hydrus . core import HydrusImageHandling
from hydrus . core import HydrusSerialisable
from hydrus . core . networking import HydrusNetwork
INT_PARAMS = { ' expires ' , ' num ' , ' since ' , ' content_type ' , ' action ' , ' status ' }
2021-05-27 00:09:06 +00:00
BYTE_PARAMS = { ' access_key ' , ' account_type_key ' , ' subject_account_key ' , ' registration_key ' , ' hash ' , ' subject_hash ' , ' update_hash ' }
2021-04-07 21:26:45 +00:00
STRING_PARAMS = { ' subject_tag ' , ' reason ' , ' message ' }
JSON_PARAMS = set ( )
JSON_BYTE_LIST_PARAMS = { ' registration_keys ' }
2021-05-27 00:09:06 +00:00
HASH_BYTE_PARAMS = { ' hash ' , ' subject_hash ' , ' update_hash ' }
2021-04-07 21:26:45 +00:00
def DumpHydrusArgsToNetworkBytes ( args ) :
if not isinstance ( args , HydrusSerialisable . SerialisableBase ) :
args = HydrusSerialisable . SerialisableDictionary ( args )
for param_name in BYTE_PARAMS :
if param_name in args :
args [ param_name ] = args [ param_name ] . hex ( )
for param_name in JSON_BYTE_LIST_PARAMS :
if param_name in args :
args [ param_name ] = [ item . hex ( ) for item in args [ param_name ] ]
if ' account_types ' in args :
args [ ' account_types ' ] = HydrusSerialisable . SerialisableList ( args [ ' account_types ' ] )
if ' account ' in args :
args [ ' account ' ] = HydrusNetwork . Account . GenerateSerialisableTupleFromAccount ( args [ ' account ' ] )
if ' accounts ' in args :
args [ ' accounts ' ] = list ( map ( HydrusNetwork . Account . GenerateSerialisableTupleFromAccount , args [ ' accounts ' ] ) )
if ' service_keys_to_access_keys ' in args :
args [ ' service_keys_to_access_keys ' ] = [ ( service_key . hex ( ) , access_key . hex ( ) ) for ( service_key , access_key ) in list ( args [ ' service_keys_to_access_keys ' ] . items ( ) ) ]
if ' services ' in args :
args [ ' services ' ] = [ service . ToSerialisableTuple ( ) for service in args [ ' services ' ] ]
network_bytes = args . DumpToNetworkBytes ( )
return network_bytes
def DumpToGETQuery ( args ) :
args = dict ( args )
if ' subject_identifier ' in args :
subject_identifier = args [ ' subject_identifier ' ]
del args [ ' subject_identifier ' ]
if subject_identifier . HasAccountKey ( ) :
account_key = subject_identifier . GetAccountKey ( )
args [ ' subject_account_key ' ] = account_key
elif subject_identifier . HasContent ( ) :
content = subject_identifier . GetContent ( )
content_type = content . GetContentType ( )
content_data = content . GetContentData ( )
if content_type == HC . CONTENT_TYPE_FILES :
hash = content_data [ 0 ]
args [ ' subject_hash ' ] = hash
elif content_type == HC . CONTENT_TYPE_MAPPING :
( tag , hash ) = content_data
args [ ' subject_hash ' ] = hash
args [ ' subject_tag ' ] = tag
for name in INT_PARAMS :
if name in args :
args [ name ] = str ( args [ name ] )
for name in BYTE_PARAMS :
if name in args :
args [ name ] = args [ name ] . hex ( )
for name in STRING_PARAMS :
if name in args :
args [ name ] = urllib . parse . quote ( args [ name ] )
query = ' & ' . join ( [ key + ' = ' + value for ( key , value ) in args . items ( ) ] )
return query
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, which you cannot upload! ' )
2021-10-13 20:16:57 +00:00
( size , mime , width , height , duration , num_frames , has_audio , num_words ) = HydrusFileHandling . GetFileInfo ( path , mime = mime )
2021-04-07 21:26:45 +00:00
except Exception as e :
raise HydrusExceptions . BadRequestException ( ' File ' + hash . hex ( ) + ' could not parse: ' + str ( e ) )
args = 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
2022-12-21 22:00:27 +00:00
( clip_rect , target_resolution ) = HydrusImageHandling . GetThumbnailResolutionAndClipRegion ( ( width , height ) , bounding_dimensions , HydrusImageHandling . THUMBNAIL_SCALE_DOWN_ONLY , 100 )
2021-04-07 21:26:45 +00:00
2022-02-02 22:14:01 +00:00
thumbnail_bytes = HydrusFileHandling . GenerateThumbnailBytes ( path , target_resolution , mime , duration , num_frames , clip_rect = clip_rect )
2021-04-07 21:26:45 +00:00
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
def ParseHydrusNetworkGETArgs ( requests_args ) :
args = ParseTwistedRequestGETArgs ( requests_args , INT_PARAMS , BYTE_PARAMS , STRING_PARAMS , JSON_PARAMS , JSON_BYTE_LIST_PARAMS )
2022-11-23 21:01:41 +00:00
if ' subject_hash ' in args : # or parent/sib stuff in args
2021-04-07 21:26:45 +00:00
hash = args [ ' subject_hash ' ]
if ' subject_tag ' in args :
tag = args [ ' subject_tag ' ]
content = HydrusNetwork . Content ( HC . CONTENT_TYPE_MAPPING , ( tag , hash ) )
else :
content = HydrusNetwork . Content ( HC . CONTENT_TYPE_FILES , [ hash ] )
2022-11-23 21:01:41 +00:00
# TODO: add siblings and parents here
2021-04-07 21:26:45 +00:00
args [ ' subject_identifier ' ] = HydrusNetwork . AccountIdentifier ( content = content )
return args
def ParseNetworkBytesToParsedHydrusArgs ( network_bytes ) :
if len ( network_bytes ) == 0 :
return HydrusSerialisable . SerialisableDictionary ( )
args = HydrusSerialisable . CreateFromNetworkBytes ( network_bytes )
if not isinstance ( args , dict ) :
raise HydrusExceptions . BadRequestException ( ' The given parameter did not seem to be a JSON Object! ' )
args = ParsedRequestArguments ( args )
for param_name in BYTE_PARAMS :
if param_name in args :
2021-05-27 00:09:06 +00:00
value = args [ param_name ]
if param_name in HASH_BYTE_PARAMS and ' : ' in value :
value = value . split ( ' : ' , 1 ) [ 1 ]
args [ param_name ] = bytes . fromhex ( value )
2021-04-07 21:26:45 +00:00
for param_name in JSON_BYTE_LIST_PARAMS :
if param_name in args :
args [ param_name ] = [ bytes . fromhex ( encoded_item ) for encoded_item in args [ param_name ] ]
# account_types should be a serialisable list, so it just works
if ' account ' in args :
args [ ' account ' ] = HydrusNetwork . Account . GenerateAccountFromSerialisableTuple ( args [ ' account ' ] )
if ' accounts ' in args :
account_tuples = args [ ' accounts ' ]
args [ ' accounts ' ] = [ HydrusNetwork . Account . GenerateAccountFromSerialisableTuple ( account_tuple ) for account_tuple in account_tuples ]
if ' service_keys_to_access_keys ' in args :
args [ ' service_keys_to_access_keys ' ] = { bytes . fromhex ( encoded_service_key ) : bytes . fromhex ( encoded_access_key ) for ( encoded_service_key , encoded_access_key ) in args [ ' service_keys_to_access_keys ' ] }
if ' services ' in args :
service_tuples = args [ ' services ' ]
args [ ' services ' ] = [ HydrusNetwork . GenerateServiceFromSerialisableTuple ( service_tuple ) for service_tuple in service_tuples ]
return args
2023-01-25 22:59:39 +00:00
def ParseTwistedRequestGETArgs ( requests_args : dict , int_params , byte_params , string_params , json_params , json_byte_list_params ) :
2021-04-07 21:26:45 +00:00
args = ParsedRequestArguments ( )
2022-03-07 02:46:01 +00:00
cbor_requested = b ' cbor ' in requests_args
2022-04-06 20:40:17 +00:00
if cbor_requested and not CBOR_AVAILABLE :
raise HydrusExceptions . NotAcceptable ( ' Sorry, this service does not support CBOR! ' )
2023-01-25 22:59:39 +00:00
for ( name_bytes , values_bytes ) in requests_args . items ( ) :
2021-04-07 21:26:45 +00:00
try :
name = str ( name_bytes , ' utf-8 ' )
except UnicodeDecodeError :
continue
value_bytes = values_bytes [ 0 ]
try :
value = str ( value_bytes , ' utf-8 ' )
except UnicodeDecodeError :
continue
if name in int_params :
try :
args [ name ] = int ( value )
except Exception as e :
raise HydrusExceptions . BadRequestException ( ' I was expecting to parse \' ' + name + ' \' as an integer, but it failed. ' ) from e
elif name in byte_params :
try :
2021-05-27 00:09:06 +00:00
if name in HASH_BYTE_PARAMS and ' : ' in value :
value = value . split ( ' : ' , 1 ) [ 1 ]
2021-04-07 21:26:45 +00:00
args [ name ] = bytes . fromhex ( value )
except Exception as e :
raise HydrusExceptions . BadRequestException ( ' I was expecting to parse \' ' + name + ' \' as a hex string, but it failed. ' ) from e
elif name in string_params :
try :
args [ name ] = urllib . parse . unquote ( value )
except Exception as e :
raise HydrusExceptions . BadRequestException ( ' I was expecting to parse \' ' + name + ' \' as a percent-encdode string, but it failed. ' ) from e
elif name in json_params :
try :
2022-04-06 20:40:17 +00:00
if cbor_requested :
2022-03-07 02:46:01 +00:00
args [ name ] = cbor2 . loads ( base64 . urlsafe_b64decode ( value ) )
else :
args [ name ] = json . loads ( urllib . parse . unquote ( value ) )
2021-04-07 21:26:45 +00:00
except Exception as e :
raise HydrusExceptions . BadRequestException ( ' I was expecting to parse \' ' + name + ' \' as a json-encoded string, but it failed. ' ) from e
elif name in json_byte_list_params :
try :
2022-04-06 20:40:17 +00:00
if cbor_requested :
2022-03-07 02:46:01 +00:00
list_of_hex_strings = cbor2 . loads ( base64 . urlsafe_b64decode ( value ) )
else :
list_of_hex_strings = json . loads ( urllib . parse . unquote ( value ) )
2021-04-07 21:26:45 +00:00
args [ name ] = [ bytes . fromhex ( hex_string ) for hex_string in list_of_hex_strings ]
except Exception as e :
raise HydrusExceptions . BadRequestException ( ' I was expecting to parse \' ' + name + ' \' as a json-encoded hex strings, but it failed. ' ) from e
return args
2023-01-25 22:59:39 +00:00
variable_type_to_text_lookup = collections . defaultdict ( lambda : ' unknown! ' )
variable_type_to_text_lookup [ int ] = ' integer '
variable_type_to_text_lookup [ str ] = ' string '
variable_type_to_text_lookup [ bytes ] = ' hex-encoded bytestring '
variable_type_to_text_lookup [ bool ] = ' boolean '
variable_type_to_text_lookup [ list ] = ' list '
variable_type_to_text_lookup [ dict ] = ' object/dict '
def GetValueFromDict ( dictionary : dict , key , expected_type , expected_list_type = None , expected_dict_types = None , default_value = None , none_on_missing = False ) :
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
# not None because in JSON sometimes people put 'null' to mean 'did not enter this optional parameter'
if key in dictionary and dictionary [ key ] is not None :
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
value = dictionary [ key ]
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
TestVariableType ( key , value , expected_type , expected_list_type = expected_list_type , expected_dict_types = expected_dict_types )
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
return value
else :
if default_value is None and not none_on_missing :
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
raise HydrusExceptions . BadRequestException ( ' The required parameter " {} " was missing! ' . format ( key ) )
2021-07-28 21:12:00 +00:00
2023-01-25 22:59:39 +00:00
else :
2021-07-28 21:12:00 +00:00
2023-01-25 22:59:39 +00:00
return default_value
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
def TestVariableType ( name : str , value : typing . Any , expected_type : type , expected_list_type = None , expected_dict_types = None , allowed_values = None ) :
if not isinstance ( value , expected_type ) :
type_error_text = variable_type_to_text_lookup [ expected_type ]
raise HydrusExceptions . BadRequestException ( ' The parameter " {} " , with value " {} " , was not the expected type: {} ! ' . format ( name , value , type_error_text ) )
if allowed_values is not None and value not in allowed_values :
raise HydrusExceptions . BadRequestException ( ' The parameter " {} " , with value " {} " , was not in the allowed values: {} ! ' . format ( name , value , allowed_values ) )
if expected_type is list and expected_list_type is not None :
for item in value :
2022-03-23 20:57:10 +00:00
2023-01-25 22:59:39 +00:00
if not isinstance ( item , expected_list_type ) :
2022-03-23 20:57:10 +00:00
2023-01-25 22:59:39 +00:00
raise HydrusExceptions . BadRequestException ( ' The list parameter " {} " held an item, " {} " that was {} and not the expected type: {} ! ' . format ( name , item , type ( item ) , variable_type_to_text_lookup [ expected_list_type ] ) )
2022-03-23 20:57:10 +00:00
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
if expected_type is dict and expected_dict_types is not None :
( expected_key_type , expected_value_type ) = expected_dict_types
for ( dict_key , dict_value ) in value . items ( ) :
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
if not isinstance ( dict_key , expected_key_type ) :
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
raise HydrusExceptions . BadRequestException ( ' The Object parameter " {} " held a key, " {} " that was {} and not the expected type: {} ! ' . format ( name , dict_key , type ( dict_key ) , variable_type_to_text_lookup [ expected_key_type ] ) )
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
if not isinstance ( dict_value , expected_value_type ) :
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
raise HydrusExceptions . BadRequestException ( ' The Object parameter " {} " held a value, " {} " that was {} and not the expected type: {} ! ' . format ( name , dict_value , type ( dict_value ) , variable_type_to_text_lookup [ expected_value_type ] ) )
2021-04-07 21:26:45 +00:00
2023-01-25 22:59:39 +00:00
class ParsedRequestArguments ( dict ) :
def __missing__ ( self , key ) :
raise HydrusExceptions . BadRequestException ( ' It looks like the parameter " {} " was missing! ' . format ( key ) )
def GetValue ( self , key , expected_type , expected_list_type = None , expected_dict_types = None , default_value = None , none_on_missing = False ) :
return GetValueFromDict ( self , key , expected_type , expected_list_type = expected_list_type , expected_dict_types = expected_dict_types , default_value = default_value , none_on_missing = none_on_missing )