1// Copyright 2015 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "webservd/request.h"
16
17#include <microhttpd.h>
18
19#include <base/bind.h>
20#include <base/files/file.h>
21#include <base/guid.h>
22#include <brillo/http/http_request.h>
23#include <brillo/http/http_utils.h>
24#include <brillo/mime_utils.h>
25#include <brillo/streams/file_stream.h>
26#include <brillo/strings/string_utils.h>
27#include "webservd/log_manager.h"
28#include "webservd/protocol_handler.h"
29#include "webservd/request_handler_interface.h"
30#include "webservd/server_interface.h"
31#include "webservd/temp_file_manager.h"
32
33namespace webservd {
34
35// Helper class to provide static callback methods to microhttpd library,
36// with the ability to access private methods of Request class.
37class RequestHelper {
38 public:
39  static int PostDataIterator(void* cls,
40                              MHD_ValueKind /* kind */,
41                              const char* key,
42                              const char* filename,
43                              const char* content_type,
44                              const char* transfer_encoding,
45                              const char* data,
46                              uint64_t off,
47                              size_t size) {
48    auto self = reinterpret_cast<Request*>(cls);
49    return self->ProcessPostData(key, filename, content_type, transfer_encoding,
50                                 data, off, size) ? MHD_YES : MHD_NO;
51  }
52
53  static int ValueCallback(void* cls,
54                           MHD_ValueKind kind,
55                           const char* key,
56                           const char* value) {
57    auto self = reinterpret_cast<Request*>(cls);
58    std::string data;
59    if (value)
60      data = value;
61    if (kind == MHD_HEADER_KIND) {
62      self->headers_.emplace_back(brillo::http::GetCanonicalHeaderName(key),
63                                  data);
64    } else if (kind == MHD_COOKIE_KIND) {
65      // TODO(avakulenko): add support for cookies...
66    } else if (kind == MHD_POSTDATA_KIND) {
67      self->post_data_.emplace_back(key, data);
68    } else if (kind == MHD_GET_ARGUMENT_KIND) {
69      self->get_data_.emplace_back(key, data);
70    }
71    return MHD_YES;
72  }
73};
74
75FileInfo::FileInfo(const std::string& in_field_name,
76                   const std::string& in_file_name,
77                   const std::string& in_content_type,
78                   const std::string& in_transfer_encoding)
79    : field_name(in_field_name),
80      file_name(in_file_name),
81      content_type(in_content_type),
82      transfer_encoding(in_transfer_encoding) {
83}
84
85Request::Request(
86    const std::string& request_handler_id,
87    const std::string& url,
88    const std::string& method,
89    const std::string& version,
90    MHD_Connection* connection,
91    ProtocolHandler* protocol_handler)
92    : id_{base::GenerateGUID()},
93      request_handler_id_{request_handler_id},
94      url_{url},
95      method_{method},
96      version_{version},
97      connection_{connection},
98      protocol_handler_{protocol_handler} {
99  // Here we create the data pipe used to transfer the request body from the
100  // web server to the remote request handler.
101  int pipe_fds[2] = {-1, -1};
102  CHECK_EQ(0, pipe(pipe_fds));
103  request_data_pipe_out_ = base::File{pipe_fds[0]};
104  CHECK(request_data_pipe_out_.IsValid());
105  request_data_stream_ = brillo::FileStream::FromFileDescriptor(
106      pipe_fds[1], true, nullptr);
107  CHECK(request_data_stream_);
108
109  // POST request processor.
110  post_processor_ = MHD_create_post_processor(
111      connection, 1024, &RequestHelper::PostDataIterator, this);
112}
113
114Request::~Request() {
115  if (post_processor_)
116    MHD_destroy_post_processor(post_processor_);
117  GetTempFileManager()->DeleteRequestTempFiles(id_);
118  protocol_handler_->RemoveRequest(this);
119}
120
121base::File Request::GetFileData(int file_id) {
122  base::File file;
123  if (file_id >= 0 && static_cast<size_t>(file_id) < file_info_.size()) {
124    file.Initialize(file_info_[file_id]->temp_file_name,
125                    base::File::FLAG_OPEN | base::File::FLAG_READ);
126  }
127  return file;
128}
129
130base::File Request::Complete(
131    int32_t status_code,
132    const std::vector<std::tuple<std::string, std::string>>& headers,
133    int64_t in_data_size) {
134  base::File file;
135  if (response_data_started_)
136    return file;
137
138  response_status_code_ = status_code;
139  response_headers_.reserve(headers.size());
140  for (const auto& tuple : headers) {
141    response_headers_.emplace_back(std::get<0>(tuple), std::get<1>(tuple));
142  }
143
144  // Create the pipe for response data.
145  int pipe_fds[2] = {-1, -1};
146  CHECK_EQ(0, pipe(pipe_fds));
147  file = base::File{pipe_fds[1]};
148  CHECK(file.IsValid());
149  response_data_stream_ = brillo::FileStream::FromFileDescriptor(
150      pipe_fds[0], true, nullptr);
151  CHECK(response_data_stream_);
152
153  response_data_size_ = in_data_size;
154  response_data_started_ = true;
155  const MHD_ConnectionInfo* info =
156      MHD_get_connection_info(connection_, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
157
158  const sockaddr* client_addr = (info ? info->client_addr : nullptr);
159  LogManager::OnRequestCompleted(base::Time::Now(), client_addr, method_, url_,
160                                 version_, status_code, in_data_size);
161  protocol_handler_->ScheduleWork();
162  return file;
163}
164
165bool Request::Complete(
166    int32_t status_code,
167    const std::vector<std::tuple<std::string, std::string>>& /* headers */,
168    const std::string& mime_type,
169    const std::string& data) {
170  std::vector<std::tuple<std::string, std::string>> headers_copy;
171  headers_copy.emplace_back(brillo::http::response_header::kContentType,
172                            mime_type);
173  base::File file = Complete(status_code, headers_copy, data.size());
174  bool success = false;
175  if (file.IsValid()) {
176    const int size = data.size();
177    success = (file.WriteAtCurrentPos(data.c_str(), size) == size);
178  }
179  return success;
180}
181
182const std::string& Request::GetProtocolHandlerID() const {
183  return protocol_handler_->GetID();
184}
185
186int Request::GetBodyDataFileDescriptor() const {
187  int fd = dup(request_data_pipe_out_.GetPlatformFile());
188  CHECK_GE(fd, 0);
189  return fd;
190}
191
192bool Request::BeginRequestData() {
193  MHD_get_connection_values(connection_, MHD_HEADER_KIND,
194                            &RequestHelper::ValueCallback, this);
195  MHD_get_connection_values(connection_, MHD_COOKIE_KIND,
196                            &RequestHelper::ValueCallback, this);
197  MHD_get_connection_values(connection_, MHD_POSTDATA_KIND,
198                            &RequestHelper::ValueCallback, this);
199  MHD_get_connection_values(connection_, MHD_GET_ARGUMENT_KIND,
200                            &RequestHelper::ValueCallback, this);
201  // If we have POST processor, then we are parsing the request ourselves and
202  // we need to dispatch it to the handler only after all the data is parsed.
203  // Otherwise forward the request immediately and let the handler read the
204  // request data as needed.
205  if (!post_processor_)
206    ForwardRequestToHandler();
207  return true;
208}
209
210bool Request::AddRequestData(const void* data, size_t* size) {
211  if (!post_processor_)
212    return AddRawRequestData(data, size);
213  int result =
214      MHD_post_process(post_processor_, static_cast<const char*>(data), *size);
215  *size = 0;
216  return result == MHD_YES;
217}
218
219void Request::EndRequestData() {
220  if (!request_data_finished_) {
221    if (request_data_stream_)
222      request_data_stream_->CloseBlocking(nullptr);
223    if (!request_forwarded_)
224      ForwardRequestToHandler();
225    request_data_finished_ = true;
226  }
227
228  if (response_data_started_ && !response_data_finished_) {
229    MHD_Response* resp = MHD_create_response_from_callback(
230        response_data_size_, 4096, &Request::ResponseDataCallback, this,
231        nullptr);
232    CHECK(resp);
233    for (const auto& pair : response_headers_) {
234      MHD_add_response_header(resp, pair.first.c_str(), pair.second.c_str());
235    }
236    CHECK_EQ(MHD_YES,
237             MHD_queue_response(connection_, response_status_code_, resp))
238        << "Failed to queue response";
239    MHD_destroy_response(resp);  // |resp| is ref-counted.
240    response_data_finished_ = true;
241  }
242}
243
244void Request::ForwardRequestToHandler() {
245  request_forwarded_ = true;
246  if (!request_handler_id_.empty()) {
247    // Close all temporary file streams, if any.
248    for (auto& file : file_info_)
249      file->data_stream->CloseBlocking(nullptr);
250
251    protocol_handler_->AddRequest(this);
252    auto p = protocol_handler_->request_handlers_.find(request_handler_id_);
253    CHECK(p != protocol_handler_->request_handlers_.end());
254    // Send the request over D-Bus and await the response.
255    p->second.handler->HandleRequest(this);
256  } else {
257    // There was no handler found when request was made, respond with
258    // 404 Page Not Found.
259    Complete(brillo::http::status_code::NotFound, {},
260             brillo::mime::text::kPlain, "Not Found");
261  }
262}
263
264bool Request::ProcessPostData(const char* key,
265                              const char* filename,
266                              const char* content_type,
267                              const char* transfer_encoding,
268                              const char* data,
269                              uint64_t off,
270                              size_t size) {
271  if (off > 0)
272    return AppendPostFieldData(key, data, size);
273
274  return AddPostFieldData(key, filename, content_type, transfer_encoding, data,
275                          size);
276}
277
278bool Request::AddRawRequestData(const void* data, size_t* size) {
279  CHECK(*size);
280  CHECK(request_data_stream_) << "Data pipe hasn't been created.";
281
282  size_t written = 0;
283  if (!request_data_stream_->WriteNonBlocking(data, *size, &written, nullptr))
284    return false;
285
286  CHECK_LE(written, *size);
287
288  // If we didn't write all the data requested, we need to let libmicrohttpd do
289  // another write cycle. Schedule a DoWork() action here.
290  if (written != *size)
291    protocol_handler_->ScheduleWork();
292
293  *size -= written;
294
295  // If written at least some data, we are good. We will be called again if more
296  // data is available.
297  if (written > 0 || waiting_for_data_)
298    return true;
299
300  // Nothing has been written. The output pipe is full. Need to stop the data
301  // transfer on the connection and wait till some data is being read from the
302  // pipe by the request handler.
303  MHD_suspend_connection(connection_);
304
305  // Now, just monitor the pipe and figure out when we can resume sending data
306  // over it.
307  waiting_for_data_ = request_data_stream_->WaitForData(
308      brillo::Stream::AccessMode::WRITE,
309      base::Bind(&Request::OnPipeAvailable, weak_ptr_factory_.GetWeakPtr()),
310      nullptr);
311
312  if (!waiting_for_data_)
313    MHD_resume_connection(connection_);
314
315  return waiting_for_data_;
316}
317
318ssize_t Request::ResponseDataCallback(void *cls, uint64_t /* pos */, char *buf,
319                                      size_t max) {
320  Request* self = static_cast<Request*>(cls);
321  size_t read = 0;
322  bool eos = false;
323  if (!self->response_data_stream_->ReadNonBlocking(buf, max, &read, &eos,
324                                                    nullptr)) {
325    return MHD_CONTENT_READER_END_WITH_ERROR;
326  }
327
328  if (read > 0 || self->waiting_for_data_)
329    return read;
330
331  if (eos)
332    return MHD_CONTENT_READER_END_OF_STREAM;
333
334  // Nothing can be read. The input pipe is empty. Need to stop the data
335  // transfer on the connection and wait till some data is available from the
336  // pipe.
337  MHD_suspend_connection(self->connection_);
338
339  self->waiting_for_data_ = self->response_data_stream_->WaitForData(
340      brillo::Stream::AccessMode::READ,
341      base::Bind(&Request::OnPipeAvailable,
342                 self->weak_ptr_factory_.GetWeakPtr()),
343      nullptr);
344
345  if (!self->waiting_for_data_) {
346    MHD_resume_connection(self->connection_);
347    return MHD_CONTENT_READER_END_WITH_ERROR;
348  }
349  return 0;
350}
351
352void Request::OnPipeAvailable(brillo::Stream::AccessMode /* mode */) {
353  MHD_resume_connection(connection_);
354  waiting_for_data_ = false;
355  protocol_handler_->ScheduleWork();
356}
357
358bool Request::AddPostFieldData(const char* key,
359                               const char* filename,
360                               const char* content_type,
361                               const char* transfer_encoding,
362                               const char* data,
363                               size_t size) {
364  if (filename) {
365    std::unique_ptr<FileInfo> file_info{
366        new FileInfo{key, filename, content_type ? content_type : "",
367                     transfer_encoding ? transfer_encoding : ""}};
368    file_info->temp_file_name = GetTempFileManager()->CreateTempFileName(id_);
369    file_info->data_stream = brillo::FileStream::Open(
370        file_info->temp_file_name, brillo::Stream::AccessMode::READ_WRITE,
371        brillo::FileStream::Disposition::CREATE_ALWAYS, nullptr);
372    if (!file_info->data_stream ||
373        !file_info->data_stream->WriteAllBlocking(data, size, nullptr)) {
374      return false;
375    }
376    file_info_.push_back(std::move(file_info));
377    last_posted_data_was_file_ = true;
378    return true;
379  }
380  std::string value{data, size};
381  post_data_.emplace_back(key, value);
382  last_posted_data_was_file_ = false;
383  return true;
384}
385
386bool Request::AppendPostFieldData(const char* key,
387                                  const char* data,
388                                  size_t size) {
389  if (last_posted_data_was_file_) {
390    CHECK(!file_info_.empty());
391    CHECK(file_info_.back()->field_name == key);
392    FileInfo* file_info = file_info_.back().get();
393    return file_info->data_stream->WriteAllBlocking(data, size, nullptr);
394  }
395
396  CHECK(!post_data_.empty());
397  CHECK(post_data_.back().first == key);
398  post_data_.back().second.append(data, size);
399  return true;
400}
401
402TempFileManager* Request::GetTempFileManager() {
403  return protocol_handler_->GetServer()->GetTempFileManager();
404}
405
406}  // namespace webservd
407