1// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <brillo/data_encoding.h>
6#include <modp_b64/modp_b64.h>
7
8#include <memory>
9
10#include <base/logging.h>
11#include <base/strings/string_util.h>
12#include <base/strings/stringprintf.h>
13#include <brillo/strings/string_utils.h>
14
15namespace {
16
17inline int HexToDec(int hex) {
18  int dec = -1;
19  if (hex >= '0' && hex <= '9') {
20    dec = hex - '0';
21  } else if (hex >= 'A' && hex <= 'F') {
22    dec = hex - 'A' + 10;
23  } else if (hex >= 'a' && hex <= 'f') {
24    dec = hex - 'a' + 10;
25  }
26  return dec;
27}
28
29// Helper for Base64Encode() and Base64EncodeWrapLines().
30std::string Base64EncodeHelper(const void* data, size_t size) {
31  std::vector<char> buffer;
32  buffer.resize(modp_b64_encode_len(size));
33  size_t out_size = modp_b64_encode(buffer.data(),
34                                    static_cast<const char*>(data),
35                                    size);
36  return std::string{buffer.begin(), buffer.begin() + out_size};
37}
38
39}  // namespace
40
41/////////////////////////////////////////////////////////////////////////
42namespace brillo {
43namespace data_encoding {
44
45std::string UrlEncode(const char* data, bool encodeSpaceAsPlus) {
46  std::string result;
47
48  while (*data) {
49    char c = *data++;
50    // According to RFC3986 (http://www.faqs.org/rfcs/rfc3986.html),
51    // section 2.3. - Unreserved Characters
52    if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
53        (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' ||
54        c == '~') {
55      result += c;
56    } else if (c == ' ' && encodeSpaceAsPlus) {
57      // For historical reasons, some URLs have spaces encoded as '+',
58      // this also applies to form data encoded as
59      // 'application/x-www-form-urlencoded'
60      result += '+';
61    } else {
62      base::StringAppendF(&result,
63                          "%%%02X",
64                          static_cast<unsigned char>(c));  // Encode as %NN
65    }
66  }
67  return result;
68}
69
70std::string UrlDecode(const char* data) {
71  std::string result;
72  while (*data) {
73    char c = *data++;
74    int part1 = 0, part2 = 0;
75    // HexToDec would return -1 even for character 0 (end of string),
76    // so it is safe to access data[0] and data[1] without overrunning the buf.
77    if (c == '%' && (part1 = HexToDec(data[0])) >= 0 &&
78        (part2 = HexToDec(data[1])) >= 0) {
79      c = static_cast<char>((part1 << 4) | part2);
80      data += 2;
81    } else if (c == '+') {
82      c = ' ';
83    }
84    result += c;
85  }
86  return result;
87}
88
89std::string WebParamsEncode(const WebParamList& params,
90                            bool encodeSpaceAsPlus) {
91  std::vector<std::string> pairs;
92  pairs.reserve(params.size());
93  for (const auto& p : params) {
94    std::string key = UrlEncode(p.first.c_str(), encodeSpaceAsPlus);
95    std::string value = UrlEncode(p.second.c_str(), encodeSpaceAsPlus);
96    pairs.push_back(brillo::string_utils::Join("=", key, value));
97  }
98
99  return brillo::string_utils::Join("&", pairs);
100}
101
102WebParamList WebParamsDecode(const std::string& data) {
103  WebParamList result;
104  std::vector<std::string> params = brillo::string_utils::Split(data, "&");
105  for (const auto& p : params) {
106    auto pair = brillo::string_utils::SplitAtFirst(p, "=");
107    result.emplace_back(UrlDecode(pair.first.c_str()),
108                        UrlDecode(pair.second.c_str()));
109  }
110  return result;
111}
112
113std::string Base64Encode(const void* data, size_t size) {
114  return Base64EncodeHelper(data, size);
115}
116
117std::string Base64EncodeWrapLines(const void* data, size_t size) {
118  std::string unwrapped = Base64EncodeHelper(data, size);
119  std::string wrapped;
120
121  for (size_t i = 0; i < unwrapped.size(); i += 64) {
122    wrapped.append(unwrapped, i, 64);
123    wrapped.append("\n");
124  }
125  return wrapped;
126}
127
128bool Base64Decode(const std::string& input, brillo::Blob* output) {
129  std::string temp_buffer;
130  const std::string* data = &input;
131  if (input.find_first_of("\r\n") != std::string::npos) {
132    base::ReplaceChars(input, "\n", "", &temp_buffer);
133    base::ReplaceChars(temp_buffer, "\r", "", &temp_buffer);
134    data = &temp_buffer;
135  }
136  // base64 decoded data has 25% fewer bytes than the original (since every
137  // 3 source octets are encoded as 4 characters in base64).
138  // modp_b64_decode_len provides an upper estimate of the size of the output
139  // data.
140  output->resize(modp_b64_decode_len(data->size()));
141
142  size_t size_read = modp_b64_decode(reinterpret_cast<char*>(output->data()),
143                                     data->data(), data->size());
144  if (size_read == MODP_B64_ERROR) {
145    output->resize(0);
146    return false;
147  }
148  output->resize(size_read);
149
150  return true;
151}
152
153}  // namespace data_encoding
154}  // namespace brillo
155