xref: /6.6.0/kv_engine/xattr/utils.cc (revision 5c64d40a)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2016 Couchbase, Inc.
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17#include <JSON_checker.h>
18#include <memcached/protocol_binary.h>
19#include <xattr/blob.h>
20#include <xattr/key_validator.h>
21#include <xattr/utils.h>
22
23#include <nlohmann/json.hpp>
24
25#include <unordered_set>
26
27namespace cb {
28namespace xattr {
29
30/**
31 * Small utility function to trim the blob object into a '\0' terminated
32 * string.
33 *
34 * @param blob the blob object to operate on
35 * @return the trimmed string
36 * @throws std::underflow_error if there isn't a '\0' in the buffer
37 */
38static cb::const_char_buffer trim_string(cb::const_char_buffer blob) {
39    const auto* end = (const char*)std::memchr(blob.buf, '\0', blob.len);
40    if (end == nullptr) {
41        throw std::out_of_range("trim_string: no '\\0' in the input buffer");
42    }
43
44    return {blob.buf, size_t(end - blob.buf)};
45}
46
47bool validate(const cb::const_char_buffer& blob) {
48    if (blob.len < 4) {
49        // we must have room for the length field
50        return false;
51    }
52
53    // Check that the offset of the body is within the blob (note that it
54    // may be the same size as the blob if the actual data payload is empty
55    auto size = get_body_offset(blob);
56    if (size > blob.len) {
57        return false;
58    }
59
60    // @todo fix the hash thing so I can use the keybuf directly
61    std::unordered_set<std::string> keys;
62
63    // You probably want to look in docs/Document.md for a detailed
64    // description of the actual memory layout and why I'm adding
65    // these "magic" values.
66    size_t offset = 4;
67    try {
68        JSON_checker::Validator validator;
69
70        // Iterate over all of the KV pairs
71        while (offset < size) {
72            // The next pair _must_ at least have:
73            //    4  byte length field,
74            //    1  byte key
75            //    2x 1 byte '\0'
76            if (offset + 7 > size) {
77                return false;
78            }
79
80            const auto kvsize = ntohl(
81                *reinterpret_cast<const uint32_t*>(blob.buf + offset));
82            offset += 4;
83            if (offset + kvsize > size) {
84                // The kvsize exceeds the blob size
85                return false;
86            }
87
88            // pick out the key
89            const auto keybuf = trim_string({blob.buf + offset, size - offset});
90            offset += keybuf.len + 1; // swallow the '\0'
91
92            // Validate the key
93            if (!is_valid_xattr_key({keybuf.buf, keybuf.len})) {
94                return false;
95            }
96
97            // pick out the value
98            const auto valuebuf = trim_string({blob.buf + offset, size - offset});
99            offset += valuebuf.len + 1; // swallow '\0'
100
101            // Validate the value (must be legal json)
102            if (!validator.validate(valuebuf.buf)) {
103                // Failed to parse the JSON
104                return false;
105            }
106
107            if (kvsize != (keybuf.len + valuebuf.len + 2)) {
108                return false;
109            }
110
111            if (!keys.insert(std::string{keybuf.buf, keybuf.len}).second) {
112                return false;
113            }
114        }
115    } catch (const std::out_of_range&) {
116        return false;
117    }
118
119    return offset == size;
120}
121
122// Test that a len doesn't exceed size, the idea that len is the value read from
123// an xattr payload and size is the document size
124static void check_len(uint32_t len, size_t size) {
125    if (len > size) {
126        throw std::out_of_range("xattr::utils::check_len(" +
127                                std::to_string(len) + ") exceeds " +
128                                std::to_string(size));
129    }
130}
131
132uint32_t get_body_offset(const cb::const_char_buffer& payload) {
133    Expects(payload.size() > 0);
134    const uint32_t* lenptr = reinterpret_cast<const uint32_t*>(payload.buf);
135    auto len = ntohl(*lenptr);
136    check_len(len, payload.size());
137    return len + sizeof(uint32_t);
138}
139
140uint32_t get_body_offset(const cb::char_buffer& payload) {
141    Expects(payload.size() > 0);
142    const uint32_t* lenptr = reinterpret_cast<const uint32_t*>(payload.buf);
143    auto len = ntohl(*lenptr);
144    check_len(len, payload.size());
145    return len + sizeof(uint32_t);
146}
147
148const_char_buffer get_body(const cb::const_char_buffer& payload) {
149    auto offset = get_body_offset(payload);
150    return {payload.buf + offset, payload.len - offset};
151}
152
153size_t get_system_xattr_size(uint8_t datatype, const cb::const_char_buffer doc) {
154    if (!::mcbp::datatype::is_xattr(datatype)) {
155        return 0;
156    }
157
158    Blob blob({const_cast<char*>(doc.data()), doc.size()},
159              ::mcbp::datatype::is_snappy(datatype));
160    return blob.get_system_size();
161}
162
163size_t get_body_size(uint8_t datatype, cb::const_char_buffer value) {
164    cb::compression::Buffer uncompressed;
165    if (::mcbp::datatype::is_snappy(datatype)) {
166        if (!cb::compression::inflate(
167                    cb::compression::Algorithm::Snappy, value, uncompressed)) {
168            throw std::invalid_argument(
169                    "get_body_size: Failed to inflate data");
170        }
171        value = uncompressed;
172    }
173
174    if (value.size() == 0) {
175        return 0;
176    }
177
178    if (!::mcbp::datatype::is_xattr(datatype)) {
179        return value.size();
180    }
181
182    return value.size() - get_body_offset(value);
183}
184}
185}
186