blob: bf708915b635ff533a21eaf183cd3193c820d12a [file] [log] [blame]
#pragma once
#include "bmcweb_config.h"
#include <openssl/crypto.h>
#include <boost/callable_traits.hpp>
#include <boost/url/parse.hpp>
#include <boost/url/url.hpp>
#include <boost/url/url_view.hpp>
#include <nlohmann/json.hpp>
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <functional>
#include <iomanip>
#include <limits>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
namespace crow
{
namespace utility
{
enum class TypeCode : uint8_t
{
Unspecified = 0,
String = 1,
Path = 2,
Max = 3,
};
// Remove when we have c++23
template <typename E>
constexpr typename std::underlying_type<E>::type toUnderlying(E e) noexcept
{
return static_cast<typename std::underlying_type<E>::type>(e);
}
constexpr inline uint64_t getParameterTag(std::string_view url)
{
uint64_t tagValue = 0;
size_t urlSegmentIndex = std::string_view::npos;
size_t paramIndex = 0;
for (size_t urlIndex = 0; urlIndex < url.size(); urlIndex++)
{
char character = url[urlIndex];
if (character == '<')
{
if (urlSegmentIndex != std::string_view::npos)
{
return 0;
}
urlSegmentIndex = urlIndex;
}
if (character == '>')
{
if (urlSegmentIndex == std::string_view::npos)
{
return 0;
}
std::string_view tag = url.substr(urlSegmentIndex,
urlIndex + 1 - urlSegmentIndex);
// Note, this is a really lame way to do std::pow(6, paramIndex)
// std::pow doesn't work in constexpr in clang.
// Ideally in the future we'd move this to use a power of 2 packing
// (probably 8 instead of 6) so that these just become bit shifts
uint64_t insertIndex = 1;
for (size_t unused = 0; unused < paramIndex; unused++)
{
insertIndex *= 3;
}
if (tag == "<str>" || tag == "<string>")
{
tagValue += insertIndex * toUnderlying(TypeCode::String);
}
if (tag == "<path>")
{
tagValue += insertIndex * toUnderlying(TypeCode::Path);
}
paramIndex++;
urlSegmentIndex = std::string_view::npos;
}
}
if (urlSegmentIndex != std::string_view::npos)
{
return 0;
}
return tagValue;
}
constexpr size_t numArgsFromTag(int tag)
{
size_t ret = 0;
while (tag > 0)
{
// Move to the next tag by removing the bottom bits from the number
tag /= toUnderlying(TypeCode::Max);
ret++;
}
return ret;
};
class Base64Encoder
{
char overflow1 = '\0';
char overflow2 = '\0';
uint8_t overflowCount = 0;
constexpr static std::array<char, 64> key = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
// Takes 3 ascii chars, and encodes them as 4 base64 chars
static void encodeTriple(char first, char second, char third,
std::string& output)
{
size_t keyIndex = 0;
keyIndex = static_cast<size_t>(first & 0xFC) >> 2;
output += key[keyIndex];
keyIndex = static_cast<size_t>(first & 0x03) << 4;
keyIndex += static_cast<size_t>(second & 0xF0) >> 4;
output += key[keyIndex];
keyIndex = static_cast<size_t>(second & 0x0F) << 2;
keyIndex += static_cast<size_t>(third & 0xC0) >> 6;
output += key[keyIndex];
keyIndex = static_cast<size_t>(third & 0x3F);
output += key[keyIndex];
}
public:
// Accepts a partial string to encode, and writes the encoded characters to
// the output stream. requires subsequently calling finalize to complete
// stream.
void encode(std::string_view data, std::string& output)
{
// Encode the last round of overflow chars first
if (overflowCount == 2)
{
if (!data.empty())
{
encodeTriple(overflow1, overflow2, data[0], output);
overflowCount = 0;
data.remove_prefix(1);
}
}
else if (overflowCount == 1)
{
if (data.size() >= 2)
{
encodeTriple(overflow1, data[0], data[1], output);
overflowCount = 0;
data.remove_prefix(2);
}
}
while (data.size() >= 3)
{
encodeTriple(data[0], data[1], data[2], output);
data.remove_prefix(3);
}
if (!data.empty() && overflowCount == 0)
{
overflow1 = data[0];
overflowCount++;
data.remove_prefix(1);
}
if (!data.empty() && overflowCount == 1)
{
overflow2 = data[0];
overflowCount++;
data.remove_prefix(1);
}
}
// Completes a base64 output, by writing any MOD(3) characters to the
// output, as well as any required trailing =
void finalize(std::string& output)
{
if (overflowCount == 0)
{
return;
}
size_t keyIndex = static_cast<size_t>(overflow1 & 0xFC) >> 2;
output += key[keyIndex];
keyIndex = static_cast<size_t>(overflow1 & 0x03) << 4;
if (overflowCount == 2)
{
keyIndex += static_cast<size_t>(overflow2 & 0xF0) >> 4;
output += key[keyIndex];
keyIndex = static_cast<size_t>(overflow2 & 0x0F) << 2;
output += key[keyIndex];
}
else
{
output += key[keyIndex];
output += '=';
}
output += '=';
overflowCount = 0;
}
// Returns the required output buffer in characters for an input of size
// inputSize
static size_t constexpr encodedSize(size_t inputSize)
{
// Base64 encodes 3 character blocks as 4 character blocks
// With a possibility of 2 trailing = characters
return (inputSize + 2) / 3 * 4;
}
};
inline std::string base64encode(std::string_view data)
{
// Encodes a 3 character stream into a 4 character stream
std::string out;
Base64Encoder base64;
out.reserve(Base64Encoder::encodedSize(data.size()));
base64.encode(data, out);
base64.finalize(out);
return out;
}
// TODO this is temporary and should be deleted once base64 is refactored out of
// crow
inline bool base64Decode(std::string_view input, std::string& output)
{
static const char nop = static_cast<char>(-1);
// See note on encoding_data[] in above function
static const std::array<char, 256> decodingData = {
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, 62, nop, nop, nop, 63, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, nop, nop, nop, nop, nop, nop, nop, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, nop, nop, nop, nop, nop, nop, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
nop, nop, nop, nop};
size_t inputLength = input.size();
// allocate space for output string
output.clear();
output.reserve(((inputLength + 2) / 3) * 4);
auto getCodeValue = [](char c) {
auto code = static_cast<unsigned char>(c);
// Ensure we cannot index outside the bounds of the decoding array
static_assert(std::numeric_limits<decltype(code)>::max() <
decodingData.size());
return decodingData[code];
};
// for each 4-bytes sequence from the input, extract 4 6-bits sequences by
// dropping first two bits
// and regenerate into 3 8-bits sequences
for (size_t i = 0; i < inputLength; i++)
{
char base64code0 = 0;
char base64code1 = 0;
char base64code2 = 0; // initialized to 0 to suppress warnings
base64code0 = getCodeValue(input[i]);
if (base64code0 == nop)
{ // non base64 character
return false;
}
if (!(++i < inputLength))
{ // we need at least two input bytes for first
// byte output
return false;
}
base64code1 = getCodeValue(input[i]);
if (base64code1 == nop)
{ // non base64 character
return false;
}
output +=
static_cast<char>((base64code0 << 2) | ((base64code1 >> 4) & 0x3));
if (++i < inputLength)
{
char c = input[i];
if (c == '=')
{ // padding , end of input
return (base64code1 & 0x0f) == 0;
}
base64code2 = getCodeValue(input[i]);
if (base64code2 == nop)
{ // non base64 character
return false;
}
output += static_cast<char>(((base64code1 << 4) & 0xf0) |
((base64code2 >> 2) & 0x0f));
}
if (++i < inputLength)
{
char c = input[i];
if (c == '=')
{ // padding , end of input
return (base64code2 & 0x03) == 0;
}
char base64code3 = getCodeValue(input[i]);
if (base64code3 == nop)
{ // non base64 character
return false;
}
output +=
static_cast<char>((((base64code2 << 6) & 0xc0) | base64code3));
}
}
return true;
}
inline bool constantTimeStringCompare(std::string_view a, std::string_view b)
{
// Important note, this function is ONLY constant time if the two input
// sizes are the same
if (a.size() != b.size())
{
return false;
}
return CRYPTO_memcmp(a.data(), b.data(), a.size()) == 0;
}
struct ConstantTimeCompare
{
bool operator()(std::string_view a, std::string_view b) const
{
return constantTimeStringCompare(a, b);
}
};
namespace details
{
inline boost::urls::url
appendUrlPieces(boost::urls::url& url,
const std::initializer_list<std::string_view> args)
{
for (std::string_view arg : args)
{
url.segments().push_back(arg);
}
return url;
}
} // namespace details
class OrMorePaths
{};
template <typename... AV>
inline void appendUrlPieces(boost::urls::url& url, const AV... args)
{
details::appendUrlPieces(url, {args...});
}
namespace details
{
// std::reference_wrapper<std::string> - extracts segment to variable
// std::string_view - checks if segment is equal to variable
using UrlSegment = std::variant<std::reference_wrapper<std::string>,
std::string_view, OrMorePaths>;
enum class UrlParseResult
{
Continue,
Fail,
Done,
};
class UrlSegmentMatcherVisitor
{
public:
UrlParseResult operator()(std::string& output)
{
output = segment;
return UrlParseResult::Continue;
}
UrlParseResult operator()(std::string_view expected)
{
if (segment == expected)
{
return UrlParseResult::Continue;
}
return UrlParseResult::Fail;
}
UrlParseResult operator()(OrMorePaths /*unused*/)
{
return UrlParseResult::Done;
}
explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) :
segment(segmentIn)
{}
private:
std::string_view segment;
};
inline bool readUrlSegments(boost::urls::url_view url,
std::initializer_list<UrlSegment>&& segments)
{
boost::urls::segments_view urlSegments = url.segments();
if (!urlSegments.is_absolute())
{
return false;
}
boost::urls::segments_view::iterator it = urlSegments.begin();
boost::urls::segments_view::iterator end = urlSegments.end();
for (const auto& segment : segments)
{
if (it == end)
{
// If the request ends with an "any" path, this was successful
return std::holds_alternative<OrMorePaths>(segment);
}
UrlParseResult res = std::visit(UrlSegmentMatcherVisitor(*it), segment);
if (res == UrlParseResult::Done)
{
return true;
}
if (res == UrlParseResult::Fail)
{
return false;
}
it++;
}
// There will be an empty segment at the end if the URI ends with a "/"
// e.g. /redfish/v1/Chassis/
if ((it != end) && urlSegments.back().empty())
{
it++;
}
return it == end;
}
} // namespace details
template <typename... Args>
inline bool readUrlSegments(boost::urls::url_view url, Args&&... args)
{
return details::readUrlSegments(url, {std::forward<Args>(args)...});
}
inline boost::urls::url replaceUrlSegment(boost::urls::url_view urlView,
const uint replaceLoc,
std::string_view newSegment)
{
boost::urls::segments_view urlSegments = urlView.segments();
boost::urls::url url("/");
if (!urlSegments.is_absolute())
{
return url;
}
boost::urls::segments_view::iterator it = urlSegments.begin();
boost::urls::segments_view::iterator end = urlSegments.end();
for (uint idx = 0; it != end; it++, idx++)
{
if (idx == replaceLoc)
{
url.segments().push_back(newSegment);
}
else
{
url.segments().push_back(*it);
}
}
return url;
}
inline void setProtocolDefaults(boost::urls::url& url,
std::string_view protocol)
{
if (url.has_scheme())
{
return;
}
if (protocol == "Redfish" || protocol.empty())
{
if (url.port_number() == 443)
{
url.set_scheme("https");
}
if (url.port_number() == 80)
{
if (bmcwebInsecureEnableHttpPushStyleEventing)
{
url.set_scheme("http");
}
}
}
else if (protocol == "SNMPv2c")
{
url.set_scheme("snmp");
}
}
inline void setPortDefaults(boost::urls::url& url)
{
uint16_t port = url.port_number();
if (port != 0)
{
return;
}
// If the user hasn't explicitly stated a port, pick one explicitly for them
// based on the protocol defaults
if (url.scheme() == "http")
{
url.set_port_number(80);
}
if (url.scheme() == "https")
{
url.set_port_number(443);
}
if (url.scheme() == "snmp")
{
url.set_port_number(162);
}
}
} // namespace utility
} // namespace crow
namespace nlohmann
{
template <>
struct adl_serializer<boost::urls::url>
{
// nlohmann requires a specific casing to look these up in adl
// NOLINTNEXTLINE(readability-identifier-naming)
static void to_json(json& j, const boost::urls::url& url)
{
j = url.buffer();
}
};
template <>
struct adl_serializer<boost::urls::url_view>
{
// NOLINTNEXTLINE(readability-identifier-naming)
static void to_json(json& j, boost::urls::url_view url)
{
j = url.buffer();
}
};
} // namespace nlohmann