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 "config.h"
18 
19 #include <cJSON_utils.h>
20 #include <unordered_set>
21 #include <memcached/protocol_binary.h>
22 #include <xattr/blob.h>
23 #include <xattr/key_validator.h>
24 #include <xattr/utils.h>
25 
26 namespace cb {
27 namespace xattr {
28 
29 /**
30  * Small utility function to trim the blob object into a '\0' terminated
31  * string.
32  *
33  * @param blob the blob object to operate on
34  * @return the trimmed string
35  * @throws std::underflow_error if there isn't a '\0' in the buffer
36  */
trim_string(cb::const_char_buffer blob)37 static cb::const_char_buffer trim_string(cb::const_char_buffer blob) {
38     const auto* end = (const char*)std::memchr(blob.buf, '\0', blob.len);
39     if (end == nullptr) {
40         throw std::out_of_range("trim_string: no '\\0' in the input buffer");
41     }
42 
43     return {blob.buf, size_t(end - blob.buf)};
44 }
45 
validate(const cb::const_char_buffer& blob)46 bool validate(const cb::const_char_buffer& blob) {
47     if (blob.len < 4) {
48         // we must have room for the length field
49         return false;
50     }
51 
52     // Check that the offset of the body is within the blob (note that it
53     // may be the same size as the blob if the actual data payload is empty
54     auto size = get_body_offset(blob);
55     if (size > blob.len) {
56         return false;
57     }
58 
59     // @todo fix the hash thing so I can use the keybuf directly
60     std::unordered_set<std::string> keys;
61 
62     // You probably want to look in docs/Document.md for a detailed
63     // description of the actual memory layout and why I'm adding
64     // these "magic" values.
65     size_t offset = 4;
66     try {
67         // Iterate over all of the KV pairs
68         while (offset < size) {
69             // The next pair _must_ at least have:
70             //    4  byte length field,
71             //    1  byte key
72             //    2x 1 byte '\0'
73             if (offset + 7 > size) {
74                 return false;
75             }
76 
77             const auto kvsize = ntohl(
78                 *reinterpret_cast<const uint32_t*>(blob.buf + offset));
79             offset += 4;
80             if (offset + kvsize > size) {
81                 // The kvsize exceeds the blob size
82                 return false;
83             }
84 
85             // pick out the key
86             const auto keybuf = trim_string({blob.buf + offset, size - offset});
87             offset += keybuf.len + 1; // swallow the '\0'
88 
89             // Validate the key
90             if (!is_valid_xattr_key({keybuf.buf, keybuf.len})) {
91                 return false;
92             }
93 
94             // pick out the value
95             const auto valuebuf = trim_string({blob.buf + offset, size - offset});
96             offset += valuebuf.len + 1; // swallow '\0'
97 
98             // Validate the value (must be legal json)
99             unique_cJSON_ptr payload{cJSON_Parse(valuebuf.buf)};
100             if (!payload) {
101                 // Failed to parse the JSON
102                 return false;
103             }
104 
105             if (kvsize != (keybuf.len + valuebuf.len + 2)) {
106                 return false;
107             }
108 
109             if (!keys.insert(std::string{keybuf.buf, keybuf.len}).second) {
110                 return false;
111             }
112         }
113     } catch (const std::out_of_range&) {
114         return false;
115     }
116 
117     return offset == size;
118 }
119 
120 // Test that a len doesn't exceed size, the idea that len is the value read from
121 // an xattr payload and size is the document size
check_len(uint32_t len, size_t size)122 static void check_len(uint32_t len, size_t size) {
123     if (len > size) {
124         throw std::out_of_range("xattr::utils::check_len(" +
125                                 std::to_string(len) + ") exceeds " +
126                                 std::to_string(size));
127     }
128 }
129 
get_body_offset(const cb::const_char_buffer& payload)130 uint32_t get_body_offset(const cb::const_char_buffer& payload) {
131     const uint32_t* lenptr = reinterpret_cast<const uint32_t*>(payload.buf);
132     auto len = ntohl(*lenptr);
133     check_len(len, payload.size());
134     return len + sizeof(uint32_t);
135 }
136 
get_body_offset(const cb::char_buffer& payload)137 uint32_t get_body_offset(const cb::char_buffer& payload) {
138     const uint32_t* lenptr = reinterpret_cast<const uint32_t*>(payload.buf);
139     auto len = ntohl(*lenptr);
140     check_len(len, payload.size());
141     return len + sizeof(uint32_t);
142 }
143 
get_body(const cb::const_char_buffer& payload)144 const_char_buffer get_body(const cb::const_char_buffer& payload) {
145     auto offset = get_body_offset(payload);
146     return {payload.buf + offset, payload.len - offset};
147 }
148 
get_body(const cb::char_buffer& payload)149 char_buffer get_body(const cb::char_buffer& payload) {
150     auto offset = get_body_offset(payload);
151     return {payload.buf + offset, payload.len - offset};
152 }
153 
get_system_xattr_size(uint8_t datatype, const cb::const_char_buffer doc)154 size_t get_system_xattr_size(uint8_t datatype, const cb::const_char_buffer doc) {
155     if (!::mcbp::datatype::is_xattr(datatype)) {
156         return 0;
157     }
158 
159     Blob blob({const_cast<char*>(doc.data()), doc.size()},
160               ::mcbp::datatype::is_snappy(datatype));
161     return blob.get_system_size();
162 }
163 
get_size_and_system_xattr_size( uint8_t datatype, const cb::const_char_buffer doc)164 std::pair<size_t, size_t> get_size_and_system_xattr_size(
165         uint8_t datatype, const cb::const_char_buffer doc) {
166     if (!::mcbp::datatype::is_xattr(datatype)) {
167         return {0, 0};
168     }
169 
170     Blob blob({const_cast<char*>(doc.data()), doc.size()},
171               ::mcbp::datatype::is_snappy(datatype));
172     return {blob.size(), blob.get_system_size()};
173 }
174 }
175 }
176