Получить имя файла, отличного от ASCII, из события уведомления S3 в Lambda

Поле key в событии уведомления AWS S3, которое обозначает имя файла, является URL-адресом экранированным.

Это видно, когда имя файла содержит пробелы или символы, отличные от ASCII.

Например, я загрузил следующее имя файла на S3:

my file řěąλλυ.txt

Уведомление принимается как:

{ 
  "Records": [
    "s3": {
        "object": {
            "key": u"my+file+%C5%99%C4%9B%C4%85%CE%BB%CE%BB%CF%85.txt"
        }
    }
  ]
}

Я попытался декодировать, используя:

key = urllib.unquote_plus(event['Records'][0]['s3']['object']['key']).decode('utf-8')

но это дает:

my file ÅÄÄλλÏ.txt

Конечно, когда я пытаюсь получить файл из S3 с помощью Boto, я получаю ошибку 404.

Ответ 1

TL;DR

Вам нужно преобразовать строку Unicode, закодированную в URL-адрес, в строку с байтами до того, как ее unr urlparsing и декодировать как UTF-8.

Например, для объекта S3 с именем файла: my file řěąλλυ.txt:

>>> utf8_urlencoded_key = event['Records'][0]['s3']['object']['key'].encode('utf-8')
# encodes the Unicode string to utf-8 encoded [byte] string. The key shouldn't contain any non-ASCII at this point, but UTF-8 will be safer.
'my+file+%C5%99%C4%9B%C4%85%CE%BB%CE%BB%CF%85.txt'

>>> key_utf8 = urllib.unquote_plus(utf8_urlencoded_key)
# the previous url-escaped UTF-8 are now converted to UTF-8 bytes
# If you passed a Unicode object to unquote_plus, you'd have got a 
# Unicode with UTF-8 encoded bytes!
'my file \xc5\x99\xc4\x9b\xc4\x85\xce\xbb\xce\xbb\xcf\x85.txt'

# Decodes key_utf-8 to a Unicode string
>>> key = key_utf8.decode('utf-8')
u'my file \u0159\u011b\u0105\u03bb\u03bb\u03c5.txt'
# Note the u prefix. The utf-8 bytes have been decoded to Unicode points.

>>> type(key)
<type 'unicode'>

>>> print(key)
my file řěąλλυ.txt

Фон

AWS совершили кардинальный грех изменения кодировки по умолчанию - https://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/

Ошибка, которую вы должны получить от вашего decode():

UnicodeEncodeError: 'ascii' codec can't encode characters in position 8-19: ordinal not in range(128)

Значение key - это Юникод. В Python 2.x вы можете декодировать Unicode, хотя это и не имеет смысла. В Python 2.x для декодирования Юникода Python сначала пытается сначала закодировать его на [byte] str, прежде чем декодировать его с использованием данной кодировки. В Python 2.x по умолчанию кодировка должна быть ASCII, которая, конечно, не может содержать символы.

Если у вас есть правильный UnicodeEncodeError из Python, вы можете найти подходящие ответы. На Python 3 вы вообще не смогли бы вызвать .decode().

Ответ 2

На всякий случай, когда кто-то приходит сюда, надеясь на решение JavaScript, вот что я получил:

function decodeS3EventKey (key = '') {
  return decodeURIComponent(key.replace(/\+/g, ' '))
}

При ограниченном тестировании он работает нормально:

  • test+image+%C3%BCtf+%E3%83%86%E3%82%B9%E3%83%88.jpg декодирует до test image ütf テスト.jpg
  • my+file+%C5%99%C4%9B%C4%85%CE%BB%CE%BB%CF%85.txt декодирует до my file řěąλλυ.txt