Move crow to its own repo
diff --git a/crow/include/crow/query_string.h b/crow/include/crow/query_string.h
new file mode 100644
index 0000000..2bdb216
--- /dev/null
+++ b/crow/include/crow/query_string.h
@@ -0,0 +1,313 @@
+#pragma once
+
+#include <stdio.h>
+#include <string.h>
+#include <iostream>
+#include <string>
+#include <vector>
+
+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);
+
+/*  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 <ctype.h>, 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 = (unsigned char)*s++;
+    u2 = (unsigned char)*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 = (unsigned char)*s++;
+      lnyb = (unsigned char)*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 = (unsigned char)*qs++;
+      lnyb = (unsigned char)*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) {
+  int i, j;
+  char* substr_ptr;
+
+  for (i = 0; i < qs_kv_size; i++) qs_kv[i] = NULL;
+
+  // find the beginning of the k/v substrings or the fragment
+  substr_ptr = qs + strcspn(qs, "?#");
+  if (substr_ptr[0] != '\0')
+    substr_ptr++;
+  else
+    return 0;  // no query or fragment
+
+  i = 0;
+  while (i < qs_kv_size) {
+    qs_kv[i] = substr_ptr;
+    j = strcspn(substr_ptr, "&");
+    if (substr_ptr[j] == '\0') {
+      break;
+    }
+    substr_ptr += j + 1;
+    i++;
+  }
+  i++;  // x &'s -> 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 < i; j++) {
+    substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
+    if (substr_ptr[0] == '&' ||
+        substr_ptr[0] == '\0')  // blank value: skip decoding
+      substr_ptr[0] = '\0';
+    else
+      qs_decode(++substr_ptr);
+  }
+
+#ifdef _qsSORTING
+// TODO: qsort qs_kv, using qs_strncmp() for the comparison
+#endif
+
+  return i;
+}
+
+inline int qs_decode(char* qs) {
+  int i = 0, j = 0;
+
+  while (CROW_QS_ISQSCHR(qs[j])) {
+    if (qs[j] == '+') {
+      qs[i] = ' ';
+    } else if (qs[j] == '%')  // easier/safer than scanf
+    {
+      if (!CROW_QS_ISHEX(qs[j + 1]) || !CROW_QS_ISHEX(qs[j + 2])) {
+        qs[i] = '\0';
+        return i;
+      }
+      qs[i] = (CROW_QS_HEX2DEC(qs[j + 1]) * 16) + CROW_QS_HEX2DEC(qs[j + 2]);
+      j += 2;
+    } else {
+      qs[i] = qs[j];
+    }
+    i++;
+    j++;
+  }
+  qs[i] = '\0';
+
+  return i;
+}
+
+inline char* qs_k2v(const char* key, char* const* qs_kv, int qs_kv_size,
+                    int nth = 0) {
+  int i;
+  size_t key_len, skip;
+
+  key_len = strlen(key);
+
+#ifdef _qsSORTING
+// TODO: binary search for key in the sorted qs_kv
+#else   // _qsSORTING
+  for (i = 0; i < qs_kv_size; i++) {
+    // we rely on the unambiguous '=' to find the value in our k/v pair
+    if (qs_strncmp(key, qs_kv[i], key_len) == 0) {
+      skip = strcspn(qs_kv[i], "=");
+      if (qs_kv[i][skip] == '=') skip++;
+      // return (zero-char value) ? ptr to trailing '\0' : ptr to value
+      if (nth == 0)
+        return qs_kv[i] + skip;
+      else
+        --nth;
+    }
+  }
+#endif  // _qsSORTING
+
+  return NULL;
+}
+
+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, "&=#");
+    strncpy(val, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1));
+    qs_decode(val);
+  } else {
+    if (val_len > 0) val[0] = '\0';
+  }
+
+  return val;
+}
+}
+// ----------------------------------------------------------------------------
+
+namespace crow {
+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 url) : url_(std::move(url)) {
+    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);
+    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;
+  }
+
+  char* get(const std::string& name) const {
+    char* ret =
+        qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
+    return ret;
+  }
+
+  std::vector<char*> get_list(const std::string& name) const {
+    std::vector<char*> ret;
+    std::string plus = name + "[]";
+    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;
+  }
+
+ private:
+  std::string url_;
+  std::vector<char*> key_value_pairs_;
+};
+
+}  // end namespace