1 /*
2  * File: capi.c
3  *
4  * Copyright 2002-2007 Jorge Arellano Cid <jcid@dillo.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  */
11 
12 /*
13  * Cache API
14  * This is the module that manages the cache and starts the CCC chains
15  * to get the requests served. Kind of a broker.
16  */
17 
18 #include <string.h>
19 
20 #include "msg.h"
21 #include "capi.h"
22 #include "IO/IO.h"    /* for IORead &friends */
23 #include "IO/Url.h"
24 #include "chain.h"
25 #include "history.h"
26 #include "nav.h"
27 #include "dpiapi.h"
28 #include "uicmd.hh"
29 #include "domain.h"
30 #include "../dpip/dpip.h"
31 
32 /* for testing dpi chat */
33 #include "bookmark.h"
34 
35 typedef struct {
36    DilloUrl *url;           /* local copy of web->url */
37    void *bw;
38    char *server;
39    char *datastr;
40    int SockFD;
41    int Flags;
42    ChainLink *InfoSend;
43    ChainLink *InfoRecv;
44 
45    int Ref;
46 } capi_conn_t;
47 
48 /* Flags for conn */
49 enum {
50    PENDING = 1,
51    TIMEOUT = 2,  /* unused */
52    ABORTED = 4
53 };
54 
55 /*
56  * Local data
57  */
58 /* Data list for active dpi connections */
59 static Dlist *CapiConns;      /* Data list for active connections; it holds
60                                * pointers to capi_conn_t structures. */
61 /* Last URL asked for view source */
62 static DilloUrl *CapiVsUrl = NULL;
63 
64 /*
65  * Forward declarations
66  */
67 void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
68                 void *Data1, void *Data2);
69 
70 
71 /* ------------------------------------------------------------------------- */
72 
73 /*
74  * Initialize capi&cache data
75  */
a_Capi_init(void)76 void a_Capi_init(void)
77 {
78    /* create an empty list */
79    CapiConns = dList_new(32);
80    /* init cache */
81    a_Cache_init();
82 }
83 
84 /* ------------------------------------------------------------------------- */
85 
86 /*
87  * Create a new connection data structure
88  */
89 static capi_conn_t *
Capi_conn_new(const DilloUrl * url,void * bw,char * server,char * datastr)90  Capi_conn_new(const DilloUrl *url, void *bw, char *server, char *datastr)
91 {
92    capi_conn_t *conn;
93 
94    conn = dNew(capi_conn_t, 1);
95    conn->url = url ? a_Url_dup(url) : NULL;
96    conn->bw = bw;
97    conn->server = dStrdup(server);
98    conn->datastr = dStrdup(datastr);
99    conn->SockFD = -1;
100    conn->Flags = (strcmp(server, "http") != 0) ? PENDING : 0;
101    conn->InfoSend = NULL;
102    conn->InfoRecv = NULL;
103    conn->Ref = 0;           /* Reference count */
104    return conn;
105 }
106 
107 /*
108  * Validate a capi_conn_t pointer.
109  * Return value: NULL if not valid, conn otherwise.
110  */
Capi_conn_valid(capi_conn_t * conn)111 static capi_conn_t *Capi_conn_valid(capi_conn_t *conn)
112 {
113    return dList_find(CapiConns, conn);
114 }
115 
116 /*
117  * Increment the reference count and add to the list if not present
118  */
Capi_conn_ref(capi_conn_t * conn)119 static void Capi_conn_ref(capi_conn_t *conn)
120 {
121    if (++conn->Ref == 1) {
122       /* add the connection data to list */
123       dList_append(CapiConns, (void *)conn);
124    }
125    _MSG(" Capi_conn_ref #%d %p\n", conn->Ref, conn);
126 }
127 
128 /*
129  * Decrement the reference count (and remove from list when zero)
130  */
Capi_conn_unref(capi_conn_t * conn)131 static void Capi_conn_unref(capi_conn_t *conn)
132 {
133    _MSG(" Capi_conn_unref #%d %p\n", conn->Ref - 1, conn);
134 
135    /* We may validate conn here, but it doesn't *seem* necessary */
136    if (--conn->Ref == 0) {
137       /* remove conn preserving the list order */
138       dList_remove(CapiConns, (void *)conn);
139       /* free dynamic memory */
140       a_Url_free(conn->url);
141       dFree(conn->server);
142       dFree(conn->datastr);
143       dFree(conn);
144    }
145    _MSG(" Capi_conn_unref CapiConns=%d\n", dList_length(CapiConns));
146 }
147 
148 /*
149  * Compare function for searching a conn by server string
150  */
Capi_conn_by_server_cmp(const void * v1,const void * v2)151 static int Capi_conn_by_server_cmp(const void *v1, const void *v2)
152 {
153    const capi_conn_t *node = v1;
154    const char *server = v2;
155    dReturn_val_if_fail(node && node->server && server, 1);
156    return strcmp(node->server, server);
157 }
158 
159 /*
160  * Find connection data by server
161  */
Capi_conn_find(char * server)162 static capi_conn_t *Capi_conn_find(char *server)
163 {
164    return dList_find_custom(CapiConns, (void*)server, Capi_conn_by_server_cmp);
165 }
166 
167 /*
168  * Resume connections that were waiting for dpid to start.
169  */
Capi_conn_resume(void)170 static void Capi_conn_resume(void)
171 {
172    int i;
173    DataBuf *dbuf;
174    capi_conn_t *conn;
175 
176    for (i = 0; i < dList_length(CapiConns); ++i) {
177       conn = dList_nth_data (CapiConns, i);
178       if (conn->Flags & PENDING) {
179          dbuf = a_Chain_dbuf_new(conn->datastr,(int)strlen(conn->datastr), 0);
180          if (conn->InfoSend) {
181             a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL);
182          }
183          dFree(dbuf);
184          conn->Flags &= ~PENDING;
185       }
186    }
187 }
188 
189 /*
190  * Abort the connection for a given url, using its CCC.
191  * (OpAbort 2,BCK removes the cache entry)
192  * TODO: when conn is already done, the cache entry isn't removed.
193  *       This may be wrong and needs a revision.
194  */
a_Capi_conn_abort_by_url(const DilloUrl * url)195 void a_Capi_conn_abort_by_url(const DilloUrl *url)
196 {
197    int i;
198    capi_conn_t *conn;
199 
200    for (i = 0; i < dList_length(CapiConns); ++i) {
201       conn = dList_nth_data (CapiConns, i);
202       if (a_Url_cmp(conn->url, url) == 0) {
203          if (conn->InfoSend) {
204             a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL);
205          }
206          if (conn->InfoRecv) {
207             a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL);
208          }
209          break;
210       }
211    }
212 }
213 
214 /* ------------------------------------------------------------------------- */
215 
216 /*
217  * Store the last URL requested by "view source"
218  */
a_Capi_set_vsource_url(const DilloUrl * url)219 void a_Capi_set_vsource_url(const DilloUrl *url)
220 {
221    a_Url_free(CapiVsUrl);
222    CapiVsUrl = a_Url_dup(url);
223 }
224 
225 /*
226  * Safety test: only allow GET|POST dpi-urls from dpi-generated pages.
227  */
a_Capi_dpi_verify_request(BrowserWindow * bw,DilloUrl * url)228 int a_Capi_dpi_verify_request(BrowserWindow *bw, DilloUrl *url)
229 {
230    const DilloUrl *referer;
231    int allow = FALSE;
232 
233    if (dStrAsciiCasecmp(URL_SCHEME(url), "dpi") == 0) {
234       if (!(URL_FLAGS(url) & (URL_Post + URL_Get))) {
235          allow = TRUE;
236       } else if (!(URL_FLAGS(url) & URL_Post) &&
237                  strncmp(URL_PATH(url), "/vsource/", 9) == 0) {
238          allow = TRUE;
239       } else {
240          /* only allow GET&POST dpi-requests from dpi-generated urls */
241          if (a_Nav_stack_size(bw)) {
242             referer = a_History_get_url(NAV_TOP_UIDX(bw));
243             if (dStrAsciiCasecmp(URL_SCHEME(referer), "dpi") == 0) {
244                allow = TRUE;
245             }
246          }
247       }
248    } else {
249       allow = TRUE;
250    }
251 
252    if (!allow) {
253       MSG("a_Capi_dpi_verify_request: Permission Denied!\n");
254       MSG("  URL_STR : %s\n", URL_STR(url));
255       if (URL_FLAGS(url) & URL_Post) {
256          MSG("  URL_DATA: %s\n", dStr_printable(URL_DATA(url), 1024));
257       }
258    }
259    return allow;
260 }
261 
262 /*
263  * If the url belongs to a dpi server, return its name.
264  */
Capi_url_uses_dpi(DilloUrl * url,char ** server_ptr)265 static int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr)
266 {
267    char *p, *server = NULL, *url_str = URL_STR(url);
268    Dstr *tmp;
269 
270    if ((dStrnAsciiCasecmp(url_str, "http:", 5) == 0) ||
271        (dStrnAsciiCasecmp(url_str, "about:", 6) == 0)) {
272       /* URL doesn't use dpi (server = NULL) */
273    } else if (dStrnAsciiCasecmp(url_str, "dpi:/", 5) == 0) {
274       /* dpi prefix, get this server's name */
275       if ((p = strchr(url_str + 5, '/')) != NULL) {
276          server = dStrndup(url_str + 5, (uint_t)(p - url_str - 5));
277       } else {
278          server = dStrdup("?");
279       }
280       if (strcmp(server, "bm") == 0) {
281          dFree(server);
282          server = dStrdup("bookmarks");
283       }
284    } else if ((p = strchr(url_str, ':')) != NULL) {
285       tmp = dStr_new("proto.");
286       dStr_append_l(tmp, url_str, p - url_str);
287       server = tmp->str;
288       dStr_free(tmp, 0);
289    }
290 
291    return ((*server_ptr = server) ? 1 : 0);
292 }
293 
294 /*
295  * Build the dpip command tag, according to URL and server.
296  */
Capi_dpi_build_cmd(DilloWeb * web,char * server)297 static char *Capi_dpi_build_cmd(DilloWeb *web, char *server)
298 {
299    char *cmd;
300 
301    if (strcmp(server, "proto.https") == 0) {
302       /* Let's be kind and make the HTTP query string for the dpi */
303       char *proxy_connect = a_Http_make_connect_str(web->url);
304       Dstr *http_query = a_Http_make_query_str(web->url, web->requester,
305                                                web->flags, FALSE);
306 
307       if ((uint_t) http_query->len > strlen(http_query->str)) {
308          /* Can't handle NULLs embedded in query data */
309          MSG_ERR("HTTPS query truncated!\n");
310       }
311 
312       /* BUG: WORKAROUND: request to only check the root URL's certificate.
313        *  This avoids the dialog bombing that stems from loading multiple
314        * https images/resources in a single page. A proper fix would take
315        * either to implement the https-dpi as a server (with state),
316        * or to move back https handling into dillo. */
317       if (proxy_connect) {
318          const char *proxy_urlstr = a_Http_get_proxy_urlstr();
319          cmd = a_Dpip_build_cmd("cmd=%s proxy_url=%s proxy_connect=%s "
320                                 "url=%s query=%s check_cert=%s",
321                                 "open_url", proxy_urlstr,
322                                 proxy_connect, URL_STR(web->url),
323                                 http_query->str,
324                                 (web->flags & WEB_RootUrl) ? "true" : "false");
325       } else {
326          cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s check_cert=%s",
327                                 "open_url", URL_STR(web->url),http_query->str,
328                                 (web->flags & WEB_RootUrl) ? "true" : "false");
329       }
330       dFree(proxy_connect);
331       dStr_free(http_query, 1);
332 
333    } else if (strcmp(server, "downloads") == 0) {
334       /* let the downloads server get it */
335       cmd = a_Dpip_build_cmd("cmd=%s url=%s destination=%s",
336                              "download", URL_STR(web->url), web->filename);
337 
338    } else {
339       /* For everyone else, the url string is enough... */
340       cmd = a_Dpip_build_cmd("cmd=%s url=%s", "open_url", URL_STR(web->url));
341    }
342    return cmd;
343 }
344 
345 /*
346  * Send the requested URL's source to the "view source" dpi
347  */
Capi_dpi_send_source(BrowserWindow * bw,DilloUrl * url)348 static void Capi_dpi_send_source(BrowserWindow *bw,  DilloUrl *url)
349 {
350    char *p, *buf, *cmd, size_str[32], *server="vsource";
351    int buf_size;
352 
353    if (!(p = strchr(URL_STR(url), ':')) || !(p = strchr(p + 1, ':')))
354       return;
355 
356    if (a_Capi_get_buf(CapiVsUrl, &buf, &buf_size)) {
357       /* send the page's source to this dpi connection */
358       snprintf(size_str, 32, "%d", buf_size);
359       cmd = a_Dpip_build_cmd("cmd=%s url=%s data_size=%s",
360                              "start_send_page", URL_STR(url), size_str);
361       a_Capi_dpi_send_cmd(NULL, bw, cmd, server, 0);
362       a_Capi_dpi_send_data(url, bw, buf, buf_size, server, 0);
363    } else {
364       cmd = a_Dpip_build_cmd("cmd=%s msg=%s",
365                              "DpiError", "Page is NOT cached");
366       a_Capi_dpi_send_cmd(NULL, bw, cmd, server, 0);
367    }
368    dFree(cmd);
369 }
370 
371 /*
372  * Most used function for requesting a URL.
373  * TODO: clean up the ad-hoc bindings with an API that allows dynamic
374  *       addition of new plugins.
375  *
376  * Return value: A primary key for identifying the client,
377  *               0 if the client is aborted in the process.
378  */
a_Capi_open_url(DilloWeb * web,CA_Callback_t Call,void * CbData)379 int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData)
380 {
381    int reload;
382    char *cmd, *server;
383    capi_conn_t *conn = NULL;
384    const char *scheme = URL_SCHEME(web->url);
385    int safe = 0, ret = 0, use_cache = 0;
386 
387    /* web->requester is NULL if the action is initiated by user */
388    if (!(a_Capi_get_flags(web->url) & CAPI_IsCached ||
389          web->requester == NULL ||
390          a_Domain_permit(web->requester, web->url))) {
391       return 0;
392    }
393 
394    /* reload test */
395    reload = (!(a_Capi_get_flags(web->url) & CAPI_IsCached) ||
396              (URL_FLAGS(web->url) & URL_E2EQuery));
397 
398    if (web->flags & WEB_Download) {
399      /* download request: if cached save from cache, else
400       * for http, ftp or https, use the downloads dpi */
401      if (a_Capi_get_flags_with_redirection(web->url) & CAPI_IsCached) {
402         if (web->filename) {
403            if ((web->stream = fopen(web->filename, "w"))) {
404               use_cache = 1;
405            } else {
406               MSG_WARN("Cannot open \"%s\" for writing.\n", web->filename);
407            }
408         }
409      } else if (a_Cache_download_enabled(web->url)) {
410         server = "downloads";
411         cmd = Capi_dpi_build_cmd(web, server);
412         a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1);
413         dFree(cmd);
414      } else {
415         MSG_WARN("Ignoring download request for '%s': "
416                  "not in cache and not downloadable.\n",
417                  URL_STR(web->url));
418      }
419 
420    } else if (Capi_url_uses_dpi(web->url, &server)) {
421       /* dpi request */
422       if ((safe = a_Capi_dpi_verify_request(web->bw, web->url))) {
423          if (dStrAsciiCasecmp(scheme, "dpi") == 0) {
424             if (strcmp(server, "vsource") == 0) {
425                /* allow "view source" reload upon user request */
426             } else {
427                /* make the other "dpi:/" prefixed urls always reload. */
428                a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EQuery);
429                reload = 1;
430             }
431          }
432          if (reload) {
433             a_Capi_conn_abort_by_url(web->url);
434             /* Send dpip command */
435             _MSG("a_Capi_open_url, reload url='%s'\n", URL_STR(web->url));
436             cmd = Capi_dpi_build_cmd(web, server);
437             a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1);
438             dFree(cmd);
439             if (strcmp(server, "vsource") == 0) {
440                Capi_dpi_send_source(web->bw, web->url);
441             }
442          }
443          use_cache = 1;
444       }
445       dFree(server);
446 
447    } else if (!dStrAsciiCasecmp(scheme, "http")) {
448       /* http request */
449       if (reload) {
450          a_Capi_conn_abort_by_url(web->url);
451          /* create a new connection and start the CCC operations */
452          conn = Capi_conn_new(web->url, web->bw, "http", "none");
453          /* start the reception branch before the query one because the DNS
454           * may callback immediately. This may avoid a race condition. */
455          a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, "http");
456          a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web);
457       }
458       use_cache = 1;
459 
460    } else if (!dStrAsciiCasecmp(scheme, "about")) {
461       /* internal request */
462       use_cache = 1;
463    }
464 
465    if (use_cache) {
466       if (!conn || (conn && Capi_conn_valid(conn))) {
467          /* not aborted, let's continue... */
468          ret = a_Cache_open_url(web, Call, CbData);
469       }
470    } else {
471       a_Web_free(web);
472    }
473    return ret;
474 }
475 
476 /*
477  * Convert cache-defined flags to Capi ones.
478  */
Capi_map_cache_flags(uint_t flags)479 static int Capi_map_cache_flags(uint_t flags)
480 {
481    int status = 0;
482 
483    if (flags) {
484       status |= CAPI_IsCached;
485       if (flags & CA_IsEmpty)
486          status |= CAPI_IsEmpty;
487       if (flags & CA_GotData)
488          status |= CAPI_Completed;
489       else
490          status |= CAPI_InProgress;
491 
492       /* CAPI_Aborted is not yet used/defined */
493    }
494    return status;
495 }
496 
497 /*
498  * Return status information of an URL's content-transfer process.
499  */
a_Capi_get_flags(const DilloUrl * Url)500 int a_Capi_get_flags(const DilloUrl *Url)
501 {
502    uint_t flags = a_Cache_get_flags(Url);
503    int status = flags ? Capi_map_cache_flags(flags) : 0;
504    return status;
505 }
506 
507 /*
508  * Same as a_Capi_get_flags() but following redirections.
509  */
a_Capi_get_flags_with_redirection(const DilloUrl * Url)510 int a_Capi_get_flags_with_redirection(const DilloUrl *Url)
511 {
512    uint_t flags = a_Cache_get_flags_with_redirection(Url);
513    int status = flags ? Capi_map_cache_flags(flags) : 0;
514    return status;
515 }
516 
517 /*
518  * Get the cache's buffer for the URL, and its size.
519  * Return: 1 cached, 0 not cached.
520  */
a_Capi_get_buf(const DilloUrl * Url,char ** PBuf,int * BufSize)521 int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize)
522 {
523    return a_Cache_get_buf(Url, PBuf, BufSize);
524 }
525 
526 /*
527  * Unref the cache's buffer when no longer using it.
528  */
a_Capi_unref_buf(const DilloUrl * Url)529 void a_Capi_unref_buf(const DilloUrl *Url)
530 {
531    a_Cache_unref_buf(Url);
532 }
533 
534 /*
535  * Get the Content-Type associated with the URL
536  */
a_Capi_get_content_type(const DilloUrl * url)537 const char *a_Capi_get_content_type(const DilloUrl *url)
538 {
539    return a_Cache_get_content_type(url);
540 }
541 
542 /*
543  * Set the Content-Type for the URL.
544  */
a_Capi_set_content_type(const DilloUrl * url,const char * ctype,const char * from)545 const char *a_Capi_set_content_type(const DilloUrl *url, const char *ctype,
546                                     const char *from)
547 {
548    return a_Cache_set_content_type(url, ctype, from);
549 }
550 
551 /*
552  * Send data to a dpi (e.g. add_bookmark, open_url, send_preferences, ...)
553  * Most of the time we send dpi commands, but it also serves for raw data
554  * as with "view source".
555  */
a_Capi_dpi_send_data(const DilloUrl * url,void * bw,char * data,int data_sz,char * server,int flags)556 int a_Capi_dpi_send_data(const DilloUrl *url, void *bw,
557                          char *data, int data_sz, char *server, int flags)
558 {
559    capi_conn_t *conn;
560    DataBuf *dbuf;
561 
562    if (flags & 1) {
563       /* open a new connection to server */
564 
565       /* Create a new connection data struct and add it to the list */
566       conn = Capi_conn_new(url, bw, server, data);
567       /* start the CCC operations */
568       a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, server);
569       a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, server);
570 
571    } else {
572       /* Re-use an open connection */
573       conn = Capi_conn_find(server);
574       if (conn && conn->InfoSend) {
575          /* found & active */
576          dbuf = a_Chain_dbuf_new(data, data_sz, 0);
577          a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL);
578          dFree(dbuf);
579       } else {
580          MSG(" ERROR: [a_Capi_dpi_send_data] No open connection found\n");
581       }
582    }
583 
584    return 0;
585 }
586 
587 /*
588  * Send a dpi cmd.
589  * (For instance: add_bookmark, open_url, send_preferences, ...)
590  */
a_Capi_dpi_send_cmd(DilloUrl * url,void * bw,char * cmd,char * server,int flags)591 int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server,
592                         int flags)
593 {
594    return a_Capi_dpi_send_data(url, bw, cmd, strlen(cmd), server, flags);
595 }
596 
597 /*
598  * Remove a client from the cache client queue.
599  * force = also abort the CCC if this is the last client.
600  */
a_Capi_stop_client(int Key,int force)601 void a_Capi_stop_client(int Key, int force)
602 {
603    CacheClient_t *Client;
604 
605    _MSG("a_Capi_stop_client:  force=%d\n", force);
606 
607    Client = a_Cache_client_get_if_unique(Key);
608    if (Client && (force || Client->BufSize == 0)) {
609       /* remove empty entries too */
610       a_Capi_conn_abort_by_url(Client->Url);
611    }
612    a_Cache_stop_client(Key);
613 }
614 
615 /*
616  * CCC function for the CAPI module
617  */
a_Capi_ccc(int Op,int Branch,int Dir,ChainLink * Info,void * Data1,void * Data2)618 void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
619                 void *Data1, void *Data2)
620 {
621    capi_conn_t *conn;
622 
623    dReturn_if_fail( a_Chain_check("a_Capi_ccc", Op, Branch, Dir, Info) );
624 
625    if (Branch == 1) {
626       if (Dir == BCK) {
627          /* Command sending branch */
628          switch (Op) {
629          case OpStart:
630             /* Data1 = conn; Data2 = {Web | server} */
631             conn = Data1;
632             Capi_conn_ref(conn);
633             Info->LocalKey = conn;
634             conn->InfoSend = Info;
635             if (strcmp(conn->server, "http") == 0) {
636                a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 1, 1);
637                a_Chain_bcb(OpStart, Info, Data2, NULL);
638             } else {
639                a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 1, 1);
640                a_Chain_bcb(OpStart, Info, Data2, NULL);
641             }
642             break;
643          case OpSend:
644             /* Data1 = dbuf */
645             a_Chain_bcb(OpSend, Info, Data1, NULL);
646             break;
647          case OpEnd:
648             conn = Info->LocalKey;
649             conn->InfoSend = NULL;
650             a_Chain_bcb(OpEnd, Info, NULL, NULL);
651             Capi_conn_unref(conn);
652             dFree(Info);
653             break;
654          case OpAbort:
655             conn = Info->LocalKey;
656             conn->InfoSend = NULL;
657             a_Chain_bcb(OpAbort, Info, NULL, NULL);
658             Capi_conn_unref(conn);
659             dFree(Info);
660             break;
661          default:
662             MSG_WARN("Unused CCC\n");
663             break;
664          }
665       } else {  /* 1 FWD */
666          /* Command sending branch (status) */
667          switch (Op) {
668          case OpSend:
669             if (!Data2) {
670                MSG_WARN("Capi.c: Opsend [1F] Data2 = NULL\n");
671             } else if (strcmp(Data2, "FD") == 0) {
672                conn = Info->LocalKey;
673                conn->SockFD = *(int*)Data1;
674                /* communicate the FD through the answer branch */
675                a_Capi_ccc(OpSend, 2, BCK, conn->InfoRecv, &conn->SockFD, "FD");
676             } else if (strcmp(Data2, "DpidOK") == 0) {
677                /* resume pending dpi requests */
678                Capi_conn_resume();
679             }
680             break;
681          case OpAbort:
682             conn = Info->LocalKey;
683             conn->InfoSend = NULL;
684             if (Data2) {
685                if (!strcmp(Data2, "DpidERROR")) {
686                   a_UIcmd_set_msg(conn->bw,
687                                   "ERROR: can't start dpid daemon "
688                                   "(URL scheme = '%s')!",
689                                   conn->url ? URL_SCHEME(conn->url) : "");
690                } else if (!strcmp(Data2, "Both") && conn->InfoRecv) {
691                   /* abort the other branch too */
692                   a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL);
693                }
694             }
695             /* if URL == expect-url */
696             a_Nav_cancel_expect_if_eq(conn->bw, conn->url);
697             /* finish conn */
698             Capi_conn_unref(conn);
699             dFree(Info);
700             break;
701          default:
702             MSG_WARN("Unused CCC\n");
703             break;
704          }
705       }
706 
707    } else if (Branch == 2) {
708       if (Dir == BCK) {
709          /* Answer branch */
710          switch (Op) {
711          case OpStart:
712             /* Data1 = conn; Data2 = {"http" | "<dpi server name>"} */
713             conn = Data1;
714             Capi_conn_ref(conn);
715             Info->LocalKey = conn;
716             conn->InfoRecv = Info;
717             a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2);
718             a_Chain_bcb(OpStart, Info, NULL, Data2);
719             break;
720          case OpSend:
721             /* Data1 = FD */
722             if (Data2 && strcmp(Data2, "FD") == 0) {
723                a_Chain_bcb(OpSend, Info, Data1, Data2);
724             }
725             break;
726          case OpAbort:
727             conn = Info->LocalKey;
728             conn->InfoRecv = NULL;
729             a_Chain_bcb(OpAbort, Info, NULL, NULL);
730             /* remove the cache entry for this URL */
731             a_Cache_entry_remove_by_url(conn->url);
732             Capi_conn_unref(conn);
733             dFree(Info);
734             break;
735          default:
736             MSG_WARN("Unused CCC\n");
737             break;
738          }
739       } else {  /* 2 FWD */
740          /* Server listening branch */
741          switch (Op) {
742          case OpSend:
743             conn = Info->LocalKey;
744             if (strcmp(Data2, "send_page_2eof") == 0) {
745                /* Data1 = dbuf */
746                DataBuf *dbuf = Data1;
747                a_Cache_process_dbuf(IORead, dbuf->Buf, dbuf->Size, conn->url);
748             } else if (strcmp(Data2, "send_status_message") == 0) {
749                a_UIcmd_set_msg(conn->bw, "%s", Data1);
750             } else if (strcmp(Data2, "chat") == 0) {
751                a_UIcmd_set_msg(conn->bw, "%s", Data1);
752                a_Bookmarks_chat_add(NULL, NULL, Data1);
753             } else if (strcmp(Data2, "dialog") == 0) {
754                a_Dpiapi_dialog(conn->bw, conn->server, Data1);
755             } else if (strcmp(Data2, "reload_request") == 0) {
756                a_Nav_reload(conn->bw);
757             } else if (strcmp(Data2, "start_send_page") == 0) {
758                /* prepare the cache to receive the data stream for this URL
759                 *
760                 * a_Capi_open_url() already added a new cache entry,
761                 * and a client for it.
762                 */
763             }
764             break;
765          case OpEnd:
766             conn = Info->LocalKey;
767             conn->InfoRecv = NULL;
768 
769             a_Cache_process_dbuf(IOClose, NULL, 0, conn->url);
770 
771             if (conn->InfoSend) {
772                /* Propagate OpEnd to the sending branch too */
773                a_Capi_ccc(OpEnd, 1, BCK, conn->InfoSend, NULL, NULL);
774             }
775             Capi_conn_unref(conn);
776             dFree(Info);
777             break;
778          default:
779             MSG_WARN("Unused CCC\n");
780             break;
781          }
782       }
783    }
784 }
785