diff options
Diffstat (limited to 'src/lib/util/path_to_regex.cpp')
-rw-r--r-- | src/lib/util/path_to_regex.cpp | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/src/lib/util/path_to_regex.cpp b/src/lib/util/path_to_regex.cpp new file mode 100644 index 00000000000..927ababbb8a --- /dev/null +++ b/src/lib/util/path_to_regex.cpp @@ -0,0 +1,217 @@ +// license:MIT +// copyright-holders:Alfred Bratterud +// NOTE: Author allowed MAME project to distribute this file under MIT +// license. Other projects need to do it under Apache 2 license +// +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +// https://github.com/pillarjs/path-to-regexp/blob/master/index.js + +#include "path_to_regex.hpp" + +namespace path2regex { + +const std::regex PATH_REGEXP = + std::regex{"((\\\\.)|(([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))))"}; + +std::regex path_to_regex(const std::string& path, Keys& keys, const Options& options) { + Tokens all_tokens = parse(path); + tokens_to_keys(all_tokens, keys); // fill keys with relevant tokens + return tokens_to_regex(all_tokens, options); +} + +std::regex path_to_regex(const std::string& path, const Options& options) { + return tokens_to_regex(parse(path), options); +} + +// Parse a string for the raw tokens +std::vector<Token> parse(const std::string& str) { + if (str.empty()) + return {}; + + Tokens tokens; + int key = 0; + int index = 0; + std::string path = ""; + std::smatch res; + + for (std::sregex_iterator i = std::sregex_iterator{str.begin(), str.end(), PATH_REGEXP}; + i != std::sregex_iterator{}; ++i) { + + res = *i; + + std::string m = res[0]; // the parameter, f.ex. /:test + std::string escaped = res[2]; + int offset = res.position(); + + // JS: path += str.slice(index, offset); from and included index to and included offset-1 + path += str.substr(index, (offset - index)); // from index, number of chars: offset - index + + index = offset + m.size(); + + if (!escaped.empty()) { + path += escaped[1]; // if escaped == \a, escaped[1] == a (if str is "/\\a" f.ex.) + continue; + } + + std::string next = (size_t(index) < str.size()) ? std::string{str.at(index)} : ""; + + std::string prefix = res[4]; // f.ex. / + std::string name = res[5]; // f.ex. test + std::string capture = res[6]; // f.ex. \d+ + std::string group = res[7]; // f.ex. (users|admins) + std::string modifier = res[8]; // f.ex. ? + std::string asterisk = res[9]; // * if path is /* + + // Push the current path onto the tokens + if (!path.empty()) { + Token stringToken; + stringToken.set_string_token(path); + tokens.push_back(stringToken); + path = ""; + } + + bool partial = (!prefix.empty()) && (!next.empty()) && (next != prefix); + bool repeat = (modifier == "+") || (modifier == "*"); + bool optional = (modifier == "?") || (modifier == "*"); + std::string delimiter = (!prefix.empty()) ? prefix : "/"; + std::string pattern; + + if (!capture.empty()) + pattern = capture; + else if (!group.empty()) + pattern = group; + else + pattern = (!asterisk.empty()) ? ".*" : ("[^" + delimiter + "]+?"); + + Token t; + t.name = (!name.empty()) ? name : std::to_string(key++); + t.prefix = prefix; + t.delimiter = delimiter; + t.optional = optional; + t.repeat = repeat; + t.partial = partial; + t.asterisk = (asterisk == "*"); + t.pattern = pattern; + t.is_string = false; + tokens.push_back(t); + } + + // Match any characters still remaining + if (size_t(index) < str.size()) + path += str.substr(index); + + // If the path exists, push it onto the end + if (!path.empty()) { + Token stringToken; + stringToken.set_string_token(path); + tokens.push_back(stringToken); + } + + return tokens; +} + +// Creates a regex based on the given tokens and options (optional) +std::regex tokens_to_regex(const Tokens& tokens, const Options& options) { + if (tokens.empty()) + return std::regex{""}; + + // Set default values for options: + bool strict = false; + bool sensitive = false; + bool end = true; + + if (!options.empty()) { + auto it = options.find("strict"); + strict = (it != options.end()) ? options.find("strict")->second : false; + + it = options.find("sensitive"); + sensitive = (it != options.end()) ? options.find("sensitive")->second : false; + + it = options.find("end"); + end = (it != options.end()) ? options.find("end")->second : true; + } + + std::string route = ""; + Token lastToken = tokens[tokens.size() - 1]; + std::regex re{"(.*\\/$)"}; + bool endsWithSlash = lastToken.is_string && std::regex_match(lastToken.name, re); + // endsWithSlash if the last char in lastToken's name is a slash + + // Iterate over the tokens and create our regexp string + for (size_t i = 0; i < tokens.size(); i++) { + Token token = tokens[i]; + + if (token.is_string) { + route += token.name; + } else { + std::string prefix = token.prefix; + std::string capture = "(?:" + token.pattern + ")"; + + if (token.repeat) + capture += "(?:" + prefix + capture + ")*"; + + if (token.optional) { + + if (!token.partial) + capture = "(?:" + prefix + "(" + capture + "))?"; + else + capture = prefix + "(" + capture + ")?"; + + } else { + capture = prefix + "(" + capture + ")"; + } + + route += capture; + } + } + + // In non-strict mode we allow a slash at the end of match. If the path to + // match already ends with a slash, we remove it for consistency. The slash + // is valid at the end of a path match, not in the middle. This is important + // in non-ending mode, where "/test/" shouldn't match "/test//route". + + if (!strict) { + if (endsWithSlash) + route = route.substr(0, (route.size() - 1)); + + route += "(?:\\/(?=$))?"; + } + + if (end) { + route += "$"; + } else { + // In non-ending mode, we need the capturing groups to match as much as + // possible by using a positive lookahead to the end or next path segment + if (!(strict && endsWithSlash)) + route += "(?=\\/|$)"; + } + + if (sensitive) + return std::regex{"^" + route}; + + return std::regex{"^" + route, std::regex_constants::ECMAScript | std::regex_constants::icase}; +} + +void tokens_to_keys(const Tokens& tokens, Keys& keys) { + for (const auto& token : tokens) + if (!token.is_string) + keys.push_back(token); +} + +} //< namespace route |