Revision 53f2e14fc0fc015621bb0f49ea216c2c513ee2eb authored by johannes hanika on 23 December 2016, 10:38:36 UTC, committed by johannes hanika on 23 December 2016, 10:38:36 UTC
1 parent 4d54ba5
Raw File
http_server.c
/*
 *    This file is part of darktable,
 *    copyright (c) 2015 tobias ellinghaus.
 *
 *    darktable 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, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    darktable 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 darktable.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <glib/gi18n.h>
#include <stdio.h>
#include <stdlib.h>

#include "common/darktable.h"
#include "common/http_server.h"

#ifndef SOUP_CHECK_VERSION
// SOUP_CHECK_VERSION was introduced only in 2.42
#define SOUP_CHECK_VERSION(x, y, z) false
#endif

#if !SOUP_CHECK_VERSION(2, 48, 0)
#define OLD_API
#endif

typedef struct _connection_t
{
  const char *id;
  dt_http_server_t *server;
  dt_http_server_callback callback;
  gpointer user_data;
} _connection_t;

static const char reply[]
    = "<!DOCTYPE html>\n"
      "<html>\n"
      "<head>\n"
      "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
      "<title>%s</title>\n"
      "<style>\n"
      "html {\n"
      "  background-color: #575656;\n"
      "  font-family: \"Lucida Grande\",Verdana,\"Bitstream Vera Sans\",Arial,sans-serif;\n"
      "  font-size: 12px;\n"
      "  padding: 50px 100px 50px 100px;\n"
      "}\n"
      "#content {\n"
      "  background-color: #cfcece;\n"
      "  border: 1px solid #000;\n"
      "  padding: 0px 40px 40px 40px;\n"
      "}\n"
      "</style>\n"
      "<script>\n"
      "  if(window.location.hash && %d) {\n"
      "    var hash = window.location.hash.substring(1);\n"
      "    window.location.search = hash;\n"
      "  }\n"
      "</script>\n"
      "</head>\n"
      "<body><div id=\"content\">\n"
      "<div style=\"font-size: 42pt; font-weight: bold; color: white; text-align: right;\">%s</div>\n"
      "%s\n"
      "</div>\n"
      "</body>\n"
      "</html>";

static void _request_finished_callback(SoupServer *server, SoupMessage *message, SoupClientContext *client,
                                       gpointer user_data)
{
  dt_http_server_kill((dt_http_server_t *)user_data);
}

// this is always in the gui thread
static void _new_connection(SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query,
                            SoupClientContext *client, gpointer user_data)
{
  _connection_t *params = (_connection_t *)user_data;
  gboolean res = TRUE;

  if(msg->method != SOUP_METHOD_GET)
  {
    soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
    goto end;
  }

  char *page_title = g_strdup_printf(_("darktable ยป %s"), params->id);
  const char *title = _(params->id);
  const char *body = _("<h1>Sorry,</h1><p>something went wrong. Please try again.</p>");

  res = params->callback(query, params->user_data);

  if(res)
    body = _("<h1>Thank you,</h1><p>everything should have worked, you can <b>close</b> your browser now and "
             "<b>go back</b> to darktable.</p>");


  char *resp_body = g_strdup_printf(reply, page_title, res ? 0 : 1, title, body);
  size_t resp_length = strlen(resp_body);
  g_free(page_title);

  soup_message_set_status(msg, SOUP_STATUS_OK);
  soup_message_set_response(msg, "text/html", SOUP_MEMORY_TAKE, resp_body, resp_length);

end:
  if(res)
  {
    dt_http_server_t *http_server = params->server;
    soup_server_remove_handler(server, path);
    g_signal_connect(G_OBJECT(server), "request-finished", G_CALLBACK(_request_finished_callback), http_server);
  }
}

dt_http_server_t *dt_http_server_create(const int *ports, const int n_ports, const char *id,
                                        const dt_http_server_callback callback, gpointer user_data)
{
  SoupServer *httpserver = NULL;
  int port = 0;

#ifdef OLD_API
  dt_print(DT_DEBUG_CONTROL, "[http server] using the old libsoup api\n");

  for(int i = 0; i < n_ports; i++)
  {
    port = ports[i];

    SoupAddress *httpaddress = soup_address_new("127.0.0.1", port);

    if(!httpaddress)
    {
      fprintf(stderr, "couldn't create libsoup httpaddress on port %d\n", port);
      return NULL;
    }

    if(soup_address_resolve_sync(httpaddress, NULL) != SOUP_STATUS_OK)
    {
      fprintf(stderr, "error: can't resolve 127.0.0.1:%d\n", port);
      return NULL;
    }

    httpserver = soup_server_new(SOUP_SERVER_SERVER_HEADER, "darktable internal server", "interface",
                                 httpaddress, NULL);

    if(httpserver) break;

    g_object_unref(httpaddress);
  }

  if(httpserver == NULL)
  {
    fprintf(stderr, "error: couldn't create libsoup httpserver\n");
    return NULL;
  }

#else
  dt_print(DT_DEBUG_CONTROL, "[http server] using the new libsoup api\n");

  httpserver = soup_server_new(SOUP_SERVER_SERVER_HEADER, "darktable internal server", NULL);
  if(httpserver == NULL)
  {
    fprintf(stderr, "error: couldn't create libsoup httpserver\n");
    return NULL;
  }

  for(int i = 0; i < n_ports; i++)
  {
    port = ports[i];

    if(soup_server_listen_local(httpserver, port, 0, NULL)) break;

    port = 0;
  }
  if(port == 0)
  {
    fprintf(stderr, "error: can't bind to any port from our pool\n");
    return NULL;
  }

#endif

  dt_http_server_t *server = (dt_http_server_t *)malloc(sizeof(dt_http_server_t));
  server->server = httpserver;

  _connection_t *params = (_connection_t *)malloc(sizeof(_connection_t));
  params->id = id;
  params->server = server;
  params->callback = callback;
  params->user_data = user_data;

  char *path = g_strdup_printf("/%s", id);
  server->url = g_strdup_printf("http://localhost:%d/%s", port, id);

  soup_server_add_handler(httpserver, path, _new_connection, params, free);

  g_free(path);

#ifdef OLD_API
  soup_server_run_async(httpserver);
#endif

  dt_print(DT_DEBUG_CONTROL, "[http server] listening on %s\n", server->url);

  return server;
}

void dt_http_server_kill(dt_http_server_t *server)
{
  if(server->server)
  {
    soup_server_disconnect(server->server);
    g_object_unref(server->server);
    server->server = NULL;
  }
  g_free(server->url);
  server->url = NULL;
  free(server);
}

// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
back to top