2014-10-14 08:48:35 +00:00
# pragma once
# include <stdio.h>
2014-10-23 16:18:29 +00:00
# include <string.h>
2014-10-14 08:48:35 +00:00
# include <string>
2014-10-23 16:18:29 +00:00
# include <vector>
2017-09-17 05:02:46 +00:00
# include <unordered_map>
2014-10-23 16:18:29 +00:00
# include <iostream>
2017-03-16 16:25:27 +00:00
# include <boost/optional.hpp>
2014-10-14 08:48:35 +00:00
2022-05-20 19:36:32 +00:00
# include "crow/http_request.h"
2016-09-21 14:11:06 +00:00
namespace crow
{
2022-05-20 19:36:32 +00:00
2014-10-14 08:48:35 +00:00
// ----------------------------------------------------------------------------
// qs_parse (modified)
// https://github.com/bartgrantham/qs_parse
// ----------------------------------------------------------------------------
/* Similar to strncmp, but handles URL-encoding for either string */
2014-10-23 16:18:29 +00:00
int qs_strncmp ( const char * s , const char * qs , size_t n ) ;
2014-10-14 08:48:35 +00:00
/* 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 . */
2022-05-20 19:36:32 +00:00
int qs_parse ( char * qs , char * qs_kv [ ] , int qs_kv_size , bool parse_url ) ;
2014-10-14 08:48:35 +00:00
/* 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
2014-10-23 16:18:29 +00:00
# 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)
2014-10-14 08:48:35 +00:00
2014-10-23 16:18:29 +00:00
inline int qs_strncmp ( const char * s , const char * qs , size_t n )
2014-10-14 08:48:35 +00:00
{
int i = 0 ;
2014-10-23 16:18:29 +00:00
unsigned char u1 , u2 , unyb , lnyb ;
2014-10-14 08:48:35 +00:00
while ( n - - > 0 )
{
2021-02-21 00:14:30 +00:00
u1 = static_cast < unsigned char > ( * s + + ) ;
u2 = static_cast < unsigned char > ( * qs + + ) ;
2014-10-14 08:48:35 +00:00
2014-10-23 16:18:29 +00:00
if ( ! CROW_QS_ISQSCHR ( u1 ) ) { u1 = ' \0 ' ; }
if ( ! CROW_QS_ISQSCHR ( u2 ) ) { u2 = ' \0 ' ; }
2014-10-14 08:48:35 +00:00
if ( u1 = = ' + ' ) { u1 = ' ' ; }
if ( u1 = = ' % ' ) // easier/safer than scanf
{
2021-02-21 00:14:30 +00:00
unyb = static_cast < unsigned char > ( * s + + ) ;
lnyb = static_cast < unsigned char > ( * s + + ) ;
2014-10-23 16:18:29 +00:00
if ( CROW_QS_ISHEX ( unyb ) & & CROW_QS_ISHEX ( lnyb ) )
u1 = ( CROW_QS_HEX2DEC ( unyb ) * 16 ) + CROW_QS_HEX2DEC ( lnyb ) ;
2014-10-14 08:48:35 +00:00
else
u1 = ' \0 ' ;
}
if ( u2 = = ' + ' ) { u2 = ' ' ; }
if ( u2 = = ' % ' ) // easier/safer than scanf
{
2021-02-21 00:14:30 +00:00
unyb = static_cast < unsigned char > ( * qs + + ) ;
lnyb = static_cast < unsigned char > ( * qs + + ) ;
2014-10-23 16:18:29 +00:00
if ( CROW_QS_ISHEX ( unyb ) & & CROW_QS_ISHEX ( lnyb ) )
u2 = ( CROW_QS_HEX2DEC ( unyb ) * 16 ) + CROW_QS_HEX2DEC ( lnyb ) ;
2014-10-14 08:48:35 +00:00
else
u2 = ' \0 ' ;
}
if ( u1 ! = u2 )
return u1 - u2 ;
if ( u1 = = ' \0 ' )
return 0 ;
i + + ;
}
2014-10-23 16:18:29 +00:00
if ( CROW_QS_ISQSCHR ( * qs ) )
2014-10-14 08:48:35 +00:00
return - 1 ;
else
return 0 ;
}
2022-05-20 19:36:32 +00:00
inline int qs_parse ( char * qs , char * qs_kv [ ] , int qs_kv_size , bool parse_url = true )
2014-10-14 08:48:35 +00:00
{
int i , j ;
char * substr_ptr ;
for ( i = 0 ; i < qs_kv_size ; i + + ) qs_kv [ i ] = NULL ;
2015-05-01 00:56:28 +00:00
// find the beginning of the k/v substrings or the fragment
2022-05-20 19:36:32 +00:00
substr_ptr = parse_url ? qs + strcspn ( qs , " ?# " ) : qs ;
2015-05-01 00:56:28 +00:00
if ( substr_ptr [ 0 ] ! = ' \0 ' )
2014-10-14 08:48:35 +00:00
substr_ptr + + ;
else
2015-05-01 00:56:28 +00:00
return 0 ; // no query or fragment
2014-10-14 08:48:35 +00:00
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 ] , " =&# " ) ;
2015-05-01 00:56:28 +00:00
if ( substr_ptr [ 0 ] = = ' & ' | | substr_ptr [ 0 ] = = ' \0 ' ) // blank value: skip decoding
2014-10-14 08:48:35 +00:00
substr_ptr [ 0 ] = ' \0 ' ;
else
qs_decode ( + + substr_ptr ) ;
}
# ifdef _qsSORTING
// TODO: qsort qs_kv, using qs_strncmp() for the comparison
# endif
return i ;
}
2014-10-22 08:40:33 +00:00
inline int qs_decode ( char * qs )
2014-10-14 08:48:35 +00:00
{
int i = 0 , j = 0 ;
2014-10-23 16:18:29 +00:00
while ( CROW_QS_ISQSCHR ( qs [ j ] ) )
2014-10-14 08:48:35 +00:00
{
if ( qs [ j ] = = ' + ' ) { qs [ i ] = ' ' ; }
else if ( qs [ j ] = = ' % ' ) // easier/safer than scanf
{
2014-10-23 16:18:29 +00:00
if ( ! CROW_QS_ISHEX ( qs [ j + 1 ] ) | | ! CROW_QS_ISHEX ( qs [ j + 2 ] ) )
2014-10-14 08:48:35 +00:00
{
qs [ i ] = ' \0 ' ;
return i ;
}
2014-10-23 16:18:29 +00:00
qs [ i ] = ( CROW_QS_HEX2DEC ( qs [ j + 1 ] ) * 16 ) + CROW_QS_HEX2DEC ( qs [ j + 2 ] ) ;
2014-10-14 08:48:35 +00:00
j + = 2 ;
}
else
{
qs [ i ] = qs [ j ] ;
}
i + + ; j + + ;
}
qs [ i ] = ' \0 ' ;
return i ;
}
2014-10-22 08:40:33 +00:00
inline char * qs_k2v ( const char * key , char * const * qs_kv , int qs_kv_size , int nth = 0 )
2014-10-14 08:48:35 +00:00
{
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
2021-05-12 12:45:55 +00:00
return nullptr ;
2014-10-14 08:48:35 +00:00
}
2017-03-16 16:25:27 +00:00
inline boost : : optional < std : : pair < std : : string , std : : string > > 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 < qs_kv_size ; i + + )
{
if ( strncmp ( dict_name , qs_kv [ i ] , name_len ) = = 0 )
{
skip_to_eq = strcspn ( qs_kv [ i ] , " = " ) ;
if ( qs_kv [ i ] [ skip_to_eq ] = = ' = ' )
skip_to_eq + + ;
skip_to_brace_open = strcspn ( qs_kv [ i ] , " [ " ) ;
if ( qs_kv [ i ] [ skip_to_brace_open ] = = ' [ ' )
skip_to_brace_open + + ;
skip_to_brace_close = strcspn ( qs_kv [ i ] , " ] " ) ;
if ( skip_to_brace_open < = skip_to_brace_close & &
skip_to_brace_open > 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 boost : : make_optional ( std : : make_pair ( key , value ) ) ;
}
else
{
- - nth ;
}
}
}
# endif // _qsSORTING
return boost : : none ;
}
2014-10-14 08:48:35 +00:00
2014-10-22 08:40:33 +00:00
inline char * qs_scanvalue ( const char * key , const char * qs , char * val , size_t val_len )
2014-10-14 08:48:35 +00:00
{
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 , " &=# " ) ;
2016-10-21 15:22:24 +00:00
# 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 ) ;
2014-10-14 08:48:35 +00:00
}
else
{
if ( val_len > 0 )
val [ 0 ] = ' \0 ' ;
}
return val ;
}
2016-09-21 14:11:06 +00:00
}
2014-10-14 08:48:35 +00:00
// ----------------------------------------------------------------------------
namespace crow
{
2020-11-18 22:13:57 +00:00
/// A class to represent any data coming after the `?` in the request URL into key-value pairs.
2014-10-14 08:48:35 +00:00
class query_string
{
public :
2014-10-23 16:18:29 +00:00
static const int MAX_KEY_VALUE_PAIRS_COUNT = 256 ;
2014-10-14 08:48:35 +00:00
query_string ( )
{
}
2014-10-23 16:18:29 +00:00
2014-11-08 23:10:51 +00:00
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 ;
}
2014-10-14 08:48:35 +00:00
query_string ( std : : string url )
2014-10-23 16:18:29 +00:00
: url_ ( std : : move ( url ) )
2014-10-14 08:48:35 +00:00
{
2014-10-23 16:18:29 +00:00
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 ) ;
2014-10-14 08:48:35 +00:00
}
2014-10-23 16:18:29 +00:00
2022-05-20 19:36:32 +00:00
query_string ( request req )
: url_ ( req . body )
{
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 , false ) ;
key_value_pairs_ . resize ( count ) ;
}
2014-10-23 16:18:29 +00:00
void clear ( )
{
key_value_pairs_ . clear ( ) ;
url_ . clear ( ) ;
2014-10-14 08:48:35 +00:00
}
2014-10-22 08:40:33 +00:00
friend std : : ostream & operator < < ( std : : ostream & os , const query_string & qs )
2014-10-14 08:48:35 +00:00
{
os < < " [ " ;
2014-10-23 16:18:29 +00:00
for ( size_t i = 0 ; i < qs . key_value_pairs_ . size ( ) ; + + i ) {
if ( i )
2014-10-14 08:48:35 +00:00
os < < " , " ;
2014-10-23 16:18:29 +00:00
os < < qs . key_value_pairs_ [ i ] ;
2014-10-14 08:48:35 +00:00
}
os < < " ] " ;
return os ;
}
2021-05-12 12:45:55 +00:00
/// Get a value from a name, used for `?name=value`.
2021-12-03 03:39:23 +00:00
2021-05-12 12:45:55 +00:00
///
2021-05-13 10:30:48 +00:00
/// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list().
2014-10-23 16:18:29 +00:00
char * get ( const std : : string & name ) const
2014-10-14 08:48:35 +00:00
{
2014-10-23 16:18:29 +00:00
char * ret = qs_k2v ( name . c_str ( ) , key_value_pairs_ . data ( ) , key_value_pairs_ . size ( ) ) ;
return ret ;
2014-10-14 08:48:35 +00:00
}
2021-05-12 12:45:55 +00:00
/// 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.
2021-12-03 03:39:23 +00:00
2021-05-12 12:45:55 +00:00
///
/// 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 < char * > get_list ( const std : : string & name , bool use_brackets = true ) const
2014-10-14 08:48:35 +00:00
{
2014-10-22 08:40:33 +00:00
std : : vector < char * > ret ;
2021-05-12 12:45:55 +00:00
std : : string plus = name + ( use_brackets ? " [] " : " " ) ;
2014-10-23 16:18:29 +00:00
char * element = nullptr ;
2014-10-14 08:48:35 +00:00
int count = 0 ;
2014-10-23 16:18:29 +00:00
while ( 1 )
2014-10-14 08:48:35 +00:00
{
2014-10-23 16:18:29 +00:00
element = qs_k2v ( plus . c_str ( ) , key_value_pairs_ . data ( ) , key_value_pairs_ . size ( ) , count + + ) ;
if ( ! element )
break ;
ret . push_back ( element ) ;
}
return ret ;
2014-10-14 08:48:35 +00:00
}
2021-05-12 12:45:55 +00:00
/// Similar to \ref get_list() but it removes the
std : : vector < char * > pop_list ( const std : : string & name , bool use_brackets = true )
{
std : : vector < char * > 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.
2021-12-03 03:39:23 +00:00
2021-05-12 12:45:55 +00:00
///
/// 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.
2017-09-17 05:02:46 +00:00
std : : unordered_map < std : : string , std : : string > get_dict ( const std : : string & name ) const
2017-03-16 16:25:27 +00:00
{
2017-09-17 05:02:46 +00:00
std : : unordered_map < std : : string , std : : string > ret ;
2017-03-16 16:25:27 +00:00
int count = 0 ;
while ( 1 )
{
2017-09-17 05:02:46 +00:00
if ( auto element = qs_dict_name2kv ( name . c_str ( ) , key_value_pairs_ . data ( ) , key_value_pairs_ . size ( ) , count + + ) )
ret . insert ( * element ) ;
2017-03-16 16:25:27 +00:00
else
break ;
}
return ret ;
}
2014-10-14 08:48:35 +00:00
2021-05-12 12:45:55 +00:00
/// Works the same as \ref get_dict() but removes the values from the query string.
std : : unordered_map < std : : string , std : : string > pop_dict ( const std : : string & name )
{
std : : unordered_map < std : : string , std : : string > 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 < std : : string > keys ( ) const
{
std : : vector < std : : string > ret ;
for ( auto element : key_value_pairs_ )
{
std : : string str_element ( element ) ;
ret . emplace_back ( str_element . substr ( 0 , str_element . find ( ' = ' ) ) ) ;
}
return ret ;
}
2014-10-14 08:48:35 +00:00
private :
2014-10-23 16:18:29 +00:00
std : : string url_ ;
std : : vector < char * > key_value_pairs_ ;
2014-10-14 08:48:35 +00:00
} ;
2014-10-23 16:18:29 +00:00
} // end namespace