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.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
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
19 * Copyright 2015 Cloudius Systems
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>
34 struct potential_match_entry
{
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
45 class buffer_replace_state
{
46 std::list
<potential_match_entry
> _potential_match
;
49 using iterator
= std::list
<potential_match_entry
>::iterator
;
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
});
56 return _potential_match
.begin();
60 return _potential_match
.end();
64 return _potential_match
.empty();
68 return _potential_match
.size() == 1;
71 auto erase(const iterator
& i
) {
72 return _potential_match
.erase(i
);
76 * \brief gets the key/value position in the buffer_replace of the match
78 size_t get_pos() const {
79 return (*_potential_match
.begin()).pos
;
83 * \brief gets the length of the remaining string
85 size_t get_remaining_length() const {
86 return _potential_match
.begin()->end
- _potential_match
.begin()->begin
;
90 _potential_match
.clear();
95 *\brief a helper class to replace strings in a buffer
96 * The keys to replace are surrounded by braces
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;
105 * \brief Add a key and value to be replaced
107 buffer_replace
& add(sstring key
, sstring value
) {
108 _values
.emplace_back(std::make_tuple("{{" + key
+ "}}", value
));
113 * \brief if there are no more buffers to consume get
114 * the remaining chars stored in the buffer_replace
116 temporary_buffer
<char> get_remaining();
119 * \brief check if the given buffer still match any of the current potential matches
122 temporary_buffer
<char> match(temporary_buffer
<char>& buf
);
124 * \brief replace the buffer content
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.
129 * For example: if buf is: "abcd{{key}}"
130 * res = replace(buf);
133 * and buf will be "{{key}}"
135 temporary_buffer
<char> replace(temporary_buffer
<char>& buf
);
138 * \brief check if we are currently in the middle of consuming
140 bool is_consuming() const {
141 return !_current
.empty();
146 class content_replace_data_sink_impl
: public data_sink_impl
{
147 output_stream
<char> _out
;
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
));
156 virtual future
<> put(net::packet data
) override
{
157 return make_ready_future
<>();
160 using data_sink_impl::put
;
162 virtual future
<> put(temporary_buffer
<char> buf
) override
{
164 return make_ready_future
<>();
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
;
176 virtual future
<> flush() override
{
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] {
194 class content_replace_data_sink
: public data_sink
{
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
))) {}
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
)) {
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
);
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
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) == '{')) {
227 const sstring
& buffer_replace::get_value(size_t pos
) const {
228 return std::get
<1>(_values
[pos
]);
231 const sstring
& buffer_replace::get_key(size_t pos
) const {
232 return std::get
<0>(_values
[pos
]);
235 temporary_buffer
<char> buffer_replace::match(temporary_buffer
<char>& buf
) {
236 if (_current
.empty()) {
237 return temporary_buffer
<char>();
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
);
254 first
= _current
.erase(first
);
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
);
266 // only partial match
271 // if we are here we run out of buffer
272 buf
.trim_front(buf_len
);
273 return temporary_buffer
<char>();
276 temporary_buffer
<char> buffer_replace::get_remaining() {
277 if (!is_consuming()) {
278 return temporary_buffer
<char>();
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
);
286 temporary_buffer
<char> buffer_replace::replace(temporary_buffer
<char>& buf
) {
288 return std::move(buf
);
290 if (is_consuming()) {
293 auto start
= find_braces(buf
.begin(), buf
.end());
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
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
++);
304 temporary_buffer
<char> res
= buf
.share(0, start
);
305 buf
.trim_front(start
+ 1);
309 return std::move(buf
);