/* $Id$ * W32 disk modules identifier for libcaptive and its clients * Copyright (C) 2003-2005 Jan Kratochvil * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; exactly version 2 of June 1991 is required * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "captive/captivemodid.h" /* self */ #include "captive/macros.h" #include "captive/libxml.h" #include #include #include #include #include #include #include #include #include #include #include struct _CaptiveCaptivemodidObject { GObject parent_instance; /* map: GINT_TO_POINTER(captive_captivemodid_module.length) -> !=NULL */ /* No allocations needed. */ GHashTable *module_valid_length_hash; /* map: (const xmlChar *)md5 -> (struct captive_captivemodid_module *) */ /* 'key' is not allocated it is shared with: 'val'->md5 */ /* 'val' is allocated, to be automatically freed by: captive_captivemodid_module_free() */ GHashTable *module_md5_hash; /* map: (const xmlChar *)type -> (gpointer)GINT_TO_POINTER(priority) */ /* We remove entry for module with already the best priority found, * therefore captive_captivemodid_module_type_best_priority_lookup() will return * 'G_MININT' afterwards. * 'key' is not allocated - it is shared with: module_md5_hash */ /* No allocations needed. */ GHashTable *module_type_best_priority_hash; /* g_strdup()ped */ gchar *pathname_loaded; }; struct _CaptiveCaptivemodidObjectClass { GObjectClass parent_class; }; static gpointer captive_captivemodid_object_parent_class=NULL; static void captive_captivemodid_object_finalize(CaptiveCaptivemodidObject *captive_captivemodid_object) { g_return_if_fail(captive_captivemodid_object!=NULL); g_hash_table_destroy(captive_captivemodid_object->module_valid_length_hash); g_hash_table_destroy(captive_captivemodid_object->module_md5_hash); g_hash_table_destroy(captive_captivemodid_object->module_type_best_priority_hash); g_free(captive_captivemodid_object->pathname_loaded); G_OBJECT_CLASS(captive_captivemodid_object_parent_class)->finalize((GObject *)captive_captivemodid_object); } static void captive_captivemodid_object_class_init(CaptiveCaptivemodidObjectClass *class) { GObjectClass *gobject_class=G_OBJECT_CLASS(class); captive_captivemodid_object_parent_class=g_type_class_ref(g_type_parent(G_TYPE_FROM_CLASS(class))); gobject_class->finalize=(void (*)(GObject *object))captive_captivemodid_object_finalize; } static void captive_captivemodid_module_free(struct captive_captivemodid_module *module) { xmlFree((xmlChar *)module->type); xmlFree((xmlChar *)module->md5); xmlFree((xmlChar *)module->id); g_free(module); } static void captive_captivemodid_object_init(CaptiveCaptivemodidObject *captive_captivemodid_object) { captive_captivemodid_object->module_valid_length_hash=g_hash_table_new(g_direct_hash,g_direct_equal); captive_captivemodid_object->module_md5_hash=g_hash_table_new_full(g_str_hash,g_str_equal, NULL, /* key_destroy_func */ (GDestroyNotify)captive_captivemodid_module_free); /* value_destroy_func */ captive_captivemodid_object->module_type_best_priority_hash=g_hash_table_new(g_str_hash,g_str_equal); } GType captive_captivemodid_object_get_type(void) { static GType captive_captivemodid_object_type=0; if (!captive_captivemodid_object_type) { static const GTypeInfo captive_captivemodid_object_info={ sizeof(CaptiveCaptivemodidObjectClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc)captive_captivemodid_object_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof(CaptiveCaptivemodidObject), 5, /* n_preallocs */ (GInstanceInitFunc)captive_captivemodid_object_init, }; captive_captivemodid_object_type=g_type_register_static(G_TYPE_OBJECT, "CaptiveCaptivemodidObject",&captive_captivemodid_object_info,0); } return captive_captivemodid_object_type; } static void captive_captivemodid_load_module (CaptiveCaptivemodidObject *captivemodid,struct captive_captivemodid_module *module) { struct captive_captivemodid_module *module_md5_conflict; gpointer valid_length_value_gpointer; if ((module_md5_conflict=g_hash_table_lookup(captivemodid->module_md5_hash,module->md5))) { g_warning(_("Ignoring module \"%s\" as it has MD5 conflict with: %s"), module->id,module_md5_conflict->id); return; } g_hash_table_insert(captivemodid->module_md5_hash,(/* de-const */ xmlChar *)module->md5,module); if (!g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(module->length), NULL, /* orig_key */ &valid_length_value_gpointer)) /* value */ g_hash_table_insert(captivemodid->module_valid_length_hash, GINT_TO_POINTER(module->length),GINT_TO_POINTER(module->cabinet_used)); else { /* Conflicting 'cabinet_used' values for single 'cabinet size'? */ if (valid_length_value_gpointer && GPOINTER_TO_INT(valid_length_value_gpointer)!=module->cabinet_used) g_hash_table_insert(captivemodid->module_valid_length_hash,GINT_TO_POINTER(module->length),NULL); } if (strcmp((const char *)module->type,"cabinet")) { if (module->priority>captive_captivemodid_module_type_best_priority_lookup(captivemodid,module->type)) { g_hash_table_insert(captivemodid->module_type_best_priority_hash, (/* de-const */ xmlChar *)module->type,GINT_TO_POINTER(module->priority)); } } } gboolean captive_captivemodid_module_length_is_valid(CaptiveCaptivemodidObject *captivemodid,GnomeVFSFileSize file_size) { gint file_size_gint; if ((GnomeVFSFileSize)(file_size_gint=file_size)!=file_size) /* Size too big to be valid. */ return FALSE; return g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(file_size_gint), NULL, /* orig_key */ NULL); /* value */ } gint captive_captivemodid_cabinet_length_to_used(CaptiveCaptivemodidObject *captivemodid,gint cabinet_length) { gpointer valid_length_value_gpointer; if (!g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(cabinet_length), NULL, /* orig_key */ &valid_length_value_gpointer)) /* value */ return 0; return GPOINTER_TO_INT(valid_length_value_gpointer); } struct captive_captivemodid_module *captive_captivemodid_module_md5_lookup (CaptiveCaptivemodidObject *captivemodid,const gchar *file_md5) { g_return_val_if_fail(file_md5!=NULL,NULL); return g_hash_table_lookup(captivemodid->module_md5_hash,file_md5); } gint captive_captivemodid_module_type_best_priority_lookup(CaptiveCaptivemodidObject *captivemodid,const xmlChar *module_type) { gpointer r_gpointer; gboolean errbool; g_return_val_if_fail(module_type!=NULL,G_MININT); errbool=g_hash_table_lookup_extended(captivemodid->module_type_best_priority_hash, module_type, /* lookup_key */ NULL, /* orig_key */ &r_gpointer); /* value */ if (!errbool) return G_MININT; return GPOINTER_TO_INT(r_gpointer); } /* Returns: TRUE if all modules were found. */ gboolean captive_captivemodid_module_type_best_priority_found (CaptiveCaptivemodidObject *captivemodid,const xmlChar *module_type) { gboolean errbool; g_return_val_if_fail(module_type!=NULL,FALSE); errbool=g_hash_table_remove(captivemodid->module_type_best_priority_hash,module_type); g_assert(errbool==TRUE); return !g_hash_table_size(captivemodid->module_type_best_priority_hash); } static xmlChar *captive_captivemodid_load_module_xml_get_attr (const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name) { xmlChar *r; if (!(r=xmlTextReaderGetAttribute(xml_reader,BAD_CAST attr_name))) { /* FIXME: File line identification? */ g_warning(_("%s: Undefined attributes: %s"),captive_captivemodid_pathname,attr_name); return NULL; } return r; } static long captive_captivemodid_load_module_xml_get_attr_l (const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name,long num_min,long num_max) { xmlChar *string; long r; char *ends; g_return_val_if_fail(num_min-1num_max) { g_warning(_("%s: Numer of out range %ld..%ld: %ld"),captive_captivemodid_pathname,num_min,num_max,r); return num_min-1; } return r; } static void captive_captivemodid_load_module_xml (CaptiveCaptivemodidObject *captivemodid,const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader) { struct captive_captivemodid_module *module; xmlChar *cabinet_used_string; captive_new0(module); if (!(module->type=captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"type"))) goto fail_free_module; if (!(module->md5 =captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"md5"))) goto fail_free_module; if (strlen((const char *)module->md5)!=strspn((const char *)module->md5,"0123456789abcdef")) { g_warning(_("%s: Attribute 'md5' can be only lower-cased hexstring: %s"),captive_captivemodid_pathname,module->md5); goto fail_free_module; } if (strlen((const char *)module->md5)!=32) { g_warning(_("%s: Attribute 'md5' length must be 32: %s"),captive_captivemodid_pathname,module->md5); goto fail_free_module; } if (!(module->id =captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"id"))) goto fail_free_module; if (0>=(module->length=captive_captivemodid_load_module_xml_get_attr_l( captive_captivemodid_pathname,xml_reader,"length",1,G_MAXINT-1))) goto fail_free_module; if (!(cabinet_used_string=xmlTextReaderGetAttribute(xml_reader,BAD_CAST "cabinet_used"))) module->cabinet_used=0; else { xmlFree(cabinet_used_string); if (0>=(module->cabinet_used=captive_captivemodid_load_module_xml_get_attr_l( captive_captivemodid_pathname,xml_reader,"cabinet_used",1,G_MAXINT-1))) goto fail_free_module; } if (G_MININT>=(module->priority=captive_captivemodid_load_module_xml_get_attr_l(captive_captivemodid_pathname,xml_reader,"priority", G_MININT+1,G_MAXINT-1))) goto fail_free_module; captive_captivemodid_load_module(captivemodid,module); return; fail_free_module: captive_captivemodid_module_free(module); } static void captive_captivemodid_load_foreach (const xmlChar *type /* key */,gpointer priority_gpointer /* value */,gpointer user_data /* unused */) { g_return_if_fail(type!=NULL); g_return_if_fail(captive_captivemodid_module_best_priority_notify!=NULL); (*captive_captivemodid_module_best_priority_notify)((const gchar *)type); } void (*captive_captivemodid_module_best_priority_notify)(const gchar *module_type); CaptiveCaptivemodidObject *captive_captivemodid_load(const gchar *captive_captivemodid_pathname) { CaptiveCaptivemodidObject *captivemodid; xmlTextReader *xml_reader; if (!(xml_reader=xmlNewTextReaderFilename(captive_captivemodid_pathname))) return FALSE; captivemodid=g_object_new( CAPTIVE_CAPTIVEMODID_TYPE_OBJECT, /* object_type */ NULL); /* first_property_name; FIXME: support properties */ captivemodid->pathname_loaded=g_strdup(captive_captivemodid_pathname); while (1==xmlTextReaderRead(xml_reader)) { switch (xmlTextReaderNodeType(xml_reader)) { case CAPTIVE_XML_TEXT_READER_NODE_TYPE_COMMENT: break; case CAPTIVE_XML_TEXT_READER_NODE_TYPE_SIGNIFICANT_WHITESPACE: break; case CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT: /* Even empty nodes have some '#text'. */ break; case CAPTIVE_XML_TEXT_READER_NODE_TYPE_END: /* We do not track tag ends. */ break; case CAPTIVE_XML_TEXT_READER_NODE_TYPE_START: { const xmlChar *xml_name; xml_name=xmlTextReaderName(xml_reader); /**/ if (!xmlStrcmp(xml_name,BAD_CAST "modid")) { /* root tag */ } else if (!xmlStrcmp(xml_name,BAD_CAST "module")) captive_captivemodid_load_module_xml(captivemodid,captive_captivemodid_pathname,xml_reader); else g_warning(_("%s: Unknown ELEMENT node: %s"),captive_captivemodid_pathname,xml_name); xmlFree((xmlChar *)xml_name); } break; default: g_assert_not_reached(); } } xmlFreeTextReader(xml_reader); if (captive_captivemodid_module_best_priority_notify) { g_hash_table_foreach(captivemodid->module_type_best_priority_hash, (GHFunc)captive_captivemodid_load_foreach, captivemodid); /* user_data */ } return captivemodid; } CaptiveCaptivemodidObject *captive_captivemodid_load_default(gboolean fatal) { CaptiveCaptivemodidObject *captivemodid=NULL; const gchar *pathname_default=G_STRINGIFY(SYSCONFDIR) "/w32-mod-id.captivemodid.xml"; const gchar *msg; if ((captivemodid=captive_captivemodid_load(pathname_default))) return captivemodid; if ((captivemodid=captive_captivemodid_load("./w32-mod-id.captivemodid.xml"))) return captivemodid; msg=_("Unable to load modid database: %s"); if (fatal) g_error(msg,pathname_default); g_warning(msg,pathname_default); return NULL; } const gchar *captive_captivemodid_get_pathname_loaded(CaptiveCaptivemodidObject *captivemodid) { return captivemodid->pathname_loaded; } gchar *captive_calc_md5(gconstpointer base,size_t length) { unsigned char md5_bin[1+128/8]; /* 128 bits==16 bytes; '1+' for leading stub to prevent shorter output of BN_bn2hex() */ BIGNUM *bignum; char *hex; gchar *r,*gs; /* already done above */ /* Calculate MD5 sum and convert it to hex string: */ MD5(base,length,md5_bin+1); md5_bin[0]=0xFF; /* stub to prevent shorter output of BN_bn2hex() */ bignum=BN_bin2bn(md5_bin,1+128/8,NULL); hex=BN_bn2hex(bignum); g_assert(strlen(hex)==2*(1+128/8)); r=g_strdup(hex+2); OPENSSL_free(hex); BN_free(bignum); g_assert(strlen(r)==32); for (gs=r;*gs;gs++) { g_assert(isxdigit(*gs)); *gs=tolower(*gs); g_assert(isxdigit(*gs)); } return r; }