├── .travis.yml ├── LICENCE.md ├── README.md ├── appveyor.yml ├── bam.lua ├── test ├── greatest.h └── url_parse_tests.cpp └── url.h /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | os: 3 | - linux 4 | - osx 5 | 6 | sudo: false 7 | 8 | env: 9 | matrix: 10 | - UUID_CONFIG=debug 11 | - UUID_CONFIG=release 12 | 13 | compiler: 14 | - gcc 15 | - clang 16 | 17 | install: 18 | - git clone https://github.com/matricks/bam.git 19 | - cd bam 20 | - ./make_unix.sh 21 | - cd .. 22 | 23 | script: 24 | - bam/bam compiler=$CC config=$UUID_CONFIG -r sc test 25 | 26 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | Simple, STB-style, parser for URL:s as specified by RFC1738 ( http://www.ietf.org/rfc/rfc1738.txt ) 2 | 3 | compile with URL_PARSER_IMPLEMENTATION defined for implementation. 4 | compile with URL_PARSER_IMPLEMENTATION_STATIC defined for static implementation. 5 | 6 | version 1.1, July, 2021 7 | 8 | Copyright (C) 2021- Fredrik Kihlander 9 | 10 | This software is provided 'as-is', without any express or implied 11 | warranty. In no event will the authors be held liable for any damages 12 | arising from the use of this software. 13 | 14 | Permission is granted to anyone to use this software for any purpose, 15 | including commercial applications, and to alter it and redistribute it 16 | freely, subject to the following restrictions: 17 | 18 | 1. The origin of this software must not be misrepresented; you must not 19 | claim that you wrote the original software. If you use this software 20 | in a product, an acknowledgment in the product documentation would be 21 | appreciated but is not required. 22 | 2. Altered source versions must be plainly marked as such, and must not be 23 | misrepresented as being the original software. 24 | 3. This notice may not be removed or altered from any source distribution. 25 | 26 | Fredrik Kihlander 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/wc-duck/url_parse.svg?branch=master)](https://travis-ci.org/wc-duck/url_parse) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/hgrs9idixsu3hgrc?svg=true)](https://ci.appveyor.com/project/wc-duck/url-parse) 3 | 4 | # url.h 5 | Simple, STB-style, parser for URL:s as specified by RFC1738 ( http://www.ietf.org/rfc/rfc1738.txt ) 6 | 7 | compile with URL_PARSER_IMPLEMENTATION defined for implementation. 8 | compile with URL_PARSER_IMPLEMENTATION_STATIC defined for static implementation. 9 | 10 | # example 11 | 12 | ```c++ 13 | 14 | #define URL_PARSER_IMPLEMENTATION 15 | #include "url.h" 16 | 17 | #include 18 | 19 | int main(int argc, char** argv) 20 | { 21 | if(argc == 0) 22 | { 23 | printf("no url provided!\n"); 24 | return 0; 25 | } 26 | 27 | // See url.h to not use malloc() for allocation. 28 | parsed_url* parsed = parse_url( argv[1], 0x0, 0 ); 29 | if( parsed == 0x0 ) 30 | printf("failed to parse URL: %s\n", argv[1]); 31 | 32 | printf( "scheme: %s\n", parsed->scheme ); 33 | printf( "host: %s\n", parsed->host ); 34 | printf( "port: %d\n", parsed->port ); 35 | printf( "path: %s\n", parsed->path ); 36 | printf( "user: %s\n", parsed->user ); 37 | printf( "pass: %s\n", parsed->pass ); 38 | printf( "scheme: %s\n", parsed->scheme ); 39 | printf( "fragment: %s\n", parsed->fragment ); 40 | 41 | free( parsed ); 42 | return 0; 43 | } 44 | 45 | ``` 46 | 47 | Contributions are happily accepted! 48 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: 2 | - x86 3 | 4 | image: 5 | - Visual Studio 2019 6 | 7 | install: 8 | - git clone https://github.com/matricks/bam.git 9 | - cd bam 10 | - make_win64_msvc.bat 11 | - cd .. 12 | - bam\bam.exe config=debug -r sc test 13 | - bam\bam.exe config=release -r sc test 14 | 15 | build: OFF 16 | -------------------------------------------------------------------------------- /bam.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Simple, STB-style, parser for URL:s as specified by RFC1738 ( http://www.ietf.org/rfc/rfc1738.txt ) 3 | 4 | compile with URL_PARSER_IMPLEMENTATION defined for implementation. 5 | compile with URL_PARSER_IMPLEMENTATION_STATIC defined for static implementation. 6 | 7 | version 1.0, June, 2014 8 | 9 | Copyright (C) 2014- Fredrik Kihlander 10 | 11 | This software is provided 'as-is', without any express or implied 12 | warranty. In no event will the authors be held liable for any damages 13 | arising from the use of this software. 14 | 15 | Permission is granted to anyone to use this software for any purpose, 16 | including commercial applications, and to alter it and redistribute it 17 | freely, subject to the following restrictions: 18 | 19 | 1. The origin of this software must not be misrepresented; you must not 20 | claim that you wrote the original software. If you use this software 21 | in a product, an acknowledgment in the product documentation would be 22 | appreciated but is not required. 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original software. 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | Fredrik Kihlander 28 | --]] 29 | 30 | BUILD_PATH = "local" 31 | 32 | config = "debug" 33 | 34 | local settings = NewSettings() -- {} 35 | 36 | settings.cc.includes:Add("include") 37 | if family == 'windows' then 38 | platform = "winx64" 39 | else 40 | platform = "linux_x86_64" 41 | settings.cc.flags:Add( "-Wconversion", "-Wextra", "-Wall", "-Werror", "-Wstrict-aliasing=2" ) 42 | end 43 | 44 | local output_path = PathJoin( BUILD_PATH, PathJoin( platform, config ) ) 45 | local output_func = function(settings, path) return PathJoin(output_path, PathFilename(PathBase(path)) .. settings.config_ext) end 46 | settings.cc.Output = output_func 47 | settings.lib.Output = output_func 48 | settings.link.Output = output_func 49 | 50 | local tests = Link( settings, 'url_tests', Compile( settings, 'test/url_parse_tests.cpp' ) ) 51 | 52 | test_args = " -v" 53 | if ScriptArgs["test"] then test_args = test_args .. " -t " .. ScriptArgs["test"] end 54 | if ScriptArgs["suite"] then test_args = test_args .. " -s " .. ScriptArgs["suite"] end 55 | 56 | if family == "windows" then 57 | AddJob( "test", "unittest", string.gsub( tests, "/", "\\" ) .. test_args, tests, tests ) 58 | else 59 | AddJob( "test", "unittest", tests .. test_args, tests, tests ) 60 | AddJob( "valgrind", "valgrind", "valgrind -v --leak-check=full --track-origins=yes " .. tests .. test_args, tests, tests ) 61 | end 62 | 63 | PseudoTarget( "all", tests, listdir ) 64 | DefaultTarget( "all" ) 65 | -------------------------------------------------------------------------------- /test/greatest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2017 Scott Vokes 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifndef GREATEST_H 18 | #define GREATEST_H 19 | 20 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) 21 | extern "C" { 22 | #endif 23 | 24 | /* 1.3.1 */ 25 | #define GREATEST_VERSION_MAJOR 1 26 | #define GREATEST_VERSION_MINOR 3 27 | #define GREATEST_VERSION_PATCH 1 28 | 29 | /* A unit testing system for C, contained in 1 file. 30 | * It doesn't use dynamic allocation or depend on anything 31 | * beyond ANSI C89. 32 | * 33 | * An up-to-date version can be found at: 34 | * https://github.com/silentbicycle/greatest/ 35 | */ 36 | 37 | 38 | /********************************************************************* 39 | * Minimal test runner template 40 | *********************************************************************/ 41 | #if 0 42 | 43 | #include "greatest.h" 44 | 45 | TEST foo_should_foo(void) { 46 | PASS(); 47 | } 48 | 49 | static void setup_cb(void *data) { 50 | printf("setup callback for each test case\n"); 51 | } 52 | 53 | static void teardown_cb(void *data) { 54 | printf("teardown callback for each test case\n"); 55 | } 56 | 57 | SUITE(suite) { 58 | /* Optional setup/teardown callbacks which will be run before/after 59 | * every test case. If using a test suite, they will be cleared when 60 | * the suite finishes. */ 61 | SET_SETUP(setup_cb, voidp_to_callback_data); 62 | SET_TEARDOWN(teardown_cb, voidp_to_callback_data); 63 | 64 | RUN_TEST(foo_should_foo); 65 | } 66 | 67 | /* Add definitions that need to be in the test runner's main file. */ 68 | GREATEST_MAIN_DEFS(); 69 | 70 | /* Set up, run suite(s) of tests, report pass/fail/skip stats. */ 71 | int run_tests(void) { 72 | GREATEST_INIT(); /* init. greatest internals */ 73 | /* List of suites to run (if any). */ 74 | RUN_SUITE(suite); 75 | 76 | /* Tests can also be run directly, without using test suites. */ 77 | RUN_TEST(foo_should_foo); 78 | 79 | GREATEST_PRINT_REPORT(); /* display results */ 80 | return greatest_all_passed(); 81 | } 82 | 83 | /* main(), for a standalone command-line test runner. 84 | * This replaces run_tests above, and adds command line option 85 | * handling and exiting with a pass/fail status. */ 86 | int main(int argc, char **argv) { 87 | GREATEST_MAIN_BEGIN(); /* init & parse command-line args */ 88 | RUN_SUITE(suite); 89 | GREATEST_MAIN_END(); /* display results */ 90 | } 91 | 92 | #endif 93 | /*********************************************************************/ 94 | 95 | 96 | #include 97 | #include 98 | #include 99 | #include 100 | 101 | /*********** 102 | * Options * 103 | ***********/ 104 | 105 | /* Default column width for non-verbose output. */ 106 | #ifndef GREATEST_DEFAULT_WIDTH 107 | #define GREATEST_DEFAULT_WIDTH 72 108 | #endif 109 | 110 | /* FILE *, for test logging. */ 111 | #ifndef GREATEST_STDOUT 112 | #define GREATEST_STDOUT stdout 113 | #endif 114 | 115 | /* Remove GREATEST_ prefix from most commonly used symbols? */ 116 | #ifndef GREATEST_USE_ABBREVS 117 | #define GREATEST_USE_ABBREVS 1 118 | #endif 119 | 120 | /* Set to 0 to disable all use of setjmp/longjmp. */ 121 | #ifndef GREATEST_USE_LONGJMP 122 | #define GREATEST_USE_LONGJMP 1 123 | #endif 124 | 125 | /* Make it possible to replace fprintf with another 126 | * function with the same interface. */ 127 | #ifndef GREATEST_FPRINTF 128 | #define GREATEST_FPRINTF fprintf 129 | #endif 130 | 131 | #if GREATEST_USE_LONGJMP 132 | #include 133 | #endif 134 | 135 | /* Set to 0 to disable all use of time.h / clock(). */ 136 | #ifndef GREATEST_USE_TIME 137 | #define GREATEST_USE_TIME 1 138 | #endif 139 | 140 | #if GREATEST_USE_TIME 141 | #include 142 | #endif 143 | 144 | /* Floating point type, for ASSERT_IN_RANGE. */ 145 | #ifndef GREATEST_FLOAT 146 | #define GREATEST_FLOAT double 147 | #define GREATEST_FLOAT_FMT "%g" 148 | #endif 149 | 150 | 151 | /********* 152 | * Types * 153 | *********/ 154 | 155 | /* Info for the current running suite. */ 156 | typedef struct greatest_suite_info { 157 | unsigned int tests_run; 158 | unsigned int passed; 159 | unsigned int failed; 160 | unsigned int skipped; 161 | 162 | #if GREATEST_USE_TIME 163 | /* timers, pre/post running suite and individual tests */ 164 | clock_t pre_suite; 165 | clock_t post_suite; 166 | clock_t pre_test; 167 | clock_t post_test; 168 | #endif 169 | } greatest_suite_info; 170 | 171 | /* Type for a suite function. */ 172 | typedef void greatest_suite_cb(void); 173 | 174 | /* Types for setup/teardown callbacks. If non-NULL, these will be run 175 | * and passed the pointer to their additional data. */ 176 | typedef void greatest_setup_cb(void *udata); 177 | typedef void greatest_teardown_cb(void *udata); 178 | 179 | /* Type for an equality comparison between two pointers of the same type. 180 | * Should return non-0 if equal, otherwise 0. 181 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ 182 | typedef int greatest_equal_cb(const void *exp, const void *got, void *udata); 183 | 184 | /* Type for a callback that prints a value pointed to by T. 185 | * Return value has the same meaning as printf's. 186 | * UDATA is a closure value, passed through from ASSERT_EQUAL_T[m]. */ 187 | typedef int greatest_printf_cb(const void *t, void *udata); 188 | 189 | /* Callbacks for an arbitrary type; needed for type-specific 190 | * comparisons via GREATEST_ASSERT_EQUAL_T[m].*/ 191 | typedef struct greatest_type_info { 192 | greatest_equal_cb *equal; 193 | greatest_printf_cb *print; 194 | } greatest_type_info; 195 | 196 | typedef struct greatest_memory_cmp_env { 197 | const unsigned char *exp; 198 | const unsigned char *got; 199 | size_t size; 200 | } greatest_memory_cmp_env; 201 | 202 | /* Callbacks for string and raw memory types. */ 203 | extern greatest_type_info greatest_type_info_string; 204 | extern greatest_type_info greatest_type_info_memory; 205 | 206 | typedef enum { 207 | GREATEST_FLAG_FIRST_FAIL = 0x01, 208 | GREATEST_FLAG_LIST_ONLY = 0x02 209 | } greatest_flag_t; 210 | 211 | /* Internal state for a PRNG, used to shuffle test order. */ 212 | struct greatest_prng { 213 | unsigned char random_order; /* use random ordering? */ 214 | unsigned char initialized; /* is random ordering initialized? */ 215 | unsigned char pad_0[2]; 216 | unsigned long state; /* PRNG state */ 217 | unsigned long count; /* how many tests, this pass */ 218 | unsigned long count_ceil; /* total number of tests */ 219 | unsigned long count_run; /* total tests run */ 220 | unsigned long mod; /* power-of-2 ceiling of count_ceil */ 221 | unsigned long a; /* LCG multiplier */ 222 | unsigned long c; /* LCG increment */ 223 | }; 224 | 225 | /* Struct containing all test runner state. */ 226 | typedef struct greatest_run_info { 227 | unsigned char flags; 228 | unsigned char verbosity; 229 | unsigned char pad_0[2]; 230 | 231 | unsigned int tests_run; /* total test count */ 232 | 233 | /* currently running test suite */ 234 | greatest_suite_info suite; 235 | 236 | /* overall pass/fail/skip counts */ 237 | unsigned int passed; 238 | unsigned int failed; 239 | unsigned int skipped; 240 | unsigned int assertions; 241 | 242 | /* info to print about the most recent failure */ 243 | unsigned int fail_line; 244 | unsigned int pad_1; 245 | const char *fail_file; 246 | const char *msg; 247 | 248 | /* current setup/teardown hooks and userdata */ 249 | greatest_setup_cb *setup; 250 | void *setup_udata; 251 | greatest_teardown_cb *teardown; 252 | void *teardown_udata; 253 | 254 | /* formatting info for ".....s...F"-style output */ 255 | unsigned int col; 256 | unsigned int width; 257 | 258 | /* only run a specific suite or test */ 259 | const char *suite_filter; 260 | const char *test_filter; 261 | const char *test_exclude; 262 | 263 | struct greatest_prng prng[2]; /* 0: suites, 1: tests */ 264 | 265 | #if GREATEST_USE_TIME 266 | /* overall timers */ 267 | clock_t begin; 268 | clock_t end; 269 | #endif 270 | 271 | #if GREATEST_USE_LONGJMP 272 | int pad_jmp_buf; 273 | jmp_buf jump_dest; 274 | #endif 275 | } greatest_run_info; 276 | 277 | struct greatest_report_t { 278 | /* overall pass/fail/skip counts */ 279 | unsigned int passed; 280 | unsigned int failed; 281 | unsigned int skipped; 282 | unsigned int assertions; 283 | }; 284 | 285 | /* Global var for the current testing context. 286 | * Initialized by GREATEST_MAIN_DEFS(). */ 287 | extern greatest_run_info greatest_info; 288 | 289 | /* Type for ASSERT_ENUM_EQ's ENUM_STR argument. */ 290 | typedef const char *greatest_enum_str_fun(int value); 291 | 292 | /********************** 293 | * Exported functions * 294 | **********************/ 295 | 296 | /* These are used internally by greatest. */ 297 | void greatest_do_pass(const char *name); 298 | void greatest_do_fail(const char *name); 299 | void greatest_do_skip(const char *name); 300 | int greatest_suite_pre(const char *suite_name); 301 | void greatest_suite_post(void); 302 | int greatest_test_pre(const char *name); 303 | void greatest_test_post(const char *name, int res); 304 | void greatest_usage(const char *name); 305 | int greatest_do_assert_equal_t(const void *exp, const void *got, 306 | greatest_type_info *type_info, void *udata); 307 | void greatest_prng_init_first_pass(int id); 308 | int greatest_prng_init_second_pass(int id, unsigned long seed); 309 | void greatest_prng_step(int id); 310 | 311 | /* These are part of the public greatest API. */ 312 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); 313 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); 314 | int greatest_all_passed(void); 315 | void greatest_set_suite_filter(const char *filter); 316 | void greatest_set_test_filter(const char *filter); 317 | void greatest_set_test_exclude(const char *filter); 318 | void greatest_stop_at_first_fail(void); 319 | void greatest_get_report(struct greatest_report_t *report); 320 | unsigned int greatest_get_verbosity(void); 321 | void greatest_set_verbosity(unsigned int verbosity); 322 | void greatest_set_flag(greatest_flag_t flag); 323 | 324 | 325 | /******************** 326 | * Language Support * 327 | ********************/ 328 | 329 | /* If __VA_ARGS__ (C99) is supported, allow parametric testing 330 | * without needing to manually manage the argument struct. */ 331 | #if __STDC_VERSION__ >= 19901L || _MSC_VER >= 1800 332 | #define GREATEST_VA_ARGS 333 | #endif 334 | 335 | 336 | /********** 337 | * Macros * 338 | **********/ 339 | 340 | /* Define a suite. */ 341 | #define GREATEST_SUITE(NAME) void NAME(void); void NAME(void) 342 | 343 | /* Declare a suite, provided by another compilation unit. */ 344 | #define GREATEST_SUITE_EXTERN(NAME) void NAME(void) 345 | 346 | /* Start defining a test function. 347 | * The arguments are not included, to allow parametric testing. */ 348 | #define GREATEST_TEST static enum greatest_test_res 349 | 350 | /* PASS/FAIL/SKIP result from a test. Used internally. */ 351 | typedef enum greatest_test_res { 352 | GREATEST_TEST_RES_PASS = 0, 353 | GREATEST_TEST_RES_FAIL = -1, 354 | GREATEST_TEST_RES_SKIP = 1 355 | } greatest_test_res; 356 | 357 | /* Run a suite. */ 358 | #define GREATEST_RUN_SUITE(S_NAME) greatest_run_suite(S_NAME, #S_NAME) 359 | 360 | /* Run a test in the current suite. */ 361 | #define GREATEST_RUN_TEST(TEST) \ 362 | do { \ 363 | if (greatest_test_pre(#TEST) == 1) { \ 364 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 365 | if (res == GREATEST_TEST_RES_PASS) { \ 366 | res = TEST(); \ 367 | } \ 368 | greatest_test_post(#TEST, res); \ 369 | } \ 370 | } while (0) 371 | 372 | /* Ignore a test, don't warn about it being unused. */ 373 | #define GREATEST_IGNORE_TEST(TEST) (void)TEST 374 | 375 | /* Run a test in the current suite with one void * argument, 376 | * which can be a pointer to a struct with multiple arguments. */ 377 | #define GREATEST_RUN_TEST1(TEST, ENV) \ 378 | do { \ 379 | if (greatest_test_pre(#TEST) == 1) { \ 380 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 381 | if (res == GREATEST_TEST_RES_PASS) { \ 382 | res = TEST(ENV); \ 383 | } \ 384 | greatest_test_post(#TEST, res); \ 385 | } \ 386 | } while (0) 387 | 388 | #ifdef GREATEST_VA_ARGS 389 | #define GREATEST_RUN_TESTp(TEST, ...) \ 390 | do { \ 391 | if (greatest_test_pre(#TEST) == 1) { \ 392 | enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ 393 | if (res == GREATEST_TEST_RES_PASS) { \ 394 | res = TEST(__VA_ARGS__); \ 395 | } \ 396 | greatest_test_post(#TEST, res); \ 397 | } \ 398 | } while (0) 399 | #endif 400 | 401 | 402 | /* Check if the test runner is in verbose mode. */ 403 | #define GREATEST_IS_VERBOSE() ((greatest_info.verbosity) > 0) 404 | #define GREATEST_LIST_ONLY() \ 405 | (greatest_info.flags & GREATEST_FLAG_LIST_ONLY) 406 | #define GREATEST_FIRST_FAIL() \ 407 | (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) 408 | #define GREATEST_FAILURE_ABORT() \ 409 | (GREATEST_FIRST_FAIL() && \ 410 | (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) 411 | 412 | /* Message-less forms of tests defined below. */ 413 | #define GREATEST_PASS() GREATEST_PASSm(NULL) 414 | #define GREATEST_FAIL() GREATEST_FAILm(NULL) 415 | #define GREATEST_SKIP() GREATEST_SKIPm(NULL) 416 | #define GREATEST_ASSERT(COND) \ 417 | GREATEST_ASSERTm(#COND, COND) 418 | #define GREATEST_ASSERT_OR_LONGJMP(COND) \ 419 | GREATEST_ASSERT_OR_LONGJMPm(#COND, COND) 420 | #define GREATEST_ASSERT_FALSE(COND) \ 421 | GREATEST_ASSERT_FALSEm(#COND, COND) 422 | #define GREATEST_ASSERT_EQ(EXP, GOT) \ 423 | GREATEST_ASSERT_EQm(#EXP " != " #GOT, EXP, GOT) 424 | #define GREATEST_ASSERT_EQ_FMT(EXP, GOT, FMT) \ 425 | GREATEST_ASSERT_EQ_FMTm(#EXP " != " #GOT, EXP, GOT, FMT) 426 | #define GREATEST_ASSERT_IN_RANGE(EXP, GOT, TOL) \ 427 | GREATEST_ASSERT_IN_RANGEm(#EXP " != " #GOT " +/- " #TOL, EXP, GOT, TOL) 428 | #define GREATEST_ASSERT_EQUAL_T(EXP, GOT, TYPE_INFO, UDATA) \ 429 | GREATEST_ASSERT_EQUAL_Tm(#EXP " != " #GOT, EXP, GOT, TYPE_INFO, UDATA) 430 | #define GREATEST_ASSERT_STR_EQ(EXP, GOT) \ 431 | GREATEST_ASSERT_STR_EQm(#EXP " != " #GOT, EXP, GOT) 432 | #define GREATEST_ASSERT_STRN_EQ(EXP, GOT, SIZE) \ 433 | GREATEST_ASSERT_STRN_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) 434 | #define GREATEST_ASSERT_MEM_EQ(EXP, GOT, SIZE) \ 435 | GREATEST_ASSERT_MEM_EQm(#EXP " != " #GOT, EXP, GOT, SIZE) 436 | #define GREATEST_ASSERT_ENUM_EQ(EXP, GOT, ENUM_STR) \ 437 | GREATEST_ASSERT_ENUM_EQm(#EXP " != " #GOT, EXP, GOT, ENUM_STR) 438 | 439 | /* The following forms take an additional message argument first, 440 | * to be displayed by the test runner. */ 441 | 442 | /* Fail if a condition is not true, with message. */ 443 | #define GREATEST_ASSERTm(MSG, COND) \ 444 | do { \ 445 | greatest_info.assertions++; \ 446 | if (!(COND)) { GREATEST_FAILm(MSG); } \ 447 | } while (0) 448 | 449 | /* Fail if a condition is not true, longjmping out of test. */ 450 | #define GREATEST_ASSERT_OR_LONGJMPm(MSG, COND) \ 451 | do { \ 452 | greatest_info.assertions++; \ 453 | if (!(COND)) { GREATEST_FAIL_WITH_LONGJMPm(MSG); } \ 454 | } while (0) 455 | 456 | /* Fail if a condition is not false, with message. */ 457 | #define GREATEST_ASSERT_FALSEm(MSG, COND) \ 458 | do { \ 459 | greatest_info.assertions++; \ 460 | if ((COND)) { GREATEST_FAILm(MSG); } \ 461 | } while (0) 462 | 463 | /* Fail if EXP != GOT (equality comparison by ==). */ 464 | #define GREATEST_ASSERT_EQm(MSG, EXP, GOT) \ 465 | do { \ 466 | greatest_info.assertions++; \ 467 | if ((EXP) != (GOT)) { GREATEST_FAILm(MSG); } \ 468 | } while (0) 469 | 470 | /* Fail if EXP != GOT (equality comparison by ==). 471 | * Warning: FMT, EXP, and GOT will be evaluated more 472 | * than once on failure. */ 473 | #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ 474 | do { \ 475 | greatest_info.assertions++; \ 476 | if ((EXP) != (GOT)) { \ 477 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ 478 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ 479 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ 480 | GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ 481 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 482 | GREATEST_FAILm(MSG); \ 483 | } \ 484 | } while (0) 485 | 486 | /* Fail if EXP is not equal to GOT, printing enum IDs. */ 487 | #define GREATEST_ASSERT_ENUM_EQm(MSG, EXP, GOT, ENUM_STR) \ 488 | do { \ 489 | int greatest_EXP = (int)(EXP); \ 490 | int greatest_GOT = (int)(GOT); \ 491 | greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ 492 | if (greatest_EXP != greatest_GOT) { \ 493 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ 494 | greatest_ENUM_STR(greatest_EXP)); \ 495 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ 496 | greatest_ENUM_STR(greatest_GOT)); \ 497 | GREATEST_FAILm(MSG); \ 498 | } \ 499 | } while (0) \ 500 | 501 | /* Fail if GOT not in range of EXP +|- TOL. */ 502 | #define GREATEST_ASSERT_IN_RANGEm(MSG, EXP, GOT, TOL) \ 503 | do { \ 504 | GREATEST_FLOAT greatest_EXP = (EXP); \ 505 | GREATEST_FLOAT greatest_GOT = (GOT); \ 506 | GREATEST_FLOAT greatest_TOL = (TOL); \ 507 | greatest_info.assertions++; \ 508 | if ((greatest_EXP > greatest_GOT && \ 509 | greatest_EXP - greatest_GOT > greatest_TOL) || \ 510 | (greatest_EXP < greatest_GOT && \ 511 | greatest_GOT - greatest_EXP > greatest_TOL)) { \ 512 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 513 | "\nExpected: " GREATEST_FLOAT_FMT \ 514 | " +/- " GREATEST_FLOAT_FMT \ 515 | "\n Got: " GREATEST_FLOAT_FMT \ 516 | "\n", \ 517 | greatest_EXP, greatest_TOL, greatest_GOT); \ 518 | GREATEST_FAILm(MSG); \ 519 | } \ 520 | } while (0) 521 | 522 | /* Fail if EXP is not equal to GOT, according to strcmp. */ 523 | #define GREATEST_ASSERT_STR_EQm(MSG, EXP, GOT) \ 524 | do { \ 525 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ 526 | &greatest_type_info_string, NULL); \ 527 | } while (0) \ 528 | 529 | /* Fail if EXP is not equal to GOT, according to strcmp. */ 530 | #define GREATEST_ASSERT_STRN_EQm(MSG, EXP, GOT, SIZE) \ 531 | do { \ 532 | size_t size = SIZE; \ 533 | GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, \ 534 | &greatest_type_info_string, &size); \ 535 | } while (0) \ 536 | 537 | /* Fail if EXP is not equal to GOT, according to memcmp. */ 538 | #define GREATEST_ASSERT_MEM_EQm(MSG, EXP, GOT, SIZE) \ 539 | do { \ 540 | greatest_memory_cmp_env env; \ 541 | env.exp = (const unsigned char *)EXP; \ 542 | env.got = (const unsigned char *)GOT; \ 543 | env.size = SIZE; \ 544 | GREATEST_ASSERT_EQUAL_Tm(MSG, env.exp, env.got, \ 545 | &greatest_type_info_memory, &env); \ 546 | } while (0) \ 547 | 548 | /* Fail if EXP is not equal to GOT, according to a comparison 549 | * callback in TYPE_INFO. If they are not equal, optionally use a 550 | * print callback in TYPE_INFO to print them. */ 551 | #define GREATEST_ASSERT_EQUAL_Tm(MSG, EXP, GOT, TYPE_INFO, UDATA) \ 552 | do { \ 553 | greatest_type_info *type_info = (TYPE_INFO); \ 554 | greatest_info.assertions++; \ 555 | if (!greatest_do_assert_equal_t(EXP, GOT, \ 556 | type_info, UDATA)) { \ 557 | if (type_info == NULL || type_info->equal == NULL) { \ 558 | GREATEST_FAILm("type_info->equal callback missing!"); \ 559 | } else { \ 560 | GREATEST_FAILm(MSG); \ 561 | } \ 562 | } \ 563 | } while (0) \ 564 | 565 | /* Pass. */ 566 | #define GREATEST_PASSm(MSG) \ 567 | do { \ 568 | greatest_info.msg = MSG; \ 569 | return GREATEST_TEST_RES_PASS; \ 570 | } while (0) 571 | 572 | /* Fail. */ 573 | #define GREATEST_FAILm(MSG) \ 574 | do { \ 575 | greatest_info.fail_file = __FILE__; \ 576 | greatest_info.fail_line = __LINE__; \ 577 | greatest_info.msg = MSG; \ 578 | return GREATEST_TEST_RES_FAIL; \ 579 | } while (0) 580 | 581 | /* Optional GREATEST_FAILm variant that longjmps. */ 582 | #if GREATEST_USE_LONGJMP 583 | #define GREATEST_FAIL_WITH_LONGJMP() GREATEST_FAIL_WITH_LONGJMPm(NULL) 584 | #define GREATEST_FAIL_WITH_LONGJMPm(MSG) \ 585 | do { \ 586 | greatest_info.fail_file = __FILE__; \ 587 | greatest_info.fail_line = __LINE__; \ 588 | greatest_info.msg = MSG; \ 589 | longjmp(greatest_info.jump_dest, GREATEST_TEST_RES_FAIL); \ 590 | } while (0) 591 | #endif 592 | 593 | /* Skip the current test. */ 594 | #define GREATEST_SKIPm(MSG) \ 595 | do { \ 596 | greatest_info.msg = MSG; \ 597 | return GREATEST_TEST_RES_SKIP; \ 598 | } while (0) 599 | 600 | /* Check the result of a subfunction using ASSERT, etc. */ 601 | #define GREATEST_CHECK_CALL(RES) \ 602 | do { \ 603 | enum greatest_test_res greatest_RES = RES; \ 604 | if (greatest_RES != GREATEST_TEST_RES_PASS) { \ 605 | return greatest_RES; \ 606 | } \ 607 | } while (0) \ 608 | 609 | #if GREATEST_USE_TIME 610 | #define GREATEST_SET_TIME(NAME) \ 611 | NAME = clock(); \ 612 | if (NAME == (clock_t) -1) { \ 613 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 614 | "clock error: %s\n", #NAME); \ 615 | exit(EXIT_FAILURE); \ 616 | } 617 | 618 | #define GREATEST_CLOCK_DIFF(C1, C2) \ 619 | GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ 620 | (long unsigned int) (C2) - (long unsigned int)(C1), \ 621 | (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) 622 | #else 623 | #define GREATEST_SET_TIME(UNUSED) 624 | #define GREATEST_CLOCK_DIFF(UNUSED1, UNUSED2) 625 | #endif 626 | 627 | #if GREATEST_USE_LONGJMP 628 | #define GREATEST_SAVE_CONTEXT() \ 629 | /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ 630 | * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ 631 | ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) 632 | #else 633 | #define GREATEST_SAVE_CONTEXT() \ 634 | /*a no-op, since setjmp/longjmp aren't being used */ \ 635 | GREATEST_TEST_RES_PASS 636 | #endif 637 | 638 | /* Run every suite / test function run within BODY in pseudo-random 639 | * order, seeded by SEED. (The top 3 bits of the seed are ignored.) 640 | * 641 | * This should be called like: 642 | * GREATEST_SHUFFLE_TESTS(seed, { 643 | * GREATEST_RUN_TEST(some_test); 644 | * GREATEST_RUN_TEST(some_other_test); 645 | * GREATEST_RUN_TEST(yet_another_test); 646 | * }); 647 | * 648 | * Note that the body of the second argument will be evaluated 649 | * multiple times. */ 650 | #define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) 651 | #define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) 652 | #define GREATEST_SHUFFLE(ID, SD, BODY) \ 653 | do { \ 654 | struct greatest_prng *prng = &greatest_info.prng[ID]; \ 655 | greatest_prng_init_first_pass(ID); \ 656 | do { \ 657 | prng->count = 0; \ 658 | if (prng->initialized) { greatest_prng_step(ID); } \ 659 | BODY; \ 660 | if (!prng->initialized) { \ 661 | if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ 662 | } else if (prng->count_run == prng->count_ceil) { \ 663 | break; \ 664 | } \ 665 | } while (!GREATEST_FAILURE_ABORT()); \ 666 | prng->count_run = prng->random_order = prng->initialized = 0; \ 667 | } while(0) 668 | 669 | /* Include several function definitions in the main test file. */ 670 | #define GREATEST_MAIN_DEFS() \ 671 | \ 672 | /* Is FILTER a subset of NAME? */ \ 673 | static int greatest_name_match(const char *name, const char *filter, \ 674 | int res_if_none) { \ 675 | size_t offset = 0; \ 676 | size_t filter_len = filter ? strlen(filter) : 0; \ 677 | if (filter_len == 0) { return res_if_none; } /* no filter */ \ 678 | while (name[offset] != '\0') { \ 679 | if (name[offset] == filter[0]) { \ 680 | if (0 == strncmp(&name[offset], filter, filter_len)) { \ 681 | return 1; \ 682 | } \ 683 | } \ 684 | offset++; \ 685 | } \ 686 | \ 687 | return 0; \ 688 | } \ 689 | \ 690 | /* Before running a test, check the name filtering and \ 691 | * test shuffling state, if applicable, and then call setup hooks. */ \ 692 | int greatest_test_pre(const char *name) { \ 693 | struct greatest_run_info *g = &greatest_info; \ 694 | int match = greatest_name_match(name, g->test_filter, 1) && \ 695 | !greatest_name_match(name, g->test_exclude, 0); \ 696 | if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ 697 | if (match) { fprintf(GREATEST_STDOUT, " %s\n", name); } \ 698 | return 0; \ 699 | } \ 700 | if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ 701 | struct greatest_prng *p = &g->prng[1]; \ 702 | if (p->random_order) { \ 703 | p->count++; \ 704 | if (!p->initialized || ((p->count - 1) != p->state)) { \ 705 | return 0; /* don't run this test yet */ \ 706 | } \ 707 | } \ 708 | GREATEST_SET_TIME(g->suite.pre_test); \ 709 | if (g->setup) { g->setup(g->setup_udata); } \ 710 | p->count_run++; \ 711 | return 1; /* test should be run */ \ 712 | } else { \ 713 | return 0; /* skipped */ \ 714 | } \ 715 | } \ 716 | \ 717 | void greatest_test_post(const char *name, int res) { \ 718 | GREATEST_SET_TIME(greatest_info.suite.post_test); \ 719 | if (greatest_info.teardown) { \ 720 | void *udata = greatest_info.teardown_udata; \ 721 | greatest_info.teardown(udata); \ 722 | } \ 723 | \ 724 | if (res <= GREATEST_TEST_RES_FAIL) { \ 725 | greatest_do_fail(name); \ 726 | } else if (res >= GREATEST_TEST_RES_SKIP) { \ 727 | greatest_do_skip(name); \ 728 | } else if (res == GREATEST_TEST_RES_PASS) { \ 729 | greatest_do_pass(name); \ 730 | } \ 731 | greatest_info.suite.tests_run++; \ 732 | greatest_info.col++; \ 733 | if (GREATEST_IS_VERBOSE()) { \ 734 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ 735 | greatest_info.suite.post_test); \ 736 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 737 | } else if (greatest_info.col % greatest_info.width == 0) { \ 738 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 739 | greatest_info.col = 0; \ 740 | } \ 741 | fflush(GREATEST_STDOUT); \ 742 | } \ 743 | \ 744 | static void report_suite(void) { \ 745 | if (greatest_info.suite.tests_run > 0) { \ 746 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 747 | "\n%u test%s - %u passed, %u failed, %u skipped", \ 748 | greatest_info.suite.tests_run, \ 749 | greatest_info.suite.tests_run == 1 ? "" : "s", \ 750 | greatest_info.suite.passed, \ 751 | greatest_info.suite.failed, \ 752 | greatest_info.suite.skipped); \ 753 | GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ 754 | greatest_info.suite.post_suite); \ 755 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 756 | } \ 757 | } \ 758 | \ 759 | static void update_counts_and_reset_suite(void) { \ 760 | greatest_info.setup = NULL; \ 761 | greatest_info.setup_udata = NULL; \ 762 | greatest_info.teardown = NULL; \ 763 | greatest_info.teardown_udata = NULL; \ 764 | greatest_info.passed += greatest_info.suite.passed; \ 765 | greatest_info.failed += greatest_info.suite.failed; \ 766 | greatest_info.skipped += greatest_info.suite.skipped; \ 767 | greatest_info.tests_run += greatest_info.suite.tests_run; \ 768 | memset(&greatest_info.suite, 0, sizeof(greatest_info.suite)); \ 769 | greatest_info.col = 0; \ 770 | } \ 771 | \ 772 | int greatest_suite_pre(const char *suite_name) { \ 773 | struct greatest_prng *p = &greatest_info.prng[0]; \ 774 | if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ 775 | || (GREATEST_FIRST_FAIL() && greatest_info.failed > 0)) { \ 776 | return 0; \ 777 | } \ 778 | if (p->random_order) { \ 779 | p->count++; \ 780 | if (!p->initialized || ((p->count - 1) != p->state)) { \ 781 | return 0; /* don't run this suite yet */ \ 782 | } \ 783 | } \ 784 | p->count_run++; \ 785 | update_counts_and_reset_suite(); \ 786 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ 787 | GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ 788 | return 1; \ 789 | } \ 790 | \ 791 | void greatest_suite_post(void) { \ 792 | GREATEST_SET_TIME(greatest_info.suite.post_suite); \ 793 | report_suite(); \ 794 | } \ 795 | \ 796 | static void greatest_run_suite(greatest_suite_cb *suite_cb, \ 797 | const char *suite_name) { \ 798 | if (greatest_suite_pre(suite_name)) { \ 799 | suite_cb(); \ 800 | greatest_suite_post(); \ 801 | } \ 802 | } \ 803 | \ 804 | void greatest_do_pass(const char *name) { \ 805 | if (GREATEST_IS_VERBOSE()) { \ 806 | GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ 807 | name, greatest_info.msg ? greatest_info.msg : ""); \ 808 | } else { \ 809 | GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ 810 | } \ 811 | greatest_info.suite.passed++; \ 812 | } \ 813 | \ 814 | void greatest_do_fail(const char *name) { \ 815 | if (GREATEST_IS_VERBOSE()) { \ 816 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 817 | "FAIL %s: %s (%s:%u)", \ 818 | name, greatest_info.msg ? greatest_info.msg : "", \ 819 | greatest_info.fail_file, greatest_info.fail_line); \ 820 | } else { \ 821 | GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ 822 | greatest_info.col++; \ 823 | /* add linebreak if in line of '.'s */ \ 824 | if (greatest_info.col != 0) { \ 825 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 826 | greatest_info.col = 0; \ 827 | } \ 828 | GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ 829 | name, \ 830 | greatest_info.msg ? greatest_info.msg : "", \ 831 | greatest_info.fail_file, greatest_info.fail_line); \ 832 | } \ 833 | greatest_info.suite.failed++; \ 834 | } \ 835 | \ 836 | void greatest_do_skip(const char *name) { \ 837 | if (GREATEST_IS_VERBOSE()) { \ 838 | GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ 839 | name, \ 840 | greatest_info.msg ? \ 841 | greatest_info.msg : "" ); \ 842 | } else { \ 843 | GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ 844 | } \ 845 | greatest_info.suite.skipped++; \ 846 | } \ 847 | \ 848 | int greatest_do_assert_equal_t(const void *exp, const void *got, \ 849 | greatest_type_info *type_info, void *udata) { \ 850 | int eq = 0; \ 851 | if (type_info == NULL || type_info->equal == NULL) { \ 852 | return 0; \ 853 | } \ 854 | eq = type_info->equal(exp, got, udata); \ 855 | if (!eq) { \ 856 | if (type_info->print != NULL) { \ 857 | GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ 858 | (void)type_info->print(exp, udata); \ 859 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ 860 | (void)type_info->print(got, udata); \ 861 | GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ 862 | } else { \ 863 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 864 | "GREATEST_ASSERT_EQUAL_T failure at %s:%u\n", \ 865 | greatest_info.fail_file, \ 866 | greatest_info.fail_line); \ 867 | } \ 868 | } \ 869 | return eq; \ 870 | } \ 871 | \ 872 | void greatest_usage(const char *name) { \ 873 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 874 | "Usage: %s [--help] [-hlfv] [-s SUITE] [-t TEST]\n" \ 875 | " -h, --help print this Help\n" \ 876 | " -l List suites and tests, then exit (dry run)\n" \ 877 | " -f Stop runner after first failure\n" \ 878 | " -v Verbose output\n" \ 879 | " -s SUITE only run suites containing string SUITE\n" \ 880 | " -t TEST only run tests containing string TEST\n" \ 881 | " -x EXCLUDE exclude tests containing string EXCLUDE\n", \ 882 | name); \ 883 | } \ 884 | \ 885 | static void greatest_parse_options(int argc, char **argv) { \ 886 | int i = 0; \ 887 | for (i = 1; i < argc; i++) { \ 888 | if (argv[i][0] == '-') { \ 889 | char f = argv[i][1]; \ 890 | if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ 891 | greatest_usage(argv[0]); exit(EXIT_FAILURE); \ 892 | } \ 893 | switch (f) { \ 894 | case 's': /* suite name filter */ \ 895 | greatest_set_suite_filter(argv[i + 1]); i++; break; \ 896 | case 't': /* test name filter */ \ 897 | greatest_set_test_filter(argv[i + 1]); i++; break; \ 898 | case 'x': /* test name exclusion */ \ 899 | greatest_set_test_exclude(argv[i + 1]); i++; break; \ 900 | case 'f': /* first fail flag */ \ 901 | greatest_stop_at_first_fail(); break; \ 902 | case 'l': /* list only (dry run) */ \ 903 | greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; break; \ 904 | case 'v': /* first fail flag */ \ 905 | greatest_info.verbosity++; break; \ 906 | case 'h': /* help */ \ 907 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ 908 | case '-': \ 909 | if (0 == strncmp("--help", argv[i], 6)) { \ 910 | greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ 911 | } else if (0 == strncmp("--", argv[i], 2)) { \ 912 | return; /* ignore following arguments */ \ 913 | } /* fall through */ \ 914 | default: \ 915 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 916 | "Unknown argument '%s'\n", argv[i]); \ 917 | greatest_usage(argv[0]); \ 918 | exit(EXIT_FAILURE); \ 919 | } \ 920 | } \ 921 | } \ 922 | } \ 923 | \ 924 | int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ 925 | \ 926 | void greatest_set_test_filter(const char *filter) { \ 927 | greatest_info.test_filter = filter; \ 928 | } \ 929 | \ 930 | void greatest_set_test_exclude(const char *filter) { \ 931 | greatest_info.test_exclude = filter; \ 932 | } \ 933 | \ 934 | void greatest_set_suite_filter(const char *filter) { \ 935 | greatest_info.suite_filter = filter; \ 936 | } \ 937 | \ 938 | void greatest_stop_at_first_fail(void) { \ 939 | greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ 940 | } \ 941 | \ 942 | void greatest_get_report(struct greatest_report_t *report) { \ 943 | if (report) { \ 944 | report->passed = greatest_info.passed; \ 945 | report->failed = greatest_info.failed; \ 946 | report->skipped = greatest_info.skipped; \ 947 | report->assertions = greatest_info.assertions; \ 948 | } \ 949 | } \ 950 | \ 951 | unsigned int greatest_get_verbosity(void) { \ 952 | return greatest_info.verbosity; \ 953 | } \ 954 | \ 955 | void greatest_set_verbosity(unsigned int verbosity) { \ 956 | greatest_info.verbosity = (unsigned char)verbosity; \ 957 | } \ 958 | \ 959 | void greatest_set_flag(greatest_flag_t flag) { \ 960 | greatest_info.flags |= (unsigned char)flag; \ 961 | } \ 962 | \ 963 | void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata) { \ 964 | greatest_info.setup = cb; \ 965 | greatest_info.setup_udata = udata; \ 966 | } \ 967 | \ 968 | void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, \ 969 | void *udata) { \ 970 | greatest_info.teardown = cb; \ 971 | greatest_info.teardown_udata = udata; \ 972 | } \ 973 | \ 974 | static int greatest_string_equal_cb(const void *exp, const void *got, \ 975 | void *udata) { \ 976 | size_t *size = (size_t *)udata; \ 977 | return (size != NULL \ 978 | ? (0 == strncmp((const char *)exp, (const char *)got, *size)) \ 979 | : (0 == strcmp((const char *)exp, (const char *)got))); \ 980 | } \ 981 | \ 982 | static int greatest_string_printf_cb(const void *t, void *udata) { \ 983 | (void)udata; /* note: does not check \0 termination. */ \ 984 | return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ 985 | } \ 986 | \ 987 | greatest_type_info greatest_type_info_string = { \ 988 | greatest_string_equal_cb, \ 989 | greatest_string_printf_cb, \ 990 | }; \ 991 | \ 992 | static int greatest_memory_equal_cb(const void *exp, const void *got, \ 993 | void *udata) { \ 994 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ 995 | return (0 == memcmp(exp, got, env->size)); \ 996 | } \ 997 | \ 998 | /* Hexdump raw memory, with differences highlighted */ \ 999 | static int greatest_memory_printf_cb(const void *t, void *udata) { \ 1000 | greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ 1001 | const unsigned char *buf = (const unsigned char *)t; \ 1002 | unsigned char diff_mark = ' '; \ 1003 | FILE *out = GREATEST_STDOUT; \ 1004 | size_t i, line_i, line_len = 0; \ 1005 | int len = 0; /* format hexdump with differences highlighted */ \ 1006 | for (i = 0; i < env->size; i+= line_len) { \ 1007 | diff_mark = ' '; \ 1008 | line_len = env->size - i; \ 1009 | if (line_len > 16) { line_len = 16; } \ 1010 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1011 | if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ 1012 | } \ 1013 | len += GREATEST_FPRINTF(out, "\n%04x %c ", \ 1014 | (unsigned int)i, diff_mark); \ 1015 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1016 | int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ 1017 | len += GREATEST_FPRINTF(out, "%02x%c", \ 1018 | buf[line_i], m ? ' ' : '<'); \ 1019 | } \ 1020 | for (line_i = 0; line_i < 16 - line_len; line_i++) { \ 1021 | len += GREATEST_FPRINTF(out, " "); \ 1022 | } \ 1023 | GREATEST_FPRINTF(out, " "); \ 1024 | for (line_i = i; line_i < i + line_len; line_i++) { \ 1025 | unsigned char c = buf[line_i]; \ 1026 | len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ 1027 | } \ 1028 | } \ 1029 | len += GREATEST_FPRINTF(out, "\n"); \ 1030 | return len; \ 1031 | } \ 1032 | \ 1033 | void greatest_prng_init_first_pass(int id) { \ 1034 | greatest_info.prng[id].random_order = 1; \ 1035 | greatest_info.prng[id].count_run = 0; \ 1036 | } \ 1037 | \ 1038 | int greatest_prng_init_second_pass(int id, unsigned long seed) { \ 1039 | static unsigned long primes[] = { 11, 101, 1009, 10007, \ 1040 | 100003, 1000003, 10000019, 100000007, 1000000007, \ 1041 | 1538461, 1865471, 17471, 2147483647 /* 2**32 - 1 */, }; \ 1042 | struct greatest_prng *prng = &greatest_info.prng[id]; \ 1043 | if (prng->count == 0) { return 0; } \ 1044 | prng->mod = 1; \ 1045 | prng->count_ceil = prng->count; \ 1046 | while (prng->mod < prng->count) { prng->mod <<= 1; } \ 1047 | prng->state = seed & 0x1fffffff; /* only use lower 29 bits... */ \ 1048 | prng->a = (4LU * prng->state) + 1; /* to avoid overflow */ \ 1049 | prng->c = primes[(seed * 16451) % sizeof(primes)/sizeof(primes[0])];\ 1050 | prng->initialized = 1; \ 1051 | return 1; \ 1052 | } \ 1053 | \ 1054 | /* Step the pseudorandom number generator until its state reaches \ 1055 | * another test ID between 0 and the test count. \ 1056 | * This use a linear congruential pseudorandom number generator, \ 1057 | * with the power-of-two ceiling of the test count as the modulus, the \ 1058 | * masked seed as the multiplier, and a prime as the increment. For \ 1059 | * each generated value < the test count, run the corresponding test. \ 1060 | * This will visit all IDs 0 <= X < mod once before repeating, \ 1061 | * with a starting position chosen based on the initial seed. \ 1062 | * For details, see: Knuth, The Art of Computer Programming \ 1063 | * Volume. 2, section 3.2.1. */ \ 1064 | void greatest_prng_step(int id) { \ 1065 | struct greatest_prng *p = &greatest_info.prng[id]; \ 1066 | do { \ 1067 | p->state = ((p->a * p->state) + p->c) & (p->mod - 1); \ 1068 | } while (p->state >= p->count_ceil); \ 1069 | } \ 1070 | \ 1071 | greatest_type_info greatest_type_info_memory = { \ 1072 | greatest_memory_equal_cb, \ 1073 | greatest_memory_printf_cb, \ 1074 | }; \ 1075 | \ 1076 | greatest_run_info greatest_info 1077 | 1078 | /* Init internals. */ 1079 | #define GREATEST_INIT() \ 1080 | do { \ 1081 | /* Suppress unused function warning if features aren't used */ \ 1082 | (void)greatest_run_suite; \ 1083 | (void)greatest_parse_options; \ 1084 | (void)greatest_prng_step; \ 1085 | (void)greatest_prng_init_first_pass; \ 1086 | (void)greatest_prng_init_second_pass; \ 1087 | \ 1088 | memset(&greatest_info, 0, sizeof(greatest_info)); \ 1089 | greatest_info.width = GREATEST_DEFAULT_WIDTH; \ 1090 | GREATEST_SET_TIME(greatest_info.begin); \ 1091 | } while (0) \ 1092 | 1093 | /* Handle command-line arguments, etc. */ 1094 | #define GREATEST_MAIN_BEGIN() \ 1095 | do { \ 1096 | GREATEST_INIT(); \ 1097 | greatest_parse_options(argc, argv); \ 1098 | } while (0) 1099 | 1100 | /* Report passes, failures, skipped tests, the number of 1101 | * assertions, and the overall run time. */ 1102 | #define GREATEST_PRINT_REPORT() \ 1103 | do { \ 1104 | if (!GREATEST_LIST_ONLY()) { \ 1105 | update_counts_and_reset_suite(); \ 1106 | GREATEST_SET_TIME(greatest_info.end); \ 1107 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 1108 | "\nTotal: %u test%s", \ 1109 | greatest_info.tests_run, \ 1110 | greatest_info.tests_run == 1 ? "" : "s"); \ 1111 | GREATEST_CLOCK_DIFF(greatest_info.begin, \ 1112 | greatest_info.end); \ 1113 | GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ 1114 | greatest_info.assertions, \ 1115 | greatest_info.assertions == 1 ? "" : "s"); \ 1116 | GREATEST_FPRINTF(GREATEST_STDOUT, \ 1117 | "Pass: %u, fail: %u, skip: %u.\n", \ 1118 | greatest_info.passed, \ 1119 | greatest_info.failed, greatest_info.skipped); \ 1120 | } \ 1121 | } while (0) 1122 | 1123 | /* Report results, exit with exit status based on results. */ 1124 | #define GREATEST_MAIN_END() \ 1125 | do { \ 1126 | GREATEST_PRINT_REPORT(); \ 1127 | return (greatest_all_passed() ? EXIT_SUCCESS : EXIT_FAILURE); \ 1128 | } while (0) 1129 | 1130 | /* Make abbreviations without the GREATEST_ prefix for the 1131 | * most commonly used symbols. */ 1132 | #if GREATEST_USE_ABBREVS 1133 | #define TEST GREATEST_TEST 1134 | #define SUITE GREATEST_SUITE 1135 | #define SUITE_EXTERN GREATEST_SUITE_EXTERN 1136 | #define RUN_TEST GREATEST_RUN_TEST 1137 | #define RUN_TEST1 GREATEST_RUN_TEST1 1138 | #define RUN_SUITE GREATEST_RUN_SUITE 1139 | #define IGNORE_TEST GREATEST_IGNORE_TEST 1140 | #define ASSERT GREATEST_ASSERT 1141 | #define ASSERTm GREATEST_ASSERTm 1142 | #define ASSERT_FALSE GREATEST_ASSERT_FALSE 1143 | #define ASSERT_EQ GREATEST_ASSERT_EQ 1144 | #define ASSERT_EQ_FMT GREATEST_ASSERT_EQ_FMT 1145 | #define ASSERT_IN_RANGE GREATEST_ASSERT_IN_RANGE 1146 | #define ASSERT_EQUAL_T GREATEST_ASSERT_EQUAL_T 1147 | #define ASSERT_STR_EQ GREATEST_ASSERT_STR_EQ 1148 | #define ASSERT_STRN_EQ GREATEST_ASSERT_STRN_EQ 1149 | #define ASSERT_MEM_EQ GREATEST_ASSERT_MEM_EQ 1150 | #define ASSERT_ENUM_EQ GREATEST_ASSERT_ENUM_EQ 1151 | #define ASSERT_FALSEm GREATEST_ASSERT_FALSEm 1152 | #define ASSERT_EQm GREATEST_ASSERT_EQm 1153 | #define ASSERT_EQ_FMTm GREATEST_ASSERT_EQ_FMTm 1154 | #define ASSERT_IN_RANGEm GREATEST_ASSERT_IN_RANGEm 1155 | #define ASSERT_EQUAL_Tm GREATEST_ASSERT_EQUAL_Tm 1156 | #define ASSERT_STR_EQm GREATEST_ASSERT_STR_EQm 1157 | #define ASSERT_STRN_EQm GREATEST_ASSERT_STRN_EQm 1158 | #define ASSERT_MEM_EQm GREATEST_ASSERT_MEM_EQm 1159 | #define ASSERT_ENUM_EQm GREATEST_ASSERT_ENUM_EQm 1160 | #define PASS GREATEST_PASS 1161 | #define FAIL GREATEST_FAIL 1162 | #define SKIP GREATEST_SKIP 1163 | #define PASSm GREATEST_PASSm 1164 | #define FAILm GREATEST_FAILm 1165 | #define SKIPm GREATEST_SKIPm 1166 | #define SET_SETUP GREATEST_SET_SETUP_CB 1167 | #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB 1168 | #define CHECK_CALL GREATEST_CHECK_CALL 1169 | #define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS 1170 | #define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES 1171 | 1172 | #ifdef GREATEST_VA_ARGS 1173 | #define RUN_TESTp GREATEST_RUN_TESTp 1174 | #endif 1175 | 1176 | #if GREATEST_USE_LONGJMP 1177 | #define ASSERT_OR_LONGJMP GREATEST_ASSERT_OR_LONGJMP 1178 | #define ASSERT_OR_LONGJMPm GREATEST_ASSERT_OR_LONGJMPm 1179 | #define FAIL_WITH_LONGJMP GREATEST_FAIL_WITH_LONGJMP 1180 | #define FAIL_WITH_LONGJMPm GREATEST_FAIL_WITH_LONGJMPm 1181 | #endif 1182 | 1183 | #endif /* USE_ABBREVS */ 1184 | 1185 | #if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) 1186 | } 1187 | #endif 1188 | 1189 | #endif 1190 | -------------------------------------------------------------------------------- /test/url_parse_tests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Simple parser for URL:s as specified by RFC1738 ( http://www.ietf.org/rfc/rfc1738.txt ) 3 | 4 | version 1.1, July, 2021 5 | 6 | Copyright (C) 2021- Fredrik Kihlander 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | 24 | Fredrik Kihlander 25 | */ 26 | 27 | #define URL_PARSER_IMPLEMENTATION 28 | 29 | #include "greatest.h" 30 | #include "../url.h" 31 | 32 | TEST full_url_parse() 33 | { 34 | parsed_url* parsed = parse_url( "http://user:pass@testurl.com:8080/sub/resource.file?query#fragment", 0x0, 0 ); 35 | if( parsed == 0x0 ) 36 | FAILm( "failed to parse url" ); 37 | 38 | ASSERT_STR_EQ( "http", parsed->scheme ); 39 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 40 | ASSERT_STR_EQ( "/sub/resource.file", parsed->path ); 41 | ASSERT_STR_EQ( "user", parsed->user ); 42 | ASSERT_STR_EQ( "pass", parsed->pass ); 43 | ASSERT_STR_EQ( "query", parsed->query ); 44 | ASSERT_STR_EQ( "fragment", parsed->fragment ); 45 | 46 | ASSERT_EQ( 8080, parsed->port ); 47 | 48 | free( parsed ); 49 | 50 | return GREATEST_TEST_RES_PASS; 51 | } 52 | 53 | TEST full_url_parse_win_style_path() 54 | { 55 | parsed_url* parsed = parse_url( "http://user:pass@testurl.com:8080/e:/whoppa?query#fragment", 0x0, 0 ); 56 | if( parsed == 0x0 ) 57 | FAILm( "failed to parse url" ); 58 | 59 | ASSERT_STR_EQ( "http", parsed->scheme ); 60 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 61 | ASSERT_STR_EQ( "/e:/whoppa", parsed->path ); 62 | ASSERT_STR_EQ( "user", parsed->user ); 63 | ASSERT_STR_EQ( "pass", parsed->pass ); 64 | ASSERT_STR_EQ( "query", parsed->query ); 65 | ASSERT_STR_EQ( "fragment", parsed->fragment ); 66 | 67 | ASSERT_EQ( 8080, parsed->port ); 68 | 69 | free( parsed ); 70 | 71 | return GREATEST_TEST_RES_PASS; 72 | } 73 | 74 | TEST url_scheme_is_lower_cased() 75 | { 76 | { 77 | parsed_url* parsed = parse_url( "HTTP://test.com", 0x0, 0 ); 78 | if( parsed == 0x0 ) 79 | FAILm( "failed to parse url" ); 80 | 81 | ASSERT_STR_EQ( "http", parsed->scheme ); 82 | 83 | free( parsed ); 84 | } 85 | 86 | { 87 | parsed_url* parsed = parse_url( "hTTp://test.com", 0x0, 0 ); 88 | if( parsed == 0x0 ) 89 | FAILm( "failed to parse url" ); 90 | 91 | ASSERT_STR_EQ( "http", parsed->scheme ); 92 | 93 | free( parsed ); 94 | } 95 | 96 | return GREATEST_TEST_RES_PASS; 97 | } 98 | 99 | TEST url_no_scheme_with_port() 100 | { 101 | parsed_url* parsed = parse_url( "testurl.com:8080", 0x0, 0 ); 102 | if( parsed == 0x0 ) 103 | FAILm( "failed to parse url" ); 104 | 105 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 106 | ASSERT_STR_EQ( "/", parsed->path ); 107 | 108 | ASSERT_EQ( 8080, parsed->port ); 109 | ASSERT_EQ( 0x0, parsed->scheme ); 110 | ASSERT_EQ( 0x0, parsed->user ); 111 | ASSERT_EQ( 0x0, parsed->pass ); 112 | ASSERT_EQ( 0x0, parsed->query ); 113 | ASSERT_EQ( 0x0, parsed->fragment ); 114 | 115 | free( parsed ); 116 | 117 | return GREATEST_TEST_RES_PASS; 118 | } 119 | 120 | TEST url_casing() 121 | { 122 | parsed_url* parsed = parse_url( "WhoPPa://UsEr:Pa$$Word@teStUrL.cOm/whIhoWeeHa?qUEry#frAGment", 0x0, 0 ); 123 | if( parsed == 0x0 ) 124 | FAILm( "failed to parse url" ); 125 | 126 | // scheme and host is case-insensitive according to spec, lowercasing it to make usage simpler. 127 | ASSERT_STR_EQ( "whoppa", parsed->scheme ); 128 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 129 | // ... user and pass is ofc ourse not! 130 | ASSERT_STR_EQ( "UsEr", parsed->user ); 131 | ASSERT_STR_EQ( "Pa$$Word", parsed->pass ); 132 | // ... same with path, query and fragment ... 133 | ASSERT_STR_EQ( "/whIhoWeeHa", parsed->path ); 134 | ASSERT_STR_EQ( "qUEry", parsed->query ); 135 | ASSERT_STR_EQ( "frAGment", parsed->fragment ); 136 | 137 | ASSERT_EQ( 0, parsed->port ); 138 | 139 | free( parsed ); 140 | 141 | return GREATEST_TEST_RES_PASS; 142 | } 143 | 144 | TEST percent_encoding() 145 | { 146 | parsed_url* parsed = parse_url( "testurl.com/%21/%23/%24/%25/%26/%27/%28/%29/%2A/%2B/%2C/%2F/%3A/%3B/%3D/%3F/%40/%5B/%5D", 0x0, 0 ); 147 | if( parsed == 0x0 ) 148 | FAILm( "failed to parse url" ); 149 | 150 | ASSERT_STR_EQ( "/!/#/$/%/&/'/(/)/*/+/,///:/;/=/?/@/[/]", parsed->path ); 151 | 152 | ASSERT_EQ( 0, parsed->port ); 153 | 154 | free( parsed ); 155 | 156 | return GREATEST_TEST_RES_PASS; 157 | } 158 | 159 | TEST url_no_host() 160 | { 161 | parsed_url* parsed = parse_url( "file:///sub/resource.file", 0x0, 0 ); 162 | if( parsed == 0x0 ) 163 | FAILm( "failed to parse url" ); 164 | 165 | ASSERT_STR_EQ( "localhost", parsed->host ); 166 | ASSERT_STR_EQ( "file", parsed->scheme ); 167 | ASSERT_STR_EQ( "/sub/resource.file", parsed->path ); 168 | 169 | ASSERT_EQ( 0x0, parsed->user ); 170 | ASSERT_EQ( 0x0, parsed->pass ); 171 | ASSERT_EQ( 0, parsed->port ); 172 | ASSERT_EQ( 0x0, parsed->query ); 173 | ASSERT_EQ( 0x0, parsed->fragment ); 174 | 175 | free( parsed ); 176 | 177 | return GREATEST_TEST_RES_PASS; 178 | } 179 | 180 | TEST url_win_style_abs_path() 181 | { 182 | parsed_url* parsed = parse_url( "file:///e:/sub/resource.file", 0x0, 0 ); 183 | if( parsed == 0x0 ) 184 | FAILm( "failed to parse url" ); 185 | 186 | ASSERT_STR_EQ( "localhost", parsed->host ); 187 | ASSERT_STR_EQ( "file", parsed->scheme ); 188 | ASSERT_STR_EQ( "/e:/sub/resource.file", parsed->path ); 189 | 190 | ASSERT_EQ( 0x0, parsed->user ); 191 | ASSERT_EQ( 0x0, parsed->pass ); 192 | ASSERT_EQ( 0, parsed->port ); 193 | ASSERT_EQ( 0x0, parsed->query ); 194 | ASSERT_EQ( 0x0, parsed->fragment ); 195 | 196 | free( parsed ); 197 | 198 | return GREATEST_TEST_RES_PASS; 199 | } 200 | 201 | TEST url_win_style_abs_path_with_host() 202 | { 203 | parsed_url* parsed = parse_url( "file://some_host/e:/sub/resource.file", 0x0, 0 ); 204 | if( parsed == 0x0 ) 205 | FAILm( "failed to parse url" ); 206 | 207 | ASSERT_STR_EQ( "some_host", parsed->host ); 208 | ASSERT_STR_EQ( "file", parsed->scheme ); 209 | ASSERT_STR_EQ( "/e:/sub/resource.file", parsed->path ); 210 | 211 | ASSERT_EQ( 0x0, parsed->user ); 212 | ASSERT_EQ( 0x0, parsed->pass ); 213 | ASSERT_EQ( 0, parsed->port ); 214 | ASSERT_EQ( 0x0, parsed->query ); 215 | ASSERT_EQ( 0x0, parsed->fragment ); 216 | 217 | free( parsed ); 218 | 219 | return GREATEST_TEST_RES_PASS; 220 | } 221 | 222 | TEST url_win_style_abs_path_with_host_and_port() 223 | { 224 | parsed_url* parsed = parse_url( "file://some_host:1337/e:/sub/resource.file", 0x0, 0 ); 225 | if( parsed == 0x0 ) 226 | FAILm( "failed to parse url" ); 227 | 228 | ASSERT_STR_EQ( "some_host", parsed->host ); 229 | ASSERT_STR_EQ( "file", parsed->scheme ); 230 | ASSERT_STR_EQ( "/e:/sub/resource.file", parsed->path ); 231 | 232 | ASSERT_EQ( 0x0, parsed->user ); 233 | ASSERT_EQ( 0x0, parsed->pass ); 234 | ASSERT_EQ( 1337, parsed->port ); 235 | ASSERT_EQ( 0x0, parsed->query ); 236 | ASSERT_EQ( 0x0, parsed->fragment ); 237 | 238 | free( parsed ); 239 | 240 | return GREATEST_TEST_RES_PASS; 241 | } 242 | 243 | TEST default_port_parse() 244 | { 245 | parsed_url* parsed; 246 | 247 | parsed = parse_url( "http://testurl.com", 0x0, 0 ); 248 | if( parsed == 0x0 ) 249 | FAILm( "failed to parse url" ); 250 | 251 | ASSERT_STR_EQ( "http", parsed->scheme ); 252 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 253 | ASSERT_STR_EQ( "/", parsed->path ); 254 | ASSERT_EQ( 80, parsed->port ); 255 | ASSERT_EQ( 0x0, parsed->user ); 256 | ASSERT_EQ( 0x0, parsed->pass ); 257 | ASSERT_EQ( 0x0, parsed->query ); 258 | ASSERT_EQ( 0x0, parsed->fragment ); 259 | 260 | free( parsed ); 261 | 262 | parsed = parse_url( "ftp://testurl.com", 0x0, 0 ); 263 | if( parsed == 0x0 ) 264 | FAILm( "failed to parse url" ); 265 | 266 | ASSERT_STR_EQ( "ftp", parsed->scheme ); 267 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 268 | ASSERT_STR_EQ( "/", parsed->path ); 269 | ASSERT_EQ( 21, parsed->port ); 270 | ASSERT_EQ( 0x0, parsed->user ); 271 | ASSERT_EQ( 0x0, parsed->pass ); 272 | ASSERT_EQ( 0x0, parsed->query ); 273 | ASSERT_EQ( 0x0, parsed->fragment ); 274 | 275 | free( parsed ); 276 | 277 | return GREATEST_TEST_RES_PASS; 278 | } 279 | 280 | TEST default_scheme_parse() 281 | { 282 | parsed_url* parsed; 283 | 284 | parsed = parse_url( "testurl.com", 0x0, 0 ); 285 | if( parsed == 0x0 ) 286 | FAILm( "failed to parse url" ); 287 | 288 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 289 | ASSERT_STR_EQ( "/", parsed->path ); 290 | 291 | ASSERT_EQ( 0x0, parsed->scheme ); 292 | ASSERT_EQ( 0, parsed->port ); 293 | ASSERT_EQ( 0x0, parsed->user ); 294 | ASSERT_EQ( 0x0, parsed->pass ); 295 | ASSERT_EQ( 0x0, parsed->query ); 296 | ASSERT_EQ( 0x0, parsed->fragment ); 297 | 298 | free( parsed ); 299 | 300 | return GREATEST_TEST_RES_PASS; 301 | } 302 | 303 | TEST default_scheme_with_user_parse() 304 | { 305 | parsed_url* parsed; 306 | 307 | parsed = parse_url( "hej:hopp@testurl.com", 0x0, 0 ); 308 | if( parsed == 0x0 ) 309 | FAILm( "failed to parse url" ); 310 | 311 | ASSERT_EQ( 0x0, parsed->scheme ); 312 | ASSERT_EQ( 0, parsed->port ); 313 | ASSERT_EQ( 0x0, parsed->query ); 314 | ASSERT_EQ( 0x0, parsed->fragment ); 315 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 316 | ASSERT_STR_EQ( "/", parsed->path ); 317 | ASSERT_STR_EQ( "hej", parsed->user ); 318 | ASSERT_STR_EQ( "hopp", parsed->pass ); 319 | 320 | free( parsed ); 321 | 322 | return GREATEST_TEST_RES_PASS; 323 | } 324 | 325 | TEST simple_query() 326 | { 327 | parsed_url* parsed; 328 | 329 | parsed = parse_url( "http://testurl.com/whoppa?apa=kossa", 0x0, 0 ); 330 | if( parsed == 0x0 ) 331 | FAILm( "failed to parse url" ); 332 | 333 | ASSERT_STR_EQ( "http", parsed->scheme ); 334 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 335 | ASSERT_STR_EQ( "/whoppa", parsed->path ); 336 | ASSERT_STR_EQ( "apa=kossa", parsed->query ); 337 | 338 | ASSERT_EQ( 0x0, parsed->user ); 339 | ASSERT_EQ( 0x0, parsed->pass ); 340 | ASSERT_EQ( 0x0, parsed->fragment ); 341 | 342 | free( parsed ); 343 | return GREATEST_TEST_RES_PASS; 344 | } 345 | 346 | TEST simple_fragment() 347 | { 348 | parsed_url* parsed; 349 | 350 | parsed = parse_url( "http://testurl.com/whoppa#le_fragment", 0x0, 0 ); 351 | if( parsed == 0x0 ) 352 | FAILm( "failed to parse url" ); 353 | 354 | ASSERT_STR_EQ( "http", parsed->scheme ); 355 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 356 | ASSERT_STR_EQ( "/whoppa", parsed->path ); 357 | ASSERT_STR_EQ( "le_fragment", parsed->fragment ); 358 | 359 | ASSERT_EQ( 0x0, parsed->user ); 360 | ASSERT_EQ( 0x0, parsed->pass ); 361 | ASSERT_EQ( 0x0, parsed->query ); 362 | 363 | 364 | free( parsed ); 365 | return GREATEST_TEST_RES_PASS; 366 | } 367 | 368 | TEST query_and_fragment() 369 | { 370 | parsed_url* parsed; 371 | 372 | parsed = parse_url( "http://testurl.com/whoppa?apa=kossa#le_query", 0x0, 0 ); 373 | if( parsed == 0x0 ) 374 | FAILm( "failed to parse url" ); 375 | 376 | ASSERT_STR_EQ( "http", parsed->scheme ); 377 | ASSERT_STR_EQ( "testurl.com", parsed->host ); 378 | ASSERT_STR_EQ( "/whoppa", parsed->path ); 379 | ASSERT_STR_EQ( "apa=kossa", parsed->query ); 380 | ASSERT_STR_EQ( "le_query", parsed->fragment ); 381 | 382 | ASSERT_EQ( 0x0, parsed->user ); 383 | ASSERT_EQ( 0x0, parsed->pass ); 384 | 385 | free( parsed ); 386 | return GREATEST_TEST_RES_PASS; 387 | } 388 | 389 | TEST mem_alloc_fail() 390 | { 391 | // ... test that all buffer-sizes less than needed will not fail the parsing ... 392 | 393 | char buffer[2048]; 394 | for(size_t i = 0; i < sizeof(buffer); ++i) 395 | { 396 | parsed_url* parsed = parse_url("http://user:pass@test.com/whoppa?apa=kossa#fragment", buffer, i); 397 | if(parsed) 398 | { 399 | ASSERT_STR_EQ( "http", parsed->scheme ); 400 | ASSERT_STR_EQ( "test.com", parsed->host ); 401 | ASSERT_STR_EQ( "/whoppa", parsed->path ); 402 | ASSERT_STR_EQ( "user", parsed->user ); 403 | ASSERT_STR_EQ( "pass", parsed->pass ); 404 | ASSERT_STR_EQ( "apa=kossa", parsed->query ); 405 | ASSERT_STR_EQ( "fragment", parsed->fragment ); 406 | 407 | ASSERT_EQ( 80, parsed->port ); 408 | return GREATEST_TEST_RES_PASS; 409 | } 410 | } 411 | return GREATEST_TEST_RES_FAIL; 412 | } 413 | 414 | TEST ipv6() 415 | { 416 | char buffer[2048]; 417 | 418 | { 419 | parsed_url* parsed = parse_url( "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]/whoppa?apa=kossa#le_query", buffer, sizeof(buffer) ); 420 | if( parsed == 0x0 ) 421 | FAILm( "failed to parse url" ); 422 | 423 | ASSERT_STR_EQ( "http", parsed->scheme ); 424 | ASSERT_STR_EQ( "2001:0db8:85a3:0000:0000:8a2e:0370:7334", parsed->host ); 425 | ASSERT_STR_EQ( "/whoppa", parsed->path ); 426 | ASSERT_STR_EQ( "apa=kossa", parsed->query ); 427 | ASSERT_STR_EQ( "le_query", parsed->fragment ); 428 | 429 | ASSERT_EQ( 0x0, parsed->user ); 430 | ASSERT_EQ( 0x0, parsed->pass ); 431 | ASSERT_EQ( 80, parsed->port ); 432 | } 433 | 434 | { 435 | // with user and pass 436 | 437 | parsed_url* parsed = parse_url( "http://user:pass@[2001:0db8:85a3:0000:0000:8a2e:0370:7334]/whoppa?apa=kossa#le_query", buffer, sizeof(buffer) ); 438 | if( parsed == 0x0 ) 439 | FAILm( "failed to parse url" ); 440 | 441 | ASSERT_STR_EQ( "http", parsed->scheme ); 442 | ASSERT_STR_EQ( "2001:0db8:85a3:0000:0000:8a2e:0370:7334", parsed->host ); 443 | ASSERT_STR_EQ( "/whoppa", parsed->path ); 444 | ASSERT_STR_EQ( "user", parsed->user ); 445 | ASSERT_STR_EQ( "pass", parsed->pass ); 446 | ASSERT_STR_EQ( "apa=kossa", parsed->query ); 447 | ASSERT_STR_EQ( "le_query", parsed->fragment ); 448 | 449 | ASSERT_EQ( 80, parsed->port ); 450 | } 451 | 452 | { 453 | // with port 454 | 455 | parsed_url* parsed = parse_url( "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:1337/whoppa?apa=kossa#le_query", buffer, sizeof(buffer) ); 456 | if( parsed == 0x0 ) 457 | FAILm( "failed to parse url" ); 458 | 459 | ASSERT_STR_EQ( "http", parsed->scheme ); 460 | ASSERT_STR_EQ( "2001:0db8:85a3:0000:0000:8a2e:0370:7334", parsed->host ); 461 | ASSERT_STR_EQ( "/whoppa", parsed->path ); 462 | ASSERT_STR_EQ( "apa=kossa", parsed->query ); 463 | ASSERT_STR_EQ( "le_query", parsed->fragment ); 464 | 465 | ASSERT_EQ( 0x0, parsed->user ); 466 | ASSERT_EQ( 0x0, parsed->pass ); 467 | ASSERT_EQ( 1337, parsed->port ); 468 | } 469 | 470 | { 471 | // local host 472 | 473 | parsed_url* parsed = parse_url( "http://[::1]/whoppa?apa=kossa#le_query", buffer, sizeof(buffer) ); 474 | if( parsed == 0x0 ) 475 | FAILm( "failed to parse url" ); 476 | 477 | ASSERT_STR_EQ( "http", parsed->scheme ); 478 | ASSERT_STR_EQ( "::1", parsed->host ); 479 | ASSERT_STR_EQ( "/whoppa", parsed->path ); 480 | ASSERT_STR_EQ( "apa=kossa", parsed->query ); 481 | ASSERT_STR_EQ( "le_query", parsed->fragment ); 482 | 483 | ASSERT_EQ( 0x0, parsed->user ); 484 | ASSERT_EQ( 0x0, parsed->pass ); 485 | ASSERT_EQ( 80, parsed->port ); 486 | } 487 | 488 | return GREATEST_TEST_RES_PASS; 489 | } 490 | 491 | TEST ipv6_invalid() 492 | { 493 | // verify fail on invalid ipv6 address... 494 | char buffer[2048]; 495 | 496 | { 497 | // no ending ']' 498 | parsed_url* parsed = parse_url( "http://[::1", buffer, sizeof(buffer) ); 499 | ASSERT_EQ( 0x0, parsed ); 500 | } 501 | 502 | { 503 | // contain non hex char 504 | parsed_url* parsed = parse_url( "http://[2001:0db8:85z3::8a2e:0370:7334]", buffer, sizeof(buffer) ); 505 | ASSERT_EQ( 0x0, parsed ); 506 | } 507 | 508 | { 509 | // contain non a-f,A-F:. 510 | parsed_url* parsed = parse_url( "http://[2001:0db8:85!3::8a2e:0370:7334]", buffer, sizeof(buffer) ); 511 | ASSERT_EQ( 0x0, parsed ); 512 | } 513 | 514 | return GREATEST_TEST_RES_PASS; 515 | } 516 | 517 | GREATEST_SUITE( url_parse ) 518 | { 519 | RUN_TEST( full_url_parse ); 520 | RUN_TEST( full_url_parse_win_style_path ); 521 | RUN_TEST( url_no_host ); 522 | RUN_TEST( url_win_style_abs_path ); 523 | RUN_TEST( url_win_style_abs_path_with_host ); 524 | RUN_TEST( url_win_style_abs_path_with_host_and_port ); 525 | RUN_TEST( url_scheme_is_lower_cased ); 526 | RUN_TEST( url_no_scheme_with_port ); 527 | RUN_TEST( url_casing ); 528 | RUN_TEST( percent_encoding ); 529 | RUN_TEST( default_port_parse ); 530 | RUN_TEST( default_scheme_parse ); 531 | RUN_TEST( default_scheme_with_user_parse ); 532 | RUN_TEST( simple_query ); 533 | RUN_TEST( simple_fragment ); 534 | RUN_TEST( query_and_fragment ); 535 | RUN_TEST( mem_alloc_fail ); 536 | RUN_TEST( ipv6 ); 537 | RUN_TEST( ipv6_invalid ); 538 | } 539 | 540 | GREATEST_MAIN_DEFS(); 541 | 542 | int main( int argc, char **argv ) 543 | { 544 | GREATEST_MAIN_BEGIN(); 545 | RUN_SUITE( url_parse ); 546 | GREATEST_MAIN_END(); 547 | } 548 | 549 | -------------------------------------------------------------------------------- /url.h: -------------------------------------------------------------------------------- 1 | /* 2 | Simple, STB-style, parser for URL:s as specified by RFC1738 ( http://www.ietf.org/rfc/rfc1738.txt ) 3 | 4 | compile with URL_PARSER_IMPLEMENTATION defined for implementation. 5 | compile with URL_PARSER_IMPLEMENTATION_STATIC defined for static implementation. 6 | 7 | version 1.0, June, 2014 8 | 9 | Copyright (C) 2014- Fredrik Kihlander 10 | 11 | This software is provided 'as-is', without any express or implied 12 | warranty. In no event will the authors be held liable for any damages 13 | arising from the use of this software. 14 | 15 | Permission is granted to anyone to use this software for any purpose, 16 | including commercial applications, and to alter it and redistribute it 17 | freely, subject to the following restrictions: 18 | 19 | 1. The origin of this software must not be misrepresented; you must not 20 | claim that you wrote the original software. If you use this software 21 | in a product, an acknowledgment in the product documentation would be 22 | appreciated but is not required. 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original software. 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | Fredrik Kihlander 28 | */ 29 | 30 | #ifndef URL_H_INCLUDED 31 | #define URL_H_INCLUDED 32 | 33 | #include 34 | 35 | #if defined(URL_PARSER_IMPLEMENTATION_STATIC) 36 | # if !defined(URL_PARSER_IMPLEMENTATION) 37 | # define URL_PARSER_IMPLEMENTATION 38 | # define URL_PARSER_LINKAGE static 39 | # endif 40 | #else 41 | # define URL_PARSER_LINKAGE 42 | #endif 43 | 44 | /** 45 | * Struct describing a parsed url. 46 | * 47 | * @example ://:@:/?# 48 | */ 49 | struct parsed_url 50 | { 51 | /** 52 | * scheme part of url or 0x0 if not present. 53 | * @note the scheme will be lower-cased! 54 | */ 55 | const char* scheme; 56 | 57 | /** 58 | * user part of url or 0x0 if not present. 59 | */ 60 | const char* user; 61 | 62 | /** 63 | * password part of url or 0x0 if not present. 64 | */ 65 | const char* pass; 66 | 67 | /** 68 | * host part of url or "localhost" if not present. 69 | * if the host is an ipv6 address, i.e. enclosed by [] such as 70 | * http://[::1]/whoppa host will be the string in question. 71 | * It will also be verified that it is a valid ipv6 address, parsing 72 | * will have failed if anything that is not an ipv6 address was found 73 | * within a [] 74 | * @note the scheme will be lower-cased! 75 | */ 76 | const char* host; 77 | 78 | /** 79 | * port part of url. 80 | * if not present a default depending on scheme is used, if no default is 81 | * available for scheme, 0 will be used. 82 | * 83 | * supported defaults: 84 | * "http" - 80 85 | * "https" - 443 86 | * "ftp" - 21 87 | * "ssh" - 22 88 | * "telnet" - 23 89 | */ 90 | unsigned int port; 91 | 92 | /** 93 | * path part of url. 94 | * if the path part of the url is not present, it will default to "/" 95 | * @note percent-encoded values will get decoded during parse, i.e. %21 will be translated 96 | * to '!' etc. 97 | * see: https://en.wikipedia.org/wiki/Percent-encoding 98 | */ 99 | const char* path; 100 | 101 | /** 102 | * query part of url, default to 0x0 if not present in url. 103 | * as this is not standardized it is not parsed for the user. 104 | */ 105 | const char* query; 106 | 107 | /** 108 | * fragment part of url, default to 0x0 if not present in url. 109 | */ 110 | const char* fragment; 111 | }; 112 | 113 | /** 114 | * Calculate the amount of memory needed to parse the specified url. 115 | * @param url the url to parse. 116 | */ 117 | URL_PARSER_LINKAGE size_t parse_url_calc_mem_usage(const char* url); 118 | 119 | /** 120 | * Parse an url specified by RFC1738 into its parts. 121 | * 122 | * @param url url to parse. 123 | * @param mem memory-buffer to use to parse the url or NULL to use malloc. 124 | * @param mem_size size of mem in bytes. 125 | * 126 | * @return parsed url. If mem is NULL this value will need to be free:ed with free(). 127 | */ 128 | URL_PARSER_LINKAGE parsed_url* parse_url(const char* url, void* mem, size_t mem_size); 129 | 130 | 131 | 132 | 133 | #if defined(URL_PARSER_IMPLEMENTATION) 134 | #include 135 | #include 136 | #include 137 | 138 | struct parse_url_ctx 139 | { 140 | void* mem; 141 | size_t memsize; 142 | size_t memleft; 143 | }; 144 | 145 | const char* parse_url_strnchr( const char* str, size_t len, int ch ) 146 | { 147 | for( size_t i = 0; i < len; ++i ) 148 | if( str[i] == ch ) 149 | return &str[i]; 150 | return 0x0; 151 | } 152 | 153 | static void* parse_url_alloc_mem( parse_url_ctx* ctx, size_t request_size ) 154 | { 155 | if( request_size > ctx->memleft ) 156 | return 0; 157 | void* res = (char*)ctx->mem + ctx->memsize - ctx->memleft; 158 | ctx->memleft -= request_size; 159 | return res; 160 | } 161 | 162 | static unsigned int parse_url_default_port_for_scheme( const char* scheme ) 163 | { 164 | if( scheme == 0x0 ) 165 | return 0; 166 | 167 | if( strcmp( scheme, "http" ) == 0 ) return 80; 168 | if( strcmp( scheme, "https" ) == 0 ) return 443; 169 | if( strcmp( scheme, "ftp" ) == 0 ) return 21; 170 | if( strcmp( scheme, "ssh" ) == 0 ) return 22; 171 | if( strcmp( scheme, "telnet" ) == 0 ) return 23; 172 | return 0x0; 173 | } 174 | 175 | static char* parse_url_alloc_string( parse_url_ctx* ctx, const char* src, size_t len) 176 | { 177 | char* dst = (char*)parse_url_alloc_mem( ctx, len + 1 ); 178 | if( dst == 0x0 ) 179 | return 0x0; 180 | memcpy( dst, src, len ); 181 | dst[len] = '\0'; 182 | return dst; 183 | } 184 | 185 | static const char* parse_url_alloc_lower_string( parse_url_ctx* ctx, const char* src, size_t len) 186 | { 187 | char* dst = (char*)parse_url_alloc_mem( ctx, len + 1 ); 188 | if( dst == 0x0 ) 189 | return 0x0; 190 | // parse_url_strncpy_lower( new_str, src, len ); 191 | for( size_t i = 0; i < len; ++i ) 192 | dst[i] = (char)tolower( src[i] ); 193 | dst[len] = '\0'; 194 | return dst; 195 | } 196 | 197 | static const char* parse_url_parse_scheme( const char* url, parse_url_ctx* ctx, parsed_url* out ) 198 | { 199 | const char* schemesep = strchr( url, ':' ); 200 | if( schemesep == 0x0 ) 201 | return url; 202 | else 203 | { 204 | // ... is this the user part of a user/pass pair or the separator host:port? ... 205 | if( schemesep[1] != '/') 206 | return url; 207 | 208 | if( schemesep[2] != '/' ) 209 | return 0x0; 210 | 211 | out->scheme = parse_url_alloc_lower_string( ctx, url, (size_t)( schemesep - url ) ); 212 | if(out->scheme == 0x0) 213 | return 0x0; 214 | return &schemesep[3]; 215 | } 216 | } 217 | 218 | static const char* parse_url_parse_user_pass( const char* url, parse_url_ctx* ctx, parsed_url* out ) 219 | { 220 | const char* atpos = strchr( url, '@' ); 221 | if( atpos != 0x0 ) 222 | { 223 | // ... check for a : before the @ ... 224 | const char* passsep = parse_url_strnchr( url, (size_t)( atpos - url ), ':' ); 225 | if( passsep == 0 ) 226 | { 227 | out->pass = ""; 228 | out->user = parse_url_alloc_string( ctx, url, (size_t)( atpos - url ) ); 229 | } 230 | else 231 | { 232 | size_t userlen = (size_t)(passsep - url); 233 | size_t passlen = (size_t)(atpos - passsep - 1); 234 | out->user = (char*)parse_url_alloc_string( ctx, url, userlen ); 235 | out->pass = (char*)parse_url_alloc_string( ctx, passsep + 1, passlen ); 236 | } 237 | 238 | if(out->user == 0x0 || out->pass == 0x0) 239 | return 0x0; 240 | 241 | return atpos + 1; 242 | } 243 | 244 | return url; 245 | } 246 | 247 | static bool parse_url_is_hex_char( char c ) 248 | { 249 | return (c >= 'a' && c <= 'f') || 250 | (c >= 'A' && c <= 'F') || 251 | (c >= '0' && c <= '9'); 252 | } 253 | 254 | static char parse_url_hex_char_value( char c ) 255 | { 256 | if(c >= '0' && c <= '9') return c - '0'; 257 | if(c >= 'a' && c <= 'f') return c - 'a' + 10; 258 | if(c >= 'A' && c <= 'F') return c - 'A' + 10; 259 | return 0; 260 | } 261 | 262 | static char* parse_url_unescape_percent_encoding( char* str ) 263 | { 264 | char* read = str; 265 | char* write = str; 266 | 267 | while(*read) 268 | { 269 | if(*read == '%') 270 | { 271 | ++read; 272 | if(!parse_url_is_hex_char(*read)) 273 | return 0x0; 274 | char v1 = parse_url_hex_char_value(*read); 275 | 276 | ++read; 277 | if(!parse_url_is_hex_char(*read)) 278 | return 0x0; 279 | 280 | char v2 = parse_url_hex_char_value(*read); 281 | 282 | *write = (char)((v1 << 4) | v2); 283 | } 284 | else 285 | { 286 | *write = *read; 287 | } 288 | ++read; 289 | ++write; 290 | } 291 | *write = '\0'; 292 | return str; 293 | } 294 | 295 | static const char* parse_url_parse_host_port( const char* url, parse_url_ctx* ctx, parsed_url* out ) 296 | { 297 | out->port = parse_url_default_port_for_scheme( out->scheme ); 298 | 299 | size_t hostlen = 0; 300 | const char* ipv6_end = 0x0; 301 | 302 | if(url[0] == '[') 303 | { 304 | // ipv6 host is always enclosed in a [] to handle the : in an ipv6 address. 305 | ipv6_end = strchr( url, ']' ); 306 | if(ipv6_end == 0x0) 307 | return 0x0; 308 | } 309 | 310 | const char* portsep = strchr( ipv6_end ? ipv6_end + 1 : url, ':' ); 311 | const char* pathsep = strchr( ipv6_end ? ipv6_end + 1 : url, '/' ); 312 | 313 | if( portsep == 0x0 ) 314 | { 315 | hostlen = pathsep == 0x0 ? strlen( url ) : (size_t)( pathsep - url ); 316 | } 317 | else 318 | { 319 | if(pathsep && portsep && (pathsep < portsep)) 320 | { 321 | // ... path separator was before port-separator, i.e. the : was not a port-separator! ... 322 | hostlen = (size_t)( pathsep - url ); 323 | } 324 | else 325 | { 326 | out->port = (unsigned int)atoi( portsep + 1 ); 327 | hostlen = (size_t)( portsep - url ); 328 | pathsep = strchr( portsep, '/' ); 329 | } 330 | } 331 | 332 | if( hostlen > 0 ) 333 | { 334 | if(ipv6_end) 335 | { 336 | // ... we have an ipv6 host, we need to strip of the [] 337 | out->host = parse_url_alloc_lower_string( ctx, url + 1, hostlen - 2 ); 338 | 339 | // ... verify that the host is actually a valid ipv6 address... I guess this 340 | // might miss one or two checks. 341 | // this only checks that it contains numbers or hex-chars or : or . 342 | 343 | for(const char* c = out->host; *c; ++c) 344 | { 345 | bool valid = parse_url_is_hex_char(*c) || 346 | (*c == ':') || 347 | (*c == '.'); 348 | if(!valid) 349 | return 0x0; 350 | } 351 | } 352 | else 353 | out->host = parse_url_alloc_lower_string( ctx, url, hostlen ); 354 | if(out->host == 0x0) 355 | return 0x0; 356 | } 357 | 358 | // ... parse path ... TODO: extract to own function. 359 | if( pathsep != 0x0 ) 360 | { 361 | // ... check if there are any query or fragment to parse ... 362 | const char* path_end = strpbrk(pathsep, "?#"); 363 | 364 | size_t reslen = 0; 365 | if(path_end) 366 | reslen = (size_t)(path_end - pathsep); 367 | else 368 | reslen = strlen( pathsep ); 369 | 370 | char* path = parse_url_alloc_string( ctx, pathsep, reslen ); 371 | if(path == 0x0) 372 | return 0x0; 373 | 374 | out->path = parse_url_unescape_percent_encoding(path); 375 | if(out->path == 0x0) 376 | return 0x0; 377 | 378 | return pathsep + reslen; 379 | } 380 | 381 | return url; 382 | } 383 | 384 | static const char* parse_url_parse_query( const char* url, parse_url_ctx* ctx, parsed_url* out ) 385 | { 386 | // ... do we have a query? ... 387 | if(*url != '?') 388 | return url; 389 | 390 | // ... skip '?' ... 391 | ++url; 392 | 393 | // ... find the end of the query ... 394 | size_t query_len = 0; 395 | 396 | const char* fragment_start = strchr(url, '#'); 397 | if(fragment_start) 398 | query_len = (size_t)(fragment_start - url); 399 | else 400 | query_len = strlen(url); 401 | 402 | out->query = parse_url_alloc_string( ctx, url, query_len ); 403 | return out->query == 0x0 404 | ? 0x0 405 | : url + query_len; 406 | } 407 | 408 | static const char* parse_url_parse_fragment( const char* url, parse_url_ctx* ctx, parsed_url* out ) 409 | { 410 | // ... do we have a fragment? ... 411 | if(*url != '#') 412 | return url; 413 | 414 | // ... skip '#' ... 415 | ++url; 416 | 417 | size_t frag_len = strlen(url); 418 | out->fragment = parse_url_alloc_string( ctx, url, frag_len ); 419 | 420 | return out->fragment == 0x0 421 | ? 0x0 422 | : url + frag_len; 423 | } 424 | 425 | #define URL_PARSE_FAIL_IF( x ) \ 426 | if( x ) \ 427 | { \ 428 | if( usermem == 0x0 ) \ 429 | free( mem ); \ 430 | return 0x0; \ 431 | } 432 | 433 | URL_PARSER_LINKAGE size_t parse_url_calc_mem_usage( const char* url ) 434 | { 435 | return sizeof( parsed_url ) + strlen( url ) + 7; // 7 == max number of '\0' terminate 436 | } 437 | 438 | URL_PARSER_LINKAGE parsed_url* parse_url( const char* url, void* usermem, size_t mem_size ) 439 | { 440 | void* mem = usermem; 441 | if( mem == 0x0 ) 442 | { 443 | mem_size = parse_url_calc_mem_usage( url ); 444 | mem = malloc( mem_size ); 445 | } 446 | 447 | parse_url_ctx ctx = {mem, mem_size, mem_size}; 448 | 449 | parsed_url* out = (parsed_url*)parse_url_alloc_mem( &ctx, sizeof( parsed_url ) ); 450 | URL_PARSE_FAIL_IF( out == 0x0 ); 451 | 452 | // ... set default values ... 453 | memset(out, 0x0, sizeof(parsed_url)); 454 | out->host = "localhost"; 455 | out->path = "/"; 456 | 457 | url = parse_url_parse_scheme ( url, &ctx, out ); URL_PARSE_FAIL_IF( url == 0x0 ); 458 | url = parse_url_parse_user_pass( url, &ctx, out ); URL_PARSE_FAIL_IF( url == 0x0 ); 459 | url = parse_url_parse_host_port( url, &ctx, out ); URL_PARSE_FAIL_IF( url == 0x0 ); 460 | url = parse_url_parse_query ( url, &ctx, out ); URL_PARSE_FAIL_IF( url == 0x0 ); 461 | url = parse_url_parse_fragment ( url, &ctx, out ); URL_PARSE_FAIL_IF( url == 0x0 ); 462 | 463 | return out; 464 | } 465 | #endif // defined(URL_PARSER_IMPLEMENTATION) 466 | 467 | #endif // URL_H_INCLUDED 468 | --------------------------------------------------------------------------------