* Add support for Maxmind GeoIP2 database files (#913). * Copy/paste error. * Mark GeoipCode3 as deprecated. * Fix build when compiling with AMBuild. * Replace loose libmaxminddb files with submodule. * Fix Linux build. * One more hack for submodule. * Actually fix Linux build. * GeoIP2 extension to sourcemod * Update basevotes When a player leaves during a voteban, he will be banned anyway. Also added a cvar with a ban time setting. * Update basevotes.sp * Update AMBuilder * ke::AString to std::string * Update extension.cpp * Update AMBuilder * Added coordination natives Added GeoipLatitude, GeoipLongitude, GeoipDistance natives. * Create osdefs.h * Update maxminddb_config.h * Update extension.cpp * Update extension.cpp * Added automatic search for database file * Fix automatic search for database file * Update extension.cpp * Update geoip.inc * .gitmodules revert * Update geoip.inc * Update libmaxminddb to version 1.5.2 * Update extension.cpp * Check language in the DB * Removed langCount variable * Determination of the client's language * Update geoip.inc * Update geoip.inc * Update extension.cpp * Update geoip.inc * Update extension.cpp * space instead of tab in .inc * Update extension.cpp * Update geoip.inc * Optimizing length measurement region code * Update package script to fetch the new GeoLite2 database This package is the last CC-BY-SA licensed GeoLite2-City database extracted from https://src.fedoraproject.org/rpms/geolite2 from december 2019. This doubles the download size for SM packages, but it's what we have to deal with atm :( * Fix potentially returning uninitialized memory in GeoipRegionCode If the lookup failed, we'd copy back whatever is on the stack in the ccode buffer. Co-authored-by: Nick Hastings <nshastings@gmail.com> Co-authored-by: Headline <michaelwflaherty@me.com> Co-authored-by: Accelerator74 <dmitry@447751-accele74.tmweb.ru> Co-authored-by: Peace-Maker <peace-maker@wcfan.de>
		
			
				
	
	
		
			2093 lines
		
	
	
		
			70 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2093 lines
		
	
	
		
			70 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#if HAVE_CONFIG_H
 | 
						|
#include <config.h>
 | 
						|
#endif
 | 
						|
#include "data-pool.h"
 | 
						|
#include "maxminddb-compat-util.h"
 | 
						|
#include "maxminddb.h"
 | 
						|
#include <assert.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <inttypes.h>
 | 
						|
#include <stdint.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <sys/stat.h>
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
#ifndef UNICODE
 | 
						|
#define UNICODE
 | 
						|
#endif
 | 
						|
#include <windows.h>
 | 
						|
#include <ws2ipdef.h>
 | 
						|
#else
 | 
						|
#include <arpa/inet.h>
 | 
						|
#include <sys/mman.h>
 | 
						|
#include <unistd.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#define MMDB_DATA_SECTION_SEPARATOR (16)
 | 
						|
#define MAXIMUM_DATA_STRUCTURE_DEPTH (512)
 | 
						|
 | 
						|
#ifdef MMDB_DEBUG
 | 
						|
#define DEBUG_MSG(msg) fprintf(stderr, msg "\n")
 | 
						|
#define DEBUG_MSGF(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__)
 | 
						|
#define DEBUG_BINARY(fmt, byte)                                                \
 | 
						|
    do {                                                                       \
 | 
						|
        char *binary = byte_to_binary(byte);                                   \
 | 
						|
        if (NULL == binary) {                                                  \
 | 
						|
            fprintf(stderr, "Calloc failed in DEBUG_BINARY\n");                \
 | 
						|
            abort();                                                           \
 | 
						|
        }                                                                      \
 | 
						|
        fprintf(stderr, fmt "\n", binary);                                     \
 | 
						|
        free(binary);                                                          \
 | 
						|
    } while (0)
 | 
						|
#define DEBUG_NL fprintf(stderr, "\n")
 | 
						|
#else
 | 
						|
#define DEBUG_MSG(...)
 | 
						|
#define DEBUG_MSGF(...)
 | 
						|
#define DEBUG_BINARY(...)
 | 
						|
#define DEBUG_NL
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef MMDB_DEBUG
 | 
						|
char *byte_to_binary(uint8_t byte) {
 | 
						|
    char *bits = calloc(9, sizeof(char));
 | 
						|
    if (NULL == bits) {
 | 
						|
        return bits;
 | 
						|
    }
 | 
						|
 | 
						|
    for (uint8_t i = 0; i < 8; i++) {
 | 
						|
        bits[i] = byte & (128 >> i) ? '1' : '0';
 | 
						|
    }
 | 
						|
    bits[8] = '\0';
 | 
						|
 | 
						|
    return bits;
 | 
						|
}
 | 
						|
 | 
						|
char *type_num_to_name(uint8_t num) {
 | 
						|
    switch (num) {
 | 
						|
        case 0:
 | 
						|
            return "extended";
 | 
						|
        case 1:
 | 
						|
            return "pointer";
 | 
						|
        case 2:
 | 
						|
            return "utf8_string";
 | 
						|
        case 3:
 | 
						|
            return "double";
 | 
						|
        case 4:
 | 
						|
            return "bytes";
 | 
						|
        case 5:
 | 
						|
            return "uint16";
 | 
						|
        case 6:
 | 
						|
            return "uint32";
 | 
						|
        case 7:
 | 
						|
            return "map";
 | 
						|
        case 8:
 | 
						|
            return "int32";
 | 
						|
        case 9:
 | 
						|
            return "uint64";
 | 
						|
        case 10:
 | 
						|
            return "uint128";
 | 
						|
        case 11:
 | 
						|
            return "array";
 | 
						|
        case 12:
 | 
						|
            return "container";
 | 
						|
        case 13:
 | 
						|
            return "end_marker";
 | 
						|
        case 14:
 | 
						|
            return "boolean";
 | 
						|
        case 15:
 | 
						|
            return "float";
 | 
						|
        default:
 | 
						|
            return "unknown type";
 | 
						|
    }
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
/* None of the values we check on the lhs are bigger than uint32_t, so on
 | 
						|
 * platforms where SIZE_MAX is a 64-bit integer, this would be a no-op, and it
 | 
						|
 * makes the compiler complain if we do the check anyway. */
 | 
						|
#if SIZE_MAX == UINT32_MAX
 | 
						|
#define MAYBE_CHECK_SIZE_OVERFLOW(lhs, rhs, error)                             \
 | 
						|
    if ((lhs) > (rhs)) {                                                       \
 | 
						|
        return error;                                                          \
 | 
						|
    }
 | 
						|
#else
 | 
						|
#define MAYBE_CHECK_SIZE_OVERFLOW(...)
 | 
						|
#endif
 | 
						|
 | 
						|
typedef struct record_info_s {
 | 
						|
    uint16_t record_length;
 | 
						|
    uint32_t (*left_record_getter)(const uint8_t *);
 | 
						|
    uint32_t (*right_record_getter)(const uint8_t *);
 | 
						|
    uint8_t right_record_offset;
 | 
						|
} record_info_s;
 | 
						|
 | 
						|
#define METADATA_MARKER "\xab\xcd\xefMaxMind.com"
 | 
						|
/* This is 128kb */
 | 
						|
#define METADATA_BLOCK_MAX_SIZE 131072
 | 
						|
 | 
						|
// 64 leads us to allocating 4 KiB on a 64bit system.
 | 
						|
#define MMDB_POOL_INIT_SIZE 64
 | 
						|
 | 
						|
static int map_file(MMDB_s *const mmdb);
 | 
						|
static const uint8_t *find_metadata(const uint8_t *file_content,
 | 
						|
                                    ssize_t file_size,
 | 
						|
                                    uint32_t *metadata_size);
 | 
						|
static int read_metadata(MMDB_s *mmdb);
 | 
						|
static MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb);
 | 
						|
static int
 | 
						|
value_for_key_as_uint16(MMDB_entry_s *start, char *key, uint16_t *value);
 | 
						|
static int
 | 
						|
value_for_key_as_uint32(MMDB_entry_s *start, char *key, uint32_t *value);
 | 
						|
static int
 | 
						|
value_for_key_as_uint64(MMDB_entry_s *start, char *key, uint64_t *value);
 | 
						|
static int
 | 
						|
value_for_key_as_string(MMDB_entry_s *start, char *key, char const **value);
 | 
						|
static int populate_languages_metadata(MMDB_s *mmdb,
 | 
						|
                                       MMDB_s *metadata_db,
 | 
						|
                                       MMDB_entry_s *metadata_start);
 | 
						|
static int populate_description_metadata(MMDB_s *mmdb,
 | 
						|
                                         MMDB_s *metadata_db,
 | 
						|
                                         MMDB_entry_s *metadata_start);
 | 
						|
static int resolve_any_address(const char *ipstr, struct addrinfo **addresses);
 | 
						|
static int find_address_in_search_tree(const MMDB_s *const mmdb,
 | 
						|
                                       uint8_t *address,
 | 
						|
                                       sa_family_t address_family,
 | 
						|
                                       MMDB_lookup_result_s *result);
 | 
						|
static record_info_s record_info_for_database(const MMDB_s *const mmdb);
 | 
						|
static int find_ipv4_start_node(MMDB_s *const mmdb);
 | 
						|
static uint8_t record_type(const MMDB_s *const mmdb, uint64_t record);
 | 
						|
static uint32_t get_left_28_bit_record(const uint8_t *record);
 | 
						|
static uint32_t get_right_28_bit_record(const uint8_t *record);
 | 
						|
static uint32_t data_section_offset_for_record(const MMDB_s *const mmdb,
 | 
						|
                                               uint64_t record);
 | 
						|
static int path_length(va_list va_path);
 | 
						|
static int lookup_path_in_array(const char *path_elem,
 | 
						|
                                const MMDB_s *const mmdb,
 | 
						|
                                MMDB_entry_data_s *entry_data);
 | 
						|
static int lookup_path_in_map(const char *path_elem,
 | 
						|
                              const MMDB_s *const mmdb,
 | 
						|
                              MMDB_entry_data_s *entry_data);
 | 
						|
static int skip_map_or_array(const MMDB_s *const mmdb,
 | 
						|
                             MMDB_entry_data_s *entry_data);
 | 
						|
static int decode_one_follow(const MMDB_s *const mmdb,
 | 
						|
                             uint32_t offset,
 | 
						|
                             MMDB_entry_data_s *entry_data);
 | 
						|
static int decode_one(const MMDB_s *const mmdb,
 | 
						|
                      uint32_t offset,
 | 
						|
                      MMDB_entry_data_s *entry_data);
 | 
						|
static int get_ext_type(int raw_ext_type);
 | 
						|
static uint32_t
 | 
						|
get_ptr_from(uint8_t ctrl, uint8_t const *const ptr, int ptr_size);
 | 
						|
static int get_entry_data_list(const MMDB_s *const mmdb,
 | 
						|
                               uint32_t offset,
 | 
						|
                               MMDB_entry_data_list_s *const entry_data_list,
 | 
						|
                               MMDB_data_pool_s *const pool,
 | 
						|
                               int depth);
 | 
						|
static float get_ieee754_float(const uint8_t *restrict p);
 | 
						|
static double get_ieee754_double(const uint8_t *restrict p);
 | 
						|
static uint32_t get_uint32(const uint8_t *p);
 | 
						|
static uint32_t get_uint24(const uint8_t *p);
 | 
						|
static uint32_t get_uint16(const uint8_t *p);
 | 
						|
static uint64_t get_uintX(const uint8_t *p, int length);
 | 
						|
static int32_t get_sintX(const uint8_t *p, int length);
 | 
						|
static void free_mmdb_struct(MMDB_s *const mmdb);
 | 
						|
static void free_languages_metadata(MMDB_s *mmdb);
 | 
						|
static void free_descriptions_metadata(MMDB_s *mmdb);
 | 
						|
static MMDB_entry_data_list_s *
 | 
						|
dump_entry_data_list(FILE *stream,
 | 
						|
                     MMDB_entry_data_list_s *entry_data_list,
 | 
						|
                     int indent,
 | 
						|
                     int *status);
 | 
						|
static void print_indentation(FILE *stream, int i);
 | 
						|
static char *bytes_to_hex(uint8_t *bytes, uint32_t size);
 | 
						|
 | 
						|
#define CHECKED_DECODE_ONE(mmdb, offset, entry_data)                           \
 | 
						|
    do {                                                                       \
 | 
						|
        int status = decode_one(mmdb, offset, entry_data);                     \
 | 
						|
        if (MMDB_SUCCESS != status) {                                          \
 | 
						|
            DEBUG_MSGF("CHECKED_DECODE_ONE failed."                            \
 | 
						|
                       " status = %d (%s)",                                    \
 | 
						|
                       status,                                                 \
 | 
						|
                       MMDB_strerror(status));                                 \
 | 
						|
            return status;                                                     \
 | 
						|
        }                                                                      \
 | 
						|
    } while (0)
 | 
						|
 | 
						|
#define CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data)                    \
 | 
						|
    do {                                                                       \
 | 
						|
        int status = decode_one_follow(mmdb, offset, entry_data);              \
 | 
						|
        if (MMDB_SUCCESS != status) {                                          \
 | 
						|
            DEBUG_MSGF("CHECKED_DECODE_ONE_FOLLOW failed."                     \
 | 
						|
                       " status = %d (%s)",                                    \
 | 
						|
                       status,                                                 \
 | 
						|
                       MMDB_strerror(status));                                 \
 | 
						|
            return status;                                                     \
 | 
						|
        }                                                                      \
 | 
						|
    } while (0)
 | 
						|
 | 
						|
#define FREE_AND_SET_NULL(p)                                                   \
 | 
						|
    {                                                                          \
 | 
						|
        free((void *)(p));                                                     \
 | 
						|
        (p) = NULL;                                                            \
 | 
						|
    }
 | 
						|
 | 
						|
int MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb) {
 | 
						|
    int status = MMDB_SUCCESS;
 | 
						|
 | 
						|
    mmdb->file_content = NULL;
 | 
						|
    mmdb->data_section = NULL;
 | 
						|
    mmdb->metadata.database_type = NULL;
 | 
						|
    mmdb->metadata.languages.count = 0;
 | 
						|
    mmdb->metadata.languages.names = NULL;
 | 
						|
    mmdb->metadata.description.count = 0;
 | 
						|
 | 
						|
    mmdb->filename = mmdb_strdup(filename);
 | 
						|
    if (NULL == mmdb->filename) {
 | 
						|
        status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    if ((flags & MMDB_MODE_MASK) == 0) {
 | 
						|
        flags |= MMDB_MODE_MMAP;
 | 
						|
    }
 | 
						|
    mmdb->flags = flags;
 | 
						|
 | 
						|
    if (MMDB_SUCCESS != (status = map_file(mmdb))) {
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
    WSADATA wsa;
 | 
						|
    WSAStartup(MAKEWORD(2, 2), &wsa);
 | 
						|
#endif
 | 
						|
 | 
						|
    uint32_t metadata_size = 0;
 | 
						|
    const uint8_t *metadata =
 | 
						|
        find_metadata(mmdb->file_content, mmdb->file_size, &metadata_size);
 | 
						|
    if (NULL == metadata) {
 | 
						|
        status = MMDB_INVALID_METADATA_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    mmdb->metadata_section = metadata;
 | 
						|
    mmdb->metadata_section_size = metadata_size;
 | 
						|
 | 
						|
    status = read_metadata(mmdb);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    if (mmdb->metadata.binary_format_major_version != 2) {
 | 
						|
        status = MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    uint32_t search_tree_size =
 | 
						|
        mmdb->metadata.node_count * mmdb->full_record_byte_size;
 | 
						|
 | 
						|
    mmdb->data_section =
 | 
						|
        mmdb->file_content + search_tree_size + MMDB_DATA_SECTION_SEPARATOR;
 | 
						|
    if (search_tree_size + MMDB_DATA_SECTION_SEPARATOR >
 | 
						|
        (uint32_t)mmdb->file_size) {
 | 
						|
        status = MMDB_INVALID_METADATA_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
    mmdb->data_section_size = (uint32_t)mmdb->file_size - search_tree_size -
 | 
						|
                              MMDB_DATA_SECTION_SEPARATOR;
 | 
						|
 | 
						|
    // Although it is likely not possible to construct a database with valid
 | 
						|
    // valid metadata, as parsed above, and a data_section_size less than 3,
 | 
						|
    // we do this check as later we assume it is at least three when doing
 | 
						|
    // bound checks.
 | 
						|
    if (mmdb->data_section_size < 3) {
 | 
						|
        status = MMDB_INVALID_DATA_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    mmdb->metadata_section = metadata;
 | 
						|
    mmdb->ipv4_start_node.node_value = 0;
 | 
						|
    mmdb->ipv4_start_node.netmask = 0;
 | 
						|
 | 
						|
    // We do this immediately as otherwise there is a race to set
 | 
						|
    // ipv4_start_node.node_value and ipv4_start_node.netmask.
 | 
						|
    if (mmdb->metadata.ip_version == 6) {
 | 
						|
        status = find_ipv4_start_node(mmdb);
 | 
						|
        if (status != MMDB_SUCCESS) {
 | 
						|
            goto cleanup;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
cleanup:
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        int saved_errno = errno;
 | 
						|
        free_mmdb_struct(mmdb);
 | 
						|
        errno = saved_errno;
 | 
						|
    }
 | 
						|
    return status;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
 | 
						|
static LPWSTR utf8_to_utf16(const char *utf8_str) {
 | 
						|
    int wide_chars = MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, NULL, 0);
 | 
						|
    wchar_t *utf16_str = (wchar_t *)calloc(wide_chars, sizeof(wchar_t));
 | 
						|
    if (!utf16_str) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, utf16_str, wide_chars) <
 | 
						|
        1) {
 | 
						|
        free(utf16_str);
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    return utf16_str;
 | 
						|
}
 | 
						|
 | 
						|
static int map_file(MMDB_s *const mmdb) {
 | 
						|
    DWORD size;
 | 
						|
    int status = MMDB_SUCCESS;
 | 
						|
    HANDLE mmh = NULL;
 | 
						|
    HANDLE fd = INVALID_HANDLE_VALUE;
 | 
						|
    LPWSTR utf16_filename = utf8_to_utf16(mmdb->filename);
 | 
						|
    if (!utf16_filename) {
 | 
						|
        status = MMDB_FILE_OPEN_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
    fd = CreateFileW(utf16_filename,
 | 
						|
                     GENERIC_READ,
 | 
						|
                     FILE_SHARE_READ,
 | 
						|
                     NULL,
 | 
						|
                     OPEN_EXISTING,
 | 
						|
                     FILE_ATTRIBUTE_NORMAL,
 | 
						|
                     NULL);
 | 
						|
    if (fd == INVALID_HANDLE_VALUE) {
 | 
						|
        status = MMDB_FILE_OPEN_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
    size = GetFileSize(fd, NULL);
 | 
						|
    if (size == INVALID_FILE_SIZE) {
 | 
						|
        status = MMDB_FILE_OPEN_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
    mmh = CreateFileMapping(fd, NULL, PAGE_READONLY, 0, size, NULL);
 | 
						|
    /* Microsoft documentation for CreateFileMapping indicates this returns
 | 
						|
        NULL not INVALID_HANDLE_VALUE on error */
 | 
						|
    if (NULL == mmh) {
 | 
						|
        status = MMDB_IO_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
    uint8_t *file_content =
 | 
						|
        (uint8_t *)MapViewOfFile(mmh, FILE_MAP_READ, 0, 0, 0);
 | 
						|
    if (file_content == NULL) {
 | 
						|
        status = MMDB_IO_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    mmdb->file_size = size;
 | 
						|
    mmdb->file_content = file_content;
 | 
						|
 | 
						|
cleanup:;
 | 
						|
    int saved_errno = errno;
 | 
						|
    if (INVALID_HANDLE_VALUE != fd) {
 | 
						|
        CloseHandle(fd);
 | 
						|
    }
 | 
						|
    if (NULL != mmh) {
 | 
						|
        CloseHandle(mmh);
 | 
						|
    }
 | 
						|
    errno = saved_errno;
 | 
						|
    free(utf16_filename);
 | 
						|
 | 
						|
    return status;
 | 
						|
}
 | 
						|
 | 
						|
#else // _WIN32
 | 
						|
 | 
						|
static int map_file(MMDB_s *const mmdb) {
 | 
						|
    ssize_t size;
 | 
						|
    int status = MMDB_SUCCESS;
 | 
						|
 | 
						|
    int flags = O_RDONLY;
 | 
						|
#ifdef O_CLOEXEC
 | 
						|
    flags |= O_CLOEXEC;
 | 
						|
#endif
 | 
						|
    int fd = open(mmdb->filename, flags);
 | 
						|
    struct stat s;
 | 
						|
    if (fd < 0 || fstat(fd, &s)) {
 | 
						|
        status = MMDB_FILE_OPEN_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    size = s.st_size;
 | 
						|
    if (size < 0 || size != s.st_size) {
 | 
						|
        status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    uint8_t *file_content =
 | 
						|
        (uint8_t *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
 | 
						|
    if (MAP_FAILED == file_content) {
 | 
						|
        if (ENOMEM == errno) {
 | 
						|
            status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
        } else {
 | 
						|
            status = MMDB_IO_ERROR;
 | 
						|
        }
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    mmdb->file_size = size;
 | 
						|
    mmdb->file_content = file_content;
 | 
						|
 | 
						|
cleanup:;
 | 
						|
    int saved_errno = errno;
 | 
						|
    if (fd >= 0) {
 | 
						|
        close(fd);
 | 
						|
    }
 | 
						|
    errno = saved_errno;
 | 
						|
 | 
						|
    return status;
 | 
						|
}
 | 
						|
 | 
						|
#endif // _WIN32
 | 
						|
 | 
						|
static const uint8_t *find_metadata(const uint8_t *file_content,
 | 
						|
                                    ssize_t file_size,
 | 
						|
                                    uint32_t *metadata_size) {
 | 
						|
    const ssize_t marker_len = sizeof(METADATA_MARKER) - 1;
 | 
						|
    ssize_t max_size = file_size > METADATA_BLOCK_MAX_SIZE
 | 
						|
                           ? METADATA_BLOCK_MAX_SIZE
 | 
						|
                           : file_size;
 | 
						|
 | 
						|
    uint8_t *search_area = (uint8_t *)(file_content + (file_size - max_size));
 | 
						|
    uint8_t *start = search_area;
 | 
						|
    uint8_t *tmp;
 | 
						|
    do {
 | 
						|
        tmp = mmdb_memmem(search_area, max_size, METADATA_MARKER, marker_len);
 | 
						|
 | 
						|
        if (NULL != tmp) {
 | 
						|
            max_size -= tmp - search_area;
 | 
						|
            search_area = tmp;
 | 
						|
 | 
						|
            /* Continue searching just after the marker we just read, in case
 | 
						|
             * there are multiple markers in the same file. This would be odd
 | 
						|
             * but is certainly not impossible. */
 | 
						|
            max_size -= marker_len;
 | 
						|
            search_area += marker_len;
 | 
						|
        }
 | 
						|
    } while (NULL != tmp);
 | 
						|
 | 
						|
    if (search_area == start) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    *metadata_size = (uint32_t)max_size;
 | 
						|
 | 
						|
    return search_area;
 | 
						|
}
 | 
						|
 | 
						|
static int read_metadata(MMDB_s *mmdb) {
 | 
						|
    /* We need to create a fake MMDB_s struct in order to decode values from
 | 
						|
       the metadata. The metadata is basically just like the data section, so we
 | 
						|
       want to use the same functions we use for the data section to get
 | 
						|
       metadata values. */
 | 
						|
    MMDB_s metadata_db = make_fake_metadata_db(mmdb);
 | 
						|
 | 
						|
    MMDB_entry_s metadata_start = {.mmdb = &metadata_db, .offset = 0};
 | 
						|
 | 
						|
    int status = value_for_key_as_uint32(
 | 
						|
        &metadata_start, "node_count", &mmdb->metadata.node_count);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (!mmdb->metadata.node_count) {
 | 
						|
        DEBUG_MSG("could not find node_count value in metadata");
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    status = value_for_key_as_uint16(
 | 
						|
        &metadata_start, "record_size", &mmdb->metadata.record_size);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (!mmdb->metadata.record_size) {
 | 
						|
        DEBUG_MSG("could not find record_size value in metadata");
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    if (mmdb->metadata.record_size != 24 && mmdb->metadata.record_size != 28 &&
 | 
						|
        mmdb->metadata.record_size != 32) {
 | 
						|
        DEBUG_MSGF("bad record size in metadata: %i",
 | 
						|
                   mmdb->metadata.record_size);
 | 
						|
        return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    status = value_for_key_as_uint16(
 | 
						|
        &metadata_start, "ip_version", &mmdb->metadata.ip_version);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (!mmdb->metadata.ip_version) {
 | 
						|
        DEBUG_MSG("could not find ip_version value in metadata");
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
    if (!(mmdb->metadata.ip_version == 4 || mmdb->metadata.ip_version == 6)) {
 | 
						|
        DEBUG_MSGF("ip_version value in metadata is not 4 or 6 - it was %i",
 | 
						|
                   mmdb->metadata.ip_version);
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    status = value_for_key_as_string(
 | 
						|
        &metadata_start, "database_type", &mmdb->metadata.database_type);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        DEBUG_MSG("error finding database_type value in metadata");
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
 | 
						|
    status = populate_languages_metadata(mmdb, &metadata_db, &metadata_start);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        DEBUG_MSG("could not populate languages from metadata");
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
 | 
						|
    status =
 | 
						|
        value_for_key_as_uint16(&metadata_start,
 | 
						|
                                "binary_format_major_version",
 | 
						|
                                &mmdb->metadata.binary_format_major_version);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (!mmdb->metadata.binary_format_major_version) {
 | 
						|
        DEBUG_MSG(
 | 
						|
            "could not find binary_format_major_version value in metadata");
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    status =
 | 
						|
        value_for_key_as_uint16(&metadata_start,
 | 
						|
                                "binary_format_minor_version",
 | 
						|
                                &mmdb->metadata.binary_format_minor_version);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
 | 
						|
    status = value_for_key_as_uint64(
 | 
						|
        &metadata_start, "build_epoch", &mmdb->metadata.build_epoch);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (!mmdb->metadata.build_epoch) {
 | 
						|
        DEBUG_MSG("could not find build_epoch value in metadata");
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    status = populate_description_metadata(mmdb, &metadata_db, &metadata_start);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        DEBUG_MSG("could not populate description from metadata");
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
 | 
						|
    mmdb->full_record_byte_size = mmdb->metadata.record_size * 2 / 8U;
 | 
						|
 | 
						|
    mmdb->depth = mmdb->metadata.ip_version == 4 ? 32 : 128;
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb) {
 | 
						|
    MMDB_s fake_metadata_db = {.data_section = mmdb->metadata_section,
 | 
						|
                               .data_section_size =
 | 
						|
                                   mmdb->metadata_section_size};
 | 
						|
 | 
						|
    return fake_metadata_db;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
value_for_key_as_uint16(MMDB_entry_s *start, char *key, uint16_t *value) {
 | 
						|
    MMDB_entry_data_s entry_data;
 | 
						|
    const char *path[] = {key, NULL};
 | 
						|
    int status = MMDB_aget_value(start, &entry_data, path);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (MMDB_DATA_TYPE_UINT16 != entry_data.type) {
 | 
						|
        DEBUG_MSGF("expect uint16 for %s but received %s",
 | 
						|
                   key,
 | 
						|
                   type_num_to_name(entry_data.type));
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
    *value = entry_data.uint16;
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
value_for_key_as_uint32(MMDB_entry_s *start, char *key, uint32_t *value) {
 | 
						|
    MMDB_entry_data_s entry_data;
 | 
						|
    const char *path[] = {key, NULL};
 | 
						|
    int status = MMDB_aget_value(start, &entry_data, path);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (MMDB_DATA_TYPE_UINT32 != entry_data.type) {
 | 
						|
        DEBUG_MSGF("expect uint32 for %s but received %s",
 | 
						|
                   key,
 | 
						|
                   type_num_to_name(entry_data.type));
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
    *value = entry_data.uint32;
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
value_for_key_as_uint64(MMDB_entry_s *start, char *key, uint64_t *value) {
 | 
						|
    MMDB_entry_data_s entry_data;
 | 
						|
    const char *path[] = {key, NULL};
 | 
						|
    int status = MMDB_aget_value(start, &entry_data, path);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (MMDB_DATA_TYPE_UINT64 != entry_data.type) {
 | 
						|
        DEBUG_MSGF("expect uint64 for %s but received %s",
 | 
						|
                   key,
 | 
						|
                   type_num_to_name(entry_data.type));
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
    *value = entry_data.uint64;
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
value_for_key_as_string(MMDB_entry_s *start, char *key, char const **value) {
 | 
						|
    MMDB_entry_data_s entry_data;
 | 
						|
    const char *path[] = {key, NULL};
 | 
						|
    int status = MMDB_aget_value(start, &entry_data, path);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (MMDB_DATA_TYPE_UTF8_STRING != entry_data.type) {
 | 
						|
        DEBUG_MSGF("expect string for %s but received %s",
 | 
						|
                   key,
 | 
						|
                   type_num_to_name(entry_data.type));
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
    *value = mmdb_strndup((char *)entry_data.utf8_string, entry_data.data_size);
 | 
						|
    if (NULL == *value) {
 | 
						|
        return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
    }
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int populate_languages_metadata(MMDB_s *mmdb,
 | 
						|
                                       MMDB_s *metadata_db,
 | 
						|
                                       MMDB_entry_s *metadata_start) {
 | 
						|
    MMDB_entry_data_s entry_data;
 | 
						|
 | 
						|
    const char *path[] = {"languages", NULL};
 | 
						|
    int status = MMDB_aget_value(metadata_start, &entry_data, path);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
    if (MMDB_DATA_TYPE_ARRAY != entry_data.type) {
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    MMDB_entry_s array_start = {.mmdb = metadata_db,
 | 
						|
                                .offset = entry_data.offset};
 | 
						|
 | 
						|
    MMDB_entry_data_list_s *member;
 | 
						|
    status = MMDB_get_entry_data_list(&array_start, &member);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
 | 
						|
    MMDB_entry_data_list_s *first_member = member;
 | 
						|
 | 
						|
    uint32_t array_size = member->entry_data.data_size;
 | 
						|
    MAYBE_CHECK_SIZE_OVERFLOW(
 | 
						|
        array_size, SIZE_MAX / sizeof(char *), MMDB_INVALID_METADATA_ERROR);
 | 
						|
 | 
						|
    mmdb->metadata.languages.count = 0;
 | 
						|
    mmdb->metadata.languages.names = calloc(array_size, sizeof(char *));
 | 
						|
    if (NULL == mmdb->metadata.languages.names) {
 | 
						|
        return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    for (uint32_t i = 0; i < array_size; i++) {
 | 
						|
        member = member->next;
 | 
						|
        if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
 | 
						|
            return MMDB_INVALID_METADATA_ERROR;
 | 
						|
        }
 | 
						|
 | 
						|
        mmdb->metadata.languages.names[i] =
 | 
						|
            mmdb_strndup((char *)member->entry_data.utf8_string,
 | 
						|
                         member->entry_data.data_size);
 | 
						|
 | 
						|
        if (NULL == mmdb->metadata.languages.names[i]) {
 | 
						|
            return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
        }
 | 
						|
        // We assign this as we go so that if we fail a calloc and need to
 | 
						|
        // free it, the count is right.
 | 
						|
        mmdb->metadata.languages.count = i + 1;
 | 
						|
    }
 | 
						|
 | 
						|
    MMDB_free_entry_data_list(first_member);
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int populate_description_metadata(MMDB_s *mmdb,
 | 
						|
                                         MMDB_s *metadata_db,
 | 
						|
                                         MMDB_entry_s *metadata_start) {
 | 
						|
    MMDB_entry_data_s entry_data;
 | 
						|
 | 
						|
    const char *path[] = {"description", NULL};
 | 
						|
    int status = MMDB_aget_value(metadata_start, &entry_data, path);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
 | 
						|
    if (MMDB_DATA_TYPE_MAP != entry_data.type) {
 | 
						|
        DEBUG_MSGF("Unexpected entry_data type: %d", entry_data.type);
 | 
						|
        return MMDB_INVALID_METADATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    MMDB_entry_s map_start = {.mmdb = metadata_db, .offset = entry_data.offset};
 | 
						|
 | 
						|
    MMDB_entry_data_list_s *member;
 | 
						|
    status = MMDB_get_entry_data_list(&map_start, &member);
 | 
						|
    if (MMDB_SUCCESS != status) {
 | 
						|
        DEBUG_MSGF(
 | 
						|
            "MMDB_get_entry_data_list failed while populating description."
 | 
						|
            " status = %d (%s)",
 | 
						|
            status,
 | 
						|
            MMDB_strerror(status));
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
 | 
						|
    MMDB_entry_data_list_s *first_member = member;
 | 
						|
 | 
						|
    uint32_t map_size = member->entry_data.data_size;
 | 
						|
    mmdb->metadata.description.count = 0;
 | 
						|
    if (0 == map_size) {
 | 
						|
        mmdb->metadata.description.descriptions = NULL;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
    MAYBE_CHECK_SIZE_OVERFLOW(map_size,
 | 
						|
                              SIZE_MAX / sizeof(MMDB_description_s *),
 | 
						|
                              MMDB_INVALID_METADATA_ERROR);
 | 
						|
 | 
						|
    mmdb->metadata.description.descriptions =
 | 
						|
        calloc(map_size, sizeof(MMDB_description_s *));
 | 
						|
    if (NULL == mmdb->metadata.description.descriptions) {
 | 
						|
        status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
        goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    for (uint32_t i = 0; i < map_size; i++) {
 | 
						|
        mmdb->metadata.description.descriptions[i] =
 | 
						|
            calloc(1, sizeof(MMDB_description_s));
 | 
						|
        if (NULL == mmdb->metadata.description.descriptions[i]) {
 | 
						|
            status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
            goto cleanup;
 | 
						|
        }
 | 
						|
 | 
						|
        mmdb->metadata.description.count = i + 1;
 | 
						|
        mmdb->metadata.description.descriptions[i]->language = NULL;
 | 
						|
        mmdb->metadata.description.descriptions[i]->description = NULL;
 | 
						|
 | 
						|
        member = member->next;
 | 
						|
 | 
						|
        if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
 | 
						|
            status = MMDB_INVALID_METADATA_ERROR;
 | 
						|
            goto cleanup;
 | 
						|
        }
 | 
						|
 | 
						|
        mmdb->metadata.description.descriptions[i]->language =
 | 
						|
            mmdb_strndup((char *)member->entry_data.utf8_string,
 | 
						|
                         member->entry_data.data_size);
 | 
						|
 | 
						|
        if (NULL == mmdb->metadata.description.descriptions[i]->language) {
 | 
						|
            status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
            goto cleanup;
 | 
						|
        }
 | 
						|
 | 
						|
        member = member->next;
 | 
						|
 | 
						|
        if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
 | 
						|
            status = MMDB_INVALID_METADATA_ERROR;
 | 
						|
            goto cleanup;
 | 
						|
        }
 | 
						|
 | 
						|
        mmdb->metadata.description.descriptions[i]->description =
 | 
						|
            mmdb_strndup((char *)member->entry_data.utf8_string,
 | 
						|
                         member->entry_data.data_size);
 | 
						|
 | 
						|
        if (NULL == mmdb->metadata.description.descriptions[i]->description) {
 | 
						|
            status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
            goto cleanup;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
cleanup:
 | 
						|
    MMDB_free_entry_data_list(first_member);
 | 
						|
 | 
						|
    return status;
 | 
						|
}
 | 
						|
 | 
						|
MMDB_lookup_result_s MMDB_lookup_string(const MMDB_s *const mmdb,
 | 
						|
                                        const char *const ipstr,
 | 
						|
                                        int *const gai_error,
 | 
						|
                                        int *const mmdb_error) {
 | 
						|
    MMDB_lookup_result_s result = {.found_entry = false,
 | 
						|
                                   .netmask = 0,
 | 
						|
                                   .entry = {.mmdb = mmdb, .offset = 0}};
 | 
						|
 | 
						|
    struct addrinfo *addresses = NULL;
 | 
						|
    *gai_error = resolve_any_address(ipstr, &addresses);
 | 
						|
 | 
						|
    if (!*gai_error) {
 | 
						|
        result = MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, mmdb_error);
 | 
						|
    }
 | 
						|
 | 
						|
    if (NULL != addresses) {
 | 
						|
        freeaddrinfo(addresses);
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
static int resolve_any_address(const char *ipstr, struct addrinfo **addresses) {
 | 
						|
    struct addrinfo hints = {
 | 
						|
        .ai_family = AF_UNSPEC,
 | 
						|
        .ai_flags = AI_NUMERICHOST,
 | 
						|
        // We set ai_socktype so that we only get one result back
 | 
						|
        .ai_socktype = SOCK_STREAM};
 | 
						|
 | 
						|
    int gai_status = getaddrinfo(ipstr, NULL, &hints, addresses);
 | 
						|
    if (gai_status) {
 | 
						|
        return gai_status;
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
MMDB_lookup_result_s MMDB_lookup_sockaddr(const MMDB_s *const mmdb,
 | 
						|
                                          const struct sockaddr *const sockaddr,
 | 
						|
                                          int *const mmdb_error) {
 | 
						|
    MMDB_lookup_result_s result = {.found_entry = false,
 | 
						|
                                   .netmask = 0,
 | 
						|
                                   .entry = {.mmdb = mmdb, .offset = 0}};
 | 
						|
 | 
						|
    uint8_t mapped_address[16], *address;
 | 
						|
    if (mmdb->metadata.ip_version == 4) {
 | 
						|
        if (sockaddr->sa_family == AF_INET6) {
 | 
						|
            *mmdb_error = MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR;
 | 
						|
            return result;
 | 
						|
        }
 | 
						|
        address = (uint8_t *)&((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
 | 
						|
    } else {
 | 
						|
        if (sockaddr->sa_family == AF_INET6) {
 | 
						|
            address = (uint8_t *)&((struct sockaddr_in6 *)sockaddr)
 | 
						|
                          ->sin6_addr.s6_addr;
 | 
						|
        } else {
 | 
						|
            address = mapped_address;
 | 
						|
            memset(address, 0, 12);
 | 
						|
            memcpy(address + 12,
 | 
						|
                   &((struct sockaddr_in *)sockaddr)->sin_addr.s_addr,
 | 
						|
                   4);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    *mmdb_error = find_address_in_search_tree(
 | 
						|
        mmdb, address, sockaddr->sa_family, &result);
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
static int find_address_in_search_tree(const MMDB_s *const mmdb,
 | 
						|
                                       uint8_t *address,
 | 
						|
                                       sa_family_t address_family,
 | 
						|
                                       MMDB_lookup_result_s *result) {
 | 
						|
    record_info_s record_info = record_info_for_database(mmdb);
 | 
						|
    if (0 == record_info.right_record_offset) {
 | 
						|
        return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    uint32_t value = 0;
 | 
						|
    uint16_t current_bit = 0;
 | 
						|
    if (mmdb->metadata.ip_version == 6 && address_family == AF_INET) {
 | 
						|
        value = mmdb->ipv4_start_node.node_value;
 | 
						|
        current_bit = mmdb->ipv4_start_node.netmask;
 | 
						|
    }
 | 
						|
 | 
						|
    uint32_t node_count = mmdb->metadata.node_count;
 | 
						|
    const uint8_t *search_tree = mmdb->file_content;
 | 
						|
    const uint8_t *record_pointer;
 | 
						|
    for (; current_bit < mmdb->depth && value < node_count; current_bit++) {
 | 
						|
        uint8_t bit =
 | 
						|
            1U & (address[current_bit >> 3] >> (7 - (current_bit % 8)));
 | 
						|
 | 
						|
        record_pointer = &search_tree[value * record_info.record_length];
 | 
						|
        if (record_pointer + record_info.record_length > mmdb->data_section) {
 | 
						|
            return MMDB_CORRUPT_SEARCH_TREE_ERROR;
 | 
						|
        }
 | 
						|
        if (bit) {
 | 
						|
            record_pointer += record_info.right_record_offset;
 | 
						|
            value = record_info.right_record_getter(record_pointer);
 | 
						|
        } else {
 | 
						|
            value = record_info.left_record_getter(record_pointer);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    result->netmask = current_bit;
 | 
						|
 | 
						|
    if (value >= node_count + mmdb->data_section_size) {
 | 
						|
        // The pointer points off the end of the database.
 | 
						|
        return MMDB_CORRUPT_SEARCH_TREE_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    if (value == node_count) {
 | 
						|
        // record is empty
 | 
						|
        result->found_entry = false;
 | 
						|
        return MMDB_SUCCESS;
 | 
						|
    }
 | 
						|
    result->found_entry = true;
 | 
						|
    result->entry.offset = data_section_offset_for_record(mmdb, value);
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static record_info_s record_info_for_database(const MMDB_s *const mmdb) {
 | 
						|
    record_info_s record_info = {.record_length = mmdb->full_record_byte_size,
 | 
						|
                                 .right_record_offset = 0};
 | 
						|
 | 
						|
    if (record_info.record_length == 6) {
 | 
						|
        record_info.left_record_getter = &get_uint24;
 | 
						|
        record_info.right_record_getter = &get_uint24;
 | 
						|
        record_info.right_record_offset = 3;
 | 
						|
    } else if (record_info.record_length == 7) {
 | 
						|
        record_info.left_record_getter = &get_left_28_bit_record;
 | 
						|
        record_info.right_record_getter = &get_right_28_bit_record;
 | 
						|
        record_info.right_record_offset = 3;
 | 
						|
    } else if (record_info.record_length == 8) {
 | 
						|
        record_info.left_record_getter = &get_uint32;
 | 
						|
        record_info.right_record_getter = &get_uint32;
 | 
						|
        record_info.right_record_offset = 4;
 | 
						|
    } else {
 | 
						|
        assert(false);
 | 
						|
    }
 | 
						|
 | 
						|
    return record_info;
 | 
						|
}
 | 
						|
 | 
						|
static int find_ipv4_start_node(MMDB_s *const mmdb) {
 | 
						|
    /* In a pathological case of a database with a single node search tree,
 | 
						|
     * this check will be true even after we've found the IPv4 start node, but
 | 
						|
     * that doesn't seem worth trying to fix. */
 | 
						|
    if (mmdb->ipv4_start_node.node_value != 0) {
 | 
						|
        return MMDB_SUCCESS;
 | 
						|
    }
 | 
						|
 | 
						|
    record_info_s record_info = record_info_for_database(mmdb);
 | 
						|
 | 
						|
    const uint8_t *search_tree = mmdb->file_content;
 | 
						|
    uint32_t node_value = 0;
 | 
						|
    const uint8_t *record_pointer;
 | 
						|
    uint16_t netmask;
 | 
						|
    uint32_t node_count = mmdb->metadata.node_count;
 | 
						|
 | 
						|
    for (netmask = 0; netmask < 96 && node_value < node_count; netmask++) {
 | 
						|
        record_pointer = &search_tree[node_value * record_info.record_length];
 | 
						|
        if (record_pointer + record_info.record_length > mmdb->data_section) {
 | 
						|
            return MMDB_CORRUPT_SEARCH_TREE_ERROR;
 | 
						|
        }
 | 
						|
        node_value = record_info.left_record_getter(record_pointer);
 | 
						|
    }
 | 
						|
 | 
						|
    mmdb->ipv4_start_node.node_value = node_value;
 | 
						|
    mmdb->ipv4_start_node.netmask = netmask;
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static uint8_t record_type(const MMDB_s *const mmdb, uint64_t record) {
 | 
						|
    uint32_t node_count = mmdb->metadata.node_count;
 | 
						|
 | 
						|
    /* Ideally we'd check to make sure that a record never points to a
 | 
						|
     * previously seen value, but that's more complicated. For now, we can
 | 
						|
     * at least check that we don't end up at the top of the tree again. */
 | 
						|
    if (record == 0) {
 | 
						|
        DEBUG_MSG("record has a value of 0");
 | 
						|
        return MMDB_RECORD_TYPE_INVALID;
 | 
						|
    }
 | 
						|
 | 
						|
    if (record < node_count) {
 | 
						|
        return MMDB_RECORD_TYPE_SEARCH_NODE;
 | 
						|
    }
 | 
						|
 | 
						|
    if (record == node_count) {
 | 
						|
        return MMDB_RECORD_TYPE_EMPTY;
 | 
						|
    }
 | 
						|
 | 
						|
    if (record - node_count < mmdb->data_section_size) {
 | 
						|
        return MMDB_RECORD_TYPE_DATA;
 | 
						|
    }
 | 
						|
 | 
						|
    DEBUG_MSG("record has a value that points outside of the database");
 | 
						|
    return MMDB_RECORD_TYPE_INVALID;
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t get_left_28_bit_record(const uint8_t *record) {
 | 
						|
    return record[0] * 65536 + record[1] * 256 + record[2] +
 | 
						|
           ((record[3] & 0xf0) << 20);
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t get_right_28_bit_record(const uint8_t *record) {
 | 
						|
    uint32_t value = get_uint32(record);
 | 
						|
    return value & 0xfffffff;
 | 
						|
}
 | 
						|
 | 
						|
int MMDB_read_node(const MMDB_s *const mmdb,
 | 
						|
                   uint32_t node_number,
 | 
						|
                   MMDB_search_node_s *const node) {
 | 
						|
    record_info_s record_info = record_info_for_database(mmdb);
 | 
						|
    if (0 == record_info.right_record_offset) {
 | 
						|
        return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    if (node_number > mmdb->metadata.node_count) {
 | 
						|
        return MMDB_INVALID_NODE_NUMBER_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    const uint8_t *search_tree = mmdb->file_content;
 | 
						|
    const uint8_t *record_pointer =
 | 
						|
        &search_tree[node_number * record_info.record_length];
 | 
						|
    node->left_record = record_info.left_record_getter(record_pointer);
 | 
						|
    record_pointer += record_info.right_record_offset;
 | 
						|
    node->right_record = record_info.right_record_getter(record_pointer);
 | 
						|
 | 
						|
    node->left_record_type = record_type(mmdb, node->left_record);
 | 
						|
    node->right_record_type = record_type(mmdb, node->right_record);
 | 
						|
 | 
						|
    // Note that offset will be invalid if the record type is not
 | 
						|
    // MMDB_RECORD_TYPE_DATA, but that's ok. Any use of the record entry
 | 
						|
    // for other data types is a programming error.
 | 
						|
    node->left_record_entry = (struct MMDB_entry_s){
 | 
						|
        .mmdb = mmdb,
 | 
						|
        .offset = data_section_offset_for_record(mmdb, node->left_record),
 | 
						|
    };
 | 
						|
    node->right_record_entry = (struct MMDB_entry_s){
 | 
						|
        .mmdb = mmdb,
 | 
						|
        .offset = data_section_offset_for_record(mmdb, node->right_record),
 | 
						|
    };
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t data_section_offset_for_record(const MMDB_s *const mmdb,
 | 
						|
                                               uint64_t record) {
 | 
						|
    return (uint32_t)record - mmdb->metadata.node_count -
 | 
						|
           MMDB_DATA_SECTION_SEPARATOR;
 | 
						|
}
 | 
						|
 | 
						|
int MMDB_get_value(MMDB_entry_s *const start,
 | 
						|
                   MMDB_entry_data_s *const entry_data,
 | 
						|
                   ...) {
 | 
						|
    va_list path;
 | 
						|
    va_start(path, entry_data);
 | 
						|
    int status = MMDB_vget_value(start, entry_data, path);
 | 
						|
    va_end(path);
 | 
						|
    return status;
 | 
						|
}
 | 
						|
 | 
						|
int MMDB_vget_value(MMDB_entry_s *const start,
 | 
						|
                    MMDB_entry_data_s *const entry_data,
 | 
						|
                    va_list va_path) {
 | 
						|
    int length = path_length(va_path);
 | 
						|
    const char *path_elem;
 | 
						|
    int i = 0;
 | 
						|
 | 
						|
    MAYBE_CHECK_SIZE_OVERFLOW(length,
 | 
						|
                              SIZE_MAX / sizeof(const char *) - 1,
 | 
						|
                              MMDB_INVALID_METADATA_ERROR);
 | 
						|
 | 
						|
    const char **path = calloc(length + 1, sizeof(const char *));
 | 
						|
    if (NULL == path) {
 | 
						|
        return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    while (NULL != (path_elem = va_arg(va_path, char *))) {
 | 
						|
        path[i] = path_elem;
 | 
						|
        i++;
 | 
						|
    }
 | 
						|
    path[i] = NULL;
 | 
						|
 | 
						|
    int status = MMDB_aget_value(start, entry_data, path);
 | 
						|
 | 
						|
    free((char **)path);
 | 
						|
 | 
						|
    return status;
 | 
						|
}
 | 
						|
 | 
						|
static int path_length(va_list va_path) {
 | 
						|
    int i = 0;
 | 
						|
    const char *ignore;
 | 
						|
    va_list path_copy;
 | 
						|
    va_copy(path_copy, va_path);
 | 
						|
 | 
						|
    while (NULL != (ignore = va_arg(path_copy, char *))) {
 | 
						|
        i++;
 | 
						|
    }
 | 
						|
 | 
						|
    va_end(path_copy);
 | 
						|
 | 
						|
    return i;
 | 
						|
}
 | 
						|
 | 
						|
int MMDB_aget_value(MMDB_entry_s *const start,
 | 
						|
                    MMDB_entry_data_s *const entry_data,
 | 
						|
                    const char *const *const path) {
 | 
						|
    const MMDB_s *const mmdb = start->mmdb;
 | 
						|
    uint32_t offset = start->offset;
 | 
						|
 | 
						|
    memset(entry_data, 0, sizeof(MMDB_entry_data_s));
 | 
						|
    DEBUG_NL;
 | 
						|
    DEBUG_MSG("looking up value by path");
 | 
						|
 | 
						|
    CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data);
 | 
						|
 | 
						|
    DEBUG_NL;
 | 
						|
    DEBUG_MSGF("top level element is a %s", type_num_to_name(entry_data->type));
 | 
						|
 | 
						|
    /* Can this happen? It'd probably represent a pathological case under
 | 
						|
     * normal use, but there's nothing preventing someone from passing an
 | 
						|
     * invalid MMDB_entry_s struct to this function */
 | 
						|
    if (!entry_data->has_data) {
 | 
						|
        return MMDB_INVALID_LOOKUP_PATH_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    const char *path_elem;
 | 
						|
    int i = 0;
 | 
						|
    while (NULL != (path_elem = path[i++])) {
 | 
						|
        DEBUG_NL;
 | 
						|
        DEBUG_MSGF("path elem = %s", path_elem);
 | 
						|
 | 
						|
        /* XXX - it'd be good to find a quicker way to skip through these
 | 
						|
           entries that doesn't involve decoding them
 | 
						|
           completely. Basically we need to just use the size from the
 | 
						|
           control byte to advance our pointer rather than calling
 | 
						|
           decode_one(). */
 | 
						|
        if (entry_data->type == MMDB_DATA_TYPE_ARRAY) {
 | 
						|
            int status = lookup_path_in_array(path_elem, mmdb, entry_data);
 | 
						|
            if (MMDB_SUCCESS != status) {
 | 
						|
                memset(entry_data, 0, sizeof(MMDB_entry_data_s));
 | 
						|
                return status;
 | 
						|
            }
 | 
						|
        } else if (entry_data->type == MMDB_DATA_TYPE_MAP) {
 | 
						|
            int status = lookup_path_in_map(path_elem, mmdb, entry_data);
 | 
						|
            if (MMDB_SUCCESS != status) {
 | 
						|
                memset(entry_data, 0, sizeof(MMDB_entry_data_s));
 | 
						|
                return status;
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            /* Once we make the code traverse maps & arrays without calling
 | 
						|
             * decode_one() we can get rid of this. */
 | 
						|
            memset(entry_data, 0, sizeof(MMDB_entry_data_s));
 | 
						|
            return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int lookup_path_in_array(const char *path_elem,
 | 
						|
                                const MMDB_s *const mmdb,
 | 
						|
                                MMDB_entry_data_s *entry_data) {
 | 
						|
    uint32_t size = entry_data->data_size;
 | 
						|
    char *first_invalid;
 | 
						|
 | 
						|
    int saved_errno = errno;
 | 
						|
    errno = 0;
 | 
						|
    int array_index = strtol(path_elem, &first_invalid, 10);
 | 
						|
    if (ERANGE == errno) {
 | 
						|
        errno = saved_errno;
 | 
						|
        return MMDB_INVALID_LOOKUP_PATH_ERROR;
 | 
						|
    }
 | 
						|
    errno = saved_errno;
 | 
						|
 | 
						|
    if (array_index < 0) {
 | 
						|
        array_index += size;
 | 
						|
 | 
						|
        if (array_index < 0) {
 | 
						|
            return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (*first_invalid || (uint32_t)array_index >= size) {
 | 
						|
        return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    for (int i = 0; i < array_index; i++) {
 | 
						|
        /* We don't want to follow a pointer here. If the next element is a
 | 
						|
         * pointer we simply skip it and keep going */
 | 
						|
        CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data);
 | 
						|
        int status = skip_map_or_array(mmdb, entry_data);
 | 
						|
        if (MMDB_SUCCESS != status) {
 | 
						|
            return status;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    MMDB_entry_data_s value;
 | 
						|
    CHECKED_DECODE_ONE_FOLLOW(mmdb, entry_data->offset_to_next, &value);
 | 
						|
    memcpy(entry_data, &value, sizeof(MMDB_entry_data_s));
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int lookup_path_in_map(const char *path_elem,
 | 
						|
                              const MMDB_s *const mmdb,
 | 
						|
                              MMDB_entry_data_s *entry_data) {
 | 
						|
    uint32_t size = entry_data->data_size;
 | 
						|
    uint32_t offset = entry_data->offset_to_next;
 | 
						|
    size_t path_elem_len = strlen(path_elem);
 | 
						|
 | 
						|
    while (size-- > 0) {
 | 
						|
        MMDB_entry_data_s key, value;
 | 
						|
        CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, &key);
 | 
						|
 | 
						|
        uint32_t offset_to_value = key.offset_to_next;
 | 
						|
 | 
						|
        if (MMDB_DATA_TYPE_UTF8_STRING != key.type) {
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
 | 
						|
        if (key.data_size == path_elem_len &&
 | 
						|
            !memcmp(path_elem, key.utf8_string, path_elem_len)) {
 | 
						|
 | 
						|
            DEBUG_MSG("found key matching path elem");
 | 
						|
 | 
						|
            CHECKED_DECODE_ONE_FOLLOW(mmdb, offset_to_value, &value);
 | 
						|
            memcpy(entry_data, &value, sizeof(MMDB_entry_data_s));
 | 
						|
            return MMDB_SUCCESS;
 | 
						|
        } else {
 | 
						|
            /* We don't want to follow a pointer here. If the next element is
 | 
						|
             * a pointer we simply skip it and keep going */
 | 
						|
            CHECKED_DECODE_ONE(mmdb, offset_to_value, &value);
 | 
						|
            int status = skip_map_or_array(mmdb, &value);
 | 
						|
            if (MMDB_SUCCESS != status) {
 | 
						|
                return status;
 | 
						|
            }
 | 
						|
            offset = value.offset_to_next;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    memset(entry_data, 0, sizeof(MMDB_entry_data_s));
 | 
						|
    return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
 | 
						|
}
 | 
						|
 | 
						|
static int skip_map_or_array(const MMDB_s *const mmdb,
 | 
						|
                             MMDB_entry_data_s *entry_data) {
 | 
						|
    if (entry_data->type == MMDB_DATA_TYPE_MAP) {
 | 
						|
        uint32_t size = entry_data->data_size;
 | 
						|
        while (size-- > 0) {
 | 
						|
            CHECKED_DECODE_ONE(
 | 
						|
                mmdb, entry_data->offset_to_next, entry_data); // key
 | 
						|
            CHECKED_DECODE_ONE(
 | 
						|
                mmdb, entry_data->offset_to_next, entry_data); // value
 | 
						|
            int status = skip_map_or_array(mmdb, entry_data);
 | 
						|
            if (MMDB_SUCCESS != status) {
 | 
						|
                return status;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    } else if (entry_data->type == MMDB_DATA_TYPE_ARRAY) {
 | 
						|
        uint32_t size = entry_data->data_size;
 | 
						|
        while (size-- > 0) {
 | 
						|
            CHECKED_DECODE_ONE(
 | 
						|
                mmdb, entry_data->offset_to_next, entry_data); // value
 | 
						|
            int status = skip_map_or_array(mmdb, entry_data);
 | 
						|
            if (MMDB_SUCCESS != status) {
 | 
						|
                return status;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int decode_one_follow(const MMDB_s *const mmdb,
 | 
						|
                             uint32_t offset,
 | 
						|
                             MMDB_entry_data_s *entry_data) {
 | 
						|
    CHECKED_DECODE_ONE(mmdb, offset, entry_data);
 | 
						|
    if (entry_data->type == MMDB_DATA_TYPE_POINTER) {
 | 
						|
        uint32_t next = entry_data->offset_to_next;
 | 
						|
        CHECKED_DECODE_ONE(mmdb, entry_data->pointer, entry_data);
 | 
						|
        /* Pointers to pointers are illegal under the spec */
 | 
						|
        if (entry_data->type == MMDB_DATA_TYPE_POINTER) {
 | 
						|
            DEBUG_MSG("pointer points to another pointer");
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
 | 
						|
        /* The pointer could point to any part of the data section but the
 | 
						|
         * next entry for this particular offset may be the one after the
 | 
						|
         * pointer, not the one after whatever the pointer points to. This
 | 
						|
         * depends on whether the pointer points to something that is a simple
 | 
						|
         * value or a compound value. For a compound value, the next one is
 | 
						|
         * the one after the pointer result, not the one after the pointer. */
 | 
						|
        if (entry_data->type != MMDB_DATA_TYPE_MAP &&
 | 
						|
            entry_data->type != MMDB_DATA_TYPE_ARRAY) {
 | 
						|
 | 
						|
            entry_data->offset_to_next = next;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
#if !MMDB_UINT128_IS_BYTE_ARRAY
 | 
						|
static mmdb_uint128_t get_uint128(const uint8_t *p, int length) {
 | 
						|
    mmdb_uint128_t value = 0;
 | 
						|
    while (length-- > 0) {
 | 
						|
        value <<= 8;
 | 
						|
        value += *p++;
 | 
						|
    }
 | 
						|
    return value;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
static int decode_one(const MMDB_s *const mmdb,
 | 
						|
                      uint32_t offset,
 | 
						|
                      MMDB_entry_data_s *entry_data) {
 | 
						|
    const uint8_t *mem = mmdb->data_section;
 | 
						|
 | 
						|
    // We subtract rather than add as it possible that offset + 1
 | 
						|
    // could overflow for a corrupt database while an underflow
 | 
						|
    // from data_section_size - 1 should not be possible.
 | 
						|
    if (offset > mmdb->data_section_size - 1) {
 | 
						|
        DEBUG_MSGF("Offset (%d) past data section (%d)",
 | 
						|
                   offset,
 | 
						|
                   mmdb->data_section_size);
 | 
						|
        return MMDB_INVALID_DATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    entry_data->offset = offset;
 | 
						|
    entry_data->has_data = true;
 | 
						|
 | 
						|
    DEBUG_NL;
 | 
						|
    DEBUG_MSGF("Offset: %i", offset);
 | 
						|
 | 
						|
    uint8_t ctrl = mem[offset++];
 | 
						|
    DEBUG_BINARY("Control byte: %s", ctrl);
 | 
						|
 | 
						|
    int type = (ctrl >> 5) & 7;
 | 
						|
    DEBUG_MSGF("Type: %i (%s)", type, type_num_to_name(type));
 | 
						|
 | 
						|
    if (type == MMDB_DATA_TYPE_EXTENDED) {
 | 
						|
        // Subtracting 1 to avoid possible overflow on offset + 1
 | 
						|
        if (offset > mmdb->data_section_size - 1) {
 | 
						|
            DEBUG_MSGF("Extended type offset (%d) past data section (%d)",
 | 
						|
                       offset,
 | 
						|
                       mmdb->data_section_size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
        type = get_ext_type(mem[offset++]);
 | 
						|
        DEBUG_MSGF("Extended type: %i (%s)", type, type_num_to_name(type));
 | 
						|
    }
 | 
						|
 | 
						|
    entry_data->type = type;
 | 
						|
 | 
						|
    if (type == MMDB_DATA_TYPE_POINTER) {
 | 
						|
        uint8_t psize = ((ctrl >> 3) & 3) + 1;
 | 
						|
        DEBUG_MSGF("Pointer size: %i", psize);
 | 
						|
 | 
						|
        // We check that the offset does not extend past the end of the
 | 
						|
        // database and that the subtraction of psize did not underflow.
 | 
						|
        if (offset > mmdb->data_section_size - psize ||
 | 
						|
            mmdb->data_section_size < psize) {
 | 
						|
            DEBUG_MSGF("Pointer offset (%d) past data section (%d)",
 | 
						|
                       offset + psize,
 | 
						|
                       mmdb->data_section_size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
        entry_data->pointer = get_ptr_from(ctrl, &mem[offset], psize);
 | 
						|
        DEBUG_MSGF("Pointer to: %i", entry_data->pointer);
 | 
						|
 | 
						|
        entry_data->data_size = psize;
 | 
						|
        entry_data->offset_to_next = offset + psize;
 | 
						|
        return MMDB_SUCCESS;
 | 
						|
    }
 | 
						|
 | 
						|
    uint32_t size = ctrl & 31;
 | 
						|
    switch (size) {
 | 
						|
        case 29:
 | 
						|
            // We subtract when checking offset to avoid possible overflow
 | 
						|
            if (offset > mmdb->data_section_size - 1) {
 | 
						|
                DEBUG_MSGF("String end (%d, case 29) past data section (%d)",
 | 
						|
                           offset,
 | 
						|
                           mmdb->data_section_size);
 | 
						|
                return MMDB_INVALID_DATA_ERROR;
 | 
						|
            }
 | 
						|
            size = 29 + mem[offset++];
 | 
						|
            break;
 | 
						|
        case 30:
 | 
						|
            // We subtract when checking offset to avoid possible overflow
 | 
						|
            if (offset > mmdb->data_section_size - 2) {
 | 
						|
                DEBUG_MSGF("String end (%d, case 30) past data section (%d)",
 | 
						|
                           offset,
 | 
						|
                           mmdb->data_section_size);
 | 
						|
                return MMDB_INVALID_DATA_ERROR;
 | 
						|
            }
 | 
						|
            size = 285 + get_uint16(&mem[offset]);
 | 
						|
            offset += 2;
 | 
						|
            break;
 | 
						|
        case 31:
 | 
						|
            // We subtract when checking offset to avoid possible overflow
 | 
						|
            if (offset > mmdb->data_section_size - 3) {
 | 
						|
                DEBUG_MSGF("String end (%d, case 31) past data section (%d)",
 | 
						|
                           offset,
 | 
						|
                           mmdb->data_section_size);
 | 
						|
                return MMDB_INVALID_DATA_ERROR;
 | 
						|
            }
 | 
						|
            size = 65821 + get_uint24(&mem[offset]);
 | 
						|
            offset += 3;
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    DEBUG_MSGF("Size: %i", size);
 | 
						|
 | 
						|
    if (type == MMDB_DATA_TYPE_MAP || type == MMDB_DATA_TYPE_ARRAY) {
 | 
						|
        entry_data->data_size = size;
 | 
						|
        entry_data->offset_to_next = offset;
 | 
						|
        return MMDB_SUCCESS;
 | 
						|
    }
 | 
						|
 | 
						|
    if (type == MMDB_DATA_TYPE_BOOLEAN) {
 | 
						|
        entry_data->boolean = size ? true : false;
 | 
						|
        entry_data->data_size = 0;
 | 
						|
        entry_data->offset_to_next = offset;
 | 
						|
        DEBUG_MSGF("boolean value: %s", entry_data->boolean ? "true" : "false");
 | 
						|
        return MMDB_SUCCESS;
 | 
						|
    }
 | 
						|
 | 
						|
    // Check that the data doesn't extend past the end of the memory
 | 
						|
    // buffer and that the calculation in doing this did not underflow.
 | 
						|
    if (offset > mmdb->data_section_size - size ||
 | 
						|
        mmdb->data_section_size < size) {
 | 
						|
        DEBUG_MSGF("Data end (%d) past data section (%d)",
 | 
						|
                   offset + size,
 | 
						|
                   mmdb->data_section_size);
 | 
						|
        return MMDB_INVALID_DATA_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    if (type == MMDB_DATA_TYPE_UINT16) {
 | 
						|
        if (size > 2) {
 | 
						|
            DEBUG_MSGF("uint16 of size %d", size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
        entry_data->uint16 = (uint16_t)get_uintX(&mem[offset], size);
 | 
						|
        DEBUG_MSGF("uint16 value: %u", entry_data->uint16);
 | 
						|
    } else if (type == MMDB_DATA_TYPE_UINT32) {
 | 
						|
        if (size > 4) {
 | 
						|
            DEBUG_MSGF("uint32 of size %d", size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
        entry_data->uint32 = (uint32_t)get_uintX(&mem[offset], size);
 | 
						|
        DEBUG_MSGF("uint32 value: %u", entry_data->uint32);
 | 
						|
    } else if (type == MMDB_DATA_TYPE_INT32) {
 | 
						|
        if (size > 4) {
 | 
						|
            DEBUG_MSGF("int32 of size %d", size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
        entry_data->int32 = get_sintX(&mem[offset], size);
 | 
						|
        DEBUG_MSGF("int32 value: %i", entry_data->int32);
 | 
						|
    } else if (type == MMDB_DATA_TYPE_UINT64) {
 | 
						|
        if (size > 8) {
 | 
						|
            DEBUG_MSGF("uint64 of size %d", size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
        entry_data->uint64 = get_uintX(&mem[offset], size);
 | 
						|
        DEBUG_MSGF("uint64 value: %" PRIu64, entry_data->uint64);
 | 
						|
    } else if (type == MMDB_DATA_TYPE_UINT128) {
 | 
						|
        if (size > 16) {
 | 
						|
            DEBUG_MSGF("uint128 of size %d", size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
#if MMDB_UINT128_IS_BYTE_ARRAY
 | 
						|
        memset(entry_data->uint128, 0, 16);
 | 
						|
        if (size > 0) {
 | 
						|
            memcpy(entry_data->uint128 + 16 - size, &mem[offset], size);
 | 
						|
        }
 | 
						|
#else
 | 
						|
        entry_data->uint128 = get_uint128(&mem[offset], size);
 | 
						|
#endif
 | 
						|
    } else if (type == MMDB_DATA_TYPE_FLOAT) {
 | 
						|
        if (size != 4) {
 | 
						|
            DEBUG_MSGF("float of size %d", size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
        size = 4;
 | 
						|
        entry_data->float_value = get_ieee754_float(&mem[offset]);
 | 
						|
        DEBUG_MSGF("float value: %f", entry_data->float_value);
 | 
						|
    } else if (type == MMDB_DATA_TYPE_DOUBLE) {
 | 
						|
        if (size != 8) {
 | 
						|
            DEBUG_MSGF("double of size %d", size);
 | 
						|
            return MMDB_INVALID_DATA_ERROR;
 | 
						|
        }
 | 
						|
        size = 8;
 | 
						|
        entry_data->double_value = get_ieee754_double(&mem[offset]);
 | 
						|
        DEBUG_MSGF("double value: %f", entry_data->double_value);
 | 
						|
    } else if (type == MMDB_DATA_TYPE_UTF8_STRING) {
 | 
						|
        entry_data->utf8_string = size == 0 ? "" : (char *)&mem[offset];
 | 
						|
        entry_data->data_size = size;
 | 
						|
#ifdef MMDB_DEBUG
 | 
						|
        char *string =
 | 
						|
            mmdb_strndup(entry_data->utf8_string, size > 50 ? 50 : size);
 | 
						|
        if (NULL == string) {
 | 
						|
            abort();
 | 
						|
        }
 | 
						|
        DEBUG_MSGF("string value: %s", string);
 | 
						|
        free(string);
 | 
						|
#endif
 | 
						|
    } else if (type == MMDB_DATA_TYPE_BYTES) {
 | 
						|
        entry_data->bytes = &mem[offset];
 | 
						|
        entry_data->data_size = size;
 | 
						|
    }
 | 
						|
 | 
						|
    entry_data->offset_to_next = offset + size;
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int get_ext_type(int raw_ext_type) { return 7 + raw_ext_type; }
 | 
						|
 | 
						|
static uint32_t
 | 
						|
get_ptr_from(uint8_t ctrl, uint8_t const *const ptr, int ptr_size) {
 | 
						|
    uint32_t new_offset;
 | 
						|
    switch (ptr_size) {
 | 
						|
        case 1:
 | 
						|
            new_offset = ((ctrl & 7) << 8) + ptr[0];
 | 
						|
            break;
 | 
						|
        case 2:
 | 
						|
            new_offset = 2048 + ((ctrl & 7) << 16) + (ptr[0] << 8) + ptr[1];
 | 
						|
            break;
 | 
						|
        case 3:
 | 
						|
            new_offset = 2048 + 524288 + ((ctrl & 7) << 24) + get_uint24(ptr);
 | 
						|
            break;
 | 
						|
        case 4:
 | 
						|
        default:
 | 
						|
            new_offset = get_uint32(ptr);
 | 
						|
            break;
 | 
						|
    }
 | 
						|
    return new_offset;
 | 
						|
}
 | 
						|
 | 
						|
int MMDB_get_metadata_as_entry_data_list(
 | 
						|
    const MMDB_s *const mmdb, MMDB_entry_data_list_s **const entry_data_list) {
 | 
						|
    MMDB_s metadata_db = make_fake_metadata_db(mmdb);
 | 
						|
 | 
						|
    MMDB_entry_s metadata_start = {.mmdb = &metadata_db, .offset = 0};
 | 
						|
 | 
						|
    return MMDB_get_entry_data_list(&metadata_start, entry_data_list);
 | 
						|
}
 | 
						|
 | 
						|
int MMDB_get_entry_data_list(MMDB_entry_s *start,
 | 
						|
                             MMDB_entry_data_list_s **const entry_data_list) {
 | 
						|
    MMDB_data_pool_s *const pool = data_pool_new(MMDB_POOL_INIT_SIZE);
 | 
						|
    if (!pool) {
 | 
						|
        return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    MMDB_entry_data_list_s *const list = data_pool_alloc(pool);
 | 
						|
    if (!list) {
 | 
						|
        data_pool_destroy(pool);
 | 
						|
        return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    int const status =
 | 
						|
        get_entry_data_list(start->mmdb, start->offset, list, pool, 0);
 | 
						|
 | 
						|
    *entry_data_list = data_pool_to_list(pool);
 | 
						|
    if (!*entry_data_list) {
 | 
						|
        data_pool_destroy(pool);
 | 
						|
        return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
    }
 | 
						|
 | 
						|
    return status;
 | 
						|
}
 | 
						|
 | 
						|
static int get_entry_data_list(const MMDB_s *const mmdb,
 | 
						|
                               uint32_t offset,
 | 
						|
                               MMDB_entry_data_list_s *const entry_data_list,
 | 
						|
                               MMDB_data_pool_s *const pool,
 | 
						|
                               int depth) {
 | 
						|
    if (depth >= MAXIMUM_DATA_STRUCTURE_DEPTH) {
 | 
						|
        DEBUG_MSG("reached the maximum data structure depth");
 | 
						|
        return MMDB_INVALID_DATA_ERROR;
 | 
						|
    }
 | 
						|
    depth++;
 | 
						|
    CHECKED_DECODE_ONE(mmdb, offset, &entry_data_list->entry_data);
 | 
						|
 | 
						|
    switch (entry_data_list->entry_data.type) {
 | 
						|
        case MMDB_DATA_TYPE_POINTER: {
 | 
						|
            uint32_t next_offset = entry_data_list->entry_data.offset_to_next;
 | 
						|
            uint32_t last_offset;
 | 
						|
            CHECKED_DECODE_ONE(mmdb,
 | 
						|
                               last_offset =
 | 
						|
                                   entry_data_list->entry_data.pointer,
 | 
						|
                               &entry_data_list->entry_data);
 | 
						|
 | 
						|
            /* Pointers to pointers are illegal under the spec */
 | 
						|
            if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_POINTER) {
 | 
						|
                DEBUG_MSG("pointer points to another pointer");
 | 
						|
                return MMDB_INVALID_DATA_ERROR;
 | 
						|
            }
 | 
						|
 | 
						|
            if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_ARRAY ||
 | 
						|
                entry_data_list->entry_data.type == MMDB_DATA_TYPE_MAP) {
 | 
						|
 | 
						|
                int status = get_entry_data_list(
 | 
						|
                    mmdb, last_offset, entry_data_list, pool, depth);
 | 
						|
                if (MMDB_SUCCESS != status) {
 | 
						|
                    DEBUG_MSG("get_entry_data_list on pointer failed.");
 | 
						|
                    return status;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            entry_data_list->entry_data.offset_to_next = next_offset;
 | 
						|
        } break;
 | 
						|
        case MMDB_DATA_TYPE_ARRAY: {
 | 
						|
            uint32_t array_size = entry_data_list->entry_data.data_size;
 | 
						|
            uint32_t array_offset = entry_data_list->entry_data.offset_to_next;
 | 
						|
            while (array_size-- > 0) {
 | 
						|
                MMDB_entry_data_list_s *entry_data_list_to =
 | 
						|
                    data_pool_alloc(pool);
 | 
						|
                if (!entry_data_list_to) {
 | 
						|
                    return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
                }
 | 
						|
 | 
						|
                int status = get_entry_data_list(
 | 
						|
                    mmdb, array_offset, entry_data_list_to, pool, depth);
 | 
						|
                if (MMDB_SUCCESS != status) {
 | 
						|
                    DEBUG_MSG("get_entry_data_list on array element failed.");
 | 
						|
                    return status;
 | 
						|
                }
 | 
						|
 | 
						|
                array_offset = entry_data_list_to->entry_data.offset_to_next;
 | 
						|
            }
 | 
						|
            entry_data_list->entry_data.offset_to_next = array_offset;
 | 
						|
 | 
						|
        } break;
 | 
						|
        case MMDB_DATA_TYPE_MAP: {
 | 
						|
            uint32_t size = entry_data_list->entry_data.data_size;
 | 
						|
 | 
						|
            offset = entry_data_list->entry_data.offset_to_next;
 | 
						|
            while (size-- > 0) {
 | 
						|
                MMDB_entry_data_list_s *list_key = data_pool_alloc(pool);
 | 
						|
                if (!list_key) {
 | 
						|
                    return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
                }
 | 
						|
 | 
						|
                int status =
 | 
						|
                    get_entry_data_list(mmdb, offset, list_key, pool, depth);
 | 
						|
                if (MMDB_SUCCESS != status) {
 | 
						|
                    DEBUG_MSG("get_entry_data_list on map key failed.");
 | 
						|
                    return status;
 | 
						|
                }
 | 
						|
 | 
						|
                offset = list_key->entry_data.offset_to_next;
 | 
						|
 | 
						|
                MMDB_entry_data_list_s *list_value = data_pool_alloc(pool);
 | 
						|
                if (!list_value) {
 | 
						|
                    return MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
                }
 | 
						|
 | 
						|
                status =
 | 
						|
                    get_entry_data_list(mmdb, offset, list_value, pool, depth);
 | 
						|
                if (MMDB_SUCCESS != status) {
 | 
						|
                    DEBUG_MSG("get_entry_data_list on map element failed.");
 | 
						|
                    return status;
 | 
						|
                }
 | 
						|
                offset = list_value->entry_data.offset_to_next;
 | 
						|
            }
 | 
						|
            entry_data_list->entry_data.offset_to_next = offset;
 | 
						|
        } break;
 | 
						|
        default:
 | 
						|
            break;
 | 
						|
    }
 | 
						|
 | 
						|
    return MMDB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static float get_ieee754_float(const uint8_t *restrict p) {
 | 
						|
    volatile float f;
 | 
						|
    uint8_t *q = (void *)&f;
 | 
						|
/* Windows builds don't use autoconf but we can assume they're all
 | 
						|
 * little-endian. */
 | 
						|
#if MMDB_LITTLE_ENDIAN || _WIN32
 | 
						|
    q[3] = p[0];
 | 
						|
    q[2] = p[1];
 | 
						|
    q[1] = p[2];
 | 
						|
    q[0] = p[3];
 | 
						|
#else
 | 
						|
    memcpy(q, p, 4);
 | 
						|
#endif
 | 
						|
    return f;
 | 
						|
}
 | 
						|
 | 
						|
static double get_ieee754_double(const uint8_t *restrict p) {
 | 
						|
    volatile double d;
 | 
						|
    uint8_t *q = (void *)&d;
 | 
						|
#if MMDB_LITTLE_ENDIAN || _WIN32
 | 
						|
    q[7] = p[0];
 | 
						|
    q[6] = p[1];
 | 
						|
    q[5] = p[2];
 | 
						|
    q[4] = p[3];
 | 
						|
    q[3] = p[4];
 | 
						|
    q[2] = p[5];
 | 
						|
    q[1] = p[6];
 | 
						|
    q[0] = p[7];
 | 
						|
#else
 | 
						|
    memcpy(q, p, 8);
 | 
						|
#endif
 | 
						|
 | 
						|
    return d;
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t get_uint32(const uint8_t *p) {
 | 
						|
    return p[0] * 16777216U + p[1] * 65536 + p[2] * 256 + p[3];
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t get_uint24(const uint8_t *p) {
 | 
						|
    return p[0] * 65536U + p[1] * 256 + p[2];
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t get_uint16(const uint8_t *p) { return p[0] * 256U + p[1]; }
 | 
						|
 | 
						|
static uint64_t get_uintX(const uint8_t *p, int length) {
 | 
						|
    uint64_t value = 0;
 | 
						|
    while (length-- > 0) {
 | 
						|
        value <<= 8;
 | 
						|
        value += *p++;
 | 
						|
    }
 | 
						|
    return value;
 | 
						|
}
 | 
						|
 | 
						|
static int32_t get_sintX(const uint8_t *p, int length) {
 | 
						|
    return (int32_t)get_uintX(p, length);
 | 
						|
}
 | 
						|
 | 
						|
void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list) {
 | 
						|
    if (entry_data_list == NULL) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    data_pool_destroy(entry_data_list->pool);
 | 
						|
}
 | 
						|
 | 
						|
void MMDB_close(MMDB_s *const mmdb) { free_mmdb_struct(mmdb); }
 | 
						|
 | 
						|
static void free_mmdb_struct(MMDB_s *const mmdb) {
 | 
						|
    if (!mmdb) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (NULL != mmdb->filename) {
 | 
						|
        FREE_AND_SET_NULL(mmdb->filename);
 | 
						|
    }
 | 
						|
    if (NULL != mmdb->file_content) {
 | 
						|
#ifdef _WIN32
 | 
						|
        UnmapViewOfFile(mmdb->file_content);
 | 
						|
        /* Winsock is only initialized if open was successful so we only have
 | 
						|
         * to cleanup then. */
 | 
						|
        WSACleanup();
 | 
						|
#else
 | 
						|
        munmap((void *)mmdb->file_content, mmdb->file_size);
 | 
						|
#endif
 | 
						|
    }
 | 
						|
 | 
						|
    if (NULL != mmdb->metadata.database_type) {
 | 
						|
        FREE_AND_SET_NULL(mmdb->metadata.database_type);
 | 
						|
    }
 | 
						|
 | 
						|
    free_languages_metadata(mmdb);
 | 
						|
    free_descriptions_metadata(mmdb);
 | 
						|
}
 | 
						|
 | 
						|
static void free_languages_metadata(MMDB_s *mmdb) {
 | 
						|
    if (!mmdb->metadata.languages.names) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    for (size_t i = 0; i < mmdb->metadata.languages.count; i++) {
 | 
						|
        FREE_AND_SET_NULL(mmdb->metadata.languages.names[i]);
 | 
						|
    }
 | 
						|
    FREE_AND_SET_NULL(mmdb->metadata.languages.names);
 | 
						|
}
 | 
						|
 | 
						|
static void free_descriptions_metadata(MMDB_s *mmdb) {
 | 
						|
    if (!mmdb->metadata.description.count) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    for (size_t i = 0; i < mmdb->metadata.description.count; i++) {
 | 
						|
        if (NULL != mmdb->metadata.description.descriptions[i]) {
 | 
						|
            if (NULL != mmdb->metadata.description.descriptions[i]->language) {
 | 
						|
                FREE_AND_SET_NULL(
 | 
						|
                    mmdb->metadata.description.descriptions[i]->language);
 | 
						|
            }
 | 
						|
 | 
						|
            if (NULL !=
 | 
						|
                mmdb->metadata.description.descriptions[i]->description) {
 | 
						|
                FREE_AND_SET_NULL(
 | 
						|
                    mmdb->metadata.description.descriptions[i]->description);
 | 
						|
            }
 | 
						|
            FREE_AND_SET_NULL(mmdb->metadata.description.descriptions[i]);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    FREE_AND_SET_NULL(mmdb->metadata.description.descriptions);
 | 
						|
}
 | 
						|
 | 
						|
const char *MMDB_lib_version(void) { return PACKAGE_VERSION; }
 | 
						|
 | 
						|
int MMDB_dump_entry_data_list(FILE *const stream,
 | 
						|
                              MMDB_entry_data_list_s *const entry_data_list,
 | 
						|
                              int indent) {
 | 
						|
    int status;
 | 
						|
    dump_entry_data_list(stream, entry_data_list, indent, &status);
 | 
						|
    return status;
 | 
						|
}
 | 
						|
 | 
						|
static MMDB_entry_data_list_s *
 | 
						|
dump_entry_data_list(FILE *stream,
 | 
						|
                     MMDB_entry_data_list_s *entry_data_list,
 | 
						|
                     int indent,
 | 
						|
                     int *status) {
 | 
						|
    switch (entry_data_list->entry_data.type) {
 | 
						|
        case MMDB_DATA_TYPE_MAP: {
 | 
						|
            uint32_t size = entry_data_list->entry_data.data_size;
 | 
						|
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream, "{\n");
 | 
						|
            indent += 2;
 | 
						|
 | 
						|
            for (entry_data_list = entry_data_list->next;
 | 
						|
                 size && entry_data_list;
 | 
						|
                 size--) {
 | 
						|
 | 
						|
                if (MMDB_DATA_TYPE_UTF8_STRING !=
 | 
						|
                    entry_data_list->entry_data.type) {
 | 
						|
                    *status = MMDB_INVALID_DATA_ERROR;
 | 
						|
                    return NULL;
 | 
						|
                }
 | 
						|
                char *key = mmdb_strndup(
 | 
						|
                    (char *)entry_data_list->entry_data.utf8_string,
 | 
						|
                    entry_data_list->entry_data.data_size);
 | 
						|
                if (NULL == key) {
 | 
						|
                    *status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
                    return NULL;
 | 
						|
                }
 | 
						|
 | 
						|
                print_indentation(stream, indent);
 | 
						|
                fprintf(stream, "\"%s\": \n", key);
 | 
						|
                free(key);
 | 
						|
 | 
						|
                entry_data_list = entry_data_list->next;
 | 
						|
                entry_data_list = dump_entry_data_list(
 | 
						|
                    stream, entry_data_list, indent + 2, status);
 | 
						|
 | 
						|
                if (MMDB_SUCCESS != *status) {
 | 
						|
                    return NULL;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            indent -= 2;
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream, "}\n");
 | 
						|
        } break;
 | 
						|
        case MMDB_DATA_TYPE_ARRAY: {
 | 
						|
            uint32_t size = entry_data_list->entry_data.data_size;
 | 
						|
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream, "[\n");
 | 
						|
            indent += 2;
 | 
						|
 | 
						|
            for (entry_data_list = entry_data_list->next;
 | 
						|
                 size && entry_data_list;
 | 
						|
                 size--) {
 | 
						|
                entry_data_list = dump_entry_data_list(
 | 
						|
                    stream, entry_data_list, indent, status);
 | 
						|
                if (MMDB_SUCCESS != *status) {
 | 
						|
                    return NULL;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            indent -= 2;
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream, "]\n");
 | 
						|
        } break;
 | 
						|
        case MMDB_DATA_TYPE_UTF8_STRING: {
 | 
						|
            char *string =
 | 
						|
                mmdb_strndup((char *)entry_data_list->entry_data.utf8_string,
 | 
						|
                             entry_data_list->entry_data.data_size);
 | 
						|
            if (NULL == string) {
 | 
						|
                *status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
                return NULL;
 | 
						|
            }
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream, "\"%s\" <utf8_string>\n", string);
 | 
						|
            free(string);
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
        } break;
 | 
						|
        case MMDB_DATA_TYPE_BYTES: {
 | 
						|
            char *hex_string =
 | 
						|
                bytes_to_hex((uint8_t *)entry_data_list->entry_data.bytes,
 | 
						|
                             entry_data_list->entry_data.data_size);
 | 
						|
 | 
						|
            if (NULL == hex_string) {
 | 
						|
                *status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
                return NULL;
 | 
						|
            }
 | 
						|
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream, "%s <bytes>\n", hex_string);
 | 
						|
            free(hex_string);
 | 
						|
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
        } break;
 | 
						|
        case MMDB_DATA_TYPE_DOUBLE:
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream,
 | 
						|
                    "%f <double>\n",
 | 
						|
                    entry_data_list->entry_data.double_value);
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
            break;
 | 
						|
        case MMDB_DATA_TYPE_FLOAT:
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream,
 | 
						|
                    "%f <float>\n",
 | 
						|
                    entry_data_list->entry_data.float_value);
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
            break;
 | 
						|
        case MMDB_DATA_TYPE_UINT16:
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(
 | 
						|
                stream, "%u <uint16>\n", entry_data_list->entry_data.uint16);
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
            break;
 | 
						|
        case MMDB_DATA_TYPE_UINT32:
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(
 | 
						|
                stream, "%u <uint32>\n", entry_data_list->entry_data.uint32);
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
            break;
 | 
						|
        case MMDB_DATA_TYPE_BOOLEAN:
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream,
 | 
						|
                    "%s <boolean>\n",
 | 
						|
                    entry_data_list->entry_data.boolean ? "true" : "false");
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
            break;
 | 
						|
        case MMDB_DATA_TYPE_UINT64:
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream,
 | 
						|
                    "%" PRIu64 " <uint64>\n",
 | 
						|
                    entry_data_list->entry_data.uint64);
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
            break;
 | 
						|
        case MMDB_DATA_TYPE_UINT128:
 | 
						|
            print_indentation(stream, indent);
 | 
						|
#if MMDB_UINT128_IS_BYTE_ARRAY
 | 
						|
            char *hex_string = bytes_to_hex(
 | 
						|
                (uint8_t *)entry_data_list->entry_data.uint128, 16);
 | 
						|
            if (NULL == hex_string) {
 | 
						|
                *status = MMDB_OUT_OF_MEMORY_ERROR;
 | 
						|
                return NULL;
 | 
						|
            }
 | 
						|
            fprintf(stream, "0x%s <uint128>\n", hex_string);
 | 
						|
            free(hex_string);
 | 
						|
#else
 | 
						|
            uint64_t high = entry_data_list->entry_data.uint128 >> 64;
 | 
						|
            uint64_t low = (uint64_t)entry_data_list->entry_data.uint128;
 | 
						|
            fprintf(stream,
 | 
						|
                    "0x%016" PRIX64 "%016" PRIX64 " <uint128>\n",
 | 
						|
                    high,
 | 
						|
                    low);
 | 
						|
#endif
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
            break;
 | 
						|
        case MMDB_DATA_TYPE_INT32:
 | 
						|
            print_indentation(stream, indent);
 | 
						|
            fprintf(stream, "%d <int32>\n", entry_data_list->entry_data.int32);
 | 
						|
            entry_data_list = entry_data_list->next;
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
            *status = MMDB_INVALID_DATA_ERROR;
 | 
						|
            return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    *status = MMDB_SUCCESS;
 | 
						|
    return entry_data_list;
 | 
						|
}
 | 
						|
 | 
						|
static void print_indentation(FILE *stream, int i) {
 | 
						|
    char buffer[1024];
 | 
						|
    int size = i >= 1024 ? 1023 : i;
 | 
						|
    memset(buffer, 32, size);
 | 
						|
    buffer[size] = '\0';
 | 
						|
    fputs(buffer, stream);
 | 
						|
}
 | 
						|
 | 
						|
static char *bytes_to_hex(uint8_t *bytes, uint32_t size) {
 | 
						|
    char *hex_string;
 | 
						|
    MAYBE_CHECK_SIZE_OVERFLOW(size, SIZE_MAX / 2 - 1, NULL);
 | 
						|
 | 
						|
    hex_string = calloc((size * 2) + 1, sizeof(char));
 | 
						|
    if (NULL == hex_string) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    for (uint32_t i = 0; i < size; i++) {
 | 
						|
        sprintf(hex_string + (2 * i), "%02X", bytes[i]);
 | 
						|
    }
 | 
						|
 | 
						|
    return hex_string;
 | 
						|
}
 | 
						|
 | 
						|
const char *MMDB_strerror(int error_code) {
 | 
						|
    switch (error_code) {
 | 
						|
        case MMDB_SUCCESS:
 | 
						|
            return "Success (not an error)";
 | 
						|
        case MMDB_FILE_OPEN_ERROR:
 | 
						|
            return "Error opening the specified MaxMind DB file";
 | 
						|
        case MMDB_CORRUPT_SEARCH_TREE_ERROR:
 | 
						|
            return "The MaxMind DB file's search tree is corrupt";
 | 
						|
        case MMDB_INVALID_METADATA_ERROR:
 | 
						|
            return "The MaxMind DB file contains invalid metadata";
 | 
						|
        case MMDB_IO_ERROR:
 | 
						|
            return "An attempt to read data from the MaxMind DB file failed";
 | 
						|
        case MMDB_OUT_OF_MEMORY_ERROR:
 | 
						|
            return "A memory allocation call failed";
 | 
						|
        case MMDB_UNKNOWN_DATABASE_FORMAT_ERROR:
 | 
						|
            return "The MaxMind DB file is in a format this library can't "
 | 
						|
                   "handle (unknown record size or binary format version)";
 | 
						|
        case MMDB_INVALID_DATA_ERROR:
 | 
						|
            return "The MaxMind DB file's data section contains bad data "
 | 
						|
                   "(unknown data type or corrupt data)";
 | 
						|
        case MMDB_INVALID_LOOKUP_PATH_ERROR:
 | 
						|
            return "The lookup path contained an invalid value (like a "
 | 
						|
                   "negative integer for an array index)";
 | 
						|
        case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR:
 | 
						|
            return "The lookup path does not match the data (key that doesn't "
 | 
						|
                   "exist, array index bigger than the array, expected array "
 | 
						|
                   "or map where none exists)";
 | 
						|
        case MMDB_INVALID_NODE_NUMBER_ERROR:
 | 
						|
            return "The MMDB_read_node function was called with a node number "
 | 
						|
                   "that does not exist in the search tree";
 | 
						|
        case MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR:
 | 
						|
            return "You attempted to look up an IPv6 address in an IPv4-only "
 | 
						|
                   "database";
 | 
						|
        default:
 | 
						|
            return "Unknown error code";
 | 
						|
    }
 | 
						|
}
 |