Mercurial > dillo_port1.3
view src/capi.c @ 1958:7860a3051241
Fix for endless loop with META refresh and same URL or no URL at all
author | Jorge Arellano Cid <jcid@dillo.org> |
---|---|
date | Fri, 15 Apr 2011 14:08:53 -0300 |
parents | 4779a3d891a1 |
children | 350af350b840 |
line wrap: on
line source
/* * File: capi.c * * Copyright 2002-2007 Jorge Arellano Cid <jcid@dillo.org> * * 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 3 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 <string.h> #include "msg.h" #include "capi.h" #include "IO/IO.h" /* for IORead &friends */ #include "IO/Url.h" #include "chain.h" #include "history.h" #include "nav.h" #include "dpiapi.h" #include "uicmd.hh" #include "../dpip/dpip.h" /* for testing dpi chat */ #include "bookmark.h" typedef struct { DilloUrl *url; /* local copy of web->url */ void *bw; char *server; char *datastr; int SockFD; int Flags; ChainLink *InfoSend; ChainLink *InfoRecv; int Ref; } capi_conn_t; /* Flags for conn */ enum { PENDING = 1, TIMEOUT = 2, /* unused */ ABORTED = 4 }; /* * Local data */ /* Data list for active dpi connections */ static Dlist *CapiConns; /* Data list for active connections; it holds * pointers to capi_conn_t structures. */ /* Last URL asked for view source */ static DilloUrl *CapiVsUrl = NULL; /* * Forward declarations */ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2); /* ------------------------------------------------------------------------- */ /* * Initialize capi&cache data */ void a_Capi_init(void) { /* create an empty list */ CapiConns = dList_new(32); /* init cache */ a_Cache_init(); } /* ------------------------------------------------------------------------- */ /* * Create a new connection data structure */ static capi_conn_t * Capi_conn_new(const DilloUrl *url, void *bw, char *server, char *datastr) { capi_conn_t *conn; conn = dNew(capi_conn_t, 1); conn->url = url ? a_Url_dup(url) : NULL; conn->bw = bw; conn->server = dStrdup(server); conn->datastr = dStrdup(datastr); conn->SockFD = -1; conn->Flags = (strcmp(server, "http") != 0) ? PENDING : 0; conn->InfoSend = NULL; conn->InfoRecv = NULL; conn->Ref = 0; /* Reference count */ return conn; } /* * Validate a capi_conn_t pointer. * Return value: NULL if not valid, conn otherwise. */ static capi_conn_t *Capi_conn_valid(capi_conn_t *conn) { return dList_find(CapiConns, conn); } /* * Increment the reference count and add to the list if not present */ static void Capi_conn_ref(capi_conn_t *conn) { if (++conn->Ref == 1) { /* add the connection data to list */ dList_append(CapiConns, (void *)conn); } _MSG(" Capi_conn_ref #%d %p\n", conn->Ref, conn); } /* * Decrement the reference count (and remove from list when zero) */ static void Capi_conn_unref(capi_conn_t *conn) { _MSG(" Capi_conn_unref #%d %p\n", conn->Ref - 1, conn); /* We may validate conn here, but it doesn't *seem* necessary */ if (--conn->Ref == 0) { /* remove conn preserving the list order */ dList_remove(CapiConns, (void *)conn); /* free dynamic memory */ a_Url_free(conn->url); dFree(conn->server); dFree(conn->datastr); dFree(conn); } _MSG(" Capi_conn_unref CapiConns=%d\n", dList_length(CapiConns)); } /* * Compare function for searching a conn by server string */ static int Capi_conn_by_server_cmp(const void *v1, const void *v2) { const capi_conn_t *node = v1; const char *server = v2; dReturn_val_if_fail(node && node->server && server, 1); return strcmp(node->server, server); } /* * Find connection data by server */ static capi_conn_t *Capi_conn_find(char *server) { return dList_find_custom(CapiConns, (void*)server, Capi_conn_by_server_cmp); } /* * Resume connections that were waiting for dpid to start. */ static void Capi_conn_resume(void) { int i; DataBuf *dbuf; capi_conn_t *conn; for (i = 0; i < dList_length(CapiConns); ++i) { conn = dList_nth_data (CapiConns, i); if (conn->Flags & PENDING) { dbuf = a_Chain_dbuf_new(conn->datastr,(int)strlen(conn->datastr), 0); if (conn->InfoSend) { a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); } dFree(dbuf); conn->Flags &= ~PENDING; } } } /* * Abort the connection for a given url, using its CCC. * (OpAbort 2,BCK removes the cache entry) * TODO: when conn is already done, the cache entry isn't removed. * This may be wrong and needs a revision. */ void a_Capi_conn_abort_by_url(const DilloUrl *url) { int i; capi_conn_t *conn; for (i = 0; i < dList_length(CapiConns); ++i) { conn = dList_nth_data (CapiConns, i); if (a_Url_cmp(conn->url, url) == 0) { if (conn->InfoSend) { a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL); } if (conn->InfoRecv) { a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL); } break; } } } /* ------------------------------------------------------------------------- */ /* * Store the last URL requested by "view source" */ void a_Capi_set_vsource_url(const DilloUrl *url) { a_Url_free(CapiVsUrl); CapiVsUrl = a_Url_dup(url); } /* * Safety test: only allow GET|POST dpi-urls from dpi-generated pages. */ int a_Capi_dpi_verify_request(BrowserWindow *bw, DilloUrl *url) { const DilloUrl *referer; int allow = FALSE; if (dStrcasecmp(URL_SCHEME(url), "dpi") == 0) { if (!(URL_FLAGS(url) & (URL_Post + URL_Get))) { allow = TRUE; } else if (!(URL_FLAGS(url) & URL_Post) && strncmp(URL_STR(url), "dpi:/vsource/", 13) == 0) { allow = TRUE; } else { /* only allow GET&POST dpi-requests from dpi-generated urls */ if (a_Nav_stack_size(bw)) { referer = a_History_get_url(NAV_TOP_UIDX(bw)); if (dStrcasecmp(URL_SCHEME(referer), "dpi") == 0) { allow = TRUE; } } } } else { allow = TRUE; } if (!allow) { MSG("a_Capi_dpi_verify_request: Permission Denied!\n"); MSG(" URL_STR : %s\n", URL_STR(url)); if (URL_FLAGS(url) & URL_Post) { MSG(" URL_DATA: %s\n", dStr_printable(URL_DATA(url), 1024)); } } return allow; } /* * If the url belongs to a dpi server, return its name. */ static int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr) { char *p, *server = NULL, *url_str = URL_STR(url); Dstr *tmp; if ((dStrncasecmp(url_str, "http:", 5) == 0) || (dStrncasecmp(url_str, "about:", 6) == 0)) { /* URL doesn't use dpi (server = NULL) */ } else if (dStrncasecmp(url_str, "dpi:/", 5) == 0) { /* dpi prefix, get this server's name */ if ((p = strchr(url_str + 5, '/')) != NULL) { server = dStrndup(url_str + 5, (uint_t)(p - url_str - 5)); } else { server = dStrdup("?"); } if (strcmp(server, "bm") == 0) { dFree(server); server = dStrdup("bookmarks"); } } else if ((p = strchr(url_str, ':')) != NULL) { tmp = dStr_new("proto."); dStr_append_l(tmp, url_str, p - url_str); server = tmp->str; dStr_free(tmp, 0); } return ((*server_ptr = server) ? 1 : 0); } /* * Build the dpip command tag, according to URL and server. */ static char *Capi_dpi_build_cmd(DilloWeb *web, char *server) { char *cmd; if (strcmp(server, "proto.https") == 0) { /* Let's be kind and make the HTTP query string for the dpi */ char *proxy_connect = a_Http_make_connect_str(web->url); Dstr *http_query = a_Http_make_query_str(web->url, FALSE); /* BUG: embedded NULLs in query data will truncate message */ if (proxy_connect) { const char *proxy_urlstr = a_Http_get_proxy_urlstr(); cmd = a_Dpip_build_cmd("cmd=%s proxy_url=%s proxy_connect=%s " "url=%s query=%s", "open_url", proxy_urlstr, proxy_connect, URL_STR(web->url), http_query->str); } else { cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s", "open_url", URL_STR(web->url),http_query->str); } dFree(proxy_connect); dStr_free(http_query, 1); } else if (strcmp(server, "downloads") == 0) { /* let the downloads server get it */ cmd = a_Dpip_build_cmd("cmd=%s url=%s destination=%s", "download", URL_STR(web->url), web->filename); } else { /* For everyone else, the url string is enough... */ cmd = a_Dpip_build_cmd("cmd=%s url=%s", "open_url", URL_STR(web->url)); } return cmd; } /* * Send the requested URL's source to the "view source" dpi */ static void Capi_dpi_send_source(BrowserWindow *bw, DilloUrl *url) { char *p, *buf, *cmd, size_str[32], *server="vsource"; int buf_size; if (!(p = strchr(URL_STR(url), ':')) || !(p = strchr(p + 1, ':'))) return; if (a_Capi_get_buf(CapiVsUrl, &buf, &buf_size)) { /* send the page's source to this dpi connection */ snprintf(size_str, 32, "%d", buf_size); cmd = a_Dpip_build_cmd("cmd=%s url=%s data_size=%s", "start_send_page", URL_STR(url), size_str); a_Capi_dpi_send_cmd(NULL, bw, cmd, server, 0); a_Capi_dpi_send_data(url, bw, buf, buf_size, server, 0); } else { cmd = a_Dpip_build_cmd("cmd=%s msg=%s", "DpiError", "Page is NOT cached"); a_Capi_dpi_send_cmd(NULL, bw, cmd, server, 0); } dFree(cmd); } /* * When dillo wants to open an URL, this can be either due to user action * (e.g., typing in an URL, clicking a link), or automatic (HTTP header * indicates redirection, META HTML tag with refresh attribute and 0 delay, * and images and stylesheets on an HTML page when autoloading is enabled). * * For a user request, the action will be permitted. * For an automatic request, permission to load depends on the filter set * by the user. */ static bool_t Capi_filters_test(const DilloUrl *wanted, const DilloUrl *requester) { bool_t ret; if (requester == NULL) { /* request made by user */ ret = TRUE; } else { switch (prefs.filter_auto_requests) { case PREFS_FILTER_SAME_DOMAIN: { const char *req_host = URL_HOST(requester), *want_host = URL_HOST(wanted), *req_suffix, *want_suffix; if (want_host[0] == '\0') { ret = (req_host[0] == '\0' || !dStrcasecmp(URL_SCHEME(wanted), "data")) ? TRUE : FALSE; } else { /* This will regard "www.dillo.org" and "www.dillo.org." as * different, but it doesn't seem worth caring about. */ req_suffix = a_Url_host_find_public_suffix(req_host); want_suffix = a_Url_host_find_public_suffix(want_host); ret = dStrcasecmp(req_suffix, want_suffix) == 0; } MSG("Capi_filters_test: %s from '%s' to '%s'\n", ret ? "ALLOW" : "DENY", req_host, want_host); break; } case PREFS_FILTER_ALLOW_ALL: default: ret = TRUE; break; } } return ret; } /* * Most used function for requesting a URL. * TODO: clean up the ad-hoc bindings with an API that allows dynamic * addition of new plugins. * * Return value: A primary key for identifying the client, * 0 if the client is aborted in the process. */ int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) { int reload; char *cmd, *server; capi_conn_t *conn = NULL; const char *scheme = URL_SCHEME(web->url); int safe = 0, ret = 0, use_cache = 0; dReturn_val_if_fail((a_Capi_get_flags(web->url) & CAPI_IsCached) || Capi_filters_test(web->url, web->requester), 0); /* reload test */ reload = (!(a_Capi_get_flags(web->url) & CAPI_IsCached) || (URL_FLAGS(web->url) & URL_E2EQuery)); if (web->flags & WEB_Download) { /* download request: if cached save from cache, else * for http, ftp or https, use the downloads dpi */ if (a_Capi_get_flags_with_redirection(web->url) & CAPI_IsCached) { if (web->filename) { if ((web->stream = fopen(web->filename, "w"))) { use_cache = 1; } else { MSG_WARN("Cannot open \"%s\" for writing.\n", web->filename); } } } else if (a_Cache_download_enabled(web->url)) { server = "downloads"; cmd = Capi_dpi_build_cmd(web, server); a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); dFree(cmd); } } else if (Capi_url_uses_dpi(web->url, &server)) { /* dpi request */ if ((safe = a_Capi_dpi_verify_request(web->bw, web->url))) { if (dStrcasecmp(scheme, "dpi") == 0) { if (strcmp(server, "vsource") == 0) { /* don't reload the "view source" page */ } else { /* make the other "dpi:/" prefixed urls always reload. */ a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EQuery); reload = 1; } } if (reload) { a_Capi_conn_abort_by_url(web->url); /* Send dpip command */ _MSG("a_Capi_open_url, reload url='%s'\n", URL_STR(web->url)); cmd = Capi_dpi_build_cmd(web, server); a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); dFree(cmd); if (strcmp(server, "vsource") == 0) { Capi_dpi_send_source(web->bw, web->url); } } use_cache = 1; } dFree(server); } else if (!dStrcasecmp(scheme, "http")) { /* http request */ if (reload) { a_Capi_conn_abort_by_url(web->url); /* create a new connection and start the CCC operations */ conn = Capi_conn_new(web->url, web->bw, "http", "none"); /* start the reception branch before the query one because the DNS * may callback immediately. This may avoid a race condition. */ a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, "http"); a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web); } use_cache = 1; } else if (!dStrcasecmp(scheme, "about")) { /* internal request */ use_cache = 1; } if (use_cache) { if (!conn || (conn && Capi_conn_valid(conn))) { /* not aborted, let's continue... */ ret = a_Cache_open_url(web, Call, CbData); } } else { a_Web_free(web); } return ret; } /* * Convert cache-defined flags to Capi ones. */ static int Capi_map_cache_flags(uint_t flags) { int status = 0; if (flags) { status |= CAPI_IsCached; if (flags & CA_IsEmpty) status |= CAPI_IsEmpty; if (flags & CA_GotData) status |= CAPI_Completed; else status |= CAPI_InProgress; /* CAPI_Aborted is not yet used/defined */ } return status; } /* * Return status information of an URL's content-transfer process. */ int a_Capi_get_flags(const DilloUrl *Url) { uint_t flags = a_Cache_get_flags(Url); int status = flags ? Capi_map_cache_flags(flags) : 0; return status; } /* * Same as a_Capi_get_flags() but following redirections. */ int a_Capi_get_flags_with_redirection(const DilloUrl *Url) { uint_t flags = a_Cache_get_flags_with_redirection(Url); int status = flags ? Capi_map_cache_flags(flags) : 0; return status; } /* * Get the cache's buffer for the URL, and its size. * Return: 1 cached, 0 not cached. */ int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) { return a_Cache_get_buf(Url, PBuf, BufSize); } /* * Unref the cache's buffer when no longer using it. */ void a_Capi_unref_buf(const DilloUrl *Url) { a_Cache_unref_buf(Url); } /* * Get the Content-Type associated with the URL */ const char *a_Capi_get_content_type(const DilloUrl *url) { return a_Cache_get_content_type(url); } /* * Set the Content-Type for the URL. */ const char *a_Capi_set_content_type(const DilloUrl *url, const char *ctype, const char *from) { return a_Cache_set_content_type(url, ctype, from); } /* * Send data to a dpi (e.g. add_bookmark, open_url, send_preferences, ...) * Most of the time we send dpi commands, but it also serves for raw data * as with "view source". */ int a_Capi_dpi_send_data(const DilloUrl *url, void *bw, char *data, int data_sz, char *server, int flags) { capi_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_conn_new(url, bw, server, data); /* start the CCC operations */ a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, server); a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, server); } else { /* Re-use an open connection */ conn = Capi_conn_find(server); if (conn) { /* found */ dbuf = a_Chain_dbuf_new(data, data_sz, 0); a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); dFree(dbuf); } else { MSG(" ERROR: [a_Capi_dpi_send_data] No open connection found\n"); } } return 0; } /* * Send a dpi cmd. * (For instance: add_bookmark, open_url, send_preferences, ...) */ int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, int flags) { return a_Capi_dpi_send_data(url, bw, cmd, strlen(cmd), server, flags); } /* * Remove a client from the cache client queue. * force = also abort the CCC if this is the last client. */ void a_Capi_stop_client(int Key, int force) { CacheClient_t *Client; _MSG("a_Capi_stop_client: force=%d\n", force); Client = a_Cache_client_get_if_unique(Key); if (Client && (force || Client->BufSize == 0)) { /* remove empty entries too */ a_Capi_conn_abort_by_url(Client->Url); } a_Cache_stop_client(Key); } /* * CCC function for the CAPI module */ void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, void *Data1, void *Data2) { capi_conn_t *conn; dReturn_if_fail( a_Chain_check("a_Capi_ccc", Op, Branch, Dir, Info) ); if (Branch == 1) { if (Dir == BCK) { /* Command sending branch */ switch (Op) { case OpStart: /* Data1 = conn; Data2 = {Web | server} */ conn = Data1; Capi_conn_ref(conn); Info->LocalKey = conn; conn->InfoSend = Info; if (strcmp(conn->server, "http") == 0) { a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 1, 1); a_Chain_bcb(OpStart, Info, Data2, NULL); } else { a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 1, 1); a_Chain_bcb(OpStart, Info, Data2, NULL); } break; case OpSend: /* Data1 = dbuf */ a_Chain_bcb(OpSend, Info, Data1, NULL); break; case OpEnd: conn = Info->LocalKey; conn->InfoSend = NULL; a_Chain_bcb(OpEnd, Info, NULL, NULL); Capi_conn_unref(conn); dFree(Info); break; case OpAbort: conn = Info->LocalKey; conn->InfoSend = NULL; a_Chain_bcb(OpAbort, Info, NULL, NULL); Capi_conn_unref(conn); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } else { /* 1 FWD */ /* Command sending branch (status) */ switch (Op) { case OpSend: if (!Data2) { MSG_WARN("Capi.c: Opsend [1F] Data2 = NULL\n"); } else if (strcmp(Data2, "FD") == 0) { conn = Info->LocalKey; conn->SockFD = *(int*)Data1; /* communicate the FD through the answer branch */ a_Capi_ccc(OpSend, 2, BCK, conn->InfoRecv, &conn->SockFD, "FD"); } else if (strcmp(Data2, "DpidOK") == 0) { /* resume pending dpi requests */ Capi_conn_resume(); } break; case OpAbort: conn = Info->LocalKey; conn->InfoSend = NULL; if (Data2) { if (!strcmp(Data2, "DpidERROR")) { a_UIcmd_set_msg(conn->bw, "ERROR: can't start dpid daemon " "(URL scheme = '%s')!", conn->url ? URL_SCHEME(conn->url) : ""); } else if (!strcmp(Data2, "Both") && conn->InfoRecv) { /* abort the other branch too */ a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL); } } /* if URL == expect-url */ a_Nav_cancel_expect_if_eq(conn->bw, conn->url); /* finish conn */ Capi_conn_unref(conn); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } } else if (Branch == 2) { if (Dir == BCK) { /* Answer branch */ switch (Op) { case OpStart: /* Data1 = conn; Data2 = {"http" | "<dpi server name>"} */ conn = Data1; Capi_conn_ref(conn); Info->LocalKey = conn; conn->InfoRecv = Info; a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2); a_Chain_bcb(OpStart, Info, NULL, Data2); break; case OpSend: /* Data1 = FD */ if (Data2 && strcmp(Data2, "FD") == 0) { a_Chain_bcb(OpSend, Info, Data1, Data2); } break; case OpAbort: conn = Info->LocalKey; conn->InfoRecv = NULL; a_Chain_bcb(OpAbort, Info, NULL, NULL); /* remove the cache entry for this URL */ a_Cache_entry_remove_by_url(conn->url); Capi_conn_unref(conn); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } else { /* 2 FWD */ /* Server listening branch */ switch (Op) { case OpSend: conn = Info->LocalKey; if (strcmp(Data2, "send_page_2eof") == 0) { /* Data1 = dbuf */ DataBuf *dbuf = Data1; a_Cache_process_dbuf(IORead, dbuf->Buf, dbuf->Size, conn->url); } else if (strcmp(Data2, "send_status_message") == 0) { a_UIcmd_set_msg(conn->bw, "%s", Data1); } else if (strcmp(Data2, "chat") == 0) { a_UIcmd_set_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, "reload_request") == 0) { a_Nav_reload(conn->bw); } else if (strcmp(Data2, "start_send_page") == 0) { /* prepare the cache to receive the data stream for this URL * * a_Capi_open_url() already added a new cache entry, * and a client for it. */ } break; case OpEnd: conn = Info->LocalKey; conn->InfoRecv = NULL; a_Cache_process_dbuf(IOClose, NULL, 0, conn->url); if (conn->InfoSend) { /* Propagate OpEnd to the sending branch too */ a_Capi_ccc(OpEnd, 1, BCK, conn->InfoSend, NULL, NULL); } Capi_conn_unref(conn); dFree(Info); break; default: MSG_WARN("Unused CCC\n"); break; } } } }