void load_configuration () { l_debug ("Loading configuration for Profile:%s",profile); if (config != NULL) g_hash_table_remove_all (config); config = g_hash_table_new (g_str_hash, g_str_equal); MYSQL_ROW row; MYSQL_RES *result; int res = do_query (FALSE, lyricDb, "SELECT config_key,config_value FROM config WHERE profile='%s'", profile); if (res != 0) { return; } result = mysql_store_result (lyricDb); gboolean conf_found = FALSE; while ((row = mysql_fetch_row (result))) { conf_found = TRUE; g_hash_table_insert (config, g_strdup (row[0]), g_strdup (row[1])); l_debug ("Config \"%s\" set to \"%s\"", row[0], row[1]); } if (!conf_found) { l_debug ("No configuration found - load lyricue to setup"); exit (1); } load_font_defaults (); mysql_free_result (result); }
MYSQL * db_connect (const char *dbname, const char *dberror) { MYSQL *tempDb = mysql_init (NULL); if (tempDb == NULL) { l_debug ("Error %u: %s", mysql_errno (tempDb), mysql_error (tempDb)); g_warning ("Error %u: %s", mysql_errno (tempDb), mysql_error (tempDb)); exit (1); } my_bool reconnect = 1; mysql_options (tempDb, MYSQL_OPT_RECONNECT, &reconnect); if (mysql_real_connect (tempDb, dbhostname, "lyric", "", dbname, 0, NULL, 0) == NULL) { l_debug ("Error %u: %s", mysql_errno (tempDb), mysql_error (tempDb)); g_warning ("Error %u: %s", mysql_errno (tempDb), mysql_error (tempDb)); return NULL; } // Re-run for mysql versions < 5.0.19 mysql_options (tempDb, MYSQL_OPT_RECONNECT, &reconnect); mysql_set_character_set (tempDb, "utf8"); return tempDb; }
// copies keys from the source that don't exist on the target LClosure *l_closure_backfill(LClosure *source, LClosure *target, LNode *node) { l_debug(L_DEBUG_STACK) printf("+++ backfilling closure\n"); target->node = node; l_clone_vars_if_missing(source->vars, target->vars); l_clone_vars_if_missing(source->locals, target->locals); return target; }
static int SetArgMem(lua_State *L) { cl_kernel krnl = *traits::CheckObject(L, 1); cl_uint index = static_cast<cl_uint>(luaL_checknumber(L, 2)); luacl_buffer_object *buffer = traits_buffer::CheckObject(L, 3); l_debug(L, "kernel:SetArg index %d, buffer %p, mem %p", index, buffer, buffer->mem); cl_int err = clSetKernelArg(krnl, index - 1, sizeof(cl_mem), &(buffer->mem)); CheckCLError(L, err, "Failed setting kernel arg as mem object: %s."); return 0; }
// creates and initializes an empty closure LClosure *l_closure_new(LNode *node) { l_debug(L_DEBUG_STACK) printf("+++ creating new closure\n"); LClosure *closure = GC_MALLOC(sizeof(LClosure)); closure->vars = create_hashmap(); closure->locals = create_hashmap(); closure->parent = NULL; closure->cloneable = true; closure->node = node; return closure; }
void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { assert(g == group || group == NULL); group = g; /* Called whenever the entry group state changes */ switch (state) { case AVAHI_ENTRY_GROUP_ESTABLISHED : /* The entry group has been established successfully */ l_debug("Service '%s' successfully established.", name); break; case AVAHI_ENTRY_GROUP_COLLISION : { char *n; /* A service name collision with a remote service * happened. Let's pick a new name */ n = avahi_alternative_service_name(name); avahi_free(name); name = n; l_debug("Service name collision, renaming service to '%s'", name); /* And recreate the services */ create_services(avahi_entry_group_get_client(g)); break; } case AVAHI_ENTRY_GROUP_FAILURE : l_debug("Entry group failure: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); /* Some kind of failure happened while we were registering our services */ avahi_simple_poll_quit(simple_poll); break; case AVAHI_ENTRY_GROUP_UNCOMMITED: case AVAHI_ENTRY_GROUP_REGISTERING: ; } }
// We use this simple function for translating strings from the browser into // cstrings. These strings can be untrusted, so verify them carefully. bool netscape_string_convert(NPString *string, char **output) { // Sanity check the string length. if (string->UTF8Length > kNetscapeStringMax) { l_debug("refusing to convert very long string, length %u", string->UTF8Length); return false; } // Does it contain nuls or other indicators of encoding issues? if (strnlen(string->UTF8Characters, string->UTF8Length) != string->UTF8Length) { l_debug("refusing to convert weird encoding, %u != %u, %s", strnlen(string->UTF8Characters, string->UTF8Length), string->UTF8Length, string->UTF8Characters); return false; } // Okay, it seems sane. Copy the output. return !! (*output = strndup(string->UTF8Characters, string->UTF8Length)); }
int do_query (gboolean silent, MYSQL * dbconnection, const char * format, ...) { if (dbconnection != NULL) { GString *query = g_string_new (NULL); va_list argp; va_start (argp, format); g_string_vprintf (query, format, argp); va_end (argp); if (!silent) l_debug ("SQL: %s", query->str); if (mysql_query (dbconnection, query->str)) { l_debug (_("SQL Error %u: %s"), mysql_errno (dbconnection), mysql_error (dbconnection)); } g_string_free (query, TRUE); return mysql_errno (dbconnection); } else { l_debug("Database not connected"); } return -1; }
// creates a new closure from the given parent closure LClosure *l_closure_clone(LClosure *parent, LNode *node) { l_debug(L_DEBUG_STACK) printf("+++ cloning closure\n"); if(!parent->cloneable) return parent; LClosure *closure = GC_MALLOC(sizeof(LClosure)); closure->vars = create_hashmap(); closure->locals = create_hashmap(); closure->parent = parent; closure->cloneable = true; closure->node = node; // copy vars from function closure l_clone_vars(parent->vars, closure->vars); l_clone_vars(parent->locals, closure->locals); return closure; }
int publish_avahi(int port_passed, char *type_in_passed) { int error; AvahiGLibPoll *glib_poll; port = port_passed; type_in = strndup(type_in_passed,32); avahi_set_allocator (avahi_glib_allocator ()); /* Create the GLIB Adaptor */ glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); name = avahi_strdup("Lyricue Display"); /* Allocate a new client */ client = avahi_client_new(avahi_glib_poll_get(glib_poll), 0, client_callback, NULL, &error); /* Check if creating the client object succeeded */ if (!client) { l_debug("Failed to create client: %s", avahi_strerror(error)); unpublish_avahi(); } /* Create the service browser */ if (miniviews==NULL) { miniviews = g_hash_table_new(g_str_hash, g_str_equal); } if (sb == NULL) { if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, "_lyricue._tcp", NULL, 0, browse_callback, client))) { l_debug("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client))); } } return 0; }
// Used to percent encode messages so we can ignore sanitisation. static bool encode_javascript_string(const char *message, char **output) { // Sanity check parameters. if (!message || strlen(message) > kMessageLengthMax) return false; // Allow for triple expansion. if (!(*output = malloc(strlen(message) * 3 + 1))) { l_debug("memory allocation failure constructing message"); return false; } // Safely encode string using percent-encodiung. for (**output = 0; *message; message++) { sprintf(*output + strlen(*output), "%%%02hx", *message); } return true; }
// Apple don't use a simple callback to NP_GetMIMEDescription, as on Linux, but // we still need to implement it as we can dynamically open other plugins. // // XXX: For the initial release, I'm not going to support external MIME types. I // will add it on request. char * platform_getmimedescription(struct plugin *plugin) { CFDictionaryRef cf_pluginplist = NULL; CFDictionaryRef cf_mimetypes = NULL; CFBundleRef cf_plugin = plugin->handle; char *mime_description = strdup(""); // If we don't have a valid handle, we return an empty MIME description. // This is jsut for convenience so that I don't have to special case // invalid plugins on shutdown. if (plugin->handle == NULL) { goto finished; } // We should already have a CFBundleRef in plugin->handle. cf_pluginplist = CFBundleGetInfoDictionary(cf_plugin); // Find the WebPluginMIMETypes key. if (CFDictionaryGetValueIfPresent(cf_pluginplist, CFSTR("WebPluginMIMETypes"), (const void **) &cf_mimetypes) == false) { // The plugin is invalid, malformed, or using external MIME Types. This // is not currently supported, so return an empty string. goto finished; } if (CFGetTypeID(cf_mimetypes) != CFDictionaryGetTypeID()) { goto finished; } l_debug("found %u keys in WebPluginMIMETypes dictionary from plugin %s", CFDictionaryGetCount(cf_mimetypes), plugin->section); // Enumerate each key in the dictionary, adding them to the description. CFDictionaryApplyFunction(cf_mimetypes, mimetype_dictionary_applier, &mime_description); finished: // Return the final string. return mime_description; }
void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) { assert(c); /* Called whenever the client or server state changes */ switch (state) { case AVAHI_CLIENT_S_RUNNING: /* The server has startup successfully and registered its host * name on the network, so it's time to create our services */ create_services(c); break; case AVAHI_CLIENT_FAILURE: l_debug("Client failure: %s", avahi_strerror(avahi_client_errno(c))); avahi_simple_poll_quit(simple_poll); break; case AVAHI_CLIENT_S_COLLISION: /* Let's drop our registered services. When the server is back * in AVAHI_SERVER_RUNNING state we will register them * again with the new host name. */ case AVAHI_CLIENT_S_REGISTERING: /* The server records are now being established. This * might be caused by a host name change. We need to wait * for our own records to register until the host name is * properly esatblished. */ if (group) avahi_entry_group_reset(group); break; case AVAHI_CLIENT_CONNECTING: ; } }
void create_services(AvahiClient *c) { char *n; gchar *type_txt, *profile_txt, *data_txt; int ret; assert(c); /* If this is the first time we're called, let's create a new * entry group if necessary */ if (!group) if (!(group = avahi_entry_group_new(c, entry_group_callback, NULL))) { l_debug("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(c))); goto fail; } /* If the group is empty (either because it was just created, or * because it was reset previously, add our entries. */ if (avahi_entry_group_is_empty(group)) { l_debug("Adding service '%s' type '%s'", name, type_in); /* Set type of service */ profile_txt = g_strdup_printf("profile=%s", profile); type_txt = g_strdup_printf("type=%s", type_in); if (extra_data == NULL) { extra_data = g_strdup(""); } data_txt = g_strdup_printf("data=%s", extra_data); /* Add the service for Lyricue Display */ if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, "_lyricue._tcp", NULL, NULL, port, type_txt, profile_txt, data_txt, NULL)) < 0) { if (ret == AVAHI_ERR_COLLISION) goto collision; l_debug("Failed to add _lyricue._tcp service: %s", avahi_strerror(ret)); goto fail; } g_free(profile_txt); g_free(type_txt); g_free(data_txt); /* Tell the server to register the service */ if ((ret = avahi_entry_group_commit(group)) < 0) { l_debug("Failed to commit entry group: %s", avahi_strerror(ret)); goto fail; } } return; collision: /* A service name collision with a local service happened. Let's * pick a new name */ n = avahi_alternative_service_name(name); avahi_free(name); name = n; l_debug("Service name collision, renaming service to '%s'", name); avahi_entry_group_reset(group); create_services(c); return; fail: avahi_simple_poll_quit(simple_poll); }
void resolve_callback( AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, AVAHI_GCC_UNUSED void* userdata) { assert(r); /* Called whenever a service has been resolved successfully or timed out */ switch (event) { case AVAHI_RESOLVER_FAILURE: l_debug("(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); break; case AVAHI_RESOLVER_FOUND: { char a[AVAHI_ADDRESS_STR_MAX]; avahi_address_snprint(a, sizeof(a), address); char *value; char *type = NULL; size_t *size=NULL; AvahiStringList *type_txt = avahi_string_list_find(txt,"type"); if (type_txt != NULL){ avahi_string_list_get_pair(type_txt,&type, &value, size); l_debug("Type = %s",value); if ((g_strcmp0(value,"miniview") == 0) || (g_strcmp0(value,"android") == 0)) { char *data="data"; char *extra_data; size_t *size2=NULL; AvahiStringList *data_txt = avahi_string_list_find(txt,"data"); if (data_txt != NULL) { avahi_string_list_get_pair(data_txt,&data, &extra_data, size2); gchar *myhost = g_strdup_printf("%s:%u",hostname,server_port); if (g_strcmp0(extra_data, myhost)==0) { gchar *host = g_strdup_printf("%s:%u",a, port); l_debug("Found miniview on %s", host); if (!g_hash_table_contains(miniviews,host)) { g_hash_table_insert(miniviews, g_strdup(name), host); } } avahi_free(data); avahi_free(extra_data); g_free(myhost); } } avahi_free(value); avahi_free(type); } } } avahi_service_resolver_free(r); }
bool netscape_plugin_geturl(NPP instance, char **url) { void *window; NPIdentifier *locationid; NPIdentifier *hrefid; NPVariant location; NPVariant href; // We need often need to query the url hosting the current instance to // apply our policy logic. This routine handles that. // // We do this by querying window.location.href, while this may seem // fragile, it's actually the officially supported method of retrieving the // URL. Being able to fool it would break most popular plugins, so we can // rely on browser vendors maintaining it. if (registry.netscape_funcs->getvalue(instance, NPNVWindowNPObject, &window) != NPERR_NO_ERROR) { l_debug("failed to fetch window object for instance %p", instance); return false; } // Create required identifiers to query the objects. // // Why not use location.hostname? // // > location.__defineGetter__("hostname", function () { return "arbitrary"; }) // > undefined // > location.hostname // > "arbitrary" // // In fact the browser guarantees nothing except window.location.href. locationid = registry.netscape_funcs->getstringidentifier("location"); hrefid = registry.netscape_funcs->getstringidentifier("href"); // Get the Location object. if (!registry.netscape_funcs->getproperty(instance, window, locationid, &location) || !NPVARIANT_IS_OBJECT(location)) { l_debug("failed to fetch location object for instance %p", instance); return false; } // Get the URL from the Location object via href. if (!registry.netscape_funcs->getproperty(instance, location.value.objectValue, hrefid, &href) || !NPVARIANT_IS_STRING(href)) { l_warning("failed to fetch href string for instance %p", instance); return false; } // No longer need location object. registry.netscape_funcs->releasevariantvalue(&location); // Finally, Convert the NPString returned into a C string. if (!netscape_string_convert(&NPVARIANT_TO_STRING(href), url)) { l_warning("failed to convert NPString to c string for %p", instance); return false; } // Clear the NPString. registry.netscape_funcs->releasevariantvalue(&href); return true; }
// We may want to display a message to the user, but don't want to have to // create our own windows. We can ask the browser to display it instead, but // have to be careful about what we send. bool netscape_display_message(NPP instance, const char *message) { void *element; char *encoded; NPError result; NPString script; NPVariant output; // Verify the parameters are sane, if (!message || !instance) { l_debug("invalid instance or message received, cannot display"); return false; } // No need to actually display the empty message if (!strlen(message)) { return true; } // Retrieve the plugin object. if (registry.netscape_funcs->getvalue(instance, NPNVPluginElementNPObject, &element) != NPERR_NO_ERROR) { l_debug("unable to retrieve element object to display message"); return false; } // We cannot display a message this way in Firefox due to a bug. if (strstr(registry.netscape_funcs->uagent(instance), "Firefox")) { l_warning("FIXME: unable to display messages in FireFox due to a bug"); return false; } // Percent encode the required string. if (!encode_javascript_string(message, &encoded)) { l_debug("unable to construct javascript safe string, failed"); return false; } // To produce the final message, we need to allow for the code to alert and // unescape the message as well. script.UTF8Characters = alloca(strlen(encoded) + strlen(kJSDisplayEncodedMessageFormat)); // Produce the NPString, which doesn't want the terminating nul. Luckily, // that's what sprintf returns. script.UTF8Length = sprintf((char *) script.UTF8Characters, kJSDisplayEncodedMessageFormat, encoded); // This should evaluate the script NPString in the context of the plugin // object. result = registry.netscape_funcs->evaluate(instance, element, &script, &output); // Print debugging message if that failed. if (result != NPERR_NO_ERROR) { l_debug("netscape returned error displaying message %s", encoded); } // Clean up. registry.netscape_funcs->releasevariantvalue(&output); free(encoded); return result == NPERR_NO_ERROR; }
// Find the matching plugin structure for the section name `section`. If no // such section exists, a new one is allocated and returned. If the section // name matches the special name "Global", it is added to the appropriate list. // // Returns true on success, false on failure. static bool find_plugin_section(struct registry *registry, const char *section, struct plugin **plugin) { struct plugin *current; // Check if this is the special "Global" section used to specify default // parameters and other special values. if (strcmp(section, "Global") == 0) { // If this is the first value from the Global section, we need to // create it. if (registry->global == NULL) { // Allocate a new structure. if ((*plugin = calloc(1, sizeof(**plugin))) == NULL) { l_error("memory allocation failure"); return false; } // Install as the global plugin. registry->global = *plugin; registry->global->section = strdup(section); } // Return pointer to parent. *plugin = registry->global; // Success. return !! registry->global->section; } else { // This is not the Global section, a regular plugin section. current = registry->plugins; for (current = registry->plugins; current; current = current->next) { // Search to see if we already recognise this section. if (strcmp(current->section, section) == 0) { // Match found. *plugin = current; return true; } } } // This is the first time we've seen this section, we have to set it up. l_debug("new plugin section %s discovered", section); // Allocate a new structure. if ((*plugin = calloc(1, sizeof(**plugin))) == NULL) { l_error("memory allocation failure"); return false; } // Check if we have any other plugins registered. if (registry->plugins != NULL) { // Find the tail of the plugin registry. for (current = registry->plugins; current->next; current = current->next) ; // Add to the list. current->next = *plugin; current = current->next; current->section = strdup(section); } else { // This is the first plugin structure we've seen, create the list head. registry->plugins = *plugin; registry->plugins->section = strdup(section); current = registry->plugins; } // Success. return !! current->section; }
LValue *l_eval_call_node(LNode *node, LValue *func, LClosure *closure) { if(func == NULL) func = l_eval_var_node(node, closure); char *name = (node != NULL) ? node->val : ""; LValue *value, *args_val; LNode *expr; int i; // create a running scope to hold arguments // and a reference to self (for recursion) // TODO this may not be necessary (can't we just do `cl=func->core.func.closure`?) LClosure *cl = l_closure_clone(func->core.func.closure, node); tail_loop: l_debug(L_DEBUG_CALL) { if(node) printf(">>> entering %s on line %d in %s\n", name, node->line_no, node->source_file); else printf(">>> entering %s\n", name); } if(strcmp(name, "") != 0) l_closure_set(cl, name, func, true); args_val = l_eval_call_args(node, func, closure, cl); if(func->core.func.ptr != NULL) { // native C code if(!setjmp(cl->jmp)) { value = func->core.func.ptr(args_val, cl); } else { // function called longjmp to initiate a tail call l_debug(L_DEBUG_CALL) printf("^^^ reached end of %s (longjmp tail call)\n", name); node = NULL; func = l_closure_get(cl, "--tail-call--"); closure = cl; l_closure_backfill(func->core.func.closure, cl, node); name = ""; goto tail_loop; } } else { // Lydia code int exprc = func->core.func.exprc; if(exprc > 0) { // eval all but the last expression for(i=0; i<exprc-1; i++) { expr = func->core.func.exprs[i]; value = l_eval_node(expr, cl); } expr = func->core.func.exprs[exprc-1]; if(expr->type == L_CALL_TYPE) { // tail call l_debug(L_DEBUG_CALL) printf("^^^ reached end of %s (tail call)\n", name); node = expr; func = l_eval_var_node(node, cl); closure = cl; l_closure_backfill(func->core.func.closure, cl, node); name = node->val; goto tail_loop; } else { value = l_eval_node(expr, cl); } } else { value = l_value_new(L_NIL_TYPE, cl); } } l_closure_free(cl); l_debug(L_DEBUG_CALL) printf("<<< returning from %s\n", name); return value; }
// Helper function to construct strings for CFDictionaryApplyFunction in // platform_getmimedescription() // key CFString containing a MIME type. // value CFDictionary containing descriptions and extensions. // context char * where we want our NP_GetMIMETypeDescription compatible output. static void mimetype_dictionary_applier(const void *key, const void *value, void *context) { CFDictionaryRef cf_mimetype_dict = value; CFStringRef cf_mimetype = key; CFIndex cf_length; CFArrayRef cf_extensions; CFStringRef cf_description; CFBooleanRef cf_enabled; char *mimetype = strdupa(""); char *description = strdupa(""); char *extensions = strdup(""); char **result = context; // Here is an example of the output we want: // // "application/example:ext1,ext2:Example MIME Type" // // Verify that we received a CFDictionary object. if (CFGetTypeID(cf_mimetype_dict) != CFDictionaryGetTypeID()) { goto finished; } // Verify that the key is a CFString. if (CFGetTypeID(cf_mimetype) != CFStringGetTypeID()) { goto finished; } // Find the length of the MIME Type, and allocate stack space for it. cf_length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cf_mimetype), kCFStringEncodingUTF8); mimetype = alloca(cf_length + 1); // Extract the string. if (CFStringGetCString(cf_mimetype, mimetype, cf_length + 1, kCFStringEncodingUTF8) != true) { goto finished; } // First we need to check if this type is disabled via WebPluginTypeEnabled. if (CFDictionaryGetValueIfPresent(cf_mimetype_dict, CFSTR("WebPluginTypeEnabled"), (const void **) &cf_enabled)) { // Verify that is a CFBoolean if (CFGetTypeID(cf_enabled) != CFBooleanGetTypeID()) { goto finished; } // Test value. if (CFBooleanGetValue(cf_enabled) == false) { goto finished; } } // Verify we have an empty string. if (!extensions) { goto finished; } // Now we need to lookup the extensions requested by the plugin. if (CFDictionaryGetValueIfPresent(cf_mimetype_dict, CFSTR("WebPluginExtensions"), (const void **) &cf_extensions)) { if (CFGetTypeID(cf_extensions) != CFArrayGetTypeID()) { goto finished; } l_debug("discovered %u extensions defined for mimetype %s", CFArrayGetCount(cf_extensions), mimetype); // Apply a function to every extension listed to concatenate them. CFArrayApplyFunction(cf_extensions, CFRangeMake(0, CFArrayGetCount(cf_extensions)), extension_array_applier, &extensions); } // Now we need the description which is a CFString if (CFDictionaryGetValueIfPresent(cf_mimetype_dict, CFSTR("WebPluginTypeDescription"), (const void **) &cf_description)) { if (CFGetTypeID(cf_description) != CFStringGetTypeID()) { goto finished; } // Find the length of the MIME Type, and allocate stack space for it. cf_length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cf_description), kCFStringEncodingUTF8); description = alloca(cf_length + 1); // Extract the string. if (CFStringGetCString(cf_description, description, cf_length + 1, kCFStringEncodingUTF8) != true) { goto finished; } } // So now we need to assemble the final string. *result = realloc(*result, (*result ? strlen(*result) : 0) + strlen(mimetype) + 1 + strlen(description) + 1 + strlen(extensions) + 1 + 1 + 1); // Verify that worked. if (!*result) { goto finished; } // Create the final string. sprintf(*result, "%s%s%s:%s:%s", *result, strlen(*result) ? ";" : "", mimetype, extensions, description); l_debug("successfully processed mimetype %s", mimetype); finished: free(extensions); return; }
void l_closure_free(LClosure *closure) { l_debug(L_DEBUG_STACK) printf("--- freeing closure\n"); if(closure->parent == NULL) return; }