Проблема с кодировкой/декодированием Strange Base64
Я использую Grails 1.3.7. У меня есть код, который использует встроенную функцию base64Encode и функцию base64Decode. Все это прекрасно работает в простых тестовых случаях, когда я кодирую некоторые двоичные данные, а затем декодирую полученную строку и записываю ее в новый файл. В этом случае файлы идентичны.
Но потом я написал веб-службу, которая взяла данные, закодированные base64, в качестве параметра в вызове POST. Хотя длина данных base64 идентична строке I, переданной в функцию, содержимое данных base64 изменяется. Я потратил DAYS на отладку и, наконец, написал тестовый контроллер, который передал данные в base64 для публикации, а также взял имя локального файла с правильными закодированными данными base64, как в:
data=AAA-base-64-data...&testFilename=/name/of/file/with/base64data
В рамках тестовой функции я сравнивал каждый байт в параметре входящих данных с соответствующим байтом в тестовом файле. Я обнаружил, что каким-то образом каждый символ "+" в параметре входных данных был заменен на "" (пробел, порядковый номер ascii 32). А? Что могло бы это сделать?
Чтобы быть уверенным, что я был прав, я добавил строку, в которой говорилось:
data = data.replaceAll(' ', '+')
и, конечно же, данные будут декодированы точно вправо. Я пробовал его с произвольно длинными двоичными файлами, и теперь он работает каждый раз. Но я не могу понять для меня, что изменит параметр данных в сообщении, чтобы преобразовать символ ord (43) в ord (32)? Я знаю, что знак плюса является одним из двух немного зависимых от платформы символов в спецификации base64, но, учитывая, что я делаю кодирование и декодирование на той же машине, я сейчас озадачен тем, что вызвало это. Конечно, у меня есть "исправление", так как я могу заставить его работать, но я нервничаю из-за "исправлений", которые я не понимаю.
Код слишком велик, чтобы публиковать здесь, но я получаю кодировку base64 следующим образом:
def inputFile = new File(inputFilename)
def rawData = inputFile.getBytes()
def encoded = rawData.encodeBase64().toString()
Затем я пишу эту закодированную строку в новый файл, чтобы потом использовать ее для тестирования. Если я снова загружу этот файл, я получаю тот же rawData:
def encodedFile = new File(encodedFilename)
String encoded = encodedFile.getText()
byte[] rawData = encoded.decodeBase64()
Итак, все хорошо. Теперь предположим, что я беру "закодированную" переменную и добавляю ее к параметру для функции POST, например:
String queryString = "data=$encoded"
String url = "http://localhost:8080/some_web_service"
def results = urlPost(url, queryString)
def urlPost(String urlString, String queryString) {
def url = new URL(urlString)
def connection = url.openConnection()
connection.setRequestMethod("POST")
connection.doOutput = true
def writer = new OutputStreamWriter(connection.outputStream)
writer.write(queryString)
writer.flush()
writer.close()
connection.connect()
return (connection.responseCode == 200) ? connection.content.text : "error $connection.responseCode, $connection.responseMessage"
}
на стороне веб-службы, в контроллере я получаю такой параметр:
String data = params?.data
println "incoming data parameter has length of ${data.size()}" //confirm right size
//unless I run the following line, the data does not decode to the same source
data = data.replaceAll(' ', '+')
//as long as I replace spaces with plus, this decodes correctly, why?
byte[] bytedata = data.decodeBase64()
Извините за длинный разглагольствование, но мне очень хотелось бы понять, почему мне пришлось "заменить пространство знаком плюс", чтобы это правильно декодировало. Есть ли проблема с знаком "плюс", который используется в параметре запроса?
Ответы
Ответ 1
Независимо от того, что заполняет params
, запрос будет представлять собой форму с URL-адресами (в частности, application/x-www-form-urlencoded
, где "+" означает пробел), но вы не кодировали URL-адрес. Я не знаю, какие функции предоставляет ваш язык, но в псевдокоде queryString
должен быть построен из
concat(uri_escape("data"), "=", uri_escape(base64_encode(rawBytes)))
что упрощается до
concat("data=", uri_escape(base64_encode(rawBytes)))
Символы "+
" будут заменены на "%2B
".
Ответ 2
Поскольку это параметр для POST, вы должны URL-кодировать данные.
См. http://en.wikipedia.org/wiki/Percent-encoding
Ответ 3
parquote из ссылки wikipedia
Используемая по умолчанию кодировка на очень ранней версии общего Правила кодирования процентов URI, с количество модификаций, таких как нормализация и замена новой строки пробелы с "+" вместо "%20"
еще одна скрытая ловушка, которую обычные веб-разработчики, как я, мало знают о
Ответ 4
Вам нужно найти для base64encode url safe, но не используйте кодировку url, потому что она не работает должным образом.
http://en.wikipedia.org/wiki/Base64#URL_applications
Я использую следующий код в php:
/**
* Custom base64 encoding. Replace unsafe url chars
*
* @param string $val
* @return string
*/
static function base64_url_encode($val) {
return strtr(base64_encode($val), '+/=', '-_,');
}
/**
* Custom base64 decode. Replace custom url safe values with normal
* base64 characters before decoding.
*
* @param string $val
* @return string
*/
static function base64_url_decode($val) {
return base64_decode(strtr($val, '-_,', '+/='));
}