├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── cpp ├── INIReader.cpp └── INIReader.h ├── examples ├── INIReaderExample.cpp ├── config.def ├── cpptest.sh ├── cpptest.txt ├── ini_dump.c ├── ini_example.c ├── ini_xmacros.c ├── meson.build └── test.ini ├── fuzzing ├── build.sh ├── fuzz.sh ├── inihfuzz.c └── testcases │ └── case1.ini ├── ini.c ├── ini.h ├── meson.build ├── meson_options.txt └── tests ├── bad_comment.ini ├── bad_multi.ini ├── bad_section.ini ├── baseline_alloc.txt ├── baseline_allow_no_value.txt ├── baseline_call_handler_on_new_section.txt ├── baseline_disallow_inline_comments.txt ├── baseline_handler_lineno.txt ├── baseline_heap.txt ├── baseline_heap_max_line.txt ├── baseline_heap_realloc.txt ├── baseline_heap_realloc_max_line.txt ├── baseline_heap_string.txt ├── baseline_multi.txt ├── baseline_multi_max_line.txt ├── baseline_single.txt ├── baseline_stop_on_first_error.txt ├── baseline_string.txt ├── bom.ini ├── duplicate_sections.ini ├── long_line.ini ├── long_section.ini ├── meson.build ├── multi_line.ini ├── no_value.ini ├── normal.ini ├── runtest.sh ├── unittest.bat ├── unittest.c ├── unittest.sh ├── unittest_alloc.c ├── unittest_string.c └── user_error.ini /.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensure MSVC CI tests with the same line endings that are used in the tarball 2 | /tests/baseline_*.txt eol=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: benhoyt 2 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build-linux: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Run Diff Tests 17 | run: | 18 | cd tests 19 | ./unittest.sh 20 | cd ../examples 21 | ./cpptest.sh 22 | git diff --exit-code 23 | 24 | build-meson: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: actions/setup-python@v5 30 | - uses: BSFishy/meson-build@v1.0.3 31 | with: 32 | action: test 33 | meson-version: 1.4.1 34 | build-meson-msvc: 35 | runs-on: windows-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v4 39 | - uses: actions/setup-python@v5 40 | - uses: BSFishy/meson-build@v1.0.3 41 | with: 42 | action: test 43 | meson-version: 1.4.1 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | fuzzing/findings 2 | fuzzing/inihfuzz 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | The "inih" library is distributed under the New BSD license: 3 | 4 | Copyright (c) 2009, Ben Hoyt 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of Ben Hoyt nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # inih (INI Not Invented Here) 2 | 3 | [![Tests](https://github.com/benhoyt/inih/actions/workflows/tests.yml/badge.svg)](https://github.com/benhoyt/inih/actions/workflows/tests.yml) 4 | 5 | **inih (INI Not Invented Here)** is a simple [.INI file](http://en.wikipedia.org/wiki/INI_file) parser written in C. It's only a couple of pages of code, and it was designed to be _small and simple_, so it's good for embedded systems. It's also more or less compatible with Python's [ConfigParser](http://docs.python.org/library/configparser.html) style of .INI files, including RFC 822-style multi-line syntax and `name: value` entries. 6 | 7 | To use it, just give `ini_parse()` an INI file, and it will call a callback for every `name=value` pair parsed, giving you strings for the section, name, and value. It's done this way ("SAX style") because it works well on low-memory embedded systems, but also because it makes for a KISS implementation. 8 | 9 | You can also call `ini_parse_file()` to parse directly from a `FILE*` object, `ini_parse_string()` to parse data from a string, or `ini_parse_stream()` to parse using a custom fgets-style reader function for custom I/O. 10 | 11 | Download a release, browse the source, or read about [how to use inih in a DRY style](http://blog.brush.co.nz/2009/08/xmacros/) with X-Macros. 12 | 13 | 14 | ## Compile-time options ## 15 | 16 | You can control various aspects of inih using preprocessor defines: 17 | 18 | ### Syntax options ### 19 | 20 | * **Multi-line entries:** By default, inih supports multi-line entries in the style of Python's ConfigParser. To disable, add `-DINI_ALLOW_MULTILINE=0`. 21 | * **UTF-8 BOM:** By default, inih allows a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of INI files. To disable, add `-DINI_ALLOW_BOM=0`. 22 | * **Inline comments:** By default, inih allows inline comments with the `;` character. To disable, add `-DINI_ALLOW_INLINE_COMMENTS=0`. You can also specify which character(s) start an inline comment using `INI_INLINE_COMMENT_PREFIXES`. 23 | * **Start-of-line comments:** By default, inih allows both `;` and `#` to start a comment at the beginning of a line. You can override this by changing `INI_START_COMMENT_PREFIXES`. 24 | * **Allow no value:** By default, inih treats a name with no value (no `=` or `:` on the line) as an error. To allow names with no values, add `-DINI_ALLOW_NO_VALUE=1`, and inih will call your handler function with value set to NULL. 25 | 26 | ### Parsing options ### 27 | 28 | * **Stop on first error:** By default, inih keeps parsing the rest of the file after an error. To stop parsing on the first error, add `-DINI_STOP_ON_FIRST_ERROR=1`. 29 | * **Report line numbers:** By default, the `ini_handler` callback doesn't receive the line number as a parameter. If you need that, add `-DINI_HANDLER_LINENO=1`. 30 | * **Call handler on new section:** By default, inih only calls the handler on each `name=value` pair. To detect new sections (e.g., the INI file has multiple sections with the same name), add `-DINI_CALL_HANDLER_ON_NEW_SECTION=1`. Your handler function will then be called each time a new section is encountered, with `section` set to the new section name but `name` and `value` set to NULL. 31 | 32 | ### Memory options ### 33 | 34 | * **Stack vs heap:** By default, inih creates a fixed-sized line buffer on the stack. To allocate on the heap using `malloc` instead, specify `-DINI_USE_STACK=0`. 35 | * **Maximum line length:** The default maximum line length (for stack or heap) is 200 bytes. To override this, add something like `-DINI_MAX_LINE=1000`. Note that `INI_MAX_LINE` must be 3 more than the longest line (due to `\r`, `\n`, and the NUL). 36 | * **Initial malloc size:** `INI_INITIAL_ALLOC` specifies the initial malloc size when using the heap. It defaults to 200 bytes. 37 | * **Allow realloc:** By default when using the heap (`-DINI_USE_STACK=0`), inih allocates a fixed-sized buffer of `INI_INITIAL_ALLOC` bytes. To allow this to grow to `INI_MAX_LINE` bytes, doubling if needed, set `-DINI_ALLOW_REALLOC=1`. 38 | * **Custom allocator:** By default when using the heap, the standard library's `malloc`, `free`, and `realloc` functions are used; to use a custom allocator, specify `-DINI_CUSTOM_ALLOCATOR=1` (and `-DINI_USE_STACK=0`). You must define and link functions named `ini_malloc`, `ini_free`, and (if `INI_ALLOW_REALLOC` is set) `ini_realloc`, which must have the same signatures as the `stdlib.h` memory allocation functions. 39 | 40 | ## Simple example in C ## 41 | 42 | ```c 43 | #include 44 | #include 45 | #include 46 | #include "../ini.h" 47 | 48 | typedef struct 49 | { 50 | int version; 51 | const char* name; 52 | const char* email; 53 | } configuration; 54 | 55 | static int handler(void* user, const char* section, const char* name, 56 | const char* value) 57 | { 58 | configuration* pconfig = (configuration*)user; 59 | 60 | #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 61 | if (MATCH("protocol", "version")) { 62 | pconfig->version = atoi(value); 63 | } else if (MATCH("user", "name")) { 64 | pconfig->name = strdup(value); 65 | } else if (MATCH("user", "email")) { 66 | pconfig->email = strdup(value); 67 | } else { 68 | return 0; /* unknown section/name, error */ 69 | } 70 | return 1; 71 | } 72 | 73 | int main(int argc, char* argv[]) 74 | { 75 | configuration config; 76 | 77 | if (ini_parse("test.ini", handler, &config) < 0) { 78 | printf("Can't load 'test.ini'\n"); 79 | return 1; 80 | } 81 | printf("Config loaded from 'test.ini': version=%d, name=%s, email=%s\n", 82 | config.version, config.name, config.email); 83 | return 0; 84 | } 85 | ``` 86 | 87 | 88 | ## C++ example ## 89 | 90 | If you're into C++ and the STL, there is also an easy-to-use [INIReader class](https://github.com/benhoyt/inih/blob/master/cpp/INIReader.h) that stores values in a `map` and lets you `Get()` them: 91 | 92 | ```cpp 93 | #include 94 | #include "INIReader.h" 95 | 96 | int main() 97 | { 98 | INIReader reader("../examples/test.ini"); 99 | 100 | if (reader.ParseError() < 0) { 101 | std::cout << "Can't load 'test.ini'\n"; 102 | return 1; 103 | } 104 | std::cout << "Config loaded from 'test.ini': version=" 105 | << reader.GetInteger("protocol", "version", -1) << ", name=" 106 | << reader.Get("user", "name", "UNKNOWN") << ", email=" 107 | << reader.Get("user", "email", "UNKNOWN") << ", pi=" 108 | << reader.GetReal("user", "pi", -1) << ", active=" 109 | << reader.GetBoolean("user", "active", true) << "\n"; 110 | return 0; 111 | } 112 | ``` 113 | 114 | This simple C++ API works fine, but it's not very fully-fledged. I'm not planning to work more on the C++ API at the moment, so if you want a bit more power (for example `GetSections()` and `GetFields()` functions), see these forks: 115 | 116 | * https://github.com/Blandinium/inih 117 | * https://github.com/OSSystems/inih 118 | 119 | 120 | ## Differences from ConfigParser ## 121 | 122 | Some differences between inih and Python's [ConfigParser](http://docs.python.org/library/configparser.html) standard library module: 123 | 124 | * INI name=value pairs given above any section headers are treated as valid items with no section (section name is an empty string). In ConfigParser having no section is an error. 125 | * Line continuations are handled with leading whitespace on continued lines (like ConfigParser). However, instead of concatenating continued lines together, they are treated as separate values for the same key (unlike ConfigParser). 126 | 127 | 128 | ## Platform-specific notes ## 129 | 130 | * Windows/Win32 uses UTF-16 filenames natively, so to handle Unicode paths you need to call `_wfopen()` to open a file and then `ini_parse_file()` to parse it; inih does not include `wchar_t` or Unicode handling. 131 | 132 | ## Meson notes ## 133 | 134 | * The `meson.build` file is not required to use or compile inih, its main purpose is for distributions. 135 | * By default Meson is set up for distro installation, but this behavior can be configured for embedded use cases: 136 | * with `-Ddefault_library=static` static libraries are built. 137 | * with `-Ddistro_install=false` libraries, headers and pkg-config files won't be installed. 138 | * with `-Dwith_INIReader=false` you can disable building the C++ library. 139 | * All compile-time options are implemented in Meson as well, you can take a look at [meson_options.txt](https://github.com/benhoyt/inih/blob/master/meson_options.txt) for their definition. These won't work if `distro_install` is set to `true`. 140 | * If you want to use inih for programs which may be shipped in a distro, consider linking against the shared libraries. The pkg-config entries are `inih` and `INIReader`. 141 | * In case you use inih as a Meson subproject, you can use the `inih_dep` and `INIReader_dep` dependency variables. You might want to set `default_library=static` and `distro_install=false` for the subproject. An official Wrap is provided on [WrapDB](https://wrapdb.mesonbuild.com/inih). 142 | * For packagers: if you want to tag the version in the pkg-config file, you will need to do this downstream. Add `version : '',` after the `license` tag in the `project()` function and `version : meson.project_version(),` after the `soversion` tag in both `library()` functions. 143 | 144 | ## Using inih with tipi.build 145 | 146 | `inih` can be easily used in [tipi.build](https://tipi.build) projects simply by adding the following entry to your `.tipi/deps` (replace `r56` with the latest version tag): 147 | 148 | ```json 149 | { 150 | "benhoyt/inih": { "@": "r56" } 151 | } 152 | ``` 153 | 154 | The required include path in your project is: 155 | 156 | ```c 157 | #include 158 | ``` 159 | 160 | ## Building from vcpkg ## 161 | 162 | You can build and install inih using [vcpkg](https://github.com/microsoft/vcpkg/) dependency manager: 163 | 164 | git clone https://github.com/Microsoft/vcpkg.git 165 | cd vcpkg 166 | ./bootstrap-vcpkg.sh 167 | ./vcpkg integrate install 168 | ./vcpkg install inih 169 | 170 | The inih port in vcpkg is kept up to date by microsoft team members and community contributors. 171 | If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. 172 | 173 | ## Related links ## 174 | 175 | * [Conan package for inih](https://github.com/mohamedghita/conan-inih) (Conan is a C/C++ package manager) 176 | -------------------------------------------------------------------------------- /cpp/INIReader.cpp: -------------------------------------------------------------------------------- 1 | // Read an INI file into easy-to-access name/value pairs. 2 | 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | 5 | // Copyright (C) 2009-2020, Ben Hoyt 6 | 7 | // inih and INIReader are released under the New BSD license (see LICENSE.txt). 8 | // Go to the project home page for more info: 9 | // 10 | // https://github.com/benhoyt/inih 11 | 12 | #include 13 | #include 14 | #include 15 | #include "../ini.h" 16 | #include "INIReader.h" 17 | 18 | using std::string; 19 | 20 | INIReader::INIReader(const string& filename) 21 | { 22 | _error = ini_parse(filename.c_str(), ValueHandler, this); 23 | } 24 | 25 | INIReader::INIReader(const char *buffer, size_t buffer_size) 26 | { 27 | string content(buffer, buffer_size); 28 | _error = ini_parse_string(content.c_str(), ValueHandler, this); 29 | } 30 | 31 | int INIReader::ParseError() const 32 | { 33 | return _error; 34 | } 35 | 36 | string INIReader::Get(const string& section, const string& name, const string& default_value) const 37 | { 38 | string key = MakeKey(section, name); 39 | // Use _values.find() here instead of _values.at() to support pre C++11 compilers 40 | return _values.count(key) ? _values.find(key)->second : default_value; 41 | } 42 | 43 | string INIReader::GetString(const string& section, const string& name, const string& default_value) const 44 | { 45 | const string str = Get(section, name, ""); 46 | return str.empty() ? default_value : str; 47 | } 48 | 49 | long INIReader::GetInteger(const string& section, const string& name, long default_value) const 50 | { 51 | string valstr = Get(section, name, ""); 52 | const char* value = valstr.c_str(); 53 | char* end; 54 | // This parses "1234" (decimal) and also "0x4D2" (hex) 55 | long n = strtol(value, &end, 0); 56 | return end > value ? n : default_value; 57 | } 58 | 59 | INI_API int64_t INIReader::GetInteger64(const string& section, const string& name, int64_t default_value) const 60 | { 61 | string valstr = Get(section, name, ""); 62 | const char* value = valstr.c_str(); 63 | char* end; 64 | // This parses "1234" (decimal) and also "0x4D2" (hex) 65 | int64_t n = strtoll(value, &end, 0); 66 | return end > value ? n : default_value; 67 | } 68 | 69 | unsigned long INIReader::GetUnsigned(const string& section, const string& name, unsigned long default_value) const 70 | { 71 | string valstr = Get(section, name, ""); 72 | const char* value = valstr.c_str(); 73 | char* end; 74 | // This parses "1234" (decimal) and also "0x4D2" (hex) 75 | unsigned long n = strtoul(value, &end, 0); 76 | return end > value ? n : default_value; 77 | } 78 | 79 | INI_API uint64_t INIReader::GetUnsigned64(const string& section, const string& name, uint64_t default_value) const 80 | { 81 | string valstr = Get(section, name, ""); 82 | const char* value = valstr.c_str(); 83 | char* end; 84 | // This parses "1234" (decimal) and also "0x4D2" (hex) 85 | uint64_t n = strtoull(value, &end, 0); 86 | return end > value ? n : default_value; 87 | } 88 | 89 | double INIReader::GetReal(const string& section, const string& name, double default_value) const 90 | { 91 | string valstr = Get(section, name, ""); 92 | const char* value = valstr.c_str(); 93 | char* end; 94 | double n = strtod(value, &end); 95 | return end > value ? n : default_value; 96 | } 97 | 98 | bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const 99 | { 100 | string valstr = Get(section, name, ""); 101 | // Convert to lower case to make string comparisons case-insensitive 102 | std::transform(valstr.begin(), valstr.end(), valstr.begin(), 103 | [](const unsigned char& ch) { return static_cast(::tolower(ch)); }); 104 | if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") 105 | return true; 106 | else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") 107 | return false; 108 | else 109 | return default_value; 110 | } 111 | 112 | std::vector INIReader::Sections() const 113 | { 114 | std::set sectionSet; 115 | for (std::map::const_iterator it = _values.begin(); it != _values.end(); ++it) { 116 | size_t pos = it->first.find('='); 117 | if (pos != string::npos) { 118 | sectionSet.insert(it->first.substr(0, pos)); 119 | } 120 | } 121 | return std::vector(sectionSet.begin(), sectionSet.end()); 122 | } 123 | 124 | std::vector INIReader::Keys(const string& section) const 125 | { 126 | std::vector keys; 127 | string keyPrefix = MakeKey(section, ""); 128 | for (std::map::const_iterator it = _values.begin(); it != _values.end(); ++it) { 129 | if (it->first.compare(0, keyPrefix.length(), keyPrefix) == 0) { 130 | keys.push_back(it->first.substr(keyPrefix.length())); 131 | } 132 | } 133 | return keys; 134 | } 135 | 136 | bool INIReader::HasSection(const string& section) const 137 | { 138 | const string key = MakeKey(section, ""); 139 | std::map::const_iterator pos = _values.lower_bound(key); 140 | if (pos == _values.end()) 141 | return false; 142 | // Does the key at the lower_bound pos start with "section"? 143 | return pos->first.compare(0, key.length(), key) == 0; 144 | } 145 | 146 | bool INIReader::HasValue(const string& section, const string& name) const 147 | { 148 | string key = MakeKey(section, name); 149 | return _values.count(key); 150 | } 151 | 152 | string INIReader::MakeKey(const string& section, const string& name) 153 | { 154 | string key = section + "=" + name; 155 | // Convert to lower case to make section/name lookups case-insensitive 156 | std::transform(key.begin(), key.end(), key.begin(), 157 | [](const unsigned char& ch) { return static_cast(::tolower(ch)); }); 158 | return key; 159 | } 160 | 161 | int INIReader::ValueHandler(void* user, const char* section, const char* name, 162 | const char* value) 163 | { 164 | if (!name) // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled 165 | return 1; 166 | INIReader* reader = static_cast(user); 167 | string key = MakeKey(section, name); 168 | if (reader->_values[key].size() > 0) 169 | reader->_values[key] += "\n"; 170 | reader->_values[key] += value ? value : ""; 171 | return 1; 172 | } 173 | -------------------------------------------------------------------------------- /cpp/INIReader.h: -------------------------------------------------------------------------------- 1 | // Read an INI file into easy-to-access name/value pairs. 2 | 3 | // SPDX-License-Identifier: BSD-3-Clause 4 | 5 | // Copyright (C) 2009-2020, Ben Hoyt 6 | 7 | // inih and INIReader are released under the New BSD license (see LICENSE.txt). 8 | // Go to the project home page for more info: 9 | // 10 | // https://github.com/benhoyt/inih 11 | 12 | #ifndef INIREADER_H 13 | #define INIREADER_H 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | // Visibility symbols, required for Windows DLLs 22 | #ifndef INI_API 23 | #if defined _WIN32 || defined __CYGWIN__ 24 | # ifdef INI_SHARED_LIB 25 | # ifdef INI_SHARED_LIB_BUILDING 26 | # define INI_API __declspec(dllexport) 27 | # else 28 | # define INI_API __declspec(dllimport) 29 | # endif 30 | # else 31 | # define INI_API 32 | # endif 33 | #else 34 | # if defined(__GNUC__) && __GNUC__ >= 4 35 | # define INI_API __attribute__ ((visibility ("default"))) 36 | # else 37 | # define INI_API 38 | # endif 39 | #endif 40 | #endif 41 | 42 | // Read an INI file into easy-to-access name/value pairs. (Note that I've gone 43 | // for simplicity here rather than speed, but it should be pretty decent.) 44 | class INIReader 45 | { 46 | public: 47 | // Construct INIReader and parse given filename. See ini.h for more info 48 | // about the parsing. 49 | INI_API explicit INIReader(const std::string& filename); 50 | 51 | // Construct INIReader and parse given buffer. See ini.h for more info 52 | // about the parsing. 53 | INI_API explicit INIReader(const char *buffer, size_t buffer_size); 54 | 55 | // Return the result of ini_parse(), i.e., 0 on success, line number of 56 | // first error on parse error, or -1 on file open error. 57 | INI_API int ParseError() const; 58 | 59 | // Get a string value from INI file, returning default_value if not found. 60 | INI_API std::string Get(const std::string& section, const std::string& name, 61 | const std::string& default_value) const; 62 | 63 | // Get a string value from INI file, returning default_value if not found, 64 | // empty, or contains only whitespace. 65 | INI_API std::string GetString(const std::string& section, const std::string& name, 66 | const std::string& default_value) const; 67 | 68 | // Get an integer (long) value from INI file, returning default_value if 69 | // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). 70 | INI_API long GetInteger(const std::string& section, const std::string& name, long default_value) const; 71 | 72 | // Get a 64-bit integer (int64_t) value from INI file, returning default_value if 73 | // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). 74 | INI_API int64_t GetInteger64(const std::string& section, const std::string& name, int64_t default_value) const; 75 | 76 | // Get an unsigned integer (unsigned long) value from INI file, returning default_value if 77 | // not found or not a valid unsigned integer (decimal "1234", or hex "0x4d2"). 78 | INI_API unsigned long GetUnsigned(const std::string& section, const std::string& name, unsigned long default_value) const; 79 | 80 | // Get an unsigned 64-bit integer (uint64_t) value from INI file, returning default_value if 81 | // not found or not a valid unsigned integer (decimal "1234", or hex "0x4d2"). 82 | INI_API uint64_t GetUnsigned64(const std::string& section, const std::string& name, uint64_t default_value) const; 83 | 84 | // Get a real (floating point double) value from INI file, returning 85 | // default_value if not found or not a valid floating point value 86 | // according to strtod(). 87 | INI_API double GetReal(const std::string& section, const std::string& name, double default_value) const; 88 | 89 | // Get a boolean value from INI file, returning default_value if not found or if 90 | // not a valid true/false value. Valid true values are "true", "yes", "on", "1", 91 | // and valid false values are "false", "no", "off", "0" (not case sensitive). 92 | INI_API bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const; 93 | 94 | // Return a newly-allocated vector of all section names, in alphabetical order. 95 | INI_API std::vector Sections() const; 96 | 97 | // Return a newly-allocated vector of keys in the given section, in alphabetical order. 98 | INI_API std::vector Keys(const std::string& section) const; 99 | 100 | // Return true if the given section exists (section must contain at least 101 | // one name=value pair). 102 | INI_API bool HasSection(const std::string& section) const; 103 | 104 | // Return true if a value exists with the given section and field names. 105 | INI_API bool HasValue(const std::string& section, const std::string& name) const; 106 | 107 | protected: 108 | int _error; 109 | std::map _values; 110 | static std::string MakeKey(const std::string& section, const std::string& name); 111 | static int ValueHandler(void* user, const char* section, const char* name, 112 | const char* value); 113 | }; 114 | 115 | #endif // INIREADER_H 116 | -------------------------------------------------------------------------------- /examples/INIReaderExample.cpp: -------------------------------------------------------------------------------- 1 | // Example that shows simple usage of the INIReader class 2 | 3 | #include 4 | #include "../cpp/INIReader.h" 5 | 6 | int main() 7 | { 8 | INIReader reader("../examples/test.ini"); 9 | 10 | if (reader.ParseError() < 0) { 11 | std::cout << "Can't load 'test.ini'\n"; 12 | return 1; 13 | } 14 | std::cout << "Config loaded from 'test.ini': version=" 15 | << reader.GetInteger("protocol", "version", -1) << ", unsigned version=" 16 | << reader.GetUnsigned("protocol", "version", -1) << ", trillion=" 17 | << reader.GetInteger64("user", "trillion", -1) << ", unsigned trillion=" 18 | << reader.GetUnsigned64("user", "trillion", -1) << ", name=" 19 | << reader.Get("user", "name", "UNKNOWN") << ", email=" 20 | << reader.Get("user", "email", "UNKNOWN") << ", pi=" 21 | << reader.GetReal("user", "pi", -1) << ", active=" 22 | << reader.GetBoolean("user", "active", true) << "\n"; 23 | std::cout << "Has values: user.name=" << reader.HasValue("user", "name") 24 | << ", user.nose=" << reader.HasValue("user", "nose") << "\n"; 25 | std::cout << "Has sections: user=" << reader.HasSection("user") 26 | << ", fizz=" << reader.HasSection("fizz") << "\n"; 27 | 28 | std::cout << "Sections:\n"; 29 | std::vector sections = reader.Sections(); 30 | for (std::vector::const_iterator it = sections.begin(); it != sections.end(); ++it) { 31 | std::cout << "- " << *it << "\n"; 32 | } 33 | 34 | for (std::vector::const_iterator it = sections.begin(); it != sections.end(); ++it) { 35 | std::cout << "Keys in section [" << *it << "]:\n"; 36 | std::vector keys = reader.Keys(*it); 37 | for (std::vector::const_iterator kit = keys.begin(); kit != keys.end(); ++kit) { 38 | std::cout << "- " << *kit << "\n"; 39 | } 40 | } 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /examples/config.def: -------------------------------------------------------------------------------- 1 | // CFG(section, name, default) 2 | 3 | CFG(protocol, version, "0") 4 | 5 | CFG(user, name, "Fatty Lumpkin") 6 | CFG(user, email, "fatty@lumpkin.com") 7 | 8 | #undef CFG 9 | -------------------------------------------------------------------------------- /examples/cpptest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | g++ -Wall INIReaderExample.cpp ../cpp/INIReader.cpp ../ini.c -o INIReaderExample 4 | ./INIReaderExample > cpptest.txt 5 | rm INIReaderExample 6 | -------------------------------------------------------------------------------- /examples/cpptest.txt: -------------------------------------------------------------------------------- 1 | Config loaded from 'test.ini': version=6, unsigned version=6, trillion=1000000000000, unsigned trillion=1000000000000, name=Bob Smith, email=bob@smith.com, pi=3.14159, active=1 2 | Has values: user.name=1, user.nose=0 3 | Has sections: user=1, fizz=0 4 | Sections: 5 | - protocol 6 | - user 7 | Keys in section [protocol]: 8 | - version 9 | Keys in section [user]: 10 | - active 11 | - email 12 | - name 13 | - pi 14 | - trillion 15 | -------------------------------------------------------------------------------- /examples/ini_dump.c: -------------------------------------------------------------------------------- 1 | /* ini.h example that simply dumps an INI file without comments */ 2 | 3 | #include 4 | #include 5 | #include "../ini.h" 6 | 7 | static int dumper(void* user, const char* section, const char* name, 8 | const char* value) 9 | { 10 | static char prev_section[50] = ""; 11 | 12 | if (strcmp(section, prev_section)) { 13 | printf("%s[%s]\n", (prev_section[0] ? "\n" : ""), section); 14 | strncpy(prev_section, section, sizeof(prev_section)); 15 | prev_section[sizeof(prev_section) - 1] = '\0'; 16 | } 17 | printf("%s = %s\n", name, value); 18 | return 1; 19 | } 20 | 21 | int main(int argc, char* argv[]) 22 | { 23 | int error; 24 | 25 | if (argc <= 1) { 26 | printf("Usage: ini_dump filename.ini\n"); 27 | return 1; 28 | } 29 | 30 | error = ini_parse(argv[1], dumper, NULL); 31 | if (error < 0) { 32 | printf("Can't read '%s'!\n", argv[1]); 33 | return 2; 34 | } 35 | else if (error) { 36 | printf("Bad config file (first error on line %d)!\n", error); 37 | return 3; 38 | } 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /examples/ini_example.c: -------------------------------------------------------------------------------- 1 | /* Example: parse a simple configuration file */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../ini.h" 7 | 8 | typedef struct 9 | { 10 | int version; 11 | const char* name; 12 | const char* email; 13 | } configuration; 14 | 15 | static int handler(void* user, const char* section, const char* name, 16 | const char* value) 17 | { 18 | configuration* pconfig = (configuration*)user; 19 | 20 | #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 21 | if (MATCH("protocol", "version")) { 22 | pconfig->version = atoi(value); 23 | } else if (MATCH("user", "name")) { 24 | pconfig->name = strdup(value); 25 | } else if (MATCH("user", "email")) { 26 | pconfig->email = strdup(value); 27 | } else { 28 | return 0; /* unknown section/name, error */ 29 | } 30 | return 1; 31 | } 32 | 33 | int main(int argc, char* argv[]) 34 | { 35 | configuration config; 36 | config.version = 0; /* set defaults */ 37 | config.name = NULL; 38 | config.email = NULL; 39 | 40 | if (ini_parse("test.ini", handler, &config) < 0) { 41 | printf("Can't load 'test.ini'\n"); 42 | return 1; 43 | } 44 | printf("Config loaded from 'test.ini': version=%d, name=%s, email=%s\n", 45 | config.version, config.name, config.email); 46 | 47 | if (config.name) 48 | free((void*)config.name); 49 | if (config.email) 50 | free((void*)config.email); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /examples/ini_xmacros.c: -------------------------------------------------------------------------------- 1 | /* Parse a configuration file into a struct using X-Macros */ 2 | 3 | #include 4 | #include 5 | #include "../ini.h" 6 | 7 | /* define the config struct type */ 8 | typedef struct { 9 | #define CFG(s, n, default) char *s##_##n; 10 | #include "config.def" 11 | } config; 12 | 13 | /* create one and fill in its default values */ 14 | config Config = { 15 | #define CFG(s, n, default) default, 16 | #include "config.def" 17 | }; 18 | 19 | /* process a line of the INI file, storing valid values into config struct */ 20 | int handler(void *user, const char *section, const char *name, 21 | const char *value) 22 | { 23 | config *cfg = (config *)user; 24 | 25 | if (0) ; 26 | #define CFG(s, n, default) else if (strcmp(section, #s)==0 && \ 27 | strcmp(name, #n)==0) cfg->s##_##n = strdup(value); 28 | #include "config.def" 29 | 30 | return 1; 31 | } 32 | 33 | /* print all the variables in the config, one per line */ 34 | void dump_config(config *cfg) 35 | { 36 | #define CFG(s, n, default) printf("%s_%s = %s\n", #s, #n, cfg->s##_##n); 37 | #include "config.def" 38 | } 39 | 40 | int main(int argc, char* argv[]) 41 | { 42 | if (ini_parse("test.ini", handler, &Config) < 0) 43 | printf("Can't load 'test.ini', using defaults\n"); 44 | dump_config(&Config); 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /examples/meson.build: -------------------------------------------------------------------------------- 1 | runtest = files(join_paths(meson.project_source_root(), 'tests', 'runtest.sh')) 2 | 3 | tests = { 4 | 'INIReaderExample': { 'args': [] }, 5 | } 6 | 7 | foreach name, properties : tests 8 | exe = executable('unittest_' + name, src_inih, src_INIReader, 'INIReaderExample.cpp', cpp_args : ['-Wall', properties['args']]) 9 | test('test_' + name, runtest, depends : [exe], args : [files('cpptest.txt'), exe.full_path()]) 10 | endforeach 11 | -------------------------------------------------------------------------------- /examples/test.ini: -------------------------------------------------------------------------------- 1 | ; Test config file for ini_example.c and INIReaderTest.cpp 2 | 3 | [protocol] ; Protocol configuration 4 | version=6 ; IPv6 5 | 6 | [user] 7 | name = Bob Smith ; Spaces around '=' are stripped 8 | email = bob@smith.com ; And comments (like this) ignored 9 | active = true ; Test a boolean 10 | pi = 3.14159 ; Test a floating point number 11 | trillion = 1000000000000 ; Test 64-bit integers 12 | -------------------------------------------------------------------------------- /fuzzing/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ../../afl-2.52b/afl-gcc inihfuzz.c ../ini.c -o inihfuzz 3 | -------------------------------------------------------------------------------- /fuzzing/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ../../afl-2.52b/afl-fuzz -i testcases -o findings -- ./inihfuzz @@ 3 | -------------------------------------------------------------------------------- /fuzzing/inihfuzz.c: -------------------------------------------------------------------------------- 1 | /* This is a slightly tweaked copy of tests/unittest.c for fuzzing */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include "../ini.h" 7 | 8 | int User; 9 | char Prev_section[50]; 10 | 11 | int dumper(void* user, const char* section, const char* name, 12 | const char* value) 13 | { 14 | User = *((int*)user); 15 | if (!name || strcmp(section, Prev_section)) { 16 | printf("... [%s]\n", section); 17 | strncpy(Prev_section, section, sizeof(Prev_section)); 18 | Prev_section[sizeof(Prev_section) - 1] = '\0'; 19 | } 20 | if (!name) { 21 | return 1; 22 | } 23 | 24 | printf("... %s%s%s;\n", name, value ? "=" : "", value ? value : ""); 25 | 26 | if (!value) { 27 | // Happens when INI_ALLOW_NO_VALUE=1 and line has no value (no '=' or ':') 28 | return 1; 29 | } 30 | 31 | return strcmp(name, "user")==0 && strcmp(value, "parse_error")==0 ? 0 : 1; 32 | } 33 | 34 | void parse(const char* fname) { 35 | static int u = 100; 36 | int e; 37 | 38 | *Prev_section = '\0'; 39 | e = ini_parse(fname, dumper, &u); 40 | printf("%s: e=%d user=%d\n", fname, e, User); 41 | u++; 42 | } 43 | 44 | int main(int argc, char **argv) 45 | { 46 | if (argc < 2) { 47 | printf("usage: inihfuzz file.ini\n"); 48 | return 1; 49 | } 50 | parse(argv[1]); 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /fuzzing/testcases/case1.ini: -------------------------------------------------------------------------------- 1 | ; comment 2 | 3 | [foo] ; section 4 | bar=1 ; name=value 5 | 6 | [bar] 7 | name = Bob 8 | age: 42 9 | -------------------------------------------------------------------------------- /ini.c: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | SPDX-License-Identifier: BSD-3-Clause 4 | 5 | Copyright (C) 2009-2020, Ben Hoyt 6 | 7 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 8 | home page for more info: 9 | 10 | https://github.com/benhoyt/inih 11 | 12 | */ 13 | 14 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 15 | #define _CRT_SECURE_NO_WARNINGS 16 | #endif 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "ini.h" 23 | 24 | #if !INI_USE_STACK 25 | #if INI_CUSTOM_ALLOCATOR 26 | #include 27 | void* ini_malloc(size_t size); 28 | void ini_free(void* ptr); 29 | void* ini_realloc(void* ptr, size_t size); 30 | #else 31 | #include 32 | #define ini_malloc malloc 33 | #define ini_free free 34 | #define ini_realloc realloc 35 | #endif 36 | #endif 37 | 38 | #define MAX_SECTION 50 39 | #define MAX_NAME 50 40 | 41 | /* Used by ini_parse_string() to keep track of string parsing state. */ 42 | typedef struct { 43 | const char* ptr; 44 | size_t num_left; 45 | } ini_parse_string_ctx; 46 | 47 | /* Strip whitespace chars off end of given string, in place. Return s. */ 48 | static char* ini_rstrip(char* s) 49 | { 50 | char* p = s + strlen(s); 51 | while (p > s && isspace((unsigned char)(*--p))) 52 | *p = '\0'; 53 | return s; 54 | } 55 | 56 | /* Return pointer to first non-whitespace char in given string. */ 57 | static char* ini_lskip(const char* s) 58 | { 59 | while (*s && isspace((unsigned char)(*s))) 60 | s++; 61 | return (char*)s; 62 | } 63 | 64 | /* Return pointer to first char (of chars) or inline comment in given string, 65 | or pointer to NUL at end of string if neither found. Inline comment must 66 | be prefixed by a whitespace character to register as a comment. */ 67 | static char* ini_find_chars_or_comment(const char* s, const char* chars) 68 | { 69 | #if INI_ALLOW_INLINE_COMMENTS 70 | int was_space = 0; 71 | while (*s && (!chars || !strchr(chars, *s)) && 72 | !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { 73 | was_space = isspace((unsigned char)(*s)); 74 | s++; 75 | } 76 | #else 77 | while (*s && (!chars || !strchr(chars, *s))) { 78 | s++; 79 | } 80 | #endif 81 | return (char*)s; 82 | } 83 | 84 | /* Similar to strncpy, but ensures dest (size bytes) is 85 | NUL-terminated, and doesn't pad with NULs. */ 86 | static char* ini_strncpy0(char* dest, const char* src, size_t size) 87 | { 88 | /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ 89 | size_t i; 90 | for (i = 0; i < size - 1 && src[i]; i++) 91 | dest[i] = src[i]; 92 | dest[i] = '\0'; 93 | return dest; 94 | } 95 | 96 | /* See documentation in header file. */ 97 | int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 98 | void* user) 99 | { 100 | /* Uses a fair bit of stack (use heap instead if you need to) */ 101 | #if INI_USE_STACK 102 | char line[INI_MAX_LINE]; 103 | size_t max_line = INI_MAX_LINE; 104 | #else 105 | char* line; 106 | size_t max_line = INI_INITIAL_ALLOC; 107 | #endif 108 | #if INI_ALLOW_REALLOC && !INI_USE_STACK 109 | char* new_line; 110 | #endif 111 | char section[MAX_SECTION] = ""; 112 | #if INI_ALLOW_MULTILINE 113 | char prev_name[MAX_NAME] = ""; 114 | #endif 115 | 116 | size_t offset; 117 | char* start; 118 | char* end; 119 | char* name; 120 | char* value; 121 | int lineno = 0; 122 | int error = 0; 123 | char abyss[16]; /* Used to consume input when a line is too long. */ 124 | 125 | #if !INI_USE_STACK 126 | line = (char*)ini_malloc(INI_INITIAL_ALLOC); 127 | if (!line) { 128 | return -2; 129 | } 130 | #endif 131 | 132 | #if INI_HANDLER_LINENO 133 | #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) 134 | #else 135 | #define HANDLER(u, s, n, v) handler(u, s, n, v) 136 | #endif 137 | 138 | /* Scan through stream line by line */ 139 | while (reader(line, (int)max_line, stream) != NULL) { 140 | offset = strlen(line); 141 | 142 | #if INI_ALLOW_REALLOC && !INI_USE_STACK 143 | while (offset == max_line - 1 && line[offset - 1] != '\n') { 144 | max_line *= 2; 145 | if (max_line > INI_MAX_LINE) 146 | max_line = INI_MAX_LINE; 147 | new_line = ini_realloc(line, max_line); 148 | if (!new_line) { 149 | ini_free(line); 150 | return -2; 151 | } 152 | line = new_line; 153 | if (reader(line + offset, (int)(max_line - offset), stream) == NULL) 154 | break; 155 | offset += strlen(line + offset); 156 | if (max_line >= INI_MAX_LINE) 157 | break; 158 | } 159 | #endif 160 | 161 | lineno++; 162 | 163 | /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */ 164 | if (offset == max_line - 1 && line[offset - 1] != '\n') { 165 | while (reader(abyss, sizeof(abyss), stream) != NULL) { 166 | if (!error) 167 | error = lineno; 168 | if (abyss[strlen(abyss) - 1] == '\n') 169 | break; 170 | } 171 | } 172 | 173 | start = line; 174 | #if INI_ALLOW_BOM 175 | if (lineno == 1 && (unsigned char)start[0] == 0xEF && 176 | (unsigned char)start[1] == 0xBB && 177 | (unsigned char)start[2] == 0xBF) { 178 | start += 3; 179 | } 180 | #endif 181 | start = ini_rstrip(ini_lskip(start)); 182 | 183 | if (strchr(INI_START_COMMENT_PREFIXES, *start)) { 184 | /* Start-of-line comment */ 185 | } 186 | #if INI_ALLOW_MULTILINE 187 | else if (*prev_name && *start && start > line) { 188 | #if INI_ALLOW_INLINE_COMMENTS 189 | end = ini_find_chars_or_comment(start, NULL); 190 | if (*end) 191 | *end = '\0'; 192 | ini_rstrip(start); 193 | #endif 194 | /* Non-blank line with leading whitespace, treat as continuation 195 | of previous name's value (as per Python configparser). */ 196 | if (!HANDLER(user, section, prev_name, start) && !error) 197 | error = lineno; 198 | } 199 | #endif 200 | else if (*start == '[') { 201 | /* A "[section]" line */ 202 | end = ini_find_chars_or_comment(start + 1, "]"); 203 | if (*end == ']') { 204 | *end = '\0'; 205 | ini_strncpy0(section, start + 1, sizeof(section)); 206 | #if INI_ALLOW_MULTILINE 207 | *prev_name = '\0'; 208 | #endif 209 | #if INI_CALL_HANDLER_ON_NEW_SECTION 210 | if (!HANDLER(user, section, NULL, NULL) && !error) 211 | error = lineno; 212 | #endif 213 | } 214 | else if (!error) { 215 | /* No ']' found on section line */ 216 | error = lineno; 217 | } 218 | } 219 | else if (*start) { 220 | /* Not a comment, must be a name[=:]value pair */ 221 | end = ini_find_chars_or_comment(start, "=:"); 222 | if (*end == '=' || *end == ':') { 223 | *end = '\0'; 224 | name = ini_rstrip(start); 225 | value = end + 1; 226 | #if INI_ALLOW_INLINE_COMMENTS 227 | end = ini_find_chars_or_comment(value, NULL); 228 | if (*end) 229 | *end = '\0'; 230 | #endif 231 | value = ini_lskip(value); 232 | ini_rstrip(value); 233 | 234 | #if INI_ALLOW_MULTILINE 235 | ini_strncpy0(prev_name, name, sizeof(prev_name)); 236 | #endif 237 | /* Valid name[=:]value pair found, call handler */ 238 | if (!HANDLER(user, section, name, value) && !error) 239 | error = lineno; 240 | } 241 | else if (!error) { 242 | /* No '=' or ':' found on name[=:]value line */ 243 | #if INI_ALLOW_NO_VALUE 244 | *end = '\0'; 245 | name = ini_rstrip(start); 246 | if (!HANDLER(user, section, name, NULL) && !error) 247 | error = lineno; 248 | #else 249 | error = lineno; 250 | #endif 251 | } 252 | } 253 | 254 | #if INI_STOP_ON_FIRST_ERROR 255 | if (error) 256 | break; 257 | #endif 258 | } 259 | 260 | #if !INI_USE_STACK 261 | ini_free(line); 262 | #endif 263 | 264 | return error; 265 | } 266 | 267 | /* See documentation in header file. */ 268 | int ini_parse_file(FILE* file, ini_handler handler, void* user) 269 | { 270 | return ini_parse_stream((ini_reader)fgets, file, handler, user); 271 | } 272 | 273 | /* See documentation in header file. */ 274 | int ini_parse(const char* filename, ini_handler handler, void* user) 275 | { 276 | FILE* file; 277 | int error; 278 | 279 | file = fopen(filename, "r"); 280 | if (!file) 281 | return -1; 282 | error = ini_parse_file(file, handler, user); 283 | fclose(file); 284 | return error; 285 | } 286 | 287 | /* An ini_reader function to read the next line from a string buffer. This 288 | is the fgets() equivalent used by ini_parse_string(). */ 289 | static char* ini_reader_string(char* str, int num, void* stream) { 290 | ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; 291 | const char* ctx_ptr = ctx->ptr; 292 | size_t ctx_num_left = ctx->num_left; 293 | char* strp = str; 294 | char c; 295 | 296 | if (ctx_num_left == 0 || num < 2) 297 | return NULL; 298 | 299 | while (num > 1 && ctx_num_left != 0) { 300 | c = *ctx_ptr++; 301 | ctx_num_left--; 302 | *strp++ = c; 303 | if (c == '\n') 304 | break; 305 | num--; 306 | } 307 | 308 | *strp = '\0'; 309 | ctx->ptr = ctx_ptr; 310 | ctx->num_left = ctx_num_left; 311 | return str; 312 | } 313 | 314 | /* See documentation in header file. */ 315 | int ini_parse_string(const char* string, ini_handler handler, void* user) { 316 | ini_parse_string_ctx ctx; 317 | 318 | ctx.ptr = string; 319 | ctx.num_left = strlen(string); 320 | return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, 321 | user); 322 | } 323 | -------------------------------------------------------------------------------- /ini.h: -------------------------------------------------------------------------------- 1 | /* inih -- simple .INI file parser 2 | 3 | SPDX-License-Identifier: BSD-3-Clause 4 | 5 | Copyright (C) 2009-2020, Ben Hoyt 6 | 7 | inih is released under the New BSD license (see LICENSE.txt). Go to the project 8 | home page for more info: 9 | 10 | https://github.com/benhoyt/inih 11 | 12 | */ 13 | 14 | #ifndef INI_H 15 | #define INI_H 16 | 17 | /* Make this header file easier to include in C++ code */ 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | 24 | /* Nonzero if ini_handler callback should accept lineno parameter. */ 25 | #ifndef INI_HANDLER_LINENO 26 | #define INI_HANDLER_LINENO 0 27 | #endif 28 | 29 | /* Visibility symbols, required for Windows DLLs */ 30 | #ifndef INI_API 31 | #if defined _WIN32 || defined __CYGWIN__ 32 | # ifdef INI_SHARED_LIB 33 | # ifdef INI_SHARED_LIB_BUILDING 34 | # define INI_API __declspec(dllexport) 35 | # else 36 | # define INI_API __declspec(dllimport) 37 | # endif 38 | # else 39 | # define INI_API 40 | # endif 41 | #else 42 | # if defined(__GNUC__) && __GNUC__ >= 4 43 | # define INI_API __attribute__ ((visibility ("default"))) 44 | # else 45 | # define INI_API 46 | # endif 47 | #endif 48 | #endif 49 | 50 | /* Typedef for prototype of handler function. 51 | 52 | Note that even though the value parameter has type "const char*", the user 53 | may cast to "char*" and modify its content, as the value is not used again 54 | after the call to ini_handler. This is not true of section and name -- 55 | those must not be modified. 56 | */ 57 | #if INI_HANDLER_LINENO 58 | typedef int (*ini_handler)(void* user, const char* section, 59 | const char* name, const char* value, 60 | int lineno); 61 | #else 62 | typedef int (*ini_handler)(void* user, const char* section, 63 | const char* name, const char* value); 64 | #endif 65 | 66 | /* Typedef for prototype of fgets-style reader function. */ 67 | typedef char* (*ini_reader)(char* str, int num, void* stream); 68 | 69 | /* Parse given INI-style file. May have [section]s, name=value pairs 70 | (whitespace stripped), and comments starting with ';' (semicolon). Section 71 | is "" if name=value pair parsed before any section heading. name:value 72 | pairs are also supported as a concession to Python's configparser. 73 | 74 | For each name=value pair parsed, call handler function with given user 75 | pointer as well as section, name, and value (data only valid for duration 76 | of handler call). Handler should return nonzero on success, zero on error. 77 | 78 | Returns 0 on success, line number of first error on parse error (doesn't 79 | stop on first error), -1 on file open error, or -2 on memory allocation 80 | error (only when INI_USE_STACK is zero). 81 | */ 82 | INI_API int ini_parse(const char* filename, ini_handler handler, void* user); 83 | 84 | /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't 85 | close the file when it's finished -- the caller must do that. */ 86 | INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); 87 | 88 | /* Same as ini_parse(), but takes an ini_reader function pointer instead of 89 | filename. Used for implementing custom or string-based I/O (see also 90 | ini_parse_string). */ 91 | INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 92 | void* user); 93 | 94 | /* Same as ini_parse(), but takes a zero-terminated string with the INI data 95 | instead of a file. Useful for parsing INI data from a network socket or 96 | already in memory. */ 97 | INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); 98 | 99 | /* Nonzero to allow multi-line value parsing, in the style of Python's 100 | configparser. If allowed, ini_parse() will call the handler with the same 101 | name for each subsequent line parsed. */ 102 | #ifndef INI_ALLOW_MULTILINE 103 | #define INI_ALLOW_MULTILINE 1 104 | #endif 105 | 106 | /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of 107 | the file. See https://github.com/benhoyt/inih/issues/21 */ 108 | #ifndef INI_ALLOW_BOM 109 | #define INI_ALLOW_BOM 1 110 | #endif 111 | 112 | /* Chars that begin a start-of-line comment. Per Python configparser, allow 113 | both ; and # comments at the start of a line by default. */ 114 | #ifndef INI_START_COMMENT_PREFIXES 115 | #define INI_START_COMMENT_PREFIXES ";#" 116 | #endif 117 | 118 | /* Nonzero to allow inline comments (with valid inline comment characters 119 | specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match 120 | Python 3.2+ configparser behaviour. */ 121 | #ifndef INI_ALLOW_INLINE_COMMENTS 122 | #define INI_ALLOW_INLINE_COMMENTS 1 123 | #endif 124 | #ifndef INI_INLINE_COMMENT_PREFIXES 125 | #define INI_INLINE_COMMENT_PREFIXES ";" 126 | #endif 127 | 128 | /* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ 129 | #ifndef INI_USE_STACK 130 | #define INI_USE_STACK 1 131 | #endif 132 | 133 | /* Maximum line length for any line in INI file (stack or heap). Note that 134 | this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ 135 | #ifndef INI_MAX_LINE 136 | #define INI_MAX_LINE 200 137 | #endif 138 | 139 | /* Nonzero to allow heap line buffer to grow via realloc(), zero for a 140 | fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is 141 | zero. */ 142 | #ifndef INI_ALLOW_REALLOC 143 | #define INI_ALLOW_REALLOC 0 144 | #endif 145 | 146 | /* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK 147 | is zero. */ 148 | #ifndef INI_INITIAL_ALLOC 149 | #define INI_INITIAL_ALLOC 200 150 | #endif 151 | 152 | /* Stop parsing on first error (default is to keep parsing). */ 153 | #ifndef INI_STOP_ON_FIRST_ERROR 154 | #define INI_STOP_ON_FIRST_ERROR 0 155 | #endif 156 | 157 | /* Nonzero to call the handler at the start of each new section (with 158 | name and value NULL). Default is to only call the handler on 159 | each name=value pair. */ 160 | #ifndef INI_CALL_HANDLER_ON_NEW_SECTION 161 | #define INI_CALL_HANDLER_ON_NEW_SECTION 0 162 | #endif 163 | 164 | /* Nonzero to allow a name without a value (no '=' or ':' on the line) and 165 | call the handler with value NULL in this case. Default is to treat 166 | no-value lines as an error. */ 167 | #ifndef INI_ALLOW_NO_VALUE 168 | #define INI_ALLOW_NO_VALUE 0 169 | #endif 170 | 171 | /* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory 172 | allocation functions (INI_USE_STACK must also be 0). These functions must 173 | have the same signatures as malloc/free/realloc and behave in a similar 174 | way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ 175 | #ifndef INI_CUSTOM_ALLOCATOR 176 | #define INI_CUSTOM_ALLOCATOR 0 177 | #endif 178 | 179 | 180 | #ifdef __cplusplus 181 | } 182 | #endif 183 | 184 | #endif /* INI_H */ 185 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('inih', 2 | ['c'], 3 | license : 'BSD-3-Clause', 4 | version : '60', 5 | default_options : ['cpp_std=c++11'], 6 | meson_version: '>=0.56.0' 7 | ) 8 | 9 | #### options #### 10 | arg_static = [] 11 | distro_install = get_option('distro_install') 12 | extra_args = [] 13 | 14 | if distro_install 15 | pkg = import('pkgconfig') 16 | else 17 | if not get_option('multi-line_entries') 18 | arg_static += ['-DINI_ALLOW_MULTILINE=0'] 19 | endif 20 | if not get_option('utf-8_bom') 21 | arg_static += ['-DINI_ALLOW_BOM=0'] 22 | endif 23 | if not get_option('inline_comments') 24 | arg_static += ['-DINI_ALLOW_INLINE_COMMENTS=0'] 25 | endif 26 | inline_comment_prefix = get_option('inline_comment_prefix') 27 | if inline_comment_prefix != ';' 28 | arg_static += ['-DINI_INLINE_COMMENT_PREFIXES="' + inline_comment_prefix + '"'] 29 | endif 30 | sol_comment_prefix = get_option('start-of-line_comment_prefix') 31 | if sol_comment_prefix != ';#' 32 | arg_static += ['-DINI_START_COMMENT_PREFIXES="' + sol_comment_prefix + '"'] 33 | endif 34 | if get_option('allow_no_value') 35 | arg_static += ['-DINI_ALLOW_NO_VALUE=1'] 36 | endif 37 | if get_option('stop_on_first_error') 38 | arg_static += ['-DINI_STOP_ON_FIRST_ERROR=1'] 39 | endif 40 | if get_option('report_line_numbers') 41 | arg_static += ['-DINI_HANDLER_LINENO=1'] 42 | endif 43 | if get_option('call_handler_on_new_section') 44 | arg_static += ['-DINI_CALL_HANDLER_ON_NEW_SECTION=1'] 45 | endif 46 | if get_option('use_heap') 47 | arg_static += ['-DINI_USE_STACK=0'] 48 | endif 49 | max_line_length = get_option('max_line_length') 50 | if max_line_length != 200 51 | arg_static += ['-DINI_MAX_LINE=' + max_line_length.to_string()] 52 | endif 53 | initial_malloc_size = get_option('initial_malloc_size') 54 | if initial_malloc_size != 200 55 | arg_static += ['-DINI_INITIAL_ALLOC=' + initial_malloc_size.to_string()] 56 | endif 57 | if get_option('allow_realloc') 58 | arg_static += ['-DINI_ALLOW_REALLOC=1'] 59 | endif 60 | endif 61 | 62 | if host_machine.system() == 'windows' 63 | lib = get_option('default_library') 64 | if lib == 'both' 65 | error('default_library=both is not supported on Windows') 66 | elif lib == 'shared' 67 | extra_args += '-DINI_SHARED_LIB' 68 | add_project_arguments('-DINI_SHARED_LIB_BUILDING', language: ['c', 'cpp']) 69 | endif 70 | endif 71 | 72 | #### inih #### 73 | inc_inih = include_directories('.') 74 | 75 | src_inih = files('ini.c') 76 | 77 | lib_inih = library('inih', 78 | [src_inih], 79 | include_directories : inc_inih, 80 | c_args : [arg_static, extra_args], 81 | install : distro_install, 82 | soversion : '0', 83 | gnu_symbol_visibility: 'hidden' 84 | ) 85 | 86 | if distro_install 87 | install_headers('ini.h') 88 | 89 | pkg.generate(lib_inih, 90 | name : 'inih', 91 | description : 'simple .INI file parser', 92 | extra_cflags : extra_args, 93 | ) 94 | endif 95 | 96 | inih_dep = declare_dependency( 97 | link_with : lib_inih, 98 | compile_args : arg_static + extra_args, 99 | include_directories : inc_inih 100 | ) 101 | 102 | if get_option('tests') 103 | subdir('tests') 104 | endif 105 | 106 | #### INIReader #### 107 | if get_option('with_INIReader') 108 | add_languages( 109 | 'cpp', 110 | native : false 111 | ) 112 | inc_INIReader = include_directories('cpp') 113 | 114 | src_INIReader = files(join_paths('cpp', 'INIReader.cpp')) 115 | 116 | lib_INIReader = library('INIReader', 117 | src_INIReader, 118 | cpp_args : extra_args, 119 | include_directories : inc_INIReader, 120 | dependencies : inih_dep, 121 | install : distro_install, 122 | soversion : '0', 123 | gnu_symbol_visibility: 'hidden' 124 | ) 125 | 126 | if distro_install 127 | install_headers('cpp/INIReader.h') 128 | 129 | pkg.generate(lib_INIReader, 130 | name : 'INIReader', 131 | description : 'simple .INI file parser for C++', 132 | extra_cflags : extra_args, 133 | ) 134 | endif 135 | 136 | INIReader_dep = declare_dependency( 137 | link_with : lib_INIReader, 138 | include_directories : inc_INIReader, 139 | compile_args : extra_args 140 | ) 141 | 142 | subdir('examples') 143 | endif 144 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('distro_install', 2 | type : 'boolean', 3 | value : true, 4 | description : 'install shared libs, headers and pkg-config entries' 5 | ) 6 | option('with_INIReader', 7 | type : 'boolean', 8 | value : true, 9 | description : 'compile and (if selected) install INIReader' 10 | ) 11 | option('multi-line_entries', 12 | type : 'boolean', 13 | value : true, 14 | description : 'support for multi-line entries in the style of Python\'s ConfigParser' 15 | ) 16 | option('utf-8_bom', 17 | type : 'boolean', 18 | value : true, 19 | description : 'allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of INI files' 20 | ) 21 | option('inline_comments', 22 | type : 'boolean', 23 | value : true, 24 | description : 'allow inline comments with the comment prefix character' 25 | ) 26 | option('inline_comment_prefix', 27 | type : 'string', 28 | value : ';', 29 | description : 'character(s) to start an inline comment (if enabled)' 30 | ) 31 | option('start-of-line_comment_prefix', 32 | type : 'string', 33 | value : ';#', 34 | description : 'character(s) to start a comment at the beginning of a line' 35 | ) 36 | option('allow_no_value', 37 | type : 'boolean', 38 | value : false, 39 | description : 'allow name with no value' 40 | ) 41 | option('stop_on_first_error', 42 | type : 'boolean', 43 | value : false, 44 | description : 'stop parsing after an error' 45 | ) 46 | option('report_line_numbers', 47 | type : 'boolean', 48 | value : false, 49 | description : 'report line number on ini_handler callback' 50 | ) 51 | option('call_handler_on_new_section', 52 | type : 'boolean', 53 | value : false, 54 | description : 'call the handler each time a new section is encountered' 55 | ) 56 | option('use_heap', 57 | type : 'boolean', 58 | value : false, 59 | description : 'allocate memory on the heap using malloc instead using a fixed-sized line buffer on the stack' 60 | ) 61 | option('max_line_length', 62 | type : 'integer', 63 | value : 200, 64 | description : 'maximum line length in bytes' 65 | ) 66 | option('initial_malloc_size', 67 | type : 'integer', 68 | value : 200, 69 | description : 'initial malloc size in bytes (when using the heap)' 70 | ) 71 | option('allow_realloc', 72 | type : 'boolean', 73 | value : false, 74 | description : 'allow initial malloc size to grow to max line length (when using the heap)' 75 | ) 76 | option('tests', 77 | type : 'boolean', 78 | value : true, 79 | description : 'build the test suite (noisy)' 80 | ) 81 | -------------------------------------------------------------------------------- /tests/bad_comment.ini: -------------------------------------------------------------------------------- 1 | This is an error 2 | -------------------------------------------------------------------------------- /tests/bad_multi.ini: -------------------------------------------------------------------------------- 1 | indented 2 | -------------------------------------------------------------------------------- /tests/bad_section.ini: -------------------------------------------------------------------------------- 1 | [section1] 2 | name1=value1 3 | [section2 4 | [section3 ; comment ] 5 | name2=value2 6 | -------------------------------------------------------------------------------- /tests/baseline_alloc.txt: -------------------------------------------------------------------------------- 1 | ini_malloc(12) 2 | ... [section] 3 | ... foo=bar; 4 | ini_realloc(24) 5 | ... bazz=buzz quxx; 6 | ini_free() 7 | basic: e=0 8 | -------------------------------------------------------------------------------- /tests/baseline_allow_no_value.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comment, needs whitespace before ';'; 15 | ... [colon_tests] 16 | ... Content-Type=text/html; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equals; 20 | ... funny2=with : colons; 21 | ... funny3=two = equals; 22 | ... funny4=two : colons; 23 | normal.ini: e=0 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... name2=value2; 27 | bad_section.ini: e=3 user=102 28 | ... This is an error; 29 | bad_comment.ini: e=0 user=103 30 | ... [section] 31 | ... a=b; 32 | ... user=parse_error; 33 | ... c=d; 34 | user_error.ini: e=3 user=104 35 | ... [section1] 36 | ... single1=abc; 37 | ... multi=this is a; 38 | ... multi=multi-line value; 39 | ... single2=xyz; 40 | ... [section2] 41 | ... multi=a; 42 | ... multi=b; 43 | ... multi=c; 44 | ... [section3] 45 | ... single=ghi; 46 | ... multi=the quick; 47 | ... multi=brown fox; 48 | ... name=bob smith; 49 | ... foo=bar; 50 | ... foo=Hi World; 51 | multi_line.ini: e=0 user=105 52 | ... indented; 53 | bad_multi.ini: e=0 user=106 54 | ... [bom_section] 55 | ... bom_name=bom_value; 56 | ... key“=value“; 57 | bom.ini: e=0 user=107 58 | ... [section1] 59 | ... single1=abc; 60 | ... single2=xyz; 61 | ... single1=def; 62 | ... single2=qrs; 63 | duplicate_sections.ini: e=0 user=108 64 | ... [section_list] 65 | ... section0; 66 | ... section1; 67 | ... [section0] 68 | ... key0=val0; 69 | ... [section1] 70 | ... key1=val1; 71 | no_value.ini: e=0 user=109 72 | ... [_123456789_123456789_123456789_123456789_12345678] 73 | ... name=value; 74 | long_section.ini: e=0 user=110 75 | ... [width = 18] 76 | ... _123456789=1234567; 77 | ... [width = 19] 78 | ... _123456789=12345678; 79 | ... [width = 20] 80 | ... _123456789=123456789; 81 | ... [2 assigns] 82 | ... _123456789=12345678name=value; 83 | ... [no trailing \n] 84 | ... _123456782=12345678; 85 | long_line.ini: e=0 user=111 86 | -------------------------------------------------------------------------------- /tests/baseline_call_handler_on_new_section.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [empty] 9 | ... [comment_test] 10 | ... test1=1;2;3; 11 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; 12 | ... test;3=345; 13 | ... test4=4#5#6; 14 | ... test7=; 15 | ... test8=; not a comment, needs whitespace before ';'; 16 | ... [colon_tests] 17 | ... Content-Type=text/html; 18 | ... foo=bar; 19 | ... adams=42; 20 | ... funny1=with = equals; 21 | ... funny2=with : colons; 22 | ... funny3=two = equals; 23 | ... funny4=two : colons; 24 | normal.ini: e=0 user=101 25 | ... [section1] 26 | ... name1=value1; 27 | ... name2=value2; 28 | bad_section.ini: e=3 user=102 29 | bad_comment.ini: e=1 user=102 30 | ... [section] 31 | ... a=b; 32 | ... user=parse_error; 33 | ... c=d; 34 | user_error.ini: e=3 user=104 35 | ... [section1] 36 | ... single1=abc; 37 | ... multi=this is a; 38 | ... multi=multi-line value; 39 | ... single2=xyz; 40 | ... [section2] 41 | ... multi=a; 42 | ... multi=b; 43 | ... multi=c; 44 | ... [section3] 45 | ... single=ghi; 46 | ... multi=the quick; 47 | ... multi=brown fox; 48 | ... name=bob smith; 49 | ... foo=bar; 50 | ... foo=Hi World; 51 | multi_line.ini: e=0 user=105 52 | bad_multi.ini: e=1 user=105 53 | ... [bom_section] 54 | ... bom_name=bom_value; 55 | ... key“=value“; 56 | bom.ini: e=0 user=107 57 | ... [section1] 58 | ... single1=abc; 59 | ... single2=xyz; 60 | ... [section1] 61 | ... single1=def; 62 | ... single2=qrs; 63 | duplicate_sections.ini: e=0 user=108 64 | ... [section_list] 65 | ... [section0] 66 | ... key0=val0; 67 | ... [section1] 68 | ... key1=val1; 69 | no_value.ini: e=2 user=109 70 | ... [_123456789_123456789_123456789_123456789_12345678] 71 | ... name=value; 72 | long_section.ini: e=0 user=110 73 | ... [width = 18] 74 | ... _123456789=1234567; 75 | ... [width = 19] 76 | ... _123456789=12345678; 77 | ... [width = 20] 78 | ... _123456789=123456789; 79 | ... [2 assigns] 80 | ... _123456789=12345678name=value; 81 | ... [no trailing \n] 82 | ... _123456782=12345678; 83 | long_line.ini: e=0 user=111 84 | -------------------------------------------------------------------------------- /tests/baseline_disallow_inline_comments.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test ; name=value comment; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3 ; only this will be a comment; 10 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; 11 | ... test;3=345 ; key should be "test;3"; 12 | ... test4=4#5#6 ; '#' only starts a comment at start of line; 13 | ... test7=; blank value, except if inline comments disabled; 14 | ... test8=; not a comment, needs whitespace before ';'; 15 | ... [colon_tests] 16 | ... Content-Type=text/html; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equals; 20 | ... funny2=with : colons; 21 | ... funny3=two = equals; 22 | ... funny4=two : colons; 23 | normal.ini: e=0 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... [section3 ; comment ] 27 | ... name2=value2; 28 | bad_section.ini: e=3 user=102 29 | bad_comment.ini: e=1 user=102 30 | ... [section] 31 | ... a=b; 32 | ... user=parse_error; 33 | ... c=d; 34 | user_error.ini: e=3 user=104 35 | ... [section1] 36 | ... single1=abc; 37 | ... multi=this is a; 38 | ... multi=multi-line value; 39 | ... single2=xyz; 40 | ... [section2] 41 | ... multi=a; 42 | ... multi=b; 43 | ... multi=c; 44 | ... [section3] 45 | ... single=ghi; 46 | ... multi=the quick; 47 | ... multi=brown fox; 48 | ... name=bob smith ; comment line 1; 49 | ... foo=bar ;c1; 50 | ... foo=Hi World ;c2; 51 | multi_line.ini: e=0 user=105 52 | bad_multi.ini: e=1 user=105 53 | ... [bom_section] 54 | ... bom_name=bom_value; 55 | ... key“=value“; 56 | bom.ini: e=0 user=107 57 | ... [section1] 58 | ... single1=abc; 59 | ... single2=xyz; 60 | ... single1=def; 61 | ... single2=qrs; 62 | duplicate_sections.ini: e=0 user=108 63 | ... [section0] 64 | ... key0=val0; 65 | ... [section1] 66 | ... key1=val1; 67 | no_value.ini: e=2 user=109 68 | ... [_123456789_123456789_123456789_123456789_12345678] 69 | ... name=value; 70 | long_section.ini: e=0 user=110 71 | ... [width = 18] 72 | ... _123456789=1234567; 73 | ... [width = 19] 74 | ... _123456789=12345678; 75 | ... [width = 20] 76 | ... _123456789=123456789; 77 | ... [2 assigns] 78 | ... _123456789=12345678name=value; 79 | ... [no trailing \n] 80 | ... _123456782=12345678; 81 | long_line.ini: e=0 user=111 82 | -------------------------------------------------------------------------------- /tests/baseline_handler_lineno.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; line 3 4 | ... two=1234; line 4 5 | ... [ section 2 ] 6 | ... happy=4; line 8 7 | ... sad=; line 9 8 | ... [comment_test] 9 | ... test1=1;2;3; line 15 10 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; line 16 11 | ... test;3=345; line 17 12 | ... test4=4#5#6; line 18 13 | ... test7=; line 21 14 | ... test8=; not a comment, needs whitespace before ';'; line 22 15 | ... [colon_tests] 16 | ... Content-Type=text/html; line 25 17 | ... foo=bar; line 26 18 | ... adams=42; line 27 19 | ... funny1=with = equals; line 28 20 | ... funny2=with : colons; line 29 21 | ... funny3=two = equals; line 30 22 | ... funny4=two : colons; line 31 23 | normal.ini: e=0 user=101 24 | ... [section1] 25 | ... name1=value1; line 2 26 | ... name2=value2; line 5 27 | bad_section.ini: e=3 user=102 28 | bad_comment.ini: e=1 user=102 29 | ... [section] 30 | ... a=b; line 2 31 | ... user=parse_error; line 3 32 | ... c=d; line 4 33 | user_error.ini: e=3 user=104 34 | ... [section1] 35 | ... single1=abc; line 2 36 | ... multi=this is a; line 3 37 | ... multi=multi-line value; line 4 38 | ... single2=xyz; line 5 39 | ... [section2] 40 | ... multi=a; line 7 41 | ... multi=b; line 8 42 | ... multi=c; line 9 43 | ... [section3] 44 | ... single=ghi; line 11 45 | ... multi=the quick; line 12 46 | ... multi=brown fox; line 13 47 | ... name=bob smith; line 14 48 | ... foo=bar; line 16 49 | ... foo=Hi World; line 17 50 | multi_line.ini: e=0 user=105 51 | bad_multi.ini: e=1 user=105 52 | ... [bom_section] 53 | ... bom_name=bom_value; line 2 54 | ... key“=value“; line 3 55 | bom.ini: e=0 user=107 56 | ... [section1] 57 | ... single1=abc; line 2 58 | ... single2=xyz; line 3 59 | ... single1=def; line 5 60 | ... single2=qrs; line 6 61 | duplicate_sections.ini: e=0 user=108 62 | ... [section0] 63 | ... key0=val0; line 6 64 | ... [section1] 65 | ... key1=val1; line 9 66 | no_value.ini: e=2 user=109 67 | ... [_123456789_123456789_123456789_123456789_12345678] 68 | ... name=value; line 3 69 | long_section.ini: e=0 user=110 70 | ... [width = 18] 71 | ... _123456789=1234567; line 7 72 | ... [width = 19] 73 | ... _123456789=12345678; line 10 74 | ... [width = 20] 75 | ... _123456789=123456789; line 13 76 | ... [2 assigns] 77 | ... _123456789=12345678name=value; line 16 78 | ... [no trailing \n] 79 | ... _123456782=12345678; line 23 80 | long_line.ini: e=0 user=111 81 | -------------------------------------------------------------------------------- /tests/baseline_heap.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comment, needs whitespace before ';'; 15 | ... [colon_tests] 16 | ... Content-Type=text/html; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equals; 20 | ... funny2=with : colons; 21 | ... funny3=two = equals; 22 | ... funny4=two : colons; 23 | normal.ini: e=0 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... name2=value2; 27 | bad_section.ini: e=3 user=102 28 | bad_comment.ini: e=1 user=102 29 | ... [section] 30 | ... a=b; 31 | ... user=parse_error; 32 | ... c=d; 33 | user_error.ini: e=3 user=104 34 | ... [section1] 35 | ... single1=abc; 36 | ... multi=this is a; 37 | ... multi=multi-line value; 38 | ... single2=xyz; 39 | ... [section2] 40 | ... multi=a; 41 | ... multi=b; 42 | ... multi=c; 43 | ... [section3] 44 | ... single=ghi; 45 | ... multi=the quick; 46 | ... multi=brown fox; 47 | ... name=bob smith; 48 | ... foo=bar; 49 | ... foo=Hi World; 50 | multi_line.ini: e=0 user=105 51 | bad_multi.ini: e=1 user=105 52 | ... [bom_section] 53 | ... bom_name=bom_value; 54 | ... key“=value“; 55 | bom.ini: e=0 user=107 56 | ... [section1] 57 | ... single1=abc; 58 | ... single2=xyz; 59 | ... single1=def; 60 | ... single2=qrs; 61 | duplicate_sections.ini: e=0 user=108 62 | ... [section0] 63 | ... key0=val0; 64 | ... [section1] 65 | ... key1=val1; 66 | no_value.ini: e=2 user=109 67 | ... [_123456789_123456789_123456789_123456789_12345678] 68 | ... name=value; 69 | long_section.ini: e=0 user=110 70 | ... [width = 18] 71 | ... _123456789=1234567; 72 | ... [width = 19] 73 | ... _123456789=12345678; 74 | ... [width = 20] 75 | ... _123456789=123456789; 76 | ... [2 assigns] 77 | ... _123456789=12345678name=value; 78 | ... [no trailing \n] 79 | ... _123456782=12345678; 80 | long_line.ini: e=0 user=111 81 | -------------------------------------------------------------------------------- /tests/baseline_heap_max_line.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comm; 15 | ... [colon_tests] 16 | ... Content-Type=text/; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equ; 20 | ... funny2=with : col; 21 | ... funny3=two = equa; 22 | ... funny4=two : colo; 23 | normal.ini: e=1 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... name2=value2; 27 | bad_section.ini: e=3 user=102 28 | bad_comment.ini: e=1 user=102 29 | ... [section] 30 | ... a=b; 31 | ... user=parse_error; 32 | ... c=d; 33 | user_error.ini: e=3 user=104 34 | ... [section1] 35 | ... single1=abc; 36 | ... multi=this is a; 37 | ... multi=multi-line; 38 | ... single2=xyz; 39 | ... [section2] 40 | ... multi=a; 41 | ... multi=b; 42 | ... multi=c; 43 | ... [section3] 44 | ... single=ghi; 45 | ... multi=the quick; 46 | ... multi=brown fox; 47 | ... name=bob smith; 48 | ... foo=bar; 49 | ... foo=Hi World; 50 | multi_line.ini: e=4 user=105 51 | bad_multi.ini: e=1 user=105 52 | ... [bom_section] 53 | ... bom_name=bom_value; 54 | ... key“=value“; 55 | bom.ini: e=0 user=107 56 | ... [section1] 57 | ... single1=abc; 58 | ... single2=xyz; 59 | ... single1=def; 60 | ... single2=qrs; 61 | duplicate_sections.ini: e=0 user=108 62 | ... [section0] 63 | ... key0=val0; 64 | ... [section1] 65 | ... key1=val1; 66 | no_value.ini: e=2 user=109 67 | ... name=value; 68 | long_section.ini: e=1 user=110 69 | ... [width = 18] 70 | ... _123456789=1234567; 71 | ... [width = 19] 72 | ... _123456789=12345678; 73 | ... [width = 20] 74 | ... _123456789=12345678; 75 | ... [2 assigns] 76 | ... _123456789=12345678; 77 | ... [no trailing \n] 78 | ... _123456782=12345678; 79 | long_line.ini: e=10 user=111 80 | -------------------------------------------------------------------------------- /tests/baseline_heap_realloc.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comment, needs whitespace before ';'; 15 | ... [colon_tests] 16 | ... Content-Type=text/html; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equals; 20 | ... funny2=with : colons; 21 | ... funny3=two = equals; 22 | ... funny4=two : colons; 23 | normal.ini: e=0 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... name2=value2; 27 | bad_section.ini: e=3 user=102 28 | bad_comment.ini: e=1 user=102 29 | ... [section] 30 | ... a=b; 31 | ... user=parse_error; 32 | ... c=d; 33 | user_error.ini: e=3 user=104 34 | ... [section1] 35 | ... single1=abc; 36 | ... multi=this is a; 37 | ... multi=multi-line value; 38 | ... single2=xyz; 39 | ... [section2] 40 | ... multi=a; 41 | ... multi=b; 42 | ... multi=c; 43 | ... [section3] 44 | ... single=ghi; 45 | ... multi=the quick; 46 | ... multi=brown fox; 47 | ... name=bob smith; 48 | ... foo=bar; 49 | ... foo=Hi World; 50 | multi_line.ini: e=0 user=105 51 | bad_multi.ini: e=1 user=105 52 | ... [bom_section] 53 | ... bom_name=bom_value; 54 | ... key“=value“; 55 | bom.ini: e=0 user=107 56 | ... [section1] 57 | ... single1=abc; 58 | ... single2=xyz; 59 | ... single1=def; 60 | ... single2=qrs; 61 | duplicate_sections.ini: e=0 user=108 62 | ... [section0] 63 | ... key0=val0; 64 | ... [section1] 65 | ... key1=val1; 66 | no_value.ini: e=2 user=109 67 | ... [_123456789_123456789_123456789_123456789_12345678] 68 | ... name=value; 69 | long_section.ini: e=0 user=110 70 | ... [width = 18] 71 | ... _123456789=1234567; 72 | ... [width = 19] 73 | ... _123456789=12345678; 74 | ... [width = 20] 75 | ... _123456789=123456789; 76 | ... [2 assigns] 77 | ... _123456789=12345678name=value; 78 | ... [no trailing \n] 79 | ... _123456782=12345678; 80 | long_line.ini: e=0 user=111 81 | -------------------------------------------------------------------------------- /tests/baseline_heap_realloc_max_line.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comm; 15 | ... [colon_tests] 16 | ... Content-Type=text/; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equ; 20 | ... funny2=with : col; 21 | ... funny3=two = equa; 22 | ... funny4=two : colo; 23 | normal.ini: e=1 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... name2=value2; 27 | bad_section.ini: e=3 user=102 28 | bad_comment.ini: e=1 user=102 29 | ... [section] 30 | ... a=b; 31 | ... user=parse_error; 32 | ... c=d; 33 | user_error.ini: e=3 user=104 34 | ... [section1] 35 | ... single1=abc; 36 | ... multi=this is a; 37 | ... multi=multi-line; 38 | ... single2=xyz; 39 | ... [section2] 40 | ... multi=a; 41 | ... multi=b; 42 | ... multi=c; 43 | ... [section3] 44 | ... single=ghi; 45 | ... multi=the quick; 46 | ... multi=brown fox; 47 | ... name=bob smith; 48 | ... foo=bar; 49 | ... foo=Hi World; 50 | multi_line.ini: e=4 user=105 51 | bad_multi.ini: e=1 user=105 52 | ... [bom_section] 53 | ... bom_name=bom_value; 54 | ... key“=value“; 55 | bom.ini: e=0 user=107 56 | ... [section1] 57 | ... single1=abc; 58 | ... single2=xyz; 59 | ... single1=def; 60 | ... single2=qrs; 61 | duplicate_sections.ini: e=0 user=108 62 | ... [section0] 63 | ... key0=val0; 64 | ... [section1] 65 | ... key1=val1; 66 | no_value.ini: e=2 user=109 67 | ... name=value; 68 | long_section.ini: e=1 user=110 69 | ... [width = 18] 70 | ... _123456789=1234567; 71 | ... [width = 19] 72 | ... _123456789=12345678; 73 | ... [width = 20] 74 | ... _123456789=12345678; 75 | ... [2 assigns] 76 | ... _123456789=12345678; 77 | ... [no trailing \n] 78 | ... _123456782=12345678; 79 | long_line.ini: e=10 user=111 80 | -------------------------------------------------------------------------------- /tests/baseline_heap_string.txt: -------------------------------------------------------------------------------- 1 | empty string: e=0 user=0 2 | ... [section] 3 | ... foo=bar; 4 | ... bazz=buzz quxx; 5 | basic: e=0 user=101 6 | ... [section] 7 | ... hello=world; 8 | ... forty_two=42; 9 | crlf: e=0 user=102 10 | ... [sec] 11 | ... foo=0123456789012; 12 | ... bar=4321; 13 | long line: e=2 user=103 14 | ... [sec] 15 | ... foo=0123456789012; 16 | long continued: e=2 user=104 17 | ... [s] 18 | ... a=1; 19 | ... c=3; 20 | error: e=3 user=105 21 | -------------------------------------------------------------------------------- /tests/baseline_multi.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comment, needs whitespace before ';'; 15 | ... [colon_tests] 16 | ... Content-Type=text/html; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equals; 20 | ... funny2=with : colons; 21 | ... funny3=two = equals; 22 | ... funny4=two : colons; 23 | normal.ini: e=0 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... name2=value2; 27 | bad_section.ini: e=3 user=102 28 | bad_comment.ini: e=1 user=102 29 | ... [section] 30 | ... a=b; 31 | ... user=parse_error; 32 | ... c=d; 33 | user_error.ini: e=3 user=104 34 | ... [section1] 35 | ... single1=abc; 36 | ... multi=this is a; 37 | ... multi=multi-line value; 38 | ... single2=xyz; 39 | ... [section2] 40 | ... multi=a; 41 | ... multi=b; 42 | ... multi=c; 43 | ... [section3] 44 | ... single=ghi; 45 | ... multi=the quick; 46 | ... multi=brown fox; 47 | ... name=bob smith; 48 | ... foo=bar; 49 | ... foo=Hi World; 50 | multi_line.ini: e=0 user=105 51 | bad_multi.ini: e=1 user=105 52 | ... [bom_section] 53 | ... bom_name=bom_value; 54 | ... key“=value“; 55 | bom.ini: e=0 user=107 56 | ... [section1] 57 | ... single1=abc; 58 | ... single2=xyz; 59 | ... single1=def; 60 | ... single2=qrs; 61 | duplicate_sections.ini: e=0 user=108 62 | ... [section0] 63 | ... key0=val0; 64 | ... [section1] 65 | ... key1=val1; 66 | no_value.ini: e=2 user=109 67 | ... [_123456789_123456789_123456789_123456789_12345678] 68 | ... name=value; 69 | long_section.ini: e=0 user=110 70 | ... [width = 18] 71 | ... _123456789=1234567; 72 | ... [width = 19] 73 | ... _123456789=12345678; 74 | ... [width = 20] 75 | ... _123456789=123456789; 76 | ... [2 assigns] 77 | ... _123456789=12345678name=value; 78 | ... [no trailing \n] 79 | ... _123456782=12345678; 80 | long_line.ini: e=0 user=111 81 | -------------------------------------------------------------------------------- /tests/baseline_multi_max_line.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comm; 15 | ... [colon_tests] 16 | ... Content-Type=text/; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equ; 20 | ... funny2=with : col; 21 | ... funny3=two = equa; 22 | ... funny4=two : colo; 23 | normal.ini: e=1 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... name2=value2; 27 | bad_section.ini: e=3 user=102 28 | bad_comment.ini: e=1 user=102 29 | ... [section] 30 | ... a=b; 31 | ... user=parse_error; 32 | ... c=d; 33 | user_error.ini: e=3 user=104 34 | ... [section1] 35 | ... single1=abc; 36 | ... multi=this is a; 37 | ... multi=multi-line; 38 | ... single2=xyz; 39 | ... [section2] 40 | ... multi=a; 41 | ... multi=b; 42 | ... multi=c; 43 | ... [section3] 44 | ... single=ghi; 45 | ... multi=the quick; 46 | ... multi=brown fox; 47 | ... name=bob smith; 48 | ... foo=bar; 49 | ... foo=Hi World; 50 | multi_line.ini: e=4 user=105 51 | bad_multi.ini: e=1 user=105 52 | ... [bom_section] 53 | ... bom_name=bom_value; 54 | ... key“=value“; 55 | bom.ini: e=0 user=107 56 | ... [section1] 57 | ... single1=abc; 58 | ... single2=xyz; 59 | ... single1=def; 60 | ... single2=qrs; 61 | duplicate_sections.ini: e=0 user=108 62 | ... [section0] 63 | ... key0=val0; 64 | ... [section1] 65 | ... key1=val1; 66 | no_value.ini: e=2 user=109 67 | ... name=value; 68 | long_section.ini: e=1 user=110 69 | ... [width = 18] 70 | ... _123456789=1234567; 71 | ... [width = 19] 72 | ... _123456789=12345678; 73 | ... [width = 20] 74 | ... _123456789=12345678; 75 | ... [2 assigns] 76 | ... _123456789=12345678; 77 | ... [no trailing \n] 78 | ... _123456782=12345678; 79 | long_line.ini: e=10 user=111 80 | -------------------------------------------------------------------------------- /tests/baseline_single.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comment, needs whitespace before ';'; 15 | ... [colon_tests] 16 | ... Content-Type=text/html; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equals; 20 | ... funny2=with : colons; 21 | ... funny3=two = equals; 22 | ... funny4=two : colons; 23 | normal.ini: e=0 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | ... name2=value2; 27 | bad_section.ini: e=3 user=102 28 | bad_comment.ini: e=1 user=102 29 | ... [section] 30 | ... a=b; 31 | ... user=parse_error; 32 | ... c=d; 33 | user_error.ini: e=3 user=104 34 | ... [section1] 35 | ... single1=abc; 36 | ... multi=this is a; 37 | ... single2=xyz; 38 | ... [section2] 39 | ... multi=a; 40 | ... [section3] 41 | ... single=ghi; 42 | ... multi=the quick; 43 | ... name=bob smith; 44 | ... foo=bar; 45 | multi_line.ini: e=4 user=105 46 | bad_multi.ini: e=1 user=105 47 | ... [bom_section] 48 | ... bom_name=bom_value; 49 | ... key“=value“; 50 | bom.ini: e=0 user=107 51 | ... [section1] 52 | ... single1=abc; 53 | ... single2=xyz; 54 | ... single1=def; 55 | ... single2=qrs; 56 | duplicate_sections.ini: e=0 user=108 57 | ... [section0] 58 | ... key0=val0; 59 | ... [section1] 60 | ... key1=val1; 61 | no_value.ini: e=2 user=109 62 | ... [_123456789_123456789_123456789_123456789_12345678] 63 | ... name=value; 64 | long_section.ini: e=0 user=110 65 | ... [width = 18] 66 | ... _123456789=1234567; 67 | ... [width = 19] 68 | ... _123456789=12345678; 69 | ... [width = 20] 70 | ... _123456789=123456789; 71 | ... [2 assigns] 72 | ... _123456789=12345678name=value; 73 | ... [no trailing \n] 74 | ... _123456782=12345678; 75 | long_line.ini: e=0 user=111 76 | -------------------------------------------------------------------------------- /tests/baseline_stop_on_first_error.txt: -------------------------------------------------------------------------------- 1 | no_file.ini: e=-1 user=0 2 | ... [section1] 3 | ... one=This is a test; 4 | ... two=1234; 5 | ... [ section 2 ] 6 | ... happy=4; 7 | ... sad=; 8 | ... [comment_test] 9 | ... test1=1;2;3; 10 | ... test2=2;3;4;this won't be a comment, needs whitespace before ';'; 11 | ... test;3=345; 12 | ... test4=4#5#6; 13 | ... test7=; 14 | ... test8=; not a comment, needs whitespace before ';'; 15 | ... [colon_tests] 16 | ... Content-Type=text/html; 17 | ... foo=bar; 18 | ... adams=42; 19 | ... funny1=with = equals; 20 | ... funny2=with : colons; 21 | ... funny3=two = equals; 22 | ... funny4=two : colons; 23 | normal.ini: e=0 user=101 24 | ... [section1] 25 | ... name1=value1; 26 | bad_section.ini: e=3 user=102 27 | bad_comment.ini: e=1 user=102 28 | ... [section] 29 | ... a=b; 30 | ... user=parse_error; 31 | user_error.ini: e=3 user=104 32 | ... [section1] 33 | ... single1=abc; 34 | ... multi=this is a; 35 | ... multi=multi-line value; 36 | ... single2=xyz; 37 | ... [section2] 38 | ... multi=a; 39 | ... multi=b; 40 | ... multi=c; 41 | ... [section3] 42 | ... single=ghi; 43 | ... multi=the quick; 44 | ... multi=brown fox; 45 | ... name=bob smith; 46 | ... foo=bar; 47 | ... foo=Hi World; 48 | multi_line.ini: e=0 user=105 49 | bad_multi.ini: e=1 user=105 50 | ... [bom_section] 51 | ... bom_name=bom_value; 52 | ... key“=value“; 53 | bom.ini: e=0 user=107 54 | ... [section1] 55 | ... single1=abc; 56 | ... single2=xyz; 57 | ... single1=def; 58 | ... single2=qrs; 59 | duplicate_sections.ini: e=0 user=108 60 | no_value.ini: e=2 user=108 61 | ... [_123456789_123456789_123456789_123456789_12345678] 62 | ... name=value; 63 | long_section.ini: e=0 user=110 64 | ... [width = 18] 65 | ... _123456789=1234567; 66 | ... [width = 19] 67 | ... _123456789=12345678; 68 | ... [width = 20] 69 | ... _123456789=123456789; 70 | ... [2 assigns] 71 | ... _123456789=12345678name=value; 72 | ... [no trailing \n] 73 | ... _123456782=12345678; 74 | long_line.ini: e=0 user=111 75 | -------------------------------------------------------------------------------- /tests/baseline_string.txt: -------------------------------------------------------------------------------- 1 | empty string: e=0 user=0 2 | ... [section] 3 | ... foo=bar; 4 | ... bazz=buzz quxx; 5 | basic: e=0 user=101 6 | ... [section] 7 | ... hello=world; 8 | ... forty_two=42; 9 | crlf: e=0 user=102 10 | ... [sec] 11 | ... foo=0123456789012; 12 | ... bar=4321; 13 | long line: e=2 user=103 14 | ... [sec] 15 | ... foo=0123456789012; 16 | long continued: e=2 user=104 17 | ... [s] 18 | ... a=1; 19 | ... c=3; 20 | error: e=3 user=105 21 | -------------------------------------------------------------------------------- /tests/bom.ini: -------------------------------------------------------------------------------- 1 | [bom_section] 2 | bom_name=bom_value 3 | key“ = value“ 4 | -------------------------------------------------------------------------------- /tests/duplicate_sections.ini: -------------------------------------------------------------------------------- 1 | [section1] 2 | single1 = abc 3 | single2 = xyz 4 | [section1] 5 | single1 = def 6 | single2 = qrs -------------------------------------------------------------------------------- /tests/long_line.ini: -------------------------------------------------------------------------------- 1 | # These tests are 2 | # only interesting 3 | # when 4 | # INI_MAX_LINE=20 5 | 6 | [width = 18] 7 | _123456789=1234567 8 | 9 | [width = 19] 10 | _123456789=12345678 11 | 12 | [width = 20] 13 | _123456789=123456789 14 | 15 | [2 assigns] 16 | _123456789=12345678name=value 17 | 18 | [no trailing \n] 19 | # trigger a false 20 | # positive in the 21 | # incomplete line 22 | # detection 23 | _123456782=12345678 -------------------------------------------------------------------------------- /tests/long_section.ini: -------------------------------------------------------------------------------- 1 | # check that section is truncated 2 | [_123456789_123456789_123456789_123456789_123456789_123456789] 3 | name = value 4 | -------------------------------------------------------------------------------- /tests/meson.build: -------------------------------------------------------------------------------- 1 | runtest = find_program('runtest.sh', required: false) 2 | if not runtest.found() 3 | subdir_done() 4 | endif 5 | 6 | tests = { 7 | 'multi': { 'args': [] }, 8 | 'multi_max_line': { 'args': ['-DINI_MAX_LINE=20'] }, 9 | 'single': { 'args': ['-DINI_ALLOW_MULTILINE=0'] }, 10 | 'disallow_inline_comments': { 'args': ['-DINI_ALLOW_INLINE_COMMENTS=0'] }, 11 | 'stop_on_first_error': { 'args': ['-DINI_STOP_ON_FIRST_ERROR=1'] }, 12 | 'handler_lineno': { 'args': ['-DINI_HANDLER_LINENO=1'] }, 13 | 'string': { 'src': 'unittest_string.c', 'args': ['-DINI_MAX_LINE=20'] }, 14 | 'heap': { 'args': ['-DINI_USE_STACK=0'] }, 15 | 'heap_max_line': { 'args': ['-DINI_USE_STACK=0', '-DINI_MAX_LINE=20', '-DINI_INITIAL_ALLOC=20'] }, 16 | 'heap_realloc': { 'args': ['-DINI_USE_STACK=0', '-DINI_ALLOW_REALLOC=1', '-DINI_INITIAL_ALLOC=5'] }, 17 | 'heap_realloc_max_line': { 'args': ['-DINI_USE_STACK=0', '-DINI_MAX_LINE=20', '-DINI_ALLOW_REALLOC=1', '-DINI_INITIAL_ALLOC=5'] }, 18 | 'heap_string': { 'src': 'unittest_string.c', 'args': ['-DINI_USE_STACK=0', '-DINI_MAX_LINE=20', '-DINI_INITIAL_ALLOC=20'] }, 19 | 'call_handler_on_new_section': { 'args': ['-DINI_CALL_HANDLER_ON_NEW_SECTION=1'] }, 20 | 'allow_no_value': { 'args': ['-DINI_ALLOW_NO_VALUE=1'] }, 21 | 'alloc': { 'src': 'unittest_alloc.c', 'args': ['-DINI_CUSTOM_ALLOCATOR=1', '-DINI_USE_STACK=0', '-DINI_ALLOW_REALLOC=1', '-DINI_INITIAL_ALLOC=12'] } 22 | } 23 | 24 | foreach name, properties : tests 25 | test_src = 'src' in properties ? properties['src'] : 'unittest.c' 26 | exe = executable('unittest_' + name, src_inih, test_src, c_args : ['-Wall', properties['args']]) 27 | test('test_' + name, runtest, depends : [exe], args : [files('baseline_' + name + '.txt'), exe.full_path()]) 28 | endforeach 29 | -------------------------------------------------------------------------------- /tests/multi_line.ini: -------------------------------------------------------------------------------- 1 | [section1] 2 | single1 = abc 3 | multi = this is a 4 | multi-line value 5 | single2 = xyz 6 | [section2] 7 | multi = a 8 | b 9 | c 10 | [section3] 11 | single: ghi 12 | multi: the quick 13 | brown fox 14 | name = bob smith ; comment line 1 15 | ; comment line 2 16 | foo = bar ;c1 17 | Hi World ;c2 18 | -------------------------------------------------------------------------------- /tests/no_value.ini: -------------------------------------------------------------------------------- 1 | [section_list] 2 | section0 3 | section1 4 | 5 | [section0] 6 | key0=val0 7 | 8 | [section1] 9 | key1=val1 10 | -------------------------------------------------------------------------------- /tests/normal.ini: -------------------------------------------------------------------------------- 1 | ; This is an INI file 2 | [section1] ; section comment 3 | one=This is a test ; name=value comment 4 | two = 1234 5 | ; x=y 6 | 7 | [ section 2 ] 8 | happy = 4 9 | sad = 10 | 11 | [empty] 12 | ; do nothing 13 | 14 | [comment_test] 15 | test1 = 1;2;3 ; only this will be a comment 16 | test2 = 2;3;4;this won't be a comment, needs whitespace before ';' 17 | test;3 = 345 ; key should be "test;3" 18 | test4 = 4#5#6 ; '#' only starts a comment at start of line 19 | #test5 = 567 ; entire line commented 20 | # test6 = 678 ; entire line commented, except in MULTILINE mode 21 | test7 = ; blank value, except if inline comments disabled 22 | test8 =; not a comment, needs whitespace before ';' 23 | 24 | [colon_tests] 25 | Content-Type: text/html 26 | foo:bar 27 | adams : 42 28 | funny1 : with = equals 29 | funny2 = with : colons 30 | funny3 = two = equals 31 | funny4 : two : colons 32 | -------------------------------------------------------------------------------- /tests/runtest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | cd "$(dirname "${1}")" 6 | "$2" | diff "${1}" - 7 | -------------------------------------------------------------------------------- /tests/unittest.bat: -------------------------------------------------------------------------------- 1 | @call tcc ..\ini.c -I..\ -run unittest.c > baseline_multi.txt 2 | @call tcc ..\ini.c -I..\ -DINI_MAX_LINE=20 -run unittest.c > baseline_multi_max_line.txt 3 | @call tcc ..\ini.c -I..\ -DINI_ALLOW_MULTILINE=0 -run unittest.c > baseline_single.txt 4 | @call tcc ..\ini.c -I..\ -DINI_ALLOW_INLINE_COMMENTS=0 -run unittest.c > baseline_disallow_inline_comments.txt 5 | @call tcc ..\ini.c -I..\ -DINI_STOP_ON_FIRST_ERROR=1 -run unittest.c > baseline_stop_on_first_error.txt 6 | @call tcc ..\ini.c -I..\ -DINI_HANDLER_LINENO=1 -run unittest.c > baseline_handler_lineno.txt 7 | @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -run unittest.c > baseline_heap.txt 8 | @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 -run unittest.c > baseline_heap_max_line.txt 9 | @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 -run unittest.c > baseline_heap_realloc.txt 10 | @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 -run unittest.c > baseline_heap_realloc_max_line.txt 11 | @call tcc ..\ini.c -I..\ -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 -run unittest.c > baseline_heap_string.txt 12 | @call tcc ..\ini.c -I..\ -DINI_CALL_HANDLER_ON_NEW_SECTION=1 -run unittest.c > baseline_call_handler_on_new_section.txt 13 | @call tcc ..\ini.c -I..\ -DINI_ALLOW_NO_VALUE=1 -run unittest.c > baseline_allow_no_value.txt 14 | @call tcc ..\ini.c -I..\ -DINI_CUSTOM_ALLOCATOR=1 -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=12 -run unittest_alloc.c > baseline_alloc.txt 15 | -------------------------------------------------------------------------------- /tests/unittest.c: -------------------------------------------------------------------------------- 1 | /* inih -- tests 2 | 3 | This works simply by dumping a bunch of info to standard output, which is 4 | redirected to an output file (baseline_*.txt) and checked into the Git 5 | repository. This baseline file is the test output, so the idea is to check it 6 | once, and if it changes -- look at the diff and see which tests failed. 7 | 8 | See unittest.bat and unittest.sh for how to run this (with tcc and gcc, 9 | respectively). 10 | 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #ifdef _WIN32 17 | #include 18 | #include 19 | #endif 20 | #include "../ini.h" 21 | 22 | int User; 23 | char Prev_section[50]; 24 | 25 | #if INI_HANDLER_LINENO 26 | int dumper(void* user, const char* section, const char* name, 27 | const char* value, int lineno) 28 | #else 29 | int dumper(void* user, const char* section, const char* name, 30 | const char* value) 31 | #endif 32 | { 33 | User = *((int*)user); 34 | if (!name || strcmp(section, Prev_section)) { 35 | printf("... [%s]\n", section); 36 | strncpy(Prev_section, section, sizeof(Prev_section)); 37 | Prev_section[sizeof(Prev_section) - 1] = '\0'; 38 | } 39 | if (!name) { 40 | return 1; 41 | } 42 | 43 | #if INI_HANDLER_LINENO 44 | printf("... %s%s%s; line %d\n", name, value ? "=" : "", value ? value : "", lineno); 45 | #else 46 | printf("... %s%s%s;\n", name, value ? "=" : "", value ? value : ""); 47 | #endif 48 | 49 | if (!value) { 50 | // Happens when INI_ALLOW_NO_VALUE=1 and line has no value (no '=' or ':') 51 | return 1; 52 | } 53 | 54 | return strcmp(name, "user")==0 && strcmp(value, "parse_error")==0 ? 0 : 1; 55 | } 56 | 57 | void parse(const char* fname) { 58 | static int u = 100; 59 | int e; 60 | 61 | *Prev_section = '\0'; 62 | e = ini_parse(fname, dumper, &u); 63 | printf("%s: e=%d user=%d\n", fname, e, User); 64 | u++; 65 | } 66 | 67 | int main(void) 68 | { 69 | #ifdef _WIN32 70 | _setmode(_fileno(stdout), _O_BINARY); 71 | #endif 72 | parse("no_file.ini"); 73 | parse("normal.ini"); 74 | parse("bad_section.ini"); 75 | parse("bad_comment.ini"); 76 | parse("user_error.ini"); 77 | parse("multi_line.ini"); 78 | parse("bad_multi.ini"); 79 | parse("bom.ini"); 80 | parse("duplicate_sections.ini"); 81 | parse("no_value.ini"); 82 | parse("long_section.ini"); 83 | parse("long_line.ini"); 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /tests/unittest.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CC="${CC:-gcc} -Wall" 4 | 5 | $CC ../ini.c unittest.c -o unittest_multi 6 | ./unittest_multi > baseline_multi.txt 7 | rm -f unittest_multi 8 | 9 | $CC ../ini.c -DINI_MAX_LINE=20 unittest.c -o unittest_multi_max_line 10 | ./unittest_multi_max_line > baseline_multi_max_line.txt 11 | rm -f unittest_multi_max_line 12 | 13 | $CC ../ini.c -DINI_ALLOW_MULTILINE=0 unittest.c -o unittest_single 14 | ./unittest_single > baseline_single.txt 15 | rm -f unittest_single 16 | 17 | $CC ../ini.c -DINI_ALLOW_INLINE_COMMENTS=0 unittest.c -o unittest_disallow_inline_comments 18 | ./unittest_disallow_inline_comments > baseline_disallow_inline_comments.txt 19 | rm -f unittest_disallow_inline_comments 20 | 21 | $CC ../ini.c -DINI_STOP_ON_FIRST_ERROR=1 unittest.c -o unittest_stop_on_first_error 22 | ./unittest_stop_on_first_error > baseline_stop_on_first_error.txt 23 | rm -f unittest_stop_on_first_error 24 | 25 | $CC ../ini.c -DINI_HANDLER_LINENO=1 unittest.c -o unittest_handler_lineno 26 | ./unittest_handler_lineno > baseline_handler_lineno.txt 27 | rm -f unittest_handler_lineno 28 | 29 | $CC ../ini.c -DINI_MAX_LINE=20 unittest_string.c -o unittest_string 30 | ./unittest_string > baseline_string.txt 31 | rm -f unittest_string 32 | 33 | $CC ../ini.c -DINI_USE_STACK=0 unittest.c -o unittest_heap 34 | ./unittest_heap > baseline_heap.txt 35 | rm -f unittest_heap 36 | 37 | $CC ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 unittest.c -o unittest_heap_max_line 38 | ./unittest_heap_max_line > baseline_heap_max_line.txt 39 | rm -f unittest_heap_max_line 40 | 41 | $CC ../ini.c -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 unittest.c -o unittest_heap_realloc 42 | ./unittest_heap_realloc > baseline_heap_realloc.txt 43 | rm -f unittest_heap_realloc 44 | 45 | $CC ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=5 unittest.c -o unittest_heap_realloc_max_line 46 | ./unittest_heap_realloc_max_line > baseline_heap_realloc_max_line.txt 47 | rm -f unittest_heap_realloc_max_line 48 | 49 | $CC ../ini.c -DINI_USE_STACK=0 -DINI_MAX_LINE=20 -DINI_INITIAL_ALLOC=20 unittest_string.c -o unittest_heap_string 50 | ./unittest_heap_string > baseline_heap_string.txt 51 | rm -f unittest_heap_string 52 | 53 | $CC ../ini.c -DINI_CALL_HANDLER_ON_NEW_SECTION=1 unittest.c -o unittest_call_handler_on_new_section 54 | ./unittest_call_handler_on_new_section > baseline_call_handler_on_new_section.txt 55 | rm -f unittest_call_handler_on_new_section 56 | 57 | $CC ../ini.c -DINI_ALLOW_NO_VALUE=1 unittest.c -o unittest_allow_no_value 58 | ./unittest_allow_no_value > baseline_allow_no_value.txt 59 | rm -f unittest_allow_no_value 60 | 61 | $CC -DINI_CUSTOM_ALLOCATOR=1 -DINI_USE_STACK=0 -DINI_ALLOW_REALLOC=1 -DINI_INITIAL_ALLOC=12 ../ini.c unittest_alloc.c -o unittest_alloc 62 | ./unittest_alloc > baseline_alloc.txt 63 | rm -f unittest_alloc 64 | -------------------------------------------------------------------------------- /tests/unittest_alloc.c: -------------------------------------------------------------------------------- 1 | /* inih -- tests with custom memory allocator */ 2 | 3 | #include 4 | #include 5 | #include 6 | #ifdef _WIN32 7 | #include 8 | #include 9 | #endif 10 | #include "../ini.h" 11 | 12 | void* ini_malloc(size_t size) { 13 | printf("ini_malloc(%d)\n", (int)size); 14 | return malloc(size); 15 | } 16 | 17 | void ini_free(void* ptr) { 18 | printf("ini_free()\n"); 19 | free(ptr); 20 | } 21 | 22 | void* ini_realloc(void* ptr, size_t size) { 23 | printf("ini_realloc(%d)\n", (int)size); 24 | return realloc(ptr, size); 25 | } 26 | 27 | char Prev_section[50]; 28 | 29 | int dumper(void* user, const char* section, const char* name, 30 | const char* value) 31 | { 32 | if (strcmp(section, Prev_section)) { 33 | printf("... [%s]\n", section); 34 | strncpy(Prev_section, section, sizeof(Prev_section)); 35 | Prev_section[sizeof(Prev_section) - 1] = '\0'; 36 | } 37 | printf("... %s=%s;\n", name, value); 38 | return 1; 39 | } 40 | 41 | void parse(const char* name, const char* string) { 42 | int e; 43 | 44 | *Prev_section = '\0'; 45 | e = ini_parse_string(string, dumper, NULL); 46 | printf("%s: e=%d\n", name, e); 47 | } 48 | 49 | int main(void) 50 | { 51 | #ifdef _WIN32 52 | _setmode(_fileno(stdout), _O_BINARY); 53 | #endif 54 | parse("basic", "[section]\nfoo = bar\nbazz = buzz quxx"); 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /tests/unittest_string.c: -------------------------------------------------------------------------------- 1 | /* inih -- tests for ini_parse_string() */ 2 | 3 | #include 4 | #include 5 | #include 6 | #ifdef _WIN32 7 | #include 8 | #include 9 | #endif 10 | #include "../ini.h" 11 | 12 | int User; 13 | char Prev_section[50]; 14 | 15 | int dumper(void* user, const char* section, const char* name, 16 | const char* value) 17 | { 18 | User = *((int*)user); 19 | if (strcmp(section, Prev_section)) { 20 | printf("... [%s]\n", section); 21 | strncpy(Prev_section, section, sizeof(Prev_section)); 22 | Prev_section[sizeof(Prev_section) - 1] = '\0'; 23 | } 24 | printf("... %s=%s;\n", name, value); 25 | return 1; 26 | } 27 | 28 | void parse(const char* name, const char* string) { 29 | static int u = 100; 30 | int e; 31 | 32 | *Prev_section = '\0'; 33 | e = ini_parse_string(string, dumper, &u); 34 | printf("%s: e=%d user=%d\n", name, e, User); 35 | u++; 36 | } 37 | 38 | int main(void) 39 | { 40 | #ifdef _WIN32 41 | _setmode(_fileno(stdout), _O_BINARY); 42 | #endif 43 | parse("empty string", ""); 44 | parse("basic", "[section]\nfoo = bar\nbazz = buzz quxx"); 45 | parse("crlf", "[section]\r\nhello = world\r\nforty_two = 42\r\n"); 46 | parse("long line", "[sec]\nfoo = 01234567890123456789\nbar=4321\n"); 47 | parse("long continued", "[sec]\nfoo = 0123456789012bix=1234\n"); 48 | parse("error", "[s]\na=1\nb\nc=3"); 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /tests/user_error.ini: -------------------------------------------------------------------------------- 1 | [section] 2 | a = b 3 | user = parse_error 4 | c = d 5 | --------------------------------------------------------------------------------