/******************************************************************************* * * * libretroshare base64 encoding utilities * * * * Copyright (C) 2015 Retroshare Team * * Copyright (C) 2020 Gioacchino Mazzurco * * Copyright (C) 2020 AsociaciĆ³n Civil Altermundi * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this program. If not, see . * * * *******************************************************************************/ #include "util/rsbase64.h" #include "util/rsdebug.h" #if __cplusplus < 201703L /* Solve weird undefined reference error with C++ < 17 see: * https://stackoverflow.com/questions/8016780/undefined-reference-to-static-constexpr-char */ /*static*/ decltype(RsBase64::bDict) constexpr RsBase64::bDict; /*static*/ decltype(RsBase64::uDict) constexpr RsBase64::uDict; /*static*/ decltype(RsBase64::rDict) constexpr RsBase64::rDict; /*static*/ decltype(RsBase64::sPad) constexpr RsBase64::sPad; #endif /*static*/ void RsBase64::encode( rs_view_ptr data, size_t len, std::string& outString, bool padding, bool urlSafe ) { if(!data || !len) { outString.clear(); return; } const char* sDict = urlSafe ? uDict : bDict; // Workaround if input and output are the same buffer. bool inplace = (outString.data() == reinterpret_cast(data)); std::string tBuff; std::string& outStr = inplace ? tBuff : outString; auto encSize = encodedSize(len, padding); outStr.resize(encSize); char* p = &outStr[0]; for (; len >= 3; len -= 3, data += 3) { *p++ = sDict[ (data[0] >> 2) & 077 ]; *p++ = sDict[ (((data[0] << 4) & 060) | ((data[1] >> 4) & 017)) & 077 ]; *p++ = sDict[ (((data[1] << 2) & 074) | ((data[2] >> 6) & 03)) & 077 ]; *p++ = sDict[ data[2] & 077 ]; } if (len == 2) { *p++ = sDict[ (data[0] >> 2) & 077 ]; *p++ = sDict[ (((data[0] << 4) & 060) | ((data[1] >> 4) & 017)) & 077 ]; *p++ = sDict[ ((data[1] << 2) & 074) ]; if(padding) *p++ = sPad; } else if (len == 1) { *p++ = sDict[ (data[0] >> 2) & 077 ]; *p++ = sDict[ (data[0] << 4) & 060 ]; if(padding) { *p++ = sPad; *p++ = sPad; } } if(inplace) outString = tBuff; } /*static*/ std::error_condition RsBase64::decode( const std::string& encoded, std::vector& decoded ) { size_t decSize; std::error_condition ec; std::tie(decSize, ec) = decodedSize(encoded); if(!decSize || ec) return ec; size_t encSize = encoded.size(); decoded.resize(decSize); for (size_t i = 0, o = 0; i < encSize; i += 4, o += 3) { char input0 = encoded[i + 0]; char input1 = encoded[i + 1]; /* At the end of the string, missing bytes 2 and 3 are considered * padding '=' */ char input2 = i + 2 < encoded.size() ? encoded[i + 2] : sPad; char input3 = i + 3 < encSize ? encoded[i + 3] : sPad; // If any unknown characters appear, it's an error. if(!( isBase64Char(input0) && isBase64Char(input1) && isBase64Char(input2) && isBase64Char(input3) )) return std::errc::argument_out_of_domain; /* If padding appears anywhere but the last 1 or 2 characters, or if * it appears but encoded.size() % 4 != 0, it's an error. */ bool at_end = (i + 4 >= encSize); if ( (input0 == sPad) || (input1 == sPad) || ( input2 == sPad && !at_end ) || ( input2 == sPad && input3 != sPad ) || ( input3 == sPad && !at_end) ) return std::errc::illegal_byte_sequence; uint32_t b0 = rDict[static_cast(input0)] & 0x3f; uint32_t b1 = rDict[static_cast(input1)] & 0x3f; uint32_t b2 = rDict[static_cast(input2)] & 0x3f; uint32_t b3 = rDict[static_cast(input3)] & 0x3f; uint32_t stream = (b0 << 18) | (b1 << 12) | (b2 << 6) | b3; decoded[o + 0] = (stream >> 16) & 0xFF; if (input2 != sPad) decoded[o + 1] = (stream >> 8) & 0xFF; /* If there are any stale bits in this from input1, the text is * malformed. */ else if (((stream >> 8) & 0xFF) != 0) return std::errc::invalid_argument; if (input3 != sPad) decoded[o + 2] = (stream >> 0) & 0xFF; /* If there are any stale bits in this from input2, the text is * malformed. */ else if (((stream >> 0) & 0xFF) != 0) return std::errc::invalid_argument; } return std::error_condition(); } /*static*/ size_t RsBase64::encodedSize(size_t decodedSize, bool padding) { if(!decodedSize) return 0; // Thanks https://stackoverflow.com/a/45401395 if(padding) return ceilDivision(decodedSize, 3) * 4; return ceilDivision(decodedSize * 8, 6); } /*static*/ std::tuple RsBase64::decodedSize( const std::string& input ) { const auto success = [](size_t val) { return std::make_tuple(val, std::error_condition()); }; if(input.empty()) return success(0); auto mod = input.size() % 4; if(mod == 1) std::make_tuple(0, std::errc::invalid_argument); size_t padded_size = ((input.size() + 3) / 4) * 3; if (mod >= 2 || (mod == 0 && input[input.size() - 1] == sPad)) { /* If the last byte is '=', or the input size % 4 is 2 or 3 (thus * there are implied '='s), then the actual size is 1-2 bytes * smaller. */ if ( mod == 2 || (mod == 0 && input[input.size() - 2] == sPad) ) { /* If the second-to-last byte is also '=', or the input * size % 4 is 2 (implying a second '='), then the actual size * is 2 bytes smaller. */ return success(padded_size - 2); } else { /* Otherwise it's just the last character and the actual size is * 1 byte smaller. */ return success(padded_size - 1); } } return success(padded_size); } /*static*/ size_t RsBase64::stripInvalid( const std::string& in, std::string& out ) { size_t strippedCnt = 0; auto inSize = in.size(); out.resize(inSize); for(size_t i = 0; i < inSize; ++i) { if(isBase64Char(in[i])) out[i-strippedCnt] = in[i]; else ++strippedCnt; } out.resize(inSize-strippedCnt); return strippedCnt; }