]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/src/http/transformers.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / seastar / src / http / transformers.cc
1 /*
2 * This file is open source software, licensed to you under the terms
3 * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4 * distributed with this work for additional information regarding copyright
5 * ownership. You may not use this file except in compliance with the License.
6 *
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,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
16 * under the License.
17 */
18 /*
19 * Copyright 2015 Cloudius Systems
20 */
21
22 #include <seastar/core/do_with.hh>
23 #include <seastar/core/loop.hh>
24 #include <boost/algorithm/string/replace.hpp>
25 #include <seastar/http/transformers.hh>
26 #include <list>
27
28 namespace seastar {
29
30 namespace httpd {
31
32 using namespace std;
33
34 struct potential_match_entry {
35 const char* begin;
36 const char* end;
37 size_t pos;
38 };
39
40 /*!
41 * \brief holds the buffer replace object current state
42 * The way the matching algorithm works, is that when there's a match
43 * it will be the first entry
44 */
45 class buffer_replace_state {
46 std::list<potential_match_entry> _potential_match;
47
48 public:
49 using iterator = std::list<potential_match_entry>::iterator;
50
51 void add_potential_match(const char* s, const char* e, size_t pos) {
52 _potential_match.emplace_back(potential_match_entry{s, e, pos});
53 }
54
55 iterator begin() {
56 return _potential_match.begin();
57 }
58
59 iterator end() {
60 return _potential_match.end();
61 }
62
63 bool empty() const {
64 return _potential_match.empty();
65 }
66
67 bool last() const {
68 return _potential_match.size() == 1;
69 }
70
71 auto erase(const iterator& i) {
72 return _potential_match.erase(i);
73 }
74
75 /*!
76 * \brief gets the key/value position in the buffer_replace of the match
77 */
78 size_t get_pos() const {
79 return (*_potential_match.begin()).pos;
80 }
81
82 /*!
83 * \brief gets the length of the remaining string
84 */
85 size_t get_remaining_length() const {
86 return _potential_match.begin()->end - _potential_match.begin()->begin;
87 }
88
89 void clear() {
90 _potential_match.clear();
91 }
92 };
93
94 /*!
95 *\brief a helper class to replace strings in a buffer
96 * The keys to replace are surrounded by braces
97 */
98 class buffer_replace {
99 std::vector<std::tuple<sstring, sstring>> _values;
100 buffer_replace_state _current;
101 const sstring& get_value(size_t pos) const;
102 const sstring& get_key(size_t pos) const;
103 public:
104 /*!
105 * \brief Add a key and value to be replaced
106 */
107 buffer_replace& add(sstring key, sstring value) {
108 _values.emplace_back(std::make_tuple("{{" + key + "}}", value));
109 return *this;
110 }
111
112 /*!
113 * \brief if there are no more buffers to consume get
114 * the remaining chars stored in the buffer_replace
115 */
116 temporary_buffer<char> get_remaining();
117
118 /*!
119 * \brief check if the given buffer still match any of the current potential matches
120 *
121 */
122 temporary_buffer<char> match(temporary_buffer<char>& buf);
123 /*!
124 * \brief replace the buffer content
125 *
126 * The returned result is after translation. The method consumes what it read
127 * from the buf, so the caller should check that buf is not empty.
128 *
129 * For example: if buf is: "abcd{{key}}"
130 * res = replace(buf);
131 *
132 * res will be "abcd"
133 * and buf will be "{{key}}"
134 */
135 temporary_buffer<char> replace(temporary_buffer<char>& buf);
136
137 /*!
138 * \brief check if we are currently in the middle of consuming
139 */
140 bool is_consuming() const {
141 return !_current.empty();
142 }
143 };
144
145
146 class content_replace_data_sink_impl : public data_sink_impl {
147 output_stream<char> _out;
148 buffer_replace _br;
149 public:
150 content_replace_data_sink_impl(output_stream<char>&& out, std::vector<std::tuple<sstring,sstring>>&& key_value) : _out(std::move(out)) {
151 for (auto& i : key_value) {
152 _br.add(std::get<0>(i), std::get<1>(i));
153 }
154 }
155
156 virtual future<> put(net::packet data) override {
157 return make_ready_future<>();
158 }
159
160 using data_sink_impl::put;
161
162 virtual future<> put(temporary_buffer<char> buf) override {
163 if (buf.empty()) {
164 return make_ready_future<>();
165 }
166 return do_with(temporary_buffer<char>(std::move(buf)), [this] (temporary_buffer<char>& buf) {
167 return repeat([&buf, this] {
168 auto bf = _br.replace(buf);
169 return _out.write(bf.get(), bf.size()).then([&buf] {
170 return (buf.empty()) ? stop_iteration::yes : stop_iteration::no;
171 });
172 });
173 });
174 }
175
176 virtual future<> flush() override {
177 return _out.flush();
178 }
179
180 virtual future<> close() override {
181 // if we are in the middle of a consuming a key
182 // there will be no match, write the remaining.
183 if (_br.is_consuming()) {
184 return do_with(temporary_buffer<char>(_br.get_remaining()), [this](temporary_buffer<char>& buf) {
185 return _out.write(buf.get(), buf.size()).then([this] {
186 return _out.flush();
187 });
188 });
189 }
190 return _out.flush();
191 }
192 };
193
194 class content_replace_data_sink : public data_sink {
195 public:
196 content_replace_data_sink(output_stream<char>&& out, std::vector<std::tuple<sstring,sstring>> key_value)
197 : data_sink(std::make_unique<content_replace_data_sink_impl>(
198 std::move(out), std::move(key_value))) {}
199 };
200
201 output_stream<char> content_replace::transform(std::unique_ptr<request> req,
202 const sstring& extension, output_stream<char>&& s) {
203 sstring host = req->get_header("Host");
204 if (host == "" || (this->extension != "" && extension != this->extension)) {
205 return std::move(s);
206 }
207 sstring protocol = req->get_protocol_name();
208 output_stream_options opts;
209 opts.trim_to_size = true;
210 return output_stream<char>(content_replace_data_sink(std::move(s), {std::make_tuple("Protocol", protocol), std::make_tuple("Host", host)}), 32000, opts);
211
212 }
213
214 /*!
215 * \brief find the open brace that surround a parameter
216 * it is either two consecutive braces or a single brace, if it's the last char in the buffer
217 */
218 ssize_t find_braces(const char* s, const char* end) {
219 for (size_t i = 0; s != end; s++, i++) {
220 if (*s == '{' && ((s + 1) == end || *(s + 1) == '{')) {
221 return i;
222 }
223 }
224 return -1;
225 }
226
227 const sstring& buffer_replace::get_value(size_t pos) const {
228 return std::get<1>(_values[pos]);
229 }
230
231 const sstring& buffer_replace::get_key(size_t pos) const {
232 return std::get<0>(_values[pos]);
233 }
234
235 temporary_buffer<char> buffer_replace::match(temporary_buffer<char>& buf) {
236 if (_current.empty()) {
237 return temporary_buffer<char>();
238 }
239 auto buf_len = buf.size();
240 auto first = _current.begin();
241 while (first != _current.end()) {
242 auto& pos = first->begin;
243 auto end = first->end;
244 size_t len_compare = std::min(buf_len, static_cast<size_t>(end - pos));
245 if (strncmp(pos, buf.begin(), len_compare)) {
246 // No match remove the entry unless it's the last one
247 // In that case, there is no match
248 // we should return what we consumed so far;
249 if (_current.last()) {
250 auto res = get_remaining();
251 _current.erase(first);
252 return res;
253 }
254 first = _current.erase(first);
255 } else {
256 // we found a match
257 if (pos + len_compare == end) {
258 // this is a full match, there could be only one so this is the first
259 // consume the buffer and return
260 const sstring& value = get_value(_current.get_pos());
261 temporary_buffer<char> res(value.data(), value.size());
262 buf.trim_front(len_compare);
263 _current.clear();
264 return res;
265 }
266 // only partial match
267 pos += len_compare;
268 ++first;
269 }
270 }
271 // if we are here we run out of buffer
272 buf.trim_front(buf_len);
273 return temporary_buffer<char>();
274 }
275
276 temporary_buffer<char> buffer_replace::get_remaining() {
277 if (!is_consuming()) {
278 return temporary_buffer<char>();
279 }
280 size_t pos = _current.get_pos();
281 const sstring& key = get_key(pos);
282 auto size = key.size() - _current.get_remaining_length();
283 return temporary_buffer<char>(key.data(), size);
284 }
285
286 temporary_buffer<char> buffer_replace::replace(temporary_buffer<char>& buf) {
287 if (buf.empty()) {
288 return std::move(buf);
289 }
290 if (is_consuming()) {
291 return match(buf);
292 }
293 auto start = find_braces(buf.begin(), buf.end());
294 if (start >= 0) {
295 // we found an opening brace that is followed by a second brace or buffer end.
296 // 1. All values are a possible match
297 // 2. We can output the beginning of the buffer
298 // 3. Need to continue matching the remaining buffer
299 size_t pos = 0;
300 for (auto&& i : _values) {
301 sstring& key = std::get<0>(i);
302 _current.add_potential_match(key.data() + 1, key.data() + key.size(), pos++);
303 }
304 temporary_buffer<char> res = buf.share(0, start);
305 buf.trim_front(start + 1);
306 return res;
307 }
308
309 return std::move(buf);
310 }
311
312 }
313
314 }