├── .gitignore ├── CMakeLists.txt ├── CxxUrl.pro ├── LICENSE ├── README.md ├── cmake └── CxxUrlConfig.cmake.in ├── main.cpp ├── string.hpp ├── url.cpp └── url.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | **/.DS_Store 3 | *.slo 4 | *.lo 5 | *.o 6 | *.obj 7 | 8 | # Precompiled Headers 9 | *.gch 10 | *.pch 11 | 12 | # Compiled Dynamic libraries 13 | *.so 14 | *.dylib 15 | *.dll 16 | 17 | # Fortran module files 18 | *.mod 19 | *.smod 20 | 21 | # Compiled Static libraries 22 | *.lai 23 | *.la 24 | *.a 25 | *.lib 26 | 27 | # Executables 28 | *.exe 29 | *.out 30 | *.app 31 | 32 | **/cmake-build-debug 33 | **/CMakeCache.txt 34 | **/cmake_install.cmake 35 | **/install_manifest.txt 36 | **/CMakeFiles/ 37 | **/CTestTestfile.cmake 38 | **/Makefile 39 | **/*.cbp 40 | **/CMakeScripts 41 | **/compile_commands.json 42 | 43 | include/divisible/* 44 | 45 | 46 | ## Local 47 | 48 | .idea/*.xml 49 | 50 | build/**/* 51 | 52 | include/* 53 | lib/* 54 | bin/* 55 | test/test_runner 56 | CxxUrl.pro.* 57 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(CxxUrl 3 | VERSION 0.3.0 4 | ) 5 | 6 | 7 | #********************************************************* 8 | # GLOBAL OPTIONS - you can change those 9 | # Note: that options are cached, so in order to reavaluate options 10 | # specified via CLI you need to remove the cmake folder 11 | #********************************************************* 12 | 13 | option(ENABLE_INSTALL "Flag to indicate if the install target should be available" ON) 14 | option(CxxUrl_BUILD_STATIC_LIBS "Whether build shared or static" ON) 15 | 16 | if(${PROJECT_IS_TOP_LEVEL}) 17 | # This will be executed if the current project is top level 18 | add_definitions(-D_GNU_SOURCE -Wall -Wextra) 19 | endif() 20 | 21 | #********************************************************* 22 | # determine platform 23 | #********************************************************* 24 | set(PLATFORM UNKNOWN) 25 | 26 | if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 27 | set(PLATFORM LINUX) 28 | if (DEFINED ${ANDROID_PLATFORM}) 29 | set(PLATFORM ANDROID) 30 | endif() 31 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") 32 | set(PLATFORM MAC_OS) 33 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows") 34 | set(PLATFORM WINDOWS) 35 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Android") 36 | set(PLATFORM ANDROID) 37 | endif() 38 | 39 | message(DEBUG "Platform: " ${CMAKE_SYSTEM_NAME} "-" ${PLATFORM}) 40 | 41 | #********************************************************* 42 | # Project Settings (no need to edit those) 43 | #********************************************************* 44 | 45 | include(GNUInstallDirs) # for CMAKE_INSTALL_FULL_INCLUDEDIR 46 | set(INSTALL_HEADER_INCLUDE_DIR "${CMAKE_INSTALL_FULL_INCLUDEDIR}/${PROJECT_NAME}") 47 | set(NAMESPACE chmike) 48 | set(TARGET_NAME ${PROJECT_NAME}) 49 | 50 | set(HEADERS 51 | ${CMAKE_CURRENT_SOURCE_DIR}/url.hpp 52 | ${CMAKE_CURRENT_SOURCE_DIR}/string.hpp) 53 | 54 | set(SOURCES 55 | ${HEADERS} 56 | ${CMAKE_CURRENT_SOURCE_DIR}/url.cpp) 57 | 58 | if(CxxUrl_BUILD_STATIC_LIBS) 59 | add_library(${TARGET_NAME} STATIC ${SOURCES}) 60 | else() 61 | add_library(${TARGET_NAME} SHARED ${SOURCES}) 62 | endif() 63 | 64 | add_library(${NAMESPACE}::${TARGET_NAME} ALIAS ${TARGET_NAME}) 65 | 66 | set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON) 67 | set_target_properties(${TARGET_NAME} PROPERTIES SOVERSION 1) 68 | set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${HEADERS}") 69 | 70 | if(${PLATFORM} STREQUAL ANDROID) 71 | message(INFO "${PROJECT_NAME} Platform Android detected") 72 | target_compile_definitions(${TARGET_NAME} PUBLIC ANDROID_PLATFORM) 73 | endif() 74 | 75 | # The following tells CMake to propagate the correct include directory if this project is linked via target_link_libraries 76 | target_include_directories(CxxUrl 77 | PUBLIC 78 | $ 79 | $ 80 | ) 81 | 82 | if(${ENABLE_INSTALL}) 83 | set(PACKAGE_NAME ${PROJECT_NAME}) # the package name is what you write in find_package 84 | set(TARGETS_FILE_NAME "${PACKAGE_NAME}Targets.cmake") 85 | set(TARGETS_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${TARGETS_FILE_NAME}") 86 | set(VERSION_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}ConfigVersion.cmake") 87 | set(CONFIG_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGE_NAME}Config.cmake") 88 | set(PACKAGE_CONFIG_IN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CxxUrlConfig.cmake.in") 89 | 90 | install( 91 | TARGETS ${TARGET_NAME} 92 | EXPORT ${PACKAGE_NAME} DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} 93 | PUBLIC_HEADER DESTINATION ${INSTALL_HEADER_INCLUDE_DIR} 94 | ) 95 | 96 | install( 97 | EXPORT ${PACKAGE_NAME} 98 | FILE ${TARGETS_FILE_NAME} 99 | NAMESPACE "${NAMESPACE}::" 100 | DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/cmake/${PACKAGE_NAME}" 101 | ) 102 | 103 | include(CMakePackageConfigHelpers) 104 | configure_package_config_file( 105 | "${PACKAGE_CONFIG_IN_FILE}" 106 | ${CONFIG_FILE_PATH} 107 | INSTALL_DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/cmake/${PACKAGE_NAME}" 108 | ) 109 | 110 | message(INFO "\tExpanding CMAKE_PREFIX_PATH to include package build dir") # this enables find_package after add_subdirectory 111 | if(${PROJECT_IS_TOP_LEVEL}) 112 | set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};${CMAKE_CURRENT_BINARY_DIR}") 113 | else() 114 | set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};${CMAKE_CURRENT_BINARY_DIR}" PARENT_SCOPE) 115 | endif() 116 | 117 | write_basic_package_version_file( 118 | ${VERSION_FILE_PATH} 119 | VERSION ${PROJECT_VERSION} 120 | COMPATIBILITY SameMinorVersion # TODO: once 1.0.0 is reached, change to SameMajorVersion 121 | ) 122 | 123 | install( 124 | FILES ${VERSION_FILE_PATH} ${CONFIG_FILE_PATH} 125 | DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/cmake/${PACKAGE_NAME}" 126 | ) 127 | 128 | export( 129 | TARGETS ${TARGET_NAME} 130 | NAMESPACE "${NAMESPACE}::" 131 | FILE "${TARGETS_FILE_PATH}" 132 | ) 133 | endif() 134 | -------------------------------------------------------------------------------- /CxxUrl.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | CONFIG += console 3 | CONFIG -= app_bundle 4 | CONFIG -= qt 5 | CONFIG += c++11 6 | 7 | SOURCES += main.cpp \ 8 | url.cpp 9 | 10 | HEADERS += \ 11 | url.hpp 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christophe Meessen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A simple C++ URL class 2 | 3 | ## Usage 4 | 5 | ### CMake 6 | 7 | If you are developing your project with CMake you can include this library with the `find_package` mechanism of CMake. 8 | First, either run `make install` or include this project via `add_subdirectory`. 9 | Afterwards, you can link to this lib like so: 10 | 11 | ```CMake 12 | cmake_minimum_required(VERSION 3.4) 13 | 14 | project(example_find_package) 15 | 16 | find_package(CxxUrl REQUIRED) 17 | 18 | add_executable(${PROJECT_NAME} main.cpp) 19 | 20 | target_link_libraries(${PROJECT_NAME} PUBLIC chmike::CxxUrl) 21 | ``` 22 | 23 | ### The `Url` object API 24 | `Url` is a C++ URL handling class with a very simple API. It's use is straightforward. 25 | 26 | URIs that don't follow the URL standard defined in [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986) might not be correctly parsed in all cases. 27 | 28 | * Construct an `Url` object 29 | ``` C++ 30 | Url u1; 31 | Url u2("http://www.example.com/"); 32 | Url u3(u2); 33 | ``` 34 | 35 | * Assigning an url to an `Url` object 36 | ``` C++ 37 | u1=u2; 38 | u2="http://www.example.com/"; 39 | ``` 40 | 41 | * Getting the url as a string 42 | ``` C++ 43 | std::string url_str=url.str(); 44 | ``` 45 | 46 | * Clearing an `Url` object 47 | ``` C++ 48 | u1.clear(); 49 | ``` 50 | 51 | * Getting the different fields of an `Url` object 52 | ``` C++ 53 | std::string scheme_str=u1.scheme(); 54 | std::string user_info_str=u1.user_info(); 55 | std::string host_str=u1.host(); 56 | std::uint8_t ip_version=u1.ip_version(); 57 | // 0=name, 4=IPv4, 6=IPv6,-1=undefined 58 | std::string port_str=u1.port(); 59 | std::string path_str=u1.path(); 60 | Url::Query query=u1.query(); 61 | std::string fragment_str=u1.fragment(); 62 | ``` 63 | 64 | * Setting the different fields of an `Url` object 65 | ``` C++ 66 | u1.clear().scheme("http").host("www.example.com").path("/"); 67 | u1.add_query("q","some search key").add_query("fast"); 68 | assert(u1.str()=="http://www.example.com/?q=some+search+key&fast"); 69 | ``` 70 | 71 | * Accessing the key and values in the query 72 | ``` C++ 73 | u1.clear().add_query("a","b").add_query("c","d"); 74 | assert(u1.query(0).val()=="b"); 75 | assert(u1.query(1).key()=="c"); 76 | assert(u1.str()=="?a=b&c=d"); 77 | u1.set_query(0).val("B"); 78 | u1.set_query(1).key("C"); 79 | assert(u1.str()=="?a=B&C=d"); 80 | ``` 81 | 82 | * Output of an `Url` object 83 | ``` C++ 84 | u1.str("http://www.example.com/?q=some+search+key&fast"); 85 | std::stringstream str; 86 | str << u1; 87 | assert(str.str()=="Url:{url(http://www.example.com/?q=some+search+key&fast) " 88 | "scheme(http) host(www.example.com) IPv(0) path(/) " 89 | "query( )}"); 90 | ``` 91 | 92 | ### Implementation detail 93 | 94 | The `Url` object uses lazy URL parsing and building. When a URL as string is 95 | assigned to the object, it is not immediatly parsed. It can then be assigned or 96 | moved to another `Url` object efficiently. The URL is parsed only when needed, 97 | which is before one of the fields is accessed or set, or when the object is 98 | output. 99 | 100 | The URL is built only when the `str()` method is called or when the `Url` object 101 | is output. 102 | 103 | ### Testing the library 104 | 105 | In order to quickly build a static library and test the code, the following 106 | sequence of bash instructions may work for you. 107 | 108 | ``` 109 | g++ -c url.cpp 110 | ar rvs CxxUrl.a url.o 111 | g++ main.cpp CxxUrl.a 112 | ./a.out 113 | ``` 114 | -------------------------------------------------------------------------------- /cmake/CxxUrlConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | if(NOT TARGET @INSTALL_TEST_TARGET@) 4 | include ("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_FILE_NAME@") 5 | endif() 6 | 7 | check_required_components(@PACKAGE_NAME@) 8 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "url.hpp" 7 | 8 | using namespace std; 9 | 10 | #define test_parse(str, m, expect) {\ 11 | try {\ 12 | Url url(str);\ 13 | if(url.m()!=expect)\ 14 | cout << "FAILED: ('" << str << "') -> "#m"('" << url.m() << "') (expect " << expect << ")" << endl;\ 15 | else\ 16 | cout << "PASSED: ('" << str << "') -> "#m"('" << url.m() << "')" << endl;\ 17 | } catch(std::exception &e) {\ 18 | cout << "FAILED: ('" << str << "') -> \"" << e.what() << "\" (expect " << expect << ")" << endl;\ 19 | }\ 20 | } 21 | 22 | 23 | #define test_valid(m1,str,m2,expect,ip_v) {\ 24 | try {\ 25 | Url url;\ 26 | if(url.m1(str).m2()!=expect)\ 27 | cout << "FAILED: "#m1"('" << str << "') -> "#m2"('" << url.m2() << "') (expect " << expect << ")" << endl;\ 28 | else if (url.ip_version()!=ip_v)\ 29 | cout << "FAILED: "#m1"('" << str << "') -> "#m2"('" << url.m2() << "') (IP version " << (int)url.ip_version() << " instead of " << ip_v << ")" << endl;\ 30 | else\ 31 | cout << "PASSED: "#m1"('" << str << "') -> "#m2"('" << url.m2() << "')" << endl;\ 32 | } catch(std::exception &e) {\ 33 | cout << "FAILED: "#m1"('" << str << "') -> \"" << e.what() << "\" (expect " << expect << ")" << endl;\ 34 | }\ 35 | } 36 | 37 | #define test_invalid(m1,str,m2,expect) {\ 38 | try {\ 39 | Url url;\ 40 | std::string out=url.m1(str).m2();\ 41 | cout << "FAILED: "#m1"('" << str << "') -> "#m2"('" << out << "') (expect \"" << expect << "\")" << endl;\ 42 | } catch(std::exception &e) {\ 43 | if (e.what()!=std::string(expect))\ 44 | cout << "FAILED: "#m1"('" << str << "') -> " << e.what() << " (expect \"" << expect << "\")" << endl;\ 45 | else\ 46 | cout << "PASSED: "#m1"('" << str << "') -> " << e.what() << endl;\ 47 | }\ 48 | } 49 | 50 | 51 | void test_all_valid() { 52 | 53 | cout << "------------------------------------------------------" << endl; 54 | cout << "Test scheme" << endl; 55 | cout << "------------------------------------------------------" << endl; 56 | test_valid(scheme,"a",scheme,"a",-1); 57 | test_valid(scheme,"HTTP",scheme,"http",-1); 58 | test_valid(scheme,"HTTPs",scheme,"https",-1); 59 | test_valid(scheme,"X-1.0",scheme,"x-1.0",-1); 60 | test_valid(scheme,"X+a",scheme,"x+a",-1); 61 | test_invalid(scheme,"",scheme,"Invalid scheme ''"); 62 | test_invalid(scheme,"1A",scheme,"Invalid scheme '1A'"); 63 | test_invalid(scheme,"a/5",scheme,"Invalid scheme 'a/5'"); 64 | 65 | cout << "------------------------------------------------------" << endl; 66 | cout << "Test user info" << endl; 67 | cout << "------------------------------------------------------" << endl; 68 | test_valid(user_info,"",user_info,"",-1); 69 | test_valid(user_info,"user:passwd",user_info,"user:passwd",-1); 70 | test_parse("http://user:pa%24%24wd@www.example.com",user_info,"user:pa$$wd"); 71 | 72 | cout << "------------------------------------------------------" << endl; 73 | cout << "Test host" << endl; 74 | cout << "------------------------------------------------------" << endl; 75 | test_valid(host,"",host,"",-1); 76 | test_valid(host,"www.example.com",host,"www.example.com",0); 77 | test_valid(host,"!$+",host,"!$+",0); 78 | test_valid(host,"12.34.56.78.com",host,"12.34.56.78.com",0); 79 | test_invalid(host,"!#",host,"Invalid host '!#'"); 80 | test_valid(host,"12.34.56.78",host,"12.34.56.78",4); 81 | test_valid(host,"0.0.0.0",host,"0.0.0.0",4); 82 | test_invalid(host,"12.34.56.257",host,"Invalid IPv4 address '12.34.56.257'"); 83 | test_valid(host,"1:2:3:4:5:6:7:8",host,"1:2:3:4:5:6:7:8",6); 84 | test_valid(host,"1:2:3:4:5:6:9.9.9.9",host,"1:2:3:4:5:6:9.9.9.9",6); 85 | test_valid(host,"::9.9.9.9",host,"::9.9.9.9",6); 86 | test_valid(host,"::2:3:4:5:6:7:8",host,"0:2:3:4:5:6:7:8",6); 87 | test_valid(host,"::2:3:4:5:6:9.9.9.9",host,"0:2:3:4:5:6:9.9.9.9",6); 88 | test_valid(host,"1::4:5:6:7:8:9",host,"1:0:4:5:6:7:8:9",6); 89 | test_valid(host,"1::4:5:6:7:8",host,"1::4:5:6:7:8",6); 90 | test_valid(host,"1::4:5:6:9.9.9.9",host,"1::4:5:6:9.9.9.9",6); 91 | test_valid(host,"1:2::5:6:7:8",host,"1:2::5:6:7:8",6); 92 | test_valid(host,"1:2::5:6:9.9.9.9",host,"1:2::5:6:9.9.9.9",6); 93 | test_valid(host,"1:2::5:6:7:8",host,"1:2::5:6:7:8",6); 94 | test_valid(host,"1:2:3::6:9.9.9.9",host,"1:2:3::6:9.9.9.9",6); 95 | test_valid(host,"1:2:3:4::7:8",host,"1:2:3:4::7:8",6); 96 | test_valid(host,"1:2:3:4::9.9.9.9",host,"1:2:3:4::9.9.9.9",6); 97 | test_valid(host,"1:2:3:4:5::8",host,"1:2:3:4:5::8",6); 98 | test_valid(host,"1:2:3:4::9.9.9.9",host,"1:2:3:4::9.9.9.9",6); 99 | test_valid(host,"1:2:3:4:5::8",host,"1:2:3:4:5::8",6); 100 | test_valid(host,"1:2:3:4:5:6::",host,"1:2:3:4:5:6::",6); 101 | test_valid(host,"1:2:3:4:5:6:7::",host,"1:2:3:4:5:6:7:0",6); 102 | test_valid(host,"1:2:3:4:5::",host,"1:2:3:4:5::",6); 103 | test_valid(host,"1:2:3:4::",host,"1:2:3:4::",6); 104 | test_valid(host,"1:2:3::",host,"1:2:3::",6); 105 | test_valid(host,"1111:2222:3333:4444:5555:6666:7777:8888",host,"1111:2222:3333:4444:5555:6666:7777:8888",6); 106 | test_valid(host,"0111:0022:0333:4444:0555:0006:0777:8888",host,"111:22:333:4444:555:6:777:8888",6); 107 | test_valid(host,"::ffff:9.9.9.9",host,"::ffff:9.9.9.9",6); 108 | test_valid(host,"DEAD::FACE:9.9.9.9",host,"dead::face:9.9.9.9",6); 109 | test_valid(host,"::",host,"::",6); 110 | test_valid(host,"::1",host,"::1",6); 111 | test_valid(host,"1::",host,"1::",6); 112 | test_valid(host,"::6:7:8",host,"::6:7:8",6); 113 | test_valid(host,"1::8",host,"1::8",6); 114 | test_valid(host,"1:2:3:4:5:6:7:8",host,"1:2:3:4:5:6:7:8",6); 115 | test_valid(host,"::2:3:4:5:6:7:8",host,"0:2:3:4:5:6:7:8",6); 116 | test_valid(host,"1:2:3:4:5:6:7::",host,"1:2:3:4:5:6:7:0",6); 117 | test_valid(host,"1:2:3::5:6:7:8",host,"1:2:3:0:5:6:7:8",6); 118 | test_valid(host,"::3:4:5:6:7:8",host,"::3:4:5:6:7:8",6); 119 | test_valid(host,"0:0:3:4:5:6:7:8",host,"::3:4:5:6:7:8",6); 120 | test_valid(host,"0:0:3:4:0:0:7:8",host,"::3:4:0:0:7:8",6); 121 | test_valid(host,"0:0:3:4:0:0:0:8",host,"0:0:3:4::8",6); 122 | test_valid(host,"0:0:3:4:0:0::",host,"0:0:3:4::",6); 123 | test_valid(host,"0:0:3:4::",host,"0:0:3:4::",6); 124 | test_valid(host,"0:AbC:d:00ef::",host,"0:abc:d:ef::",6); 125 | test_valid(host,"::ffff:9.9.9.9",host,"::ffff:9.9.9.9",6); 126 | test_valid(host,"0:0:0::ffff:255.199.19.9",host,"::ffff:255.199.19.9",6); 127 | test_valid(host,"xxx",host,"xxx",0); 128 | test_invalid(host,"xxx::",host,"Invalid host 'xxx::'"); 129 | test_invalid(host,"x:x:x::x:x:x",host,"Invalid host 'x:x:x::x:x:x'"); 130 | test_invalid(host,"x:x:x::x:x:x:y.y.y.y",host,"Invalid host 'x:x:x::x:x:x:y.y.y.y'"); 131 | test_invalid(host,"1111:2222:3333:4444:5555:6666:7777:88889",host,"Invalid IPv6 address '1111:2222:3333:4444:5555:6666:7777:88889'"); 132 | test_invalid(host,"1:2:3:4:5:6:7:8:9",host,"Invalid IPv6 address '1:2:3:4:5:6:7:8:9'"); 133 | test_invalid(host,":1:2:3:4:5:6:7:8",host,"Invalid IPv6 address ':1:2:3:4:5:6:7:8'"); 134 | test_invalid(host,":1:2:3::6:7:8",host,"Invalid IPv6 address ':1:2:3::6:7:8'"); 135 | test_invalid(host,"1:2:3:4:5:6:7:8:",host,"Invalid IPv6 address '1:2:3:4:5:6:7:8:'"); 136 | test_invalid(host,"1:2:3:4:5:6:7:8::",host,"Invalid IPv6 address '1:2:3:4:5:6:7:8::'"); 137 | test_invalid(host,"1:2::5:6:7:8:",host,"Invalid IPv6 address '1:2::5:6:7:8:'"); 138 | test_invalid(host,"1:2::5:6:7:8::",host,"Invalid IPv6 address '1:2::5:6:7:8::'"); 139 | test_invalid(host,"::3:4:5:6::",host,"Invalid IPv6 address '::3:4:5:6::'"); 140 | test_invalid(host,"1::4::7:8",host,"Invalid IPv6 address '1::4::7:8'"); 141 | test_invalid(host,"1:2:3:4:5:6:9.257.9.9",host,"Invalid IPv6 address '1:2:3:4:5:6:9.257.9.9'"); 142 | test_invalid(host,"1:2:3:4:5:6:.9.9.9",host,"Invalid IPv6 address '1:2:3:4:5:6:.9.9.9'"); 143 | 144 | cout << "------------------------------------------------------" << endl; 145 | cout << "Test port" << endl; 146 | cout << "------------------------------------------------------" << endl; 147 | test_valid(port,"123",port,"123",-1); 148 | test_valid(port,"0",port,"0",-1); 149 | test_valid(port,"65535",port,"65535",-1); 150 | test_invalid(port,"65536",port,"Invalid port '65536'"); 151 | test_invalid(port,"12a55",port,"Invalid port '12a55'"); 152 | 153 | cout << "------------------------------------------------------" << endl; 154 | cout << "Test path" << endl; 155 | cout << "------------------------------------------------------" << endl; 156 | test_valid(path,"",path,"",-1); 157 | test_valid(path,"a",path,"a",-1); 158 | test_valid(path,".",path,"",-1); 159 | test_valid(path,"..",path,"",-1); 160 | test_valid(path,"a/",path,"a/",-1); 161 | test_valid(path,"./",path,"",-1); 162 | test_valid(path,"../",path,"",-1); 163 | test_valid(path,"/a",path,"/a",-1); 164 | test_valid(path,"/.",path,"/",-1); 165 | test_valid(path,"/..",path,"/",-1); 166 | test_valid(path,"/a/",path,"/a/",-1); 167 | test_valid(path,"/./",path,"/",-1); 168 | test_valid(path,"/../",path,"/",-1); 169 | test_valid(path,"/a/",path,"/a/",-1); 170 | test_valid(path,"/./",path,"/",-1); 171 | test_valid(path,"/../",path,"/",-1); 172 | test_valid(path,"/a/a",path,"/a/a",-1); 173 | test_valid(path,"/./a",path,"/a",-1); 174 | test_valid(path,"/../a",path,"/a",-1); 175 | test_valid(path,"/a/.",path,"/a/",-1); 176 | test_valid(path,"/./.",path,"/",-1); 177 | test_valid(path,"/../.",path,"/",-1); 178 | test_valid(path,"/a/..",path,"/",-1); 179 | test_valid(path,"/./..",path,"/",-1); 180 | test_valid(path,"/../..",path,"/",-1); 181 | test_valid(path,"/a/a/",path,"/a/a/",-1); 182 | test_valid(path,"/./a/",path,"/a/",-1); 183 | test_valid(path,"/../a/",path,"/a/",-1); 184 | test_valid(path,"/a/./",path,"/a/",-1); 185 | test_valid(path,"/././",path,"/",-1); 186 | test_valid(path,"/.././",path,"/",-1); 187 | test_valid(path,"/a/../",path,"/",-1); 188 | test_valid(path,"/./../",path,"/",-1); 189 | test_valid(path,"/../../",path,"/",-1); 190 | test_valid(path,"a/a",path,"a/a",-1); 191 | test_valid(path,"./a",path,"a",-1); 192 | test_valid(path,"../a",path,"a",-1); 193 | test_valid(path,"a/.",path,"a/",-1); 194 | test_valid(path,"./.",path,"",-1); 195 | test_valid(path,"../.",path,"",-1); 196 | test_valid(path,"a/..",path,"",-1); 197 | test_valid(path,"./..",path,"",-1); 198 | test_valid(path,"../..",path,"",-1); 199 | test_valid(path,"a/a/",path,"a/a/",-1); 200 | test_valid(path,"./a/",path,"a/",-1); 201 | test_valid(path,"../a/",path,"a/",-1); 202 | test_valid(path,"a/./",path,"a/",-1); 203 | test_valid(path,"././",path,"",-1); 204 | test_valid(path,".././",path,"",-1); 205 | test_valid(path,"a/../",path,"",-1); 206 | test_valid(path,"./../",path,"",-1); 207 | test_valid(path,"../../",path,"",-1); 208 | test_valid(path,"a/./a",path,"a/a",-1); 209 | test_valid(path,"a/./..",path,"",-1); 210 | test_valid(path,"ab cd",path,"ab cd",-1); 211 | 212 | cout << "------------------------------------------------------" << endl; 213 | cout << "Test fragment" << endl; 214 | cout << "------------------------------------------------------" << endl; 215 | test_valid(fragment,"xxx",fragment,"xxx",-1); 216 | test_valid(fragment,"ab cd",fragment,"ab cd",-1); 217 | 218 | cout << "------------------------------------------------------" << endl; 219 | cout << "Test url" << endl; 220 | cout << "------------------------------------------------------" << endl; 221 | test_valid(str,"",str,"",-1); 222 | test_valid(str,"http:",str,"http:",-1); 223 | test_valid(str,"http:",scheme,"http",-1); 224 | test_valid(scheme,"http",str,"http:",-1); 225 | test_valid(str,"test/path",str,"test/path",-1); 226 | test_valid(str,"test/path/..",str,"test",-1); 227 | test_valid(str,"test/path/../..",str,"",-1); 228 | test_valid(str,"test/path/../../.",str,"",-1); 229 | test_valid(str,"/test/path/../../",str,"/",-1); 230 | test_valid(str,"/test/path/../../.",str,"/",-1); 231 | test_valid(str,"test/path/.././..",str,"",-1); 232 | test_valid(str,"test/path/../../..",str,"",-1); 233 | test_invalid(str,"test path",str,"Path 'test path' in 'test path' is invalid"); 234 | test_valid(str,"test+path",str,"test+path",-1); 235 | test_valid(str,"test%2bpath",str,"test+path",-1); 236 | test_valid(str,"test%20path",str,"test%20path",-1); 237 | test_valid(str,"test%2Fpath",str,"test/path",-1); 238 | test_valid(str,"/test+path",str,"/test+path",-1); 239 | test_valid(str,"/test%2bpath",str,"/test+path",-1); 240 | test_valid(str,"/test%20path",str,"/test%20path",-1); 241 | test_valid(str,"/test%2Fpath",str,"/test/path",-1); 242 | test_valid(str,"%2Ftest+path%2F",str,"/test+path/",-1); 243 | test_valid(str,"%2Ftest%2bpath%2F",str,"/test+path/",-1); 244 | test_valid(str,"%2Ftest%20path%2F",str,"/test%20path/",-1); 245 | test_valid(str,"%2Ftest%2Fpath%2F",str,"/test/path/",-1); 246 | test_valid(str,"",path,"",-1); 247 | test_valid(str,"test/path",path,"test/path",-1); 248 | test_valid(str,"test/path/..",path,"test",-1); 249 | test_valid(str,"test/path/../..",path,"",-1); 250 | test_valid(str,"test/path/../../.",path,"",-1); 251 | test_valid(str,"/test/path/../../",path,"/",-1); 252 | test_valid(str,"/test/path/../../.",path,"/",-1); 253 | test_valid(str,"test/path/.././..",path,"",-1); 254 | test_valid(str,"test/path/../../..",path,"",-1); 255 | test_valid(str,"test+path",path,"test+path",-1); 256 | test_valid(str,"test%2bpath",path,"test+path",-1); 257 | test_valid(str,"test%20path",path,"test path",-1); 258 | test_valid(str,"test%2Fpath",path,"test/path",-1); 259 | test_valid(str,"/test+path",path,"/test+path",-1); 260 | test_valid(str,"/test%2bpath",path,"/test+path",-1); 261 | test_valid(str,"/test%20path",path,"/test path",-1); 262 | test_valid(str,"/test%2Fpath",path,"/test/path",-1); 263 | test_valid(str,"%2Ftest+path%2F",path,"/test+path/",-1); 264 | test_valid(str,"%2Ftest%2bpath%2F",path,"/test+path/",-1); 265 | test_valid(str,"%2Ftest%20path%2F",path,"/test path/",-1); 266 | test_valid(str,"%2Ftest%2Fpath%2F",path,"/test/path/",-1); 267 | test_valid(path,"test/path",str,"test/path",-1); 268 | test_valid(path,"test/path/..",str,"test",-1); 269 | test_valid(path,"test/path/../..",str,"",-1); 270 | test_valid(path,"test/path/../../.",str,"",-1); 271 | test_valid(path,"/test/path/../../",str,"/",-1); 272 | test_valid(path,"/test/path/../../.",str,"/",-1); 273 | test_valid(path,"test/path/.././..",str,"",-1); 274 | test_valid(path,"test/path/../../..",str,"",-1); 275 | test_valid(path,"test path",str,"test%20path",-1); 276 | test_valid(path,"test+path",str,"test+path",-1); 277 | test_valid(path,"test%2bpath",str,"test%252bpath",-1); 278 | test_valid(path,"test%20path",str,"test%2520path",-1); 279 | test_valid(path,"test%2Fpath",str,"test%252Fpath",-1); 280 | test_valid(path,"/test+path",str,"/test+path",-1); 281 | test_valid(path,"/test%2bpath",str,"/test%252bpath",-1); 282 | test_valid(path,"/test%20path",str,"/test%2520path",-1); 283 | test_valid(path,"/test%2Fpath",str,"/test%252Fpath",-1); 284 | test_valid(path,"%2Ftest+path%2F",str,"%252Ftest+path%252F",-1); 285 | test_valid(path,"%2Ftest%2bpath%2F",str,"%252Ftest%252bpath%252F",-1); 286 | test_valid(path,"%2Ftest%20path%2F",str,"%252Ftest%2520path%252F",-1); 287 | test_valid(path,"%2Ftest%2Fpath%2F",str,"%252Ftest%252Fpath%252F",-1); 288 | test_valid(path,"test=path",str,"test=path",-1); 289 | test_valid(str,"test=path",path,"test=path",-1); 290 | test_valid(str,"http:test/path",scheme,"http",-1); 291 | test_valid(str,"http:test/path",host,"",-1); 292 | test_valid(str,"http:test/path",path,"test/path",-1); 293 | test_valid(str,"http:test/path/..",str,"http:test",-1); 294 | test_valid(str,"http:test/path/../..",str,"http:",-1); 295 | test_valid(str,"http:test/path/../../.",str,"http:",-1); 296 | test_valid(str,"http:/test/path/../../",str,"http:/",-1); 297 | test_valid(str,"http:/test/path/../../.",str,"http:/",-1); 298 | test_valid(str,"http:test/path/.././..",str,"http:",-1); 299 | test_valid(str,"http:test/path/../../..",str,"http:",-1); 300 | test_invalid(str,"http:test path",str,"Path 'test path' in 'http:test path' is invalid"); 301 | test_valid(str,"http:test+path",str,"http:test+path",-1); 302 | test_valid(str,"http:test%2bpath",str,"http:test+path",-1); 303 | test_valid(str,"http:test%20path",str,"http:test%20path",-1); 304 | test_valid(str,"http:test%2Fpath",str,"http:test/path",-1); 305 | test_valid(str,"http:/test+path",str,"http:/test+path",-1); 306 | test_valid(str,"http:/test%2bpath",str,"http:/test+path",-1); 307 | test_valid(str,"http:/test%20path",str,"http:/test%20path",-1); 308 | test_valid(str,"http:/test%2Fpath",str,"http:/test/path",-1); 309 | test_valid(str,"http:%2Ftest+path%2F",str,"http:/test+path/",-1); 310 | test_valid(str,"http:%2Ftest%2bpath%2F",str,"http:/test+path/",-1); 311 | test_valid(str,"http:%2Ftest%20path%2F",str,"http:/test%20path/",-1); 312 | test_valid(str,"http:%2Ftest%20path%2F",path,"/test path/",-1); 313 | test_valid(str,"http:%2Ftest%2Fpath%2F",str,"http:/test/path/",-1); 314 | test_valid(str,"?",str,"",-1); 315 | test_valid(str,"?#",str,"",-1); 316 | test_valid(str,"?ab",str,"?ab",-1); 317 | test_valid(str,"?ab%20cd",str,"?ab+cd",-1); 318 | test_valid(str,"?ab+cd",str,"?ab+cd",-1); 319 | test_valid(str,"?ab%2bcd",str,"?ab%2Bcd",-1); 320 | test_valid(str,"?a=b;c=d",str,"?a=b&c=d",-1); 321 | test_valid(str,"?a=b&c=d",str,"?a=b&c=d",-1); 322 | test_valid(str,"?a=b?c=d",str,"?a=b?c=d",-1); 323 | test_valid(str,"?a?b=c!d;q=http://test/path?m=n#xxx",str,"?a?b=c!d&q=http://test/path?m=n#xxx",-1); 324 | test_valid(str,"?a=b;c=d#xxx",str,"?a=b&c=d#xxx",-1); 325 | test_valid(str,"?a=b;c=d#xxx",fragment,"xxx",-1); 326 | test_valid(str,"?a=b;c=d#xx/?x",str,"?a=b&c=d#xx/?x",-1); 327 | test_valid(str,"?a=b;c=d#xx/?x",fragment,"xx/?x",-1); 328 | test_valid(str,"?a=b;c=d",str,"?a=b&c=d",-1); 329 | test_valid(str,"?a=b;c=d#xxx",str,"?a=b&c=d#xxx",-1); 330 | test_valid(str,"?a=b;c=d#xx/?x",str,"?a=b&c=d#xx/?x",-1); 331 | test_valid(str,"#xxx",str,"#xxx",-1); 332 | test_valid(str,"#xxx",fragment,"xxx",-1); 333 | test_valid(fragment,"xxx",str,"#xxx",-1); 334 | test_valid(fragment,"#xxx",str,"#%23xxx",-1); 335 | test_valid(str,"http://www.example.net/test/",str,"http://www.example.net/test/",0); 336 | test_valid(str,"http://www.example.net:343/test/",str,"http://www.example.net:343/test/",0); 337 | test_valid(str,"http://www.example.net/",str,"http://www.example.net/",0); 338 | test_valid(str,"http://www.example.net",str,"http://www.example.net",0); 339 | test_valid(str,"http://www.example.net:343",str,"http://www.example.net:343",0); 340 | test_valid(str,"http://tutu@www.example.net:343",str,"http://tutu@www.example.net:343",0); 341 | test_valid(str,"http://toto:pwd@www.example.net:343",str,"http://toto:pwd@www.example.net:343",0); 342 | test_valid(str,"http://www.example.net/test",str,"http://www.example.net/test",0); 343 | test_valid(str,"http://www.example.net/test?aa",str,"http://www.example.net/test?aa",0); 344 | test_valid(str,"//www.example.net/test?aa",str,"//www.example.net/test?aa",0); 345 | test_valid(str,"/test/",str,"/test/",-1); 346 | test_valid(str,"img.jpg",str,"img.jpg",-1); 347 | test_valid(str,"/test/?a=b",str,"/test/?a=b",-1); 348 | test_valid(str,"/test%2F%2E?a=b%2E&c%20#f%20f",str,"/test/?a=b.&c+#f%20f",-1); 349 | test_valid(str,"HTTP://www.example.net?a=b#f",str,"http://www.example.net?a=b#f",0); 350 | test_valid(str,"?a.?/@a%3db=c",str,"?a.?/@a%3Db=c",-1); 351 | test_valid(str,"?a.?/@a%3db=c",query(0).key,"a.?/@a=b",-1); 352 | test_valid(str,"?a.?/@a%3db=c",query(0).val,"c",-1); 353 | test_valid(str,"?a=b;cd+ef",query(1).key,"cd ef",-1); 354 | test_valid(str,"?a=b;cd+ef",query(1).val,"",-1); 355 | test_invalid(str,"?a=b;cd+ef",query(2).key,"Invalid Url query index (2)"); 356 | 357 | test_valid(str,"http://bavaria.com",str,"http://bavaria.com",0); 358 | test_valid(str,"http://1.2.3.4",str,"http://1.2.3.4",4); 359 | test_valid(str,"http://[1:2:3:4:5:6:7:8]",str,"http://[1:2:3:4:5:6:7:8]",6); 360 | test_valid(str,"http://[v7.1:2:3:4:5:6:7:8]",str,"http://[v7.1:2:3:4:5:6:7:8]",7); 361 | test_invalid(str,"http://[v7.1:2:3:4:5:6:7:8]:",str,"Port '' in 'http://[v7.1:2:3:4:5:6:7:8]:' is invalid"); 362 | test_valid(str,"http://bavaria.com:12",str,"http://bavaria.com:12",0); 363 | test_valid(str,"http://1.2.3.4:12",str,"http://1.2.3.4:12",4); 364 | test_valid(str,"http://[1:2:3:4:5:6:7:8]:0",str,"http://[1:2:3:4:5:6:7:8]:0",6); 365 | } 366 | 367 | 368 | int main() 369 | { 370 | cout << "Starting..." << endl; 371 | clock_t start = clock(); 372 | 373 | test_all_valid(); 374 | 375 | cout << endl << endl << "Usage Examples..." << endl; 376 | 377 | Url u("http://www.example.com"); 378 | cout << u << endl; 379 | 380 | u.path("/path with spaces").add_query("q","some search key").add_query("fast"); 381 | cout << u << endl; 382 | cout << u.str() << endl; 383 | 384 | u.host("").host("test"); 385 | cout << u << endl; 386 | cout << u.str() << endl; 387 | 388 | u.path("").path("path").host(""); 389 | cout << u << endl; 390 | cout << u.str() << endl; 391 | 392 | 393 | Url u1, u2, u3; 394 | u2.scheme("http"); 395 | cout << u2.str() << endl; 396 | 397 | u1.clear().scheme("http").host("www.example.com").path("/"); 398 | u1.add_query("q","some search key").add_query("fast"); 399 | assert(u1.str()=="http://www.example.com/?q=some+search+key&fast"); 400 | 401 | u1.clear().add_query("a","b").add_query("c","d"); 402 | assert(u1.query(0).val()=="b"); 403 | assert(u1.query(1).key()=="c"); 404 | cout << u1.str() << endl; 405 | assert(u1.str()=="?a=b&c=d"); 406 | u1.set_query(0).val("B"); 407 | u1.set_query(1).key("C"); 408 | assert(u1.str()=="?a=B&C=d"); 409 | 410 | u1.str("http://www.example.com/?q=some+search+key&fast"); 411 | std::stringstream str; 412 | str << u1; 413 | cout << u1 << endl; 414 | assert(str.str()=="Url:{url(http://www.example.com/?q=some+search+key&fast) " 415 | "scheme(http) host(www.example.com) IPv(0) path(/) " 416 | "query( )}"); 417 | 418 | u1.clear(); 419 | u1.str("http://www.example.com/?q=some+search+key&fast"); 420 | u3=u1; 421 | Url u4(std::move(u1)); 422 | u2=std::move(u3); 423 | cout << u2.str() << endl; 424 | 425 | u1.str("http://bavaria.com"); 426 | cout << u1.str() << endl; 427 | assert(u1.str()=="http://bavaria.com"); 428 | 429 | u1.str("http://[::]"); 430 | cout << u1.str() << endl; 431 | assert(u1.str()=="http://[::]"); 432 | 433 | 434 | cout << "processing time: " << double(clock() - start) / CLOCKS_PER_SEC << endl; 435 | return 0; 436 | } 437 | 438 | -------------------------------------------------------------------------------- /string.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STRING_H 2 | #define STRING_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef ANDROID_PLATFORM 8 | namespace std 9 | { 10 | template < typename T > std::string to_string( const T& n ) 11 | { 12 | std::ostringstream stm ; 13 | stm << n ; 14 | return stm.str() ; 15 | } 16 | } 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /url.cpp: -------------------------------------------------------------------------------- 1 | #include "url.hpp" 2 | //#include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | 12 | 13 | namespace { 14 | 15 | static const uint8_t tbl[256] = { 16 | 0,0,0,0, 0,0,0,0, // NUL SOH STX ETX EOT ENQ ACK BEL 17 | 0,0,0,0, 0,0,0,0, // BS HT LF VT FF CR SO SI 18 | 0,0,0,0, 0,0,0,0, // DLE DC1 DC2 DC3 DC4 NAK SYN ETB 19 | 0,0,0,0, 0,0,0,0, // CAN EM SUB ESC FS GS RS US 20 | 0x00,0x01,0x00,0x00, 0x01,0x20,0x01,0x01, // SP ! " # $ % & ' 21 | 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x08, // ( ) * + , - . / 22 | 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // 0 1 2 3 4 5 6 7 23 | 0x01,0x01,0x04,0x01, 0x00,0x01,0x00,0x10, // 8 9 : ; < = > ? 24 | 0x02,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // @ A B C D E F G 25 | 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // H I J K L M N O 26 | 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // P Q R S T U V W 27 | 0x01,0x01,0x01,0x00, 0x00,0x00,0x00,0x01, // X Y Z [ \ ] ^ _ 28 | 0x00,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // ` a b c d e f g 29 | 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // h i j k l m n o 30 | 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, // p q r s t u v w 31 | 0x01,0x01,0x01,0x00, 0x00,0x00,0x01,0x00, // x y z { | } ~ DEL 32 | 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 33 | 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 34 | 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 35 | 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 36 | }; 37 | 38 | 39 | inline bool is_char(char c, std::uint8_t mask) { 40 | return (tbl[static_cast(c)]&mask) != 0; 41 | } 42 | 43 | 44 | inline bool is_chars(const char* s, const char* e, std::uint8_t mask) { 45 | while(s!=e) 46 | if (!is_char(*s++,mask)) 47 | return false; 48 | return true; 49 | } 50 | 51 | 52 | inline bool is_alpha(char c) { 53 | return (c>='A'&&c<='Z')||(c>='a'&&c<='z'); 54 | } 55 | 56 | 57 | inline bool is_num(char c) { 58 | return c>='0'&&c<='9'; 59 | } 60 | 61 | 62 | inline bool is_alnum(char c) { 63 | return is_alpha(c)||is_num(c); 64 | } 65 | 66 | 67 | inline bool is_hexdigit(char c) { 68 | return is_num(c)||(c>='A'&&c<='F')||(c>='a'&&c<='f'); 69 | } 70 | 71 | 72 | inline bool is_uint(const char *&s, const char *e, uint32_t max) { 73 | if (s==e || !is_num(*s)) 74 | return false; 75 | const char *t=s; 76 | uint32_t val = *t++-'0'; 77 | if (val) 78 | while(t!=e && is_num(*t)) 79 | val=val*10+(*t++-'0'); 80 | if (val>max) 81 | return false; 82 | s=t; 83 | return true; 84 | } 85 | 86 | 87 | inline char get_hex_digit(char c) { 88 | if (c>='0'&&c<='9') 89 | return c-'0'; 90 | if (c>='A'&&c<='F') 91 | return c-'A'+10; 92 | if (c>='a'&&c<='f') 93 | return c-'a'+10; 94 | return -1; 95 | } 96 | 97 | 98 | inline void to_lower(std::string& s) { 99 | for(auto& c : s) 100 | if (c>='A' && c<='Z') 101 | c |= 0x20; 102 | } 103 | 104 | 105 | inline const char* find_first_of(const char *s, const char *e, const char *q) { 106 | for(; s!=e; ++s) 107 | for(const char *t=q; *t; ++t) 108 | if (*s==*t) 109 | return s; 110 | return e; 111 | } 112 | 113 | 114 | inline const char* find_char(const char *s, const char *e, const char c) { 115 | while (s!=e && *s!=c) 116 | ++s; 117 | return s; 118 | } 119 | 120 | 121 | inline bool is_scheme(const char *s, const char *e) 122 | { 123 | if (!s||!e||s==e||!is_alpha(*s)) 124 | return false; 125 | char c; 126 | while(++s!=e) 127 | if (!is_alnum(c=*s)&&c!='+'&&c!='-'&&c!='.') 128 | return false; 129 | return true; 130 | } 131 | 132 | 133 | inline bool is_scheme(const std::string &s) { 134 | return is_scheme(s.data(),s.data()+s.length()); 135 | } 136 | 137 | 138 | std::string normalize_scheme(const char *b, const char *e) { 139 | std::string o(b,e-b); 140 | to_lower(o); 141 | return o; 142 | } 143 | 144 | 145 | inline bool is_ipv4(const char *s, const char *e) { 146 | size_t l=e-s; 147 | if (l<7 || l>254) 148 | return false; 149 | for (const char *p=s; p!=e; ++p) 150 | if (*p!='.'&&!is_num(*p)) 151 | return false; 152 | return true; 153 | } 154 | 155 | 156 | inline bool is_ipv4(const std::string &s) { 157 | return is_ipv4(s.data(),s.data()+s.length()); 158 | } 159 | 160 | 161 | inline bool is_valid_ipv4(const char *s, const char *e) { 162 | return is_uint(s,e,255) && s!=e && *s++=='.' && 163 | is_uint(s,e,255) && s!=e && *s++=='.' && 164 | is_uint(s,e,255) && s!=e && *s++=='.' && 165 | is_uint(s,e,255) && s==e; 166 | } 167 | 168 | 169 | inline bool is_valid_ipv4(const std::string &s) { 170 | return is_valid_ipv4(s.data(),s.data()+s.length()); 171 | } 172 | 173 | 174 | inline bool is_reg_name(const char *s, const char *e) { 175 | return is_chars(s, e, 0x01); 176 | } 177 | 178 | 179 | inline bool is_reg_name(const std::string &s) { 180 | return is_reg_name(s.data(),s.data()+s.length()); 181 | } 182 | 183 | 184 | std::string normalize_reg_name(const std::string& s) { 185 | std::string o(s); 186 | to_lower(o); // see rfc 4343 187 | return o; 188 | } 189 | 190 | 191 | bool is_ipv6(const char *s, const char *e) { 192 | size_t l=e-s; 193 | if (l<2 || l>254) 194 | return false; 195 | for (const char *p=s; p!=e; ++p) 196 | if (*p!=':'&&*p!='.'&&!is_hexdigit(*p)) 197 | return false; 198 | return true; 199 | } 200 | 201 | 202 | inline bool is_ipv6(const std::string &s) { 203 | return is_ipv6(s.data(),s.data()+s.length()); 204 | } 205 | 206 | 207 | bool is_valid_ipv6(const char *s, const char *e) { 208 | if ((e-s)>39||(e-s)<2) 209 | return false; 210 | bool null_field=false; 211 | const char *b=s, *p=s; 212 | int nfields=0, ndigits=0; 213 | if (p[0]==':') { 214 | if (p[1]!=':') 215 | return false; 216 | null_field=true; 217 | b=(p+=2); 218 | if (p==e) 219 | return true; 220 | } 221 | while(p!=e) { 222 | if (*p=='.') { 223 | return ((!null_field&&nfields==6)||(null_field&&nfields<7))&&is_valid_ipv4(b, e); 224 | } else if (*p==':') { 225 | if (ndigits==0) { 226 | if (null_field) 227 | return false; 228 | null_field=true; 229 | } else { 230 | ++nfields; 231 | ndigits=0; 232 | } 233 | b=++p; 234 | } else { 235 | if ((++ndigits>4) || !is_hexdigit(*p++)) 236 | return false; 237 | } 238 | } 239 | if (ndigits>0) 240 | ++nfields; 241 | else { 242 | if (e[-1]==':') { 243 | if (e[-2]==':' && nfields<8) 244 | return true; 245 | return false; 246 | } 247 | } 248 | return (!null_field&&nfields==8)||(null_field&&nfields<8); 249 | } 250 | 251 | 252 | inline bool is_valid_ipv6(const std::string &s) { 253 | return is_valid_ipv6(s.data(),s.data()+s.length()); 254 | } 255 | 256 | 257 | std::string normalize_IPv6(const char *s, const char *e) { 258 | if (!is_ipv6(s, e)) 259 | throw Url::parse_error("IPv6 ["+std::string(s,e-s)+"] is invalid"); 260 | if ((e-s)==2 && s[0]==':' && s[1]==':') 261 | return std::string(s,e-s); 262 | 263 | // Split IPv6 at colons 264 | const size_t token_size = 10; 265 | const char *p=s, *tokens[token_size]; 266 | if (*p==':') 267 | ++p; 268 | if (e[-1]==':') 269 | --e; 270 | const char *b=p; 271 | size_t i=0; 272 | while (p!=e) { 273 | if (*p++==':') { 274 | if (i+1 >= token_size) { 275 | throw Url::parse_error("IPv6 ["+std::string(s,e-s)+"] is invalid"); 276 | } 277 | tokens[i++]=b; 278 | b=p; 279 | } 280 | } 281 | if (i<8) 282 | tokens[i++]=b; 283 | tokens[i]=p; 284 | size_t ntokens=i; 285 | 286 | // Get IPv4 address which is normalized by default 287 | const char *ipv4_b=nullptr, *ipv4_e=nullptr; 288 | if ((tokens[ntokens]-tokens[ntokens-1])>5) { 289 | ipv4_b=tokens[ntokens-1]; 290 | ipv4_e=tokens[ntokens]; 291 | --ntokens; 292 | } 293 | 294 | // Decode the fields 295 | const size_t fields_size = 8; 296 | std::uint16_t fields[fields_size]; 297 | size_t null_pos=8, null_len=0, nfields=0; 298 | for(size_t i=0; i= fields_size) { 304 | throw Url::parse_error("IPv6 ["+std::string(s,e-s)+"] is invalid"); 305 | } 306 | std::uint16_t field=get_hex_digit(*p++); 307 | while (p!=tokens[i+1] && *p!=':') 308 | field=(field<<4)|get_hex_digit(*p++); 309 | fields[nfields++]=field; 310 | } 311 | } 312 | i = nfields; 313 | nfields=(ipv4_b)?6:8; 314 | if (inull_len) { 337 | null_pos=first; 338 | null_len=i-first; 339 | } 340 | if (i==nfields) 341 | break; 342 | } 343 | if (null_len==1) { 344 | null_pos=nfields; 345 | null_len=1; 346 | } 347 | 348 | // Encode normalized IPv6 349 | std::stringstream str; 350 | if (null_pos==0) { 351 | str << std::hex << ':'; 352 | i=null_len; 353 | } else { 354 | str << std::hex << fields[0]; 355 | for (i=1; i elems; 392 | std::stringstream si(s); 393 | 394 | while(!std::getline(si, elem, '/').eof()){ 395 | if (elem=="" || elem==".") 396 | continue; 397 | if (elem=="..") { 398 | if (!elems.empty()) 399 | elems.pop_back(); 400 | continue; 401 | } 402 | elems.push_back(elem); 403 | } 404 | if (elem==".") 405 | elems.push_back(""); 406 | else if (elem=="..") { 407 | if (!elems.empty()) 408 | elems.pop_back(); 409 | } 410 | else 411 | elems.push_back(elem); 412 | 413 | std::stringstream so; 414 | if (s[0]=='/') 415 | so << '/'; 416 | if (!elems.empty()) { 417 | auto it=elems.begin(), end=elems.end(); 418 | so << *it; 419 | while(++it!=end) 420 | so << '/' << *it; 421 | } 422 | return so.str(); 423 | } 424 | 425 | 426 | std::string decode(const char *s, const char *e) { 427 | std::string o; 428 | o.reserve(e-s); 429 | while(s!=e) { 430 | char c=*s++, a, b; 431 | if (c=='%') { 432 | if (s==e || (a=get_hex_digit(*s++))<0 || s==e || (b=get_hex_digit(*s++))<0) 433 | throw Url::parse_error("Invalid percent encoding"); 434 | c=(a<<4)|b; 435 | } 436 | o.push_back(c); 437 | } 438 | return o; 439 | } 440 | 441 | 442 | std::string decode_plus(const char *s, const char *e) { 443 | std::string o; 444 | o.reserve(e-s); 445 | while(s!=e) { 446 | char c=*s++, a, b; 447 | if (c=='+') 448 | c=' '; 449 | else if (c=='%') { 450 | if (s==e || (a=get_hex_digit(*s++))<0 || s==e || (b=get_hex_digit(*s++))<0) 451 | throw Url::parse_error("Invalid percent encoding"); 452 | c=(a<<4)|b; 453 | } 454 | o.push_back(c); 455 | } 456 | return o; 457 | } 458 | 459 | 460 | class encode { 461 | public: 462 | encode(const std::string& s, std::uint8_t mask) : m_s(s), m_mask(mask) {} 463 | private: 464 | const std::string& m_s; 465 | std::uint8_t m_mask; 466 | friend std::ostream& operator<< (std::ostream& o, const encode& e) { 467 | for (const char c:e.m_s) 468 | if (is_char(c,e.m_mask)) 469 | o<>4]<<"0123456789ABCDEF"[((uint8_t)c)&0xF]; 472 | return o; 473 | } 474 | }; 475 | 476 | 477 | class encode_query_key { 478 | public: 479 | encode_query_key(const std::string& s, std::uint8_t mask) : m_s(s), m_mask(mask) {} 480 | private: 481 | const std::string& m_s; 482 | std::uint8_t m_mask; 483 | friend std::ostream& operator<< (std::ostream& o, const encode_query_key& e) { 484 | for (const char c:e.m_s) 485 | if (c==' ') 486 | o<<'+'; 487 | else if (c=='+') 488 | o<<"%2B"; 489 | else if (c=='=') 490 | o<<"%3D"; 491 | else if (c=='&') 492 | o<<"%26"; 493 | else if (c==';') 494 | o<<"%3B"; 495 | else if (is_char(c,e.m_mask)) 496 | o<>4]<<"0123456789ABCDEF"[((uint8_t)c)&0xF]; 499 | return o; 500 | } 501 | }; 502 | 503 | 504 | class encode_query_val { 505 | public: 506 | encode_query_val(const std::string& s, std::uint8_t mask) : m_s(s), m_mask(mask) {} 507 | private: 508 | const std::string& m_s; 509 | std::uint8_t m_mask; 510 | friend std::ostream& operator<< (std::ostream& o, const encode_query_val& e) { 511 | for (const char c:e.m_s) 512 | if (c==' ') 513 | o<<'+'; 514 | else if (c=='+') 515 | o<<"%2B"; 516 | else if (c=='&') 517 | o<<"%26"; 518 | else if (c==';') 519 | o<<"%3B"; 520 | else if (is_char(c,e.m_mask)) 521 | o<>4]<<"0123456789ABCDEF"[((uint8_t)c)&0xF]; 524 | return o; 525 | } 526 | }; 527 | 528 | 529 | 530 | } // end of anonymous namnespace 531 | // --------------------------------------------------------------------- 532 | 533 | 534 | // Copy assignment 535 | void Url::assign(const Url &url) { 536 | m_parse=url.m_parse; 537 | m_built=url.m_built; 538 | if (m_parse) { 539 | m_scheme=url.m_scheme; 540 | m_user=url.m_user; 541 | m_host=url.m_host; 542 | m_ip_v=url.m_ip_v; 543 | m_port=url.m_port; 544 | m_path=url.m_path; 545 | m_query=url.m_query; 546 | m_fragment=url.m_fragment; 547 | } 548 | if (!m_parse || m_built) 549 | m_url=url.m_url; 550 | } 551 | 552 | 553 | // Move assignment 554 | void Url::assign(Url&& url) { 555 | m_parse=url.m_parse; 556 | m_built=url.m_built; 557 | if (m_parse) { 558 | m_scheme=std::move(url.m_scheme); 559 | m_user=std::move(url.m_user); 560 | m_host=std::move(url.m_host); 561 | m_ip_v=std::move(url.m_ip_v); 562 | m_port=std::move(url.m_port); 563 | m_path=std::move(url.m_path); 564 | m_query=std::move(url.m_query); 565 | m_fragment=std::move(url.m_fragment); 566 | } 567 | if (!m_parse || m_built) 568 | m_url=std::move(url.m_url); 569 | } 570 | 571 | 572 | Url &Url::scheme(const std::string& s) { 573 | if (!is_scheme(s)) 574 | throw Url::parse_error("Invalid scheme '"+s+"'"); 575 | lazy_parse(); 576 | std::string o(s); 577 | to_lower(o); 578 | if (o!=m_scheme) { 579 | m_scheme=o; 580 | m_built=false; 581 | if ((m_scheme=="http" && m_port=="80") || (m_scheme=="https" && m_port=="443")) 582 | m_port=""; 583 | } 584 | return *this; 585 | } 586 | 587 | 588 | Url &Url::user_info(const std::string& s) { 589 | if (s.length()>256) 590 | throw Url::parse_error("User info is longer than 256 characters '"+s+"'"); 591 | lazy_parse(); 592 | if (m_user!=s) { 593 | m_user=s; 594 | m_built=false; 595 | } 596 | return *this; 597 | } 598 | 599 | 600 | Url &Url::host(const std::string& h, std::uint8_t ip_v) { 601 | if (h.length()>253) 602 | throw Url::parse_error("Host is longer than 253 characters '"+h+"'"); 603 | lazy_parse(); 604 | std::string o; 605 | if (h.empty()) 606 | ip_v=-1; 607 | else if (is_ipv4(h)) { 608 | if (!is_valid_ipv4(h)) 609 | throw Url::parse_error("Invalid IPv4 address '"+h+"'"); 610 | ip_v=4; 611 | o=h; 612 | } else if(ip_v!=0&&ip_v!=4&&ip_v!=6) { 613 | if (!is_ipv6(h)) { 614 | throw Url::parse_error("Invalid IPvFuture address '"+h+"'"); 615 | } 616 | o=h; 617 | } else if (is_ipv6(h)) { 618 | if (!is_valid_ipv6(h)) 619 | throw Url::parse_error("Invalid IPv6 address '"+h+"'"); 620 | ip_v=6; 621 | o=normalize_IPv6(h); 622 | } else if (is_reg_name(h)) { 623 | ip_v=0; 624 | o=normalize_reg_name(h); 625 | } else 626 | throw Url::parse_error("Invalid host '"+h+"'"); 627 | if (m_host!=o||m_ip_v!=ip_v) { 628 | m_host=o; 629 | m_ip_v=ip_v; 630 | m_built=false; 631 | } 632 | return *this; 633 | } 634 | 635 | 636 | Url &Url::port(const std::string& p) { 637 | if (!is_port(p)) 638 | throw Url::parse_error("Invalid port '"+p+"'"); 639 | lazy_parse(); 640 | std::string o(p); 641 | if ((m_scheme=="http" && o=="80") || (m_scheme=="https" && o=="443")) 642 | o=""; 643 | if (m_port!=o) { 644 | m_port=o; 645 | m_built=false; 646 | } 647 | return *this; 648 | } 649 | 650 | 651 | Url &Url::path(const std::string& p) { 652 | if (p.length()>8000) 653 | throw Url::parse_error("Path is longer than 8000 characters '"+p+"'"); 654 | lazy_parse(); 655 | std::string o(normalize_path(p)); 656 | if (m_path!=o) { 657 | m_path=o; 658 | m_built=false; 659 | } 660 | return *this; 661 | } 662 | 663 | 664 | Url &Url::fragment(const std::string& f) { 665 | if (f.length()>256) 666 | throw Url::parse_error("Fragment is longer than 256 characters '"+f+"'"); 667 | lazy_parse(); 668 | if (m_fragment!=f) { 669 | m_fragment=f; 670 | m_built=false; 671 | } 672 | return *this; 673 | } 674 | 675 | 676 | Url &Url::clear() { 677 | m_url.clear(); 678 | m_scheme.clear(); 679 | m_user.clear(); 680 | m_host.clear(); 681 | m_port.clear(); 682 | m_path.clear(); 683 | m_query.clear(); 684 | m_fragment.clear(); 685 | m_ip_v=-1; 686 | m_built=true; 687 | m_parse=true; 688 | return *this; 689 | } 690 | 691 | 692 | void Url::parse_url() const { 693 | if (m_url.empty()) { 694 | const_cast(this)->clear(); 695 | m_parse=m_built=true; 696 | return; 697 | } 698 | if (m_url.length()>8000) 699 | throw Url::parse_error("URI is longer than 8000 characters"); 700 | 701 | const char *s=m_url.data(), *e=s+m_url.length(); 702 | std::int8_t ip_v=-1; 703 | const char *scheme_b, *scheme_e, *user_b, *user_e, *host_b, *host_e, 704 | *port_b, *port_e, *path_b, *path_e, *query_b, *query_e, 705 | *fragment_b, *fragment_e; 706 | scheme_b=scheme_e=user_b=user_e=host_b=host_e=port_b=port_e=path_b= 707 | path_e=query_b=query_e=fragment_b=fragment_e=nullptr; 708 | 709 | const char *b=s, *p=find_first_of(b, e, ":/?#"); 710 | if (p==e) { 711 | if (!is_chars(b, p, 0x2F)) 712 | throw Url::parse_error("Path '"+std::string(b,p)+"' in '"+std::string(s,e-s)+"' is invalid"); 713 | path_b=b; 714 | path_e=e; 715 | } else { 716 | // get schema if any 717 | if (*p==':') { 718 | if (!is_scheme(b, p)) 719 | throw Url::parse_error("Scheme in '"+std::string(s,e-s)+"' is invalid"); 720 | scheme_b=b; 721 | scheme_e=p; 722 | p=find_first_of(b=p+1, e, "/?#"); 723 | } 724 | // get authority if any 725 | if (p!=e && *p=='/' && (e-b)>1 && b[0]=='/' && b[1]=='/') { 726 | const char *ea=find_first_of(b+=2, e, "/?#"); // locate end of authority 727 | p=find_char(b, ea, '@'); 728 | // get user info if any 729 | if (p!=ea) { 730 | if (!is_chars(b, p, 0x25)) 731 | throw Url::parse_error("User info in '"+std::string(s,e-s)+"' is invalid"); 732 | user_b=b; 733 | user_e=p; 734 | b=p+1; 735 | } 736 | // Get IP literal if any 737 | if (*b=='[') { 738 | // locate end of IP literal 739 | p=find_char(++b, ea, ']'); 740 | if (*p!=']') 741 | throw Url::parse_error("Missing ] in '"+std::string(s,e-s)+"'"); 742 | // decode IPvFuture protocol version 743 | if (*b=='v') { 744 | if (is_hexdigit(*++b)) { 745 | ip_v=get_hex_digit(*b); 746 | if (is_hexdigit(*++b)) { 747 | ip_v=(ip_v<<8)|get_hex_digit(*b); 748 | } 749 | } 750 | if (ip_v==-1||*b++!='.'||!is_chars(b,p,0x05)) 751 | throw Url::parse_error("Host address in '"+std::string(s,e-s)+"' is invalid"); 752 | } else if (is_ipv6(b,p)) { 753 | ip_v=6; 754 | } else 755 | throw Url::parse_error("Host address in '"+std::string(s,e-s)+"' is invalid"); 756 | host_b=b; 757 | host_e=p; 758 | b=p+1; 759 | } else { 760 | p=find_char(b, ea, ':'); 761 | if (is_ipv4(b, p)) 762 | ip_v=4; 763 | else if (is_reg_name(b, p)) 764 | ip_v=0; 765 | else 766 | throw Url::parse_error("Host address in '"+std::string(s,e-s)+"' is invalid"); 767 | host_b=b; 768 | host_e=p; 769 | b=p; 770 | } 771 | //get port if any 772 | if (b!=ea&&*b==':') { 773 | if (!is_port(++b, ea)) 774 | throw Url::parse_error("Port '"+std::string(b,ea-b)+"' in '"+std::string(s,e-s)+"' is invalid"); 775 | port_b=b; 776 | port_e=ea; 777 | } 778 | b=ea; 779 | } 780 | p=find_first_of(b,e,"?#"); 781 | if (!is_chars(b, p, 0x2F)) 782 | throw Url::parse_error("Path '"+std::string(b,p)+"' in '"+std::string(s,e-s)+"' is invalid"); 783 | path_b=b; 784 | path_e=p; 785 | if (p!=e && *p=='?') { 786 | p=find_char(b=p+1,e,'#'); 787 | query_b=b; 788 | query_e=p; 789 | } 790 | if (p!=e && *p=='#') { 791 | if (!is_chars(p+1, e, 0x3F)) 792 | throw Url::parse_error("Fragment '"+std::string(p+1,e)+"' in '"+std::string(s,e-s)+"' is invalid"); 793 | fragment_b=p+1; 794 | fragment_e=e; 795 | } 796 | } 797 | std::string _scheme, _user, _host, _port, _path, _query, _fragment; 798 | Query query_v; 799 | 800 | if (scheme_b) 801 | _scheme=normalize_scheme(scheme_b, scheme_e); 802 | if (user_b) 803 | _user=decode(user_b, user_e); 804 | if (host_b) { 805 | _host=decode(host_b, host_e); 806 | if (ip_v==0) 807 | _host=normalize_reg_name(_host); 808 | else if (ip_v==6) 809 | _host=normalize_IPv6(_host); 810 | } 811 | if (port_b) 812 | _port=std::string(port_b,port_e-port_b); 813 | if (path_b) 814 | _path=normalize_path(decode(path_b, path_e)); 815 | if (query_b) { 816 | _query=std::string(query_b, query_e); 817 | p=b=query_b; 818 | while (p!=query_e) { 819 | p=find_first_of(b, query_e, "=;&"); 820 | if (!is_chars(b, p, 0x3F)) 821 | throw Url::parse_error("Query key '"+std::string(b,p)+"' in '"+std::string(s,e-s)+"' is invalid"); 822 | std::string key(decode_plus(b,p)), val; 823 | if (p!=query_e) { 824 | if (*p=='=') { 825 | p=find_first_of(b=p+1, query_e, ";&"); 826 | if (!is_chars(b, p, 0x3F)) 827 | throw Url::parse_error("Query value '"+std::string(b,p)+"' in '"+std::string(s,e-s)+"' is invalid"); 828 | val=decode_plus(b,p); 829 | } 830 | b=p+1; 831 | } 832 | query_v.emplace_back(key, val); 833 | } 834 | } 835 | if (fragment_b) 836 | _fragment=decode(fragment_b, fragment_e); 837 | 838 | m_scheme=_scheme; 839 | m_user=_user; 840 | m_host=_host; 841 | m_ip_v=ip_v; 842 | m_port=_port; 843 | m_path=_path; 844 | m_query=query_v; 845 | m_fragment=_fragment; 846 | m_parse=true; 847 | m_built=false; 848 | } 849 | 850 | 851 | void Url::build_url() const { 852 | lazy_parse(); 853 | std::stringstream url; 854 | if (!m_scheme.empty()) 855 | url<key().empty()) 889 | throw Url::build_error("First query entry has no key"); 890 | url<key(), 0x1F); 891 | if (!it->val().empty()) 892 | url<<"="<val(), 0x1F); 893 | while(++it!=end) { 894 | if (it->key().empty()) 895 | throw Url::build_error("A query entry has no key"); 896 | url<<"&"<key(), 0x1F); 897 | if (!it->val().empty()) 898 | url<<"="<val(), 0x1F); 899 | } 900 | } 901 | if (!m_fragment.empty()) 902 | url<<"#"< 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "string.hpp" 11 | 12 | class Url { 13 | public: 14 | // Exception thut may be thrown when decoding an URL or an assigning value 15 | class parse_error: public std::invalid_argument { 16 | public: 17 | parse_error(const std::string &reason) : std::invalid_argument(reason) {} 18 | }; 19 | 20 | // Exception that may be thrown when building an URL 21 | class build_error: public std::runtime_error { 22 | public: 23 | build_error(const std::string &reason) : std::runtime_error(reason) {} 24 | }; 25 | 26 | // Default constructor 27 | Url() : m_parse(true),m_built(true),m_ip_v(-1) {} 28 | 29 | // Copy initializer constructor 30 | Url(const Url &url) : m_ip_v(-1) {assign(url);} 31 | 32 | // Move constructor 33 | Url(Url&& url) : m_ip_v(-1) {assign(std::move(url));} 34 | 35 | // Construct Url with the given string 36 | Url(const std::string &url_str) : m_url(url_str),m_parse(false),m_built(false),m_ip_v(-1) {} 37 | 38 | // Assign the given URL string 39 | Url &operator=(const std::string &url_str) {return str(url_str);} 40 | 41 | // Assign the given Url object 42 | Url &operator=(const Url &url) {assign(url); return *this;} 43 | 44 | // Move the given Url object 45 | Url &operator=(Url&& url) {assign(std::move(url)); return *this;} 46 | 47 | // Clear the Url object 48 | Url &clear(); 49 | 50 | // Build Url if needed and return it as string 51 | std::string str() const {if(!m_built) build_url(); return m_url;} 52 | 53 | // Set the Url to the given string. All fields are overwritten 54 | Url& str(const std::string &url_str) {m_url=url_str; m_built=m_parse=false; return *this;} 55 | 56 | // Get scheme 57 | const std::string& scheme() const {lazy_parse(); return m_scheme;} 58 | 59 | // Set scheme 60 | Url &scheme(const std::string& s); 61 | 62 | // Get user info 63 | const std::string& user_info() const {lazy_parse(); return m_user;} 64 | 65 | // Set user info 66 | Url &user_info(const std::string& s); 67 | 68 | // Get host 69 | const std::string& host() const {lazy_parse(); return m_host;} 70 | 71 | // Set host 72 | Url &host(const std::string& h, uint8_t ip_v=0); 73 | 74 | // Get host IP version: 0=name, 4=IPv4, 6=IPv6, -1=undefined 75 | std::int8_t ip_version() const {lazy_parse(); return m_ip_v;} 76 | 77 | // Get port 78 | const std::string& port() const {lazy_parse(); return m_port;} 79 | 80 | // Set Port given as string 81 | Url &port(const std::string& str); 82 | 83 | // Set port given as a 16bit unsigned integer 84 | Url &port(std::uint16_t num) {return port(std::to_string(num));} 85 | 86 | // Get path 87 | const std::string& path() const {lazy_parse(); return m_path;} 88 | 89 | // Set path 90 | Url &path(const std::string& str); 91 | 92 | class KeyVal { 93 | public: 94 | // Default constructor 95 | KeyVal() {} 96 | 97 | // Construct with provided Key and Value strings 98 | KeyVal(const std::string &key, const std::string &val) : m_key(key),m_val(val) {} 99 | 100 | // Construct with provided Key string, val will be empty 101 | KeyVal(const std::string &key) : m_key(key) {} 102 | 103 | // Equality test operator 104 | bool operator==(const KeyVal &q) const {return m_key==q.m_key&&m_val==q.m_val;} 105 | 106 | // Swap this with q 107 | void swap(KeyVal& q) {std::swap(m_key,q.m_key); std::swap(m_val,q.m_val);} 108 | 109 | // Get key 110 | const std::string& key() const {return m_key;} 111 | 112 | // Set key 113 | void key(const std::string &k) {m_key=k;} 114 | 115 | // Get value 116 | const std::string& val() const {return m_val;} 117 | 118 | // Set value 119 | void val(const std::string &v) {m_val=v;} 120 | 121 | // Output key value pair 122 | friend std::ostream& operator<<(std::ostream &o, const KeyVal &kv) 123 | {o<<" "; return o;} 124 | 125 | private: 126 | std::string m_key; 127 | std::string m_val; 128 | }; 129 | 130 | // Define Query as vector of Key Value pairs 131 | typedef std::vector Query; 132 | 133 | // Get a reference to the query vector for read only access 134 | const Query& query() const {lazy_parse(); return m_query;} 135 | 136 | // Get a reference to a specific Key Value pair in the query vector for read only access 137 | const KeyVal& query(size_t i) const { 138 | lazy_parse(); 139 | if (i>=m_query.size()) 140 | throw std::out_of_range("Invalid Url query index ("+std::to_string(i)+")"); 141 | return m_query[i]; 142 | } 143 | 144 | // Get a reference to the query vector for a writable access 145 | Query& set_query() {lazy_parse(); m_built=false; return m_query;} 146 | 147 | // Get a reference to specific Key Value pair in the query vector for a writable access 148 | KeyVal& set_query(size_t i) { 149 | lazy_parse(); 150 | if (i>=m_query.size()) 151 | throw std::out_of_range("Invalid Url query index ("+std::to_string(i)+")"); 152 | m_built=false; 153 | return m_query[i]; 154 | } 155 | 156 | // Set the query vector to the Query vector q 157 | Url &set_query(const Query &q) 158 | {lazy_parse(); if (q != m_query) {m_query=q; m_built=false;} return *this;} 159 | 160 | // Append KeyVal kv to the query 161 | Url &add_query(const KeyVal &kv) 162 | {lazy_parse(); m_built=false; m_query.push_back(kv); return *this;} 163 | 164 | // Append key val pair to the query 165 | Url &add_query(const std::string &key, const std::string &val) 166 | {lazy_parse(); m_built=false; m_query.emplace_back(key,val); return *this;} 167 | 168 | // Append key with empty val to the query 169 | Url &add_query(const std::string &key) 170 | {lazy_parse(); m_built=false; m_query.emplace_back(key); return *this;} 171 | 172 | // Get the fragment 173 | const std::string& fragment() const {lazy_parse(); return m_fragment;} 174 | 175 | // Set the fragment 176 | Url &fragment(const std::string& f); 177 | 178 | // Output 179 | std::ostream& output(std::ostream &o) const; 180 | 181 | // Output strean operator 182 | friend std::ostream& operator<<(std::ostream &o, const Url &u) {return u.output(o);} 183 | 184 | private: 185 | void assign(const Url &url); 186 | void assign(Url&& url); 187 | void build_url() const; 188 | void lazy_parse() const {if (!m_parse) parse_url();} 189 | void parse_url() const; 190 | 191 | mutable std::string m_scheme; 192 | mutable std::string m_user; 193 | mutable std::string m_host; 194 | mutable std::string m_port; 195 | mutable std::string m_path; 196 | mutable Query m_query; 197 | mutable std::string m_fragment; 198 | mutable std::string m_url; 199 | mutable bool m_parse; 200 | mutable bool m_built; 201 | mutable std::int8_t m_ip_v; 202 | }; 203 | 204 | 205 | 206 | 207 | #endif // URL_HPP 208 | 209 | --------------------------------------------------------------------------------