#pragma once #include #include #include #include #include #include namespace crow { // ---------------------------------------------------------------------------- // qs_parse (modified) // https://github.com/bartgrantham/qs_parse // ---------------------------------------------------------------------------- /* Similar to strncmp, but handles URL-encoding for either string */ int qs_strncmp(const char* s, const char* qs, size_t n); /* Finds the beginning of each key/value pair and stores a pointer in qs_kv. * Also decodes the value portion of the k/v pair *in-place*. In a future * enhancement it will also have a compile-time option of sorting qs_kv * alphabetically by key. */ int qs_parse(char* qs, char* qs_kv[], int qs_kv_size, bool parse_url); /* Used by qs_parse to decode the value portion of a k/v pair */ int qs_decode(char * qs); /* Looks up the value according to the key on a pre-processed query string * A future enhancement will be a compile-time option to look up the key * in a pre-sorted qs_kv array via a binary search. */ //char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size); char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth); /* Non-destructive lookup of value, based on key. User provides the * destinaton string and length. */ char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len); // TODO: implement sorting of the qs_kv array; for now ensure it's not compiled #undef _qsSORTING // isxdigit _is_ available in , but let's avoid another header instead #define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0) #define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0) #define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1) inline int qs_strncmp(const char * s, const char * qs, size_t n) { int i=0; unsigned char u1, u2, unyb, lnyb; while(n-- > 0) { u1 = static_cast(*s++); u2 = static_cast(*qs++); if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; } if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; } if ( u1 == '+' ) { u1 = ' '; } if ( u1 == '%' ) // easier/safer than scanf { unyb = static_cast(*s++); lnyb = static_cast(*s++); if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) ) u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); else u1 = '\0'; } if ( u2 == '+' ) { u2 = ' '; } if ( u2 == '%' ) // easier/safer than scanf { unyb = static_cast(*qs++); lnyb = static_cast(*qs++); if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) ) u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); else u2 = '\0'; } if ( u1 != u2 ) return u1 - u2; if ( u1 == '\0' ) return 0; i++; } if ( CROW_QS_ISQSCHR(*qs) ) return -1; else return 0; } inline int qs_parse(char* qs, char* qs_kv[], int qs_kv_size, bool parse_url = true) { int i, j; char * substr_ptr; for(i=0; i means x iterations of this loop -> means *x+1* k/v pairs // we only decode the values in place, the keys could have '='s in them // which will hose our ability to distinguish keys from values later for(j=0; j* qs_dict_name2kv(const char * dict_name, char * const * qs_kv, int qs_kv_size, int nth = 0) { int i; size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close; name_len = strlen(dict_name); #ifdef _qsSORTING // TODO: binary search for key in the sorted qs_kv #else // _qsSORTING for(i=0; i 0 && skip_to_brace_close > 0 && nth == 0 ) { auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open); auto value = std::string(qs_kv[i] + skip_to_eq); return new std::pair(key, value); } else { --nth; } } } #endif // _qsSORTING return nullptr; } inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len) { size_t i, key_len; const char * tmp; // find the beginning of the k/v substrings if ( (tmp = strchr(qs, '?')) != NULL ) qs = tmp + 1; key_len = strlen(key); while(qs[0] != '#' && qs[0] != '\0') { if ( qs_strncmp(key, qs, key_len) == 0 ) break; qs += strcspn(qs, "&") + 1; } if ( qs[0] == '\0' ) return NULL; qs += strcspn(qs, "=&#"); if ( qs[0] == '=' ) { qs++; i = strcspn(qs, "&=#"); #ifdef _MSC_VER strncpy_s(val, val_len, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1)); #else strncpy(val, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1)); #endif qs_decode(val); } else { if ( val_len > 0 ) val[0] = '\0'; } return val; } } // ---------------------------------------------------------------------------- namespace crow { struct request; /// A class to represent any data coming after the `?` in the request URL into key-value pairs. class query_string { public: static const int MAX_KEY_VALUE_PAIRS_COUNT = 256; query_string() { } query_string(const query_string& qs): url_(qs.url_) { for (auto p : qs.key_value_pairs_) { key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str())); } } query_string& operator=(const query_string& qs) { url_ = qs.url_; key_value_pairs_.clear(); for (auto p : qs.key_value_pairs_) { key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str())); } return *this; } query_string& operator=(query_string&& qs) { key_value_pairs_ = std::move(qs.key_value_pairs_); char* old_data = (char*)qs.url_.c_str(); url_ = std::move(qs.url_); for (auto& p : key_value_pairs_) { p += (char*)url_.c_str() - old_data; } return *this; } query_string(std::string params, bool url = true): url_(std::move(params)) { if (url_.empty()) return; key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT); int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url); key_value_pairs_.resize(count); } void clear() { key_value_pairs_.clear(); url_.clear(); } friend std::ostream& operator<<(std::ostream& os, const query_string& qs) { os << "[ "; for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i) { if (i) os << ", "; os << qs.key_value_pairs_[i]; } os << " ]"; return os; } /// Get a value from a name, used for `?name=value`. /// /// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list(). char* get(const std::string& name) const { char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size()); return ret; } /// Works similar to \ref get() except it removes the item from the query string. char* pop(const std::string& name) { char* ret = get(name); if (ret != nullptr) { for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if (str_item.substr(0, name.size() + 1) == name + '=') { key_value_pairs_.erase(key_value_pairs_.begin() + i); break; } } } return ret; } /// Returns a list of values, passed as `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of the list. /// /// Note: Square brackets in the above example are controlled by `use_brackets` boolean (true by default). If set to false, the example becomes `?name=value1,name=value2...name=valuen` std::vector get_list(const std::string& name, bool use_brackets = true) const { std::vector ret; std::string plus = name + (use_brackets ? "[]" : ""); char* element = nullptr; int count = 0; while (1) { element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++); if (!element) break; ret.push_back(element); } return ret; } /// Similar to \ref get_list() but it removes the std::vector pop_list(const std::string& name, bool use_brackets = true) { std::vector ret = get_list(name, use_brackets); if (!ret.empty()) { for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if ((use_brackets ? (str_item.substr(0, name.size() + 3) == name + "[]=") : (str_item.substr(0, name.size() + 1) == name + '='))) { key_value_pairs_.erase(key_value_pairs_.begin() + i--); } } } return ret; } /// Works similar to \ref get_list() except the brackets are mandatory must not be empty. /// /// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`. /// /// if your query string has both empty brackets and ones with a key inside, use pop_list() to get all the values without a key before running this method. std::unordered_map get_dict(const std::string& name) const { std::unordered_map ret; int count = 0; while (1) { auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++); if (element) ret.insert(*element); else break; delete element; } return ret; } /// Works the same as \ref get_dict() but removes the values from the query string. std::unordered_map pop_dict(const std::string& name) { std::unordered_map ret = get_dict(name); if (!ret.empty()) { for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if (str_item.substr(0, name.size() + 1) == name + '[') { key_value_pairs_.erase(key_value_pairs_.begin() + i--); } } } return ret; } std::vector keys() const { std::vector ret; for (auto element : key_value_pairs_) { std::string str_element(element); ret.emplace_back(str_element.substr(0, str_element.find('='))); } return ret; } private: std::string url_; std::vector key_value_pairs_; }; } // namespace crow