├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples └── server_config.c ├── src ├── rini.c └── rini.h └── tests └── TODO.md /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LloydLabs/librini/312d8f718828058c6ee3fde33ceb1429d21712ff/.gitignore -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(Rini) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | set(SOURCE_FILES src/rini.c src/rini.h src/lib.c src/lib.h) 7 | add_library(Rini ${SOURCE_FILES}) 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php) 2 | 3 | # Rini - librini 4 | Rini (*really small ini parser*) is a tiny, standalone, blazing fast .ini file parser. All other parsers rely on libc functions such as 'strtok', however Rini is in complete raw C code designed to handle the 'ini' format thus is much faster and efficient. This is the first parser I've made completely from scratch, feedback is welcome. I'll be removing the libc helper functions in the next release. 5 | 6 | # Advantages over other parsers 7 | - No overhead of using functions such as strtok. 8 | - Much smaller in size compared to other parsers. 9 | - Clean, maintainable code. 10 | - Much faster than its counterparts. 11 | - Tiny footprint. Great for an embedded environment. 12 | - Does not touch the heap at all, meaning less overhead of useless allocations. It is stack based only. 13 | - Support for inline comments and the escaping of special characters. 14 | 15 | # Supported data types 16 | - Strings, ASCII strings - no support for wide characters currently. Rini supports "quoted strings" too. When it comes to strings too, Rini supports all escaped special characters. For example, to escape the ';' character which is used for inline comments: "$x = 0\;". 17 | - Booleans, tokens such as 'TRUE', '1', 'ON', 'YES', 'Y' are all accepted (and all of their lowercase equivalents). 18 | - Signed integers, Rini has a built-in signed integer converter. 19 | 20 | `MAX_NAME` is the set maximum length of keys (not values) and headers and has a default value of 256, you may however change this macro in `rini.h`. 21 | 22 | # Building 23 | ``` 24 | cmake --build . 25 | ``` 26 | 27 | # Usage 28 | 29 | The only method exported by librini is `rini_get_key` which is located in the API header `rini.h`. 30 | 31 | ```C 32 | int rini_get_key(const char* parent, const char* key, const char* config, unsigned config_size, const void* out, unsigned out_size, value_types_t type) 33 | ``` 34 | 35 | Consider we have the following INI format in a variable named `config` in the type `const char`: 36 | ```ini 37 | [server.main] 38 | # This server is used for clustering.. 39 | hostname="root" 40 | distro=Ubuntu 41 | ssh_port=99 42 | [server.labs] 43 | hostname=honeypot 44 | distro=Debian 45 | ssh_port=80 ; this is the SSH port 46 | ``` 47 | 48 | ```C 49 | char server_hostname[MAX_NAME], distro_name[MAX_NAME]; 50 | memset(server_hostname, 0, sizeof(server_hostname)); 51 | memset(distro_name, 0, sizeof(distro_name)); 52 | 53 | int main_port = 0; 54 | 55 | if (rini_get_key("server.main", "hostname", config, sizeof(config), server_hostname, sizeof(server_hostname), STRING_VAL)) 56 | { 57 | printf("Name of main server: %s\n", server_hostname); 58 | } 59 | 60 | if (rini_get_key("server.main", "ssh_port", config, sizeof(config), &main_port, sizeof(main_port), INT_VAL)) 61 | { 62 | if (main_port <= 1024) 63 | { 64 | printf("Please change the port from %d to a number above 1024\n", main_port); 65 | } 66 | } 67 | 68 | if (rini_get_key("server.labs", "distro", config, sizeof(config), distro_name, sizeof(distro_name), STRING_VAL)) 69 | { 70 | if (strncmp(distro_name, "Arch", sizeof(distro_name)) == 0) 71 | { 72 | puts("You have a proper distro installed"); 73 | } 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /examples/server_config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | // Globals are bad, this is just here for the example, please don't use them. 7 | const char config[] = "[server.main]" 8 | "# This server is used for clustering" 9 | "hostname=root\n" 10 | "distro=Ubuntu\n" 11 | "ssh_port=99\n" 12 | "[server.labs]\n" 13 | "hostname=honeypot\n" 14 | "distro=Debian\n" 15 | "ssh_port=80\n"; 16 | 17 | int main(int argc, char** argv) 18 | { 19 | char server_hostname[MAX_NAME], distro_name[MAX_NAME]; 20 | memset(server_hostname, 0, sizeof(server_hostname)); 21 | memset(distro_name, 0, sizeof(distro_name)); 22 | 23 | int main_port = 0; 24 | 25 | if (rini_get_key("server.main", "hostname", config, sizeof(config), server_hostname, sizeof(server_hostname), STRING_VAL)) 26 | { 27 | printf("Name of main server: %s\n", server_hostname); 28 | } 29 | 30 | if (rini_get_key("server.main", "ssh_port", config, sizeof(config), &main_port, sizeof(main_port), INT_VAL)) 31 | { 32 | if (main_port <= 1024) 33 | { 34 | printf("Please change the port from %d to a number above 1024\n", main_port); 35 | } 36 | } 37 | 38 | if (rini_get_key("server.labs", "distro", config, sizeof(config), distro_name, sizeof(distro_name), STRING_VAL)) 39 | { 40 | if (strncmp(distro_name, "Arch", sizeof(distro_name)) == 0) 41 | { 42 | puts("You have a proper distro installed"); 43 | } 44 | } 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /src/rini.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file rini.c 3 | * @author LloydLabs 4 | * @date 10/2/2017 5 | * @brief Rini is a tiny, super fast, .ini file parser programmed from scratch in C99. 6 | * Rini (really small ini parser). 7 | * All other parsers rely on libc functions such as 'strtok', however Rini is in complete raw C code designed to handle the 'ini' format thus is much faster and efficent. 8 | * This is the first parser I've made completely from scratch, feedback is welcome. 9 | * 10 | * @see https://github.com/LloydLabs/librini 11 | * @see https://en.wikipedia.org/wiki/INI_file 12 | */ 13 | 14 | #include "rini.h" 15 | 16 | /** 17 | * Seeks to the location of the start of the [parent], sets the @param config_buf to the location the definition ends at. 18 | * @param parent The parent [section] to find the offset of. 19 | * @param config_buf The contents of the ini configuration, we need this so that we can find the offset to the parent. 20 | * @param size The size of the configuration buffer. 21 | * @return Returns an address to where the [parent] starts. 22 | */ 23 | static char* rini_seek_section(const char* parent, char* config_buf, 24 | unsigned size) 25 | { 26 | unsigned buf_size = 0; 27 | bool head_found = false; 28 | 29 | char current_head[MAX_NAME]; 30 | memset(current_head, 0, MAX_NAME); 31 | 32 | char* head_buf = (char*)current_head; 33 | 34 | for (unsigned total_read = 0; total_read < size; total_read++, config_buf++) 35 | { 36 | if (*config_buf == '[') 37 | { 38 | if (total_read++ >= size) 39 | { 40 | break; 41 | } 42 | 43 | config_buf++; 44 | 45 | for ( ; *config_buf != ']' && total_read < size; config_buf++, buf_size++, total_read++) 46 | { 47 | if ((buf_size + 1) >= MAX_NAME) 48 | { 49 | break; 50 | } 51 | 52 | *head_buf++ = *config_buf; 53 | } 54 | 55 | if (total_read++ >= size) 56 | { 57 | break; 58 | } 59 | 60 | config_buf++; 61 | 62 | if (strncmp(parent, (const char*)current_head, MAX_NAME) == 0) 63 | { 64 | head_found = true; 65 | break; 66 | } 67 | else 68 | { 69 | CLEAN_PTR(current_head, sizeof(current_head), head_buf); 70 | } 71 | } 72 | } 73 | 74 | return (head_found ? config_buf : NULL); 75 | } 76 | 77 | /** 78 | * https://en.wikipedia.org/wiki/INI_file#Escape_characters 79 | * checks if a character is of a special type and must be escaped 80 | * @param c The character to check 81 | * @return This will return 1 on success 82 | */ 83 | static bool rini_is_escaped(char c) 84 | { 85 | const char escape_chars[] = { 86 | '"', ';', '#', ':', 87 | '=', '\\' 88 | }; 89 | 90 | for (unsigned i = 0; i < ARRAY_SIZE(escape_chars); i++) 91 | { 92 | if (c == escape_chars[i]) 93 | { 94 | return true; 95 | } 96 | } 97 | 98 | return false; 99 | } 100 | 101 | /** 102 | * attempts to parse a string of a given size from a human-like boolean value. 103 | * @see bool_type_t 104 | * @param buf The alleged boolean value. 105 | * @param buf_size The size of the buffer. 106 | * @return Will return BOOL_KEY_ERROR on error, else, a boolean value. 107 | */ 108 | static bool_type_t rini_get_bool(char* buf, unsigned buf_size) 109 | { 110 | if (buf == NULL || buf_size > (MAX_BOOL_KEY_SIZE - 1)) 111 | { 112 | return BOOL_KEY_ERROR; 113 | } 114 | 115 | bool_keys_t bool_keys[] = { 116 | { BOOL_KEY_TRUE, "1", 1 }, { BOOL_KEY_TRUE, "yes", 3 }, 117 | { BOOL_KEY_TRUE, "on", 2 }, { BOOL_KEY_TRUE, "true", 4 }, 118 | { BOOL_KEY_TRUE, "y", 1 }, 119 | { BOOL_KEY_FALSE, "0", 1 }, { BOOL_KEY_FALSE, "no", 2 }, 120 | { BOOL_KEY_FALSE, "off", 3 }, { BOOL_KEY_FALSE, "false", 5 }, 121 | { BOOL_KEY_FALSE, "n", 1 } 122 | }; 123 | 124 | char bool_key[MAX_BOOL_KEY_SIZE]; 125 | memset(bool_key, 0, MAX_BOOL_KEY_SIZE); 126 | 127 | memcpy(bool_key, buf, buf_size); 128 | 129 | for (unsigned i = 0; i < ARRAY_SIZE(bool_keys); i++) 130 | { 131 | if (bool_keys[i].size > buf_size) 132 | { 133 | continue; 134 | } 135 | 136 | if (memcmp(bool_key, bool_keys[i].key, bool_keys[i].size) == 0) 137 | { 138 | return bool_keys[i].val; 139 | } 140 | } 141 | 142 | return BOOL_KEY_ERROR; 143 | } 144 | 145 | /** 146 | * The main parsing logic is conducted here, first of all the key is parsed from the input, 147 | * we then make sure the key is valid. Then, from here, we skip the '=' character, and then go on to 148 | * parse the value. Once the value has been parsed, we do the relevant operations to convert it into 149 | * the data type specified in the @param val_type. 150 | * 151 | * @param node In all cases with Rini, this will be the line to parse, this function does not handle line parsing. 152 | * @param name The name to the key to parse. 153 | * @param out The buffer to send the output to. The type is of a void pointer as the type can be variable, as defined by @param val_type. 154 | * @param out_size The size of the output buffer. We must know this in order to make sure that the parsed data is no greater than this value. 155 | * @param val_type The type of value that we are parsing, the relevant conversions will take place towards the end of the function. 156 | * @param size The overall size of the configuration buffer, this will make sure that the @param out_size is not greater than our overall size. 157 | * @return Returns 1 on success. 158 | */ 159 | static bool rini_get_node(char* node, char* name, void* out, 160 | unsigned out_size, value_types_t val_type, 161 | unsigned size) 162 | { 163 | if (out == NULL) 164 | { 165 | return false; 166 | } 167 | 168 | if (val_type == INT_VAL && out_size < sizeof(int)) 169 | { 170 | return false; 171 | } 172 | 173 | char name_parsed[MAX_NAME], int_str[MAX_INT_STR_SIZE], bool_str[MAX_BOOL_KEY_SIZE]; 174 | 175 | memset(name_parsed, 0, MAX_NAME); 176 | memset(int_str, 0, MAX_INT_STR_SIZE); 177 | memset(bool_str, 0, MAX_BOOL_KEY_SIZE); 178 | 179 | char* name_buf = (char*)name_parsed, *int_buf = (char*)int_str, *bool_buf = (char*)bool_str, *node_buf = node; 180 | unsigned buf_size = 0, val_size = 1; 181 | 182 | for ( ; PTR_NOT_END(node_buf) && buf_size < size; node_buf++, buf_size++) 183 | { 184 | for ( ; *node_buf != '=' && buf_size < size; node_buf++, buf_size++) 185 | { 186 | if ((buf_size + 1) >= MAX_NAME) 187 | { 188 | return false; 189 | } 190 | 191 | *name_buf++ = *node_buf; 192 | } 193 | 194 | if (strncmp(name_parsed, name, MAX_NAME) == 0) 195 | { 196 | break; 197 | } 198 | } 199 | 200 | if (*node_buf != '=') 201 | { 202 | return false; 203 | } 204 | 205 | if (buf_size++ > size) 206 | { 207 | return false; 208 | } 209 | 210 | node_buf++; 211 | 212 | parser_flags_t parser_flags = FLAGS_DEFAULT; 213 | 214 | char* val_buf = (char*)out; 215 | for ( ; PTR_NOT_END(node_buf) && buf_size < size; node_buf++, buf_size++, val_size++) 216 | { 217 | if (parser_flags & END_EARLY) 218 | { 219 | break; 220 | } 221 | 222 | if ((parser_flags & LAST_ESCAPE_CHAR) == 0) 223 | { 224 | if (*node_buf == '#' || *node_buf == ';') 225 | { 226 | parser_flags |= END_EARLY; 227 | continue; 228 | } 229 | } 230 | 231 | switch (val_type) 232 | { 233 | case STRING_VAL: 234 | if ((val_size + 1) > out_size) 235 | { 236 | return false; 237 | } 238 | 239 | if (buf_size == 0) 240 | { 241 | if (*node_buf == '"') 242 | { 243 | parser_flags |= EXPECT_QUOTE; 244 | continue; 245 | } 246 | } 247 | 248 | if (*node_buf == '\\') 249 | { 250 | parser_flags |= LAST_ESCAPE_CHAR; 251 | break; 252 | } 253 | 254 | if (parser_flags & LAST_ESCAPE_CHAR) 255 | { 256 | parser_flags |= ESCAPE_NOT_FOUND; 257 | if (rini_is_escaped(*node_buf)) 258 | { 259 | *val_buf++ = *node_buf; 260 | parser_flags &= ~ESCAPE_NOT_FOUND; 261 | } 262 | 263 | if (parser_flags & ESCAPE_NOT_FOUND) 264 | { 265 | *val_buf++ = '\\'; 266 | node_buf--; 267 | buf_size--; 268 | 269 | parser_flags &= ~ESCAPE_NOT_FOUND; 270 | continue; 271 | } 272 | 273 | parser_flags &= ~LAST_ESCAPE_CHAR; 274 | } 275 | else if (*node_buf == '"' && (parser_flags & EXPECT_QUOTE)) 276 | { 277 | parser_flags |= END_EARLY; 278 | continue; 279 | } 280 | else 281 | { 282 | *val_buf++ = *node_buf; 283 | } 284 | break; 285 | 286 | case BOOL_VAL: 287 | if (val_size > (MAX_BOOL_KEY_SIZE - 1)) 288 | { 289 | return false; 290 | } 291 | 292 | *bool_buf++ = *node_buf; 293 | break; 294 | 295 | case INT_VAL: 296 | if (val_size > (MAX_INT_STR_SIZE - 1)) 297 | { 298 | return false; 299 | } 300 | 301 | if (buf_size == 0 && *node_buf == '-') 302 | { 303 | *int_buf++ = *node_buf; 304 | continue; 305 | } 306 | 307 | if (*node_buf < '0' || *node_buf > '9') 308 | { 309 | return false; 310 | } 311 | 312 | *int_buf++ = *node_buf; 313 | break; 314 | } 315 | } 316 | 317 | if (val_type == STRING_VAL) 318 | { 319 | *val_buf++ = 0; 320 | } 321 | else if (val_type == INT_VAL) 322 | { 323 | *int_buf++ = 0; 324 | int_buf = (char*)int_str; 325 | 326 | int numb_buf; 327 | char* conv_buf; 328 | if ((numb_buf = (int)strtol(int_buf, &conv_buf, 10)) < 0) 329 | { 330 | return false; 331 | } 332 | 333 | if (conv_buf == NULL) 334 | { 335 | return false; 336 | } 337 | 338 | memcpy(out, &numb_buf, sizeof(int)); 339 | } 340 | else if (val_type == BOOL_VAL) 341 | { 342 | bool_type_t bool_val = BOOL_KEY_ERROR; 343 | if ((bool_val = rini_get_bool(bool_str, val_size)) == BOOL_KEY_ERROR) 344 | { 345 | return false; 346 | } 347 | 348 | *val_buf = bool_val; 349 | } 350 | 351 | return true; 352 | } 353 | 354 | /** 355 | * this function will parse a key from the given parent, key and configuration. 356 | * @see rini_seek_section 357 | * @see rini_get_node 358 | * @param parent The parent [section], NULL if no section. 359 | * @param key The name of the key to find 360 | * @param config The configuration to parse 361 | * @param config_size The size of the configuration 362 | * @param out The buffer to recieve the key's value. 363 | * @param out_size The size of the buffer to recieve the key's value. 364 | * @param type The type of data that the key holds. 365 | * @return On success 1 is returned 366 | */ 367 | bool rini_get_key(const char* parent, const char* key, const char* config, 368 | unsigned config_size, const void* out, unsigned out_size, 369 | value_types_t type) 370 | { 371 | char* config_buf = (char*)config; 372 | 373 | if (key != NULL) 374 | { 375 | if ((config_buf = rini_seek_section(parent, config_buf, config_size)) == NULL) 376 | { 377 | return false; 378 | } 379 | } 380 | 381 | char line[MAX_LINE_SIZE(out_size)]; 382 | memset(line, 0, sizeof(line)); 383 | 384 | char* line_buf = (char*)line; 385 | unsigned line_size = 0; 386 | 387 | for (unsigned total_read = 0; total_read < config_size; config_buf++, total_read++) 388 | { 389 | line_size = 0; 390 | for ( ; PTR_NOT_END(config_buf) && total_read < config_size; config_buf++, line_size++, total_read++) 391 | { 392 | if ((line_size + 1) >= MAX_LINE_SIZE(out_size)) 393 | { 394 | return false; 395 | } 396 | 397 | if (line_size == 0) 398 | { 399 | if (*config_buf == '#' || *config_buf == ';') 400 | { 401 | continue; 402 | } 403 | 404 | if (*config_buf == '[') 405 | { 406 | break; 407 | } 408 | } 409 | 410 | *line_buf++ = *config_buf; 411 | } 412 | 413 | if (rini_get_node(line, key, (void*)out, out_size, type, line_size) == 1) 414 | { 415 | return true; 416 | } 417 | 418 | CLEAN_PTR(line, MAX_NAME, line_buf); 419 | } 420 | 421 | return false; 422 | } 423 | -------------------------------------------------------------------------------- /src/rini.h: -------------------------------------------------------------------------------- 1 | #ifndef RINI_H 2 | #define RINI_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define PTR_NOT_END(a) (*(a) != '\r' && *(a) != '\n' && *(a) != 0) 9 | 10 | #define CLEAN_PTR(a, b, c) do { \ 11 | memset(a, 0, b); \ 12 | (c) = (char*)(a); \ 13 | } while (0) 14 | 15 | #ifndef ARRAY_SIZE 16 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 17 | #endif 18 | 19 | #define MAX_NAME 256 20 | #define MAX_INT_STR_SIZE 32 21 | #define MAX_BOOL_KEY_SIZE 6 22 | #define MAX_LINE_SIZE(x) ((MAX_NAME + x) + 1) 23 | 24 | typedef enum _value_types_t { 25 | STRING_VAL, 26 | INT_VAL, 27 | BOOL_VAL 28 | } value_types_t; 29 | 30 | typedef enum _parser_flags_t { 31 | FLAGS_DEFAULT, 32 | EXPECT_QUOTE, 33 | LAST_ESCAPE_CHAR, 34 | ESCAPE_NOT_FOUND, 35 | END_EARLY, 36 | INT_NEG_NUMB 37 | } parser_flags_t; 38 | 39 | typedef enum _bool_type_t { 40 | BOOL_KEY_FALSE = 0, 41 | BOOL_KEY_TRUE = 1, 42 | BOOL_KEY_ERROR = 2 43 | } bool_type_t; 44 | 45 | typedef struct _bool_key_record_t { 46 | bool_type_t val; 47 | char* key; 48 | unsigned size; 49 | } bool_keys_t; 50 | 51 | int rini_get_key(const char* parent, const char* key, const char* config, unsigned config_size, const void* out, unsigned out_size, value_types_t type); 52 | 53 | #endif // RINI_H 54 | -------------------------------------------------------------------------------- /tests/TODO.md: -------------------------------------------------------------------------------- 1 | ## This relevant section will be completed soon. 2 | --------------------------------------------------------------------------------