/* * File: capi.c * * Copyright 2002 Jorge Arellano Cid * * This program 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 2 of the License, or * (at your option) any later version. */ /* * Cache API * This is the module that manages the cache and starts the CCC chains * to get the requests served. Kind of a broker. */ #include #include /* for pipe */ #include "msg.h" #include "capi.h" #include "IO/Url.h" #include "chain.h" #include "list.h" #include "interface.h" #include "history.h" #include "nav.h" #include "misc.h" #include "dpiapi.h" #include "../dpip/dpip.h" /* for testing dpi chat */ #include "bookmark.h" #define DEBUG_LEVEL 5 #include "debug.h" typedef struct { DilloWeb *web; DilloUrl *url; /* local copy of web->url. Used when the latter is freed */ void *bw; gchar *server; gchar *datastr; gint SockFD; gint Flags; gint DpiPipe[2]; ChainLink *InfoSend; ChainLink *InfoRecv; ChainLink *InfoPipe; gint Ref; } dpi_conn_t; /* Flags for conn */ enum { PENDING = 1, TIMEOUT = 2, /* unused */ ABORTED = 4 }; /* * Local data */ /* Data list for active dpi connections */ static dpi_conn_t **DpiConn = NULL; static gint DpiConnSize; static gint DpiConnMax = 4; /* ID for the timeout function that waits for dpid to start */ static guint dpi_conn_timeout_id = 0; /* * Forward declarations */ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2); /* ------------------------------------------------------------------------- */ /* * Create a new connection data structure */ static dpi_conn_t * Capi_dpi_conn_new(DilloWeb *web, void *bw, char *server, gchar *datastr) { dpi_conn_t *conn; conn = g_new(dpi_conn_t, 1); conn->web = web; conn->url = (web) ? a_Url_dup(web->url) : NULL; conn->bw = bw; conn->server = g_strdup(server); conn->datastr = g_strdup(datastr); conn->SockFD = -1; conn->Flags = PENDING; conn->InfoSend = a_Chain_new(); conn->InfoRecv = NULL; /* will be set later */ conn->InfoPipe = NULL; /* may be set later */ conn->Ref = 0; /* Reference count */ return conn; } /* * Increment the reference count and add to the list if not present */ static void Capi_dpi_conn_ref(dpi_conn_t *conn) { if (++conn->Ref == 1) { /* add the connection data to list */ a_List_add(DpiConn, DpiConnSize, DpiConnMax); DpiConn[DpiConnSize] = conn; DpiConnSize++; } } /* * Decrement the reference count (and remove from list when zero) */ static void Capi_dpi_conn_unref(dpi_conn_t *conn) { gint i, j; --conn->Ref; if (conn->Ref == 0) { for (i = 0; i < DpiConnSize; ++i) if (DpiConn[i] == conn) { /* remove conn preserving the list order */ for (j = i; j + 1 < DpiConnSize; ++j) DpiConn[j] = DpiConn[j + 1]; --DpiConnSize; /* free dynamic memory */ a_Url_free(conn->url); g_free(conn->server); g_free(conn->datastr); g_free(conn); break; } } } /* * Find connection data by server */ static dpi_conn_t *Capi_dpi_conn_find(gchar *server) { gint i; for (i = 0; i < DpiConnSize; ++i) if (strcmp(server, DpiConn[i]->server) == 0) return DpiConn[i]; return NULL; } /* * Resume connections that were waiting for dpid to start. */ static void Capi_dpi_conn_resume(void) { gint i; DataBuf *dbuf; for (i = 0; i < DpiConnSize; ++i) if (DpiConn[i]->Flags & PENDING) { dpi_conn_t *conn = DpiConn[i]; dbuf = a_Chain_dbuf_new(conn->datastr,(gint)strlen(conn->datastr), 0); a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); g_free(dbuf); conn->Flags &= ~PENDING; } } /* * Test dpid and resume connections if it already started. */ static gint Capi_dpi_conn_timeout(gpointer data) { static gint try = 0; gint i; ++try; DEBUG_MSG(5, "Capi_dpi_conn_timeout:: try %d\n", try); for (i = 0; i < DpiConnSize; ++i) if (DpiConn[i]->Flags & PENDING) { dpi_conn_t *conn = DpiConn[i]; a_Chain_bcb(OpStart, conn->InfoSend, conn->server, NULL); break; } for (i = 0; i < DpiConnSize; ++i) if (DpiConn[i]->Flags & PENDING) return TRUE; try = 0; dpi_conn_timeout_id = 0; return FALSE; } /* ------------------------------------------------------------------------- */ /* * Safety test: only allow dpi-urls from dpi-generated pages. */ static gint Capi_verify_dpi_url_request(DilloWeb *web) { DilloUrl *referer; gint allow = FALSE; /* test POST and GET */ if (strchr(URL_STR(web->url), '?') || URL_DATA_(web->url)) { /* safety measure: only allow dpi requests from dpi-generated urls */ if (a_Nav_stack_size(web->bw)) { referer = a_History_get_url(NAV_TOP(web->bw)); if (g_strncasecmp(URL_STR(referer), "dpi:/", 5) == 0) allow = TRUE; } } else { allow = TRUE; } if (!allow) { MSG("Capi_verify_dpi_url_request: Permission Denied!\n"); MSG(" URL_STR : %s\n", URL_STR(web->url)); if (URL_DATA_(web->url)) MSG(" URL_DATA: %s\n", URL_DATA(web->url)); } return allow; } /* * If the url belongs to a dpi server, return its name. */ gint a_Capi_url_uses_dpi(gchar *url_str, gchar **server_ptr) { gchar *p, *server = NULL; if (g_strncasecmp(url_str, "dpi:/", 5) == 0) { /* dpi prefix, get this server's name */ if ((p = strchr(url_str + 5, '/')) != NULL) server = g_strndup(url_str + 5, (guint)(p - url_str - 5)); else server = g_strdup("?"); if (strcmp(server, "bm") == 0) { g_free(server); server = g_strdup("bookmarks"); } } else if (g_strncasecmp(url_str, "ftp:/", 5) == 0) { server = g_strdup("ftp"); } else if (g_strncasecmp(url_str, "https:/", 7) == 0) { server = g_strdup("https"); } else if (g_strncasecmp(url_str, "file:", 5) == 0) { server = g_strdup("file"); } else if (g_strncasecmp(url_str, "data:", 5) == 0) { server = g_strdup("datauri"); } return ((*server_ptr = server) ? 1 : 0); } /* * Build the dpip command tag, according to URL and server. * todo: make it PROXY-aware (AFAIS, it should be easy) */ static gchar *Capi_dpi_build_cmd(DilloUrl *url, gchar *server) { gchar *cmd, *http_query; if (strcmp(server, "https") == 0) { /* Let's be kind and make the HTTP query string for the dpi */ http_query = a_Http_make_query_str(url, FALSE); cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s", "open_url", URL_STR(url), http_query); g_free(http_query); } else { /* For everyone else, the url_str is enough... */ cmd = a_Dpip_build_cmd("cmd=%s url=%s", "open_url", URL_STR(url)); } return cmd; } /* * Most used function. * todo: clean up the ad-hoc bindings with an API that allows dynamic * addition of new plugins. */ gint a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) { gint buf_size; char *cmd, *server, *buf; char *url_str = URL_STR(web->url); gint use_cache = 0, safe = 0, reload = 0, ret = 0; _MSG(" a_Capi_open_url:: web->Image=%p\n", web->Image); if (a_Capi_url_uses_dpi(url_str, &server) && !Call) { _MSG(" url_str = %s\n", url_str); if (g_strncasecmp(url_str, "dpi:/", 5) == 0) { /* safety check... */ safe = Capi_verify_dpi_url_request(web); /* make "dpi:/" prefixed urls always reload. */ a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EReload); reload = 1; } else { /* reload test */ reload = (!a_Capi_get_buf(web->url, &buf, &buf_size) || (URL_FLAGS(web->url) & URL_E2EReload)); safe = 1; } _MSG(" reload=%d URL_E2EReload=%d\n", reload, (URL_FLAGS(web->url) & URL_E2EReload)); if (safe && reload) { /* Send dpip command */ cmd = Capi_dpi_build_cmd(web->url, server); a_Capi_dpi_send_cmd(web, web->bw, cmd, server, 1); g_free(cmd); /* test the new dpi-cache connection! */ use_cache = 1; } else if (safe && !reload) { use_cache = 1; } g_free(server); } else { use_cache = 1; } if (use_cache) ret = a_Cache_open_url(web, Call, CbData); else a_Web_free(web); return ret; } /* * Get the cache's buffer for the URL, and its size. * Return: 1 cached, 0 not cached. */ gint a_Capi_get_buf(const DilloUrl *Url, gchar **PBuf, gint *BufSize) { return a_Cache_get_buf(Url, PBuf, BufSize); } /* * Send a dpi cmd. * (For instance: add_bookmark, open_url, send_preferences, ...) */ gint a_Capi_dpi_send_cmd(DilloWeb *web, void *bw, char *cmd, char *server, gint flags) { dpi_conn_t *conn; DataBuf *dbuf; if (flags & 1) { /* open a new connection to server */ /* Create a new connection data struct and add it to the list */ conn = Capi_dpi_conn_new(web, bw, server, cmd); /* start the CCC operations */ a_Capi_ccc(OpStart, 1, BCK, conn->InfoSend, conn, server); } else { /* Re-use an open connection */ conn = Capi_dpi_conn_find(server); if (conn) { /* found */ dbuf = a_Chain_dbuf_new(cmd, (gint)strlen(cmd), 0); a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); g_free(dbuf); } else { MSG(" ERROR: [a_Capi_dpi_send_cmd] No open connection found\n"); } } return 0; } /* * CCC function for the CAPI module */ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2) { dpi_conn_t *conn; a_Chain_debug_msg("a_Capi_ccc", Op, Branch, Dir); if (Branch == 1) { if (Dir == BCK) { /* Command sending branch */ switch (Op) { case OpStart: conn = Data1; Capi_dpi_conn_ref(conn); Info->LocalKey = Data1; a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 1); a_Chain_bcb(OpStart, Info, Data2, NULL); break; case OpSend: a_Chain_bcb(OpSend, Info, Data1, NULL); break; case OpEnd: a_Chain_bcb(OpEnd, Info, NULL, NULL); Capi_dpi_conn_unref(Info->LocalKey); g_free(Info); break; case OpStop: case OpAbort: MSG(" Not implemented\n"); break; } } else { /* FWD */ /* Command sending branch (status) */ switch (Op) { case OpSend: if (!Data2) { g_warning("Capi.c: Opsend [1F] Data2 = NULL\n"); } else if (strcmp(Data2, "SockFD") == 0) { /* start the receiving branch */ dpi_conn_t *conn = Info->LocalKey; conn->SockFD = *(int*)Data1; a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(),Info->LocalKey, NULL); } else if (strcmp(Data2, "DpidOK") == 0) { /* send the data inmediatly! */ Capi_dpi_conn_resume(); } else if (strcmp(Data2, "DpidEAGAIN") == 0) { /* set a timeout function to retry later... */ if (dpi_conn_timeout_id == 0) dpi_conn_timeout_id = gtk_timeout_add(250, (GtkFunction) Capi_dpi_conn_timeout, NULL); } break; case OpStop: case OpAbort: conn = Info->LocalKey; if (Data1 && !strcmp(Data1, "ERR_dpid")) a_Interface_msg(conn->bw, "ERROR: can't start dpid daemon!"); Capi_dpi_conn_unref(conn); g_free(Info); break; } } } else if (Branch == 2) { if (Dir == BCK) { /* Server listening branch (status) */ switch (Op) { case OpStart: { dpi_conn_t *conn = Data1; Capi_dpi_conn_ref(conn); Info->LocalKey = Data1; conn->InfoRecv = Info; a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 3, 2); a_Chain_bcb(OpStart, Info, &conn->SockFD, "SockFD"); break; } case OpStop: case OpAbort: Capi_dpi_conn_unref(Info->LocalKey); MSG(" Not implemented\n"); break; } } else { /* FWD */ /* Server listening branch */ switch (Op) { case OpSend: conn = Info->LocalKey; if (conn->Flags & ABORTED || (conn->web && !a_Web_valid(conn->web))) { /* there's no client for this transfer!*/ _MSG(" ** Capi 2F catched an invalid 'web' structure\n"); /* this flag is used just in case the same memory address * is reused for a new 'web' and the test passes */ conn->Flags &= ABORTED; /* Make the dpi module stop this transfer */ a_Chain_bcb(OpStop, Info, conn->url, NULL); } else if (strcmp(Data2, "send_status_message") == 0) { a_Interface_msg(conn->bw, "%s", Data1); } else if (strcmp(Data2, "chat") == 0) { a_Interface_msg(conn->bw, "%s", Data1); a_Bookmarks_chat_add(NULL, NULL, Data1); } else if (strcmp(Data2, "dialog") == 0) { a_Dpiapi_dialog(conn->bw, conn->server, Data1); } else if (strcmp(Data2, "start_send_page") == 0) { /* start the pipe-writing chain */ a_Capi_ccc(OpStart, 3, BCK, a_Chain_new(), Info->LocalKey,NULL); /* let the dpi know the reading end of the pipe */ a_Chain_bcb(OpSend, Info, &conn->DpiPipe[0], conn->web->url); } else if (strcmp(Data2, "send_page_2eof") == 0) { a_Capi_ccc(OpSend, 3, BCK, conn->InfoPipe, Data1, NULL); } else if (strcmp(Data2, "reload_request") == 0) { a_Nav_reload(conn->bw); } break; case OpEnd: { dpi_conn_t *conn = Info->LocalKey; a_Chain_del_link(Info, BCK); conn->InfoRecv = NULL; if (conn->InfoSend) { /* Propagate OpEnd to the sending branch too */ a_Capi_ccc(OpEnd, 1, BCK, conn->InfoSend, NULL, NULL); } if (conn->InfoPipe) { /* Propagate OpEnd to the pipe branch too */ a_Capi_ccc(OpEnd, 3, BCK, conn->InfoPipe, NULL, NULL); } Capi_dpi_conn_unref(conn); g_free(Info); break; } case OpStop: case OpAbort: MSG(" Not implemented\n"); break; } } } else if (Branch == 3) { if (Dir == BCK) { /* Pipe writing branch */ switch (Op) { case OpStart: { dpi_conn_t *conn = Data1; Info->LocalKey = Data1; Capi_dpi_conn_ref(conn); conn->InfoPipe = Info; if (pipe(conn->DpiPipe)) { MSG(" Error with pipe\n"); return; } a_Chain_link_new(Info, a_Capi_ccc, BCK, a_IO_ccc, 3, 3); a_Chain_bcb(OpStart, Info, &conn->DpiPipe[1], "SockFD"); break; } case OpSend: a_Chain_bcb(OpSend, Info, Data1, NULL); break; case OpEnd: a_Chain_bcb(OpEnd, Info, (void*)1, NULL); Capi_dpi_conn_unref(Info->LocalKey); g_free(Info); break; case OpStop: case OpAbort: Capi_dpi_conn_unref(Info->LocalKey); MSG(" Not implemented\n"); break; } } else { /* FWD */ /* Pipe branch (status) */ } } }