"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/maxminddb.c" between
libmaxminddb-1.5.0.tar.gz and libmaxminddb-1.5.2.tar.gz

About: libmaxminddb is a library that provides functions for working with MaxMind DB files (free GeoLite 2 or commercial GeoIP2).

maxminddb.c  (libmaxminddb-1.5.0):maxminddb.c  (libmaxminddb-1.5.2)
#if HAVE_CONFIG_H #if HAVE_CONFIG_H
#include <config.h> #include <config.h>
#endif #endif
#include "data-pool.h" #include "data-pool.h"
#include "maxminddb.h"
#include "maxminddb-compat-util.h" #include "maxminddb-compat-util.h"
#include "maxminddb.h"
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/stat.h> #include <sys/stat.h>
#ifdef _WIN32 #ifdef _WIN32
skipping to change at line 32 skipping to change at line 32
#else #else
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <unistd.h> #include <unistd.h>
#endif #endif
#define MMDB_DATA_SECTION_SEPARATOR (16) #define MMDB_DATA_SECTION_SEPARATOR (16)
#define MAXIMUM_DATA_STRUCTURE_DEPTH (512) #define MAXIMUM_DATA_STRUCTURE_DEPTH (512)
#ifdef MMDB_DEBUG #ifdef MMDB_DEBUG
#define LOCAL
#define DEBUG_MSG(msg) fprintf(stderr, msg "\n") #define DEBUG_MSG(msg) fprintf(stderr, msg "\n")
#define DEBUG_MSGF(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__) #define DEBUG_MSGF(fmt, ...) fprintf(stderr, fmt "\n", __VA_ARGS__)
#define DEBUG_BINARY(fmt, byte) \ #define DEBUG_BINARY(fmt, byte) \
do { \ do { \
char *binary = byte_to_binary(byte); \ char *binary = byte_to_binary(byte); \
if (NULL == binary) { \ if (NULL == binary) { \
fprintf(stderr, "Calloc failed in DEBUG_BINARY\n"); \ fprintf(stderr, "Calloc failed in DEBUG_BINARY\n"); \
abort(); \ abort(); \
} \ } \
fprintf(stderr, fmt "\n", binary); \ fprintf(stderr, fmt "\n", binary); \
free(binary); \ free(binary); \
} while (0) } while (0)
#define DEBUG_NL fprintf(stderr, "\n") #define DEBUG_NL fprintf(stderr, "\n")
#else #else
#define LOCAL static
#define DEBUG_MSG(...) #define DEBUG_MSG(...)
#define DEBUG_MSGF(...) #define DEBUG_MSGF(...)
#define DEBUG_BINARY(...) #define DEBUG_BINARY(...)
#define DEBUG_NL #define DEBUG_NL
#endif #endif
#ifdef MMDB_DEBUG #ifdef MMDB_DEBUG
char *byte_to_binary(uint8_t byte) char *byte_to_binary(uint8_t byte) {
{
char *bits = calloc(9, sizeof(char)); char *bits = calloc(9, sizeof(char));
if (NULL == bits) { if (NULL == bits) {
return bits; return bits;
} }
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
bits[i] = byte & (128 >> i) ? '1' : '0'; bits[i] = byte & (128 >> i) ? '1' : '0';
} }
bits[8] = '\0'; bits[8] = '\0';
return bits; return bits;
} }
char *type_num_to_name(uint8_t num) char *type_num_to_name(uint8_t num) {
{
switch (num) { switch (num) {
case 0: case 0:
return "extended"; return "extended";
case 1: case 1:
return "pointer"; return "pointer";
case 2: case 2:
return "utf8_string"; return "utf8_string";
case 3: case 3:
return "double"; return "double";
case 4: case 4:
return "bytes"; return "bytes";
case 5: case 5:
return "uint16"; return "uint16";
case 6: case 6:
return "uint32"; return "uint32";
case 7: case 7:
return "map"; return "map";
case 8: case 8:
return "int32"; return "int32";
case 9: case 9:
return "uint64"; return "uint64";
case 10: case 10:
return "uint128"; return "uint128";
case 11: case 11:
return "array"; return "array";
case 12: case 12:
return "container"; return "container";
case 13: case 13:
return "end_marker"; return "end_marker";
case 14: case 14:
return "boolean"; return "boolean";
case 15: case 15:
return "float"; return "float";
default: default:
return "unknown type"; return "unknown type";
} }
} }
#endif #endif
/* None of the values we check on the lhs are bigger than uint32_t, so on /* 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 * 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. */ * makes the compiler complain if we do the check anyway. */
#if SIZE_MAX == UINT32_MAX #if SIZE_MAX == UINT32_MAX
#define MAYBE_CHECK_SIZE_OVERFLOW(lhs, rhs, error) \ #define MAYBE_CHECK_SIZE_OVERFLOW(lhs, rhs, error) \
if ((lhs) > (rhs)) { \ if ((lhs) > (rhs)) { \
return error; \ return error; \
} }
#else #else
#define MAYBE_CHECK_SIZE_OVERFLOW(...) #define MAYBE_CHECK_SIZE_OVERFLOW(...)
#endif #endif
typedef struct record_info_s { typedef struct record_info_s {
uint16_t record_length; uint16_t record_length;
uint32_t (*left_record_getter)(const uint8_t *); uint32_t (*left_record_getter)(const uint8_t *);
uint32_t (*right_record_getter)(const uint8_t *); uint32_t (*right_record_getter)(const uint8_t *);
uint8_t right_record_offset; uint8_t right_record_offset;
} record_info_s; } record_info_s;
#define METADATA_MARKER "\xab\xcd\xefMaxMind.com" #define METADATA_MARKER "\xab\xcd\xefMaxMind.com"
/* This is 128kb */ /* This is 128kb */
#define METADATA_BLOCK_MAX_SIZE 131072 #define METADATA_BLOCK_MAX_SIZE 131072
// 64 leads us to allocating 4 KiB on a 64bit system. // 64 leads us to allocating 4 KiB on a 64bit system.
#define MMDB_POOL_INIT_SIZE 64 #define MMDB_POOL_INIT_SIZE 64
LOCAL int map_file(MMDB_s *const mmdb); static int map_file(MMDB_s *const mmdb);
LOCAL const uint8_t *find_metadata(const uint8_t *file_content, static const uint8_t *find_metadata(const uint8_t *file_content,
ssize_t file_size, uint32_t *metadata_size); ssize_t file_size,
LOCAL int read_metadata(MMDB_s *mmdb); uint32_t *metadata_size);
LOCAL MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb); static int read_metadata(MMDB_s *mmdb);
LOCAL int value_for_key_as_uint16(MMDB_entry_s *start, char *key, static MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb);
uint16_t *value); static int
LOCAL int value_for_key_as_uint32(MMDB_entry_s *start, char *key, value_for_key_as_uint16(MMDB_entry_s *start, char *key, uint16_t *value);
uint32_t *value); static int
LOCAL int value_for_key_as_uint64(MMDB_entry_s *start, char *key, value_for_key_as_uint32(MMDB_entry_s *start, char *key, uint32_t *value);
uint64_t *value); static int
LOCAL int value_for_key_as_string(MMDB_entry_s *start, char *key, value_for_key_as_uint64(MMDB_entry_s *start, char *key, uint64_t *value);
char const **value); static int
LOCAL int populate_languages_metadata(MMDB_s *mmdb, MMDB_s *metadata_db, value_for_key_as_string(MMDB_entry_s *start, char *key, char const **value);
MMDB_entry_s *metadata_start); static int populate_languages_metadata(MMDB_s *mmdb,
LOCAL int populate_description_metadata(MMDB_s *mmdb, MMDB_s *metadata_db, MMDB_s *metadata_db,
MMDB_entry_s *metadata_start); MMDB_entry_s *metadata_start);
LOCAL int resolve_any_address(const char *ipstr, struct addrinfo **addresses); static int populate_description_metadata(MMDB_s *mmdb,
LOCAL int find_address_in_search_tree(const MMDB_s *const mmdb, MMDB_s *metadata_db,
uint8_t *address, MMDB_entry_s *metadata_start);
sa_family_t address_family, static int resolve_any_address(const char *ipstr, struct addrinfo **addresses);
MMDB_lookup_result_s *result); static int find_address_in_search_tree(const MMDB_s *const mmdb,
LOCAL record_info_s record_info_for_database(const MMDB_s *const mmdb); uint8_t *address,
LOCAL int find_ipv4_start_node(MMDB_s *const mmdb); sa_family_t address_family,
LOCAL uint8_t record_type(const MMDB_s *const mmdb, uint64_t record); MMDB_lookup_result_s *result);
LOCAL uint32_t get_left_28_bit_record(const uint8_t *record); static record_info_s record_info_for_database(const MMDB_s *const mmdb);
LOCAL uint32_t get_right_28_bit_record(const uint8_t *record); static int find_ipv4_start_node(MMDB_s *const mmdb);
LOCAL uint32_t data_section_offset_for_record(const MMDB_s *const mmdb, static uint8_t record_type(const MMDB_s *const mmdb, uint64_t record);
uint64_t record); static uint32_t get_left_28_bit_record(const uint8_t *record);
LOCAL int path_length(va_list va_path); static uint32_t get_right_28_bit_record(const uint8_t *record);
LOCAL int lookup_path_in_array(const char *path_elem, const MMDB_s *const mmdb, static uint32_t data_section_offset_for_record(const MMDB_s *const mmdb,
MMDB_entry_data_s *entry_data); uint64_t record);
LOCAL int lookup_path_in_map(const char *path_elem, const MMDB_s *const mmdb, 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); MMDB_entry_data_s *entry_data);
LOCAL int skip_map_or_array(const MMDB_s *const mmdb, static int decode_one_follow(const MMDB_s *const mmdb,
MMDB_entry_data_s *entry_data); uint32_t offset,
LOCAL int decode_one_follow(const MMDB_s *const mmdb, uint32_t offset, MMDB_entry_data_s *entry_data);
MMDB_entry_data_s *entry_data); static int decode_one(const MMDB_s *const mmdb,
LOCAL int decode_one(const MMDB_s *const mmdb, uint32_t offset, uint32_t offset,
MMDB_entry_data_s *entry_data); MMDB_entry_data_s *entry_data);
LOCAL int get_ext_type(int raw_ext_type); static int get_ext_type(int raw_ext_type);
LOCAL uint32_t get_ptr_from(uint8_t ctrl, uint8_t const *const ptr, static uint32_t
int ptr_size); get_ptr_from(uint8_t ctrl, uint8_t const *const ptr, int ptr_size);
LOCAL int get_entry_data_list(const MMDB_s *const mmdb, static int get_entry_data_list(const MMDB_s *const mmdb,
uint32_t offset, uint32_t offset,
MMDB_entry_data_list_s *const entry_data_list, MMDB_entry_data_list_s *const entry_data_list,
MMDB_data_pool_s *const pool, MMDB_data_pool_s *const pool,
int depth); int depth);
LOCAL float get_ieee754_float(const uint8_t *restrict p); static float get_ieee754_float(const uint8_t *restrict p);
LOCAL double get_ieee754_double(const uint8_t *restrict p); static double get_ieee754_double(const uint8_t *restrict p);
LOCAL uint32_t get_uint32(const uint8_t *p); static uint32_t get_uint32(const uint8_t *p);
LOCAL uint32_t get_uint24(const uint8_t *p); static uint32_t get_uint24(const uint8_t *p);
LOCAL uint32_t get_uint16(const uint8_t *p); static uint32_t get_uint16(const uint8_t *p);
LOCAL uint64_t get_uintX(const uint8_t *p, int length); static uint64_t get_uintX(const uint8_t *p, int length);
LOCAL int32_t get_sintX(const uint8_t *p, int length); static int32_t get_sintX(const uint8_t *p, int length);
LOCAL void free_mmdb_struct(MMDB_s *const mmdb); static void free_mmdb_struct(MMDB_s *const mmdb);
LOCAL void free_languages_metadata(MMDB_s *mmdb); static void free_languages_metadata(MMDB_s *mmdb);
LOCAL void free_descriptions_metadata(MMDB_s *mmdb); static void free_descriptions_metadata(MMDB_s *mmdb);
LOCAL MMDB_entry_data_list_s *dump_entry_data_list( static MMDB_entry_data_list_s *
FILE *stream, MMDB_entry_data_list_s *entry_data_list, int indent, dump_entry_data_list(FILE *stream,
int *status); MMDB_entry_data_list_s *entry_data_list,
LOCAL void print_indentation(FILE *stream, int i); int indent,
LOCAL char *bytes_to_hex(uint8_t *bytes, uint32_t size); int *status);
static void print_indentation(FILE *stream, int i);
#define CHECKED_DECODE_ONE(mmdb, offset, entry_data) \ static char *bytes_to_hex(uint8_t *bytes, uint32_t size);
do { \
int status = decode_one(mmdb, offset, entry_data); \ #define CHECKED_DECODE_ONE(mmdb, offset, entry_data) \
if (MMDB_SUCCESS != status) { \ do { \
DEBUG_MSGF("CHECKED_DECODE_ONE failed." \ int status = decode_one(mmdb, offset, entry_data); \
" status = %d (%s)", status, MMDB_strerror(status)); \ if (MMDB_SUCCESS != status) { \
return status; \ DEBUG_MSGF("CHECKED_DECODE_ONE failed." \
} \ " status = %d (%s)", \
status, \
MMDB_strerror(status)); \
return status; \
} \
} while (0) } while (0)
#define CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data) \ #define CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data) \
do { \ do { \
int status = decode_one_follow(mmdb, offset, entry_data); \ int status = decode_one_follow(mmdb, offset, entry_data); \
if (MMDB_SUCCESS != status) { \ if (MMDB_SUCCESS != status) { \
DEBUG_MSGF("CHECKED_DECODE_ONE_FOLLOW failed." \ DEBUG_MSGF("CHECKED_DECODE_ONE_FOLLOW failed." \
" status = %d (%s)", status, MMDB_strerror(status)); \ " status = %d (%s)", \
return status; \ status, \
} \ MMDB_strerror(status)); \
return status; \
} \
} while (0) } while (0)
#define FREE_AND_SET_NULL(p) { free((void *)(p)); (p) = NULL; } #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 MMDB_open(const char *const filename, uint32_t flags, MMDB_s *const mmdb) {
{
int status = MMDB_SUCCESS; int status = MMDB_SUCCESS;
mmdb->file_content = NULL; mmdb->file_content = NULL;
mmdb->data_section = NULL; mmdb->data_section = NULL;
mmdb->metadata.database_type = NULL; mmdb->metadata.database_type = NULL;
mmdb->metadata.languages.count = 0; mmdb->metadata.languages.count = 0;
mmdb->metadata.languages.names = NULL; mmdb->metadata.languages.names = NULL;
mmdb->metadata.description.count = 0; mmdb->metadata.description.count = 0;
mmdb->filename = mmdb_strdup(filename); mmdb->filename = mmdb_strdup(filename);
skipping to change at line 255 skipping to change at line 267
if (MMDB_SUCCESS != (status = map_file(mmdb))) { if (MMDB_SUCCESS != (status = map_file(mmdb))) {
goto cleanup; goto cleanup;
} }
#ifdef _WIN32 #ifdef _WIN32
WSADATA wsa; WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa); WSAStartup(MAKEWORD(2, 2), &wsa);
#endif #endif
uint32_t metadata_size = 0; uint32_t metadata_size = 0;
const uint8_t *metadata = find_metadata(mmdb->file_content, mmdb->file_size, const uint8_t *metadata =
&metadata_size); find_metadata(mmdb->file_content, mmdb->file_size, &metadata_size);
if (NULL == metadata) { if (NULL == metadata) {
status = MMDB_INVALID_METADATA_ERROR; status = MMDB_INVALID_METADATA_ERROR;
goto cleanup; goto cleanup;
} }
mmdb->metadata_section = metadata; mmdb->metadata_section = metadata;
mmdb->metadata_section_size = metadata_size; mmdb->metadata_section_size = metadata_size;
status = read_metadata(mmdb); status = read_metadata(mmdb);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
goto cleanup; goto cleanup;
} }
if (mmdb->metadata.binary_format_major_version != 2) { if (mmdb->metadata.binary_format_major_version != 2) {
status = MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; status = MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
goto cleanup; goto cleanup;
} }
uint32_t search_tree_size = mmdb->metadata.node_count * uint32_t search_tree_size =
mmdb->full_record_byte_size; mmdb->metadata.node_count * mmdb->full_record_byte_size;
mmdb->data_section = mmdb->file_content + search_tree_size mmdb->data_section =
+ MMDB_DATA_SECTION_SEPARATOR; mmdb->file_content + search_tree_size + MMDB_DATA_SECTION_SEPARATOR;
if (search_tree_size + MMDB_DATA_SECTION_SEPARATOR > if (search_tree_size + MMDB_DATA_SECTION_SEPARATOR >
(uint32_t)mmdb->file_size) { (uint32_t)mmdb->file_size) {
status = MMDB_INVALID_METADATA_ERROR; status = MMDB_INVALID_METADATA_ERROR;
goto cleanup; goto cleanup;
} }
mmdb->data_section_size = (uint32_t)mmdb->file_size - search_tree_size - mmdb->data_section_size = (uint32_t)mmdb->file_size - search_tree_size -
MMDB_DATA_SECTION_SEPARATOR; MMDB_DATA_SECTION_SEPARATOR;
// Although it is likely not possible to construct a database with valid // 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, // valid metadata, as parsed above, and a data_section_size less than 3,
skipping to change at line 310 skipping to change at line 322
// We do this immediately as otherwise there is a race to set // We do this immediately as otherwise there is a race to set
// ipv4_start_node.node_value and ipv4_start_node.netmask. // ipv4_start_node.node_value and ipv4_start_node.netmask.
if (mmdb->metadata.ip_version == 6) { if (mmdb->metadata.ip_version == 6) {
status = find_ipv4_start_node(mmdb); status = find_ipv4_start_node(mmdb);
if (status != MMDB_SUCCESS) { if (status != MMDB_SUCCESS) {
goto cleanup; goto cleanup;
} }
} }
cleanup: cleanup:
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
int saved_errno = errno; int saved_errno = errno;
free_mmdb_struct(mmdb); free_mmdb_struct(mmdb);
errno = saved_errno; errno = saved_errno;
} }
return status; return status;
} }
#ifdef _WIN32 #ifdef _WIN32
LOCAL LPWSTR utf8_to_utf16(const char *utf8_str) static LPWSTR utf8_to_utf16(const char *utf8_str) {
{
int wide_chars = MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, NULL, 0); int wide_chars = MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, NULL, 0);
wchar_t *utf16_str = (wchar_t *)calloc(wide_chars, sizeof(wchar_t)); 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, if (MultiByteToWideChar(CP_UTF8, 0, utf8_str, -1, utf16_str, wide_chars) <
wide_chars) < 1) { 1) {
free(utf16_str); free(utf16_str);
return NULL; return NULL;
} }
return utf16_str; return utf16_str;
} }
LOCAL int map_file(MMDB_s *const mmdb) static int map_file(MMDB_s *const mmdb) {
{
DWORD size; DWORD size;
int status = MMDB_SUCCESS; int status = MMDB_SUCCESS;
HANDLE mmh = NULL; HANDLE mmh = NULL;
HANDLE fd = INVALID_HANDLE_VALUE; HANDLE fd = INVALID_HANDLE_VALUE;
LPWSTR utf16_filename = utf8_to_utf16(mmdb->filename); LPWSTR utf16_filename = utf8_to_utf16(mmdb->filename);
if (!utf16_filename) { if (!utf16_filename) {
status = MMDB_FILE_OPEN_ERROR; status = MMDB_FILE_OPEN_ERROR;
goto cleanup; goto cleanup;
} }
fd = CreateFileW(utf16_filename, GENERIC_READ, FILE_SHARE_READ, NULL, fd = CreateFileW(utf16_filename,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (fd == INVALID_HANDLE_VALUE) { if (fd == INVALID_HANDLE_VALUE) {
status = MMDB_FILE_OPEN_ERROR; status = MMDB_FILE_OPEN_ERROR;
goto cleanup; goto cleanup;
} }
size = GetFileSize(fd, NULL); size = GetFileSize(fd, NULL);
if (size == INVALID_FILE_SIZE) { if (size == INVALID_FILE_SIZE) {
status = MMDB_FILE_OPEN_ERROR; status = MMDB_FILE_OPEN_ERROR;
goto cleanup; goto cleanup;
} }
mmh = CreateFileMapping(fd, NULL, PAGE_READONLY, 0, size, NULL); mmh = CreateFileMapping(fd, NULL, PAGE_READONLY, 0, size, NULL);
skipping to change at line 374 skipping to change at line 392
uint8_t *file_content = uint8_t *file_content =
(uint8_t *)MapViewOfFile(mmh, FILE_MAP_READ, 0, 0, 0); (uint8_t *)MapViewOfFile(mmh, FILE_MAP_READ, 0, 0, 0);
if (file_content == NULL) { if (file_content == NULL) {
status = MMDB_IO_ERROR; status = MMDB_IO_ERROR;
goto cleanup; goto cleanup;
} }
mmdb->file_size = size; mmdb->file_size = size;
mmdb->file_content = file_content; mmdb->file_content = file_content;
cleanup:; cleanup:;
int saved_errno = errno; int saved_errno = errno;
if (INVALID_HANDLE_VALUE != fd) { if (INVALID_HANDLE_VALUE != fd) {
CloseHandle(fd); CloseHandle(fd);
} }
if (NULL != mmh) { if (NULL != mmh) {
CloseHandle(mmh); CloseHandle(mmh);
} }
errno = saved_errno; errno = saved_errno;
free(utf16_filename); free(utf16_filename);
return status; return status;
} }
#else // _WIN32 #else // _WIN32
LOCAL int map_file(MMDB_s *const mmdb) static int map_file(MMDB_s *const mmdb) {
{
ssize_t size; ssize_t size;
int status = MMDB_SUCCESS; int status = MMDB_SUCCESS;
int flags = O_RDONLY; int flags = O_RDONLY;
#ifdef O_CLOEXEC #ifdef O_CLOEXEC
flags |= O_CLOEXEC; flags |= O_CLOEXEC;
#endif #endif
int fd = open(mmdb->filename, flags); int fd = open(mmdb->filename, flags);
struct stat s; struct stat s;
if (fd < 0 || fstat(fd, &s)) { if (fd < 0 || fstat(fd, &s)) {
skipping to change at line 426 skipping to change at line 443
status = MMDB_OUT_OF_MEMORY_ERROR; status = MMDB_OUT_OF_MEMORY_ERROR;
} else { } else {
status = MMDB_IO_ERROR; status = MMDB_IO_ERROR;
} }
goto cleanup; goto cleanup;
} }
mmdb->file_size = size; mmdb->file_size = size;
mmdb->file_content = file_content; mmdb->file_content = file_content;
cleanup:; cleanup:;
int saved_errno = errno; int saved_errno = errno;
if (fd >= 0) { if (fd >= 0) {
close(fd); close(fd);
} }
errno = saved_errno; errno = saved_errno;
return status; return status;
} }
#endif // _WIN32 #endif // _WIN32
LOCAL const uint8_t *find_metadata(const uint8_t *file_content, static const uint8_t *find_metadata(const uint8_t *file_content,
ssize_t file_size, uint32_t *metadata_size) ssize_t file_size,
{ uint32_t *metadata_size) {
const ssize_t marker_len = sizeof(METADATA_MARKER) - 1; const ssize_t marker_len = sizeof(METADATA_MARKER) - 1;
ssize_t max_size = file_size > ssize_t max_size = file_size > METADATA_BLOCK_MAX_SIZE
METADATA_BLOCK_MAX_SIZE ? METADATA_BLOCK_MAX_SIZE : ? METADATA_BLOCK_MAX_SIZE
file_size; : file_size;
uint8_t *search_area = (uint8_t *)(file_content + (file_size - max_size)); uint8_t *search_area = (uint8_t *)(file_content + (file_size - max_size));
uint8_t *start = search_area; uint8_t *start = search_area;
uint8_t *tmp; uint8_t *tmp;
do { do {
tmp = mmdb_memmem(search_area, max_size, tmp = mmdb_memmem(search_area, max_size, METADATA_MARKER, marker_len);
METADATA_MARKER, marker_len);
if (NULL != tmp) { if (NULL != tmp) {
max_size -= tmp - search_area; max_size -= tmp - search_area;
search_area = tmp; search_area = tmp;
/* Continue searching just after the marker we just read, in case /* Continue searching just after the marker we just read, in case
* there are multiple markers in the same file. This would be odd * there are multiple markers in the same file. This would be odd
* but is certainly not impossible. */ * but is certainly not impossible. */
max_size -= marker_len; max_size -= marker_len;
search_area += marker_len; search_area += marker_len;
skipping to change at line 474 skipping to change at line 490
if (search_area == start) { if (search_area == start) {
return NULL; return NULL;
} }
*metadata_size = (uint32_t)max_size; *metadata_size = (uint32_t)max_size;
return search_area; return search_area;
} }
LOCAL int read_metadata(MMDB_s *mmdb) static int read_metadata(MMDB_s *mmdb) {
{
/* We need to create a fake MMDB_s struct in order to decode values from /* 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 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 metadat want to use the same functions we use for the data section to get
a metadata values. */
values. */
MMDB_s metadata_db = make_fake_metadata_db(mmdb); MMDB_s metadata_db = make_fake_metadata_db(mmdb);
MMDB_entry_s metadata_start = { MMDB_entry_s metadata_start = {.mmdb = &metadata_db, .offset = 0};
.mmdb = &metadata_db,
.offset = 0
};
int status = int status = value_for_key_as_uint32(
value_for_key_as_uint32(&metadata_start, "node_count", &metadata_start, "node_count", &mmdb->metadata.node_count);
&mmdb->metadata.node_count);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (!mmdb->metadata.node_count) { if (!mmdb->metadata.node_count) {
DEBUG_MSG("could not find node_count value in metadata"); DEBUG_MSG("could not find node_count value in metadata");
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
status = value_for_key_as_uint16(&metadata_start, "record_size", status = value_for_key_as_uint16(
&mmdb->metadata.record_size); &metadata_start, "record_size", &mmdb->metadata.record_size);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (!mmdb->metadata.record_size) { if (!mmdb->metadata.record_size) {
DEBUG_MSG("could not find record_size value in metadata"); DEBUG_MSG("could not find record_size value in metadata");
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
if (mmdb->metadata.record_size != 24 && mmdb->metadata.record_size != 28 if (mmdb->metadata.record_size != 24 && mmdb->metadata.record_size != 28 &&
&& mmdb->metadata.record_size != 32) { mmdb->metadata.record_size != 32) {
DEBUG_MSGF("bad record size in metadata: %i", DEBUG_MSGF("bad record size in metadata: %i",
mmdb->metadata.record_size); mmdb->metadata.record_size);
return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
} }
status = value_for_key_as_uint16(&metadata_start, "ip_version", status = value_for_key_as_uint16(
&mmdb->metadata.ip_version); &metadata_start, "ip_version", &mmdb->metadata.ip_version);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (!mmdb->metadata.ip_version) { if (!mmdb->metadata.ip_version) {
DEBUG_MSG("could not find ip_version value in metadata"); DEBUG_MSG("could not find ip_version value in metadata");
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
if (!(mmdb->metadata.ip_version == 4 || mmdb->metadata.ip_version == 6)) { 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", DEBUG_MSGF("ip_version value in metadata is not 4 or 6 - it was %i",
mmdb->metadata.ip_version); mmdb->metadata.ip_version);
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
status = value_for_key_as_string(&metadata_start, "database_type", status = value_for_key_as_string(
&mmdb->metadata.database_type); &metadata_start, "database_type", &mmdb->metadata.database_type);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
DEBUG_MSG("error finding database_type value in metadata"); DEBUG_MSG("error finding database_type value in metadata");
return status; return status;
} }
status = status = populate_languages_metadata(mmdb, &metadata_db, &metadata_start);
populate_languages_metadata(mmdb, &metadata_db, &metadata_start);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
DEBUG_MSG("could not populate languages from metadata"); DEBUG_MSG("could not populate languages from metadata");
return status; return status;
} }
status = value_for_key_as_uint16( status =
&metadata_start, "binary_format_major_version", value_for_key_as_uint16(&metadata_start,
&mmdb->metadata.binary_format_major_version); "binary_format_major_version",
&mmdb->metadata.binary_format_major_version);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (!mmdb->metadata.binary_format_major_version) { if (!mmdb->metadata.binary_format_major_version) {
DEBUG_MSG( DEBUG_MSG(
"could not find binary_format_major_version value in metadata"); "could not find binary_format_major_version value in metadata");
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
status = value_for_key_as_uint16( status =
&metadata_start, "binary_format_minor_version", value_for_key_as_uint16(&metadata_start,
&mmdb->metadata.binary_format_minor_version); "binary_format_minor_version",
&mmdb->metadata.binary_format_minor_version);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
status = value_for_key_as_uint64(&metadata_start, "build_epoch", status = value_for_key_as_uint64(
&mmdb->metadata.build_epoch); &metadata_start, "build_epoch", &mmdb->metadata.build_epoch);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (!mmdb->metadata.build_epoch) { if (!mmdb->metadata.build_epoch) {
DEBUG_MSG("could not find build_epoch value in metadata"); DEBUG_MSG("could not find build_epoch value in metadata");
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
status = populate_description_metadata(mmdb, &metadata_db, &metadata_start); status = populate_description_metadata(mmdb, &metadata_db, &metadata_start);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
skipping to change at line 586 skipping to change at line 598
return status; return status;
} }
mmdb->full_record_byte_size = mmdb->metadata.record_size * 2 / 8U; mmdb->full_record_byte_size = mmdb->metadata.record_size * 2 / 8U;
mmdb->depth = mmdb->metadata.ip_version == 4 ? 32 : 128; mmdb->depth = mmdb->metadata.ip_version == 4 ? 32 : 128;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb) static MMDB_s make_fake_metadata_db(const MMDB_s *const mmdb) {
{ MMDB_s fake_metadata_db = {.data_section = mmdb->metadata_section,
MMDB_s fake_metadata_db = { .data_section_size =
.data_section = mmdb->metadata_section, mmdb->metadata_section_size};
.data_section_size = mmdb->metadata_section_size
};
return fake_metadata_db; return fake_metadata_db;
} }
LOCAL int value_for_key_as_uint16(MMDB_entry_s *start, char *key, static int
uint16_t *value) value_for_key_as_uint16(MMDB_entry_s *start, char *key, uint16_t *value) {
{
MMDB_entry_data_s entry_data; MMDB_entry_data_s entry_data;
const char *path[] = { key, NULL }; const char *path[] = {key, NULL};
int status = MMDB_aget_value(start, &entry_data, path); int status = MMDB_aget_value(start, &entry_data, path);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (MMDB_DATA_TYPE_UINT16 != entry_data.type) { if (MMDB_DATA_TYPE_UINT16 != entry_data.type) {
DEBUG_MSGF("expect uint16 for %s but received %s", key, DEBUG_MSGF("expect uint16 for %s but received %s",
type_num_to_name( key,
entry_data.type)); type_num_to_name(entry_data.type));
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
*value = entry_data.uint16; *value = entry_data.uint16;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int value_for_key_as_uint32(MMDB_entry_s *start, char *key, static int
uint32_t *value) value_for_key_as_uint32(MMDB_entry_s *start, char *key, uint32_t *value) {
{
MMDB_entry_data_s entry_data; MMDB_entry_data_s entry_data;
const char *path[] = { key, NULL }; const char *path[] = {key, NULL};
int status = MMDB_aget_value(start, &entry_data, path); int status = MMDB_aget_value(start, &entry_data, path);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (MMDB_DATA_TYPE_UINT32 != entry_data.type) { if (MMDB_DATA_TYPE_UINT32 != entry_data.type) {
DEBUG_MSGF("expect uint32 for %s but received %s", key, DEBUG_MSGF("expect uint32 for %s but received %s",
type_num_to_name( key,
entry_data.type)); type_num_to_name(entry_data.type));
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
*value = entry_data.uint32; *value = entry_data.uint32;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int value_for_key_as_uint64(MMDB_entry_s *start, char *key, static int
uint64_t *value) value_for_key_as_uint64(MMDB_entry_s *start, char *key, uint64_t *value) {
{
MMDB_entry_data_s entry_data; MMDB_entry_data_s entry_data;
const char *path[] = { key, NULL }; const char *path[] = {key, NULL};
int status = MMDB_aget_value(start, &entry_data, path); int status = MMDB_aget_value(start, &entry_data, path);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (MMDB_DATA_TYPE_UINT64 != entry_data.type) { if (MMDB_DATA_TYPE_UINT64 != entry_data.type) {
DEBUG_MSGF("expect uint64 for %s but received %s", key, DEBUG_MSGF("expect uint64 for %s but received %s",
type_num_to_name( key,
entry_data.type)); type_num_to_name(entry_data.type));
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
*value = entry_data.uint64; *value = entry_data.uint64;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int value_for_key_as_string(MMDB_entry_s *start, char *key, static int
char const **value) value_for_key_as_string(MMDB_entry_s *start, char *key, char const **value) {
{
MMDB_entry_data_s entry_data; MMDB_entry_data_s entry_data;
const char *path[] = { key, NULL }; const char *path[] = {key, NULL};
int status = MMDB_aget_value(start, &entry_data, path); int status = MMDB_aget_value(start, &entry_data, path);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (MMDB_DATA_TYPE_UTF8_STRING != entry_data.type) { if (MMDB_DATA_TYPE_UTF8_STRING != entry_data.type) {
DEBUG_MSGF("expect string for %s but received %s", key, DEBUG_MSGF("expect string for %s but received %s",
type_num_to_name( key,
entry_data.type)); type_num_to_name(entry_data.type));
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
*value = mmdb_strndup((char *)entry_data.utf8_string, entry_data.data_size); *value = mmdb_strndup((char *)entry_data.utf8_string, entry_data.data_size);
if (NULL == *value) { if (NULL == *value) {
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int populate_languages_metadata(MMDB_s *mmdb, MMDB_s *metadata_db, static int populate_languages_metadata(MMDB_s *mmdb,
MMDB_entry_s *metadata_start) MMDB_s *metadata_db,
{ MMDB_entry_s *metadata_start) {
MMDB_entry_data_s entry_data; MMDB_entry_data_s entry_data;
const char *path[] = { "languages", NULL }; const char *path[] = {"languages", NULL};
int status = MMDB_aget_value(metadata_start, &entry_data, path); int status = MMDB_aget_value(metadata_start, &entry_data, path);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (MMDB_DATA_TYPE_ARRAY != entry_data.type) { if (MMDB_DATA_TYPE_ARRAY != entry_data.type) {
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
MMDB_entry_s array_start = { MMDB_entry_s array_start = {.mmdb = metadata_db,
.mmdb = metadata_db, .offset = entry_data.offset};
.offset = entry_data.offset
};
MMDB_entry_data_list_s *member; MMDB_entry_data_list_s *member;
status = MMDB_get_entry_data_list(&array_start, &member); status = MMDB_get_entry_data_list(&array_start, &member);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
MMDB_entry_data_list_s *first_member = member; MMDB_entry_data_list_s *first_member = member;
uint32_t array_size = member->entry_data.data_size; uint32_t array_size = member->entry_data.data_size;
MAYBE_CHECK_SIZE_OVERFLOW(array_size, SIZE_MAX / sizeof(char *), MAYBE_CHECK_SIZE_OVERFLOW(
MMDB_INVALID_METADATA_ERROR); array_size, SIZE_MAX / sizeof(char *), MMDB_INVALID_METADATA_ERROR);
mmdb->metadata.languages.count = 0; mmdb->metadata.languages.count = 0;
mmdb->metadata.languages.names = calloc(array_size, sizeof(char *)); mmdb->metadata.languages.names = calloc(array_size, sizeof(char *));
if (NULL == mmdb->metadata.languages.names) { if (NULL == mmdb->metadata.languages.names) {
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
for (uint32_t i = 0; i < array_size; i++) { for (uint32_t i = 0; i < array_size; i++) {
member = member->next; member = member->next;
if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) { if (MMDB_DATA_TYPE_UTF8_STRING != member->entry_data.type) {
skipping to change at line 735 skipping to change at line 739
// We assign this as we go so that if we fail a calloc and need to // We assign this as we go so that if we fail a calloc and need to
// free it, the count is right. // free it, the count is right.
mmdb->metadata.languages.count = i + 1; mmdb->metadata.languages.count = i + 1;
} }
MMDB_free_entry_data_list(first_member); MMDB_free_entry_data_list(first_member);
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int populate_description_metadata(MMDB_s *mmdb, MMDB_s *metadata_db, static int populate_description_metadata(MMDB_s *mmdb,
MMDB_entry_s *metadata_start) MMDB_s *metadata_db,
{ MMDB_entry_s *metadata_start) {
MMDB_entry_data_s entry_data; MMDB_entry_data_s entry_data;
const char *path[] = { "description", NULL }; const char *path[] = {"description", NULL};
int status = MMDB_aget_value(metadata_start, &entry_data, path); int status = MMDB_aget_value(metadata_start, &entry_data, path);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
if (MMDB_DATA_TYPE_MAP != entry_data.type) { if (MMDB_DATA_TYPE_MAP != entry_data.type) {
DEBUG_MSGF("Unexpected entry_data type: %d", entry_data.type); DEBUG_MSGF("Unexpected entry_data type: %d", entry_data.type);
return MMDB_INVALID_METADATA_ERROR; return MMDB_INVALID_METADATA_ERROR;
} }
MMDB_entry_s map_start = { MMDB_entry_s map_start = {.mmdb = metadata_db, .offset = entry_data.offset};
.mmdb = metadata_db,
.offset = entry_data.offset
};
MMDB_entry_data_list_s *member; MMDB_entry_data_list_s *member;
status = MMDB_get_entry_data_list(&map_start, &member); status = MMDB_get_entry_data_list(&map_start, &member);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
DEBUG_MSGF( DEBUG_MSGF(
"MMDB_get_entry_data_list failed while populating description." "MMDB_get_entry_data_list failed while populating description."
" status = %d (%s)", status, MMDB_strerror(status)); " status = %d (%s)",
status,
MMDB_strerror(status));
return status; return status;
} }
MMDB_entry_data_list_s *first_member = member; MMDB_entry_data_list_s *first_member = member;
uint32_t map_size = member->entry_data.data_size; uint32_t map_size = member->entry_data.data_size;
mmdb->metadata.description.count = 0; mmdb->metadata.description.count = 0;
if (0 == map_size) { if (0 == map_size) {
mmdb->metadata.description.descriptions = NULL; mmdb->metadata.description.descriptions = NULL;
goto cleanup; goto cleanup;
} }
MAYBE_CHECK_SIZE_OVERFLOW(map_size, SIZE_MAX / sizeof(MMDB_description_s *), MAYBE_CHECK_SIZE_OVERFLOW(map_size,
SIZE_MAX / sizeof(MMDB_description_s *),
MMDB_INVALID_METADATA_ERROR); MMDB_INVALID_METADATA_ERROR);
mmdb->metadata.description.descriptions = mmdb->metadata.description.descriptions =
calloc(map_size, sizeof(MMDB_description_s *)); calloc(map_size, sizeof(MMDB_description_s *));
if (NULL == mmdb->metadata.description.descriptions) { if (NULL == mmdb->metadata.description.descriptions) {
status = MMDB_OUT_OF_MEMORY_ERROR; status = MMDB_OUT_OF_MEMORY_ERROR;
goto cleanup; goto cleanup;
} }
for (uint32_t i = 0; i < map_size; i++) { for (uint32_t i = 0; i < map_size; i++) {
skipping to change at line 828 skipping to change at line 832
mmdb->metadata.description.descriptions[i]->description = mmdb->metadata.description.descriptions[i]->description =
mmdb_strndup((char *)member->entry_data.utf8_string, mmdb_strndup((char *)member->entry_data.utf8_string,
member->entry_data.data_size); member->entry_data.data_size);
if (NULL == mmdb->metadata.description.descriptions[i]->description) { if (NULL == mmdb->metadata.description.descriptions[i]->description) {
status = MMDB_OUT_OF_MEMORY_ERROR; status = MMDB_OUT_OF_MEMORY_ERROR;
goto cleanup; goto cleanup;
} }
} }
cleanup: cleanup:
MMDB_free_entry_data_list(first_member); MMDB_free_entry_data_list(first_member);
return status; return status;
} }
MMDB_lookup_result_s MMDB_lookup_string(const MMDB_s *const mmdb, MMDB_lookup_result_s MMDB_lookup_string(const MMDB_s *const mmdb,
const char *const ipstr, const char *const ipstr,
int *const gai_error, int *const gai_error,
int *const mmdb_error) int *const mmdb_error) {
{ MMDB_lookup_result_s result = {.found_entry = false,
MMDB_lookup_result_s result = { .netmask = 0,
.found_entry = false, .entry = {.mmdb = mmdb, .offset = 0}};
.netmask = 0,
.entry = {
.mmdb = mmdb,
.offset = 0
}
};
struct addrinfo *addresses = NULL; struct addrinfo *addresses = NULL;
*gai_error = resolve_any_address(ipstr, &addresses); *gai_error = resolve_any_address(ipstr, &addresses);
if (!*gai_error) { if (!*gai_error) {
result = MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, mmdb_error); result = MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, mmdb_error);
} }
if (NULL != addresses) { if (NULL != addresses) {
freeaddrinfo(addresses); freeaddrinfo(addresses);
} }
return result; return result;
} }
LOCAL int resolve_any_address(const char *ipstr, struct addrinfo **addresses) static int resolve_any_address(const char *ipstr, struct addrinfo **addresses) {
{
struct addrinfo hints = { struct addrinfo hints = {
.ai_family = AF_UNSPEC, .ai_family = AF_UNSPEC,
.ai_flags = AI_NUMERICHOST, .ai_flags = AI_NUMERICHOST,
// We set ai_socktype so that we only get one result back // We set ai_socktype so that we only get one result back
.ai_socktype = SOCK_STREAM .ai_socktype = SOCK_STREAM};
};
int gai_status = getaddrinfo(ipstr, NULL, &hints, addresses); int gai_status = getaddrinfo(ipstr, NULL, &hints, addresses);
if (gai_status) { if (gai_status) {
return gai_status; return gai_status;
} }
return 0; return 0;
} }
MMDB_lookup_result_s MMDB_lookup_sockaddr( MMDB_lookup_result_s MMDB_lookup_sockaddr(const MMDB_s *const mmdb,
const MMDB_s *const mmdb, const struct sockaddr *const sockaddr,
const struct sockaddr *const sockaddr, int *const mmdb_error) {
int *const mmdb_error) MMDB_lookup_result_s result = {.found_entry = false,
{ .netmask = 0,
MMDB_lookup_result_s result = { .entry = {.mmdb = mmdb, .offset = 0}};
.found_entry = false,
.netmask = 0,
.entry = {
.mmdb = mmdb,
.offset = 0
}
};
uint8_t mapped_address[16], *address; uint8_t mapped_address[16], *address;
if (mmdb->metadata.ip_version == 4) { if (mmdb->metadata.ip_version == 4) {
if (sockaddr->sa_family == AF_INET6) { if (sockaddr->sa_family == AF_INET6) {
*mmdb_error = MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR; *mmdb_error = MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR;
return result; return result;
} }
address = (uint8_t *)&((struct sockaddr_in *)sockaddr)->sin_addr.s_addr; address = (uint8_t *)&((struct sockaddr_in *)sockaddr)->sin_addr.s_addr;
} else { } else {
if (sockaddr->sa_family == AF_INET6) { if (sockaddr->sa_family == AF_INET6) {
address = address = (uint8_t *)&((struct sockaddr_in6 *)sockaddr)
(uint8_t *)&((struct sockaddr_in6 *)sockaddr)->sin6_addr. ->sin6_addr.s6_addr;
s6_addr;
} else { } else {
address = mapped_address; address = mapped_address;
memset(address, 0, 12); memset(address, 0, 12);
memcpy(address + 12, memcpy(address + 12,
&((struct sockaddr_in *)sockaddr)->sin_addr.s_addr, 4); &((struct sockaddr_in *)sockaddr)->sin_addr.s_addr,
4);
} }
} }
*mmdb_error = *mmdb_error = find_address_in_search_tree(
find_address_in_search_tree(mmdb, address, sockaddr->sa_family, mmdb, address, sockaddr->sa_family, &result);
&result);
return result; return result;
} }
LOCAL int find_address_in_search_tree(const MMDB_s *const mmdb, static int find_address_in_search_tree(const MMDB_s *const mmdb,
uint8_t *address, uint8_t *address,
sa_family_t address_family, sa_family_t address_family,
MMDB_lookup_result_s *result) MMDB_lookup_result_s *result) {
{
record_info_s record_info = record_info_for_database(mmdb); record_info_s record_info = record_info_for_database(mmdb);
if (0 == record_info.right_record_offset) { if (0 == record_info.right_record_offset) {
return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
} }
uint32_t value = 0; uint32_t value = 0;
uint16_t current_bit = 0; uint16_t current_bit = 0;
if (mmdb->metadata.ip_version == 6 && address_family == AF_INET) { if (mmdb->metadata.ip_version == 6 && address_family == AF_INET) {
value = mmdb->ipv4_start_node.node_value; value = mmdb->ipv4_start_node.node_value;
current_bit = mmdb->ipv4_start_node.netmask; current_bit = mmdb->ipv4_start_node.netmask;
} }
uint32_t node_count = mmdb->metadata.node_count; uint32_t node_count = mmdb->metadata.node_count;
const uint8_t *search_tree = mmdb->file_content; const uint8_t *search_tree = mmdb->file_content;
const uint8_t *record_pointer; const uint8_t *record_pointer;
for (; current_bit < mmdb->depth && value < node_count; current_bit++) { for (; current_bit < mmdb->depth && value < node_count; current_bit++) {
uint8_t bit = 1U & uint8_t bit =
(address[current_bit >> 3] >> (7 - (current_bit % 8))); 1U & (address[current_bit >> 3] >> (7 - (current_bit % 8)));
record_pointer = &search_tree[value * record_info.record_length]; record_pointer = &search_tree[value * record_info.record_length];
if (record_pointer + record_info.record_length > mmdb->data_section) { if (record_pointer + record_info.record_length > mmdb->data_section) {
return MMDB_CORRUPT_SEARCH_TREE_ERROR; return MMDB_CORRUPT_SEARCH_TREE_ERROR;
} }
if (bit) { if (bit) {
record_pointer += record_info.right_record_offset; record_pointer += record_info.right_record_offset;
value = record_info.right_record_getter(record_pointer); value = record_info.right_record_getter(record_pointer);
} else { } else {
value = record_info.left_record_getter(record_pointer); value = record_info.left_record_getter(record_pointer);
skipping to change at line 974 skipping to change at line 961
// record is empty // record is empty
result->found_entry = false; result->found_entry = false;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
result->found_entry = true; result->found_entry = true;
result->entry.offset = data_section_offset_for_record(mmdb, value); result->entry.offset = data_section_offset_for_record(mmdb, value);
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL record_info_s record_info_for_database(const MMDB_s *const mmdb) 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,
record_info_s record_info = { .right_record_offset = 0};
.record_length = mmdb->full_record_byte_size,
.right_record_offset = 0
};
if (record_info.record_length == 6) { if (record_info.record_length == 6) {
record_info.left_record_getter = &get_uint24; record_info.left_record_getter = &get_uint24;
record_info.right_record_getter = &get_uint24; record_info.right_record_getter = &get_uint24;
record_info.right_record_offset = 3; record_info.right_record_offset = 3;
} else if (record_info.record_length == 7) { } else if (record_info.record_length == 7) {
record_info.left_record_getter = &get_left_28_bit_record; record_info.left_record_getter = &get_left_28_bit_record;
record_info.right_record_getter = &get_right_28_bit_record; record_info.right_record_getter = &get_right_28_bit_record;
record_info.right_record_offset = 3; record_info.right_record_offset = 3;
} else if (record_info.record_length == 8) { } else if (record_info.record_length == 8) {
record_info.left_record_getter = &get_uint32; record_info.left_record_getter = &get_uint32;
record_info.right_record_getter = &get_uint32; record_info.right_record_getter = &get_uint32;
record_info.right_record_offset = 4; record_info.right_record_offset = 4;
} else { } else {
assert(false); assert(false);
} }
return record_info; return record_info;
} }
LOCAL int find_ipv4_start_node(MMDB_s *const mmdb) static int find_ipv4_start_node(MMDB_s *const mmdb) {
{
/* In a pathological case of a database with a single node search tree, /* 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 * this check will be true even after we've found the IPv4 start node, but
* that doesn't seem worth trying to fix. */ * that doesn't seem worth trying to fix. */
if (mmdb->ipv4_start_node.node_value != 0) { if (mmdb->ipv4_start_node.node_value != 0) {
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
record_info_s record_info = record_info_for_database(mmdb); record_info_s record_info = record_info_for_database(mmdb);
const uint8_t *search_tree = mmdb->file_content; const uint8_t *search_tree = mmdb->file_content;
skipping to change at line 1031 skipping to change at line 1014
} }
node_value = record_info.left_record_getter(record_pointer); node_value = record_info.left_record_getter(record_pointer);
} }
mmdb->ipv4_start_node.node_value = node_value; mmdb->ipv4_start_node.node_value = node_value;
mmdb->ipv4_start_node.netmask = netmask; mmdb->ipv4_start_node.netmask = netmask;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL uint8_t record_type(const MMDB_s *const mmdb, uint64_t record) static uint8_t record_type(const MMDB_s *const mmdb, uint64_t record) {
{
uint32_t node_count = mmdb->metadata.node_count; uint32_t node_count = mmdb->metadata.node_count;
/* Ideally we'd check to make sure that a record never points to a /* 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 * 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. */ * at least check that we don't end up at the top of the tree again. */
if (record == 0) { if (record == 0) {
DEBUG_MSG("record has a value of 0"); DEBUG_MSG("record has a value of 0");
return MMDB_RECORD_TYPE_INVALID; return MMDB_RECORD_TYPE_INVALID;
} }
skipping to change at line 1059 skipping to change at line 1041
} }
if (record - node_count < mmdb->data_section_size) { if (record - node_count < mmdb->data_section_size) {
return MMDB_RECORD_TYPE_DATA; return MMDB_RECORD_TYPE_DATA;
} }
DEBUG_MSG("record has a value that points outside of the database"); DEBUG_MSG("record has a value that points outside of the database");
return MMDB_RECORD_TYPE_INVALID; return MMDB_RECORD_TYPE_INVALID;
} }
LOCAL uint32_t get_left_28_bit_record(const uint8_t *record) static uint32_t get_left_28_bit_record(const uint8_t *record) {
{
return record[0] * 65536 + record[1] * 256 + record[2] + return record[0] * 65536 + record[1] * 256 + record[2] +
((record[3] & 0xf0) << 20); ((record[3] & 0xf0) << 20);
} }
LOCAL uint32_t get_right_28_bit_record(const uint8_t *record) static uint32_t get_right_28_bit_record(const uint8_t *record) {
{
uint32_t value = get_uint32(record); uint32_t value = get_uint32(record);
return value & 0xfffffff; return value & 0xfffffff;
} }
int MMDB_read_node(const MMDB_s *const mmdb, uint32_t node_number, int MMDB_read_node(const MMDB_s *const mmdb,
MMDB_search_node_s *const node) uint32_t node_number,
{ MMDB_search_node_s *const node) {
record_info_s record_info = record_info_for_database(mmdb); record_info_s record_info = record_info_for_database(mmdb);
if (0 == record_info.right_record_offset) { if (0 == record_info.right_record_offset) {
return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR;
} }
if (node_number > mmdb->metadata.node_count) { if (node_number > mmdb->metadata.node_count) {
return MMDB_INVALID_NODE_NUMBER_ERROR; return MMDB_INVALID_NODE_NUMBER_ERROR;
} }
const uint8_t *search_tree = mmdb->file_content; const uint8_t *search_tree = mmdb->file_content;
skipping to change at line 1096 skipping to change at line 1076
node->left_record = record_info.left_record_getter(record_pointer); node->left_record = record_info.left_record_getter(record_pointer);
record_pointer += record_info.right_record_offset; record_pointer += record_info.right_record_offset;
node->right_record = record_info.right_record_getter(record_pointer); node->right_record = record_info.right_record_getter(record_pointer);
node->left_record_type = record_type(mmdb, node->left_record); node->left_record_type = record_type(mmdb, node->left_record);
node->right_record_type = record_type(mmdb, node->right_record); node->right_record_type = record_type(mmdb, node->right_record);
// Note that offset will be invalid if the record type is not // 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 // MMDB_RECORD_TYPE_DATA, but that's ok. Any use of the record entry
// for other data types is a programming error. // for other data types is a programming error.
node->left_record_entry = (struct MMDB_entry_s) { node->left_record_entry = (struct MMDB_entry_s){
.mmdb = mmdb, .mmdb = mmdb,
.offset = data_section_offset_for_record(mmdb, node->left_record), .offset = data_section_offset_for_record(mmdb, node->left_record),
}; };
node->right_record_entry = (struct MMDB_entry_s) { node->right_record_entry = (struct MMDB_entry_s){
.mmdb = mmdb, .mmdb = mmdb,
.offset = data_section_offset_for_record(mmdb, node->right_record), .offset = data_section_offset_for_record(mmdb, node->right_record),
}; };
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL uint32_t data_section_offset_for_record(const MMDB_s *const mmdb, static uint32_t data_section_offset_for_record(const MMDB_s *const mmdb,
uint64_t record) uint64_t record) {
{
return (uint32_t)record - mmdb->metadata.node_count - return (uint32_t)record - mmdb->metadata.node_count -
MMDB_DATA_SECTION_SEPARATOR; MMDB_DATA_SECTION_SEPARATOR;
} }
int MMDB_get_value(MMDB_entry_s *const start, int MMDB_get_value(MMDB_entry_s *const start,
MMDB_entry_data_s *const entry_data, MMDB_entry_data_s *const entry_data,
...) ...) {
{
va_list path; va_list path;
va_start(path, entry_data); va_start(path, entry_data);
int status = MMDB_vget_value(start, entry_data, path); int status = MMDB_vget_value(start, entry_data, path);
va_end(path); va_end(path);
return status; return status;
} }
int MMDB_vget_value(MMDB_entry_s *const start, int MMDB_vget_value(MMDB_entry_s *const start,
MMDB_entry_data_s *const entry_data, MMDB_entry_data_s *const entry_data,
va_list va_path) va_list va_path) {
{
int length = path_length(va_path); int length = path_length(va_path);
const char *path_elem; const char *path_elem;
int i = 0; int i = 0;
MAYBE_CHECK_SIZE_OVERFLOW(length, SIZE_MAX / sizeof(const char *) - 1, MAYBE_CHECK_SIZE_OVERFLOW(length,
SIZE_MAX / sizeof(const char *) - 1,
MMDB_INVALID_METADATA_ERROR); MMDB_INVALID_METADATA_ERROR);
const char **path = calloc(length + 1, sizeof(const char *)); const char **path = calloc(length + 1, sizeof(const char *));
if (NULL == path) { if (NULL == path) {
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
while (NULL != (path_elem = va_arg(va_path, char *))) { while (NULL != (path_elem = va_arg(va_path, char *))) {
path[i] = path_elem; path[i] = path_elem;
i++; i++;
} }
path[i] = NULL; path[i] = NULL;
int status = MMDB_aget_value(start, entry_data, path); int status = MMDB_aget_value(start, entry_data, path);
free((char **)path); free((char **)path);
return status; return status;
} }
LOCAL int path_length(va_list va_path) static int path_length(va_list va_path) {
{
int i = 0; int i = 0;
const char *ignore; const char *ignore;
va_list path_copy; va_list path_copy;
va_copy(path_copy, va_path); va_copy(path_copy, va_path);
while (NULL != (ignore = va_arg(path_copy, char *))) { while (NULL != (ignore = va_arg(path_copy, char *))) {
i++; i++;
} }
va_end(path_copy); va_end(path_copy);
return i; return i;
} }
int MMDB_aget_value(MMDB_entry_s *const start, int MMDB_aget_value(MMDB_entry_s *const start,
MMDB_entry_data_s *const entry_data, MMDB_entry_data_s *const entry_data,
const char *const *const path) const char *const *const path) {
{
const MMDB_s *const mmdb = start->mmdb; const MMDB_s *const mmdb = start->mmdb;
uint32_t offset = start->offset; uint32_t offset = start->offset;
memset(entry_data, 0, sizeof(MMDB_entry_data_s)); memset(entry_data, 0, sizeof(MMDB_entry_data_s));
DEBUG_NL; DEBUG_NL;
DEBUG_MSG("looking up value by path"); DEBUG_MSG("looking up value by path");
CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data); CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, entry_data);
DEBUG_NL; DEBUG_NL;
skipping to change at line 1228 skipping to change at line 1204
/* Once we make the code traverse maps & arrays without calling /* Once we make the code traverse maps & arrays without calling
* decode_one() we can get rid of this. */ * decode_one() we can get rid of this. */
memset(entry_data, 0, sizeof(MMDB_entry_data_s)); memset(entry_data, 0, sizeof(MMDB_entry_data_s));
return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR; return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
} }
} }
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int lookup_path_in_array(const char *path_elem, static int lookup_path_in_array(const char *path_elem,
const MMDB_s *const mmdb, const MMDB_s *const mmdb,
MMDB_entry_data_s *entry_data) MMDB_entry_data_s *entry_data) {
{
uint32_t size = entry_data->data_size; uint32_t size = entry_data->data_size;
char *first_invalid; char *first_invalid;
int saved_errno = errno; int saved_errno = errno;
errno = 0; errno = 0;
int array_index = strtol(path_elem, &first_invalid, 10); int array_index = strtol(path_elem, &first_invalid, 10);
if (ERANGE == errno) { if (ERANGE == errno) {
errno = saved_errno; errno = saved_errno;
return MMDB_INVALID_LOOKUP_PATH_ERROR; return MMDB_INVALID_LOOKUP_PATH_ERROR;
} }
skipping to change at line 1273 skipping to change at line 1248
} }
} }
MMDB_entry_data_s value; MMDB_entry_data_s value;
CHECKED_DECODE_ONE_FOLLOW(mmdb, entry_data->offset_to_next, &value); CHECKED_DECODE_ONE_FOLLOW(mmdb, entry_data->offset_to_next, &value);
memcpy(entry_data, &value, sizeof(MMDB_entry_data_s)); memcpy(entry_data, &value, sizeof(MMDB_entry_data_s));
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int lookup_path_in_map(const char *path_elem, static int lookup_path_in_map(const char *path_elem,
const MMDB_s *const mmdb, const MMDB_s *const mmdb,
MMDB_entry_data_s *entry_data) MMDB_entry_data_s *entry_data) {
{
uint32_t size = entry_data->data_size; uint32_t size = entry_data->data_size;
uint32_t offset = entry_data->offset_to_next; uint32_t offset = entry_data->offset_to_next;
size_t path_elem_len = strlen(path_elem); size_t path_elem_len = strlen(path_elem);
while (size-- > 0) { while (size-- > 0) {
MMDB_entry_data_s key, value; MMDB_entry_data_s key, value;
CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, &key); CHECKED_DECODE_ONE_FOLLOW(mmdb, offset, &key);
uint32_t offset_to_value = key.offset_to_next; uint32_t offset_to_value = key.offset_to_next;
skipping to change at line 1315 skipping to change at line 1289
return status; return status;
} }
offset = value.offset_to_next; offset = value.offset_to_next;
} }
} }
memset(entry_data, 0, sizeof(MMDB_entry_data_s)); memset(entry_data, 0, sizeof(MMDB_entry_data_s));
return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR; return MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR;
} }
LOCAL int skip_map_or_array(const MMDB_s *const mmdb, static int skip_map_or_array(const MMDB_s *const mmdb,
MMDB_entry_data_s *entry_data) MMDB_entry_data_s *entry_data) {
{
if (entry_data->type == MMDB_DATA_TYPE_MAP) { if (entry_data->type == MMDB_DATA_TYPE_MAP) {
uint32_t size = entry_data->data_size; uint32_t size = entry_data->data_size;
while (size-- > 0) { while (size-- > 0) {
CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data); CHECKED_DECODE_ONE(
// key mmdb, entry_data->offset_to_next, entry_data); // key
CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data); CHECKED_DECODE_ONE(
// value mmdb, entry_data->offset_to_next, entry_data); // value
int status = skip_map_or_array(mmdb, entry_data); int status = skip_map_or_array(mmdb, entry_data);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
} }
} else if (entry_data->type == MMDB_DATA_TYPE_ARRAY) { } else if (entry_data->type == MMDB_DATA_TYPE_ARRAY) {
uint32_t size = entry_data->data_size; uint32_t size = entry_data->data_size;
while (size-- > 0) { while (size-- > 0) {
CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data); CHECKED_DECODE_ONE(
// value mmdb, entry_data->offset_to_next, entry_data); // value
int status = skip_map_or_array(mmdb, entry_data); int status = skip_map_or_array(mmdb, entry_data);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
return status; return status;
} }
} }
} }
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int decode_one_follow(const MMDB_s *const mmdb, uint32_t offset, static int decode_one_follow(const MMDB_s *const mmdb,
MMDB_entry_data_s *entry_data) uint32_t offset,
{ MMDB_entry_data_s *entry_data) {
CHECKED_DECODE_ONE(mmdb, offset, entry_data); CHECKED_DECODE_ONE(mmdb, offset, entry_data);
if (entry_data->type == MMDB_DATA_TYPE_POINTER) { if (entry_data->type == MMDB_DATA_TYPE_POINTER) {
uint32_t next = entry_data->offset_to_next; uint32_t next = entry_data->offset_to_next;
CHECKED_DECODE_ONE(mmdb, entry_data->pointer, entry_data); CHECKED_DECODE_ONE(mmdb, entry_data->pointer, entry_data);
/* Pointers to pointers are illegal under the spec */ /* Pointers to pointers are illegal under the spec */
if (entry_data->type == MMDB_DATA_TYPE_POINTER) { if (entry_data->type == MMDB_DATA_TYPE_POINTER) {
DEBUG_MSG("pointer points to another pointer"); DEBUG_MSG("pointer points to another pointer");
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
/* The pointer could point to any part of the data section but the /* 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 * next entry for this particular offset may be the one after the
* pointer, not the one after whatever the pointer points to. This * pointer, not the one after whatever the pointer points to. This
* depends on whether the pointer points to something that is a simple * 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 * 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. */ * the one after the pointer result, not the one after the pointer. */
if (entry_data->type != MMDB_DATA_TYPE_MAP if (entry_data->type != MMDB_DATA_TYPE_MAP &&
&& entry_data->type != MMDB_DATA_TYPE_ARRAY) { entry_data->type != MMDB_DATA_TYPE_ARRAY) {
entry_data->offset_to_next = next; entry_data->offset_to_next = next;
} }
} }
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
#if !MMDB_UINT128_IS_BYTE_ARRAY #if !MMDB_UINT128_IS_BYTE_ARRAY
LOCAL mmdb_uint128_t get_uint128(const uint8_t *p, int length) static mmdb_uint128_t get_uint128(const uint8_t *p, int length) {
{
mmdb_uint128_t value = 0; mmdb_uint128_t value = 0;
while (length-- > 0) { while (length-- > 0) {
value <<= 8; value <<= 8;
value += *p++; value += *p++;
} }
return value; return value;
} }
#endif #endif
LOCAL int decode_one(const MMDB_s *const mmdb, uint32_t offset, static int decode_one(const MMDB_s *const mmdb,
MMDB_entry_data_s *entry_data) uint32_t offset,
{ MMDB_entry_data_s *entry_data) {
const uint8_t *mem = mmdb->data_section; const uint8_t *mem = mmdb->data_section;
// We subtract rather than add as it possible that offset + 1 // We subtract rather than add as it possible that offset + 1
// could overflow for a corrupt database while an underflow // could overflow for a corrupt database while an underflow
// from data_section_size - 1 should not be possible. // from data_section_size - 1 should not be possible.
if (offset > mmdb->data_section_size - 1) { if (offset > mmdb->data_section_size - 1) {
DEBUG_MSGF("Offset (%d) past data section (%d)", offset, DEBUG_MSGF("Offset (%d) past data section (%d)",
offset,
mmdb->data_section_size); mmdb->data_section_size);
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
entry_data->offset = offset; entry_data->offset = offset;
entry_data->has_data = true; entry_data->has_data = true;
DEBUG_NL; DEBUG_NL;
DEBUG_MSGF("Offset: %i", offset); DEBUG_MSGF("Offset: %i", offset);
skipping to change at line 1431 skipping to change at line 1407
entry_data->type = type; entry_data->type = type;
if (type == MMDB_DATA_TYPE_POINTER) { if (type == MMDB_DATA_TYPE_POINTER) {
uint8_t psize = ((ctrl >> 3) & 3) + 1; uint8_t psize = ((ctrl >> 3) & 3) + 1;
DEBUG_MSGF("Pointer size: %i", psize); DEBUG_MSGF("Pointer size: %i", psize);
// We check that the offset does not extend past the end of the // We check that the offset does not extend past the end of the
// database and that the subtraction of psize did not underflow. // database and that the subtraction of psize did not underflow.
if (offset > mmdb->data_section_size - psize || if (offset > mmdb->data_section_size - psize ||
mmdb->data_section_size < psize) { mmdb->data_section_size < psize) {
DEBUG_MSGF("Pointer offset (%d) past data section (%d)", offset + DEBUG_MSGF("Pointer offset (%d) past data section (%d)",
psize, offset + psize,
mmdb->data_section_size); mmdb->data_section_size);
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
entry_data->pointer = get_ptr_from(ctrl, &mem[offset], psize); entry_data->pointer = get_ptr_from(ctrl, &mem[offset], psize);
DEBUG_MSGF("Pointer to: %i", entry_data->pointer); DEBUG_MSGF("Pointer to: %i", entry_data->pointer);
entry_data->data_size = psize; entry_data->data_size = psize;
entry_data->offset_to_next = offset + psize; entry_data->offset_to_next = offset + psize;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
uint32_t size = ctrl & 31; uint32_t size = ctrl & 31;
switch (size) { switch (size) {
case 29: case 29:
// We subtract when checking offset to avoid possible overflow // We subtract when checking offset to avoid possible overflow
if (offset > mmdb->data_section_size - 1) { if (offset > mmdb->data_section_size - 1) {
DEBUG_MSGF("String end (%d, case 29) past data section (%d)", DEBUG_MSGF("String end (%d, case 29) past data section (%d)",
offset, offset,
mmdb->data_section_size); mmdb->data_section_size);
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
size = 29 + mem[offset++]; size = 29 + mem[offset++];
break; break;
case 30: case 30:
// We subtract when checking offset to avoid possible overflow // We subtract when checking offset to avoid possible overflow
if (offset > mmdb->data_section_size - 2) { if (offset > mmdb->data_section_size - 2) {
DEBUG_MSGF("String end (%d, case 30) past data section (%d)", DEBUG_MSGF("String end (%d, case 30) past data section (%d)",
offset, offset,
mmdb->data_section_size); mmdb->data_section_size);
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
size = 285 + get_uint16(&mem[offset]); size = 285 + get_uint16(&mem[offset]);
offset += 2; offset += 2;
break; break;
case 31: case 31:
// We subtract when checking offset to avoid possible overflow // We subtract when checking offset to avoid possible overflow
if (offset > mmdb->data_section_size - 3) { if (offset > mmdb->data_section_size - 3) {
DEBUG_MSGF("String end (%d, case 31) past data section (%d)", DEBUG_MSGF("String end (%d, case 31) past data section (%d)",
offset, offset,
mmdb->data_section_size); mmdb->data_section_size);
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
size = 65821 + get_uint24(&mem[offset]); size = 65821 + get_uint24(&mem[offset]);
offset += 3; offset += 3;
default: default:
break; break;
} }
DEBUG_MSGF("Size: %i", size); DEBUG_MSGF("Size: %i", size);
if (type == MMDB_DATA_TYPE_MAP || type == MMDB_DATA_TYPE_ARRAY) { if (type == MMDB_DATA_TYPE_MAP || type == MMDB_DATA_TYPE_ARRAY) {
entry_data->data_size = size; entry_data->data_size = size;
entry_data->offset_to_next = offset; entry_data->offset_to_next = offset;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
skipping to change at line 1501 skipping to change at line 1477
entry_data->data_size = 0; entry_data->data_size = 0;
entry_data->offset_to_next = offset; entry_data->offset_to_next = offset;
DEBUG_MSGF("boolean value: %s", entry_data->boolean ? "true" : "false"); DEBUG_MSGF("boolean value: %s", entry_data->boolean ? "true" : "false");
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
// Check that the data doesn't extend past the end of the memory // Check that the data doesn't extend past the end of the memory
// buffer and that the calculation in doing this did not underflow. // buffer and that the calculation in doing this did not underflow.
if (offset > mmdb->data_section_size - size || if (offset > mmdb->data_section_size - size ||
mmdb->data_section_size < size) { mmdb->data_section_size < size) {
DEBUG_MSGF("Data end (%d) past data section (%d)", offset + size, DEBUG_MSGF("Data end (%d) past data section (%d)",
offset + size,
mmdb->data_section_size); mmdb->data_section_size);
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
if (type == MMDB_DATA_TYPE_UINT16) { if (type == MMDB_DATA_TYPE_UINT16) {
if (size > 2) { if (size > 2) {
DEBUG_MSGF("uint16 of size %d", size); DEBUG_MSGF("uint16 of size %d", size);
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
entry_data->uint16 = (uint16_t)get_uintX(&mem[offset], size); entry_data->uint16 = (uint16_t)get_uintX(&mem[offset], size);
skipping to change at line 1567 skipping to change at line 1544
DEBUG_MSGF("double of size %d", size); DEBUG_MSGF("double of size %d", size);
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
size = 8; size = 8;
entry_data->double_value = get_ieee754_double(&mem[offset]); entry_data->double_value = get_ieee754_double(&mem[offset]);
DEBUG_MSGF("double value: %f", entry_data->double_value); DEBUG_MSGF("double value: %f", entry_data->double_value);
} else if (type == MMDB_DATA_TYPE_UTF8_STRING) { } else if (type == MMDB_DATA_TYPE_UTF8_STRING) {
entry_data->utf8_string = size == 0 ? "" : (char *)&mem[offset]; entry_data->utf8_string = size == 0 ? "" : (char *)&mem[offset];
entry_data->data_size = size; entry_data->data_size = size;
#ifdef MMDB_DEBUG #ifdef MMDB_DEBUG
char *string = mmdb_strndup(entry_data->utf8_string, char *string =
size > 50 ? 50 : size); mmdb_strndup(entry_data->utf8_string, size > 50 ? 50 : size);
if (NULL == string) { if (NULL == string) {
abort(); abort();
} }
DEBUG_MSGF("string value: %s", string); DEBUG_MSGF("string value: %s", string);
free(string); free(string);
#endif #endif
} else if (type == MMDB_DATA_TYPE_BYTES) { } else if (type == MMDB_DATA_TYPE_BYTES) {
entry_data->bytes = &mem[offset]; entry_data->bytes = &mem[offset];
entry_data->data_size = size; entry_data->data_size = size;
} }
entry_data->offset_to_next = offset + size; entry_data->offset_to_next = offset + size;
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL int get_ext_type(int raw_ext_type) static int get_ext_type(int raw_ext_type) { return 7 + raw_ext_type; }
{
return 7 + raw_ext_type;
}
LOCAL uint32_t get_ptr_from(uint8_t ctrl, uint8_t const *const ptr, static uint32_t
int ptr_size) get_ptr_from(uint8_t ctrl, uint8_t const *const ptr, int ptr_size) {
{
uint32_t new_offset; uint32_t new_offset;
switch (ptr_size) { switch (ptr_size) {
case 1: case 1:
new_offset = ( (ctrl & 7) << 8) + ptr[0]; new_offset = ((ctrl & 7) << 8) + ptr[0];
break; break;
case 2: case 2:
new_offset = 2048 + ( (ctrl & 7) << 16 ) + ( ptr[0] << 8) + ptr[1]; new_offset = 2048 + ((ctrl & 7) << 16) + (ptr[0] << 8) + ptr[1];
break; break;
case 3: case 3:
new_offset = 2048 + 524288 + ( (ctrl & 7) << 24 ) + get_uint24(ptr); new_offset = 2048 + 524288 + ((ctrl & 7) << 24) + get_uint24(ptr);
break; break;
case 4: case 4:
default: default:
new_offset = get_uint32(ptr); new_offset = get_uint32(ptr);
break; break;
} }
return new_offset; return new_offset;
} }
int MMDB_get_metadata_as_entry_data_list( int MMDB_get_metadata_as_entry_data_list(
const MMDB_s *const mmdb, MMDB_entry_data_list_s **const 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_s metadata_db = make_fake_metadata_db(mmdb);
MMDB_entry_s metadata_start = { MMDB_entry_s metadata_start = {.mmdb = &metadata_db, .offset = 0};
.mmdb = &metadata_db,
.offset = 0
};
return MMDB_get_entry_data_list(&metadata_start, entry_data_list); return MMDB_get_entry_data_list(&metadata_start, entry_data_list);
} }
int MMDB_get_entry_data_list( int MMDB_get_entry_data_list(MMDB_entry_s *start,
MMDB_entry_s *start, MMDB_entry_data_list_s **const entry_data_list) MMDB_entry_data_list_s **const entry_data_list) {
{
MMDB_data_pool_s *const pool = data_pool_new(MMDB_POOL_INIT_SIZE); MMDB_data_pool_s *const pool = data_pool_new(MMDB_POOL_INIT_SIZE);
if (!pool) { if (!pool) {
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
MMDB_entry_data_list_s *const list = data_pool_alloc(pool); MMDB_entry_data_list_s *const list = data_pool_alloc(pool);
if (!list) { if (!list) {
data_pool_destroy(pool); data_pool_destroy(pool);
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
int const status = get_entry_data_list(start->mmdb, start->offset, list, int const status =
pool, 0); get_entry_data_list(start->mmdb, start->offset, list, pool, 0);
*entry_data_list = data_pool_to_list(pool); *entry_data_list = data_pool_to_list(pool);
if (!*entry_data_list) { if (!*entry_data_list) {
data_pool_destroy(pool); data_pool_destroy(pool);
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
return status; return status;
} }
LOCAL int get_entry_data_list(const MMDB_s *const mmdb, static int get_entry_data_list(const MMDB_s *const mmdb,
uint32_t offset, uint32_t offset,
MMDB_entry_data_list_s *const entry_data_list, MMDB_entry_data_list_s *const entry_data_list,
MMDB_data_pool_s *const pool, MMDB_data_pool_s *const pool,
int depth) int depth) {
{
if (depth >= MAXIMUM_DATA_STRUCTURE_DEPTH) { if (depth >= MAXIMUM_DATA_STRUCTURE_DEPTH) {
DEBUG_MSG("reached the maximum data structure depth"); DEBUG_MSG("reached the maximum data structure depth");
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
depth++; depth++;
CHECKED_DECODE_ONE(mmdb, offset, &entry_data_list->entry_data); CHECKED_DECODE_ONE(mmdb, offset, &entry_data_list->entry_data);
switch (entry_data_list->entry_data.type) { switch (entry_data_list->entry_data.type) {
case MMDB_DATA_TYPE_POINTER: case MMDB_DATA_TYPE_POINTER: {
{
uint32_t next_offset = entry_data_list->entry_data.offset_to_next; uint32_t next_offset = entry_data_list->entry_data.offset_to_next;
uint32_t last_offset; uint32_t last_offset;
CHECKED_DECODE_ONE(mmdb, last_offset = CHECKED_DECODE_ONE(mmdb,
last_offset =
entry_data_list->entry_data.pointer, entry_data_list->entry_data.pointer,
&entry_data_list->entry_data); &entry_data_list->entry_data);
/* Pointers to pointers are illegal under the spec */ /* Pointers to pointers are illegal under the spec */
if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_POINTER) { if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_POINTER) {
DEBUG_MSG("pointer points to another pointer"); DEBUG_MSG("pointer points to another pointer");
return MMDB_INVALID_DATA_ERROR; return MMDB_INVALID_DATA_ERROR;
} }
if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_ARRAY if (entry_data_list->entry_data.type == MMDB_DATA_TYPE_ARRAY ||
|| entry_data_list->entry_data.type == MMDB_DATA_TYPE_MAP) { entry_data_list->entry_data.type == MMDB_DATA_TYPE_MAP) {
int status = int status = get_entry_data_list(
get_entry_data_list(mmdb, last_offset, entry_data_list, mmdb, last_offset, entry_data_list, pool, depth);
pool, depth);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
DEBUG_MSG("get_entry_data_list on pointer failed."); DEBUG_MSG("get_entry_data_list on pointer failed.");
return status; return status;
} }
} }
entry_data_list->entry_data.offset_to_next = next_offset; entry_data_list->entry_data.offset_to_next = next_offset;
} } break;
break; case MMDB_DATA_TYPE_ARRAY: {
case MMDB_DATA_TYPE_ARRAY:
{
uint32_t array_size = entry_data_list->entry_data.data_size; uint32_t array_size = entry_data_list->entry_data.data_size;
uint32_t array_offset = entry_data_list->entry_data.offset_to_next; uint32_t array_offset = entry_data_list->entry_data.offset_to_next;
while (array_size-- > 0) { while (array_size-- > 0) {
MMDB_entry_data_list_s *entry_data_list_to = MMDB_entry_data_list_s *entry_data_list_to =
data_pool_alloc(pool); data_pool_alloc(pool);
if (!entry_data_list_to) { if (!entry_data_list_to) {
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
int status = int status = get_entry_data_list(
get_entry_data_list(mmdb, array_offset, entry_data_list_to, mmdb, array_offset, entry_data_list_to, pool, depth);
pool, depth);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
DEBUG_MSG("get_entry_data_list on array element failed."); DEBUG_MSG("get_entry_data_list on array element failed.");
return status; return status;
} }
array_offset = entry_data_list_to->entry_data.offset_to_next; array_offset = entry_data_list_to->entry_data.offset_to_next;
} }
entry_data_list->entry_data.offset_to_next = array_offset; entry_data_list->entry_data.offset_to_next = array_offset;
} } break;
break; case MMDB_DATA_TYPE_MAP: {
case MMDB_DATA_TYPE_MAP:
{
uint32_t size = entry_data_list->entry_data.data_size; uint32_t size = entry_data_list->entry_data.data_size;
offset = entry_data_list->entry_data.offset_to_next; offset = entry_data_list->entry_data.offset_to_next;
while (size-- > 0) { while (size-- > 0) {
MMDB_entry_data_list_s *list_key = data_pool_alloc(pool); MMDB_entry_data_list_s *list_key = data_pool_alloc(pool);
if (!list_key) { if (!list_key) {
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
int status = int status =
skipping to change at line 1743 skipping to change at line 1704
return status; return status;
} }
offset = list_key->entry_data.offset_to_next; offset = list_key->entry_data.offset_to_next;
MMDB_entry_data_list_s *list_value = data_pool_alloc(pool); MMDB_entry_data_list_s *list_value = data_pool_alloc(pool);
if (!list_value) { if (!list_value) {
return MMDB_OUT_OF_MEMORY_ERROR; return MMDB_OUT_OF_MEMORY_ERROR;
} }
status = get_entry_data_list(mmdb, offset, list_value, pool, status =
depth); get_entry_data_list(mmdb, offset, list_value, pool, depth);
if (MMDB_SUCCESS != status) { if (MMDB_SUCCESS != status) {
DEBUG_MSG("get_entry_data_list on map element failed."); DEBUG_MSG("get_entry_data_list on map element failed.");
return status; return status;
} }
offset = list_value->entry_data.offset_to_next; offset = list_value->entry_data.offset_to_next;
} }
entry_data_list->entry_data.offset_to_next = offset; entry_data_list->entry_data.offset_to_next = offset;
} } break;
break; default:
default: break;
break;
} }
return MMDB_SUCCESS; return MMDB_SUCCESS;
} }
LOCAL float get_ieee754_float(const uint8_t *restrict p) static float get_ieee754_float(const uint8_t *restrict p) {
{
volatile float f; volatile float f;
uint8_t *q = (void *)&f; uint8_t *q = (void *)&f;
/* Windows builds don't use autoconf but we can assume they're all /* Windows builds don't use autoconf but we can assume they're all
* little-endian. */ * little-endian. */
#if MMDB_LITTLE_ENDIAN || _WIN32 #if MMDB_LITTLE_ENDIAN || _WIN32
q[3] = p[0]; q[3] = p[0];
q[2] = p[1]; q[2] = p[1];
q[1] = p[2]; q[1] = p[2];
q[0] = p[3]; q[0] = p[3];
#else #else
memcpy(q, p, 4); memcpy(q, p, 4);
#endif #endif
return f; return f;
} }
LOCAL double get_ieee754_double(const uint8_t *restrict p) static double get_ieee754_double(const uint8_t *restrict p) {
{
volatile double d; volatile double d;
uint8_t *q = (void *)&d; uint8_t *q = (void *)&d;
#if MMDB_LITTLE_ENDIAN || _WIN32 #if MMDB_LITTLE_ENDIAN || _WIN32
q[7] = p[0]; q[7] = p[0];
q[6] = p[1]; q[6] = p[1];
q[5] = p[2]; q[5] = p[2];
q[4] = p[3]; q[4] = p[3];
q[3] = p[4]; q[3] = p[4];
q[2] = p[5]; q[2] = p[5];
q[1] = p[6]; q[1] = p[6];
q[0] = p[7]; q[0] = p[7];
#else #else
memcpy(q, p, 8); memcpy(q, p, 8);
#endif #endif
return d; return d;
} }
LOCAL uint32_t get_uint32(const uint8_t *p) static uint32_t get_uint32(const uint8_t *p) {
{
return p[0] * 16777216U + p[1] * 65536 + p[2] * 256 + p[3]; return p[0] * 16777216U + p[1] * 65536 + p[2] * 256 + p[3];
} }
LOCAL uint32_t get_uint24(const uint8_t *p) static uint32_t get_uint24(const uint8_t *p) {
{
return p[0] * 65536U + p[1] * 256 + p[2]; return p[0] * 65536U + p[1] * 256 + p[2];
} }
LOCAL uint32_t get_uint16(const uint8_t *p) static uint32_t get_uint16(const uint8_t *p) { return p[0] * 256U + p[1]; }
{
return p[0] * 256U + p[1];
}
LOCAL uint64_t get_uintX(const uint8_t *p, int length) static uint64_t get_uintX(const uint8_t *p, int length) {
{
uint64_t value = 0; uint64_t value = 0;
while (length-- > 0) { while (length-- > 0) {
value <<= 8; value <<= 8;
value += *p++; value += *p++;
} }
return value; return value;
} }
LOCAL int32_t get_sintX(const uint8_t *p, int length) static int32_t get_sintX(const uint8_t *p, int length) {
{
return (int32_t)get_uintX(p, length); return (int32_t)get_uintX(p, length);
} }
void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list) void MMDB_free_entry_data_list(MMDB_entry_data_list_s *const entry_data_list) {
{
if (entry_data_list == NULL) { if (entry_data_list == NULL) {
return; return;
} }
data_pool_destroy(entry_data_list->pool); data_pool_destroy(entry_data_list->pool);
} }
void MMDB_close(MMDB_s *const mmdb) void MMDB_close(MMDB_s *const mmdb) { free_mmdb_struct(mmdb); }
{
free_mmdb_struct(mmdb);
}
LOCAL void free_mmdb_struct(MMDB_s *const mmdb) static void free_mmdb_struct(MMDB_s *const mmdb) {
{
if (!mmdb) { if (!mmdb) {
return; return;
} }
if (NULL != mmdb->filename) { if (NULL != mmdb->filename) {
FREE_AND_SET_NULL(mmdb->filename); FREE_AND_SET_NULL(mmdb->filename);
} }
if (NULL != mmdb->file_content) { if (NULL != mmdb->file_content) {
#ifdef _WIN32 #ifdef _WIN32
UnmapViewOfFile(mmdb->file_content); UnmapViewOfFile(mmdb->file_content);
skipping to change at line 1869 skipping to change at line 1815
} }
if (NULL != mmdb->metadata.database_type) { if (NULL != mmdb->metadata.database_type) {
FREE_AND_SET_NULL(mmdb->metadata.database_type); FREE_AND_SET_NULL(mmdb->metadata.database_type);
} }
free_languages_metadata(mmdb); free_languages_metadata(mmdb);
free_descriptions_metadata(mmdb); free_descriptions_metadata(mmdb);
} }
LOCAL void free_languages_metadata(MMDB_s *mmdb) static void free_languages_metadata(MMDB_s *mmdb) {
{
if (!mmdb->metadata.languages.names) { if (!mmdb->metadata.languages.names) {
return; return;
} }
for (size_t i = 0; i < mmdb->metadata.languages.count; i++) { 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[i]);
} }
FREE_AND_SET_NULL(mmdb->metadata.languages.names); FREE_AND_SET_NULL(mmdb->metadata.languages.names);
} }
LOCAL void free_descriptions_metadata(MMDB_s *mmdb) static void free_descriptions_metadata(MMDB_s *mmdb) {
{
if (!mmdb->metadata.description.count) { if (!mmdb->metadata.description.count) {
return; return;
} }
for (size_t i = 0; i < mmdb->metadata.description.count; i++) { 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]) {
if (NULL != if (NULL != mmdb->metadata.description.descriptions[i]->language) {
mmdb->metadata.description.descriptions[i]->language) {
FREE_AND_SET_NULL( FREE_AND_SET_NULL(
mmdb->metadata.description.descriptions[i]->language); mmdb->metadata.description.descriptions[i]->language);
} }
if (NULL != if (NULL !=
mmdb->metadata.description.descriptions[i]->description) { mmdb->metadata.description.descriptions[i]->description) {
FREE_AND_SET_NULL( FREE_AND_SET_NULL(
mmdb->metadata.description.descriptions[i]->description); mmdb->metadata.description.descriptions[i]->description);
} }
FREE_AND_SET_NULL(mmdb->metadata.description.descriptions[i]); FREE_AND_SET_NULL(mmdb->metadata.description.descriptions[i]);
} }
} }
FREE_AND_SET_NULL(mmdb->metadata.description.descriptions); FREE_AND_SET_NULL(mmdb->metadata.description.descriptions);
} }
const char *MMDB_lib_version(void) const char *MMDB_lib_version(void) { return PACKAGE_VERSION; }
{
return PACKAGE_VERSION;
}
int MMDB_dump_entry_data_list(FILE *const stream, int MMDB_dump_entry_data_list(FILE *const stream,
MMDB_entry_data_list_s *const entry_data_list, MMDB_entry_data_list_s *const entry_data_list,
int indent) int indent) {
{
int status; int status;
dump_entry_data_list(stream, entry_data_list, indent, &status); dump_entry_data_list(stream, entry_data_list, indent, &status);
return status; return status;
} }
LOCAL MMDB_entry_data_list_s *dump_entry_data_list( static MMDB_entry_data_list_s *
FILE *stream, MMDB_entry_data_list_s *entry_data_list, int indent, dump_entry_data_list(FILE *stream,
int *status) MMDB_entry_data_list_s *entry_data_list,
{ int indent,
int *status) {
switch (entry_data_list->entry_data.type) { switch (entry_data_list->entry_data.type) {
case MMDB_DATA_TYPE_MAP: case MMDB_DATA_TYPE_MAP: {
{
uint32_t size = entry_data_list->entry_data.data_size; uint32_t size = entry_data_list->entry_data.data_size;
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "{\n"); fprintf(stream, "{\n");
indent += 2; indent += 2;
for (entry_data_list = entry_data_list->next; for (entry_data_list = entry_data_list->next;
size && entry_data_list; size--) { size && entry_data_list;
size--) {
if (MMDB_DATA_TYPE_UTF8_STRING != if (MMDB_DATA_TYPE_UTF8_STRING !=
entry_data_list->entry_data.type) { entry_data_list->entry_data.type) {
*status = MMDB_INVALID_DATA_ERROR; *status = MMDB_INVALID_DATA_ERROR;
return NULL; return NULL;
} }
char *key = char *key = mmdb_strndup(
mmdb_strndup( (char *)entry_data_list->entry_data.utf8_string,
(char *)entry_data_list->entry_data.utf8_string, entry_data_list->entry_data.data_size);
entry_data_list->entry_data.data_size);
if (NULL == key) { if (NULL == key) {
*status = MMDB_OUT_OF_MEMORY_ERROR; *status = MMDB_OUT_OF_MEMORY_ERROR;
return NULL; return NULL;
} }
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "\"%s\": \n", key); fprintf(stream, "\"%s\": \n", key);
free(key); free(key);
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
entry_data_list = entry_data_list = dump_entry_data_list(
dump_entry_data_list(stream, entry_data_list, indent + 2, stream, entry_data_list, indent + 2, status);
status);
if (MMDB_SUCCESS != *status) { if (MMDB_SUCCESS != *status) {
return NULL; return NULL;
} }
} }
indent -= 2; indent -= 2;
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "}\n"); fprintf(stream, "}\n");
} } break;
break; case MMDB_DATA_TYPE_ARRAY: {
case MMDB_DATA_TYPE_ARRAY:
{
uint32_t size = entry_data_list->entry_data.data_size; uint32_t size = entry_data_list->entry_data.data_size;
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "[\n"); fprintf(stream, "[\n");
indent += 2; indent += 2;
for (entry_data_list = entry_data_list->next; for (entry_data_list = entry_data_list->next;
size && entry_data_list; size--) { size && entry_data_list;
entry_data_list = size--) {
dump_entry_data_list(stream, entry_data_list, indent, entry_data_list = dump_entry_data_list(
status); stream, entry_data_list, indent, status);
if (MMDB_SUCCESS != *status) { if (MMDB_SUCCESS != *status) {
return NULL; return NULL;
} }
} }
indent -= 2; indent -= 2;
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "]\n"); fprintf(stream, "]\n");
} } break;
break; case MMDB_DATA_TYPE_UTF8_STRING: {
case MMDB_DATA_TYPE_UTF8_STRING:
{
char *string = char *string =
mmdb_strndup((char *)entry_data_list->entry_data.utf8_string, mmdb_strndup((char *)entry_data_list->entry_data.utf8_string,
entry_data_list->entry_data.data_size); entry_data_list->entry_data.data_size);
if (NULL == string) { if (NULL == string) {
*status = MMDB_OUT_OF_MEMORY_ERROR; *status = MMDB_OUT_OF_MEMORY_ERROR;
return NULL; return NULL;
} }
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "\"%s\" <utf8_string>\n", string); fprintf(stream, "\"%s\" <utf8_string>\n", string);
free(string); free(string);
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
} } break;
break; case MMDB_DATA_TYPE_BYTES: {
case MMDB_DATA_TYPE_BYTES:
{
char *hex_string = char *hex_string =
bytes_to_hex((uint8_t *)entry_data_list->entry_data.bytes, bytes_to_hex((uint8_t *)entry_data_list->entry_data.bytes,
entry_data_list->entry_data.data_size); entry_data_list->entry_data.data_size);
if (NULL == hex_string) { if (NULL == hex_string) {
*status = MMDB_OUT_OF_MEMORY_ERROR; *status = MMDB_OUT_OF_MEMORY_ERROR;
return NULL; return NULL;
} }
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "%s <bytes>\n", hex_string); fprintf(stream, "%s <bytes>\n", hex_string);
free(hex_string); free(hex_string);
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
} } break;
break; case MMDB_DATA_TYPE_DOUBLE:
case MMDB_DATA_TYPE_DOUBLE: print_indentation(stream, indent);
print_indentation(stream, indent); fprintf(stream,
fprintf(stream, "%f <double>\n", "%f <double>\n",
entry_data_list->entry_data.double_value); entry_data_list->entry_data.double_value);
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
break; break;
case MMDB_DATA_TYPE_FLOAT: case MMDB_DATA_TYPE_FLOAT:
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "%f <float>\n", fprintf(stream,
entry_data_list->entry_data.float_value); "%f <float>\n",
entry_data_list = entry_data_list->next; entry_data_list->entry_data.float_value);
break; entry_data_list = entry_data_list->next;
case MMDB_DATA_TYPE_UINT16: break;
print_indentation(stream, indent); case MMDB_DATA_TYPE_UINT16:
fprintf(stream, "%u <uint16>\n", entry_data_list->entry_data.uint16); print_indentation(stream, indent);
entry_data_list = entry_data_list->next; fprintf(
break; stream, "%u <uint16>\n", entry_data_list->entry_data.uint16);
case MMDB_DATA_TYPE_UINT32: entry_data_list = entry_data_list->next;
print_indentation(stream, indent); break;
fprintf(stream, "%u <uint32>\n", entry_data_list->entry_data.uint32); case MMDB_DATA_TYPE_UINT32:
entry_data_list = entry_data_list->next; print_indentation(stream, indent);
break; fprintf(
case MMDB_DATA_TYPE_BOOLEAN: stream, "%u <uint32>\n", entry_data_list->entry_data.uint32);
print_indentation(stream, indent); entry_data_list = entry_data_list->next;
fprintf(stream, "%s <boolean>\n", break;
entry_data_list->entry_data.boolean ? "true" : "false"); case MMDB_DATA_TYPE_BOOLEAN:
entry_data_list = entry_data_list->next; print_indentation(stream, indent);
break; fprintf(stream,
case MMDB_DATA_TYPE_UINT64: "%s <boolean>\n",
print_indentation(stream, indent); entry_data_list->entry_data.boolean ? "true" : "false");
fprintf(stream, "%" PRIu64 " <uint64>\n", entry_data_list = entry_data_list->next;
entry_data_list->entry_data.uint64); break;
entry_data_list = entry_data_list->next; case MMDB_DATA_TYPE_UINT64:
break; print_indentation(stream, indent);
case MMDB_DATA_TYPE_UINT128: fprintf(stream,
print_indentation(stream, indent); "%" 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 #if MMDB_UINT128_IS_BYTE_ARRAY
char *hex_string = char *hex_string = bytes_to_hex(
bytes_to_hex((uint8_t *)entry_data_list->entry_data.uint128, 16); (uint8_t *)entry_data_list->entry_data.uint128, 16);
if (NULL == hex_string) { if (NULL == hex_string) {
*status = MMDB_OUT_OF_MEMORY_ERROR; *status = MMDB_OUT_OF_MEMORY_ERROR;
return NULL; return NULL;
} }
fprintf(stream, "0x%s <uint128>\n", hex_string); fprintf(stream, "0x%s <uint128>\n", hex_string);
free(hex_string); free(hex_string);
#else #else
uint64_t high = entry_data_list->entry_data.uint128 >> 64; uint64_t high = entry_data_list->entry_data.uint128 >> 64;
uint64_t low = (uint64_t)entry_data_list->entry_data.uint128; uint64_t low = (uint64_t)entry_data_list->entry_data.uint128;
fprintf(stream, "0x%016" PRIX64 "%016" PRIX64 " <uint128>\n", high, fprintf(stream,
low); "0x%016" PRIX64 "%016" PRIX64 " <uint128>\n",
high,
low);
#endif #endif
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
break; break;
case MMDB_DATA_TYPE_INT32: case MMDB_DATA_TYPE_INT32:
print_indentation(stream, indent); print_indentation(stream, indent);
fprintf(stream, "%d <int32>\n", entry_data_list->entry_data.int32); fprintf(stream, "%d <int32>\n", entry_data_list->entry_data.int32);
entry_data_list = entry_data_list->next; entry_data_list = entry_data_list->next;
break; break;
default: default:
*status = MMDB_INVALID_DATA_ERROR; *status = MMDB_INVALID_DATA_ERROR;
return NULL; return NULL;
} }
*status = MMDB_SUCCESS; *status = MMDB_SUCCESS;
return entry_data_list; return entry_data_list;
} }
LOCAL void print_indentation(FILE *stream, int i) static void print_indentation(FILE *stream, int i) {
{
char buffer[1024]; char buffer[1024];
int size = i >= 1024 ? 1023 : i; int size = i >= 1024 ? 1023 : i;
memset(buffer, 32, size); memset(buffer, 32, size);
buffer[size] = '\0'; buffer[size] = '\0';
fputs(buffer, stream); fputs(buffer, stream);
} }
LOCAL char *bytes_to_hex(uint8_t *bytes, uint32_t size) static char *bytes_to_hex(uint8_t *bytes, uint32_t size) {
{
char *hex_string; char *hex_string;
MAYBE_CHECK_SIZE_OVERFLOW(size, SIZE_MAX / 2 - 1, NULL); MAYBE_CHECK_SIZE_OVERFLOW(size, SIZE_MAX / 2 - 1, NULL);
hex_string = calloc((size * 2) + 1, sizeof(char)); hex_string = calloc((size * 2) + 1, sizeof(char));
if (NULL == hex_string) { if (NULL == hex_string) {
return NULL; return NULL;
} }
for (uint32_t i = 0; i < size; i++) { for (uint32_t i = 0; i < size; i++) {
sprintf(hex_string + (2 * i), "%02X", bytes[i]); sprintf(hex_string + (2 * i), "%02X", bytes[i]);
} }
return hex_string; return hex_string;
} }
const char *MMDB_strerror(int error_code) const char *MMDB_strerror(int error_code) {
{
switch (error_code) { switch (error_code) {
case MMDB_SUCCESS: case MMDB_SUCCESS:
return "Success (not an error)"; return "Success (not an error)";
case MMDB_FILE_OPEN_ERROR: case MMDB_FILE_OPEN_ERROR:
return "Error opening the specified MaxMind DB file"; return "Error opening the specified MaxMind DB file";
case MMDB_CORRUPT_SEARCH_TREE_ERROR: case MMDB_CORRUPT_SEARCH_TREE_ERROR:
return "The MaxMind DB file's search tree is corrupt"; return "The MaxMind DB file's search tree is corrupt";
case MMDB_INVALID_METADATA_ERROR: case MMDB_INVALID_METADATA_ERROR:
return "The MaxMind DB file contains invalid metadata"; return "The MaxMind DB file contains invalid metadata";
case MMDB_IO_ERROR: case MMDB_IO_ERROR:
return "An attempt to read data from the MaxMind DB file failed"; return "An attempt to read data from the MaxMind DB file failed";
case MMDB_OUT_OF_MEMORY_ERROR: case MMDB_OUT_OF_MEMORY_ERROR:
return "A memory allocation call failed"; return "A memory allocation call failed";
case MMDB_UNKNOWN_DATABASE_FORMAT_ERROR: case MMDB_UNKNOWN_DATABASE_FORMAT_ERROR:
return return "The MaxMind DB file is in a format this library can't "
"The MaxMind DB file is in a format this library can't handle (unkno "handle (unknown record size or binary format version)";
wn record size or binary format version)"; case MMDB_INVALID_DATA_ERROR:
case MMDB_INVALID_DATA_ERROR: return "The MaxMind DB file's data section contains bad data "
return "(unknown data type or corrupt data)";
"The MaxMind DB file's data section contains bad data (unknown data case MMDB_INVALID_LOOKUP_PATH_ERROR:
type or corrupt data)"; return "The lookup path contained an invalid value (like a "
case MMDB_INVALID_LOOKUP_PATH_ERROR: "negative integer for an array index)";
return case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR:
"The lookup path contained an invalid value (like a negative integer return "The lookup path does not match the data (key that doesn't "
for an array index)"; "exist, array index bigger than the array, expected array "
case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR: "or map where none exists)";
return case MMDB_INVALID_NODE_NUMBER_ERROR:
"The lookup path does not match the data (key that doesn't exist, ar return "The MMDB_read_node function was called with a node number "
ray index bigger than the array, expected array or map where none exists)"; "that does not exist in the search tree";
case MMDB_INVALID_NODE_NUMBER_ERROR: case MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR:
return return "You attempted to look up an IPv6 address in an IPv4-only "
"The MMDB_read_node function was called with a node number that does "database";
not exist in the search tree"; default:
case MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR: return "Unknown error code";
return
"You attempted to look up an IPv6 address in an IPv4-only database";
default:
return "Unknown error code";
} }
} }
 End of changes. 151 change blocks. 
618 lines changed or deleted 546 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)