/* * Safety test: only allow GET|POST dpi-urls from dpi-generated pages. */ int a_Capi_dpi_verify_request(BrowserWindow *bw, DilloUrl *url) { DilloUrl *referer; int allow = FALSE; /* test POST and GET */ if (dStrcasecmp(URL_SCHEME(url), "dpi") == 0 && URL_FLAGS(url) & (URL_Post + URL_Get)) { /* only allow 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; }
/* * Process redirections (HTTP 30x answers) * (This is a work in progress --not finished yet) */ static int Cache_redirect(CacheEntry_t *entry, int Flags, BrowserWindow *bw) { DilloUrl *NewUrl; _MSG(" Cache_redirect: redirect_level = %d\n", bw->redirect_level); /* Don't allow redirection for SpamSafe/local URLs */ if (URL_FLAGS(entry->Url) & URL_SpamSafe) { a_UIcmd_set_msg(bw, "WARNING: local URL with redirection. Aborting."); return 0; } /* if there's a redirect loop, stop now */ if (bw->redirect_level >= 5) entry->Flags |= CA_RedirectLoop; if (entry->Flags & CA_RedirectLoop) { a_UIcmd_set_msg(bw, "ERROR: redirect loop for: %s", URL_STR_(entry->Url)); bw->redirect_level = 0; return 0; } if ((entry->Flags & CA_Redirect && entry->Location) && (entry->Flags & CA_ForceRedirect || entry->Flags & CA_TempRedirect || !entry->Data->len || entry->Data->len < 1024)) { _MSG(">>>> Redirect from: %s\n to %s <<<<\n", URL_STR_(entry->Url), URL_STR_(entry->Location)); _MSG("%s", entry->Header->str); if (Flags & WEB_RootUrl) { /* Redirection of the main page */ NewUrl = a_Url_new(URL_STR_(entry->Location), URL_STR_(entry->Url)); if (entry->Flags & CA_TempRedirect) a_Url_set_flags(NewUrl, URL_FLAGS(NewUrl) | URL_E2EQuery); a_Nav_push(bw, NewUrl, entry->Url); a_Url_free(NewUrl); } else { /* Sub entity redirection (most probably an image) */ if (!entry->Data->len) { _MSG(">>>> Image redirection without entity-content <<<<\n"); } else { _MSG(">>>> Image redirection with entity-content <<<<\n"); } } } return 0; }
/* * Try finding the url in the cache. If it hits, send the cache contents * from there. If it misses, set up a new connection. * * - 'Web' is an auxiliary data structure with misc. parameters. * - 'Call' is the callback that receives the data * - 'CbData' is custom data passed to 'Call' * Note: 'Call' and/or 'CbData' can be NULL, in that case they get set * later by a_Web_dispatch_by_type, based on content/type and 'Web' data. * * Return value: A primary key for identifying the client, */ int a_Cache_open_url(void *web, CA_Callback_t Call, void *CbData) { int ClientKey; CacheEntry_t *entry; DilloWeb *Web = web; DilloUrl *Url = Web->url; if (URL_FLAGS(Url) & URL_E2EQuery) { /* remove current entry */ Cache_entry_remove(NULL, Url); } if ((entry = Cache_entry_search(Url))) { /* URL is cached: feed our client with cached data */ ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData); Cache_delayed_process_queue(entry); } else { /* URL not cached: create an entry, send our client to the queue, * and open a new connection */ entry = Cache_entry_add(Url); ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData); } return ClientKey; }
/* * Generate Content-Type header value for a POST query. */ static Dstr *Http_make_content_type(const DilloUrl *url) { Dstr *dstr; if (URL_FLAGS(url) & URL_MultipartEnc) { MSG("submitting multipart/form-data!\n"); dstr = dStr_new("multipart/form-data; boundary=\""); if (URL_DATA(url)->len > 2) { /* boundary lines have "--" prepended. Skip that. */ const char *start = URL_DATA(url)->str + 2; char *eol = strchr(start, '\r'); if (eol) dStr_append_l(dstr, start, eol - start); } else { /* Zero parts; arbitrary boundary */ dStr_append_c(dstr, '0'); } dStr_append_c(dstr,'"'); } else { dstr = dStr_new("application/x-www-form-urlencoded"); } return dstr; }
/* * Make the http query string */ Dstr *a_Http_make_query_str(const DilloUrl *url, const DilloUrl *requester, bool_t use_proxy) { char *ptr, *cookies, *referer, *auth; Dstr *query = dStr_new(""), *request_uri = dStr_new(""), *proxy_auth = dStr_new(""); if (use_proxy) { dStr_sprintfa(request_uri, "%s%s", URL_STR(url), (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/"); if ((ptr = strrchr(request_uri->str, '#'))) dStr_truncate(request_uri, ptr - request_uri->str); if (HTTP_Proxy_Auth_base64) dStr_sprintf(proxy_auth, "Proxy-Authorization: Basic %s\r\n", HTTP_Proxy_Auth_base64); } else { dStr_sprintfa(request_uri, "%s%s%s%s", URL_PATH(url), URL_QUERY_(url) ? "?" : "", URL_QUERY(url), (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/"); } cookies = a_Cookies_get_query(url, requester); auth = a_Auth_get_auth_str(url, request_uri->str); referer = Http_get_referer(url); if (URL_FLAGS(url) & URL_Post) { Dstr *content_type = Http_make_content_type(url); dStr_sprintfa( query, "POST %s HTTP/1.1\r\n" "Connection: close\r\n" "Accept: text/*,image/*,*/*;q=0.2\r\n" "Accept-Charset: utf-8,*;q=0.8\r\n" "Accept-Encoding: gzip\r\n" "%s" /* language */ "%s" /* auth */ "Host: %s\r\n" "%s" "%s" "User-Agent: %s\r\n" "Content-Length: %ld\r\n" "Content-Type: %s\r\n" "%s" /* cookies */ "\r\n", request_uri->str, HTTP_Language_hdr, auth ? auth : "", URL_AUTHORITY(url), proxy_auth->str, referer, prefs.http_user_agent, (long)URL_DATA(url)->len, content_type->str, cookies); dStr_append_l(query, URL_DATA(url)->str, URL_DATA(url)->len); dStr_free(content_type, TRUE); } else { dStr_sprintfa( query, "GET %s HTTP/1.1\r\n" "%s" "Connection: close\r\n" "Accept: text/*,image/*,*/*;q=0.2\r\n" "Accept-Charset: utf-8,*;q=0.8\r\n" "Accept-Encoding: gzip\r\n" "%s" /* language */ "%s" /* auth */ "Host: %s\r\n" "%s" "%s" "User-Agent: %s\r\n" "%s" /* cookies */ "\r\n", request_uri->str, (URL_FLAGS(url) & URL_E2EQuery) ? "Cache-Control: no-cache\r\nPragma: no-cache\r\n" : "", HTTP_Language_hdr, auth ? auth : "", URL_AUTHORITY(url), proxy_auth->str, referer, prefs.http_user_agent, cookies); } dFree(referer); dFree(cookies); dFree(auth); dStr_free(request_uri, TRUE); dStr_free(proxy_auth, TRUE); _MSG("Query: {%s}\n", dStr_printable(query, 8192)); return query; }
/* * Scan, allocate, and set things according to header info. * (This function needs the whole header to work) */ static void Cache_parse_header(CacheEntry_t *entry) { char *header = entry->Header->str; char *Length, *Type, *location_str, *encoding; #ifndef DISABLE_COOKIES Dlist *Cookies; #endif Dlist *warnings; void *data; int i; _MSG("Cache_parse_header\n"); if (entry->Header->len > 12) { if (header[9] == '1' && header[10] == '0' && header[11] == '0') { /* 100: Continue. The "real" header has not come yet. */ MSG("An actual 100 Continue header!\n"); entry->Flags &= ~CA_GotHeader; dStr_free(entry->Header, 1); entry->Header = dStr_new(""); return; } if (header[9] == '3' && header[10] == '0' && (location_str = Cache_parse_field(header, "Location"))) { /* 30x: URL redirection */ DilloUrl *location_url = a_Url_new(location_str,URL_STR_(entry->Url)); if (prefs.filter_auto_requests == PREFS_FILTER_SAME_DOMAIN && !a_Url_same_organization(entry->Url, location_url)) { /* don't redirect; just show body like usual (if any) */ MSG("Redirection not followed from %s to %s\n", URL_HOST(entry->Url), URL_STR(location_url)); a_Url_free(location_url); } else { entry->Flags |= CA_Redirect; if (header[11] == '1') entry->Flags |= CA_ForceRedirect; /* 301 Moved Permanently */ else if (header[11] == '2') entry->Flags |= CA_TempRedirect; /* 302 Temporary Redirect */ if (URL_FLAGS(location_url) & (URL_Post + URL_Get) && dStrAsciiCasecmp(URL_SCHEME(location_url), "dpi") == 0 && dStrAsciiCasecmp(URL_SCHEME(entry->Url), "dpi") != 0) { /* Forbid dpi GET and POST from non dpi-generated urls */ MSG("Redirection Denied! '%s' -> '%s'\n", URL_STR(entry->Url), URL_STR(location_url)); a_Url_free(location_url); } else { entry->Location = location_url; } } dFree(location_str); } else if (strncmp(header + 9, "401", 3) == 0) { entry->Auth = Cache_parse_multiple_fields(header, "WWW-Authenticate"); } else if (strncmp(header + 9, "404", 3) == 0) { entry->Flags |= CA_NotFound; } } if ((warnings = Cache_parse_multiple_fields(header, "Warning"))) { for (i = 0; (data = dList_nth_data(warnings, i)); ++i) { MSG_HTTP("%s\n", (char *)data); dFree(data); } dList_free(warnings); } /* * Get Transfer-Encoding and initialize decoder */ encoding = Cache_parse_field(header, "Transfer-Encoding"); entry->TransferDecoder = a_Decode_transfer_init(encoding); if ((Length = Cache_parse_field(header, "Content-Length")) != NULL) { if (encoding) { /* * If Transfer-Encoding is present, Content-Length must be ignored. * If the Transfer-Encoding is non-identity, it is an error. */ if (dStrAsciiCasecmp(encoding, "identity")) MSG_HTTP("Content-Length and non-identity Transfer-Encoding " "headers both present.\n"); } else { entry->Flags |= CA_GotLength; entry->ExpectedSize = MAX(strtol(Length, NULL, 10), 0); } dFree(Length); } dFree(encoding); /* free Transfer-Encoding */ #ifndef DISABLE_COOKIES if ((Cookies = Cache_parse_multiple_fields(header, "Set-Cookie"))) { CacheClient_t *client; for (i = 0; (client = dList_nth_data(ClientQueue, i)); ++i) { if (client->Url == entry->Url) { DilloWeb *web = client->Web; if (!web->requester || a_Url_same_organization(entry->Url, web->requester)) { char *server_date = Cache_parse_field(header, "Date"); a_Cookies_set(Cookies, entry->Url, server_date); dFree(server_date); break; } } } if (i >= dList_length(ClientQueue)) { MSG("Cache: cookies not accepted from '%s'\n", URL_STR(entry->Url)); } for (i = 0; (data = dList_nth_data(Cookies, i)); ++i) dFree(data); dList_free(Cookies); } #endif /* !DISABLE_COOKIES */ /* * Get Content-Encoding and initialize decoder */ encoding = Cache_parse_field(header, "Content-Encoding"); entry->ContentDecoder = a_Decode_content_init(encoding); dFree(encoding); if (entry->ExpectedSize > 0) { if (entry->ExpectedSize > HUGE_FILESIZE) { entry->Flags |= CA_HugeFile; } /* Avoid some reallocs. With MAX_INIT_BUF we avoid a SEGFAULT * with huge files (e.g. iso files). * Note: the buffer grows automatically. */ dStr_free(entry->Data, 1); entry->Data = dStr_sized_new(MIN(entry->ExpectedSize, MAX_INIT_BUF)); } /* Get Content-Type */ if ((Type = Cache_parse_field(header, "Content-Type"))) { /* This HTTP Content-Type is not trusted. It's checked against real data * in Cache_process_queue(); only then CA_GotContentType becomes true. */ a_Cache_set_content_type(entry->Url, Type, "http"); _MSG("TypeHdr {%s} {%s}\n", Type, URL_STR(entry->Url)); _MSG("TypeMeta {%s}\n", entry->TypeMeta); dFree(Type); } Cache_ref_data(entry); }
/* * 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; /* 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) { /* make "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 */ cmd = Capi_dpi_build_cmd(web, server); a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); dFree(cmd); } 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 immediatly. 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; }