archive_write_set_format_warc.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. /*-
  2. * Copyright (c) 2014 Sebastian Freundt
  3. * Author: Sebastian Freundt <[email protected]>
  4. *
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. * 1. Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * 2. Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
  17. * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  18. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  19. * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
  20. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  21. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  22. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  23. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  25. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. #include "archive_platform.h"
  28. __FBSDID("$FreeBSD$");
  29. #ifdef HAVE_ERRNO_H
  30. #include <errno.h>
  31. #endif
  32. #include <stdio.h>
  33. #ifdef HAVE_STDLIB_H
  34. #include <stdlib.h>
  35. #endif
  36. #ifdef HAVE_STRING_H
  37. #include <string.h>
  38. #endif
  39. #ifdef HAVE_TIME_H
  40. #include <time.h>
  41. #endif
  42. #include "archive.h"
  43. #include "archive_entry.h"
  44. #include "archive_entry_locale.h"
  45. #include "archive_private.h"
  46. #include "archive_random_private.h"
  47. #include "archive_write_private.h"
  48. #include "archive_write_set_format_private.h"
  49. struct warc_s {
  50. unsigned int omit_warcinfo:1;
  51. time_t now;
  52. mode_t typ;
  53. unsigned int rng;
  54. /* populated size */
  55. uint64_t populz;
  56. };
  57. static const char warcinfo[] =
  58. "software: libarchive/" ARCHIVE_VERSION_ONLY_STRING "\r\n"
  59. "format: WARC file version 1.0\r\n";
  60. typedef enum {
  61. WT_NONE,
  62. /* warcinfo */
  63. WT_INFO,
  64. /* metadata */
  65. WT_META,
  66. /* resource */
  67. WT_RSRC,
  68. /* request, unsupported */
  69. WT_REQ,
  70. /* response, unsupported */
  71. WT_RSP,
  72. /* revisit, unsupported */
  73. WT_RVIS,
  74. /* conversion, unsupported */
  75. WT_CONV,
  76. /* continuation, unsupported at the moment */
  77. WT_CONT,
  78. /* invalid type */
  79. LAST_WT
  80. } warc_type_t;
  81. typedef struct {
  82. warc_type_t type;
  83. const char *tgturi;
  84. const char *recid;
  85. time_t rtime;
  86. time_t mtime;
  87. const char *cnttyp;
  88. uint64_t cntlen;
  89. } warc_essential_hdr_t;
  90. typedef struct {
  91. unsigned int u[4U];
  92. } warc_uuid_t;
  93. static int _warc_options(struct archive_write*, const char *key, const char *v);
  94. static int _warc_header(struct archive_write *a, struct archive_entry *entry);
  95. static ssize_t _warc_data(struct archive_write *a, const void *buf, size_t sz);
  96. static int _warc_finish_entry(struct archive_write *a);
  97. static int _warc_close(struct archive_write *a);
  98. static int _warc_free(struct archive_write *a);
  99. /* private routines */
  100. static ssize_t _popul_ehdr(struct archive_string *t, size_t z, warc_essential_hdr_t);
  101. static int _gen_uuid(warc_uuid_t *tgt);
  102. /*
  103. * Set output format to ISO 28500 (aka WARC) format.
  104. */
  105. int
  106. archive_write_set_format_warc(struct archive *_a)
  107. {
  108. struct archive_write *a = (struct archive_write *)_a;
  109. struct warc_s *w;
  110. archive_check_magic(_a, ARCHIVE_WRITE_MAGIC,
  111. ARCHIVE_STATE_NEW, "archive_write_set_format_warc");
  112. /* If another format was already registered, unregister it. */
  113. if (a->format_free != NULL) {
  114. (a->format_free)(a);
  115. }
  116. w = malloc(sizeof(*w));
  117. if (w == NULL) {
  118. archive_set_error(&a->archive, ENOMEM,
  119. "Can't allocate warc data");
  120. return (ARCHIVE_FATAL);
  121. }
  122. /* by default we're emitting a file wide header */
  123. w->omit_warcinfo = 0U;
  124. /* obtain current time for date fields */
  125. w->now = time(NULL);
  126. /* reset file type info */
  127. w->typ = 0;
  128. /* also initialise our rng */
  129. w->rng = (unsigned int)w->now;
  130. a->format_data = w;
  131. a->format_name = "WARC/1.0";
  132. a->format_options = _warc_options;
  133. a->format_write_header = _warc_header;
  134. a->format_write_data = _warc_data;
  135. a->format_close = _warc_close;
  136. a->format_free = _warc_free;
  137. a->format_finish_entry = _warc_finish_entry;
  138. a->archive.archive_format = ARCHIVE_FORMAT_WARC;
  139. a->archive.archive_format_name = "WARC/1.0";
  140. return (ARCHIVE_OK);
  141. }
  142. /* archive methods */
  143. static int
  144. _warc_options(struct archive_write *a, const char *key, const char *val)
  145. {
  146. struct warc_s *w = a->format_data;
  147. if (strcmp(key, "omit-warcinfo") == 0) {
  148. if (val == NULL || strcmp(val, "true") == 0) {
  149. /* great */
  150. w->omit_warcinfo = 1U;
  151. return (ARCHIVE_OK);
  152. }
  153. }
  154. /* Note: The "warn" return is just to inform the options
  155. * supervisor that we didn't handle it. It will generate
  156. * a suitable error if no one used this option. */
  157. return (ARCHIVE_WARN);
  158. }
  159. static int
  160. _warc_header(struct archive_write *a, struct archive_entry *entry)
  161. {
  162. struct warc_s *w = a->format_data;
  163. struct archive_string hdr;
  164. #define MAX_HDR_SIZE 512
  165. /* check whether warcinfo record needs outputting */
  166. if (!w->omit_warcinfo) {
  167. ssize_t r;
  168. warc_essential_hdr_t wi = {
  169. WT_INFO,
  170. /*uri*/NULL,
  171. /*urn*/NULL,
  172. /*rtm*/0,
  173. /*mtm*/0,
  174. /*cty*/"application/warc-fields",
  175. /*len*/sizeof(warcinfo) - 1U,
  176. };
  177. wi.rtime = w->now;
  178. wi.mtime = w->now;
  179. archive_string_init(&hdr);
  180. r = _popul_ehdr(&hdr, MAX_HDR_SIZE, wi);
  181. if (r >= 0) {
  182. /* jackpot! */
  183. /* now also use HDR buffer for the actual warcinfo */
  184. archive_strncat(&hdr, warcinfo, sizeof(warcinfo) -1);
  185. /* append end-of-record indicator */
  186. archive_strncat(&hdr, "\r\n\r\n", 4);
  187. /* write to output stream */
  188. __archive_write_output(a, hdr.s, archive_strlen(&hdr));
  189. }
  190. /* indicate we're done with file header writing */
  191. w->omit_warcinfo = 1U;
  192. archive_string_free(&hdr);
  193. }
  194. if (archive_entry_pathname(entry) == NULL) {
  195. archive_set_error(&a->archive, EINVAL,
  196. "Invalid filename");
  197. return (ARCHIVE_WARN);
  198. }
  199. w->typ = archive_entry_filetype(entry);
  200. w->populz = 0U;
  201. if (w->typ == AE_IFREG) {
  202. warc_essential_hdr_t rh = {
  203. WT_RSRC,
  204. /*uri*/NULL,
  205. /*urn*/NULL,
  206. /*rtm*/0,
  207. /*mtm*/0,
  208. /*cty*/NULL,
  209. /*len*/0,
  210. };
  211. ssize_t r;
  212. rh.tgturi = archive_entry_pathname(entry);
  213. rh.rtime = w->now;
  214. rh.mtime = archive_entry_mtime(entry);
  215. rh.cntlen = (size_t)archive_entry_size(entry);
  216. archive_string_init(&hdr);
  217. r = _popul_ehdr(&hdr, MAX_HDR_SIZE, rh);
  218. if (r < 0) {
  219. /* don't bother */
  220. archive_set_error(
  221. &a->archive,
  222. ARCHIVE_ERRNO_FILE_FORMAT,
  223. "cannot archive file");
  224. return (ARCHIVE_WARN);
  225. }
  226. /* otherwise append to output stream */
  227. __archive_write_output(a, hdr.s, r);
  228. /* and let subsequent calls to _data() know about the size */
  229. w->populz = rh.cntlen;
  230. archive_string_free(&hdr);
  231. return (ARCHIVE_OK);
  232. }
  233. /* just resort to erroring as per Tim's advice */
  234. __archive_write_entry_filetype_unsupported(
  235. &a->archive, entry, "WARC");
  236. return (ARCHIVE_FAILED);
  237. }
  238. static ssize_t
  239. _warc_data(struct archive_write *a, const void *buf, size_t len)
  240. {
  241. struct warc_s *w = a->format_data;
  242. if (w->typ == AE_IFREG) {
  243. int rc;
  244. /* never write more bytes than announced */
  245. if (len > w->populz) {
  246. len = (size_t)w->populz;
  247. }
  248. /* now then, out we put the whole shebang */
  249. rc = __archive_write_output(a, buf, len);
  250. if (rc != ARCHIVE_OK) {
  251. return rc;
  252. }
  253. }
  254. return len;
  255. }
  256. static int
  257. _warc_finish_entry(struct archive_write *a)
  258. {
  259. static const char _eor[] = "\r\n\r\n";
  260. struct warc_s *w = a->format_data;
  261. if (w->typ == AE_IFREG) {
  262. int rc = __archive_write_output(a, _eor, sizeof(_eor) - 1U);
  263. if (rc != ARCHIVE_OK) {
  264. return rc;
  265. }
  266. }
  267. /* reset type info */
  268. w->typ = 0;
  269. return (ARCHIVE_OK);
  270. }
  271. static int
  272. _warc_close(struct archive_write *a)
  273. {
  274. (void)a; /* UNUSED */
  275. return (ARCHIVE_OK);
  276. }
  277. static int
  278. _warc_free(struct archive_write *a)
  279. {
  280. struct warc_s *w = a->format_data;
  281. free(w);
  282. a->format_data = NULL;
  283. return (ARCHIVE_OK);
  284. }
  285. /* private routines */
  286. static void
  287. xstrftime(struct archive_string *as, const char *fmt, time_t t)
  288. {
  289. /** like strftime(3) but for time_t objects */
  290. struct tm *rt;
  291. #if defined(HAVE_GMTIME_R) || defined(HAVE__GMTIME64_S)
  292. struct tm timeHere;
  293. #endif
  294. #if defined(HAVE__GMTIME64_S)
  295. errno_t terr;
  296. __time64_t tmptime;
  297. #endif
  298. char strtime[100];
  299. size_t len;
  300. #ifdef HAVE_GMTIME_R
  301. if ((rt = gmtime_r(&t, &timeHere)) == NULL)
  302. return;
  303. #elif defined(HAVE__GMTIME64_S)
  304. tmptime = t;
  305. terr = _gmtime64_s(&timeHere, &tmptime);
  306. if (terr)
  307. rt = NULL;
  308. else
  309. rt = &timeHere;
  310. #else
  311. if ((rt = gmtime(&t)) == NULL)
  312. return;
  313. #endif
  314. /* leave the hard yacker to our role model strftime() */
  315. len = strftime(strtime, sizeof(strtime)-1, fmt, rt);
  316. archive_strncat(as, strtime, len);
  317. }
  318. static ssize_t
  319. _popul_ehdr(struct archive_string *tgt, size_t tsz, warc_essential_hdr_t hdr)
  320. {
  321. static const char _ver[] = "WARC/1.0\r\n";
  322. static const char * const _typ[LAST_WT] = {
  323. NULL, "warcinfo", "metadata", "resource", NULL
  324. };
  325. char std_uuid[48U];
  326. if (hdr.type == WT_NONE || hdr.type > WT_RSRC) {
  327. /* brilliant, how exactly did we get here? */
  328. return -1;
  329. }
  330. archive_strcpy(tgt, _ver);
  331. archive_string_sprintf(tgt, "WARC-Type: %s\r\n", _typ[hdr.type]);
  332. if (hdr.tgturi != NULL) {
  333. /* check if there's a xyz:// */
  334. static const char _uri[] = "";
  335. static const char _fil[] = "file://";
  336. const char *u;
  337. char *chk = strchr(hdr.tgturi, ':');
  338. if (chk != NULL && chk[1U] == '/' && chk[2U] == '/') {
  339. /* yep, it's definitely a URI */
  340. u = _uri;
  341. } else {
  342. /* hm, best to prepend file:// then */
  343. u = _fil;
  344. }
  345. archive_string_sprintf(tgt,
  346. "WARC-Target-URI: %s%s\r\n", u, hdr.tgturi);
  347. }
  348. /* record time is usually when the http is sent off,
  349. * just treat the archive writing as such for a moment */
  350. xstrftime(tgt, "WARC-Date: %Y-%m-%dT%H:%M:%SZ\r\n", hdr.rtime);
  351. /* while we're at it, record the mtime */
  352. xstrftime(tgt, "Last-Modified: %Y-%m-%dT%H:%M:%SZ\r\n", hdr.mtime);
  353. if (hdr.recid == NULL) {
  354. /* generate one, grrrr */
  355. warc_uuid_t u;
  356. _gen_uuid(&u);
  357. /* Unfortunately, archive_string_sprintf does not
  358. * handle the minimum number following '%'.
  359. * So we have to use snprintf function here instead
  360. * of archive_string_snprintf function. */
  361. #if defined(_WIN32) && !defined(__CYGWIN__) && !( defined(_MSC_VER) && _MSC_VER >= 1900)
  362. #define snprintf _snprintf
  363. #endif
  364. snprintf(
  365. std_uuid, sizeof(std_uuid),
  366. "<urn:uuid:%08x-%04x-%04x-%04x-%04x%08x>",
  367. u.u[0U],
  368. u.u[1U] >> 16U, u.u[1U] & 0xffffU,
  369. u.u[2U] >> 16U, u.u[2U] & 0xffffU,
  370. u.u[3U]);
  371. hdr.recid = std_uuid;
  372. }
  373. /* record-id is mandatory, fingers crossed we won't fail */
  374. archive_string_sprintf(tgt, "WARC-Record-ID: %s\r\n", hdr.recid);
  375. if (hdr.cnttyp != NULL) {
  376. archive_string_sprintf(tgt, "Content-Type: %s\r\n", hdr.cnttyp);
  377. }
  378. /* next one is mandatory */
  379. archive_string_sprintf(tgt, "Content-Length: %ju\r\n", (uintmax_t)hdr.cntlen);
  380. /**/
  381. archive_strncat(tgt, "\r\n", 2);
  382. return (archive_strlen(tgt) >= tsz)? -1: (ssize_t)archive_strlen(tgt);
  383. }
  384. static int
  385. _gen_uuid(warc_uuid_t *tgt)
  386. {
  387. archive_random(tgt->u, sizeof(tgt->u));
  388. /* obey uuid version 4 rules */
  389. tgt->u[1U] &= 0xffff0fffU;
  390. tgt->u[1U] |= 0x4000U;
  391. tgt->u[2U] &= 0x3fffffffU;
  392. tgt->u[2U] |= 0x80000000U;
  393. return 0;
  394. }
  395. /* archive_write_set_format_warc.c ends here */