Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Copyright 2010 John-Mark Bell <jmb@netsurf-browser.org>
  3.  *
  4.  * This file is part of NetSurf, http://www.netsurf-browser.org/
  5.  *
  6.  * NetSurf 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; version 2 of the License.
  9.  *
  10.  * NetSurf is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.  * GNU General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  */
  18.  
  19. /**
  20.  * \file desktop/download.c
  21.  * \brief Core download context implementation
  22.  */
  23.  
  24. #include <assert.h>
  25. #include <stdlib.h>
  26.  
  27. #include "content/llcache.h"
  28. #include "desktop/download.h"
  29. #include "desktop/gui.h"
  30. #include "utils/http.h"
  31. #include "utils/url.h"
  32. #include "utils/utils.h"
  33.  
  34. /**
  35.  * A context for a download
  36.  */
  37. struct download_context {
  38.         llcache_handle *llcache;                /**< Low-level cache handle */
  39.         struct gui_window *parent;              /**< Parent window */
  40.  
  41.         lwc_string *mime_type;                  /**< MIME type of download */
  42.         unsigned long total_length;             /**< Length of data, in bytes */
  43.         char *filename;                         /**< Suggested filename */
  44.  
  45.         struct gui_download_window *window;     /**< GUI download window */
  46. };
  47.  
  48. /**
  49.  * Parse a filename parameter value
  50.  *
  51.  * \param filename  Value to parse
  52.  * \return Sanitised filename, or NULL on memory exhaustion
  53.  */
  54. static char *download_parse_filename(const char *filename)
  55. {
  56.         const char *slash = strrchr(filename, '/');
  57.  
  58.         if (slash != NULL)
  59.                 slash++;
  60.         else
  61.                 slash = filename;
  62.  
  63.         return strdup(slash);
  64. }
  65.  
  66. /**
  67.  * Compute a default filename for a download
  68.  *
  69.  * \param url  URL of item being fetched
  70.  * \return Default filename, or NULL on memory exhaustion
  71.  */
  72. static char *download_default_filename(const char *url)
  73. {
  74.         char *nice;
  75.  
  76.         if (url_nice(url, &nice, false) == URL_FUNC_OK)
  77.                 return nice;
  78.  
  79.         return NULL;
  80. }
  81.  
  82. /**
  83.  * Process fetch headers for a download context.
  84.  * Extracts MIME type, total length, and creates gui_download_window
  85.  *
  86.  * \param ctx  Context to process
  87.  * \return NSERROR_OK on success, appropriate error otherwise
  88.  */
  89. static nserror download_context_process_headers(download_context *ctx)
  90. {
  91.         const char *http_header;
  92.         http_content_type *content_type;
  93.         unsigned long length;
  94.         nserror error;
  95.  
  96.         /* Retrieve and parse Content-Type */
  97.         http_header = llcache_handle_get_header(ctx->llcache, "Content-Type");
  98.         if (http_header == NULL)
  99.                 http_header = "text/plain";
  100.  
  101.         error = http_parse_content_type(http_header, &content_type);
  102.         if (error != NSERROR_OK)
  103.                 return error;
  104.  
  105.         /* Retrieve and parse Content-Length */
  106.         http_header = llcache_handle_get_header(ctx->llcache, "Content-Length");
  107.         if (http_header == NULL)
  108.                 length = 0;
  109.         else
  110.                 length = strtoul(http_header, NULL, 10);
  111.  
  112.         /* Retrieve and parse Content-Disposition */
  113.         http_header = llcache_handle_get_header(ctx->llcache,
  114.                         "Content-Disposition");
  115.         if (http_header != NULL) {
  116.                 lwc_string *filename;
  117.                 lwc_string *filename_value;
  118.                 http_content_disposition *disposition;
  119.  
  120.                 if (lwc_intern_string("filename", SLEN("filename"),
  121.                                 &filename) != lwc_error_ok) {
  122.                         http_content_type_destroy(content_type);
  123.                         return NSERROR_NOMEM;
  124.                 }
  125.  
  126.                 error = http_parse_content_disposition(http_header,
  127.                                 &disposition);
  128.                 if (error != NSERROR_OK) {
  129.                         lwc_string_unref(filename);
  130.                         http_content_type_destroy(content_type);
  131.                         return error;
  132.                 }
  133.  
  134.                 error = http_parameter_list_find_item(disposition->parameters,
  135.                                 filename, &filename_value);
  136.                 if (error == NSERROR_OK) {
  137.                         ctx->filename = download_parse_filename(
  138.                                         lwc_string_data(filename_value));
  139.                         lwc_string_unref(filename_value);
  140.                 }
  141.  
  142.                 http_content_disposition_destroy(disposition);
  143.                 lwc_string_unref(filename);
  144.         }
  145.  
  146.         ctx->mime_type = lwc_string_ref(content_type->media_type);
  147.         ctx->total_length = length;
  148.         if (ctx->filename == NULL) {
  149.                 ctx->filename = download_default_filename(
  150.                                 nsurl_access(
  151.                                 llcache_handle_get_url(ctx->llcache)));
  152.         }
  153.  
  154.         http_content_type_destroy(content_type);
  155.  
  156.         if (ctx->filename == NULL) {
  157.                 lwc_string_unref(ctx->mime_type);
  158.                 ctx->mime_type = NULL;
  159.                 return NSERROR_NOMEM;
  160.         }
  161.  
  162.         /* Create the frontend window */
  163.         ctx->window = gui_download_window_create(ctx, ctx->parent);
  164.         if (ctx->window == NULL) {
  165.                 free(ctx->filename);
  166.                 ctx->filename = NULL;
  167.                 lwc_string_unref(ctx->mime_type);
  168.                 ctx->mime_type = NULL;
  169.                 return NSERROR_NOMEM;
  170.         }
  171.  
  172.         return NSERROR_OK;
  173. }
  174.  
  175. /**
  176.  * Callback for low-level cache events
  177.  *
  178.  * \param handle  Low-level cache handle
  179.  * \param event   Event object
  180.  * \param pw      Our context
  181.  * \return NSERROR_OK on success, appropriate error otherwise
  182.  */
  183. static nserror download_callback(llcache_handle *handle,
  184.                 const llcache_event *event, void *pw)
  185. {
  186.         download_context *ctx = pw;
  187.         nserror error = NSERROR_OK;
  188.  
  189.         switch (event->type) {
  190.         case LLCACHE_EVENT_HAD_HEADERS:
  191.                 error = download_context_process_headers(ctx);
  192.                 if (error != NSERROR_OK) {
  193.                         llcache_handle_abort(handle);
  194.                         download_context_destroy(ctx);
  195.                 }
  196.  
  197.                 break;
  198.  
  199.         case LLCACHE_EVENT_HAD_DATA:
  200.                 /* If we didn't know up-front that this fetch was for download,
  201.                  * then we won't receive the HAD_HEADERS event. Catch up now.
  202.                  */
  203.                 if (ctx->window == NULL) {
  204.                         error = download_context_process_headers(ctx);
  205.                         if (error != NSERROR_OK) {
  206.                                 llcache_handle_abort(handle);
  207.                                 download_context_destroy(ctx);
  208.                         }
  209.                 }
  210.  
  211.                 if (error == NSERROR_OK) {
  212.                         /** \todo Lose ugly cast */
  213.                         error = gui_download_window_data(ctx->window,
  214.                                         (char *) event->data.data.buf,
  215.                                         event->data.data.len);
  216.                         if (error != NSERROR_OK)
  217.                                 llcache_handle_abort(handle);
  218.                 }
  219.  
  220.                 break;
  221.  
  222.         case LLCACHE_EVENT_DONE:
  223.                 /* There may be no associated window if there was no data or headers */
  224.                 if (ctx->window != NULL)
  225.                         gui_download_window_done(ctx->window);
  226.                 else
  227.                         download_context_destroy(ctx);
  228.  
  229.                 break;
  230.  
  231.         case LLCACHE_EVENT_ERROR:
  232.                 if (ctx->window != NULL)
  233.                         gui_download_window_error(ctx->window, event->data.msg);
  234.                 else
  235.                         download_context_destroy(ctx);
  236.  
  237.                 break;
  238.  
  239.         case LLCACHE_EVENT_PROGRESS:
  240.                 break;
  241.         }
  242.  
  243.         return error;
  244. }
  245.  
  246. /* See download.h for documentation */
  247. nserror download_context_create(llcache_handle *llcache,
  248.                 struct gui_window *parent)
  249. {
  250.         download_context *ctx;
  251.  
  252.         ctx = malloc(sizeof(*ctx));
  253.         if (ctx == NULL)
  254.                 return NSERROR_NOMEM;
  255.  
  256.         ctx->llcache = llcache;
  257.         ctx->parent = parent;
  258.         ctx->mime_type = NULL;
  259.         ctx->total_length = 0;
  260.         ctx->filename = NULL;
  261.         ctx->window = NULL;
  262.  
  263.         llcache_handle_change_callback(llcache, download_callback, ctx);
  264.  
  265.         return NSERROR_OK;
  266. }
  267.  
  268. /* See download.h for documentation */
  269. void download_context_destroy(download_context *ctx)
  270. {
  271.         llcache_handle_release(ctx->llcache);
  272.  
  273.         if (ctx->mime_type != NULL)
  274.                 lwc_string_unref(ctx->mime_type);
  275.  
  276.         free(ctx->filename);
  277.  
  278.         /* Window is not owned by us, so don't attempt to destroy it */
  279.  
  280.         free(ctx);
  281. }
  282.  
  283. /* See download.h for documentation */
  284. void download_context_abort(download_context *ctx)
  285. {
  286.         llcache_handle_abort(ctx->llcache);
  287. }
  288.  
  289. /* See download.h for documentation */
  290. const char *download_context_get_url(const download_context *ctx)
  291. {
  292.         return nsurl_access(llcache_handle_get_url(ctx->llcache));
  293. }
  294.  
  295. /* See download.h for documentation */
  296. const char *download_context_get_mime_type(const download_context *ctx)
  297. {
  298.         return lwc_string_data(ctx->mime_type);
  299. }
  300.  
  301. /* See download.h for documentation */
  302. unsigned long download_context_get_total_length(const download_context *ctx)
  303. {
  304.         return ctx->total_length;
  305. }
  306.  
  307. /* See download.h for documentation */
  308. const char *download_context_get_filename(const download_context *ctx)
  309. {
  310.         return ctx->filename;
  311. }
  312.  
  313.