summaryrefslogtreecommitdiff
path: root/src/regex__FFI.cc
blob: 66e25a8a71ea7a64e73d91082471b7ed17fa18d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright (C) 2015 the Massachusetts Institute of Technology
// Copyright (C) 2015 Benjamin Barenblat
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.

#include "src/regex__FFI.h"

#include <cstdio>
#include <cstring>
#include <regex>  // NOLINT(build/c++11)

#include <boost/numeric/conversion/cast.hpp>  // NOLINT(build/include_order)
extern "C" {
#include <urweb/urweb_cpp.h>  // NOLINT(build/include_order)
}

#include "./config.h"

namespace {

// Asserts a condition without crashing or releasing information about where the
// error occurred.  This function is essential for web programming, where an
// attacker should not be able to bring down the app by causing an assertion
// failure.
void Assert(uw_context* const context, const bool condition,
            const failure_kind action, const char* const message) {
  if (!condition) {
    uw_error(context, action, message);
  }
}

void Assert(uw_context* const context, const bool condition,
            const char* const message) {
  Assert(context, condition, FATAL, message);
}

void DeleteMatchResults(void* match_result,
                        [[gnu::unused]] const int _will_retry) {
  delete reinterpret_cast<std::cmatch*>(match_result);
}

// Bounds-checked numeric type conversion
template <typename Target, typename Source>
Target Number(uw_context* const context, Source arg) {
  try {
    return boost::numeric_cast<Target>(arg);
  } catch (const boost::numeric::bad_numeric_cast& e) {
    uw_error(context, FATAL, "regex: %s", e.what());
  }
}

}  // namespace

uw_Basis_bool uw_Regex__FFI_succeeded([[gnu::unused]] uw_context* const context,
                                      const uw_Regex__FFI_match match) {
  if (reinterpret_cast<std::cmatch*>(match.result)->empty()) {
    return uw_Basis_False;
  } else {
    return uw_Basis_True;
  }
}

uw_Basis_int uw_Regex__FFI_n_subexpression_matches(
    uw_context* const context, const uw_Regex__FFI_match match) {
  const std::cmatch::size_type n_matches =
      reinterpret_cast<std::cmatch*>(match.result)->size();
  if (n_matches == 0) {
    // Nothing got matched.
    return 0;
  } else {
    // At least one match occurred.  Compute the number of parenthesized
    // subexpressions that got matched, and return it.
    return Number<uw_Basis_int>(context, n_matches) - 1;
  }
}

uw_Basis_string uw_Regex__FFI_subexpression_match(
    uw_context* const context, const uw_Regex__FFI_match match,
    const uw_Basis_int match_index_signed) {
  const std::cmatch* const match_result =
      reinterpret_cast<std::cmatch*>(match.result);
  const std::size_t match_index =
      Number<std::size_t>(context, match_index_signed);
  Assert(context, match_index < match_result->size(),
         "regex: match does not exist");
  const auto matched_substring = (*match_result)[match_index + 1];
  // Save the matched substring.
  const std::size_t result_length =
      Number<std::size_t>(context, matched_substring.length());
  uw_Basis_string result =
      reinterpret_cast<uw_Basis_string>(uw_malloc(context, result_length + 1));
  Assert(context, std::snprintf(result, result_length + 1, "%s",
                                matched_substring.str().c_str()) >= 0,
         "regex: snprintf failed during match");
  return result;
}

uw_Regex__FFI_match uw_Regex__FFI_do_match(uw_context* const context,
                                           const uw_Basis_string needle_string,
                                           const uw_Basis_string haystack) {
  std::regex needle;
  try {
    needle.assign(needle_string, std::regex_constants::ECMAScript);
  } catch (const std::regex_error& e) {
    switch (e.code()) {
      case std::regex_constants::error_space:
      case std::regex_constants::error_stack:
        // We ran out of memory.
        uw_error(context, BOUNDED_RETRY, "regex: compilation failed: %s",
                 e.what());
      default:
        uw_error(context, FATAL, "regex: compilation failed: %s", e.what());
    }
  }
  uw_Regex__FFI_match result;
  // Make a duplicate of the string to match against, so if it goes out of
  // scope in the calling Ur code, we still have it.
  const auto haystack_length = std::strlen(haystack);
  result.haystack =
      reinterpret_cast<char*>(uw_malloc(context, haystack_length + 1));
  Assert(context, std::snprintf(result.haystack, haystack_length + 1, "%s",
                                haystack) >= 0,
         "regex: snprintf failed during match");
  // Allocate to store the match information.
  auto* match_results = new std::cmatch;
  Assert(context, uw_register_transactional(context, match_results, nullptr,
                                            nullptr, DeleteMatchResults) == 0,
         "regex: could not register DeleteMatchResults finalizer");
  result.result = match_results;
  // Execute the regex on the saved haystack, not the original one.
  std::regex_search(result.haystack, *match_results, needle);
  return result;
}