Проблема с кодировкой/декодированием 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".

Ответ 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, '-_,', '+/='));

    }