2 * W32 disk modules identifier for libcaptive and its clients
3 * Copyright (C) 2003-2005 Jan Kratochvil <project-captive@jankratochvil.net>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; exactly version 2 of June 1991 is required
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include "captive/captivemodid.h" /* self */
23 #include "captive/macros.h"
24 #include "captive/libxml.h"
25 #include <glib/gmessages.h>
26 #include <libxml/xmlreader.h>
27 #include <glib/ghash.h>
30 #include <libgnomevfs/gnome-vfs-file-size.h>
31 #include <openssl/md5.h>
32 #include <openssl/bn.h>
33 #include <openssl/crypto.h>
34 #include <glib/gstrfuncs.h>
38 struct _CaptiveCaptivemodidObject {
39 GObject parent_instance;
41 /* map: GINT_TO_POINTER(captive_captivemodid_module.length) -> !=NULL */
42 /* No allocations needed. */
43 GHashTable *module_valid_length_hash;
45 /* map: (const xmlChar *)md5 -> (struct captive_captivemodid_module *) */
46 /* 'key' is not allocated it is shared with: 'val'->md5 */
47 /* 'val' is allocated, to be automatically freed by: captive_captivemodid_module_free() */
48 GHashTable *module_md5_hash;
50 /* map: (const xmlChar *)type -> (gpointer)GINT_TO_POINTER(priority) */
51 /* We remove entry for module with already the best priority found,
52 * therefore captive_captivemodid_module_type_best_priority_lookup() will return
53 * 'G_MININT' afterwards.
54 * 'key' is not allocated - it is shared with: module_md5_hash */
55 /* No allocations needed. */
56 GHashTable *module_type_best_priority_hash;
59 gchar *pathname_loaded;
61 struct _CaptiveCaptivemodidObjectClass {
62 GObjectClass parent_class;
66 static gpointer captive_captivemodid_object_parent_class=NULL;
69 static void captive_captivemodid_object_finalize(CaptiveCaptivemodidObject *captive_captivemodid_object)
71 g_return_if_fail(captive_captivemodid_object!=NULL);
73 g_hash_table_destroy(captive_captivemodid_object->module_valid_length_hash);
74 g_hash_table_destroy(captive_captivemodid_object->module_md5_hash);
75 g_hash_table_destroy(captive_captivemodid_object->module_type_best_priority_hash);
76 g_free(captive_captivemodid_object->pathname_loaded);
78 G_OBJECT_CLASS(captive_captivemodid_object_parent_class)->finalize((GObject *)captive_captivemodid_object);
82 static void captive_captivemodid_object_class_init(CaptiveCaptivemodidObjectClass *class)
84 GObjectClass *gobject_class=G_OBJECT_CLASS(class);
86 captive_captivemodid_object_parent_class=g_type_class_ref(g_type_parent(G_TYPE_FROM_CLASS(class)));
87 gobject_class->finalize=(void (*)(GObject *object))captive_captivemodid_object_finalize;
91 static void captive_captivemodid_module_free(struct captive_captivemodid_module *module)
93 xmlFree((xmlChar *)module->type);
94 xmlFree((xmlChar *)module->md5);
95 xmlFree((xmlChar *)module->id);
99 static void captive_captivemodid_object_init(CaptiveCaptivemodidObject *captive_captivemodid_object)
101 captive_captivemodid_object->module_valid_length_hash=g_hash_table_new(g_direct_hash,g_direct_equal);
102 captive_captivemodid_object->module_md5_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
103 NULL, /* key_destroy_func */
104 (GDestroyNotify)captive_captivemodid_module_free); /* value_destroy_func */
105 captive_captivemodid_object->module_type_best_priority_hash=g_hash_table_new(g_str_hash,g_str_equal);
109 GType captive_captivemodid_object_get_type(void)
111 static GType captive_captivemodid_object_type=0;
113 if (!captive_captivemodid_object_type) {
114 static const GTypeInfo captive_captivemodid_object_info={
115 sizeof(CaptiveCaptivemodidObjectClass),
116 NULL, /* base_init */
117 NULL, /* base_finalize */
118 (GClassInitFunc)captive_captivemodid_object_class_init,
119 NULL, /* class_finalize */
120 NULL, /* class_data */
121 sizeof(CaptiveCaptivemodidObject),
123 (GInstanceInitFunc)captive_captivemodid_object_init,
126 captive_captivemodid_object_type=g_type_register_static(G_TYPE_OBJECT,
127 "CaptiveCaptivemodidObject",&captive_captivemodid_object_info,0);
130 return captive_captivemodid_object_type;
134 static void captive_captivemodid_load_module
135 (CaptiveCaptivemodidObject *captivemodid,struct captive_captivemodid_module *module)
137 struct captive_captivemodid_module *module_md5_conflict;
138 gpointer valid_length_value_gpointer;
140 if ((module_md5_conflict=g_hash_table_lookup(captivemodid->module_md5_hash,module->md5))) {
141 g_warning(_("Ignoring module \"%s\" as it has MD5 conflict with: %s"),
142 module->id,module_md5_conflict->id);
145 g_hash_table_insert(captivemodid->module_md5_hash,(/* de-const */ xmlChar *)module->md5,module);
147 if (!g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(module->length),
149 &valid_length_value_gpointer)) /* value */
150 g_hash_table_insert(captivemodid->module_valid_length_hash,
151 GINT_TO_POINTER(module->length),GINT_TO_POINTER(module->cabinet_used));
153 /* Conflicting 'cabinet_used' values for single 'cabinet size'? */
154 if (valid_length_value_gpointer && GPOINTER_TO_INT(valid_length_value_gpointer)!=module->cabinet_used)
155 g_hash_table_insert(captivemodid->module_valid_length_hash,GINT_TO_POINTER(module->length),NULL);
158 if (strcmp((const char *)module->type,"cabinet")) {
159 if (module->priority>captive_captivemodid_module_type_best_priority_lookup(captivemodid,module->type)) {
160 g_hash_table_insert(captivemodid->module_type_best_priority_hash,
161 (/* de-const */ xmlChar *)module->type,GINT_TO_POINTER(module->priority));
166 gboolean captive_captivemodid_module_length_is_valid(CaptiveCaptivemodidObject *captivemodid,GnomeVFSFileSize file_size)
170 if ((GnomeVFSFileSize)(file_size_gint=file_size)!=file_size) /* Size too big to be valid. */
172 return g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(file_size_gint),
177 gint captive_captivemodid_cabinet_length_to_used(CaptiveCaptivemodidObject *captivemodid,gint cabinet_length)
179 gpointer valid_length_value_gpointer;
181 if (!g_hash_table_lookup_extended(captivemodid->module_valid_length_hash,GINT_TO_POINTER(cabinet_length),
183 &valid_length_value_gpointer)) /* value */
185 return GPOINTER_TO_INT(valid_length_value_gpointer);
188 struct captive_captivemodid_module *captive_captivemodid_module_md5_lookup
189 (CaptiveCaptivemodidObject *captivemodid,const gchar *file_md5)
191 g_return_val_if_fail(file_md5!=NULL,NULL);
193 return g_hash_table_lookup(captivemodid->module_md5_hash,file_md5);
196 gint captive_captivemodid_module_type_best_priority_lookup(CaptiveCaptivemodidObject *captivemodid,const xmlChar *module_type)
201 g_return_val_if_fail(module_type!=NULL,G_MININT);
203 errbool=g_hash_table_lookup_extended(captivemodid->module_type_best_priority_hash,
204 module_type, /* lookup_key */
206 &r_gpointer); /* value */
210 return GPOINTER_TO_INT(r_gpointer);
213 /* Returns: TRUE if all modules were found. */
214 gboolean captive_captivemodid_module_type_best_priority_found
215 (CaptiveCaptivemodidObject *captivemodid,const xmlChar *module_type)
219 g_return_val_if_fail(module_type!=NULL,FALSE);
221 errbool=g_hash_table_remove(captivemodid->module_type_best_priority_hash,module_type);
222 g_assert(errbool==TRUE);
224 return !g_hash_table_size(captivemodid->module_type_best_priority_hash);
227 static xmlChar *captive_captivemodid_load_module_xml_get_attr
228 (const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name)
232 if (!(r=xmlTextReaderGetAttribute(xml_reader,BAD_CAST attr_name))) {
233 /* FIXME: File line identification? */
234 g_warning(_("%s: Undefined attributes: %s"),captive_captivemodid_pathname,attr_name);
240 static long captive_captivemodid_load_module_xml_get_attr_l
241 (const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader,const gchar *attr_name,long num_min,long num_max)
247 g_return_val_if_fail(num_min-1<num_min,-1);
248 g_return_val_if_fail(num_min<=num_max,num_min-1);
249 g_return_val_if_fail(LONG_MIN<num_min,LONG_MIN);
250 g_return_val_if_fail(num_max<LONG_MAX,num_min-1);
252 if (!(string=captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,attr_name)))
254 r=strtol((const char *)string,&ends,0);
256 if (r<num_min || r>num_max) {
257 g_warning(_("%s: Numer of out range %ld..%ld: %ld"),captive_captivemodid_pathname,num_min,num_max,r);
263 static void captive_captivemodid_load_module_xml
264 (CaptiveCaptivemodidObject *captivemodid,const gchar *captive_captivemodid_pathname,xmlTextReader *xml_reader)
266 struct captive_captivemodid_module *module;
267 xmlChar *cabinet_used_string;
269 captive_new0(module);
270 if (!(module->type=captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"type")))
271 goto fail_free_module;
272 if (!(module->md5 =captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"md5")))
273 goto fail_free_module;
274 if (strlen((const char *)module->md5)!=strspn((const char *)module->md5,"0123456789abcdef")) {
275 g_warning(_("%s: Attribute 'md5' can be only lower-cased hexstring: %s"),captive_captivemodid_pathname,module->md5);
276 goto fail_free_module;
278 if (strlen((const char *)module->md5)!=32) {
279 g_warning(_("%s: Attribute 'md5' length must be 32: %s"),captive_captivemodid_pathname,module->md5);
280 goto fail_free_module;
282 if (!(module->id =captive_captivemodid_load_module_xml_get_attr(captive_captivemodid_pathname,xml_reader,"id")))
283 goto fail_free_module;
284 if (0>=(module->length=captive_captivemodid_load_module_xml_get_attr_l(
285 captive_captivemodid_pathname,xml_reader,"length",1,G_MAXINT-1)))
286 goto fail_free_module;
287 if (!(cabinet_used_string=xmlTextReaderGetAttribute(xml_reader,BAD_CAST "cabinet_used")))
288 module->cabinet_used=0;
290 xmlFree(cabinet_used_string);
291 if (0>=(module->cabinet_used=captive_captivemodid_load_module_xml_get_attr_l(
292 captive_captivemodid_pathname,xml_reader,"cabinet_used",1,G_MAXINT-1)))
293 goto fail_free_module;
295 if (G_MININT>=(module->priority=captive_captivemodid_load_module_xml_get_attr_l(captive_captivemodid_pathname,xml_reader,"priority",
296 G_MININT+1,G_MAXINT-1)))
297 goto fail_free_module;
298 captive_captivemodid_load_module(captivemodid,module);
302 captive_captivemodid_module_free(module);
305 static void captive_captivemodid_load_foreach
306 (const xmlChar *type /* key */,gpointer priority_gpointer /* value */,gpointer user_data /* unused */)
308 g_return_if_fail(type!=NULL);
310 g_return_if_fail(captive_captivemodid_module_best_priority_notify!=NULL);
312 (*captive_captivemodid_module_best_priority_notify)((const gchar *)type);
315 void (*captive_captivemodid_module_best_priority_notify)(const gchar *module_type);
317 CaptiveCaptivemodidObject *captive_captivemodid_load(const gchar *captive_captivemodid_pathname)
319 CaptiveCaptivemodidObject *captivemodid;
320 xmlTextReader *xml_reader;
322 if (!(xml_reader=xmlNewTextReaderFilename(captive_captivemodid_pathname)))
325 captivemodid=g_object_new(
326 CAPTIVE_CAPTIVEMODID_TYPE_OBJECT, /* object_type */
327 NULL); /* first_property_name; FIXME: support properties */
328 captivemodid->pathname_loaded=g_strdup(captive_captivemodid_pathname);
330 while (1==xmlTextReaderRead(xml_reader)) {
331 switch (xmlTextReaderNodeType(xml_reader)) {
333 case CAPTIVE_XML_TEXT_READER_NODE_TYPE_COMMENT:
336 case CAPTIVE_XML_TEXT_READER_NODE_TYPE_SIGNIFICANT_WHITESPACE:
339 case CAPTIVE_XML_TEXT_READER_NODE_TYPE_TEXT: /* Even empty nodes have some '#text'. */
342 case CAPTIVE_XML_TEXT_READER_NODE_TYPE_END: /* We do not track tag ends. */
345 case CAPTIVE_XML_TEXT_READER_NODE_TYPE_START: {
346 const xmlChar *xml_name;
348 xml_name=xmlTextReaderName(xml_reader);
349 /**/ if (!xmlStrcmp(xml_name,BAD_CAST "modid")) { /* root tag */
351 else if (!xmlStrcmp(xml_name,BAD_CAST "module"))
352 captive_captivemodid_load_module_xml(captivemodid,captive_captivemodid_pathname,xml_reader);
353 else g_warning(_("%s: Unknown ELEMENT node: %s"),captive_captivemodid_pathname,xml_name);
354 xmlFree((xmlChar *)xml_name);
357 default: g_assert_not_reached();
360 xmlFreeTextReader(xml_reader);
362 if (captive_captivemodid_module_best_priority_notify) {
363 g_hash_table_foreach(captivemodid->module_type_best_priority_hash,
364 (GHFunc)captive_captivemodid_load_foreach,
365 captivemodid); /* user_data */
371 CaptiveCaptivemodidObject *captive_captivemodid_load_default(gboolean fatal)
373 CaptiveCaptivemodidObject *captivemodid=NULL;
374 const gchar *pathname_default=G_STRINGIFY(SYSCONFDIR) "/w32-mod-id.captivemodid.xml";
377 if ((captivemodid=captive_captivemodid_load(pathname_default)))
379 if ((captivemodid=captive_captivemodid_load("./w32-mod-id.captivemodid.xml")))
381 msg=_("Unable to load modid database: %s");
383 g_error(msg,pathname_default);
384 g_warning(msg,pathname_default);
388 const gchar *captive_captivemodid_get_pathname_loaded(CaptiveCaptivemodidObject *captivemodid)
390 return captivemodid->pathname_loaded;
393 gchar *captive_calc_md5(gconstpointer base,size_t length)
395 unsigned char md5_bin[1+128/8]; /* 128 bits==16 bytes; '1+' for leading stub to prevent shorter output of BN_bn2hex() */
400 /* already done above */
401 /* Calculate MD5 sum and convert it to hex string: */
402 MD5(base,length,md5_bin+1);
403 md5_bin[0]=0xFF; /* stub to prevent shorter output of BN_bn2hex() */
404 bignum=BN_bin2bn(md5_bin,1+128/8,NULL);
405 hex=BN_bn2hex(bignum);
406 g_assert(strlen(hex)==2*(1+128/8));
411 g_assert(strlen(r)==32);
412 for (gs=r;*gs;gs++) {
413 g_assert(isxdigit(*gs));
415 g_assert(isxdigit(*gs));