static gint _cookie_permission_manager_ask_for_policy(CookiePermissionManager *self, MidoriView *inView, SoupMessage *inMessage, GSList *inUnknownCookies) { /* Ask user for policy of unkndown domains in an undistracting way. * The idea is to put the message not in a modal window but into midori's info bar. * Then we'll set up our own GMainLoop to simulate a modal info bar. We need to * connect to all possible signals of info bar, web view and so on to handle user's * decision and to get out of our own GMainLoop. After that webkit resumes processing * data. */ CookiePermissionManagerPrivate *priv=self->priv; GtkWidget *infobar; /* FIXME: Find a way to add "details" widget */ #ifndef NO_INFOBAR_DETAILS GtkWidget *widget; GtkWidget *contentArea; GtkWidget *vbox, *hbox; GtkWidget *expander; GtkListStore *listStore; GtkTreeIter listIter; GtkWidget *scrolled; GtkWidget *list; GtkCellRenderer *renderer; GtkTreeViewColumn *column; #endif gchar *text; gint numberDomains, numberCookies; GSList *sortedCookies, *cookies; WebKitWebView *webkitView; CookiePermissionManagerModalInfobar *modalInfo; /* Get webkit view of midori view */ webkitView=WEBKIT_WEB_VIEW(midori_view_get_web_view(inView)); modalInfo=g_new0(CookiePermissionManagerModalInfobar, 1); /* Create a copy of cookies and sort them */ sortedCookies=_cookie_permission_manager_get_number_domains_and_cookies(self, inUnknownCookies, &numberDomains, &numberCookies); /* FIXME: Find a way to add "details" widget */ #ifndef NO_INFOBAR_DETAILS /* Create list model and fill in data */ listStore=gtk_list_store_new(N_COLUMN, G_TYPE_STRING, /* DOMAIN_COLUMN */ G_TYPE_STRING, /* PATH_COLUMN */ G_TYPE_STRING, /* NAME_COLUMN */ G_TYPE_STRING, /* VALUE_COLUMN */ G_TYPE_STRING /* EXPIRE_DATE_COLUMN */); for(cookies=sortedCookies; cookies; cookies=cookies->next) { SoupCookie *cookie=(SoupCookie*)cookies->data; SoupDate *cookieDate=soup_cookie_get_expires(cookie); if(cookieDate) text=soup_date_to_string(cookieDate, SOUP_DATE_HTTP); else text=g_strdup(_("Till session end")); gtk_list_store_append(listStore, &listIter); gtk_list_store_set(listStore, &listIter, DOMAIN_COLUMN, soup_cookie_get_domain(cookie), PATH_COLUMN, soup_cookie_get_path(cookie), NAME_COLUMN, soup_cookie_get_name(cookie), VALUE_COLUMN, soup_cookie_get_value(cookie), EXPIRE_DATE_COLUMN, text, -1); g_free(text); } #endif /* Create description text */ if(numberDomains==1) { const gchar *cookieDomain=soup_cookie_get_domain((SoupCookie*)sortedCookies->data); if(*cookieDomain=='.') cookieDomain++; if(numberCookies>1) text=g_strdup_printf(_("The website %s wants to store %d cookies."), cookieDomain, numberCookies); else text=g_strdup_printf(_("The website %s wants to store a cookie."), cookieDomain); } else { text=g_strdup_printf(_("Multiple websites want to store %d cookies in total."), numberCookies); } /* Create info bar message and buttons */ infobar=midori_view_add_info_bar(inView, GTK_MESSAGE_QUESTION, text, G_CALLBACK(_cookie_permission_manager_on_infobar_policy_decision), NULL, _("_Accept"), COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT, _("Accept for this _session"), COOKIE_PERMISSION_MANAGER_POLICY_ACCEPT_FOR_SESSION, _("De_ny"), COOKIE_PERMISSION_MANAGER_POLICY_BLOCK, _("Deny _this time"), COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED, NULL); g_free(text); /* midori_view_add_info_bar() in version 0.4.8 expects a GObject as user data * but I don't want to create an GObject just for a simple struct. So set object * data by our own */ g_object_set_data_full(G_OBJECT(infobar), "cookie-permission-manager-infobar-data", modalInfo, (GDestroyNotify)g_free); /* FIXME: Find a way to add "details" widget */ #ifndef NO_INFOBAR_DETAILS /* Get content area of infobar */ contentArea=gtk_info_bar_get_content_area(GTK_INFO_BAR(infobar)); /* Create list and set up columns of list */ list=gtk_tree_view_new_with_model(GTK_TREE_MODEL(listStore)); #ifndef HAVE_GTK3 gtk_widget_set_size_request(list, -1, 100); #endif renderer=gtk_cell_renderer_text_new(); column=gtk_tree_view_column_new_with_attributes(_("Domain"), renderer, "text", DOMAIN_COLUMN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(list), column); renderer=gtk_cell_renderer_text_new(); column=gtk_tree_view_column_new_with_attributes(_("Path"), renderer, "text", PATH_COLUMN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(list), column); renderer=gtk_cell_renderer_text_new(); column=gtk_tree_view_column_new_with_attributes(_("Name"), renderer, "text", NAME_COLUMN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(list), column); renderer=gtk_cell_renderer_text_new(); column=gtk_tree_view_column_new_with_attributes(_("Value"), renderer, "text", VALUE_COLUMN, NULL); g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, "width-chars", 30, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(list), column); renderer=gtk_cell_renderer_text_new(); column=gtk_tree_view_column_new_with_attributes(_("Expire date"), renderer, "text", EXPIRE_DATE_COLUMN, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(list), column); scrolled=gtk_scrolled_window_new(NULL, NULL); #ifdef HAVE_GTK3 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolled), 100); #endif gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add(GTK_CONTAINER(scrolled), list); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(expander), scrolled); gtk_widget_show_all(vbox); gtk_container_add(GTK_CONTAINER(contentArea), vbox); /* Set state of expander based on config 'show-details-when-ask' */ gtk_expander_set_expanded(GTK_EXPANDER(expander), midori_extension_get_boolean(priv->extension, "show-details-when-ask")); g_signal_connect_swapped(expander, "notify::expanded", G_CALLBACK(_cookie_permission_manager_when_ask_expander_changed), self); #endif /* Show all widgets of info bar */ gtk_widget_show_all(infobar); /* Connect signals to quit main loop */ g_signal_connect(webkitView, "navigation-policy-decision-requested", G_CALLBACK(_cookie_permission_manager_on_infobar_webview_navigate), infobar); g_signal_connect(infobar, "destroy", G_CALLBACK(_cookie_permission_manager_on_infobar_destroy), modalInfo); /* Let info bar be modal and set response to default */ modalInfo->response=COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED; modalInfo->mainLoop=g_main_loop_new(NULL, FALSE); GDK_THREADS_LEAVE(); g_main_loop_run(modalInfo->mainLoop); GDK_THREADS_ENTER(); g_main_loop_unref(modalInfo->mainLoop); modalInfo->mainLoop=NULL; /* Disconnect signal handler to webkit's web view */ g_signal_handlers_disconnect_by_func(webkitView, G_CALLBACK(_cookie_permission_manager_on_infobar_webview_navigate), infobar); /* Store user's decision in database if it is not a temporary block. * We use the already sorted list of cookies to prevent multiple * updates of database for the same domain. This sorted list is a copy * to avoid a reorder of cookies */ if(modalInfo->response!=COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED) { const gchar *lastDomain=NULL; /* Iterate through cookies and store decision for each domain once */ for(cookies=sortedCookies; cookies; cookies=cookies->next) { SoupCookie *cookie=(SoupCookie*)cookies->data; const gchar *cookieDomain=soup_cookie_get_domain(cookie); if(*cookieDomain=='.') cookieDomain++; /* Store decision if new domain found while iterating through cookies */ if(!lastDomain || g_ascii_strcasecmp(lastDomain, cookieDomain)!=0) { gchar *sql; gchar *error=NULL; gint success; sql=sqlite3_mprintf("INSERT OR REPLACE INTO policies (domain, value) VALUES ('%q', %d);", cookieDomain, modalInfo->response); success=sqlite3_exec(priv->database, sql, NULL, NULL, &error); if(success!=SQLITE_OK) g_warning(_("SQL fails: %s"), error); if(error) sqlite3_free(error); sqlite3_free(sql); lastDomain=cookieDomain; } } } /* Free up allocated resources */ g_slist_free(sortedCookies); /* Return response */ return(modalInfo->response==COOKIE_PERMISSION_MANAGER_POLICY_UNDETERMINED ? COOKIE_PERMISSION_MANAGER_POLICY_BLOCK : modalInfo->response); }
/* FIXME: moar tests! */ static void do_cookies_parsing_test (void) { SoupSession *session; SoupMessage *msg; SoupCookieJar *jar; GSList *cookies, *iter; SoupCookie *cookie; gboolean got1, got2, got3; debug_printf (1, "\nSoupCookie parsing test\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR); jar = SOUP_COOKIE_JAR (soup_session_get_feature (session, SOUP_TYPE_COOKIE_JAR)); /* "httponly" is case-insensitive, and its value (if any) is ignored */ msg = soup_message_new_from_uri ("GET", first_party_uri); soup_message_headers_append (msg->request_headers, "Echo-Set-Cookie", "one=1; httponly; max-age=100"); soup_session_send_message (session, msg); g_object_unref (msg); msg = soup_message_new_from_uri ("GET", first_party_uri); soup_message_headers_append (msg->request_headers, "Echo-Set-Cookie", "two=2; HttpOnly; max-age=100"); soup_session_send_message (session, msg); g_object_unref (msg); msg = soup_message_new_from_uri ("GET", first_party_uri); soup_message_headers_append (msg->request_headers, "Echo-Set-Cookie", "three=3; httpONLY=Wednesday; max-age=100"); soup_session_send_message (session, msg); g_object_unref (msg); cookies = soup_cookie_jar_get_cookie_list (jar, first_party_uri, TRUE); got1 = got2 = got3 = FALSE; for (iter = cookies; iter; iter = iter->next) { cookie = iter->data; if (!strcmp (soup_cookie_get_name (cookie), "one")) { got1 = TRUE; if (!soup_cookie_get_http_only (cookie)) { debug_printf (1, " cookie 1 is not HttpOnly!\n"); errors++; } if (!soup_cookie_get_expires (cookie)) { debug_printf (1, " cookie 1 did not fully parse!\n"); errors++; } } else if (!strcmp (soup_cookie_get_name (cookie), "two")) { got2 = TRUE; if (!soup_cookie_get_http_only (cookie)) { debug_printf (1, " cookie 2 is not HttpOnly!\n"); errors++; } if (!soup_cookie_get_expires (cookie)) { debug_printf (1, " cookie 3 did not fully parse!\n"); errors++; } } else if (!strcmp (soup_cookie_get_name (cookie), "three")) { got3 = TRUE; if (!soup_cookie_get_http_only (cookie)) { debug_printf (1, " cookie 3 is not HttpOnly!\n"); errors++; } if (!soup_cookie_get_expires (cookie)) { debug_printf (1, " cookie 3 did not fully parse!\n"); errors++; } } else { debug_printf (1, " got unexpected cookie '%s'\n", soup_cookie_get_name (cookie)); errors++; } soup_cookie_free (cookie); } g_slist_free (cookies); if (!got1) { debug_printf (1, " didn't get cookie 1\n"); errors++; } if (!got2) { debug_printf (1, " didn't get cookie 2\n"); errors++; } if (!got3) { debug_printf (1, " didn't get cookie 3\n"); errors++; } soup_test_session_abort_unref (session); }