├── .gitignore ├── Sources ├── CLibMaxMindDB │ ├── module.modulemap │ └── libmaxminddb-1.12.2 │ │ ├── include │ │ ├── maxminddb_config.h │ │ └── maxminddb.h │ │ └── src │ │ ├── data-pool.h │ │ ├── data-pool.c │ │ ├── maxminddb-compat-util.h │ │ └── maxminddb.c └── MaxMindDBSwift │ └── GeoIP2.swift ├── .github └── workflows │ ├── build-package.yml │ └── update-check.yml ├── Package.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /build 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | DerivedData/ 8 | .swiftpm/ 9 | +.version 10 | +.build-number 11 | *.zip 12 | -------------------------------------------------------------------------------- /Sources/CLibMaxMindDB/module.modulemap: -------------------------------------------------------------------------------- 1 | module CLibMaxMindDB { 2 | umbrella header "libmaxminddb-1.12.2/include/maxminddb.h" 3 | export * 4 | module * { export * } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/CLibMaxMindDB/libmaxminddb-1.12.2/include/maxminddb_config.h: -------------------------------------------------------------------------------- 1 | #ifndef MAXMINDDB_CONFIG_H 2 | #define MAXMINDDB_CONFIG_H 3 | 4 | #define PACKAGE_VERSION "1.12.2" 5 | 6 | #ifndef _WIN32 7 | #define HAVE_MMAP 1 8 | #endif 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /.github/workflows/build-package.yml: -------------------------------------------------------------------------------- 1 | name: Build Swift Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build-package: 10 | runs-on: macos-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Create build script 17 | env: 18 | BUILD_SCRIPT: ${{ secrets.BUILD_SCRIPT }} 19 | run: | 20 | echo "$BUILD_SCRIPT" > ./build.sh 21 | chmod +x ./build.sh 22 | 23 | - name: Run build script 24 | run: | 25 | ./build.sh 26 | rm ./build.sh 27 | 28 | - name: Upload artifacts 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: swift-package 32 | path: PackageOutput/ -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "MaxMindDBSwift", 6 | platforms: [.iOS(.v12), .macOS(.v10_15)], 7 | products: [ 8 | .library( 9 | name: "MaxMindDB", 10 | targets: ["MaxMindDB"] 11 | ) 12 | ], 13 | targets: [ 14 | .target( 15 | name: "MaxMindDB", 16 | dependencies: ["CLibMaxMindDB"], 17 | path: "Sources/MaxMindDBSwift" 18 | ), 19 | .target( 20 | name: "CLibMaxMindDB", 21 | path: "Sources/CLibMaxMindDB", 22 | sources: [ 23 | "libmaxminddb-1.12.2/src/maxminddb.c", 24 | "libmaxminddb-1.12.2/src/data-pool.c" 25 | ], 26 | publicHeadersPath: "libmaxminddb-1.12.2/include" 27 | ) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /Sources/CLibMaxMindDB/libmaxminddb-1.12.2/src/data-pool.h: -------------------------------------------------------------------------------- 1 | #ifndef DATA_POOL_H 2 | #define DATA_POOL_H 3 | 4 | #include "maxminddb.h" 5 | 6 | #include 7 | #include 8 | 9 | // This should be large enough that we never need to grow the array of pointers 10 | // to blocks. 32 is enough. Even starting out of with size 1 (1 struct), the 11 | // 32nd element alone will provide 2**32 structs as we exponentially increase 12 | // the number in each block. Being confident that we do not have to grow the 13 | // array lets us avoid writing code to do that. That code would be risky as it 14 | // would rarely be hit and likely not be well tested. 15 | #define DATA_POOL_NUM_BLOCKS 32 16 | 17 | // A pool of memory for MMDB_entry_data_list_s structs. This is so we can 18 | // allocate multiple up front rather than one at a time for performance 19 | // reasons. 20 | // 21 | // The order you add elements to it (by calling data_pool_alloc()) ends up as 22 | // the order of the list. 23 | // 24 | // The memory only grows. There is no support for releasing an element you take 25 | // back to the pool. 26 | typedef struct MMDB_data_pool_s { 27 | // Index of the current block we're allocating out of. 28 | size_t index; 29 | 30 | // The size of the current block, counting by structs. 31 | size_t size; 32 | 33 | // How many used in the current block, counting by structs. 34 | size_t used; 35 | 36 | // The current block we're allocating out of. 37 | MMDB_entry_data_list_s *block; 38 | 39 | // The size of each block. 40 | size_t sizes[DATA_POOL_NUM_BLOCKS]; 41 | 42 | // An array of pointers to blocks of memory holding space for list 43 | // elements. 44 | MMDB_entry_data_list_s *blocks[DATA_POOL_NUM_BLOCKS]; 45 | } MMDB_data_pool_s; 46 | 47 | bool can_multiply(size_t const, size_t const, size_t const); 48 | MMDB_data_pool_s *data_pool_new(size_t const); 49 | void data_pool_destroy(MMDB_data_pool_s *const); 50 | MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const); 51 | MMDB_entry_data_list_s *data_pool_to_list(MMDB_data_pool_s *const); 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /Sources/CLibMaxMindDB/libmaxminddb-1.12.2/src/data-pool.c: -------------------------------------------------------------------------------- 1 | #ifndef _POSIX_C_SOURCE 2 | #define _POSIX_C_SOURCE 200809L 3 | #endif 4 | 5 | #include "data-pool.h" 6 | #include "maxminddb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | // Allocate an MMDB_data_pool_s. It initially has space for size 13 | // MMDB_entry_data_list_s structs. 14 | MMDB_data_pool_s *data_pool_new(size_t const size) { 15 | MMDB_data_pool_s *const pool = calloc(1, sizeof(MMDB_data_pool_s)); 16 | if (!pool) { 17 | return NULL; 18 | } 19 | 20 | if (size == 0 || 21 | !can_multiply(SIZE_MAX, size, sizeof(MMDB_entry_data_list_s))) { 22 | data_pool_destroy(pool); 23 | return NULL; 24 | } 25 | pool->size = size; 26 | pool->blocks[0] = calloc(pool->size, sizeof(MMDB_entry_data_list_s)); 27 | if (!pool->blocks[0]) { 28 | data_pool_destroy(pool); 29 | return NULL; 30 | } 31 | pool->blocks[0]->pool = pool; 32 | 33 | pool->sizes[0] = size; 34 | 35 | pool->block = pool->blocks[0]; 36 | 37 | return pool; 38 | } 39 | 40 | // Determine if we can multiply m*n. We can do this if the result will be below 41 | // the given max. max will typically be SIZE_MAX. 42 | // 43 | // We want to know if we'll wrap around. 44 | bool can_multiply(size_t const max, size_t const m, size_t const n) { 45 | if (m == 0) { 46 | return false; 47 | } 48 | 49 | return n <= max / m; 50 | } 51 | 52 | // Clean up the data pool. 53 | void data_pool_destroy(MMDB_data_pool_s *const pool) { 54 | if (!pool) { 55 | return; 56 | } 57 | 58 | for (size_t i = 0; i <= pool->index; i++) { 59 | free(pool->blocks[i]); 60 | } 61 | 62 | free(pool); 63 | } 64 | 65 | // Claim a new struct from the pool. Doing this may cause the pool's size to 66 | // grow. 67 | MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const pool) { 68 | if (!pool) { 69 | return NULL; 70 | } 71 | 72 | if (pool->used < pool->size) { 73 | MMDB_entry_data_list_s *const element = pool->block + pool->used; 74 | pool->used++; 75 | return element; 76 | } 77 | 78 | // Take it from a new block of memory. 79 | 80 | size_t const new_index = pool->index + 1; 81 | if (new_index == DATA_POOL_NUM_BLOCKS) { 82 | // See the comment about not growing this on DATA_POOL_NUM_BLOCKS. 83 | return NULL; 84 | } 85 | 86 | if (!can_multiply(SIZE_MAX, pool->size, 2)) { 87 | return NULL; 88 | } 89 | size_t const new_size = pool->size * 2; 90 | 91 | if (!can_multiply(SIZE_MAX, new_size, sizeof(MMDB_entry_data_list_s))) { 92 | return NULL; 93 | } 94 | pool->blocks[new_index] = calloc(new_size, sizeof(MMDB_entry_data_list_s)); 95 | if (!pool->blocks[new_index]) { 96 | return NULL; 97 | } 98 | 99 | // We don't need to set this, but it's useful for introspection in tests. 100 | pool->blocks[new_index]->pool = pool; 101 | 102 | pool->index = new_index; 103 | pool->block = pool->blocks[pool->index]; 104 | 105 | pool->size = new_size; 106 | pool->sizes[pool->index] = pool->size; 107 | 108 | MMDB_entry_data_list_s *const element = pool->block; 109 | pool->used = 1; 110 | return element; 111 | } 112 | 113 | // Turn the structs in the array-like pool into a linked list. 114 | // 115 | // Before calling this function, the list isn't linked up. 116 | MMDB_entry_data_list_s *data_pool_to_list(MMDB_data_pool_s *const pool) { 117 | if (!pool) { 118 | return NULL; 119 | } 120 | 121 | if (pool->index == 0 && pool->used == 0) { 122 | return NULL; 123 | } 124 | 125 | for (size_t i = 0; i <= pool->index; i++) { 126 | MMDB_entry_data_list_s *const block = pool->blocks[i]; 127 | 128 | size_t size = pool->sizes[i]; 129 | if (i == pool->index) { 130 | size = pool->used; 131 | } 132 | 133 | for (size_t j = 0; j < size - 1; j++) { 134 | MMDB_entry_data_list_s *const cur = block + j; 135 | cur->next = block + j + 1; 136 | } 137 | 138 | if (i < pool->index) { 139 | MMDB_entry_data_list_s *const last = block + size - 1; 140 | last->next = pool->blocks[i + 1]; 141 | } 142 | } 143 | 144 | return pool->blocks[0]; 145 | } 146 | 147 | #ifdef TEST_DATA_POOL 148 | 149 | #include 150 | #include 151 | 152 | static void test_can_multiply(void); 153 | 154 | int main(void) { 155 | plan(NO_PLAN); 156 | test_can_multiply(); 157 | done_testing(); 158 | } 159 | 160 | static void test_can_multiply(void) { 161 | { 162 | ok(can_multiply(SIZE_MAX, 1, SIZE_MAX), "1*SIZE_MAX is ok"); 163 | } 164 | 165 | { 166 | ok(!can_multiply(SIZE_MAX, 2, SIZE_MAX), "2*SIZE_MAX is not ok"); 167 | } 168 | 169 | { 170 | ok(can_multiply(SIZE_MAX, 10240, sizeof(MMDB_entry_data_list_s)), 171 | "1024 entry_data_list_s's are okay"); 172 | } 173 | } 174 | 175 | #endif 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaxMindDBSwift 2 | 3 | A high-performance Swift wrapper for MaxMind's GeoIP2 database. This library provides an elegant Swift interface to [libmaxminddb](https://github.com/maxmind/libmaxminddb), offering efficient IP geolocation lookups with type-safe access to MaxMind's MMDB format. 4 | 5 | ## Key Features 6 | 7 | - High-performance Swift implementation with iOS 12.0+ and macOS 10.15+ support 8 | - Thread-safe concurrent IP lookups using Grand Central Dispatch 9 | - Zero-overhead memory management with direct C interop 10 | - Memory-efficient string caching for better performance 11 | - Type-safe API with comprehensive error handling 12 | - Simple integration via Swift Package Manager 13 | 14 | ## Installation 15 | 16 | ### Swift Package Manager 17 | 18 | Add the package dependency to your `Package.swift`: 19 | 20 | ```swift 21 | dependencies: [ 22 | .package(url: "https://github.com/SunboyGo/MaxMindDBSwift.git", from: "1.1.2") 23 | ] 24 | ``` 25 | 26 | Alternatively, in Xcode: 27 | 1. Navigate to File > Add Packages... 28 | 2. Input the repository URL: `https://github.com/SunboyGo/MaxMindDBSwift.git` 29 | 3. Select "Up to Next Major Version" for version rules 30 | 31 | ## Quick Start 32 | 33 | ```swift 34 | import MaxMindDB 35 | 36 | do { 37 | // Load the GeoIP database from your app bundle 38 | guard let fileURL = Bundle.main.url(forResource: "GeoLite2-Country", withExtension: "mmdb") else { 39 | print("GeoLite2-Country.mmdb not found in the app bundle") 40 | return 41 | } 42 | 43 | // Initialize the GeoIP2 database reader 44 | let geoIP = try GeoIP2(databasePath: fileURL.path) 45 | 46 | // Perform an IP lookup 47 | let result = try geoIP.lookup(ip: "8.8.8.8") 48 | 49 | // Display the complete lookup result 50 | print(result.prettyPrint()) 51 | 52 | /* Example output: 53 | { 54 | "continent": { 55 | "code": "NA", 56 | "geoname_id": 6255149, 57 | "names": { 58 | "de": "Nordamerika", 59 | "en": "North America", 60 | "es": "Norteamérica", 61 | "fr": "Amérique du Nord", 62 | "ja": "北アメリカ", 63 | "pt-BR": "América do Norte", 64 | "ru": "Северная Америка", 65 | "zh-CN": "北美洲" 66 | } 67 | }, 68 | "country": { 69 | "geoname_id": 6252001, 70 | "iso_code": "US", 71 | "names": { 72 | "de": "USA", 73 | "en": "United States", 74 | "es": "Estados Unidos", 75 | "fr": "États-Unis", 76 | "ja": "アメリカ合衆国", 77 | "pt-BR": "Estados Unidos", 78 | "ru": "США", 79 | "zh-CN": "美国" 80 | } 81 | }, 82 | "registered_country": { 83 | "geoname_id": 6252001, 84 | "iso_code": "US", 85 | "names": { 86 | "de": "USA", 87 | "en": "United States", 88 | "es": "Estados Unidos", 89 | "fr": "États-Unis", 90 | "ja": "アメリカ合衆国", 91 | "pt-BR": "Estados Unidos", 92 | "ru": "США", 93 | "zh-CN": "美国" 94 | } 95 | } 96 | } 97 | */ 98 | 99 | // Extract specific geolocation data 100 | if let country = result.data["country"] as? [String: Any], 101 | let names = country["names"] as? [String: String], 102 | let countryName = names["en"] { 103 | print("Country: \(countryName)") // Output: "United States" 104 | } 105 | 106 | if let country = result.data["country"] as? [String: Any], 107 | let isoCode = country["iso_code"] as? String { 108 | print("ISO Code: \(isoCode)") // Output: "US" 109 | } 110 | 111 | if let continent = result.data["continent"] as? [String: Any], 112 | let continentCode = continent["code"] as? String { 113 | print("Continent: \(continentCode)") // Output: "NA" 114 | } 115 | 116 | // Get result as JSON string 117 | if let jsonString = result.toJSON(prettyPrinted: true) { 118 | print("JSON Result:\n\(jsonString)") 119 | } 120 | 121 | // Alternatively, get JSON directly 122 | let jsonResult = try geoIP.lookupJSON(ip: "8.8.8.8") 123 | print("Direct JSON Result:\n\(jsonResult)") 124 | 125 | // For raw data with different formatting 126 | let rawJSON = try geoIP.getRawDataJSON(ip: "8.8.8.8") 127 | print("Raw JSON:\n\(rawJSON)") 128 | 129 | } catch { 130 | print("Lookup failed: \(error.localizedDescription)") 131 | } 132 | ``` 133 | 134 | ## Asynchronous Usage 135 | 136 | The library also supports asynchronous lookups for non-blocking operations: 137 | 138 | ```swift 139 | geoIP.lookupAsync(ip: "8.8.8.8") { result in 140 | switch result { 141 | case .success(let geoIPResult): 142 | print("Async lookup successful: \(geoIPResult.prettyPrint())") 143 | case .failure(let error): 144 | print("Async lookup failed: \(error.localizedDescription)") 145 | } 146 | } 147 | ``` 148 | 149 | ## Database Metadata 150 | 151 | You can access database metadata to get information about the database: 152 | 153 | ```swift 154 | do { 155 | let metadata = try geoIP.metadata() 156 | print("Database description: \(metadata["description"] ?? "Not available")") 157 | print("Database build date: \(metadata["build_epoch"] ?? "Not available")") 158 | } catch { 159 | print("Failed to get metadata: \(error.localizedDescription)") 160 | } 161 | ``` 162 | 163 | ## Thread Safety and Performance 164 | 165 | The `GeoIP2` class implements an efficient concurrent read model using a Grand Central Dispatch concurrent queue. This allows multiple threads to perform IP lookups simultaneously while maintaining thread safety. 166 | 167 | Performance optimizations include: 168 | - Memory-mapped database access (MMAP) using libmaxminddb for efficient I/O 169 | - String caching for frequently accessed values to reduce memory allocations 170 | - Direct pointer manipulation to minimize overhead 171 | 172 | ## Acknowledgments 173 | 174 | Special thanks to [MaxMind](https://www.maxmind.com/) for creating and maintaining the excellent [libmaxminddb](https://github.com/maxmind/libmaxminddb) C library, which forms the foundation of this project. 175 | 176 | ## License 177 | 178 | Released under the MIT License. See the [LICENSE](LICENSE) file for the complete license terms. 179 | -------------------------------------------------------------------------------- /Sources/CLibMaxMindDB/libmaxminddb-1.12.2/src/maxminddb-compat-util.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* *INDENT-OFF* */ 5 | 6 | /* The memmem, strdup, and strndup functions were all copied from the 7 | * FreeBSD source, along with the relevant copyright notice. 8 | * 9 | * It'd be nicer to simply use the functions available on the system if they 10 | * exist, but there doesn't seem to be a good way to detect them without also 11 | * defining things like _GNU_SOURCE, which we want to avoid, because then we 12 | * end up _accidentally_ using GNU features without noticing, which then 13 | * breaks on systems like OSX. 14 | * 15 | * C is fun! */ 16 | 17 | /* Applies to memmem implementation */ 18 | /*- 19 | * Copyright (c) 2005 Pascal Gloor 20 | * 21 | * Redistribution and use in source and binary forms, with or without 22 | * modification, are permitted provided that the following conditions 23 | * are met: 24 | * 1. Redistributions of source code must retain the above copyright 25 | * notice, this list of conditions and the following disclaimer. 26 | * 2. Redistributions in binary form must reproduce the above copyright 27 | * notice, this list of conditions and the following disclaimer in the 28 | * documentation and/or other materials provided with the distribution. 29 | * 3. The name of the author may not be used to endorse or promote 30 | * products derived from this software without specific prior written 31 | * permission. 32 | * 33 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 34 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 35 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 37 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 38 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 39 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 40 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 41 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 42 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 43 | * SUCH DAMAGE. 44 | */ 45 | static const void * 46 | mmdb_memmem(const void *l, size_t l_len, const void *s, size_t s_len) { 47 | const char *cur, *last; 48 | const char *cl = (const char *)l; 49 | const char *cs = (const char *)s; 50 | 51 | /* we need something to compare */ 52 | if (l_len == 0 || s_len == 0) 53 | return NULL; 54 | 55 | /* "s" must be smaller or equal to "l" */ 56 | if (l_len < s_len) 57 | return NULL; 58 | 59 | /* special case where s_len == 1 */ 60 | if (s_len == 1) 61 | return memchr(l, (int)*cs, l_len); 62 | 63 | /* the last position where its possible to find "s" in "l" */ 64 | last = cl + l_len - s_len; 65 | 66 | for (cur = cl; cur <= last; cur++) 67 | if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) 68 | return cur; 69 | 70 | return NULL; 71 | } 72 | 73 | /* Applies to strnlen implementation */ 74 | /*- 75 | * Copyright (c) 2009 David Schultz 76 | * All rights reserved. 77 | * 78 | * Redistribution and use in source and binary forms, with or without 79 | * modification, are permitted provided that the following conditions 80 | * are met: 81 | * 1. Redistributions of source code must retain the above copyright 82 | * notice, this list of conditions and the following disclaimer. 83 | * 2. Redistributions in binary form must reproduce the above copyright 84 | * notice, this list of conditions and the following disclaimer in the 85 | * documentation and/or other materials provided with the distribution. 86 | * 87 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 88 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 89 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 90 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 91 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 92 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 93 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 94 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 95 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 96 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 97 | * SUCH DAMAGE. 98 | */ 99 | static size_t mmdb_strnlen(const char *s, size_t maxlen) { 100 | size_t len; 101 | 102 | for (len = 0; len < maxlen; len++, s++) { 103 | if (!*s) 104 | break; 105 | } 106 | return (len); 107 | } 108 | 109 | /* Applies to strdup and strndup implementation */ 110 | /* 111 | * Copyright (c) 1988, 1993 112 | * The Regents of the University of California. All rights reserved. 113 | * 114 | * Redistribution and use in source and binary forms, with or without 115 | * modification, are permitted provided that the following conditions 116 | * are met: 117 | * 1. Redistributions of source code must retain the above copyright 118 | * notice, this list of conditions and the following disclaimer. 119 | * 2. Redistributions in binary form must reproduce the above copyright 120 | * notice, this list of conditions and the following disclaimer in the 121 | * documentation and/or other materials provided with the distribution. 122 | * 3. Neither the name of the University nor the names of its contributors 123 | * may be used to endorse or promote products derived from this software 124 | * without specific prior written permission. 125 | * 126 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 127 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 128 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 129 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 130 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 131 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 132 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 133 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 134 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 135 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 136 | * SUCH DAMAGE. 137 | */ 138 | static char *mmdb_strdup(const char *str) { 139 | size_t len; 140 | char *copy; 141 | 142 | len = strlen(str) + 1; 143 | if ((copy = malloc(len)) == NULL) 144 | return (NULL); 145 | memcpy(copy, str, len); 146 | return (copy); 147 | } 148 | 149 | static char *mmdb_strndup(const char *str, size_t n) { 150 | size_t len; 151 | char *copy; 152 | 153 | len = mmdb_strnlen(str, n); 154 | if ((copy = malloc(len + 1)) == NULL) 155 | return (NULL); 156 | memcpy(copy, str, len); 157 | copy[len] = '\0'; 158 | return (copy); 159 | } 160 | /* *INDENT-ON* */ 161 | -------------------------------------------------------------------------------- /Sources/CLibMaxMindDB/libmaxminddb-1.12.2/include/maxminddb.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | 5 | #ifndef MAXMINDDB_H 6 | #define MAXMINDDB_H 7 | 8 | #include "maxminddb_config.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #ifdef _WIN32 16 | #include 17 | #include 18 | /* libmaxminddb package version from configure */ 19 | 20 | #if defined(_MSC_VER) 21 | /* MSVC doesn't define signed size_t, copy it from configure */ 22 | #define ssize_t SSIZE_T 23 | 24 | /* MSVC doesn't support restricted pointers */ 25 | #define restrict 26 | #endif 27 | #else 28 | #include 29 | #include 30 | #include 31 | #endif 32 | 33 | #define MMDB_DATA_TYPE_EXTENDED (0) 34 | #define MMDB_DATA_TYPE_POINTER (1) 35 | #define MMDB_DATA_TYPE_UTF8_STRING (2) 36 | #define MMDB_DATA_TYPE_DOUBLE (3) 37 | #define MMDB_DATA_TYPE_BYTES (4) 38 | #define MMDB_DATA_TYPE_UINT16 (5) 39 | #define MMDB_DATA_TYPE_UINT32 (6) 40 | #define MMDB_DATA_TYPE_MAP (7) 41 | #define MMDB_DATA_TYPE_INT32 (8) 42 | #define MMDB_DATA_TYPE_UINT64 (9) 43 | #define MMDB_DATA_TYPE_UINT128 (10) 44 | #define MMDB_DATA_TYPE_ARRAY (11) 45 | #define MMDB_DATA_TYPE_CONTAINER (12) 46 | #define MMDB_DATA_TYPE_END_MARKER (13) 47 | #define MMDB_DATA_TYPE_BOOLEAN (14) 48 | #define MMDB_DATA_TYPE_FLOAT (15) 49 | 50 | #define MMDB_RECORD_TYPE_SEARCH_NODE (0) 51 | #define MMDB_RECORD_TYPE_EMPTY (1) 52 | #define MMDB_RECORD_TYPE_DATA (2) 53 | #define MMDB_RECORD_TYPE_INVALID (3) 54 | 55 | /* flags for open */ 56 | #define MMDB_MODE_MMAP (1) 57 | #define MMDB_MODE_MASK (7) 58 | 59 | /* error codes */ 60 | #define MMDB_SUCCESS (0) 61 | #define MMDB_FILE_OPEN_ERROR (1) 62 | #define MMDB_CORRUPT_SEARCH_TREE_ERROR (2) 63 | #define MMDB_INVALID_METADATA_ERROR (3) 64 | #define MMDB_IO_ERROR (4) 65 | #define MMDB_OUT_OF_MEMORY_ERROR (5) 66 | #define MMDB_UNKNOWN_DATABASE_FORMAT_ERROR (6) 67 | #define MMDB_INVALID_DATA_ERROR (7) 68 | #define MMDB_INVALID_LOOKUP_PATH_ERROR (8) 69 | #define MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR (9) 70 | #define MMDB_INVALID_NODE_NUMBER_ERROR (10) 71 | #define MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR (11) 72 | 73 | #if !(MMDB_UINT128_IS_BYTE_ARRAY) 74 | #if MMDB_UINT128_USING_MODE 75 | typedef unsigned int mmdb_uint128_t __attribute__((__mode__(TI))); 76 | #else 77 | typedef unsigned __int128 mmdb_uint128_t; 78 | #endif 79 | #endif 80 | 81 | /* This is a pointer into the data section for a given IP address lookup */ 82 | typedef struct MMDB_entry_s { 83 | const struct MMDB_s *mmdb; 84 | uint32_t offset; 85 | } MMDB_entry_s; 86 | 87 | typedef struct MMDB_lookup_result_s { 88 | bool found_entry; 89 | MMDB_entry_s entry; 90 | uint16_t netmask; 91 | } MMDB_lookup_result_s; 92 | 93 | typedef struct MMDB_entry_data_s { 94 | bool has_data; 95 | union { 96 | uint32_t pointer; 97 | const char *utf8_string; 98 | double double_value; 99 | const uint8_t *bytes; 100 | uint16_t uint16; 101 | uint32_t uint32; 102 | int32_t int32; 103 | uint64_t uint64; 104 | #if MMDB_UINT128_IS_BYTE_ARRAY 105 | uint8_t uint128[16]; 106 | #else 107 | mmdb_uint128_t uint128; 108 | #endif 109 | bool boolean; 110 | float float_value; 111 | }; 112 | /* This is a 0 if a given entry cannot be found. This can only happen 113 | * when a call to MMDB_(v)get_value() asks for hash keys or array 114 | * indices that don't exist. */ 115 | uint32_t offset; 116 | /* This is the next entry in the data section, but it's really only 117 | * relevant for entries that part of a larger map or array 118 | * struct. There's no good reason for an end user to look at this 119 | * directly. */ 120 | uint32_t offset_to_next; 121 | /* This is only valid for strings, utf8_strings or binary data */ 122 | uint32_t data_size; 123 | /* This is an MMDB_DATA_TYPE_* constant */ 124 | uint32_t type; 125 | } MMDB_entry_data_s; 126 | 127 | /* This is the return type when someone asks for all the entry data in a map or 128 | * array */ 129 | typedef struct MMDB_entry_data_list_s { 130 | MMDB_entry_data_s entry_data; 131 | struct MMDB_entry_data_list_s *next; 132 | void *pool; 133 | } MMDB_entry_data_list_s; 134 | 135 | typedef struct MMDB_description_s { 136 | const char *language; 137 | const char *description; 138 | } MMDB_description_s; 139 | 140 | /* WARNING: do not add new fields to this struct without bumping the SONAME. 141 | * The struct is allocated by the users of this library and increasing the 142 | * size will cause existing users to allocate too little space when the shared 143 | * library is upgraded */ 144 | typedef struct MMDB_metadata_s { 145 | uint32_t node_count; 146 | uint16_t record_size; 147 | uint16_t ip_version; 148 | const char *database_type; 149 | struct { 150 | size_t count; 151 | const char **names; 152 | } languages; 153 | uint16_t binary_format_major_version; 154 | uint16_t binary_format_minor_version; 155 | uint64_t build_epoch; 156 | struct { 157 | size_t count; 158 | MMDB_description_s **descriptions; 159 | } description; 160 | /* See above warning before adding fields */ 161 | } MMDB_metadata_s; 162 | 163 | /* WARNING: do not add new fields to this struct without bumping the SONAME. 164 | * The struct is allocated by the users of this library and increasing the 165 | * size will cause existing users to allocate too little space when the shared 166 | * library is upgraded */ 167 | typedef struct MMDB_ipv4_start_node_s { 168 | uint16_t netmask; 169 | uint32_t node_value; 170 | /* See above warning before adding fields */ 171 | } MMDB_ipv4_start_node_s; 172 | 173 | /* WARNING: do not add new fields to this struct without bumping the SONAME. 174 | * The struct is allocated by the users of this library and increasing the 175 | * size will cause existing users to allocate too little space when the shared 176 | * library is upgraded */ 177 | typedef struct MMDB_s { 178 | uint32_t flags; 179 | const char *filename; 180 | ssize_t file_size; 181 | const uint8_t *file_content; 182 | const uint8_t *data_section; 183 | uint32_t data_section_size; 184 | const uint8_t *metadata_section; 185 | uint32_t metadata_section_size; 186 | uint16_t full_record_byte_size; 187 | uint16_t depth; 188 | MMDB_ipv4_start_node_s ipv4_start_node; 189 | MMDB_metadata_s metadata; 190 | /* See above warning before adding fields */ 191 | } MMDB_s; 192 | 193 | typedef struct MMDB_search_node_s { 194 | uint64_t left_record; 195 | uint64_t right_record; 196 | uint8_t left_record_type; 197 | uint8_t right_record_type; 198 | MMDB_entry_s left_record_entry; 199 | MMDB_entry_s right_record_entry; 200 | } MMDB_search_node_s; 201 | 202 | extern int 203 | MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb); 204 | extern MMDB_lookup_result_s MMDB_lookup_string(const MMDB_s *const mmdb, 205 | const char *const ipstr, 206 | int *const gai_error, 207 | int *const mmdb_error); 208 | extern MMDB_lookup_result_s 209 | MMDB_lookup_sockaddr(const MMDB_s *const mmdb, 210 | const struct sockaddr *const sockaddr, 211 | int *const mmdb_error); 212 | extern int MMDB_read_node(const MMDB_s *const mmdb, 213 | uint32_t node_number, 214 | MMDB_search_node_s *const node); 215 | extern int MMDB_get_value(MMDB_entry_s *const start, 216 | MMDB_entry_data_s *const entry_data, 217 | ...); 218 | extern int MMDB_vget_value(MMDB_entry_s *const start, 219 | MMDB_entry_data_s *const entry_data, 220 | va_list va_path); 221 | extern int MMDB_aget_value(MMDB_entry_s *const start, 222 | MMDB_entry_data_s *const entry_data, 223 | const char *const *const path); 224 | extern int MMDB_get_metadata_as_entry_data_list( 225 | const MMDB_s *const mmdb, MMDB_entry_data_list_s **const entry_data_list); 226 | extern int 227 | MMDB_get_entry_data_list(MMDB_entry_s *start, 228 | MMDB_entry_data_list_s **const entry_data_list); 229 | extern void 230 | MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list); 231 | extern void MMDB_close(MMDB_s *const mmdb); 232 | extern const char *MMDB_lib_version(void); 233 | extern int 234 | MMDB_dump_entry_data_list(FILE *const stream, 235 | MMDB_entry_data_list_s *const entry_data_list, 236 | int indent); 237 | extern const char *MMDB_strerror(int error_code); 238 | 239 | #endif /* MAXMINDDB_H */ 240 | 241 | #ifdef __cplusplus 242 | } 243 | #endif 244 | -------------------------------------------------------------------------------- /.github/workflows/update-check.yml: -------------------------------------------------------------------------------- 1 | name: Check for Updates 2 | 3 | on: 4 | schedule: 5 | - cron: '0 */12 * * *' # 每12小时检查一次 6 | workflow_dispatch: # 允许手动触发 7 | 8 | jobs: 9 | check-and-update: 10 | runs-on: macos-latest 11 | 12 | steps: 13 | - name: Checkout with token 14 | uses: actions/checkout@v4 15 | with: 16 | token: ${{ secrets.PAT }} # 使用PAT确保有推送权限 17 | fetch-depth: 0 # 获取完整历史 18 | 19 | - name: Install jq 20 | run: | 21 | brew install jq 22 | 23 | - name: Check libmaxminddb for updates 24 | id: check 25 | env: 26 | GH_TOKEN: ${{ secrets.PAT }} # 使用PAT进行API请求身份验证 27 | run: | 28 | # 当前使用的版本 29 | CURRENT_VERSION="1.12.1" 30 | 31 | # 获取最新版本 - 使用授权请求避免API限制 32 | echo "Fetching latest version from GitHub API..." 33 | API_RESPONSE=$(curl -s -H "Authorization: token $GH_TOKEN" https://api.github.com/repos/maxmind/libmaxminddb/releases/latest) 34 | echo "API Response received" 35 | 36 | # 打印完整API响应用于调试(脱敏) 37 | echo "API Response preview (first 100 chars):" 38 | echo "${API_RESPONSE:0:100}..." 39 | 40 | # 检查API响应是否为空 41 | if [ -z "$API_RESPONSE" ]; then 42 | echo "Error: Empty API response" 43 | exit 1 44 | fi 45 | 46 | # 使用jq解析JSON并检查tag_name是否存在 47 | if echo "$API_RESPONSE" | jq -e .tag_name > /dev/null 2>&1; then 48 | LATEST_VERSION=$(echo "$API_RESPONSE" | jq -r .tag_name) 49 | echo "Successfully parsed API response" 50 | 51 | # 确保不是null 52 | if [ "$LATEST_VERSION" = "null" ]; then 53 | echo "Error: API returned null tag_name" 54 | exit 1 55 | fi 56 | else 57 | echo "Error: Could not find tag_name in API response" 58 | echo "Response contains these keys:" 59 | echo "$API_RESPONSE" | jq 'keys' 60 | exit 1 61 | fi 62 | 63 | # 移除版本号前的 'v' 如果存在 64 | LATEST_VERSION=${LATEST_VERSION#v} 65 | 66 | echo "Latest version from API: $LATEST_VERSION" 67 | echo "Current version: $CURRENT_VERSION" 68 | 69 | # 再次验证版本号有效性 70 | if [[ ! "$LATEST_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 71 | echo "Error: Invalid version format: $LATEST_VERSION" 72 | exit 1 73 | fi 74 | 75 | if [ "$LATEST_VERSION" = "$CURRENT_VERSION" ]; then 76 | echo "No updates available" 77 | echo "update_needed=false" >> $GITHUB_OUTPUT 78 | exit 0 79 | fi 80 | 81 | # 设置libmaxminddb版本号 82 | echo "LIBMAXMINDDB_VERSION=$LATEST_VERSION" >> $GITHUB_ENV 83 | echo "update_needed=true" >> $GITHUB_OUTPUT 84 | 85 | - name: Calculate next version 86 | if: steps.check.outputs.update_needed == 'true' 87 | id: semver 88 | run: | 89 | # 获取最新的标签 90 | git fetch --tags 91 | 92 | # 默认起始版本为 1.1.0 93 | DEFAULT_VERSION="1.1.0" 94 | 95 | # 尝试获取最新版本标签 96 | LATEST_TAG=$(git tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1) 97 | 98 | if [ -z "$LATEST_TAG" ]; then 99 | # 如果没有找到符合格式的标签,使用默认版本 100 | NEXT_VERSION="$DEFAULT_VERSION" 101 | echo "No previous version found. Starting with $NEXT_VERSION" 102 | else 103 | # 解析版本号 104 | MAJOR=$(echo $LATEST_TAG | cut -d. -f1) 105 | MINOR=$(echo $LATEST_TAG | cut -d. -f2) 106 | PATCH=$(echo $LATEST_TAG | cut -d. -f3) 107 | 108 | # 递增补丁版本号 109 | PATCH=$((PATCH + 1)) 110 | 111 | NEXT_VERSION="$MAJOR.$MINOR.$PATCH" 112 | echo "Previous version: $LATEST_TAG" 113 | echo "Next version: $NEXT_VERSION" 114 | fi 115 | 116 | # 设置版本号环境变量 117 | echo "VERSION=$NEXT_VERSION" >> $GITHUB_ENV 118 | 119 | - name: Update source files 120 | if: steps.check.outputs.update_needed == 'true' 121 | env: 122 | GH_TOKEN: ${{ secrets.PAT }} # 添加身份验证令牌 123 | run: | 124 | # 再次验证 LIBMAXMINDDB_VERSION 不为空 125 | if [ -z "$LIBMAXMINDDB_VERSION" ] || [ "$LIBMAXMINDDB_VERSION" = "null" ]; then 126 | echo "Error: Invalid LIBMAXMINDDB_VERSION: $LIBMAXMINDDB_VERSION" 127 | exit 1 128 | fi 129 | 130 | echo "Using libmaxminddb version: $LIBMAXMINDDB_VERSION" 131 | 132 | # 删除所有旧版本 133 | echo "Cleaning up all previous versions..." 134 | find "Sources/CLibMaxMindDB" -type d -name "libmaxminddb-*" -exec rm -rf {} + 2>/dev/null || true 135 | 136 | # 克隆并更新源码 - 使用授权以避免限制 137 | git clone https://x-access-token:${GH_TOKEN}@github.com/maxmind/libmaxminddb.git temp 138 | cd temp 139 | git fetch --tags 140 | 141 | # 检查标签是否存在 142 | if git rev-parse --verify "v$LIBMAXMINDDB_VERSION" >/dev/null 2>&1; then 143 | echo "Checking out tag v$LIBMAXMINDDB_VERSION" 144 | git checkout "v$LIBMAXMINDDB_VERSION" 145 | elif git rev-parse --verify "$LIBMAXMINDDB_VERSION" >/dev/null 2>&1; then 146 | echo "Checking out tag $LIBMAXMINDDB_VERSION" 147 | git checkout "$LIBMAXMINDDB_VERSION" 148 | else 149 | echo "Tag not found for version $LIBMAXMINDDB_VERSION" 150 | echo "Available tags:" 151 | git tag | grep -E "v?$LIBMAXMINDDB_VERSION" || echo "No matching tags" 152 | 153 | # 尝试使用最新的提交 154 | echo "Trying to use latest commit instead..." 155 | git checkout main || git checkout master 156 | fi 157 | 158 | # 复制所有必需文件 159 | mkdir -p "../Sources/CLibMaxMindDB/libmaxminddb-$LIBMAXMINDDB_VERSION/include" 160 | mkdir -p "../Sources/CLibMaxMindDB/libmaxminddb-$LIBMAXMINDDB_VERSION/src" 161 | 162 | # 复制源文件 163 | cp src/maxminddb.c "../Sources/CLibMaxMindDB/libmaxminddb-$LIBMAXMINDDB_VERSION/src/" 164 | cp src/data-pool.c "../Sources/CLibMaxMindDB/libmaxminddb-$LIBMAXMINDDB_VERSION/src/" 165 | 166 | # 复制头文件 167 | cp include/maxminddb.h "../Sources/CLibMaxMindDB/libmaxminddb-$LIBMAXMINDDB_VERSION/include/" 168 | 169 | # 复制内部头文件 170 | cp src/data-pool.h "../Sources/CLibMaxMindDB/libmaxminddb-$LIBMAXMINDDB_VERSION/src/" 171 | cp src/maxminddb-compat-util.h "../Sources/CLibMaxMindDB/libmaxminddb-$LIBMAXMINDDB_VERSION/src/" 172 | 173 | # 更新配置文件 174 | cat > "../Sources/CLibMaxMindDB/libmaxminddb-$LIBMAXMINDDB_VERSION/include/maxminddb_config.h" << EOF 175 | #ifndef MAXMINDDB_CONFIG_H 176 | #define MAXMINDDB_CONFIG_H 177 | 178 | #define PACKAGE_VERSION "$LIBMAXMINDDB_VERSION" 179 | 180 | #ifndef _WIN32 181 | #define HAVE_MMAP 1 182 | #endif 183 | 184 | #endif 185 | EOF 186 | 187 | cd .. 188 | rm -rf temp 189 | 190 | # 更新 Package.swift 191 | sed -i '' "s/libmaxminddb-[0-9.][0-9.]*\/src/libmaxminddb-$LIBMAXMINDDB_VERSION\/src/g" Package.swift 192 | sed -i '' "s/libmaxminddb-[0-9.][0-9.]*\/include/libmaxminddb-$LIBMAXMINDDB_VERSION\/include/g" Package.swift 193 | 194 | # 更新 module.modulemap 195 | sed -i '' "s/libmaxminddb-[0-9.][0-9.]*\/include/libmaxminddb-$LIBMAXMINDDB_VERSION\/include/g" Sources/CLibMaxMindDB/module.modulemap 196 | 197 | - name: Run build script 198 | if: steps.check.outputs.update_needed == 'true' 199 | env: 200 | BUILD_SCRIPT: ${{ secrets.BUILD_SCRIPT }} 201 | run: | 202 | # 创建临时目录 203 | mkdir -p build_output 204 | cd build_output 205 | 206 | # 创建并运行构建脚本 207 | echo "$BUILD_SCRIPT" > ./build.sh 208 | chmod +x ./build.sh 209 | ./build.sh 210 | 211 | # 如果生成了嵌套的 MaxMindDBSwift 目录,将内容复制到正确位置 212 | if [ -d "MaxMindDBSwift" ]; then 213 | echo "Copying files from nested directory to root..." 214 | # 复制文件到仓库根目录 215 | cp -R MaxMindDBSwift/* ../ 216 | fi 217 | 218 | # 返回根目录并清理 219 | cd .. 220 | rm -rf build_output 221 | 222 | # 检查是否有嵌套目录 223 | if [ -d "MaxMindDBSwift" ]; then 224 | echo "Removing nested MaxMindDBSwift directory..." 225 | rm -rf MaxMindDBSwift 226 | fi 227 | 228 | # 再次清理所有冗余版本 229 | echo "Final cleanup of redundant versions..." 230 | # 获取当前被使用的版本号 231 | USED_VERSION=$(grep -o 'libmaxminddb-[0-9.]*' Package.swift | head -1 | cut -d'-' -f2) 232 | echo "Current version in use: $USED_VERSION" 233 | 234 | # 删除所有不是当前版本的目录 235 | find "Sources/CLibMaxMindDB" -type d -name "libmaxminddb-*" | grep -v "libmaxminddb-$USED_VERSION" | xargs rm -rf 236 | 237 | - name: Commit changes 238 | if: steps.check.outputs.update_needed == 'true' 239 | run: | 240 | git config --local user.email "action@github.com" 241 | git config --local user.name "GitHub Action" 242 | 243 | # 添加所有更改 244 | git add -A 245 | 246 | # 提交更改 247 | git commit -m "Update libmaxminddb to version $LIBMAXMINDDB_VERSION" 248 | 249 | # 确保能看到所有错误输出 250 | set -x 251 | 252 | # 推送提交 253 | git push 254 | 255 | echo "Changes committed and pushed." 256 | 257 | - name: Create and push tag 258 | if: steps.check.outputs.update_needed == 'true' 259 | run: | 260 | git config --local user.email "action@github.com" 261 | git config --local user.name "GitHub Action" 262 | 263 | # 创建标签(不带v前缀) 264 | git tag "$VERSION" 265 | 266 | # 推送标签 267 | git push origin "$VERSION" 268 | 269 | echo "Tagged version $VERSION and pushed tag." 270 | 271 | - name: Create Release 272 | if: steps.check.outputs.update_needed == 'true' 273 | uses: softprops/action-gh-release@v1 274 | with: 275 | tag_name: ${{ env.VERSION }} 276 | name: Release ${{ env.VERSION }} 277 | body: | 278 | Updated to libmaxminddb version ${{ env.LIBMAXMINDDB_VERSION }} 279 | 280 | ### Changes 281 | - Updated libmaxminddb source code to version ${{ env.LIBMAXMINDDB_VERSION }} 282 | 283 | ### Installation 284 | ```swift 285 | dependencies: [ 286 | .package(url: "https://github.com/SunboyGo/MaxMindDBSwift.git", from: "${{ env.VERSION }}") 287 | ] 288 | ``` 289 | draft: false 290 | prerelease: false 291 | env: 292 | GITHUB_TOKEN: ${{ secrets.PAT }} 293 | -------------------------------------------------------------------------------- /Sources/MaxMindDBSwift/GeoIP2.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CLibMaxMindDB 3 | 4 | /// Represents errors that can occur during GeoIP2 operations 5 | public enum GeoIP2Error: Error, LocalizedError { 6 | /// Failed to open database 7 | case openFailed(code: Int32) 8 | /// Failed to lookup IP address 9 | case lookupFailed(code: Int32, gaiError: Int32?) 10 | /// Failed to parse data 11 | case dataParsingFailed(reason: String? = nil) 12 | /// Invalid database type 13 | case invalidDatabaseType 14 | 15 | public var errorDescription: String? { 16 | switch self { 17 | case .invalidDatabaseType: 18 | return "Invalid database type (requires GeoIP2 format)" 19 | case .openFailed(let code): 20 | return "Failed to open database: \(code) - \(mmdbStatusDescription(code))" 21 | case .lookupFailed(let code, let gaiError): 22 | if let gaiError = gaiError { 23 | return "Failed to lookup: mmdb error \(code) (\(mmdbStatusDescription(code))), network error \(gaiError)" 24 | } 25 | return "Failed to lookup: error \(code) - \(mmdbStatusDescription(code))" 26 | case .dataParsingFailed(let reason): 27 | if let reason = reason { 28 | return "Failed to parse data: \(reason)" 29 | } 30 | return "Failed to parse data" 31 | } 32 | } 33 | 34 | /// Get description for MMDB status code 35 | private func mmdbStatusDescription(_ code: Int32) -> String { 36 | switch code { 37 | case MMDB_SUCCESS: 38 | return "Success" 39 | case MMDB_FILE_OPEN_ERROR: 40 | return "File open error" 41 | case MMDB_CORRUPT_SEARCH_TREE_ERROR: 42 | return "Corrupt search tree" 43 | case MMDB_INVALID_METADATA_ERROR: 44 | return "Invalid metadata" 45 | case MMDB_IO_ERROR: 46 | return "I/O error" 47 | case MMDB_OUT_OF_MEMORY_ERROR: 48 | return "Out of memory" 49 | case MMDB_UNKNOWN_DATABASE_FORMAT_ERROR: 50 | return "Unknown database format" 51 | case MMDB_INVALID_DATA_ERROR: 52 | return "Invalid data" 53 | case MMDB_INVALID_LOOKUP_PATH_ERROR: 54 | return "Invalid lookup path" 55 | case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR: 56 | return "Lookup path does not match data" 57 | case MMDB_INVALID_NODE_NUMBER_ERROR: 58 | return "Invalid node number" 59 | case MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR: 60 | return "IPv6 lookup in IPv4 database" 61 | default: 62 | return "Unknown error" 63 | } 64 | } 65 | } 66 | 67 | /// Represents the result of a GeoIP2 query 68 | public struct GeoIP2Result { 69 | /// Raw data dictionary 70 | public let data: [String: Any] 71 | 72 | /// Create a new GeoIP2 result 73 | /// - Parameter data: Raw data dictionary 74 | public init(data: [String: Any]) { 75 | self.data = data 76 | } 77 | 78 | /// Format the result as a readable string, preserving the original data structure 79 | /// - Parameter indent: Indentation string 80 | /// - Returns: Formatted string 81 | public func prettyPrint(indent: String = "") -> String { 82 | return formatValue(data, indent: indent) 83 | } 84 | 85 | /// Convert the result to a JSON string 86 | /// - Parameter prettyPrinted: Whether to pretty-print the output 87 | /// - Returns: JSON string, or nil if conversion fails 88 | public func toJSON(prettyPrinted: Bool = true) -> String? { 89 | let options: JSONSerialization.WritingOptions = prettyPrinted ? [.prettyPrinted] : [] 90 | if let jsonData = try? JSONSerialization.data(withJSONObject: data, options: options) { 91 | return String(data: jsonData, encoding: .utf8) 92 | } 93 | return nil 94 | } 95 | 96 | /// Format any value as a string 97 | /// - Parameters: 98 | /// - value: Value to format 99 | /// - indent: Indentation string 100 | /// - Returns: Formatted string 101 | private func formatValue(_ value: Any, indent: String) -> String { 102 | switch value { 103 | case let dict as [String: Any]: 104 | if dict.isEmpty { 105 | return "{}" 106 | } 107 | 108 | var result = "{\n" 109 | // Sort keys to maintain consistent output order 110 | for (key, val) in dict.sorted(by: { $0.key < $1.key }) { 111 | let valueStr = formatValue(val, indent: indent + " ") 112 | result += "\(indent) \"\(key)\": \(valueStr),\n" 113 | } 114 | // Remove the last comma 115 | if result.hasSuffix(",\n") { 116 | result.removeLast(2) 117 | result += "\n" 118 | } 119 | result += "\(indent)}" 120 | return result 121 | 122 | case let array as [Any]: 123 | if array.isEmpty { 124 | return "[]" 125 | } 126 | 127 | var result = "[\n" 128 | for item in array { 129 | result += "\(indent) \(formatValue(item, indent: indent + " ")),\n" 130 | } 131 | // Remove the last comma 132 | if result.hasSuffix(",\n") { 133 | result.removeLast(2) 134 | result += "\n" 135 | } 136 | result += "\(indent)]" 137 | return result 138 | 139 | case let str as String: 140 | return "\"\(str)\"" 141 | 142 | case let num as NSNumber: 143 | return "\(num)" 144 | 145 | case let bool as Bool: 146 | return bool ? "true" : "false" 147 | 148 | case is NSNull: 149 | return "null" 150 | 151 | default: 152 | return "\(value)" 153 | } 154 | } 155 | } 156 | 157 | /// GeoIP2 database access class 158 | public final class GeoIP2 { 159 | /// Internal MMDB pointer 160 | private let mmdb: UnsafeMutablePointer 161 | /// Queue for thread safety 162 | private let queue = DispatchQueue(label: "com.geoip.queue", attributes: .concurrent) 163 | 164 | /// Initialize a GeoIP2 instance 165 | /// - Parameter databasePath: Path to the database file 166 | /// - Throws: GeoIP2Error if opening the database fails 167 | public init(databasePath: String) throws { 168 | // Directly allocate MMDB structure memory 169 | let mmdbPtr = UnsafeMutablePointer.allocate(capacity: 1) 170 | mmdbPtr.pointee = MMDB_s() 171 | 172 | // Open the database 173 | let status = MMDB_open(databasePath, UInt32(MMDB_MODE_MMAP), mmdbPtr) 174 | 175 | // Check open status 176 | guard status == MMDB_SUCCESS else { 177 | mmdbPtr.deallocate() 178 | throw GeoIP2Error.openFailed(code: status) 179 | } 180 | 181 | self.mmdb = mmdbPtr 182 | } 183 | 184 | /// Synchronously lookup IP address information 185 | /// - Parameter ip: IP address string 186 | /// - Returns: Query result 187 | /// - Throws: GeoIP2Error if lookup fails 188 | public func lookup(ip: String) throws -> GeoIP2Result { 189 | try ip.withCString { cString in 190 | var gai_error: Int32 = 0 191 | var mmdb_error: Int32 = 0 192 | 193 | // Execute query 194 | let result = MMDB_lookup_string(mmdb, cString, &gai_error, &mmdb_error) 195 | 196 | // Check MMDB error 197 | if mmdb_error != MMDB_SUCCESS { 198 | throw GeoIP2Error.lookupFailed(code: mmdb_error, gaiError: nil) 199 | } 200 | 201 | // Check network error 202 | if gai_error != 0 { 203 | throw GeoIP2Error.lookupFailed(code: mmdb_error, gaiError: gai_error) 204 | } 205 | 206 | // If no entry is found, return empty result 207 | guard result.found_entry else { 208 | return GeoIP2Result(data: [:]) 209 | } 210 | 211 | // Parse complete data 212 | var entry = result.entry 213 | let fullData = try parseFullData(entry: &entry) 214 | 215 | return GeoIP2Result(data: fullData) 216 | } 217 | } 218 | 219 | /// Asynchronously lookup IP address information 220 | /// - Parameters: 221 | /// - ip: IP address string 222 | /// - completion: Completion callback, returns result or error 223 | public func lookupAsync(ip: String, completion: @escaping (Result) -> Void) { 224 | queue.async { 225 | do { 226 | let result = try self.lookup(ip: ip) 227 | completion(.success(result)) 228 | } catch { 229 | completion(.failure(error)) 230 | } 231 | } 232 | } 233 | 234 | /// Parse complete data 235 | /// - Parameter entry: MMDB entry 236 | /// - Returns: Parsed data dictionary 237 | /// - Throws: GeoIP2Error if parsing fails 238 | private func parseFullData(entry: inout MMDB_entry_s) throws -> [String: Any] { 239 | var entryList: UnsafeMutablePointer? 240 | let status = MMDB_get_entry_data_list(&entry, &entryList) 241 | 242 | // Ensure resources are released 243 | defer { 244 | if let list = entryList { 245 | MMDB_free_entry_data_list(list) 246 | } 247 | } 248 | 249 | // Check status 250 | guard status == MMDB_SUCCESS, let list = entryList else { 251 | throw GeoIP2Error.dataParsingFailed(reason: "Failed to get entry data list, status: \(status)") 252 | } 253 | 254 | return try parseEntryDataList(entryList: list) 255 | } 256 | 257 | /// Parse entry data list 258 | /// - Parameter entryList: MMDB entry data list 259 | /// - Returns: Parsed data dictionary 260 | /// - Throws: GeoIP2Error if parsing fails 261 | private func parseEntryDataList(entryList: UnsafeMutablePointer) throws -> [String: Any] { 262 | // Get the first entry 263 | let firstEntry = entryList.pointee 264 | 265 | // Ensure top-level data is MAP type 266 | guard firstEntry.entry_data.type == UInt32(MMDB_DATA_TYPE_MAP) else { 267 | throw GeoIP2Error.dataParsingFailed(reason: "Top level data is not a MAP") 268 | } 269 | 270 | // Parse top-level MAP 271 | return try parseMapStructure(entryList: entryList) 272 | } 273 | 274 | /// Parse MAP structure 275 | /// - Parameter entryList: Data list pointer 276 | /// - Returns: Parsed dictionary 277 | /// - Throws: GeoIP2Error if parsing fails 278 | private func parseMapStructure(entryList: UnsafeMutablePointer) throws -> [String: Any] { 279 | let mapData = entryList.pointee.entry_data 280 | let size = Int(mapData.data_size) 281 | var result = Dictionary(minimumCapacity: size) 282 | 283 | // Move to the first key 284 | var current = entryList.pointee.next 285 | 286 | // Parse key-value pairs in the MAP 287 | for _ in 0..) throws -> [Any] { 362 | let arrayData = entryList.pointee.entry_data 363 | let size = Int(arrayData.data_size) 364 | var result = [Any]() 365 | result.reserveCapacity(size) 366 | 367 | // Move to the first element 368 | var current = entryList.pointee.next 369 | 370 | // Parse array elements 371 | for _ in 0..) -> UnsafeMutablePointer? { 427 | let entryData = entry.pointee.entry_data 428 | var count = 0 429 | var current = entry.pointee.next 430 | 431 | switch entryData.type { 432 | case UInt32(MMDB_DATA_TYPE_MAP): 433 | count = Int(entryData.data_size) 434 | // Each key-value pair in a MAP requires 2 entries (key and value) 435 | for _ in 0.. String { 474 | guard let str = data.utf8_string else { 475 | return "" 476 | } 477 | let dataSize = Int(data.data_size) 478 | let stringData = Data(bytes: str, count: dataSize) 479 | 480 | guard let string = String(data: stringData, encoding: .utf8) else { 481 | return "" 482 | } 483 | return string 484 | } 485 | 486 | /// Get database metadata 487 | /// - Returns: Database metadata dictionary 488 | /// - Throws: GeoIP2Error if retrieval fails 489 | public func metadata() throws -> [String: Any] { 490 | var entryList: UnsafeMutablePointer? 491 | let status = MMDB_get_metadata_as_entry_data_list(mmdb, &entryList) 492 | 493 | // Ensure resources are released 494 | defer { 495 | if let list = entryList { 496 | MMDB_free_entry_data_list(list) 497 | } 498 | } 499 | 500 | guard status == MMDB_SUCCESS, let list = entryList else { 501 | throw GeoIP2Error.dataParsingFailed(reason: "Failed to get metadata") 502 | } 503 | 504 | return try parseEntryDataList(entryList: list) 505 | } 506 | 507 | /// Return data directly as a JSON string 508 | /// - Parameter ip: IP address 509 | /// - Returns: Raw JSON string 510 | /// - Throws: Error if lookup or conversion fails 511 | public func lookupJSON(ip: String, prettyPrinted: Bool = true) throws -> String { 512 | let result = try lookup(ip: ip) 513 | if let json = result.toJSON(prettyPrinted: prettyPrinted) { 514 | return json 515 | } 516 | throw GeoIP2Error.dataParsingFailed(reason: "Failed to convert result to JSON") 517 | } 518 | 519 | /// Get raw data as JSON representation 520 | /// - Parameter ip: IP address 521 | /// - Returns: Raw data JSON string 522 | /// - Throws: Error if lookup fails 523 | public func getRawDataJSON(ip: String) throws -> String { 524 | try ip.withCString { cString in 525 | var gai_error: Int32 = 0 526 | var mmdb_error: Int32 = 0 527 | 528 | // Execute query 529 | let result = MMDB_lookup_string(mmdb, cString, &gai_error, &mmdb_error) 530 | 531 | // Check errors 532 | if mmdb_error != MMDB_SUCCESS { 533 | throw GeoIP2Error.lookupFailed(code: mmdb_error, gaiError: nil) 534 | } 535 | if gai_error != 0 { 536 | throw GeoIP2Error.lookupFailed(code: mmdb_error, gaiError: gai_error) 537 | } 538 | 539 | // If no entry is found, return empty result 540 | guard result.found_entry else { 541 | return "{}" 542 | } 543 | 544 | var entry = result.entry 545 | var entryList: UnsafeMutablePointer? 546 | let status = MMDB_get_entry_data_list(&entry, &entryList) 547 | 548 | // Ensure resources are released 549 | defer { 550 | if let list = entryList { 551 | MMDB_free_entry_data_list(list) 552 | } 553 | } 554 | 555 | guard status == MMDB_SUCCESS, let list = entryList else { 556 | throw GeoIP2Error.dataParsingFailed(reason: "Failed to get entry data list") 557 | } 558 | 559 | // Parse data 560 | let data = try parseEntryDataList(entryList: list) 561 | 562 | // Convert to JSON 563 | let options: JSONSerialization.WritingOptions = [.prettyPrinted, .sortedKeys] 564 | guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: options), 565 | let jsonString = String(data: jsonData, encoding: .utf8) else { 566 | throw GeoIP2Error.dataParsingFailed(reason: "Failed to convert data to JSON") 567 | } 568 | 569 | return jsonString 570 | } 571 | } 572 | 573 | /// Parse MAP type data 574 | /// - Parameter entryList: Data list pointer 575 | /// - Returns: Parsed dictionary 576 | /// - Throws: GeoIP2Error if parsing fails 577 | private func parseMap(entryList: UnsafeMutablePointer) throws -> [String: Any] { 578 | // This method has been replaced by parseMapStructure, kept for backward compatibility 579 | return try parseMapStructure(entryList: entryList) 580 | } 581 | 582 | /// Parse array type data 583 | /// - Parameter entryList: Data list pointer 584 | /// - Returns: Parsed array 585 | /// - Throws: GeoIP2Error if parsing fails 586 | private func parseArray(entryList: UnsafeMutablePointer) throws -> [Any] { 587 | // This method has been replaced by parseArrayStructure, kept for backward compatibility 588 | return try parseArrayStructure(entryList: entryList) 589 | } 590 | 591 | /// Release resources 592 | deinit { 593 | MMDB_close(mmdb) 594 | mmdb.deallocate() 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /Sources/CLibMaxMindDB/libmaxminddb-1.12.2/src/maxminddb.c: -------------------------------------------------------------------------------- 1 | #ifndef _POSIX_C_SOURCE 2 | #define _POSIX_C_SOURCE 200809L 3 | #endif 4 | 5 | #if HAVE_CONFIG_H 6 | #include 7 | #endif 8 | #include "data-pool.h" 9 | #include "maxminddb-compat-util.h" 10 | #include "maxminddb.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #ifdef _WIN32 21 | #ifndef UNICODE 22 | #define UNICODE 23 | #endif 24 | #include 25 | #include 26 | #ifndef SSIZE_MAX 27 | #define SSIZE_MAX INTPTR_MAX 28 | #endif 29 | typedef ADDRESS_FAMILY sa_family_t; 30 | #else 31 | #include 32 | #include 33 | #include 34 | #endif 35 | 36 | #define MMDB_DATA_SECTION_SEPARATOR (16) 37 | #define MAXIMUM_DATA_STRUCTURE_DEPTH (512) 38 | 39 | #ifdef MMDB_DEBUG 40 | #define DEBUG_MSG(msg) fprintf(stderr, msg "\n") 41 | #define DEBUG_MSGF(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__) 42 | #define DEBUG_BINARY(fmt, byte) \ 43 | do { \ 44 | char *binary = byte_to_binary(byte); \ 45 | if (NULL == binary) { \ 46 | fprintf(stderr, "Calloc failed in DEBUG_BINARY\n"); \ 47 | abort(); \ 48 | } \ 49 | fprintf(stderr, fmt "\n", binary); \ 50 | free(binary); \ 51 | } while (0) 52 | #define DEBUG_NL fprintf(stderr, "\n") 53 | #else 54 | #define DEBUG_MSG(...) 55 | #define DEBUG_MSGF(...) 56 | #define DEBUG_BINARY(...) 57 | #define DEBUG_NL 58 | #endif 59 | 60 | #ifdef MMDB_DEBUG 61 | char *byte_to_binary(uint8_t byte) { 62 | char *bits = calloc(9, sizeof(char)); 63 | if (NULL == bits) { 64 | return bits; 65 | } 66 | 67 | for (uint8_t i = 0; i < 8; i++) { 68 | bits[i] = byte & (128 >> i) ? '1' : '0'; 69 | } 70 | bits[8] = '\0'; 71 | 72 | return bits; 73 | } 74 | 75 | char *type_num_to_name(uint8_t num) { 76 | switch (num) { 77 | case 0: 78 | return "extended"; 79 | case 1: 80 | return "pointer"; 81 | case 2: 82 | return "utf8_string"; 83 | case 3: 84 | return "double"; 85 | case 4: 86 | return "bytes"; 87 | case 5: 88 | return "uint16"; 89 | case 6: 90 | return "uint32"; 91 | case 7: 92 | return "map"; 93 | case 8: 94 | return "int32"; 95 | case 9: 96 | return "uint64"; 97 | case 10: 98 | return "uint128"; 99 | case 11: 100 | return "array"; 101 | case 12: 102 | return "container"; 103 | case 13: 104 | return "end_marker"; 105 | case 14: 106 | return "boolean"; 107 | case 15: 108 | return "float"; 109 | default: 110 | return "unknown type"; 111 | } 112 | } 113 | #endif 114 | 115 | /* None of the values we check on the lhs are bigger than uint32_t, so on 116 | * platforms where SIZE_MAX is a 64-bit integer, this would be a no-op, and it 117 | * makes the compiler complain if we do the check anyway. */ 118 | #if SIZE_MAX == UINT32_MAX 119 | #define MAYBE_CHECK_SIZE_OVERFLOW(lhs, rhs, error) \ 120 | if ((lhs) > (rhs)) { \ 121 | return error; \ 122 | } 123 | #else 124 | #define MAYBE_CHECK_SIZE_OVERFLOW(...) 125 | #endif 126 | 127 | typedef struct record_info_s { 128 | uint16_t record_length; 129 | uint32_t (*left_record_getter)(const uint8_t *); 130 | uint32_t (*right_record_getter)(const uint8_t *); 131 | uint8_t right_record_offset; 132 | } record_info_s; 133 | 134 | #define METADATA_MARKER "\xab\xcd\xefMaxMind.com" 135 | /* This is 128kb */ 136 | #define METADATA_BLOCK_MAX_SIZE 131072 137 | 138 | // 64 leads us to allocating 4 KiB on a 64bit system. 139 | #define MMDB_POOL_INIT_SIZE 64 140 | 141 | static int map_file(MMDB_s *const mmdb); 142 | static const uint8_t *find_metadata(const uint8_t *file_content, 143 | ssize_t file_size, 144 | uint32_t *metadata_size); 145 | static int read_metadata(MMDB_s *mmdb); 146 | static MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb); 147 | static int 148 | value_for_key_as_uint16(MMDB_entry_s *start, char *key, uint16_t *value); 149 | static int 150 | value_for_key_as_uint32(MMDB_entry_s *start, char *key, uint32_t *value); 151 | static int 152 | value_for_key_as_uint64(MMDB_entry_s *start, char *key, uint64_t *value); 153 | static int 154 | value_for_key_as_string(MMDB_entry_s *start, char *key, char const **value); 155 | static int populate_languages_metadata(MMDB_s *mmdb, 156 | MMDB_s *metadata_db, 157 | MMDB_entry_s *metadata_start); 158 | static int populate_description_metadata(MMDB_s *mmdb, 159 | MMDB_s *metadata_db, 160 | MMDB_entry_s *metadata_start); 161 | static int resolve_any_address(const char *ipstr, struct addrinfo **addresses); 162 | static int find_address_in_search_tree(const MMDB_s *const mmdb, 163 | uint8_t const *address, 164 | sa_family_t address_family, 165 | MMDB_lookup_result_s *result); 166 | static record_info_s record_info_for_database(const MMDB_s *const mmdb); 167 | static int find_ipv4_start_node(MMDB_s *const mmdb); 168 | static uint8_t record_type(const MMDB_s *const mmdb, uint64_t record); 169 | static uint32_t get_left_28_bit_record(const uint8_t *record); 170 | static uint32_t get_right_28_bit_record(const uint8_t *record); 171 | static uint32_t data_section_offset_for_record(const MMDB_s *const mmdb, 172 | uint64_t record); 173 | static size_t path_length(va_list va_path); 174 | static int lookup_path_in_array(const char *path_elem, 175 | const MMDB_s *const mmdb, 176 | MMDB_entry_data_s *entry_data); 177 | static int lookup_path_in_map(const char *path_elem, 178 | const MMDB_s *const mmdb, 179 | MMDB_entry_data_s *entry_data); 180 | static int skip_map_or_array(const MMDB_s *const mmdb, 181 | MMDB_entry_data_s *entry_data); 182 | static int decode_one_follow(const MMDB_s *const mmdb, 183 | uint32_t offset, 184 | MMDB_entry_data_s *entry_data); 185 | static int decode_one(const MMDB_s *const mmdb, 186 | uint32_t offset, 187 | MMDB_entry_data_s *entry_data); 188 | static int get_ext_type(int raw_ext_type); 189 | static uint32_t 190 | get_ptr_from(uint8_t ctrl, uint8_t const *const ptr, int ptr_size); 191 | static int get_entry_data_list(const MMDB_s *const mmdb, 192 | uint32_t offset, 193 | MMDB_entry_data_list_s *const entry_data_list, 194 | MMDB_data_pool_s *const pool, 195 | int depth); 196 | static float get_ieee754_float(const uint8_t *restrict p); 197 | static double get_ieee754_double(const uint8_t *restrict p); 198 | static uint32_t get_uint32(const uint8_t *p); 199 | static uint32_t get_uint24(const uint8_t *p); 200 | static uint32_t get_uint16(const uint8_t *p); 201 | static uint64_t get_uintX(const uint8_t *p, int length); 202 | static int32_t get_sintX(const uint8_t *p, int length); 203 | static void free_mmdb_struct(MMDB_s *const mmdb); 204 | static void free_languages_metadata(MMDB_s *mmdb); 205 | static void free_descriptions_metadata(MMDB_s *mmdb); 206 | static MMDB_entry_data_list_s * 207 | dump_entry_data_list(FILE *stream, 208 | MMDB_entry_data_list_s *entry_data_list, 209 | int indent, 210 | int *status); 211 | static void print_indentation(FILE *stream, int i); 212 | static char *bytes_to_hex(uint8_t const *bytes, uint32_t size); 213 | 214 | #define CHECKED_DECODE_ONE(mmdb, offset, entry_data) \ 215 | do { \ 216 | int status = decode_one(mmdb, offset, entry_data); \ 217 | if (MMDB_SUCCESS != status) { \ 218 | DEBUG_MSGF("CHECKED_DECODE_ONE failed." \ 219 | " status = %d (%s)", \ 220 | status, \ 221 | MMDB_strerror(status)); \ 222 | return status; \ 223 | } \ 224 | } while (0) 225 | 226 | #define CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data) \ 227 | do { \ 228 | int status = decode_one_follow(mmdb, offset, entry_data); \ 229 | if (MMDB_SUCCESS != status) { \ 230 | DEBUG_MSGF("CHECKED_DECODE_ONE_FOLLOW failed." \ 231 | " status = %d (%s)", \ 232 | status, \ 233 | MMDB_strerror(status)); \ 234 | return status; \ 235 | } \ 236 | } while (0) 237 | 238 | #define FREE_AND_SET_NULL(p) \ 239 | { \ 240 | free((void *)(p)); \ 241 | (p) = NULL; \ 242 | } 243 | 244 | int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb) { 245 | int status = MMDB_SUCCESS; 246 | 247 | mmdb->file_content = NULL; 248 | mmdb->data_section = NULL; 249 | mmdb->metadata.database_type = NULL; 250 | mmdb->metadata.languages.count = 0; 251 | mmdb->metadata.languages.names = NULL; 252 | mmdb->metadata.description.count = 0; 253 | 254 | mmdb->filename = mmdb_strdup(filename); 255 | if (NULL == mmdb->filename) { 256 | status = MMDB_OUT_OF_MEMORY_ERROR; 257 | goto cleanup; 258 | } 259 | 260 | if ((flags & MMDB_MODE_MASK) == 0) { 261 | flags |= MMDB_MODE_MMAP; 262 | } 263 | mmdb->flags = flags; 264 | 265 | if (MMDB_SUCCESS != (status = map_file(mmdb))) { 266 | goto cleanup; 267 | } 268 | 269 | #ifdef _WIN32 270 | WSADATA wsa; 271 | WSAStartup(MAKEWORD(2, 2), &wsa); 272 | #endif 273 | 274 | uint32_t metadata_size = 0; 275 | const uint8_t *metadata = 276 | find_metadata(mmdb->file_content, mmdb->file_size, &metadata_size); 277 | if (NULL == metadata) { 278 | status = MMDB_INVALID_METADATA_ERROR; 279 | goto cleanup; 280 | } 281 | 282 | mmdb->metadata_section = metadata; 283 | mmdb->metadata_section_size = metadata_size; 284 | 285 | status = read_metadata(mmdb); 286 | if (MMDB_SUCCESS != status) { 287 | goto cleanup; 288 | } 289 | 290 | if (mmdb->metadata.binary_format_major_version != 2) { 291 | status = MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; 292 | goto cleanup; 293 | } 294 | 295 | if (!can_multiply(SSIZE_MAX, 296 | mmdb->metadata.node_count, 297 | mmdb->full_record_byte_size)) { 298 | status = MMDB_INVALID_METADATA_ERROR; 299 | goto cleanup; 300 | } 301 | ssize_t search_tree_size = (ssize_t)mmdb->metadata.node_count * 302 | (ssize_t)mmdb->full_record_byte_size; 303 | 304 | mmdb->data_section = 305 | mmdb->file_content + search_tree_size + MMDB_DATA_SECTION_SEPARATOR; 306 | if (mmdb->file_size < MMDB_DATA_SECTION_SEPARATOR || 307 | search_tree_size > mmdb->file_size - MMDB_DATA_SECTION_SEPARATOR) { 308 | status = MMDB_INVALID_METADATA_ERROR; 309 | goto cleanup; 310 | } 311 | ssize_t data_section_size = 312 | mmdb->file_size - search_tree_size - MMDB_DATA_SECTION_SEPARATOR; 313 | if (data_section_size > UINT32_MAX || data_section_size <= 0) { 314 | status = MMDB_INVALID_METADATA_ERROR; 315 | goto cleanup; 316 | } 317 | mmdb->data_section_size = (uint32_t)data_section_size; 318 | 319 | // Although it is likely not possible to construct a database with valid 320 | // valid metadata, as parsed above, and a data_section_size less than 3, 321 | // we do this check as later we assume it is at least three when doing 322 | // bound checks. 323 | if (mmdb->data_section_size < 3) { 324 | status = MMDB_INVALID_DATA_ERROR; 325 | goto cleanup; 326 | } 327 | 328 | mmdb->metadata_section = metadata; 329 | mmdb->ipv4_start_node.node_value = 0; 330 | mmdb->ipv4_start_node.netmask = 0; 331 | 332 | // We do this immediately as otherwise there is a race to set 333 | // ipv4_start_node.node_value and ipv4_start_node.netmask. 334 | if (mmdb->metadata.ip_version == 6) { 335 | status = find_ipv4_start_node(mmdb); 336 | if (status != MMDB_SUCCESS) { 337 | goto cleanup; 338 | } 339 | } 340 | 341 | cleanup: 342 | if (MMDB_SUCCESS != status) { 343 | int saved_errno = errno; 344 | free_mmdb_struct(mmdb); 345 | errno = saved_errno; 346 | } 347 | return status; 348 | } 349 | 350 | #ifdef _WIN32 351 | 352 | static LPWSTR utf8_to_utf16(const char *utf8_str) { 353 | int wide_chars = MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, NULL, 0); 354 | wchar_t *utf16_str = (wchar_t *)calloc(wide_chars, sizeof(wchar_t)); 355 | if (!utf16_str) { 356 | return NULL; 357 | } 358 | 359 | if (MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, utf16_str, wide_chars) < 360 | 1) { 361 | free(utf16_str); 362 | return NULL; 363 | } 364 | 365 | return utf16_str; 366 | } 367 | 368 | static int map_file(MMDB_s *const mmdb) { 369 | DWORD size; 370 | int status = MMDB_SUCCESS; 371 | HANDLE mmh = NULL; 372 | HANDLE fd = INVALID_HANDLE_VALUE; 373 | LPWSTR utf16_filename = utf8_to_utf16(mmdb->filename); 374 | if (!utf16_filename) { 375 | status = MMDB_FILE_OPEN_ERROR; 376 | goto cleanup; 377 | } 378 | fd = CreateFileW(utf16_filename, 379 | GENERIC_READ, 380 | FILE_SHARE_READ, 381 | NULL, 382 | OPEN_EXISTING, 383 | FILE_ATTRIBUTE_NORMAL, 384 | NULL); 385 | if (fd == INVALID_HANDLE_VALUE) { 386 | status = MMDB_FILE_OPEN_ERROR; 387 | goto cleanup; 388 | } 389 | size = GetFileSize(fd, NULL); 390 | if (size == INVALID_FILE_SIZE) { 391 | status = MMDB_FILE_OPEN_ERROR; 392 | goto cleanup; 393 | } 394 | mmh = CreateFileMapping(fd, NULL, PAGE_READONLY, 0, size, NULL); 395 | /* Microsoft documentation for CreateFileMapping indicates this returns 396 | NULL not INVALID_HANDLE_VALUE on error */ 397 | if (NULL == mmh) { 398 | status = MMDB_IO_ERROR; 399 | goto cleanup; 400 | } 401 | uint8_t *file_content = 402 | (uint8_t *)MapViewOfFile(mmh, FILE_MAP_READ, 0, 0, 0); 403 | if (file_content == NULL) { 404 | status = MMDB_IO_ERROR; 405 | goto cleanup; 406 | } 407 | 408 | mmdb->file_size = size; 409 | mmdb->file_content = file_content; 410 | 411 | cleanup:; 412 | int saved_errno = errno; 413 | if (INVALID_HANDLE_VALUE != fd) { 414 | CloseHandle(fd); 415 | } 416 | if (NULL != mmh) { 417 | CloseHandle(mmh); 418 | } 419 | errno = saved_errno; 420 | free(utf16_filename); 421 | 422 | return status; 423 | } 424 | 425 | #else // _WIN32 426 | 427 | static int map_file(MMDB_s *const mmdb) { 428 | int status = MMDB_SUCCESS; 429 | 430 | int o_flags = O_RDONLY; 431 | #ifdef O_CLOEXEC 432 | o_flags |= O_CLOEXEC; 433 | #endif 434 | int fd = open(mmdb->filename, o_flags); 435 | if (fd < 0) { 436 | status = MMDB_FILE_OPEN_ERROR; 437 | goto cleanup; 438 | } 439 | 440 | #if defined(FD_CLOEXEC) && !defined(O_CLOEXEC) 441 | int fd_flags = fcntl(fd, F_GETFD); 442 | if (fd_flags >= 0) { 443 | fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC); 444 | } 445 | #endif 446 | 447 | struct stat s; 448 | if (fstat(fd, &s)) { 449 | status = MMDB_FILE_OPEN_ERROR; 450 | goto cleanup; 451 | } 452 | 453 | off_t size = s.st_size; 454 | if (size < 0 || size > SSIZE_MAX) { 455 | status = MMDB_OUT_OF_MEMORY_ERROR; 456 | goto cleanup; 457 | } 458 | 459 | uint8_t *file_content = 460 | (uint8_t *)mmap(NULL, (size_t)size, PROT_READ, MAP_SHARED, fd, 0); 461 | if (MAP_FAILED == file_content) { 462 | if (ENOMEM == errno) { 463 | status = MMDB_OUT_OF_MEMORY_ERROR; 464 | } else { 465 | status = MMDB_IO_ERROR; 466 | } 467 | goto cleanup; 468 | } 469 | 470 | mmdb->file_size = (ssize_t)size; 471 | mmdb->file_content = file_content; 472 | 473 | cleanup:; 474 | int saved_errno = errno; 475 | if (fd >= 0) { 476 | close(fd); 477 | } 478 | errno = saved_errno; 479 | 480 | return status; 481 | } 482 | 483 | #endif // _WIN32 484 | 485 | static const uint8_t *find_metadata(const uint8_t *file_content, 486 | ssize_t file_size, 487 | uint32_t *metadata_size) { 488 | const ssize_t marker_len = sizeof(METADATA_MARKER) - 1; 489 | ssize_t max_size = file_size > METADATA_BLOCK_MAX_SIZE 490 | ? METADATA_BLOCK_MAX_SIZE 491 | : file_size; 492 | if (max_size < 0) { 493 | return NULL; 494 | } 495 | 496 | uint8_t const *search_area = (file_content + (file_size - max_size)); 497 | uint8_t const *start = search_area; 498 | uint8_t const *tmp; 499 | do { 500 | tmp = mmdb_memmem( 501 | search_area, (size_t)max_size, METADATA_MARKER, marker_len); 502 | 503 | if (NULL != tmp) { 504 | max_size -= tmp - search_area; 505 | search_area = tmp; 506 | 507 | /* Continue searching just after the marker we just read, in case 508 | * there are multiple markers in the same file. This would be odd 509 | * but is certainly not impossible. */ 510 | max_size -= marker_len; 511 | search_area += marker_len; 512 | } 513 | } while (NULL != tmp); 514 | 515 | if (search_area == start) { 516 | return NULL; 517 | } 518 | 519 | *metadata_size = (uint32_t)max_size; 520 | 521 | return search_area; 522 | } 523 | 524 | static int read_metadata(MMDB_s *mmdb) { 525 | /* We need to create a fake MMDB_s struct in order to decode values from 526 | the metadata. The metadata is basically just like the data section, so we 527 | want to use the same functions we use for the data section to get 528 | metadata values. */ 529 | MMDB_s metadata_db = make_fake_metadata_db(mmdb); 530 | 531 | MMDB_entry_s metadata_start = {.mmdb = &metadata_db, .offset = 0}; 532 | 533 | int status = value_for_key_as_uint32( 534 | &metadata_start, "node_count", &mmdb->metadata.node_count); 535 | if (MMDB_SUCCESS != status) { 536 | return status; 537 | } 538 | if (!mmdb->metadata.node_count) { 539 | DEBUG_MSG("could not find node_count value in metadata"); 540 | return MMDB_INVALID_METADATA_ERROR; 541 | } 542 | 543 | status = value_for_key_as_uint16( 544 | &metadata_start, "record_size", &mmdb->metadata.record_size); 545 | if (MMDB_SUCCESS != status) { 546 | return status; 547 | } 548 | if (!mmdb->metadata.record_size) { 549 | DEBUG_MSG("could not find record_size value in metadata"); 550 | return MMDB_INVALID_METADATA_ERROR; 551 | } 552 | 553 | if (mmdb->metadata.record_size != 24 && mmdb->metadata.record_size != 28 && 554 | mmdb->metadata.record_size != 32) { 555 | DEBUG_MSGF("bad record size in metadata: %i", 556 | mmdb->metadata.record_size); 557 | return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; 558 | } 559 | 560 | status = value_for_key_as_uint16( 561 | &metadata_start, "ip_version", &mmdb->metadata.ip_version); 562 | if (MMDB_SUCCESS != status) { 563 | return status; 564 | } 565 | if (!mmdb->metadata.ip_version) { 566 | DEBUG_MSG("could not find ip_version value in metadata"); 567 | return MMDB_INVALID_METADATA_ERROR; 568 | } 569 | if (!(mmdb->metadata.ip_version == 4 || mmdb->metadata.ip_version == 6)) { 570 | DEBUG_MSGF("ip_version value in metadata is not 4 or 6 - it was %i", 571 | mmdb->metadata.ip_version); 572 | return MMDB_INVALID_METADATA_ERROR; 573 | } 574 | 575 | status = value_for_key_as_string( 576 | &metadata_start, "database_type", &mmdb->metadata.database_type); 577 | if (MMDB_SUCCESS != status) { 578 | DEBUG_MSG("error finding database_type value in metadata"); 579 | return status; 580 | } 581 | 582 | status = populate_languages_metadata(mmdb, &metadata_db, &metadata_start); 583 | if (MMDB_SUCCESS != status) { 584 | DEBUG_MSG("could not populate languages from metadata"); 585 | return status; 586 | } 587 | 588 | status = 589 | value_for_key_as_uint16(&metadata_start, 590 | "binary_format_major_version", 591 | &mmdb->metadata.binary_format_major_version); 592 | if (MMDB_SUCCESS != status) { 593 | return status; 594 | } 595 | if (!mmdb->metadata.binary_format_major_version) { 596 | DEBUG_MSG( 597 | "could not find binary_format_major_version value in metadata"); 598 | return MMDB_INVALID_METADATA_ERROR; 599 | } 600 | 601 | status = 602 | value_for_key_as_uint16(&metadata_start, 603 | "binary_format_minor_version", 604 | &mmdb->metadata.binary_format_minor_version); 605 | if (MMDB_SUCCESS != status) { 606 | return status; 607 | } 608 | 609 | status = value_for_key_as_uint64( 610 | &metadata_start, "build_epoch", &mmdb->metadata.build_epoch); 611 | if (MMDB_SUCCESS != status) { 612 | return status; 613 | } 614 | if (!mmdb->metadata.build_epoch) { 615 | DEBUG_MSG("could not find build_epoch value in metadata"); 616 | return MMDB_INVALID_METADATA_ERROR; 617 | } 618 | 619 | status = populate_description_metadata(mmdb, &metadata_db, &metadata_start); 620 | if (MMDB_SUCCESS != status) { 621 | DEBUG_MSG("could not populate description from metadata"); 622 | return status; 623 | } 624 | 625 | mmdb->full_record_byte_size = mmdb->metadata.record_size * 2 / 8U; 626 | 627 | mmdb->depth = mmdb->metadata.ip_version == 4 ? 32 : 128; 628 | 629 | return MMDB_SUCCESS; 630 | } 631 | 632 | static MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb) { 633 | MMDB_s fake_metadata_db = {.data_section = mmdb->metadata_section, 634 | .data_section_size = 635 | mmdb->metadata_section_size}; 636 | 637 | return fake_metadata_db; 638 | } 639 | 640 | static int 641 | value_for_key_as_uint16(MMDB_entry_s *start, char *key, uint16_t *value) { 642 | MMDB_entry_data_s entry_data; 643 | const char *path[] = {key, NULL}; 644 | int status = MMDB_aget_value(start, &entry_data, path); 645 | if (MMDB_SUCCESS != status) { 646 | return status; 647 | } 648 | if (MMDB_DATA_TYPE_UINT16 != entry_data.type) { 649 | DEBUG_MSGF("expect uint16 for %s but received %s", 650 | key, 651 | type_num_to_name(entry_data.type)); 652 | return MMDB_INVALID_METADATA_ERROR; 653 | } 654 | *value = entry_data.uint16; 655 | return MMDB_SUCCESS; 656 | } 657 | 658 | static int 659 | value_for_key_as_uint32(MMDB_entry_s *start, char *key, uint32_t *value) { 660 | MMDB_entry_data_s entry_data; 661 | const char *path[] = {key, NULL}; 662 | int status = MMDB_aget_value(start, &entry_data, path); 663 | if (MMDB_SUCCESS != status) { 664 | return status; 665 | } 666 | if (MMDB_DATA_TYPE_UINT32 != entry_data.type) { 667 | DEBUG_MSGF("expect uint32 for %s but received %s", 668 | key, 669 | type_num_to_name(entry_data.type)); 670 | return MMDB_INVALID_METADATA_ERROR; 671 | } 672 | *value = entry_data.uint32; 673 | return MMDB_SUCCESS; 674 | } 675 | 676 | static int 677 | value_for_key_as_uint64(MMDB_entry_s *start, char *key, uint64_t *value) { 678 | MMDB_entry_data_s entry_data; 679 | const char *path[] = {key, NULL}; 680 | int status = MMDB_aget_value(start, &entry_data, path); 681 | if (MMDB_SUCCESS != status) { 682 | return status; 683 | } 684 | if (MMDB_DATA_TYPE_UINT64 != entry_data.type) { 685 | DEBUG_MSGF("expect uint64 for %s but received %s", 686 | key, 687 | type_num_to_name(entry_data.type)); 688 | return MMDB_INVALID_METADATA_ERROR; 689 | } 690 | *value = entry_data.uint64; 691 | return MMDB_SUCCESS; 692 | } 693 | 694 | static int 695 | value_for_key_as_string(MMDB_entry_s *start, char *key, char const **value) { 696 | MMDB_entry_data_s entry_data; 697 | const char *path[] = {key, NULL}; 698 | int status = MMDB_aget_value(start, &entry_data, path); 699 | if (MMDB_SUCCESS != status) { 700 | return status; 701 | } 702 | if (MMDB_DATA_TYPE_UTF8_STRING != entry_data.type) { 703 | DEBUG_MSGF("expect string for %s but received %s", 704 | key, 705 | type_num_to_name(entry_data.type)); 706 | return MMDB_INVALID_METADATA_ERROR; 707 | } 708 | *value = mmdb_strndup(entry_data.utf8_string, entry_data.data_size); 709 | if (NULL == *value) { 710 | return MMDB_OUT_OF_MEMORY_ERROR; 711 | } 712 | return MMDB_SUCCESS; 713 | } 714 | 715 | static int populate_languages_metadata(MMDB_s *mmdb, 716 | MMDB_s *metadata_db, 717 | MMDB_entry_s *metadata_start) { 718 | MMDB_entry_data_s entry_data; 719 | 720 | const char *path[] = {"languages", NULL}; 721 | int status = MMDB_aget_value(metadata_start, &entry_data, path); 722 | if (MMDB_SUCCESS != status) { 723 | return status; 724 | } 725 | if (MMDB_DATA_TYPE_ARRAY != entry_data.type) { 726 | return MMDB_INVALID_METADATA_ERROR; 727 | } 728 | 729 | MMDB_entry_s array_start = {.mmdb = metadata_db, 730 | .offset = entry_data.offset}; 731 | 732 | MMDB_entry_data_list_s *member; 733 | status = MMDB_get_entry_data_list(&array_start, &member); 734 | if (MMDB_SUCCESS != status) { 735 | return status; 736 | } 737 | 738 | MMDB_entry_data_list_s *first_member = member; 739 | 740 | uint32_t array_size = member->entry_data.data_size; 741 | MAYBE_CHECK_SIZE_OVERFLOW( 742 | array_size, SIZE_MAX / sizeof(char *), MMDB_INVALID_METADATA_ERROR); 743 | 744 | mmdb->metadata.languages.count = 0; 745 | mmdb->metadata.languages.names = calloc(array_size, sizeof(char *)); 746 | if (NULL == mmdb->metadata.languages.names) { 747 | MMDB_free_entry_data_list(first_member); 748 | return MMDB_OUT_OF_MEMORY_ERROR; 749 | } 750 | 751 | for (uint32_t i = 0; i < array_size; i++) { 752 | member = member->next; 753 | if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) { 754 | MMDB_free_entry_data_list(first_member); 755 | return MMDB_INVALID_METADATA_ERROR; 756 | } 757 | 758 | mmdb->metadata.languages.names[i] = mmdb_strndup( 759 | member->entry_data.utf8_string, member->entry_data.data_size); 760 | 761 | if (NULL == mmdb->metadata.languages.names[i]) { 762 | MMDB_free_entry_data_list(first_member); 763 | return MMDB_OUT_OF_MEMORY_ERROR; 764 | } 765 | // We assign this as we go so that if we fail a calloc and need to 766 | // free it, the count is right. 767 | mmdb->metadata.languages.count = i + 1; 768 | } 769 | 770 | MMDB_free_entry_data_list(first_member); 771 | 772 | return MMDB_SUCCESS; 773 | } 774 | 775 | static int populate_description_metadata(MMDB_s *mmdb, 776 | MMDB_s *metadata_db, 777 | MMDB_entry_s *metadata_start) { 778 | MMDB_entry_data_s entry_data; 779 | 780 | const char *path[] = {"description", NULL}; 781 | int status = MMDB_aget_value(metadata_start, &entry_data, path); 782 | if (MMDB_SUCCESS != status) { 783 | return status; 784 | } 785 | 786 | if (MMDB_DATA_TYPE_MAP != entry_data.type) { 787 | DEBUG_MSGF("Unexpected entry_data type: %d", entry_data.type); 788 | return MMDB_INVALID_METADATA_ERROR; 789 | } 790 | 791 | MMDB_entry_s map_start = {.mmdb = metadata_db, .offset = entry_data.offset}; 792 | 793 | MMDB_entry_data_list_s *member; 794 | status = MMDB_get_entry_data_list(&map_start, &member); 795 | if (MMDB_SUCCESS != status) { 796 | DEBUG_MSGF( 797 | "MMDB_get_entry_data_list failed while populating description." 798 | " status = %d (%s)", 799 | status, 800 | MMDB_strerror(status)); 801 | return status; 802 | } 803 | 804 | MMDB_entry_data_list_s *first_member = member; 805 | 806 | uint32_t map_size = member->entry_data.data_size; 807 | mmdb->metadata.description.count = 0; 808 | if (0 == map_size) { 809 | mmdb->metadata.description.descriptions = NULL; 810 | goto cleanup; 811 | } 812 | MAYBE_CHECK_SIZE_OVERFLOW(map_size, 813 | SIZE_MAX / sizeof(MMDB_description_s *), 814 | MMDB_INVALID_METADATA_ERROR); 815 | 816 | mmdb->metadata.description.descriptions = 817 | calloc(map_size, sizeof(MMDB_description_s *)); 818 | if (NULL == mmdb->metadata.description.descriptions) { 819 | status = MMDB_OUT_OF_MEMORY_ERROR; 820 | goto cleanup; 821 | } 822 | 823 | for (uint32_t i = 0; i < map_size; i++) { 824 | mmdb->metadata.description.descriptions[i] = 825 | calloc(1, sizeof(MMDB_description_s)); 826 | if (NULL == mmdb->metadata.description.descriptions[i]) { 827 | status = MMDB_OUT_OF_MEMORY_ERROR; 828 | goto cleanup; 829 | } 830 | 831 | mmdb->metadata.description.count = i + 1; 832 | mmdb->metadata.description.descriptions[i]->language = NULL; 833 | mmdb->metadata.description.descriptions[i]->description = NULL; 834 | 835 | member = member->next; 836 | 837 | if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) { 838 | status = MMDB_INVALID_METADATA_ERROR; 839 | goto cleanup; 840 | } 841 | 842 | mmdb->metadata.description.descriptions[i]->language = mmdb_strndup( 843 | member->entry_data.utf8_string, member->entry_data.data_size); 844 | 845 | if (NULL == mmdb->metadata.description.descriptions[i]->language) { 846 | status = MMDB_OUT_OF_MEMORY_ERROR; 847 | goto cleanup; 848 | } 849 | 850 | member = member->next; 851 | 852 | if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) { 853 | status = MMDB_INVALID_METADATA_ERROR; 854 | goto cleanup; 855 | } 856 | 857 | mmdb->metadata.description.descriptions[i]->description = mmdb_strndup( 858 | member->entry_data.utf8_string, member->entry_data.data_size); 859 | 860 | if (NULL == mmdb->metadata.description.descriptions[i]->description) { 861 | status = MMDB_OUT_OF_MEMORY_ERROR; 862 | goto cleanup; 863 | } 864 | } 865 | 866 | cleanup: 867 | MMDB_free_entry_data_list(first_member); 868 | 869 | return status; 870 | } 871 | 872 | MMDB_lookup_result_s MMDB_lookup_string(const MMDB_s *const mmdb, 873 | const char *const ipstr, 874 | int *const gai_error, 875 | int *const mmdb_error) { 876 | MMDB_lookup_result_s result = {.found_entry = false, 877 | .netmask = 0, 878 | .entry = {.mmdb = mmdb, .offset = 0}}; 879 | 880 | struct addrinfo *addresses = NULL; 881 | *gai_error = resolve_any_address(ipstr, &addresses); 882 | 883 | if (!*gai_error) { 884 | result = MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, mmdb_error); 885 | } 886 | 887 | if (NULL != addresses) { 888 | freeaddrinfo(addresses); 889 | } 890 | 891 | return result; 892 | } 893 | 894 | static int resolve_any_address(const char *ipstr, struct addrinfo **addresses) { 895 | struct addrinfo hints = { 896 | .ai_family = AF_UNSPEC, 897 | .ai_flags = AI_NUMERICHOST, 898 | // We set ai_socktype so that we only get one result back 899 | .ai_socktype = SOCK_STREAM}; 900 | 901 | int gai_status = getaddrinfo(ipstr, NULL, &hints, addresses); 902 | if (gai_status) { 903 | return gai_status; 904 | } 905 | 906 | return 0; 907 | } 908 | 909 | MMDB_lookup_result_s MMDB_lookup_sockaddr(const MMDB_s *const mmdb, 910 | const struct sockaddr *const sockaddr, 911 | int *const mmdb_error) { 912 | MMDB_lookup_result_s result = {.found_entry = false, 913 | .netmask = 0, 914 | .entry = {.mmdb = mmdb, .offset = 0}}; 915 | 916 | uint8_t mapped_address[16]; 917 | uint8_t const *address; 918 | if (mmdb->metadata.ip_version == 4) { 919 | if (sockaddr->sa_family == AF_INET6) { 920 | *mmdb_error = MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR; 921 | return result; 922 | } 923 | address = (uint8_t const *)&((struct sockaddr_in const *)sockaddr) 924 | ->sin_addr.s_addr; 925 | } else { 926 | if (sockaddr->sa_family == AF_INET6) { 927 | address = (uint8_t const *)&((struct sockaddr_in6 const *)sockaddr) 928 | ->sin6_addr.s6_addr; 929 | } else { 930 | address = mapped_address; 931 | memset(mapped_address, 0, 12); 932 | memcpy(mapped_address + 12, 933 | &((struct sockaddr_in const *)sockaddr)->sin_addr.s_addr, 934 | 4); 935 | } 936 | } 937 | 938 | *mmdb_error = find_address_in_search_tree( 939 | mmdb, address, sockaddr->sa_family, &result); 940 | 941 | return result; 942 | } 943 | 944 | static int find_address_in_search_tree(const MMDB_s *const mmdb, 945 | uint8_t const *address, 946 | sa_family_t address_family, 947 | MMDB_lookup_result_s *result) { 948 | record_info_s record_info = record_info_for_database(mmdb); 949 | if (record_info.right_record_offset == 0) { 950 | return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; 951 | } 952 | 953 | uint64_t value = 0; 954 | uint16_t current_bit = 0; 955 | if (mmdb->metadata.ip_version == 6 && address_family == AF_INET) { 956 | value = mmdb->ipv4_start_node.node_value; 957 | current_bit = mmdb->ipv4_start_node.netmask; 958 | } 959 | 960 | uint32_t node_count = mmdb->metadata.node_count; 961 | const uint8_t *search_tree = mmdb->file_content; 962 | const uint8_t *record_pointer; 963 | for (; current_bit < mmdb->depth && value < node_count; current_bit++) { 964 | uint8_t bit = 965 | 1U & (address[current_bit >> 3] >> (7 - (current_bit % 8))); 966 | 967 | // Note that value*record_info.record_length can be larger than 2**32 968 | record_pointer = &search_tree[value * record_info.record_length]; 969 | if (record_pointer + record_info.record_length > mmdb->data_section) { 970 | return MMDB_CORRUPT_SEARCH_TREE_ERROR; 971 | } 972 | if (bit) { 973 | record_pointer += record_info.right_record_offset; 974 | value = record_info.right_record_getter(record_pointer); 975 | } else { 976 | value = record_info.left_record_getter(record_pointer); 977 | } 978 | } 979 | 980 | result->netmask = current_bit; 981 | 982 | if (value >= node_count + mmdb->data_section_size) { 983 | // The pointer points off the end of the database. 984 | return MMDB_CORRUPT_SEARCH_TREE_ERROR; 985 | } 986 | 987 | if (value == node_count) { 988 | // record is empty 989 | result->found_entry = false; 990 | return MMDB_SUCCESS; 991 | } 992 | result->found_entry = true; 993 | result->entry.offset = data_section_offset_for_record(mmdb, value); 994 | 995 | return MMDB_SUCCESS; 996 | } 997 | 998 | static record_info_s record_info_for_database(const MMDB_s *const mmdb) { 999 | record_info_s record_info = {.record_length = mmdb->full_record_byte_size, 1000 | .right_record_offset = 0}; 1001 | 1002 | if (record_info.record_length == 6) { 1003 | record_info.left_record_getter = &get_uint24; 1004 | record_info.right_record_getter = &get_uint24; 1005 | record_info.right_record_offset = 3; 1006 | } else if (record_info.record_length == 7) { 1007 | record_info.left_record_getter = &get_left_28_bit_record; 1008 | record_info.right_record_getter = &get_right_28_bit_record; 1009 | record_info.right_record_offset = 3; 1010 | } else if (record_info.record_length == 8) { 1011 | record_info.left_record_getter = &get_uint32; 1012 | record_info.right_record_getter = &get_uint32; 1013 | record_info.right_record_offset = 4; 1014 | } 1015 | 1016 | // Callers must check that right_record_offset is non-zero in case none of 1017 | // the above conditions matched. 1018 | 1019 | return record_info; 1020 | } 1021 | 1022 | static int find_ipv4_start_node(MMDB_s *const mmdb) { 1023 | /* In a pathological case of a database with a single node search tree, 1024 | * this check will be true even after we've found the IPv4 start node, but 1025 | * that doesn't seem worth trying to fix. */ 1026 | if (mmdb->ipv4_start_node.node_value != 0) { 1027 | return MMDB_SUCCESS; 1028 | } 1029 | 1030 | record_info_s record_info = record_info_for_database(mmdb); 1031 | if (record_info.right_record_offset == 0) { 1032 | return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; 1033 | } 1034 | 1035 | const uint8_t *search_tree = mmdb->file_content; 1036 | uint32_t node_value = 0; 1037 | const uint8_t *record_pointer; 1038 | uint16_t netmask; 1039 | uint32_t node_count = mmdb->metadata.node_count; 1040 | 1041 | for (netmask = 0; netmask < 96 && node_value < node_count; netmask++) { 1042 | record_pointer = &search_tree[node_value * record_info.record_length]; 1043 | if (record_pointer + record_info.record_length > mmdb->data_section) { 1044 | return MMDB_CORRUPT_SEARCH_TREE_ERROR; 1045 | } 1046 | node_value = record_info.left_record_getter(record_pointer); 1047 | } 1048 | 1049 | mmdb->ipv4_start_node.node_value = node_value; 1050 | mmdb->ipv4_start_node.netmask = netmask; 1051 | 1052 | return MMDB_SUCCESS; 1053 | } 1054 | 1055 | static uint8_t record_type(const MMDB_s *const mmdb, uint64_t record) { 1056 | uint32_t node_count = mmdb->metadata.node_count; 1057 | 1058 | /* Ideally we'd check to make sure that a record never points to a 1059 | * previously seen value, but that's more complicated. For now, we can 1060 | * at least check that we don't end up at the top of the tree again. */ 1061 | if (record == 0) { 1062 | DEBUG_MSG("record has a value of 0"); 1063 | return MMDB_RECORD_TYPE_INVALID; 1064 | } 1065 | 1066 | if (record < node_count) { 1067 | return MMDB_RECORD_TYPE_SEARCH_NODE; 1068 | } 1069 | 1070 | if (record == node_count) { 1071 | return MMDB_RECORD_TYPE_EMPTY; 1072 | } 1073 | 1074 | if (record - node_count < mmdb->data_section_size) { 1075 | return MMDB_RECORD_TYPE_DATA; 1076 | } 1077 | 1078 | DEBUG_MSG("record has a value that points outside of the database"); 1079 | return MMDB_RECORD_TYPE_INVALID; 1080 | } 1081 | 1082 | static uint32_t get_left_28_bit_record(const uint8_t *record) { 1083 | return record[0] * 65536 + record[1] * 256 + record[2] + 1084 | (uint32_t)((record[3] & 0xf0) << 20); 1085 | } 1086 | 1087 | static uint32_t get_right_28_bit_record(const uint8_t *record) { 1088 | uint32_t value = get_uint32(record); 1089 | return value & 0xfffffff; 1090 | } 1091 | 1092 | int MMDB_read_node(const MMDB_s *const mmdb, 1093 | uint32_t node_number, 1094 | MMDB_search_node_s *const node) { 1095 | record_info_s record_info = record_info_for_database(mmdb); 1096 | if (record_info.right_record_offset == 0) { 1097 | return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; 1098 | } 1099 | 1100 | if (node_number > mmdb->metadata.node_count) { 1101 | return MMDB_INVALID_NODE_NUMBER_ERROR; 1102 | } 1103 | 1104 | const uint8_t *search_tree = mmdb->file_content; 1105 | const uint8_t *record_pointer = 1106 | &search_tree[node_number * record_info.record_length]; 1107 | node->left_record = record_info.left_record_getter(record_pointer); 1108 | record_pointer += record_info.right_record_offset; 1109 | node->right_record = record_info.right_record_getter(record_pointer); 1110 | 1111 | node->left_record_type = record_type(mmdb, node->left_record); 1112 | node->right_record_type = record_type(mmdb, node->right_record); 1113 | 1114 | // Note that offset will be invalid if the record type is not 1115 | // MMDB_RECORD_TYPE_DATA, but that's ok. Any use of the record entry 1116 | // for other data types is a programming error. 1117 | node->left_record_entry = (struct MMDB_entry_s){ 1118 | .mmdb = mmdb, 1119 | .offset = data_section_offset_for_record(mmdb, node->left_record), 1120 | }; 1121 | node->right_record_entry = (struct MMDB_entry_s){ 1122 | .mmdb = mmdb, 1123 | .offset = data_section_offset_for_record(mmdb, node->right_record), 1124 | }; 1125 | 1126 | return MMDB_SUCCESS; 1127 | } 1128 | 1129 | static uint32_t data_section_offset_for_record(const MMDB_s *const mmdb, 1130 | uint64_t record) { 1131 | return (uint32_t)record - mmdb->metadata.node_count - 1132 | MMDB_DATA_SECTION_SEPARATOR; 1133 | } 1134 | 1135 | int MMDB_get_value(MMDB_entry_s *const start, 1136 | MMDB_entry_data_s *const entry_data, 1137 | ...) { 1138 | va_list path; 1139 | va_start(path, entry_data); 1140 | int status = MMDB_vget_value(start, entry_data, path); 1141 | va_end(path); 1142 | return status; 1143 | } 1144 | 1145 | int MMDB_vget_value(MMDB_entry_s *const start, 1146 | MMDB_entry_data_s *const entry_data, 1147 | va_list va_path) { 1148 | size_t length = path_length(va_path); 1149 | const char *path_elem; 1150 | int i = 0; 1151 | 1152 | if (length == SIZE_MAX) { 1153 | return MMDB_INVALID_METADATA_ERROR; 1154 | } 1155 | 1156 | const char **path = calloc(length + 1, sizeof(const char *)); 1157 | if (NULL == path) { 1158 | return MMDB_OUT_OF_MEMORY_ERROR; 1159 | } 1160 | 1161 | while (NULL != (path_elem = va_arg(va_path, char *))) { 1162 | path[i] = path_elem; 1163 | i++; 1164 | } 1165 | path[i] = NULL; 1166 | 1167 | int status = MMDB_aget_value(start, entry_data, path); 1168 | 1169 | free(path); 1170 | 1171 | return status; 1172 | } 1173 | 1174 | static size_t path_length(va_list va_path) { 1175 | size_t i = 0; 1176 | va_list path_copy; 1177 | va_copy(path_copy, va_path); 1178 | 1179 | while (NULL != va_arg(path_copy, char *)) { 1180 | i++; 1181 | } 1182 | 1183 | va_end(path_copy); 1184 | 1185 | return i; 1186 | } 1187 | 1188 | int MMDB_aget_value(MMDB_entry_s *const start, 1189 | MMDB_entry_data_s *const entry_data, 1190 | const char *const *const path) { 1191 | const MMDB_s *const mmdb = start->mmdb; 1192 | uint32_t offset = start->offset; 1193 | 1194 | memset(entry_data, 0, sizeof(MMDB_entry_data_s)); 1195 | DEBUG_NL; 1196 | DEBUG_MSG("looking up value by path"); 1197 | 1198 | CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data); 1199 | 1200 | DEBUG_NL; 1201 | DEBUG_MSGF("top level element is a %s", type_num_to_name(entry_data->type)); 1202 | 1203 | /* Can this happen? It'd probably represent a pathological case under 1204 | * normal use, but there's nothing preventing someone from passing an 1205 | * invalid MMDB_entry_s struct to this function */ 1206 | if (!entry_data->has_data) { 1207 | return MMDB_INVALID_LOOKUP_PATH_ERROR; 1208 | } 1209 | 1210 | const char *path_elem; 1211 | int i = 0; 1212 | while (NULL != (path_elem = path[i++])) { 1213 | DEBUG_NL; 1214 | DEBUG_MSGF("path elem = %s", path_elem); 1215 | 1216 | /* XXX - it'd be good to find a quicker way to skip through these 1217 | entries that doesn't involve decoding them 1218 | completely. Basically we need to just use the size from the 1219 | control byte to advance our pointer rather than calling 1220 | decode_one(). */ 1221 | if (entry_data->type == MMDB_DATA_TYPE_ARRAY) { 1222 | int status = lookup_path_in_array(path_elem, mmdb, entry_data); 1223 | if (MMDB_SUCCESS != status) { 1224 | memset(entry_data, 0, sizeof(MMDB_entry_data_s)); 1225 | return status; 1226 | } 1227 | } else if (entry_data->type == MMDB_DATA_TYPE_MAP) { 1228 | int status = lookup_path_in_map(path_elem, mmdb, entry_data); 1229 | if (MMDB_SUCCESS != status) { 1230 | memset(entry_data, 0, sizeof(MMDB_entry_data_s)); 1231 | return status; 1232 | } 1233 | } else { 1234 | /* Once we make the code traverse maps & arrays without calling 1235 | * decode_one() we can get rid of this. */ 1236 | memset(entry_data, 0, sizeof(MMDB_entry_data_s)); 1237 | return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR; 1238 | } 1239 | } 1240 | 1241 | return MMDB_SUCCESS; 1242 | } 1243 | 1244 | static int lookup_path_in_array(const char *path_elem, 1245 | const MMDB_s *const mmdb, 1246 | MMDB_entry_data_s *entry_data) { 1247 | uint32_t size = entry_data->data_size; 1248 | char *first_invalid; 1249 | 1250 | int saved_errno = errno; 1251 | errno = 0; 1252 | long array_index = strtol(path_elem, &first_invalid, 10); 1253 | if (ERANGE == errno) { 1254 | errno = saved_errno; 1255 | return MMDB_INVALID_LOOKUP_PATH_ERROR; 1256 | } 1257 | errno = saved_errno; 1258 | 1259 | if (array_index < 0) { 1260 | array_index += size; 1261 | 1262 | if (array_index < 0) { 1263 | return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR; 1264 | } 1265 | } 1266 | 1267 | if (*first_invalid || (unsigned long)array_index >= size) { 1268 | return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR; 1269 | } 1270 | 1271 | for (long i = 0; i < array_index; i++) { 1272 | /* We don't want to follow a pointer here. If the next element is a 1273 | * pointer we simply skip it and keep going */ 1274 | CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data); 1275 | int status = skip_map_or_array(mmdb, entry_data); 1276 | if (MMDB_SUCCESS != status) { 1277 | return status; 1278 | } 1279 | } 1280 | 1281 | MMDB_entry_data_s value; 1282 | CHECKED_DECODE_ONE_FOLLOW(mmdb, entry_data->offset_to_next, &value); 1283 | memcpy(entry_data, &value, sizeof(MMDB_entry_data_s)); 1284 | 1285 | return MMDB_SUCCESS; 1286 | } 1287 | 1288 | static int lookup_path_in_map(const char *path_elem, 1289 | const MMDB_s *const mmdb, 1290 | MMDB_entry_data_s *entry_data) { 1291 | uint32_t size = entry_data->data_size; 1292 | uint32_t offset = entry_data->offset_to_next; 1293 | size_t path_elem_len = strlen(path_elem); 1294 | 1295 | while (size-- > 0) { 1296 | MMDB_entry_data_s key, value; 1297 | CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, &key); 1298 | 1299 | uint32_t offset_to_value = key.offset_to_next; 1300 | 1301 | if (MMDB_DATA_TYPE_UTF8_STRING != key.type) { 1302 | return MMDB_INVALID_DATA_ERROR; 1303 | } 1304 | 1305 | if (key.data_size == path_elem_len && 1306 | !memcmp(path_elem, key.utf8_string, path_elem_len)) { 1307 | 1308 | DEBUG_MSG("found key matching path elem"); 1309 | 1310 | CHECKED_DECODE_ONE_FOLLOW(mmdb, offset_to_value, &value); 1311 | memcpy(entry_data, &value, sizeof(MMDB_entry_data_s)); 1312 | return MMDB_SUCCESS; 1313 | } else { 1314 | /* We don't want to follow a pointer here. If the next element is 1315 | * a pointer we simply skip it and keep going */ 1316 | CHECKED_DECODE_ONE(mmdb, offset_to_value, &value); 1317 | int status = skip_map_or_array(mmdb, &value); 1318 | if (MMDB_SUCCESS != status) { 1319 | return status; 1320 | } 1321 | offset = value.offset_to_next; 1322 | } 1323 | } 1324 | 1325 | memset(entry_data, 0, sizeof(MMDB_entry_data_s)); 1326 | return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR; 1327 | } 1328 | 1329 | static int skip_map_or_array(const MMDB_s *const mmdb, 1330 | MMDB_entry_data_s *entry_data) { 1331 | if (entry_data->type == MMDB_DATA_TYPE_MAP) { 1332 | uint32_t size = entry_data->data_size; 1333 | while (size-- > 0) { 1334 | CHECKED_DECODE_ONE( 1335 | mmdb, entry_data->offset_to_next, entry_data); // key 1336 | CHECKED_DECODE_ONE( 1337 | mmdb, entry_data->offset_to_next, entry_data); // value 1338 | int status = skip_map_or_array(mmdb, entry_data); 1339 | if (MMDB_SUCCESS != status) { 1340 | return status; 1341 | } 1342 | } 1343 | } else if (entry_data->type == MMDB_DATA_TYPE_ARRAY) { 1344 | uint32_t size = entry_data->data_size; 1345 | while (size-- > 0) { 1346 | CHECKED_DECODE_ONE( 1347 | mmdb, entry_data->offset_to_next, entry_data); // value 1348 | int status = skip_map_or_array(mmdb, entry_data); 1349 | if (MMDB_SUCCESS != status) { 1350 | return status; 1351 | } 1352 | } 1353 | } 1354 | 1355 | return MMDB_SUCCESS; 1356 | } 1357 | 1358 | static int decode_one_follow(const MMDB_s *const mmdb, 1359 | uint32_t offset, 1360 | MMDB_entry_data_s *entry_data) { 1361 | CHECKED_DECODE_ONE(mmdb, offset, entry_data); 1362 | if (entry_data->type == MMDB_DATA_TYPE_POINTER) { 1363 | uint32_t next = entry_data->offset_to_next; 1364 | CHECKED_DECODE_ONE(mmdb, entry_data->pointer, entry_data); 1365 | /* Pointers to pointers are illegal under the spec */ 1366 | if (entry_data->type == MMDB_DATA_TYPE_POINTER) { 1367 | DEBUG_MSG("pointer points to another pointer"); 1368 | return MMDB_INVALID_DATA_ERROR; 1369 | } 1370 | 1371 | /* The pointer could point to any part of the data section but the 1372 | * next entry for this particular offset may be the one after the 1373 | * pointer, not the one after whatever the pointer points to. This 1374 | * depends on whether the pointer points to something that is a simple 1375 | * value or a compound value. For a compound value, the next one is 1376 | * the one after the pointer result, not the one after the pointer. */ 1377 | if (entry_data->type != MMDB_DATA_TYPE_MAP && 1378 | entry_data->type != MMDB_DATA_TYPE_ARRAY) { 1379 | 1380 | entry_data->offset_to_next = next; 1381 | } 1382 | } 1383 | 1384 | return MMDB_SUCCESS; 1385 | } 1386 | 1387 | #if !MMDB_UINT128_IS_BYTE_ARRAY 1388 | static mmdb_uint128_t get_uint128(const uint8_t *p, int length) { 1389 | mmdb_uint128_t value = 0; 1390 | while (length-- > 0) { 1391 | value <<= 8; 1392 | value += *p++; 1393 | } 1394 | return value; 1395 | } 1396 | #endif 1397 | 1398 | static int decode_one(const MMDB_s *const mmdb, 1399 | uint32_t offset, 1400 | MMDB_entry_data_s *entry_data) { 1401 | const uint8_t *mem = mmdb->data_section; 1402 | 1403 | // We subtract rather than add as it possible that offset + 1 1404 | // could overflow for a corrupt database while an underflow 1405 | // from data_section_size - 1 should not be possible. 1406 | if (offset > mmdb->data_section_size - 1) { 1407 | DEBUG_MSGF("Offset (%d) past data section (%d)", 1408 | offset, 1409 | mmdb->data_section_size); 1410 | return MMDB_INVALID_DATA_ERROR; 1411 | } 1412 | 1413 | entry_data->offset = offset; 1414 | entry_data->has_data = true; 1415 | 1416 | DEBUG_NL; 1417 | DEBUG_MSGF("Offset: %i", offset); 1418 | 1419 | uint8_t ctrl = mem[offset++]; 1420 | DEBUG_BINARY("Control byte: %s", ctrl); 1421 | 1422 | int type = (ctrl >> 5) & 7; 1423 | DEBUG_MSGF("Type: %i (%s)", type, type_num_to_name(type)); 1424 | 1425 | if (type == MMDB_DATA_TYPE_EXTENDED) { 1426 | // Subtracting 1 to avoid possible overflow on offset + 1 1427 | if (offset > mmdb->data_section_size - 1) { 1428 | DEBUG_MSGF("Extended type offset (%d) past data section (%d)", 1429 | offset, 1430 | mmdb->data_section_size); 1431 | return MMDB_INVALID_DATA_ERROR; 1432 | } 1433 | type = get_ext_type(mem[offset++]); 1434 | DEBUG_MSGF("Extended type: %i (%s)", type, type_num_to_name(type)); 1435 | } 1436 | 1437 | entry_data->type = (uint32_t)type; 1438 | 1439 | if (type == MMDB_DATA_TYPE_POINTER) { 1440 | uint8_t psize = ((ctrl >> 3) & 3) + 1; 1441 | DEBUG_MSGF("Pointer size: %i", psize); 1442 | 1443 | // We check that the offset does not extend past the end of the 1444 | // database and that the subtraction of psize did not underflow. 1445 | if (offset > mmdb->data_section_size - psize || 1446 | mmdb->data_section_size < psize) { 1447 | DEBUG_MSGF("Pointer offset (%d) past data section (%d)", 1448 | offset + psize, 1449 | mmdb->data_section_size); 1450 | return MMDB_INVALID_DATA_ERROR; 1451 | } 1452 | entry_data->pointer = get_ptr_from(ctrl, &mem[offset], psize); 1453 | DEBUG_MSGF("Pointer to: %i", entry_data->pointer); 1454 | 1455 | entry_data->data_size = psize; 1456 | entry_data->offset_to_next = offset + psize; 1457 | return MMDB_SUCCESS; 1458 | } 1459 | 1460 | uint32_t size = ctrl & 31; 1461 | switch (size) { 1462 | case 29: 1463 | // We subtract when checking offset to avoid possible overflow 1464 | if (offset > mmdb->data_section_size - 1) { 1465 | DEBUG_MSGF("String end (%d, case 29) past data section (%d)", 1466 | offset, 1467 | mmdb->data_section_size); 1468 | return MMDB_INVALID_DATA_ERROR; 1469 | } 1470 | size = 29 + mem[offset++]; 1471 | break; 1472 | case 30: 1473 | // We subtract when checking offset to avoid possible overflow 1474 | if (offset > mmdb->data_section_size - 2) { 1475 | DEBUG_MSGF("String end (%d, case 30) past data section (%d)", 1476 | offset, 1477 | mmdb->data_section_size); 1478 | return MMDB_INVALID_DATA_ERROR; 1479 | } 1480 | size = 285 + get_uint16(&mem[offset]); 1481 | offset += 2; 1482 | break; 1483 | case 31: 1484 | // We subtract when checking offset to avoid possible overflow 1485 | if (offset > mmdb->data_section_size - 3) { 1486 | DEBUG_MSGF("String end (%d, case 31) past data section (%d)", 1487 | offset, 1488 | mmdb->data_section_size); 1489 | return MMDB_INVALID_DATA_ERROR; 1490 | } 1491 | size = 65821 + get_uint24(&mem[offset]); 1492 | offset += 3; 1493 | break; 1494 | default: 1495 | break; 1496 | } 1497 | 1498 | DEBUG_MSGF("Size: %i", size); 1499 | 1500 | if (type == MMDB_DATA_TYPE_MAP || type == MMDB_DATA_TYPE_ARRAY) { 1501 | entry_data->data_size = size; 1502 | entry_data->offset_to_next = offset; 1503 | return MMDB_SUCCESS; 1504 | } 1505 | 1506 | if (type == MMDB_DATA_TYPE_BOOLEAN) { 1507 | entry_data->boolean = size ? true : false; 1508 | entry_data->data_size = 0; 1509 | entry_data->offset_to_next = offset; 1510 | DEBUG_MSGF("boolean value: %s", entry_data->boolean ? "true" : "false"); 1511 | return MMDB_SUCCESS; 1512 | } 1513 | 1514 | // Check that the data doesn't extend past the end of the memory 1515 | // buffer and that the calculation in doing this did not underflow. 1516 | if (offset > mmdb->data_section_size - size || 1517 | mmdb->data_section_size < size) { 1518 | DEBUG_MSGF("Data end (%d) past data section (%d)", 1519 | offset + size, 1520 | mmdb->data_section_size); 1521 | return MMDB_INVALID_DATA_ERROR; 1522 | } 1523 | 1524 | if (type == MMDB_DATA_TYPE_UINT16) { 1525 | if (size > 2) { 1526 | DEBUG_MSGF("uint16 of size %d", size); 1527 | return MMDB_INVALID_DATA_ERROR; 1528 | } 1529 | entry_data->uint16 = (uint16_t)get_uintX(&mem[offset], (int)size); 1530 | DEBUG_MSGF("uint16 value: %u", entry_data->uint16); 1531 | } else if (type == MMDB_DATA_TYPE_UINT32) { 1532 | if (size > 4) { 1533 | DEBUG_MSGF("uint32 of size %d", size); 1534 | return MMDB_INVALID_DATA_ERROR; 1535 | } 1536 | entry_data->uint32 = (uint32_t)get_uintX(&mem[offset], (int)size); 1537 | DEBUG_MSGF("uint32 value: %u", entry_data->uint32); 1538 | } else if (type == MMDB_DATA_TYPE_INT32) { 1539 | if (size > 4) { 1540 | DEBUG_MSGF("int32 of size %d", size); 1541 | return MMDB_INVALID_DATA_ERROR; 1542 | } 1543 | entry_data->int32 = get_sintX(&mem[offset], (int)size); 1544 | DEBUG_MSGF("int32 value: %i", entry_data->int32); 1545 | } else if (type == MMDB_DATA_TYPE_UINT64) { 1546 | if (size > 8) { 1547 | DEBUG_MSGF("uint64 of size %d", size); 1548 | return MMDB_INVALID_DATA_ERROR; 1549 | } 1550 | entry_data->uint64 = get_uintX(&mem[offset], (int)size); 1551 | DEBUG_MSGF("uint64 value: %" PRIu64, entry_data->uint64); 1552 | } else if (type == MMDB_DATA_TYPE_UINT128) { 1553 | if (size > 16) { 1554 | DEBUG_MSGF("uint128 of size %d", size); 1555 | return MMDB_INVALID_DATA_ERROR; 1556 | } 1557 | #if MMDB_UINT128_IS_BYTE_ARRAY 1558 | memset(entry_data->uint128, 0, 16); 1559 | if (size > 0) { 1560 | memcpy(entry_data->uint128 + 16 - size, &mem[offset], size); 1561 | } 1562 | #else 1563 | entry_data->uint128 = get_uint128(&mem[offset], (int)size); 1564 | #endif 1565 | } else if (type == MMDB_DATA_TYPE_FLOAT) { 1566 | if (size != 4) { 1567 | DEBUG_MSGF("float of size %d", size); 1568 | return MMDB_INVALID_DATA_ERROR; 1569 | } 1570 | size = 4; 1571 | entry_data->float_value = get_ieee754_float(&mem[offset]); 1572 | DEBUG_MSGF("float value: %f", entry_data->float_value); 1573 | } else if (type == MMDB_DATA_TYPE_DOUBLE) { 1574 | if (size != 8) { 1575 | DEBUG_MSGF("double of size %d", size); 1576 | return MMDB_INVALID_DATA_ERROR; 1577 | } 1578 | size = 8; 1579 | entry_data->double_value = get_ieee754_double(&mem[offset]); 1580 | DEBUG_MSGF("double value: %f", entry_data->double_value); 1581 | } else if (type == MMDB_DATA_TYPE_UTF8_STRING) { 1582 | entry_data->utf8_string = size == 0 ? "" : (char const *)&mem[offset]; 1583 | entry_data->data_size = size; 1584 | #ifdef MMDB_DEBUG 1585 | char *string = 1586 | mmdb_strndup(entry_data->utf8_string, size > 50 ? 50 : size); 1587 | if (NULL == string) { 1588 | abort(); 1589 | } 1590 | DEBUG_MSGF("string value: %s", string); 1591 | free(string); 1592 | #endif 1593 | } else if (type == MMDB_DATA_TYPE_BYTES) { 1594 | entry_data->bytes = &mem[offset]; 1595 | entry_data->data_size = size; 1596 | } 1597 | 1598 | entry_data->offset_to_next = offset + size; 1599 | 1600 | return MMDB_SUCCESS; 1601 | } 1602 | 1603 | static int get_ext_type(int raw_ext_type) { return 7 + raw_ext_type; } 1604 | 1605 | static uint32_t 1606 | get_ptr_from(uint8_t ctrl, uint8_t const *const ptr, int ptr_size) { 1607 | uint32_t new_offset; 1608 | switch (ptr_size) { 1609 | case 1: 1610 | new_offset = (uint32_t)((ctrl & 7) << 8) + ptr[0]; 1611 | break; 1612 | case 2: 1613 | new_offset = 2048 + (uint32_t)((ctrl & 7) << 16) + 1614 | (uint32_t)(ptr[0] << 8) + ptr[1]; 1615 | break; 1616 | case 3: 1617 | new_offset = 1618 | 2048 + 524288 + (uint32_t)((ctrl & 7) << 24) + get_uint24(ptr); 1619 | break; 1620 | case 4: 1621 | default: 1622 | new_offset = get_uint32(ptr); 1623 | break; 1624 | } 1625 | return new_offset; 1626 | } 1627 | 1628 | int MMDB_get_metadata_as_entry_data_list( 1629 | const MMDB_s *const mmdb, MMDB_entry_data_list_s **const entry_data_list) { 1630 | MMDB_s metadata_db = make_fake_metadata_db(mmdb); 1631 | 1632 | MMDB_entry_s metadata_start = {.mmdb = &metadata_db, .offset = 0}; 1633 | 1634 | return MMDB_get_entry_data_list(&metadata_start, entry_data_list); 1635 | } 1636 | 1637 | int MMDB_get_entry_data_list(MMDB_entry_s *start, 1638 | MMDB_entry_data_list_s **const entry_data_list) { 1639 | *entry_data_list = NULL; 1640 | 1641 | MMDB_data_pool_s *const pool = data_pool_new(MMDB_POOL_INIT_SIZE); 1642 | if (!pool) { 1643 | return MMDB_OUT_OF_MEMORY_ERROR; 1644 | } 1645 | 1646 | MMDB_entry_data_list_s *const list = data_pool_alloc(pool); 1647 | if (!list) { 1648 | data_pool_destroy(pool); 1649 | return MMDB_OUT_OF_MEMORY_ERROR; 1650 | } 1651 | 1652 | int const status = 1653 | get_entry_data_list(start->mmdb, start->offset, list, pool, 0); 1654 | if (MMDB_SUCCESS != status) { 1655 | data_pool_destroy(pool); 1656 | return status; 1657 | } 1658 | 1659 | *entry_data_list = data_pool_to_list(pool); 1660 | if (!*entry_data_list) { 1661 | data_pool_destroy(pool); 1662 | return MMDB_OUT_OF_MEMORY_ERROR; 1663 | } 1664 | 1665 | return status; 1666 | } 1667 | 1668 | static int get_entry_data_list(const MMDB_s *const mmdb, 1669 | uint32_t offset, 1670 | MMDB_entry_data_list_s *const entry_data_list, 1671 | MMDB_data_pool_s *const pool, 1672 | int depth) { 1673 | if (depth >= MAXIMUM_DATA_STRUCTURE_DEPTH) { 1674 | DEBUG_MSG("reached the maximum data structure depth"); 1675 | return MMDB_INVALID_DATA_ERROR; 1676 | } 1677 | depth++; 1678 | CHECKED_DECODE_ONE(mmdb, offset, &entry_data_list->entry_data); 1679 | 1680 | switch (entry_data_list->entry_data.type) { 1681 | case MMDB_DATA_TYPE_POINTER: { 1682 | uint32_t next_offset = entry_data_list->entry_data.offset_to_next; 1683 | uint32_t last_offset; 1684 | CHECKED_DECODE_ONE(mmdb, 1685 | last_offset = 1686 | entry_data_list->entry_data.pointer, 1687 | &entry_data_list->entry_data); 1688 | 1689 | /* Pointers to pointers are illegal under the spec */ 1690 | if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_POINTER) { 1691 | DEBUG_MSG("pointer points to another pointer"); 1692 | return MMDB_INVALID_DATA_ERROR; 1693 | } 1694 | 1695 | if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_ARRAY || 1696 | entry_data_list->entry_data.type == MMDB_DATA_TYPE_MAP) { 1697 | 1698 | int status = get_entry_data_list( 1699 | mmdb, last_offset, entry_data_list, pool, depth); 1700 | if (MMDB_SUCCESS != status) { 1701 | DEBUG_MSG("get_entry_data_list on pointer failed."); 1702 | return status; 1703 | } 1704 | } 1705 | entry_data_list->entry_data.offset_to_next = next_offset; 1706 | } break; 1707 | case MMDB_DATA_TYPE_ARRAY: { 1708 | uint32_t array_size = entry_data_list->entry_data.data_size; 1709 | uint32_t array_offset = entry_data_list->entry_data.offset_to_next; 1710 | while (array_size-- > 0) { 1711 | MMDB_entry_data_list_s *entry_data_list_to = 1712 | data_pool_alloc(pool); 1713 | if (!entry_data_list_to) { 1714 | return MMDB_OUT_OF_MEMORY_ERROR; 1715 | } 1716 | 1717 | int status = get_entry_data_list( 1718 | mmdb, array_offset, entry_data_list_to, pool, depth); 1719 | if (MMDB_SUCCESS != status) { 1720 | DEBUG_MSG("get_entry_data_list on array element failed."); 1721 | return status; 1722 | } 1723 | 1724 | array_offset = entry_data_list_to->entry_data.offset_to_next; 1725 | } 1726 | entry_data_list->entry_data.offset_to_next = array_offset; 1727 | 1728 | } break; 1729 | case MMDB_DATA_TYPE_MAP: { 1730 | uint32_t size = entry_data_list->entry_data.data_size; 1731 | 1732 | offset = entry_data_list->entry_data.offset_to_next; 1733 | while (size-- > 0) { 1734 | MMDB_entry_data_list_s *list_key = data_pool_alloc(pool); 1735 | if (!list_key) { 1736 | return MMDB_OUT_OF_MEMORY_ERROR; 1737 | } 1738 | 1739 | int status = 1740 | get_entry_data_list(mmdb, offset, list_key, pool, depth); 1741 | if (MMDB_SUCCESS != status) { 1742 | DEBUG_MSG("get_entry_data_list on map key failed."); 1743 | return status; 1744 | } 1745 | 1746 | offset = list_key->entry_data.offset_to_next; 1747 | 1748 | MMDB_entry_data_list_s *list_value = data_pool_alloc(pool); 1749 | if (!list_value) { 1750 | return MMDB_OUT_OF_MEMORY_ERROR; 1751 | } 1752 | 1753 | status = 1754 | get_entry_data_list(mmdb, offset, list_value, pool, depth); 1755 | if (MMDB_SUCCESS != status) { 1756 | DEBUG_MSG("get_entry_data_list on map element failed."); 1757 | return status; 1758 | } 1759 | offset = list_value->entry_data.offset_to_next; 1760 | } 1761 | entry_data_list->entry_data.offset_to_next = offset; 1762 | } break; 1763 | default: 1764 | break; 1765 | } 1766 | 1767 | return MMDB_SUCCESS; 1768 | } 1769 | 1770 | static float get_ieee754_float(const uint8_t *restrict p) { 1771 | volatile float f; 1772 | volatile uint8_t *q = (volatile void *)&f; 1773 | /* Windows builds don't use autoconf but we can assume they're all 1774 | * little-endian. */ 1775 | #if MMDB_LITTLE_ENDIAN || _WIN32 1776 | q[3] = p[0]; 1777 | q[2] = p[1]; 1778 | q[1] = p[2]; 1779 | q[0] = p[3]; 1780 | #else 1781 | memcpy(q, p, 4); 1782 | #endif 1783 | return f; 1784 | } 1785 | 1786 | static double get_ieee754_double(const uint8_t *restrict p) { 1787 | volatile double d; 1788 | volatile uint8_t *q = (volatile void *)&d; 1789 | #if MMDB_LITTLE_ENDIAN || _WIN32 1790 | q[7] = p[0]; 1791 | q[6] = p[1]; 1792 | q[5] = p[2]; 1793 | q[4] = p[3]; 1794 | q[3] = p[4]; 1795 | q[2] = p[5]; 1796 | q[1] = p[6]; 1797 | q[0] = p[7]; 1798 | #else 1799 | memcpy(q, p, 8); 1800 | #endif 1801 | 1802 | return d; 1803 | } 1804 | 1805 | static uint32_t get_uint32(const uint8_t *p) { 1806 | return p[0] * 16777216U + p[1] * 65536 + p[2] * 256 + p[3]; 1807 | } 1808 | 1809 | static uint32_t get_uint24(const uint8_t *p) { 1810 | return p[0] * 65536U + p[1] * 256 + p[2]; 1811 | } 1812 | 1813 | static uint32_t get_uint16(const uint8_t *p) { return p[0] * 256U + p[1]; } 1814 | 1815 | static uint64_t get_uintX(const uint8_t *p, int length) { 1816 | uint64_t value = 0; 1817 | while (length-- > 0) { 1818 | value <<= 8; 1819 | value += *p++; 1820 | } 1821 | return value; 1822 | } 1823 | 1824 | static int32_t get_sintX(const uint8_t *p, int length) { 1825 | return (int32_t)get_uintX(p, length); 1826 | } 1827 | 1828 | void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list) { 1829 | if (entry_data_list == NULL) { 1830 | return; 1831 | } 1832 | data_pool_destroy(entry_data_list->pool); 1833 | } 1834 | 1835 | void MMDB_close(MMDB_s *const mmdb) { free_mmdb_struct(mmdb); } 1836 | 1837 | static void free_mmdb_struct(MMDB_s *const mmdb) { 1838 | if (!mmdb) { 1839 | return; 1840 | } 1841 | 1842 | if (NULL != mmdb->filename) { 1843 | #if defined(__clang__) 1844 | // This is a const char * that we need to free, which isn't valid. However it 1845 | // would mean changing the public API to fix this. 1846 | #pragma clang diagnostic push 1847 | #pragma clang diagnostic ignored "-Wcast-qual" 1848 | #endif 1849 | FREE_AND_SET_NULL(mmdb->filename); 1850 | #if defined(__clang__) 1851 | #pragma clang diagnostic pop 1852 | #endif 1853 | } 1854 | if (NULL != mmdb->file_content) { 1855 | #ifdef _WIN32 1856 | UnmapViewOfFile(mmdb->file_content); 1857 | /* Winsock is only initialized if open was successful so we only have 1858 | * to cleanup then. */ 1859 | WSACleanup(); 1860 | #else 1861 | #if defined(__clang__) 1862 | // This is a const char * that we need to free, which isn't valid. However it 1863 | // would mean changing the public API to fix this. 1864 | #pragma clang diagnostic push 1865 | #pragma clang diagnostic ignored "-Wcast-qual" 1866 | #endif 1867 | munmap((void *)mmdb->file_content, (size_t)mmdb->file_size); 1868 | #if defined(__clang__) 1869 | #pragma clang diagnostic pop 1870 | #endif 1871 | #endif 1872 | } 1873 | 1874 | if (NULL != mmdb->metadata.database_type) { 1875 | #if defined(__clang__) 1876 | // This is a const char * that we need to free, which isn't valid. However it 1877 | // would mean changing the public API to fix this. 1878 | #pragma clang diagnostic push 1879 | #pragma clang diagnostic ignored "-Wcast-qual" 1880 | #endif 1881 | FREE_AND_SET_NULL(mmdb->metadata.database_type); 1882 | #if defined(__clang__) 1883 | #pragma clang diagnostic pop 1884 | #endif 1885 | } 1886 | 1887 | free_languages_metadata(mmdb); 1888 | free_descriptions_metadata(mmdb); 1889 | } 1890 | 1891 | static void free_languages_metadata(MMDB_s *mmdb) { 1892 | if (!mmdb->metadata.languages.names) { 1893 | return; 1894 | } 1895 | 1896 | for (size_t i = 0; i < mmdb->metadata.languages.count; i++) { 1897 | #if defined(__clang__) 1898 | // This is a const char * that we need to free, which isn't valid. However it 1899 | // would mean changing the public API to fix this. 1900 | #pragma clang diagnostic push 1901 | #pragma clang diagnostic ignored "-Wcast-qual" 1902 | #endif 1903 | FREE_AND_SET_NULL(mmdb->metadata.languages.names[i]); 1904 | #if defined(__clang__) 1905 | #pragma clang diagnostic pop 1906 | #endif 1907 | } 1908 | FREE_AND_SET_NULL(mmdb->metadata.languages.names); 1909 | } 1910 | 1911 | static void free_descriptions_metadata(MMDB_s *mmdb) { 1912 | if (!mmdb->metadata.description.count) { 1913 | return; 1914 | } 1915 | 1916 | for (size_t i = 0; i < mmdb->metadata.description.count; i++) { 1917 | if (NULL != mmdb->metadata.description.descriptions[i]) { 1918 | if (NULL != mmdb->metadata.description.descriptions[i]->language) { 1919 | #if defined(__clang__) 1920 | // This is a const char * that we need to free, which isn't valid. However it 1921 | // would mean changing the public API to fix this. 1922 | #pragma clang diagnostic push 1923 | #pragma clang diagnostic ignored "-Wcast-qual" 1924 | #endif 1925 | FREE_AND_SET_NULL( 1926 | mmdb->metadata.description.descriptions[i]->language); 1927 | #if defined(__clang__) 1928 | #pragma clang diagnostic pop 1929 | #endif 1930 | } 1931 | 1932 | if (NULL != 1933 | mmdb->metadata.description.descriptions[i]->description) { 1934 | #if defined(__clang__) 1935 | // This is a const char * that we need to free, which isn't valid. However it 1936 | // would mean changing the public API to fix this. 1937 | #pragma clang diagnostic push 1938 | #pragma clang diagnostic ignored "-Wcast-qual" 1939 | #endif 1940 | FREE_AND_SET_NULL( 1941 | mmdb->metadata.description.descriptions[i]->description); 1942 | #if defined(__clang__) 1943 | #pragma clang diagnostic pop 1944 | #endif 1945 | } 1946 | FREE_AND_SET_NULL(mmdb->metadata.description.descriptions[i]); 1947 | } 1948 | } 1949 | 1950 | FREE_AND_SET_NULL(mmdb->metadata.description.descriptions); 1951 | } 1952 | 1953 | const char *MMDB_lib_version(void) { return PACKAGE_VERSION; } 1954 | 1955 | int MMDB_dump_entry_data_list(FILE *const stream, 1956 | MMDB_entry_data_list_s *const entry_data_list, 1957 | int indent) { 1958 | int status; 1959 | dump_entry_data_list(stream, entry_data_list, indent, &status); 1960 | return status; 1961 | } 1962 | 1963 | static MMDB_entry_data_list_s * 1964 | dump_entry_data_list(FILE *stream, 1965 | MMDB_entry_data_list_s *entry_data_list, 1966 | int indent, 1967 | int *status) { 1968 | switch (entry_data_list->entry_data.type) { 1969 | case MMDB_DATA_TYPE_MAP: { 1970 | uint32_t size = entry_data_list->entry_data.data_size; 1971 | 1972 | print_indentation(stream, indent); 1973 | fprintf(stream, "{\n"); 1974 | indent += 2; 1975 | 1976 | for (entry_data_list = entry_data_list->next; 1977 | size && entry_data_list; 1978 | size--) { 1979 | 1980 | if (MMDB_DATA_TYPE_UTF8_STRING != 1981 | entry_data_list->entry_data.type) { 1982 | *status = MMDB_INVALID_DATA_ERROR; 1983 | return NULL; 1984 | } 1985 | char *key = 1986 | mmdb_strndup(entry_data_list->entry_data.utf8_string, 1987 | entry_data_list->entry_data.data_size); 1988 | if (NULL == key) { 1989 | *status = MMDB_OUT_OF_MEMORY_ERROR; 1990 | return NULL; 1991 | } 1992 | 1993 | print_indentation(stream, indent); 1994 | fprintf(stream, "\"%s\": \n", key); 1995 | free(key); 1996 | 1997 | entry_data_list = entry_data_list->next; 1998 | entry_data_list = dump_entry_data_list( 1999 | stream, entry_data_list, indent + 2, status); 2000 | 2001 | if (MMDB_SUCCESS != *status) { 2002 | return NULL; 2003 | } 2004 | } 2005 | 2006 | indent -= 2; 2007 | print_indentation(stream, indent); 2008 | fprintf(stream, "}\n"); 2009 | } break; 2010 | case MMDB_DATA_TYPE_ARRAY: { 2011 | uint32_t size = entry_data_list->entry_data.data_size; 2012 | 2013 | print_indentation(stream, indent); 2014 | fprintf(stream, "[\n"); 2015 | indent += 2; 2016 | 2017 | for (entry_data_list = entry_data_list->next; 2018 | size && entry_data_list; 2019 | size--) { 2020 | entry_data_list = dump_entry_data_list( 2021 | stream, entry_data_list, indent, status); 2022 | if (MMDB_SUCCESS != *status) { 2023 | return NULL; 2024 | } 2025 | } 2026 | 2027 | indent -= 2; 2028 | print_indentation(stream, indent); 2029 | fprintf(stream, "]\n"); 2030 | } break; 2031 | case MMDB_DATA_TYPE_UTF8_STRING: { 2032 | char *string = mmdb_strndup(entry_data_list->entry_data.utf8_string, 2033 | entry_data_list->entry_data.data_size); 2034 | if (NULL == string) { 2035 | *status = MMDB_OUT_OF_MEMORY_ERROR; 2036 | return NULL; 2037 | } 2038 | print_indentation(stream, indent); 2039 | fprintf(stream, "\"%s\" \n", string); 2040 | free(string); 2041 | entry_data_list = entry_data_list->next; 2042 | } break; 2043 | case MMDB_DATA_TYPE_BYTES: { 2044 | char *hex_string = 2045 | bytes_to_hex(entry_data_list->entry_data.bytes, 2046 | entry_data_list->entry_data.data_size); 2047 | 2048 | if (NULL == hex_string) { 2049 | *status = MMDB_OUT_OF_MEMORY_ERROR; 2050 | return NULL; 2051 | } 2052 | 2053 | print_indentation(stream, indent); 2054 | fprintf(stream, "%s \n", hex_string); 2055 | free(hex_string); 2056 | 2057 | entry_data_list = entry_data_list->next; 2058 | } break; 2059 | case MMDB_DATA_TYPE_DOUBLE: 2060 | print_indentation(stream, indent); 2061 | fprintf(stream, 2062 | "%f \n", 2063 | entry_data_list->entry_data.double_value); 2064 | entry_data_list = entry_data_list->next; 2065 | break; 2066 | case MMDB_DATA_TYPE_FLOAT: 2067 | print_indentation(stream, indent); 2068 | fprintf(stream, 2069 | "%f \n", 2070 | entry_data_list->entry_data.float_value); 2071 | entry_data_list = entry_data_list->next; 2072 | break; 2073 | case MMDB_DATA_TYPE_UINT16: 2074 | print_indentation(stream, indent); 2075 | fprintf( 2076 | stream, "%u \n", entry_data_list->entry_data.uint16); 2077 | entry_data_list = entry_data_list->next; 2078 | break; 2079 | case MMDB_DATA_TYPE_UINT32: 2080 | print_indentation(stream, indent); 2081 | fprintf( 2082 | stream, "%u \n", entry_data_list->entry_data.uint32); 2083 | entry_data_list = entry_data_list->next; 2084 | break; 2085 | case MMDB_DATA_TYPE_BOOLEAN: 2086 | print_indentation(stream, indent); 2087 | fprintf(stream, 2088 | "%s \n", 2089 | entry_data_list->entry_data.boolean ? "true" : "false"); 2090 | entry_data_list = entry_data_list->next; 2091 | break; 2092 | case MMDB_DATA_TYPE_UINT64: 2093 | print_indentation(stream, indent); 2094 | fprintf(stream, 2095 | "%" PRIu64 " \n", 2096 | entry_data_list->entry_data.uint64); 2097 | entry_data_list = entry_data_list->next; 2098 | break; 2099 | case MMDB_DATA_TYPE_UINT128: 2100 | print_indentation(stream, indent); 2101 | #if MMDB_UINT128_IS_BYTE_ARRAY 2102 | char *hex_string = bytes_to_hex( 2103 | (uint8_t *)entry_data_list->entry_data.uint128, 16); 2104 | if (NULL == hex_string) { 2105 | *status = MMDB_OUT_OF_MEMORY_ERROR; 2106 | return NULL; 2107 | } 2108 | fprintf(stream, "0x%s \n", hex_string); 2109 | free(hex_string); 2110 | #else 2111 | uint64_t high = entry_data_list->entry_data.uint128 >> 64; 2112 | uint64_t low = (uint64_t)entry_data_list->entry_data.uint128; 2113 | fprintf(stream, 2114 | "0x%016" PRIX64 "%016" PRIX64 " \n", 2115 | high, 2116 | low); 2117 | #endif 2118 | entry_data_list = entry_data_list->next; 2119 | break; 2120 | case MMDB_DATA_TYPE_INT32: 2121 | print_indentation(stream, indent); 2122 | fprintf(stream, "%d \n", entry_data_list->entry_data.int32); 2123 | entry_data_list = entry_data_list->next; 2124 | break; 2125 | default: 2126 | *status = MMDB_INVALID_DATA_ERROR; 2127 | return NULL; 2128 | } 2129 | 2130 | *status = MMDB_SUCCESS; 2131 | return entry_data_list; 2132 | } 2133 | 2134 | static void print_indentation(FILE *stream, int i) { 2135 | char buffer[1024]; 2136 | int size = i >= 1024 ? 1023 : i; 2137 | memset(buffer, 32, (size_t)size); 2138 | buffer[size] = '\0'; 2139 | fputs(buffer, stream); 2140 | } 2141 | 2142 | static char *bytes_to_hex(uint8_t const *bytes, uint32_t size) { 2143 | char *hex_string; 2144 | MAYBE_CHECK_SIZE_OVERFLOW(size, SIZE_MAX / 2 - 1, NULL); 2145 | 2146 | hex_string = calloc((size * 2) + 1, sizeof(char)); 2147 | if (NULL == hex_string) { 2148 | return NULL; 2149 | } 2150 | 2151 | for (uint32_t i = 0; i < size; i++) { 2152 | sprintf(hex_string + (2 * i), "%02X", bytes[i]); 2153 | } 2154 | 2155 | return hex_string; 2156 | } 2157 | 2158 | const char *MMDB_strerror(int error_code) { 2159 | switch (error_code) { 2160 | case MMDB_SUCCESS: 2161 | return "Success (not an error)"; 2162 | case MMDB_FILE_OPEN_ERROR: 2163 | return "Error opening the specified MaxMind DB file"; 2164 | case MMDB_CORRUPT_SEARCH_TREE_ERROR: 2165 | return "The MaxMind DB file's search tree is corrupt"; 2166 | case MMDB_INVALID_METADATA_ERROR: 2167 | return "The MaxMind DB file contains invalid metadata"; 2168 | case MMDB_IO_ERROR: 2169 | return "An attempt to read data from the MaxMind DB file failed"; 2170 | case MMDB_OUT_OF_MEMORY_ERROR: 2171 | return "A memory allocation call failed"; 2172 | case MMDB_UNKNOWN_DATABASE_FORMAT_ERROR: 2173 | return "The MaxMind DB file is in a format this library can't " 2174 | "handle (unknown record size or binary format version)"; 2175 | case MMDB_INVALID_DATA_ERROR: 2176 | return "The MaxMind DB file's data section contains bad data " 2177 | "(unknown data type or corrupt data)"; 2178 | case MMDB_INVALID_LOOKUP_PATH_ERROR: 2179 | return "The lookup path contained an invalid value (like a " 2180 | "negative integer for an array index)"; 2181 | case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR: 2182 | return "The lookup path does not match the data (key that doesn't " 2183 | "exist, array index bigger than the array, expected array " 2184 | "or map where none exists)"; 2185 | case MMDB_INVALID_NODE_NUMBER_ERROR: 2186 | return "The MMDB_read_node function was called with a node number " 2187 | "that does not exist in the search tree"; 2188 | case MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR: 2189 | return "You attempted to look up an IPv6 address in an IPv4-only " 2190 | "database"; 2191 | default: 2192 | return "Unknown error code"; 2193 | } 2194 | } 2195 | --------------------------------------------------------------------------------