1/*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2015 Tatsuhiro Tsujikawa
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25#include "shrpx_mruby_module_request.h"
26
27#include <mruby/variable.h>
28#include <mruby/string.h>
29#include <mruby/hash.h>
30#include <mruby/array.h>
31
32#include "shrpx_downstream.h"
33#include "shrpx_upstream.h"
34#include "shrpx_client_handler.h"
35#include "shrpx_mruby.h"
36#include "shrpx_mruby_module.h"
37#include "shrpx_log.h"
38#include "util.h"
39#include "http2.h"
40
41namespace shrpx {
42
43namespace mruby {
44
45namespace {
46mrb_value request_init(mrb_state *mrb, mrb_value self) { return self; }
47} // namespace
48
49namespace {
50mrb_value request_get_http_version_major(mrb_state *mrb, mrb_value self) {
51  auto data = static_cast<MRubyAssocData *>(mrb->ud);
52  auto downstream = data->downstream;
53  const auto &req = downstream->request();
54  return mrb_fixnum_value(req.http_major);
55}
56} // namespace
57
58namespace {
59mrb_value request_get_http_version_minor(mrb_state *mrb, mrb_value self) {
60  auto data = static_cast<MRubyAssocData *>(mrb->ud);
61  auto downstream = data->downstream;
62  const auto &req = downstream->request();
63  return mrb_fixnum_value(req.http_minor);
64}
65} // namespace
66
67namespace {
68mrb_value request_get_method(mrb_state *mrb, mrb_value self) {
69  auto data = static_cast<MRubyAssocData *>(mrb->ud);
70  auto downstream = data->downstream;
71  const auto &req = downstream->request();
72  auto method = http2::to_method_string(req.method);
73
74  return mrb_str_new(mrb, method.c_str(), method.size());
75}
76} // namespace
77
78namespace {
79mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
80  auto data = static_cast<MRubyAssocData *>(mrb->ud);
81  auto downstream = data->downstream;
82  auto &req = downstream->request();
83
84  check_phase(mrb, data->phase, PHASE_REQUEST);
85
86  const char *method;
87  mrb_int n;
88  mrb_get_args(mrb, "s", &method, &n);
89  if (n == 0) {
90    mrb_raise(mrb, E_RUNTIME_ERROR, "method must not be empty string");
91  }
92  auto token =
93      http2::lookup_method_token(reinterpret_cast<const uint8_t *>(method), n);
94  if (token == -1) {
95    mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported");
96  }
97
98  req.method = token;
99
100  return self;
101}
102} // namespace
103
104namespace {
105mrb_value request_get_authority(mrb_state *mrb, mrb_value self) {
106  auto data = static_cast<MRubyAssocData *>(mrb->ud);
107  auto downstream = data->downstream;
108  const auto &req = downstream->request();
109
110  return mrb_str_new(mrb, req.authority.c_str(), req.authority.size());
111}
112} // namespace
113
114namespace {
115mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
116  auto data = static_cast<MRubyAssocData *>(mrb->ud);
117  auto downstream = data->downstream;
118  auto &req = downstream->request();
119
120  auto &balloc = downstream->get_block_allocator();
121
122  check_phase(mrb, data->phase, PHASE_REQUEST);
123
124  const char *authority;
125  mrb_int n;
126  mrb_get_args(mrb, "s", &authority, &n);
127  if (n == 0) {
128    mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string");
129  }
130
131  req.authority =
132      make_string_ref(balloc, StringRef{authority, static_cast<size_t>(n)});
133
134  return self;
135}
136} // namespace
137
138namespace {
139mrb_value request_get_scheme(mrb_state *mrb, mrb_value self) {
140  auto data = static_cast<MRubyAssocData *>(mrb->ud);
141  auto downstream = data->downstream;
142  const auto &req = downstream->request();
143
144  return mrb_str_new(mrb, req.scheme.c_str(), req.scheme.size());
145}
146} // namespace
147
148namespace {
149mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
150  auto data = static_cast<MRubyAssocData *>(mrb->ud);
151  auto downstream = data->downstream;
152  auto &req = downstream->request();
153
154  auto &balloc = downstream->get_block_allocator();
155
156  check_phase(mrb, data->phase, PHASE_REQUEST);
157
158  const char *scheme;
159  mrb_int n;
160  mrb_get_args(mrb, "s", &scheme, &n);
161  if (n == 0) {
162    mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string");
163  }
164
165  req.scheme =
166      make_string_ref(balloc, StringRef{scheme, static_cast<size_t>(n)});
167
168  return self;
169}
170} // namespace
171
172namespace {
173mrb_value request_get_path(mrb_state *mrb, mrb_value self) {
174  auto data = static_cast<MRubyAssocData *>(mrb->ud);
175  auto downstream = data->downstream;
176  const auto &req = downstream->request();
177
178  return mrb_str_new(mrb, req.path.c_str(), req.path.size());
179}
180} // namespace
181
182namespace {
183mrb_value request_set_path(mrb_state *mrb, mrb_value self) {
184  auto data = static_cast<MRubyAssocData *>(mrb->ud);
185  auto downstream = data->downstream;
186  auto &req = downstream->request();
187
188  auto &balloc = downstream->get_block_allocator();
189
190  check_phase(mrb, data->phase, PHASE_REQUEST);
191
192  const char *path;
193  mrb_int pathlen;
194  mrb_get_args(mrb, "s", &path, &pathlen);
195
196  req.path =
197      make_string_ref(balloc, StringRef{path, static_cast<size_t>(pathlen)});
198
199  return self;
200}
201} // namespace
202
203namespace {
204mrb_value request_get_headers(mrb_state *mrb, mrb_value self) {
205  auto data = static_cast<MRubyAssocData *>(mrb->ud);
206  auto downstream = data->downstream;
207  const auto &req = downstream->request();
208  return create_headers_hash(mrb, req.fs.headers());
209}
210} // namespace
211
212namespace {
213mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
214  auto data = static_cast<MRubyAssocData *>(mrb->ud);
215  auto downstream = data->downstream;
216  auto &req = downstream->request();
217  auto &balloc = downstream->get_block_allocator();
218
219  check_phase(mrb, data->phase, PHASE_REQUEST);
220
221  mrb_value key, values;
222  mrb_get_args(mrb, "So", &key, &values);
223
224  if (RSTRING_LEN(key) == 0) {
225    mrb_raise(mrb, E_RUNTIME_ERROR, "empty key is not allowed");
226  }
227
228  auto ai = mrb_gc_arena_save(mrb);
229
230  key = mrb_funcall(mrb, key, "downcase", 0);
231
232  auto keyref =
233      make_string_ref(balloc, StringRef{RSTRING_PTR(key),
234                                        static_cast<size_t>(RSTRING_LEN(key))});
235
236  mrb_gc_arena_restore(mrb, ai);
237
238  auto token = http2::lookup_token(keyref.byte(), keyref.size());
239
240  if (repl) {
241    size_t p = 0;
242    auto &headers = req.fs.headers();
243    for (size_t i = 0; i < headers.size(); ++i) {
244      auto &kv = headers[i];
245      if (kv.name == keyref) {
246        continue;
247      }
248      if (i != p) {
249        headers[p] = std::move(kv);
250      }
251      ++p;
252    }
253    headers.resize(p);
254  }
255
256  if (mrb_array_p(values)) {
257    auto n = RARRAY_LEN(values);
258    for (int i = 0; i < n; ++i) {
259      auto value = mrb_ary_ref(mrb, values, i);
260      if (!mrb_string_p(value)) {
261        mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
262      }
263
264      req.fs.add_header_token(
265          keyref,
266          make_string_ref(balloc,
267                          StringRef{RSTRING_PTR(value),
268                                    static_cast<size_t>(RSTRING_LEN(value))}),
269          false, token);
270    }
271  } else if (mrb_string_p(values)) {
272    req.fs.add_header_token(
273        keyref,
274        make_string_ref(balloc,
275                        StringRef{RSTRING_PTR(values),
276                                  static_cast<size_t>(RSTRING_LEN(values))}),
277        false, token);
278  } else {
279    mrb_raise(mrb, E_RUNTIME_ERROR, "value must be string");
280  }
281
282  return mrb_nil_value();
283}
284} // namespace
285
286namespace {
287mrb_value request_set_header(mrb_state *mrb, mrb_value self) {
288  return request_mod_header(mrb, self, true);
289}
290} // namespace
291
292namespace {
293mrb_value request_add_header(mrb_state *mrb, mrb_value self) {
294  return request_mod_header(mrb, self, false);
295}
296} // namespace
297
298namespace {
299mrb_value request_clear_headers(mrb_state *mrb, mrb_value self) {
300  auto data = static_cast<MRubyAssocData *>(mrb->ud);
301  auto downstream = data->downstream;
302  auto &req = downstream->request();
303
304  check_phase(mrb, data->phase, PHASE_REQUEST);
305
306  req.fs.clear_headers();
307
308  return mrb_nil_value();
309}
310} // namespace
311
312namespace {
313mrb_value request_push(mrb_state *mrb, mrb_value self) {
314  auto data = static_cast<MRubyAssocData *>(mrb->ud);
315  auto downstream = data->downstream;
316  auto upstream = downstream->get_upstream();
317
318  const char *uri;
319  mrb_int len;
320  mrb_get_args(mrb, "s", &uri, &len);
321
322  upstream->initiate_push(downstream, StringRef{uri, static_cast<size_t>(len)});
323
324  return mrb_nil_value();
325}
326} // namespace
327
328void init_request_class(mrb_state *mrb, RClass *module) {
329  auto request_class =
330      mrb_define_class_under(mrb, module, "Request", mrb->object_class);
331
332  mrb_define_method(mrb, request_class, "initialize", request_init,
333                    MRB_ARGS_NONE());
334  mrb_define_method(mrb, request_class, "http_version_major",
335                    request_get_http_version_major, MRB_ARGS_NONE());
336  mrb_define_method(mrb, request_class, "http_version_minor",
337                    request_get_http_version_minor, MRB_ARGS_NONE());
338  mrb_define_method(mrb, request_class, "method", request_get_method,
339                    MRB_ARGS_NONE());
340  mrb_define_method(mrb, request_class, "method=", request_set_method,
341                    MRB_ARGS_REQ(1));
342  mrb_define_method(mrb, request_class, "authority", request_get_authority,
343                    MRB_ARGS_NONE());
344  mrb_define_method(mrb, request_class, "authority=", request_set_authority,
345                    MRB_ARGS_REQ(1));
346  mrb_define_method(mrb, request_class, "scheme", request_get_scheme,
347                    MRB_ARGS_NONE());
348  mrb_define_method(mrb, request_class, "scheme=", request_set_scheme,
349                    MRB_ARGS_REQ(1));
350  mrb_define_method(mrb, request_class, "path", request_get_path,
351                    MRB_ARGS_NONE());
352  mrb_define_method(mrb, request_class, "path=", request_set_path,
353                    MRB_ARGS_REQ(1));
354  mrb_define_method(mrb, request_class, "headers", request_get_headers,
355                    MRB_ARGS_NONE());
356  mrb_define_method(mrb, request_class, "add_header", request_add_header,
357                    MRB_ARGS_REQ(2));
358  mrb_define_method(mrb, request_class, "set_header", request_set_header,
359                    MRB_ARGS_REQ(2));
360  mrb_define_method(mrb, request_class, "clear_headers", request_clear_headers,
361                    MRB_ARGS_NONE());
362  mrb_define_method(mrb, request_class, "push", request_push, MRB_ARGS_REQ(1));
363}
364
365} // namespace mruby
366
367} // namespace shrpx
368