Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Copyright 2012 John-Mark Bell <jmb@netsurf-browser.org>
  3.  * Copyright 2004-2007 James Bursa <bursa@users.sourceforge.net>
  4.  *
  5.  * This file is part of NetSurf, http://www.netsurf-browser.org/
  6.  *
  7.  * NetSurf is free software; you can redistribute it and/or modify
  8.  * it under the terms of the GNU General Public License as published by
  9.  * the Free Software Foundation; version 2 of the License.
  10.  *
  11.  * NetSurf is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18.  */
  19.  
  20. /** \file
  21.  * Save HTML document with dependencies (implementation).
  22.  */
  23.  
  24. #include "utils/config.h"
  25.  
  26. #include <assert.h>
  27. #include <ctype.h>
  28. #include <errno.h>
  29. #include <stdio.h>
  30. #include <string.h>
  31. #include <sys/types.h>
  32. #include <regex.h>
  33.  
  34. #include <dom/dom.h>
  35.  
  36. #include "content/content.h"
  37. #include "content/hlcache.h"
  38. #include "css/css.h"
  39. #include "desktop/save_complete.h"
  40. #include "render/box.h"
  41. #include "render/html.h"
  42. #include "utils/corestrings.h"
  43. #include "utils/log.h"
  44. #include "utils/nsurl.h"
  45. #include "utils/utf8.h"
  46. #include "utils/utils.h"
  47.  
  48. regex_t save_complete_import_re;
  49.  
  50. /** An entry in save_complete_list. */
  51. typedef struct save_complete_entry {
  52.         hlcache_handle *content;
  53.         struct save_complete_entry *next; /**< Next entry in list */
  54. } save_complete_entry;
  55.  
  56. typedef struct save_complete_ctx {
  57.     const char *path;
  58.     save_complete_entry *list;
  59.     save_complete_set_type_cb set_type;
  60.  
  61.     nsurl *base;
  62.     FILE *fp;
  63.     enum { STATE_NORMAL, STATE_IN_STYLE } iter_state;
  64. } save_complete_ctx;
  65.  
  66. typedef enum {
  67.         EVENT_ENTER,
  68.         EVENT_LEAVE
  69. } save_complete_event_type;
  70.  
  71.  
  72. static bool save_complete_save_html(save_complete_ctx *ctx, hlcache_handle *c,
  73.                 bool index);
  74. static bool save_complete_save_imported_sheets(save_complete_ctx *ctx,
  75.                 struct nscss_import *imports, uint32_t import_count);
  76.  
  77.  
  78. static void save_complete_ctx_initialise(save_complete_ctx *ctx,
  79.                 const char *path, save_complete_set_type_cb set_type)
  80. {
  81.         ctx->path = path;
  82.         ctx->list = NULL;
  83.         ctx->set_type = set_type;
  84. }
  85.  
  86. static void save_complete_ctx_finalise(save_complete_ctx *ctx)
  87. {
  88.         save_complete_entry *list = ctx->list;
  89.  
  90.         while (list != NULL) {
  91.                 save_complete_entry *next = list->next;
  92.                 free(list);
  93.                 list = next;
  94.         }
  95. }
  96.  
  97. static bool save_complete_ctx_add_content(save_complete_ctx *ctx,
  98.                 hlcache_handle *content)
  99. {
  100.         save_complete_entry *entry;
  101.  
  102.         entry = malloc(sizeof (*entry));
  103.         if (entry == NULL)
  104.                 return false;
  105.  
  106.         entry->content = content;
  107.         entry->next = ctx->list;
  108.         ctx->list = entry;
  109.  
  110.         return true;
  111. }
  112.  
  113.  
  114. static hlcache_handle *save_complete_ctx_find_content(save_complete_ctx *ctx,
  115.                 const nsurl *url)
  116. {
  117.         save_complete_entry *entry;
  118.  
  119.         for (entry = ctx->list; entry != NULL; entry = entry->next)
  120.                 if (nsurl_compare(url,
  121.                                 hlcache_handle_get_url(entry->content),
  122.                                 NSURL_COMPLETE))
  123.                         return entry->content;
  124.  
  125.         return NULL;
  126. }
  127.  
  128.  
  129. static bool save_complete_ctx_has_content(save_complete_ctx *ctx,
  130.                 hlcache_handle *content)
  131. {
  132.         save_complete_entry *entry;
  133.  
  134.         for (entry = ctx->list; entry != NULL; entry = entry->next)
  135.                 if (entry->content == content)
  136.                         return true;
  137.  
  138.         return false;
  139. }
  140.  
  141. static bool save_complete_save_buffer(save_complete_ctx *ctx,
  142.                 const char *leafname, const char *data, size_t data_len,
  143.                 lwc_string *mime_type)
  144. {
  145.         FILE *fp;
  146.         bool error;
  147.         char fullpath[PATH_MAX];
  148.  
  149.         strncpy(fullpath, ctx->path, sizeof fullpath);
  150.         error = path_add_part(fullpath, sizeof fullpath, leafname);
  151.         if (error == false) {
  152.                 warn_user("NoMemory", NULL);
  153.                 return false;
  154.         }
  155.  
  156.         fp = fopen(fullpath, "wb");
  157.         if (fp == NULL) {
  158.                 LOG(("fopen(): errno = %i", errno));
  159.                 warn_user("SaveError", strerror(errno));
  160.                 return false;
  161.         }
  162.  
  163.         fwrite(data, sizeof(*data), data_len, fp);
  164.  
  165.         fclose(fp);
  166.  
  167.         if (ctx->set_type != NULL)
  168.                 ctx->set_type(fullpath, mime_type);
  169.  
  170.         return true;
  171. }
  172.  
  173. /**
  174.  * Rewrite stylesheet \@import rules for save complete.
  175.  *
  176.  * \param  source  stylesheet source
  177.  * \param  size    size of source
  178.  * \param  base    url of stylesheet
  179.  * \param  osize   updated with the size of the result
  180.  * \return  converted source, or NULL on out of memory
  181.  */
  182.  
  183. static char *save_complete_rewrite_stylesheet_urls(save_complete_ctx *ctx,
  184.                 const char *source, unsigned long size, const nsurl *base,
  185.                 unsigned long *osize)
  186. {
  187.         char *rewritten;
  188.         unsigned long offset = 0;
  189.         unsigned int imports = 0;
  190.         nserror error;
  191.  
  192.         /* count number occurrences of @import to (over)estimate result size */
  193.         /* can't use strstr because source is not 0-terminated string */
  194.         for (offset = 0; SLEN("@import") < size &&
  195.                         offset <= size - SLEN("@import"); offset++) {
  196.                 if (source[offset] == '@' &&
  197.                                 tolower(source[offset + 1]) == 'i' &&
  198.                                 tolower(source[offset + 2]) == 'm' &&
  199.                                 tolower(source[offset + 3]) == 'p' &&
  200.                                 tolower(source[offset + 4]) == 'o' &&
  201.                                 tolower(source[offset + 5]) == 'r' &&
  202.                                 tolower(source[offset + 6]) == 't')
  203.                         imports++;
  204.         }
  205.  
  206.         rewritten = malloc(size + imports * 20);
  207.         if (rewritten == NULL)
  208.                 return NULL;
  209.         *osize = 0;
  210.  
  211.         offset = 0;
  212.         while (offset < size) {
  213.                 const char *import_url = NULL;
  214.                 char *import_url_copy;
  215.                 int import_url_len = 0;
  216.                 nsurl *url = NULL;
  217.                 regmatch_t match[11];
  218.                 int m = regexec(&save_complete_import_re, source + offset,
  219.                                 11, match, 0);
  220.                 if (m)
  221.                         break;
  222.  
  223.                 if (match[2].rm_so != -1) {
  224.                         import_url = source + offset + match[2].rm_so;
  225.                         import_url_len = match[2].rm_eo - match[2].rm_so;
  226.                 } else if (match[4].rm_so != -1) {
  227.                         import_url = source + offset + match[4].rm_so;
  228.                         import_url_len = match[4].rm_eo - match[4].rm_so;
  229.                 } else if (match[6].rm_so != -1) {
  230.                         import_url = source + offset + match[6].rm_so;
  231.                         import_url_len = match[6].rm_eo - match[6].rm_so;
  232.                 } else if (match[8].rm_so != -1) {
  233.                         import_url = source + offset + match[8].rm_so;
  234.                         import_url_len = match[8].rm_eo - match[8].rm_so;
  235.                 } else if (match[10].rm_so != -1) {
  236.                         import_url = source + offset + match[10].rm_so;
  237.                         import_url_len = match[10].rm_eo - match[10].rm_so;
  238.                 }
  239.                 assert(import_url != NULL);
  240.  
  241.                 import_url_copy = strndup(import_url, import_url_len);
  242.                 if (import_url_copy == NULL) {
  243.                         free(rewritten);
  244.                         return NULL;
  245.                 }
  246.  
  247.                 error = nsurl_join(base, import_url_copy, &url);
  248.                 free(import_url_copy);
  249.                 if (error == NSERROR_NOMEM) {
  250.                         free(rewritten);
  251.                         return NULL;
  252.                 }
  253.  
  254.                 /* copy data before match */
  255.                 memcpy(rewritten + *osize, source + offset, match[0].rm_so);
  256.                 *osize += match[0].rm_so;
  257.  
  258.                 if (url != NULL) {
  259.                         hlcache_handle *content;
  260.                         content = save_complete_ctx_find_content(ctx, url);
  261.                         if (content != NULL) {
  262.                                 /* replace import */
  263.                                 char buf[64];
  264.                                 snprintf(buf, sizeof buf, "@import '%p'",
  265.                                                 content);
  266.                                 memcpy(rewritten + *osize, buf, strlen(buf));
  267.                                 *osize += strlen(buf);
  268.                         } else {
  269.                                 /* copy import */
  270.                                 memcpy(rewritten + *osize,
  271.                                         source + offset + match[0].rm_so,
  272.                                         match[0].rm_eo - match[0].rm_so);
  273.                                 *osize += match[0].rm_eo - match[0].rm_so;
  274.                         }
  275.                         nsurl_unref(url);
  276.                 } else {
  277.                         /* copy import */
  278.                         memcpy(rewritten + *osize,
  279.                                 source + offset + match[0].rm_so,
  280.                                 match[0].rm_eo - match[0].rm_so);
  281.                         *osize += match[0].rm_eo - match[0].rm_so;
  282.                 }
  283.  
  284.                 assert(0 < match[0].rm_eo);
  285.                 offset += match[0].rm_eo;
  286.         }
  287.  
  288.         /* copy rest of source */
  289.         if (offset < size) {
  290.                 memcpy(rewritten + *osize, source + offset, size - offset);
  291.                 *osize += size - offset;
  292.         }
  293.  
  294.         return rewritten;
  295. }
  296.  
  297. static bool save_complete_save_stylesheet(save_complete_ctx *ctx,
  298.                 hlcache_handle *css)
  299. {
  300.         const char *css_data;
  301.         unsigned long css_size;
  302.         char *source;
  303.         unsigned long source_len;
  304.         struct nscss_import *imports;
  305.         uint32_t import_count;
  306.         lwc_string *type;
  307.         char filename[32];
  308.         bool result;
  309.  
  310.         if (save_complete_ctx_has_content(ctx, css))
  311.                 return true;
  312.  
  313.         if (save_complete_ctx_add_content(ctx, css) == false) {
  314.                 warn_user("NoMemory", 0);
  315.                 return false;
  316.         }
  317.  
  318.         imports = nscss_get_imports(css, &import_count);
  319.         if (save_complete_save_imported_sheets(ctx,
  320.                         imports, import_count) == false)
  321.                 return false;
  322.  
  323.         css_data = content_get_source_data(css, &css_size);
  324.         source = save_complete_rewrite_stylesheet_urls(ctx, css_data, css_size,
  325.                         hlcache_handle_get_url(css), &source_len);
  326.         if (source == NULL) {
  327.                 warn_user("NoMemory", 0);
  328.                 return false;
  329.         }
  330.  
  331.         type = content_get_mime_type(css);
  332.         if (type == NULL) {
  333.                 free(source);
  334.                 return false;
  335.         }
  336.  
  337.         snprintf(filename, sizeof filename, "%p", css);
  338.  
  339.         result = save_complete_save_buffer(ctx, filename,
  340.                         source, source_len, type);
  341.  
  342.         lwc_string_unref(type);
  343.         free(source);
  344.  
  345.         return result;
  346. }
  347.  
  348. static bool save_complete_save_imported_sheets(save_complete_ctx *ctx,
  349.                 struct nscss_import *imports, uint32_t import_count)
  350. {
  351.         uint32_t i;
  352.  
  353.         for (i = 0; i < import_count; i++) {
  354.                 if (save_complete_save_stylesheet(ctx, imports[i].c) == false)
  355.                         return false;
  356.         }
  357.  
  358.         return true;
  359. }
  360.  
  361. static bool save_complete_save_html_stylesheet(save_complete_ctx *ctx,
  362.                 struct html_stylesheet *sheet)
  363. {
  364.         if (sheet->type == HTML_STYLESHEET_INTERNAL) {
  365.                 if (save_complete_save_imported_sheets(ctx,
  366.                                 sheet->data.internal->imports,
  367.                                 sheet->data.internal->import_count) == false)
  368.                         return false;
  369.  
  370.                 return true;
  371.         }
  372.  
  373.         if (sheet->data.external == NULL)
  374.                 return true;
  375.  
  376.         return save_complete_save_stylesheet(ctx, sheet->data.external);
  377. }
  378.  
  379. static bool save_complete_save_html_stylesheets(save_complete_ctx *ctx,
  380.                 hlcache_handle *c)
  381. {
  382.         struct html_stylesheet *sheets;
  383.         unsigned int i, count;
  384.  
  385.         sheets = html_get_stylesheets(c, &count);
  386.  
  387.         for (i = STYLESHEET_START; i != count; i++) {
  388.                 if (save_complete_save_html_stylesheet(ctx,
  389.                                 &sheets[i]) == false)
  390.                         return false;
  391.         }
  392.  
  393.         return true;
  394. }
  395.  
  396. static bool save_complete_save_html_object(save_complete_ctx *ctx,
  397.                 hlcache_handle *obj)
  398. {
  399.         const char *obj_data;
  400.         unsigned long obj_size;
  401.         lwc_string *type;
  402.         bool result;
  403.         char filename[32];
  404.  
  405.         if (content_get_type(obj) == CONTENT_NONE)
  406.                 return true;
  407.  
  408.         obj_data = content_get_source_data(obj, &obj_size);
  409.         if (obj_data == NULL)
  410.                 return true;
  411.  
  412.         if (save_complete_ctx_has_content(ctx, obj))
  413.                 return true;
  414.  
  415.         if (save_complete_ctx_add_content(ctx, obj) == false) {
  416.                 warn_user("NoMemory", 0);
  417.                 return false;
  418.         }
  419.  
  420.         if (content_get_type(obj) == CONTENT_HTML) {
  421.                 return save_complete_save_html(ctx, obj, false);
  422.         }
  423.  
  424.         snprintf(filename, sizeof filename, "%p", obj);
  425.  
  426.         type = content_get_mime_type(obj);
  427.         if (type == NULL)
  428.                 return false;
  429.  
  430.         result = save_complete_save_buffer(ctx, filename,
  431.                         obj_data, obj_size, type);
  432.  
  433.         lwc_string_unref(type);
  434.  
  435.         return result;
  436. }
  437.  
  438. static bool save_complete_save_html_objects(save_complete_ctx *ctx,
  439.                 hlcache_handle *c)
  440. {
  441.         struct content_html_object *object;
  442.         unsigned int count;
  443.  
  444.         object = html_get_objects(c, &count);
  445.  
  446.         for (; object != NULL; object = object->next) {
  447.                 if (object->content != NULL) {
  448.                         if (save_complete_save_html_object(ctx,
  449.                                         object->content) == false)
  450.                                 return false;
  451.                 }
  452.         }
  453.  
  454.         return true;
  455. }
  456.  
  457. static bool save_complete_libdom_treewalk(dom_node *root,
  458.                 bool (*callback)(dom_node *node,
  459.                                 save_complete_event_type event_type, void *ctx),
  460.                 void *ctx)
  461. {
  462.         dom_node *node;
  463.  
  464.         node = dom_node_ref(root); /* tree root */
  465.  
  466.         while (node != NULL) {
  467.                 dom_node *next = NULL;
  468.                 dom_exception exc;
  469.  
  470.                 exc = dom_node_get_first_child(node, &next);
  471.                 if (exc != DOM_NO_ERR) {
  472.                         dom_node_unref(node);
  473.                         break;
  474.                 }
  475.  
  476.                 if (next != NULL) {  /* 1. children */
  477.                         dom_node_unref(node);
  478.                         node = next;
  479.                 } else {
  480.                         exc = dom_node_get_next_sibling(node, &next);
  481.                         if (exc != DOM_NO_ERR) {
  482.                                 dom_node_unref(node);
  483.                                 break;
  484.                         }
  485.  
  486.                         if (next != NULL) {  /* 2. siblings */
  487.                                 if (callback(node, EVENT_LEAVE, ctx) == false) {
  488.                                         return false;
  489.                                 }
  490.                                 dom_node_unref(node);
  491.                                 node = next;
  492.                         } else {  /* 3. ancestor siblings */
  493.                                 while (node != NULL) {
  494.                                         exc = dom_node_get_next_sibling(node,
  495.                                                         &next);
  496.                                         if (exc != DOM_NO_ERR) {
  497.                                                 dom_node_unref(node);
  498.                                                 node = NULL;
  499.                                                 break;
  500.                                         }
  501.  
  502.                                         if (next != NULL) {
  503.                                                 dom_node_unref(next);
  504.                                                 break;
  505.                                         }
  506.  
  507.                                         exc = dom_node_get_parent_node(node,
  508.                                                         &next);
  509.                                         if (exc != DOM_NO_ERR) {
  510.                                                 dom_node_unref(node);
  511.                                                 node = NULL;
  512.                                                 break;
  513.                                         }
  514.  
  515.                                         if (callback(node, EVENT_LEAVE,
  516.                                                         ctx) == false) {
  517.                                                 return false;
  518.                                         }
  519.                                         dom_node_unref(node);
  520.                                         node = next;
  521.                                 }
  522.  
  523.                                 if (node == NULL)
  524.                                         break;
  525.  
  526.                                 exc = dom_node_get_next_sibling(node, &next);
  527.                                 if (exc != DOM_NO_ERR) {
  528.                                         dom_node_unref(node);
  529.                                         break;
  530.                                 }
  531.  
  532.                                 if (callback(node, EVENT_LEAVE, ctx) == false) {
  533.                                         return false;
  534.                                 }
  535.                                 dom_node_unref(node);
  536.                                 node = next;
  537.                         }
  538.                 }
  539.  
  540.                 assert(node != NULL);
  541.  
  542.                 if (callback(node, EVENT_ENTER, ctx) == false) {
  543.                         return false; /* callback caused early termination */
  544.                 }
  545.  
  546.         }
  547.  
  548.         return true;
  549. }
  550.  
  551. static bool save_complete_rewrite_url_value(save_complete_ctx *ctx,
  552.                 const char *value, size_t value_len)
  553. {
  554.         nsurl *url;
  555.         hlcache_handle *content;
  556.         char *escaped;
  557.         nserror error;
  558.         utf8_convert_ret ret;
  559.  
  560.         error = nsurl_join(ctx->base, value, &url);
  561.         if (error == NSERROR_NOMEM)
  562.                 return false;
  563.  
  564.         if (url != NULL) {
  565.                 content = save_complete_ctx_find_content(ctx, url);
  566.                 if (content != NULL) {
  567.                         /* found a match */
  568.                         nsurl_unref(url);
  569.  
  570.                         fprintf(ctx->fp, "\"%p\"", content);
  571.                 } else {
  572.                         /* no match found */
  573.                         ret = utf8_to_html(nsurl_access(url), "UTF-8",
  574.                                         nsurl_length(url), &escaped);
  575.                         nsurl_unref(url);
  576.  
  577.                         if (ret != UTF8_CONVERT_OK)
  578.                                 return false;
  579.  
  580.                         fprintf(ctx->fp, "\"%s\"", escaped);
  581.  
  582.                         free(escaped);
  583.                 }
  584.         } else {
  585.                 ret = utf8_to_html(value, "UTF-8", value_len, &escaped);
  586.                 if (ret != UTF8_CONVERT_OK)
  587.                         return false;
  588.  
  589.                 fprintf(ctx->fp, "\"%s\"", escaped);
  590.  
  591.                 free(escaped);
  592.         }
  593.  
  594.         return true;
  595. }
  596.  
  597. static bool save_complete_write_value(save_complete_ctx *ctx,
  598.                 const char *value, size_t value_len)
  599. {
  600.         char *escaped;
  601.         utf8_convert_ret ret;
  602.  
  603.         ret = utf8_to_html(value, "UTF-8", value_len, &escaped);
  604.         if (ret != UTF8_CONVERT_OK)
  605.                 return false;
  606.  
  607.         fprintf(ctx->fp, "\"%s\"", escaped);
  608.  
  609.         free(escaped);
  610.  
  611.         return true;
  612. }
  613.  
  614. static bool save_complete_handle_attr_value(save_complete_ctx *ctx,
  615.                 dom_string *node_name, dom_string *attr_name,
  616.                 dom_string *attr_value)
  617. {
  618.         const char *node_data = dom_string_data(node_name);
  619.         size_t node_len = dom_string_byte_length(node_name);
  620.         const char *name_data = dom_string_data(attr_name);
  621.         size_t name_len = dom_string_byte_length(attr_name);
  622.         const char *value_data = dom_string_data(attr_value);
  623.         size_t value_len = dom_string_byte_length(attr_value);
  624.  
  625.         /**
  626.          * We only need to consider the following cases:
  627.          *
  628.          * Attribute:      Elements:
  629.          *
  630.          * 1)   data         <object>
  631.          * 2)   href         <a> <area> <link>
  632.          * 3)   src          <script> <input> <frame> <iframe> <img>
  633.          * 4)   background   any (except those above)
  634.          */
  635.         /* 1 */
  636.         if (name_len == SLEN("data") &&
  637.                         strncasecmp(name_data, "data", name_len) == 0) {
  638.                 if (node_len == SLEN("object") &&
  639.                                 strncasecmp(node_data,
  640.                                                 "object", node_len) == 0) {
  641.                         return save_complete_rewrite_url_value(ctx,
  642.                                         value_data, value_len);
  643.                 } else {
  644.                         return save_complete_write_value(ctx,
  645.                                         value_data, value_len);
  646.                 }
  647.         }
  648.         /* 2 */
  649.         else if (name_len == SLEN("href") &&
  650.                         strncasecmp(name_data, "href", name_len) == 0) {
  651.                 if ((node_len == SLEN("a") &&
  652.                                 strncasecmp(node_data, "a", node_len) == 0) ||
  653.                         (node_len == SLEN("area") &&
  654.                                 strncasecmp(node_data, "area",
  655.                                         node_len) == 0) ||
  656.                         (node_len == SLEN("link") &&
  657.                                 strncasecmp(node_data, "link",
  658.                                         node_len) == 0)) {
  659.                         return save_complete_rewrite_url_value(ctx,
  660.                                         value_data, value_len);
  661.                 } else {
  662.                         return save_complete_write_value(ctx,
  663.                                         value_data, value_len);
  664.                 }
  665.         }
  666.         /* 3 */
  667.         else if (name_len == SLEN("src") &&
  668.                         strncasecmp(name_data, "src", name_len) == 0) {
  669.                 if ((node_len == SLEN("frame") &&
  670.                                 strncasecmp(node_data, "frame",
  671.                                         node_len) == 0) ||
  672.                         (node_len == SLEN("iframe") &&
  673.                                 strncasecmp(node_data, "iframe",
  674.                                         node_len) == 0) ||
  675.                         (node_len == SLEN("input") &&
  676.                                 strncasecmp(node_data, "input",
  677.                                         node_len) == 0) ||
  678.                         (node_len == SLEN("img") &&
  679.                                 strncasecmp(node_data, "img",
  680.                                         node_len) == 0) ||
  681.                         (node_len == SLEN("script") &&
  682.                                 strncasecmp(node_data, "script",
  683.                                         node_len) == 0)) {
  684.                         return save_complete_rewrite_url_value(ctx,
  685.                                         value_data, value_len);
  686.                 } else {
  687.                         return save_complete_write_value(ctx,
  688.                                         value_data, value_len);
  689.                 }
  690.         }
  691.         /* 4 */
  692.         else if (name_len == SLEN("background") &&
  693.                         strncasecmp(name_data, "background", name_len) == 0) {
  694.                 return save_complete_rewrite_url_value(ctx,
  695.                                 value_data, value_len);
  696.         } else {
  697.                 return save_complete_write_value(ctx,
  698.                                 value_data, value_len);
  699.         }
  700. }
  701.  
  702. static bool save_complete_handle_attr(save_complete_ctx *ctx,
  703.                 dom_string *node_name, dom_attr *attr)
  704. {
  705.         dom_string *name;
  706.         const char *name_data;
  707.         size_t name_len;
  708.         dom_string *value;
  709.         dom_exception error;
  710.  
  711.         error = dom_attr_get_name(attr, &name);
  712.         if (error != DOM_NO_ERR)
  713.                 return false;
  714.  
  715.         if (name == NULL)
  716.                 return true;
  717.  
  718.         error = dom_attr_get_value(attr, &value);
  719.         if (error != DOM_NO_ERR) {
  720.                 dom_string_unref(name);
  721.                 return false;
  722.         }
  723.  
  724.         name_data = dom_string_data(name);
  725.         name_len = dom_string_byte_length(name);
  726.  
  727.         fputc(' ', ctx->fp);
  728.         fwrite(name_data, sizeof(*name_data), name_len, ctx->fp);
  729.  
  730.         if (value != NULL) {
  731.                 fputc('=', ctx->fp);
  732.                 if (save_complete_handle_attr_value(ctx, node_name,
  733.                                 name, value) == false) {
  734.                         dom_string_unref(value);
  735.                         dom_string_unref(name);
  736.                         return false;
  737.                 }
  738.         }
  739.  
  740.         dom_string_unref(name);
  741.  
  742.         return true;
  743. }
  744.  
  745. static bool save_complete_handle_attrs(save_complete_ctx *ctx,
  746.                 dom_string *node_name, dom_namednodemap *attrs)
  747. {
  748.         uint32_t length, i;
  749.         dom_exception error;
  750.  
  751.         error = dom_namednodemap_get_length(attrs, &length);
  752.         if (error != DOM_NO_ERR)
  753.                 return false;
  754.  
  755.         for (i = 0; i < length; i++) {
  756.                 dom_attr *attr;
  757.  
  758.                 error = dom_namednodemap_item(attrs, i, (void *) &attr);
  759.                 if (error != DOM_NO_ERR)
  760.                         return false;
  761.  
  762.                 if (attr == NULL)
  763.                         continue;
  764.  
  765.                 if (save_complete_handle_attr(ctx, node_name, attr) == false) {
  766.                         dom_node_unref(attr);
  767.                         return false;
  768.                 }
  769.  
  770.                 dom_node_unref(attr);
  771.         }
  772.  
  773.         return true;
  774. }
  775.  
  776. static bool save_complete_handle_element(save_complete_ctx *ctx,
  777.                 dom_node *node, save_complete_event_type event_type)
  778. {
  779.         dom_string *name;
  780.         dom_namednodemap *attrs;
  781.         const char *name_data;
  782.         size_t name_len;
  783.         bool process = true;
  784.         dom_exception error;
  785.  
  786.         ctx->iter_state = STATE_NORMAL;
  787.  
  788.         error = dom_node_get_node_name(node, &name);
  789.         if (error != DOM_NO_ERR)
  790.                 return false;
  791.  
  792.         if (name == NULL)
  793.                 return true;
  794.  
  795.         name_data = dom_string_data(name);
  796.         name_len = dom_string_byte_length(name);
  797.  
  798.         if (name_len == SLEN("base") &&
  799.                         strncasecmp(name_data, "base", name_len) == 0) {
  800.                 /* Elide BASE elements from the output */
  801.                 process = false;
  802.         } else if (name_len == SLEN("meta") &&
  803.                         strncasecmp(name_data, "meta", name_len) == 0) {
  804.                 /* Don't emit close tags for META elements */
  805.                 if (event_type == EVENT_LEAVE) {
  806.                         process = false;
  807.                 } else {
  808.                         /* Elide meta charsets */
  809.                         dom_string *value;
  810.                         error = dom_element_get_attribute(node,
  811.                                         corestring_dom_http_equiv, &value);
  812.                         if (error != DOM_NO_ERR) {
  813.                                 dom_string_unref(name);
  814.                                 return false;
  815.                         }
  816.  
  817.                         if (value != NULL) {
  818.                                 if (dom_string_length(value) ==
  819.                                         SLEN("Content-Type") &&
  820.                                         strncasecmp(dom_string_data(value),
  821.                                                 "Content-Type",
  822.                                                 SLEN("Content-Type")) == 0)
  823.                                         process = false;
  824.  
  825.                                 dom_string_unref(value);
  826.                         } else {
  827.                                 bool yes;
  828.  
  829.                                 error = dom_element_has_attribute(node,
  830.                                                 corestring_dom_charset, &yes);
  831.                                 if (error != DOM_NO_ERR) {
  832.                                         dom_string_unref(name);
  833.                                         return false;
  834.                                 }
  835.  
  836.                                 if (yes)
  837.                                         process = false;
  838.                         }
  839.                 }
  840.         } else if (event_type == EVENT_LEAVE &&
  841.                         ((name_len == SLEN("link") &&
  842.                         strncasecmp(name_data, "link", name_len) == 0))) {
  843.                 /* Don't emit close tags for void elements */
  844.                 process = false;
  845.         }
  846.  
  847.         if (process == false) {
  848.                 dom_string_unref(name);
  849.                 return true;
  850.         }
  851.  
  852.         fputc('<', ctx->fp);
  853.         if (event_type == EVENT_LEAVE)
  854.                 fputc('/', ctx->fp);
  855.         fwrite(name_data, sizeof(*name_data), name_len, ctx->fp);
  856.  
  857.         if (event_type == EVENT_ENTER) {
  858.                 error = dom_node_get_attributes(node, &attrs);
  859.                 if (error != DOM_NO_ERR) {
  860.                         dom_string_unref(name);
  861.                         return false;
  862.                 }
  863.  
  864.                 if (save_complete_handle_attrs(ctx, name, attrs) == false) {
  865.                         dom_namednodemap_unref(attrs);
  866.                         dom_string_unref(name);
  867.                         return false;
  868.                 }
  869.  
  870.                 dom_namednodemap_unref(attrs);
  871.         }
  872.  
  873.         fputc('>', ctx->fp);
  874.  
  875.         /* Rewrite contents of style elements */
  876.         if (event_type == EVENT_ENTER && name_len == SLEN("style") &&
  877.                         strncasecmp(name_data, "style", name_len) == 0) {
  878.                 dom_string *content;
  879.  
  880.                 error = dom_node_get_text_content(node, &content);
  881.                 if (error != DOM_NO_ERR) {
  882.                         dom_string_unref(name);
  883.                         return false;
  884.                 }
  885.  
  886.                 if (content != NULL) {
  887.                         char *rewritten;
  888.                         unsigned long len;
  889.  
  890.                         /* Rewrite @import rules */
  891.                         rewritten = save_complete_rewrite_stylesheet_urls(
  892.                                         ctx,
  893.                                         dom_string_data(content),
  894.                                         dom_string_byte_length(content),
  895.                                         ctx->base,
  896.                                         &len);
  897.                         if (rewritten == NULL) {
  898.                                 dom_string_unref(content);
  899.                                 dom_string_unref(name);
  900.                                 return false;
  901.                         }
  902.  
  903.                         dom_string_unref(content);
  904.  
  905.                         fwrite(rewritten, sizeof(*rewritten), len, ctx->fp);
  906.  
  907.                         free(rewritten);
  908.                 }
  909.  
  910.                 ctx->iter_state = STATE_IN_STYLE;
  911.         } else if (event_type == EVENT_ENTER && name_len == SLEN("head") &&
  912.                         strncasecmp(name_data, "head", name_len) == 0) {
  913.                 /* If this is a HEAD element, insert a meta charset */
  914.                 fputs("<META http-equiv=\"Content-Type\" "
  915.                                 "content=\"text/html; charset=utf-8\">",
  916.                                 ctx->fp);
  917.         }
  918.  
  919.         dom_string_unref(name);
  920.  
  921.         return true;
  922. }
  923.  
  924. static bool save_complete_node_handler(dom_node *node,
  925.                 save_complete_event_type event_type, void *ctxin)
  926. {
  927.         save_complete_ctx *ctx = ctxin;
  928.         dom_node_type type;
  929.         dom_exception error;
  930.         utf8_convert_ret ret;
  931.  
  932.         error = dom_node_get_node_type(node, &type);
  933.         if (error != DOM_NO_ERR)
  934.                 return false;
  935.  
  936.         if (type == DOM_ELEMENT_NODE) {
  937.                 return save_complete_handle_element(ctx, node, event_type);
  938.         } else if (type == DOM_TEXT_NODE || type == DOM_COMMENT_NODE) {
  939.                 if (event_type != EVENT_ENTER)
  940.                         return true;
  941.  
  942.                 if (ctx->iter_state != STATE_IN_STYLE) {
  943.                         /* Emit text content */
  944.                         dom_string *text;
  945.                         const char *text_data;
  946.                         size_t text_len;
  947.  
  948.                         error = dom_characterdata_get_data(node, &text);
  949.                         if (error != DOM_NO_ERR) {
  950.                                 return false;
  951.                         }
  952.  
  953.                         if (type == DOM_COMMENT_NODE)
  954.                                 fwrite("<!--", 1, sizeof("<!--") - 1, ctx->fp);
  955.  
  956.                         if (text != NULL) {
  957.                                 char *escaped;
  958.  
  959.                                 text_data = dom_string_data(text);
  960.                                 text_len = dom_string_byte_length(text);
  961.  
  962.                                 ret = utf8_to_html(text_data, "UTF-8",
  963.                                                 text_len, &escaped);
  964.                                 if (ret != UTF8_CONVERT_OK)
  965.                                         return false;
  966.  
  967.                                 fwrite(escaped, sizeof(*escaped),
  968.                                                 strlen(escaped), ctx->fp);
  969.  
  970.                                 free(escaped);
  971.  
  972.                                 dom_string_unref(text);
  973.                         }
  974.  
  975.                         if (type == DOM_COMMENT_NODE) {
  976.                                 fwrite("-->", 1, sizeof("-->") - 1, ctx->fp);
  977.                         }
  978.                 }
  979.  
  980.         } else if (type == DOM_DOCUMENT_TYPE_NODE) {
  981.                 dom_string *name;
  982.                 const char *name_data;
  983.                 size_t name_len;
  984.  
  985.                 if (event_type != EVENT_ENTER)
  986.                         return true;
  987.  
  988.                 error = dom_document_type_get_name(node, &name);
  989.                 if (error != DOM_NO_ERR)
  990.                         return false;
  991.  
  992.                 if (name == NULL)
  993.                         return true;
  994.  
  995.                 name_data = dom_string_data(name);
  996.                 name_len = dom_string_byte_length(name);
  997.  
  998.                 fputs("<!DOCTYPE ", ctx->fp);
  999.                 fwrite(name_data, sizeof(*name_data), name_len, ctx->fp);
  1000.  
  1001.                 dom_string_unref(name);
  1002.  
  1003.                 error = dom_document_type_get_public_id(node, &name);
  1004.                 if (error != DOM_NO_ERR)
  1005.                         return false;
  1006.  
  1007.                 if (name != NULL) {
  1008.                         name_data = dom_string_data(name);
  1009.                         name_len = dom_string_byte_length(name);
  1010.  
  1011.                         if (name_len > 0)
  1012.                                 fprintf(ctx->fp, " PUBLIC \"%.*s\"",
  1013.                                                 (int) name_len, name_data);
  1014.  
  1015.                         dom_string_unref(name);
  1016.                 }
  1017.  
  1018.                 error = dom_document_type_get_system_id(node, &name);
  1019.                 if (error != DOM_NO_ERR)
  1020.                         return false;
  1021.  
  1022.                 if (name != NULL) {
  1023.                         name_data = dom_string_data(name);
  1024.                         name_len = dom_string_byte_length(name);
  1025.  
  1026.                         if (name_len > 0)
  1027.                                 fprintf(ctx->fp, " \"%.*s\"",
  1028.                                                 (int) name_len, name_data);
  1029.  
  1030.                         dom_string_unref(name);
  1031.                 }
  1032.  
  1033.                 fputc('>', ctx->fp);
  1034.         } else if (type == DOM_DOCUMENT_NODE) {
  1035.                 /* Do nothing */
  1036.         } else {
  1037.                 LOG(("Unhandled node type: %d", type));
  1038.         }
  1039.  
  1040.         return true;
  1041. }
  1042.  
  1043. static bool save_complete_save_html_document(save_complete_ctx *ctx,
  1044.                 hlcache_handle *c, bool index)
  1045. {
  1046.         bool error;
  1047.         FILE *fp;
  1048.         dom_document *doc;
  1049.         lwc_string *mime_type;
  1050.         char filename[32];
  1051.         char fullpath[PATH_MAX];
  1052.  
  1053.         strncpy(fullpath, ctx->path, sizeof fullpath);
  1054.  
  1055.         if (index)
  1056.                 snprintf(filename, sizeof filename, "index");
  1057.         else
  1058.                 snprintf(filename, sizeof filename, "%p", c);
  1059.  
  1060.         error = path_add_part(fullpath, sizeof fullpath, filename);
  1061.         if (error == false) {
  1062.                 warn_user("NoMemory", NULL);
  1063.                 return false;
  1064.         }
  1065.  
  1066.         fp = fopen(fullpath, "wb");
  1067.         if (fp == NULL) {
  1068.                 warn_user("NoMemory", NULL);
  1069.                 return false;
  1070.         }
  1071.  
  1072.         ctx->base = html_get_base_url(c);
  1073.         ctx->fp = fp;
  1074.         ctx->iter_state = STATE_NORMAL;
  1075.  
  1076.         doc = html_get_document(c);
  1077.  
  1078.         if (save_complete_libdom_treewalk((dom_node *) doc,
  1079.                         save_complete_node_handler, ctx) == false) {
  1080.                 warn_user("NoMemory", 0);
  1081.                 fclose(fp);
  1082.                 return false;
  1083.         }
  1084.  
  1085.         fclose(fp);
  1086.  
  1087.         mime_type = content_get_mime_type(c);
  1088.         if (mime_type != NULL) {
  1089.                 if (ctx->set_type != NULL)
  1090.                         ctx->set_type(fullpath, mime_type);
  1091.  
  1092.                 lwc_string_unref(mime_type);
  1093.         }
  1094.  
  1095.         return true;
  1096. }
  1097.  
  1098. /**
  1099.  * Save an HTML page with all dependencies, recursing through imported pages.
  1100.  *
  1101.  * \param  ctx    Save complete context
  1102.  * \param  c      Content to save
  1103.  * \param  index  true to save as "index"
  1104.  * \return  true on success, false on error and error reported
  1105.  */
  1106. static bool save_complete_save_html(save_complete_ctx *ctx, hlcache_handle *c,
  1107.                 bool index)
  1108. {
  1109.         if (content_get_type(c) != CONTENT_HTML)
  1110.                 return false;
  1111.  
  1112.         if (save_complete_ctx_has_content(ctx, c))
  1113.                 return true;
  1114.  
  1115.         if (save_complete_save_html_stylesheets(ctx, c) == false)
  1116.                 return false;
  1117.  
  1118.         if (save_complete_save_html_objects(ctx, c) == false)
  1119.                 return false;
  1120.  
  1121.         return save_complete_save_html_document(ctx, c, index);
  1122. }
  1123.  
  1124.  
  1125. /**
  1126.  * Create the inventory file listing original URLs.
  1127.  */
  1128.  
  1129. static bool save_complete_inventory(save_complete_ctx *ctx)
  1130. {
  1131.         FILE *fp;
  1132.         bool error;
  1133.         save_complete_entry *entry;
  1134.         char fullpath[PATH_MAX];
  1135.  
  1136.         strncpy(fullpath, ctx->path, sizeof fullpath);
  1137.         error = path_add_part(fullpath, sizeof fullpath, "Inventory");
  1138.         if (error == false) {
  1139.                 warn_user("NoMemory", NULL);
  1140.                 return false;
  1141.         }
  1142.  
  1143.         fp = fopen(fullpath, "w");
  1144.         if (fp == NULL) {
  1145.                 LOG(("fopen(): errno = %i", errno));
  1146.                 warn_user("SaveError", strerror(errno));
  1147.                 return false;
  1148.         }
  1149.  
  1150.         for (entry = ctx->list; entry != NULL; entry = entry->next) {
  1151.                 fprintf(fp, "%p %s\n", entry->content,
  1152.                                 nsurl_access(hlcache_handle_get_url(
  1153.                                                 entry->content)));
  1154.         }
  1155.  
  1156.         fclose(fp);
  1157.  
  1158.         return true;
  1159. }
  1160.  
  1161. /* Documented in save_complete.h */
  1162. void save_complete_init(void)
  1163. {
  1164.         /* Match an @import rule - see CSS 2.1 G.1. */
  1165.         regcomp_wrapper(&save_complete_import_re,
  1166.                         "@import"               /* IMPORT_SYM */
  1167.                         "[ \t\r\n\f]*"          /* S* */
  1168.                         /* 1 */
  1169.                         "("                     /* [ */
  1170.                         /* 2 3 */
  1171.                         "\"(([^\"]|[\\]\")*)\"" /* STRING (approximated) */
  1172.                         "|"
  1173.                         /* 4 5 */
  1174.                         "'(([^']|[\\]')*)'"
  1175.                         "|"                     /* | */
  1176.                         "url\\([ \t\r\n\f]*"    /* URI (approximated) */
  1177.                              /* 6 7 */
  1178.                              "\"(([^\"]|[\\]\")*)\""
  1179.                              "[ \t\r\n\f]*\\)"
  1180.                         "|"
  1181.                         "url\\([ \t\r\n\f]*"
  1182.                             /* 8 9 */
  1183.                              "'(([^']|[\\]')*)'"
  1184.                              "[ \t\r\n\f]*\\)"
  1185.                         "|"
  1186.                         "url\\([ \t\r\n\f]*"
  1187.                            /* 10 */
  1188.                              "([^) \t\r\n\f]*)"
  1189.                              "[ \t\r\n\f]*\\)"
  1190.                         ")",                    /* ] */
  1191.                         REG_EXTENDED | REG_ICASE);
  1192. }
  1193.  
  1194. /* Documented in save_complete.h */
  1195. bool save_complete(hlcache_handle *c, const char *path,
  1196.                 save_complete_set_type_cb set_type)
  1197. {
  1198.         bool result;
  1199.         save_complete_ctx ctx;
  1200.  
  1201.         save_complete_ctx_initialise(&ctx, path, set_type);
  1202.        
  1203.         result = save_complete_save_html(&ctx, c, true);
  1204.  
  1205.         if (result)
  1206.                 result = save_complete_inventory(&ctx);
  1207.  
  1208.         save_complete_ctx_finalise(&ctx);
  1209.  
  1210.         return result;
  1211. }
  1212.  
  1213.