Example #1
0
static void
do_get (OtTrivialHttpd    *self,
        SoupServer        *server,
        SoupMessage       *msg,
        const char        *path,
        SoupClientContext *context)
{
  char *slash;
  int ret;
  struct stat stbuf;

  httpd_log (self, "serving %s\n", path);

  if (opt_expected_cookies)
    {
      GSList *cookies = soup_cookies_from_request (msg);
      GSList *l;
      int i;

      for (i = 0 ; opt_expected_cookies[i] != NULL; i++)
        {
          gboolean found = FALSE;
          gchar *k = opt_expected_cookies[i];
          gchar *v = strchr (k, '=') + 1;

          for (l = cookies;  l != NULL ; l = g_slist_next (l))
            {
              SoupCookie *c = l->data;

              if (!strncmp (k, soup_cookie_get_name (c), v - k - 1) &&
                  !strcmp (v, soup_cookie_get_value (c)))
                {
                  found = TRUE;
                  break;
                }
            }

          if (!found)
            {
              httpd_log (self, "Expected cookie not found %s\n", k);
              soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
              soup_cookies_free (cookies);
              goto out;
            }
        }
      soup_cookies_free (cookies);
    }

  if (opt_expected_headers)
    {
      for (int i = 0 ; opt_expected_headers[i] != NULL; i++)
        {
          const gchar *kv = opt_expected_headers[i];
          const gchar *eq = strchr (kv, '=');

          g_assert (eq);

          {
            g_autofree char *k = g_strndup (kv, eq - kv);
            const gchar *expected_v = eq + 1;
            const gchar *found_v = soup_message_headers_get_one (msg->request_headers, k);

            if (!found_v)
              {
                httpd_log (self, "Expected header not found %s\n", k);
                soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
                goto out;
              }
            if (strcmp (found_v, expected_v) != 0)
              {
                httpd_log (self, "Expected header %s: %s but found %s\n", k, expected_v, found_v);
                soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
                goto out;
              }
          }
        }
    }

  if (strstr (path, "../") != NULL)
    {
      soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
      goto out;
    }

  if (opt_random_500s_percentage > 0 &&
      emitted_random_500s_count < opt_random_500s_max &&
      g_random_int_range (0, 100) < opt_random_500s_percentage)
    {
      emitted_random_500s_count++;
      soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
      goto out;
    }

  while (path[0] == '/')
    path++;

  do
    ret = fstatat (self->root_dfd, path, &stbuf, 0);
  while (ret == -1 && errno == EINTR);
  if (ret == -1)
    {
      if (errno == EPERM)
        soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
      else if (errno == ENOENT)
        soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
      else
        soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
      goto out;
    }

  if (!is_safe_to_access (&stbuf))
    {
      soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
      goto out;
    }

  if (S_ISDIR (stbuf.st_mode))
    {
      slash = strrchr (path, '/');
      if (!slash || slash[1])
        {
          g_autofree char *redir_uri = NULL;

          redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path);
          soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY,
                                     redir_uri);
        }
      else
        {
          g_autofree char *index_realpath = g_strconcat (path, "/index.html", NULL);
          if (fstatat (self->root_dfd, index_realpath, &stbuf, 0) != -1)
            {
              g_autofree char *index_path = g_strconcat (path, "/index.html", NULL);
              do_get (self, server, msg, index_path, context);
            }
          else
            {
              GString *listing = get_directory_listing (self->root_dfd, path);
              soup_message_set_response (msg, "text/html",
                                         SOUP_MEMORY_TAKE,
                                         listing->str, listing->len);
              soup_message_set_status (msg, SOUP_STATUS_OK);
              g_string_free (listing, FALSE);
            }
        }
    }
  else 
    {
      if (!S_ISREG (stbuf.st_mode))
        {
          soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
          goto out;
        }
      
      if (msg->method == SOUP_METHOD_GET)
        {
          glnx_autofd int fd = -1;
          g_autoptr(GMappedFile) mapping = NULL;
          gsize buffer_length, file_size;
          SoupRange *ranges;
          int ranges_length;
          gboolean have_ranges;

          fd = openat (self->root_dfd, path, O_RDONLY | O_CLOEXEC);
          if (fd < 0)
            {
              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
              goto out;
            }

          mapping = g_mapped_file_new_from_fd (fd, FALSE, NULL);
          if (!mapping)
            {
              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
              goto out;
            }
          (void) close (fd); fd = -1;

          file_size = g_mapped_file_get_length (mapping);
          have_ranges = soup_message_headers_get_ranges(msg->request_headers, file_size, &ranges, &ranges_length);
          if (opt_force_ranges && !have_ranges && g_strrstr (path, "/objects") != NULL)
            {
              SoupSocket *sock;
              buffer_length = file_size/2;
              soup_message_headers_set_content_length (msg->response_headers, file_size);
              soup_message_headers_append (msg->response_headers,
                                           "Connection", "close");

              /* soup-message-io will wait for us to add
               * another chunk after the first, to fill out
               * the declared Content-Length. Instead, we
               * forcibly close the socket at that point.
               */
              sock = soup_client_context_get_socket (context);
              g_signal_connect (msg, "wrote-chunk", G_CALLBACK (close_socket), sock);
            }
          else
            buffer_length = file_size;

          if (have_ranges)
            {
              if (ranges_length > 0 && ranges[0].start >= file_size)
                {
                  soup_message_set_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE);
                  soup_message_headers_free_ranges (msg->request_headers, ranges);
                  goto out;
                }
              soup_message_headers_free_ranges (msg->request_headers, ranges);
            }
          if (buffer_length > 0)
            {
              SoupBuffer *buffer;

              buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
                                                   buffer_length,
                                                   g_mapped_file_ref (mapping),
                                                   (GDestroyNotify)g_mapped_file_unref);
              soup_message_body_append_buffer (msg->response_body, buffer);
              soup_buffer_free (buffer);
            }
        }
      else /* msg->method == SOUP_METHOD_HEAD */
        {
          g_autofree char *length = NULL;

          /* We could just use the same code for both GET and
           * HEAD (soup-message-server-io.c will fix things up).
           * But we'll optimize and avoid the extra I/O.
           */
          length = g_strdup_printf ("%lu", (gulong)stbuf.st_size);
          soup_message_headers_append (msg->response_headers,
                                       "Content-Length", length);
        }
      soup_message_set_status (msg, SOUP_STATUS_OK);
    }
 out:
  {
    guint status = 0;
    g_autofree gchar *reason = NULL;

    g_object_get (msg,
                  "status-code", &status,
                  "reason-phrase", &reason,
                  NULL);
    httpd_log (self, "  status: %s (%u)\n", reason, status);
  }
  return;
}
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);
}