/* * Copyright 2013 Michael Drake * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf 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; version 2 of the License. * * NetSurf 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, see . */ /** * \file * Implementation of Cookie Management * * This allows users to view and remove their web cookies. * * \todo the viewing of cookies is pretty basic and it would be good * to apply more processing of the values and perhaps top level domain * suffix highlighting to give a better user information as to how the * cookies interact with the users sessions. * * \todo In addition to removing cookies it might be useful to allow * users to edit them, especially to alter expiry times. */ #include #include #include "utils/messages.h" #include "utils/utils.h" #include "utils/log.h" #include "content/urldb.h" #include "desktop/cookie_manager.h" #include "desktop/treeview.h" enum cookie_manager_field { COOKIE_M_NAME, COOKIE_M_CONTENT, COOKIE_M_DOMAIN, COOKIE_M_PATH, COOKIE_M_EXPIRES, COOKIE_M_LAST_USED, COOKIE_M_RESTRICTIONS, COOKIE_M_VERSION, COOKIE_M_DOMAIN_FOLDER, COOKIE_M_N_FIELDS }; enum cookie_manager_value { COOKIE_M_HTTPS, COOKIE_M_SECURE, COOKIE_M_HTTP, COOKIE_M_NONE, COOKIE_M_NETSCAPE, COOKIE_M_RFC2109, COOKIE_M_RFC2965, COOKIE_M_YES, COOKIE_M_NO, COOKIE_M_N_VALUES }; struct cookie_manager_folder { treeview_node *folder; struct treeview_field_data data; }; struct cookie_manager_ctx { treeview *tree; struct treeview_field_desc fields[COOKIE_M_N_FIELDS]; struct treeview_field_data values[COOKIE_M_N_VALUES]; bool built; }; struct cookie_manager_ctx cm_ctx; struct cookie_manager_entry { bool user_delete; treeview_node *entry; struct treeview_field_data data[COOKIE_M_N_FIELDS - 1]; }; struct treeview_walk_ctx { const char *title; size_t title_len; struct cookie_manager_folder *folder; struct cookie_manager_entry *entry; }; /** Callback for treeview_walk */ static nserror cookie_manager_walk_cb(void *ctx, void *node_data, enum treeview_node_type type, bool *abort) { struct treeview_walk_ctx *tw = ctx; if (type == TREE_NODE_ENTRY) { struct cookie_manager_entry *entry = node_data; if (entry->data[COOKIE_M_NAME].value_len == tw->title_len && strcmp(tw->title, entry->data[COOKIE_M_NAME].value) == 0) { /* Found what we're looking for */ tw->entry = entry; *abort = true; } } else if (type == TREE_NODE_FOLDER) { struct cookie_manager_folder *folder = node_data; if (folder->data.value_len == tw->title_len && strcmp(tw->title, folder->data.value) == 0) { /* Found what we're looking for */ tw->folder = folder; *abort = true; } } return NSERROR_OK; } /** * Find a cookie entry in the cookie manager's treeview * * \param root Search root node, or NULL to search from tree's root * \param title ID of the node to look for * \param title_len Byte length of title string * \param found Updated to the matching node's cookie maanger entry * \return NSERROR_OK on success, appropriate error otherwise */ static nserror cookie_manager_find_entry(treeview_node *root, const char *title, size_t title_len, struct cookie_manager_entry **found) { nserror err; struct treeview_walk_ctx tw = { .title = title, .title_len = title_len, .folder = NULL, .entry = NULL }; err = treeview_walk(cm_ctx.tree, root, cookie_manager_walk_cb, NULL, &tw, TREE_NODE_ENTRY); if (err != NSERROR_OK) return err; *found = tw.entry; return NSERROR_OK; } /** * Find a cookie domain folder in the cookie manager's treeview * * \param root Search root node, or NULL to search from tree's root * \param title ID of the node to look for * \param title_len Byte length of title string * \param found Updated to the matching node's cookie maanger folder * \return NSERROR_OK on success, appropriate error otherwise */ static nserror cookie_manager_find_folder(treeview_node *root, const char *title, size_t title_len, struct cookie_manager_folder **found) { nserror err; struct treeview_walk_ctx tw = { .title = title, .title_len = title_len, .folder = NULL, .entry = NULL }; err = treeview_walk(cm_ctx.tree, root, cookie_manager_walk_cb, NULL, &tw, TREE_NODE_FOLDER); if (err != NSERROR_OK) return err; *found = tw.folder; return NSERROR_OK; } /** * Free a cookie manager entry's treeview field data. * * \param e Cookie manager entry to free data from */ static void cookie_manager_free_treeview_field_data( struct cookie_manager_entry *e) { /* Eww */ free((void *)e->data[COOKIE_M_NAME].value); free((void *)e->data[COOKIE_M_CONTENT].value); free((void *)e->data[COOKIE_M_DOMAIN].value); free((void *)e->data[COOKIE_M_PATH].value); free((void *)e->data[COOKIE_M_EXPIRES].value); free((void *)e->data[COOKIE_M_LAST_USED].value); } /** * Build a cookie manager treeview field from given text * * \param field Cookie manager treeview field to build * \param data Cookie manager entry field data to set * \param value Text to set in field, ownership yielded * \return NSERROR_OK on success, appropriate error otherwise */ static inline nserror cookie_manager_field_builder(enum cookie_manager_field field, struct treeview_field_data *data, const char *value) { data->field = cm_ctx.fields[field].field; data->value = value; data->value_len = (value != NULL) ? strlen(value) : 0; return NSERROR_OK; } /** * Build a cookie manager treeview field from given time * * The time should be converted to text in the users locacle * * \todo This should probably generate the user text using localtime * and strftime with the c format specifier. Currently ctime will * always generate output in the C locale. * * \param field Cookie manager treeview field to build * \param fdata Cookie manager entry field data to set * \param value Time to show in field * \return NSERROR_OK on success, appropriate error otherwise */ static inline nserror cookie_manager_field_builder_time(enum cookie_manager_field field, struct treeview_field_data *fdata, const time_t *value) { const char *date; char *date2; fdata->field = cm_ctx.fields[field].field; date = ctime(value); date2 = strdup(date); if (date2 == NULL) { fdata->value = NULL; fdata->value_len = 0; } else { assert(date2[24] == '\n'); date2[24] = '\0'; fdata->value = date2; fdata->value_len = strlen(date2); } return NSERROR_OK; } /** * Set a cookie manager entry's data from the cookie_data. * * \param e Cookie manager entry to set up * \param data Data associated with entry's cookie * \return NSERROR_OK on success, appropriate error otherwise */ static nserror cookie_manager_set_treeview_field_data(struct cookie_manager_entry *e, const struct cookie_data *data) { assert(e != NULL); assert(data != NULL); /* Set the fields up */ cookie_manager_field_builder(COOKIE_M_NAME, &e->data[COOKIE_M_NAME], strdup(data->name)); cookie_manager_field_builder(COOKIE_M_CONTENT, &e->data[COOKIE_M_CONTENT], strdup(data->value)); cookie_manager_field_builder(COOKIE_M_DOMAIN, &e->data[COOKIE_M_DOMAIN], strdup(data->domain)); cookie_manager_field_builder(COOKIE_M_PATH, &e->data[COOKIE_M_PATH], strdup(data->path)); /* Set the Expires date field */ if (data->expires == -1) { cookie_manager_field_builder(COOKIE_M_EXPIRES, &e->data[COOKIE_M_EXPIRES], strdup(messages_get("CookieManagerSession"))); } else { cookie_manager_field_builder_time(COOKIE_M_EXPIRES, &e->data[COOKIE_M_EXPIRES], &data->expires); } /* Set the Last used date field */ cookie_manager_field_builder_time(COOKIE_M_LAST_USED, &e->data[COOKIE_M_LAST_USED], &data->last_used); /* Set the Restrictions text */ if (data->secure && data->http_only) { e->data[COOKIE_M_RESTRICTIONS] = cm_ctx.values[COOKIE_M_HTTPS]; } else if (data->secure) { e->data[COOKIE_M_RESTRICTIONS] = cm_ctx.values[COOKIE_M_SECURE]; } else if (data->http_only) { e->data[COOKIE_M_RESTRICTIONS] = cm_ctx.values[COOKIE_M_HTTP]; } else { e->data[COOKIE_M_RESTRICTIONS] = cm_ctx.values[COOKIE_M_NONE]; } /* Set the Version text */ switch (data->version) { case COOKIE_NETSCAPE: e->data[COOKIE_M_VERSION] = cm_ctx.values[COOKIE_M_NETSCAPE]; break; case COOKIE_RFC2109: e->data[COOKIE_M_VERSION] = cm_ctx.values[COOKIE_M_RFC2109]; break; case COOKIE_RFC2965: e->data[COOKIE_M_VERSION] = cm_ctx.values[COOKIE_M_RFC2965]; break; } return NSERROR_OK; } /** * Creates an empty tree entry for a cookie, and links it into the tree. * * All information is copied from the cookie_data, and as such can * be edited and should be freed. * * \param parent the node to link to * \param data the cookie data to use * \return NSERROR_OK on success, appropriate error otherwise */ static nserror cookie_manager_create_cookie_node( struct cookie_manager_folder *parent, const struct cookie_data *data) { nserror err; struct cookie_manager_entry *cookie; /* Create new cookie manager entry */ cookie = malloc(sizeof(struct cookie_manager_entry)); if (cookie == NULL) { return NSERROR_NOMEM; } cookie->user_delete = false; err = cookie_manager_set_treeview_field_data(cookie, data); if (err != NSERROR_OK) { free(cookie); return err; } err = treeview_create_node_entry(cm_ctx.tree, &(cookie->entry), parent->folder, TREE_REL_FIRST_CHILD, cookie->data, cookie, cm_ctx.built ? TREE_OPTION_NONE : TREE_OPTION_SUPPRESS_RESIZE | TREE_OPTION_SUPPRESS_REDRAW); if (err != NSERROR_OK) { cookie_manager_free_treeview_field_data(cookie); free(cookie); return err; } return NSERROR_OK; } /** * Updates a cookie manager entry for updated cookie_data. * * All information is copied from the cookie_data, and as such can * be edited and should be freed. * * \param e the entry to update * \param data the cookie data to use * \return NSERROR_OK on success, appropriate error otherwise */ static nserror cookie_manager_update_cookie_node( struct cookie_manager_entry *e, const struct cookie_data *data) { nserror err; assert(e != NULL); /* Reset to defaults */ e->user_delete = false; cookie_manager_free_treeview_field_data(e); /* Set new field values from the cookie_data */ err = cookie_manager_set_treeview_field_data(e, data); if (err != NSERROR_OK) { return err; } /* Update the treeview */ err = treeview_update_node_entry(cm_ctx.tree, e->entry, e->data, e); if (err != NSERROR_OK) { return err; } return NSERROR_OK; } /** * Creates an empty tree folder for a cookie domain, and links it into the tree. * * All information is copied from the cookie_data, and as such can * be edited and should be freed. * * \param folder updated to the new folder * \param data the cookie data to use * \return NSERROR_OK on success, appropriate error otherwise */ static nserror cookie_manager_create_domain_folder( struct cookie_manager_folder **folder, const struct cookie_data *data) { nserror err; struct cookie_manager_folder *f; /* Create new cookie manager entry */ f = malloc(sizeof(struct cookie_manager_folder)); if (f == NULL) { return NSERROR_NOMEM; } f->data.field = cm_ctx.fields[COOKIE_M_N_FIELDS - 1].field; f->data.value = strdup(data->domain); f->data.value_len = (f->data.value != NULL) ? strlen(data->domain) : 0; err = treeview_create_node_folder(cm_ctx.tree, &(f->folder), NULL, TREE_REL_FIRST_CHILD, &f->data, f, cm_ctx.built ? TREE_OPTION_NONE : TREE_OPTION_SUPPRESS_RESIZE | TREE_OPTION_SUPPRESS_REDRAW); if (err != NSERROR_OK) { free((void *)f->data.value); free(f); return err; } *folder = f; return NSERROR_OK; } /* exported interface documented in cookie_manager.h */ bool cookie_manager_add(const struct cookie_data *data) { struct cookie_manager_folder *parent = NULL; struct cookie_manager_entry *cookie = NULL; nserror err; assert(data != NULL); /* If we don't have a cookie manager at the moment, just return true */ if (cm_ctx.tree == NULL) return true; err = cookie_manager_find_folder(NULL, data->domain, strlen(data->domain), &parent); if (err != NSERROR_OK) { return false; } if (parent == NULL) { /* Need to create domain directory */ err = cookie_manager_create_domain_folder(&parent, data); if (err != NSERROR_OK || parent == NULL) return false; } /* Create cookie node */ err = cookie_manager_find_entry(parent->folder, data->name, strlen(data->name), &cookie); if (err != NSERROR_OK) return false; if (cookie == NULL) { err = cookie_manager_create_cookie_node(parent, data); } else { err = cookie_manager_update_cookie_node(cookie, data); } if (err != NSERROR_OK) return false; return true; } /* exported interface documented in cookie_manager.h */ void cookie_manager_remove(const struct cookie_data *data) { struct cookie_manager_folder *parent = NULL; struct cookie_manager_entry *cookie = NULL; nserror err; assert(data != NULL); /* If we don't have a cookie manager at the moment, just return */ if (cm_ctx.tree == NULL) return; err = cookie_manager_find_folder(NULL, data->domain, strlen(data->domain), &parent); if (err != NSERROR_OK || parent == NULL) { /* Nothing to delete */ return; } err = cookie_manager_find_entry(parent->folder, data->name, strlen(data->name), &cookie); if (err != NSERROR_OK || cookie == NULL) { /* Nothing to delete */ return; } /* Delete the node */ treeview_delete_node(cm_ctx.tree, cookie->entry, TREE_OPTION_NONE); } /* exported interface documented in cookie_manager.h */ nserror cookie_manager_set_search_string( const char *string) { /* If we don't have a cookie manager at the moment, just return */ if (cm_ctx.tree == NULL) { return NSERROR_NOT_FOUND; } return treeview_set_search_string(cm_ctx.tree, string); } /** * Initialise the treeview entry feilds * * \return true on success, false on memory exhaustion */ static nserror cookie_manager_init_entry_fields(void) { int i; const char *label; for (i = 0; i < COOKIE_M_N_FIELDS; i++) cm_ctx.fields[i].field = NULL; cm_ctx.fields[COOKIE_M_NAME].flags = TREE_FLAG_DEFAULT; label = "TreeviewLabelName"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_NAME].field) != lwc_error_ok) { goto error; } cm_ctx.fields[COOKIE_M_CONTENT].flags = TREE_FLAG_SHOW_NAME; label = "TreeviewLabelContent"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_CONTENT].field) != lwc_error_ok) { goto error; } cm_ctx.fields[COOKIE_M_DOMAIN].flags = TREE_FLAG_SHOW_NAME | TREE_FLAG_SEARCHABLE; label = "TreeviewLabelDomain"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_DOMAIN].field) != lwc_error_ok) { goto error; } cm_ctx.fields[COOKIE_M_PATH].flags = TREE_FLAG_SHOW_NAME; label = "TreeviewLabelPath"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_PATH].field) != lwc_error_ok) { goto error; } cm_ctx.fields[COOKIE_M_EXPIRES].flags = TREE_FLAG_SHOW_NAME; label = "TreeviewLabelExpires"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_EXPIRES].field) != lwc_error_ok) { goto error; } cm_ctx.fields[COOKIE_M_LAST_USED].flags = TREE_FLAG_SHOW_NAME; label = "TreeviewLabelLastUsed"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_LAST_USED].field) != lwc_error_ok) { goto error; } cm_ctx.fields[COOKIE_M_RESTRICTIONS].flags = TREE_FLAG_SHOW_NAME; label = "TreeviewLabelRestrictions"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_RESTRICTIONS].field) != lwc_error_ok) { goto error; } cm_ctx.fields[COOKIE_M_VERSION].flags = TREE_FLAG_SHOW_NAME; label = "TreeviewLabelVersion"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_VERSION].field) != lwc_error_ok) { goto error; } cm_ctx.fields[COOKIE_M_DOMAIN_FOLDER].flags = TREE_FLAG_DEFAULT; label = "TreeviewLabelDomainFolder"; label = messages_get(label); if (lwc_intern_string(label, strlen(label), &cm_ctx.fields[COOKIE_M_DOMAIN_FOLDER].field) != lwc_error_ok) { return false; } return NSERROR_OK; error: for (i = 0; i < COOKIE_M_N_FIELDS; i++) if (cm_ctx.fields[i].field != NULL) lwc_string_unref(cm_ctx.fields[i].field); return NSERROR_UNKNOWN; } /** * Initialise the common entry values * * \return true on success, false on memory exhaustion */ static nserror cookie_manager_init_common_values(void) { const char *temp; /* Set the Restrictions text */ temp = messages_get("CookieManagerHTTPS"); cookie_manager_field_builder(COOKIE_M_RESTRICTIONS, &cm_ctx.values[COOKIE_M_HTTPS], strdup(temp)); temp = messages_get("CookieManagerSecure"); cookie_manager_field_builder(COOKIE_M_RESTRICTIONS, &cm_ctx.values[COOKIE_M_SECURE], strdup(temp)); temp = messages_get("CookieManagerHTTP"); cookie_manager_field_builder(COOKIE_M_RESTRICTIONS, &cm_ctx.values[COOKIE_M_HTTP], strdup(temp)); temp = messages_get("None"); cookie_manager_field_builder(COOKIE_M_RESTRICTIONS, &cm_ctx.values[COOKIE_M_NONE], strdup(temp)); /* Set the Cookie version text */ assert(COOKIE_NETSCAPE == 0); temp = messages_get("TreeVersion0"); cookie_manager_field_builder(COOKIE_M_VERSION, &cm_ctx.values[COOKIE_M_NETSCAPE], strdup(temp)); assert(COOKIE_RFC2109 == 1); temp = messages_get("TreeVersion1"); cookie_manager_field_builder(COOKIE_M_VERSION, &cm_ctx.values[COOKIE_M_RFC2109], strdup(temp)); assert(COOKIE_RFC2965 == 2); temp = messages_get("TreeVersion2"); cookie_manager_field_builder(COOKIE_M_VERSION, &cm_ctx.values[COOKIE_M_RFC2965], strdup(temp)); return NSERROR_OK; } /** * Delete cookie manager entries (and optionally delete from urldb) * * \param e Cookie manager entry to delete. */ static void cookie_manager_delete_entry(struct cookie_manager_entry *e) { const char *domain; const char *path; const char *name; if (e->user_delete) { /* Delete the cookie from URLdb */ domain = e->data[COOKIE_M_DOMAIN].value; path = e->data[COOKIE_M_PATH].value; name = e->data[COOKIE_M_NAME].value; if ((domain != NULL) && (path != NULL) && (name != NULL)) { urldb_delete_cookie(domain, path, name); } else { NSLOG(netsurf, INFO, "Delete cookie fail: ""need domain, path, and name."); } } /* Delete the cookie manager entry */ cookie_manager_free_treeview_field_data(e); free(e); } static nserror cookie_manager_tree_node_folder_cb( struct treeview_node_msg msg, void *data) { struct cookie_manager_folder *f = data; switch (msg.msg) { case TREE_MSG_NODE_DELETE: free(f); break; case TREE_MSG_NODE_EDIT: break; case TREE_MSG_NODE_LAUNCH: break; } return NSERROR_OK; } static nserror cookie_manager_tree_node_entry_cb( struct treeview_node_msg msg, void *data) { struct cookie_manager_entry *e = data; switch (msg.msg) { case TREE_MSG_NODE_DELETE: e->entry = NULL; e->user_delete = msg.data.delete.user; cookie_manager_delete_entry(e); break; case TREE_MSG_NODE_EDIT: break; case TREE_MSG_NODE_LAUNCH: break; } return NSERROR_OK; } struct treeview_callback_table cm_tree_cb_t = { .folder = cookie_manager_tree_node_folder_cb, .entry = cookie_manager_tree_node_entry_cb }; /* Exported interface, documented in cookie_manager.h */ nserror cookie_manager_init(struct core_window_callback_table *cw_t, void *core_window_handle) { nserror err; err = treeview_init(); if (err != NSERROR_OK) { return err; } NSLOG(netsurf, INFO, "Generating cookie manager data"); /* Init. cookie manager treeview entry fields */ err = cookie_manager_init_entry_fields(); if (err != NSERROR_OK) { cm_ctx.tree = NULL; return err; } /* Init. common treeview field values */ err = cookie_manager_init_common_values(); if (err != NSERROR_OK) { cm_ctx.tree = NULL; return err; } /* Create the cookie manager treeview */ err = treeview_create(&cm_ctx.tree, &cm_tree_cb_t, COOKIE_M_N_FIELDS, cm_ctx.fields, cw_t, core_window_handle, TREEVIEW_NO_MOVES | TREEVIEW_DEL_EMPTY_DIRS | TREEVIEW_SEARCHABLE); if (err != NSERROR_OK) { cm_ctx.tree = NULL; return err; } /* Load the cookies */ urldb_iterate_cookies(cookie_manager_add); /* Cookie manager is built * We suppress the treeview height callback on entry insertion before * the treeview is built. */ cm_ctx.built = true; /* Inform client of window height */ treeview_get_height(cm_ctx.tree); NSLOG(netsurf, INFO, "Generated cookie manager data"); return NSERROR_OK; } /* Exported interface, documented in cookie_manager.h */ nserror cookie_manager_fini(void) { int i; nserror err; NSLOG(netsurf, INFO, "Finalising cookie manager"); cm_ctx.built = false; /* Destroy the cookie manager treeview */ err = treeview_destroy(cm_ctx.tree); cm_ctx.tree = NULL; /* Free cookie manager treeview entry fields */ for (i = 0; i < COOKIE_M_N_FIELDS; i++) if (cm_ctx.fields[i].field != NULL) lwc_string_unref(cm_ctx.fields[i].field); /* Free cookie manager treeview common entry values */ for (i = 0; i < COOKIE_M_N_VALUES; i++) if (cm_ctx.values[i].value != NULL) free((void *) cm_ctx.values[i].value); err = treeview_fini(); if (err != NSERROR_OK) { return err; } NSLOG(netsurf, INFO, "Finalised cookie manager"); return err; } /* Exported interface, documented in cookie_manager.h */ void cookie_manager_redraw(int x, int y, struct rect *clip, const struct redraw_context *ctx) { treeview_redraw(cm_ctx.tree, x, y, clip, ctx); } /* Exported interface, documented in cookie_manager.h */ void cookie_manager_mouse_action(enum browser_mouse_state mouse, int x, int y) { treeview_mouse_action(cm_ctx.tree, mouse, x, y); } /* Exported interface, documented in cookie_manager.h */ bool cookie_manager_keypress(uint32_t key) { return treeview_keypress(cm_ctx.tree, key); } /* Exported interface, documented in cookie_manager.h */ bool cookie_manager_has_selection(void) { return treeview_has_selection(cm_ctx.tree); } /* Exported interface, documented in cookie_manager.h */ nserror cookie_manager_expand(bool only_folders) { return treeview_expand(cm_ctx.tree, only_folders); } /* Exported interface, documented in cookie_manager.h */ nserror cookie_manager_contract(bool all) { return treeview_contract(cm_ctx.tree, all); }