Home | History | Annotate | Line # | Download | only in libarchive
      1 /*-
      2  * Copyright (c) 2014 Sebastian Freundt
      3  * Author: Sebastian Freundt  <devel (at) fresse.org>
      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 
     28 #include "archive_platform.h"
     29 
     30 #ifdef HAVE_ERRNO_H
     31 #include <errno.h>
     32 #endif
     33 #include <stdio.h>
     34 #ifdef HAVE_STDLIB_H
     35 #include <stdlib.h>
     36 #endif
     37 #ifdef HAVE_STRING_H
     38 #include <string.h>
     39 #endif
     40 #ifdef HAVE_TIME_H
     41 #include <time.h>
     42 #endif
     43 
     44 #include "archive.h"
     45 #include "archive_entry.h"
     46 #include "archive_entry_locale.h"
     47 #include "archive_private.h"
     48 #include "archive_random_private.h"
     49 #include "archive_write_private.h"
     50 #include "archive_write_set_format_private.h"
     51 
     52 struct warc_s {
     53 	unsigned int omit_warcinfo:1;
     54 
     55 	time_t now;
     56 	mode_t typ;
     57 	unsigned int rng;
     58 	/* populated size */
     59 	uint64_t populz;
     60 };
     61 
     62 static const char warcinfo[] =
     63     "software: libarchive/" ARCHIVE_VERSION_ONLY_STRING "\r\n"
     64     "format: WARC file version 1.0\r\n";
     65 
     66 typedef enum {
     67 	WT_NONE,
     68 	/* warcinfo */
     69 	WT_INFO,
     70 	/* metadata */
     71 	WT_META,
     72 	/* resource */
     73 	WT_RSRC,
     74 	/* request, unsupported */
     75 	WT_REQ,
     76 	/* response, unsupported */
     77 	WT_RSP,
     78 	/* revisit, unsupported */
     79 	WT_RVIS,
     80 	/* conversion, unsupported */
     81 	WT_CONV,
     82 	/* continuation, unsupported at the moment */
     83 	WT_CONT,
     84 	/* invalid type */
     85 	LAST_WT
     86 } warc_type_t;
     87 
     88 typedef struct {
     89 	warc_type_t type;
     90 	const char *tgturi;
     91 	const char *recid;
     92 	time_t rtime;
     93 	time_t mtime;
     94 	const char *cnttyp;
     95 	uint64_t cntlen;
     96 } warc_essential_hdr_t;
     97 
     98 typedef struct {
     99 	unsigned int u[4U];
    100 } warc_uuid_t;
    101 
    102 static int _warc_options(struct archive_write*, const char *key, const char *v);
    103 static int _warc_header(struct archive_write *a, struct archive_entry *entry);
    104 static ssize_t _warc_data(struct archive_write *a, const void *buf, size_t sz);
    105 static int _warc_finish_entry(struct archive_write *a);
    106 static int _warc_close(struct archive_write *a);
    107 static int _warc_free(struct archive_write *a);
    108 
    109 /* private routines */
    110 static ssize_t _popul_ehdr(struct archive_string *t, size_t z, warc_essential_hdr_t);
    111 static int _gen_uuid(warc_uuid_t *tgt);
    112 
    113 
    114 /*
    116  * Set output format to ISO 28500 (aka WARC) format.
    117  */
    118 int
    119 archive_write_set_format_warc(struct archive *_a)
    120 {
    121 	struct archive_write *a = (struct archive_write *)_a;
    122 	struct warc_s *w;
    123 
    124 	archive_check_magic(_a, ARCHIVE_WRITE_MAGIC,
    125 	    ARCHIVE_STATE_NEW, "archive_write_set_format_warc");
    126 
    127 	/* If another format was already registered, unregister it. */
    128 	if (a->format_free != NULL) {
    129 		(a->format_free)(a);
    130 	}
    131 
    132 	w = malloc(sizeof(*w));
    133 	if (w == NULL) {
    134 		archive_set_error(&a->archive, ENOMEM,
    135 		    "Can't allocate warc data");
    136 		return (ARCHIVE_FATAL);
    137 	}
    138 	/* by default we're emitting a file wide header */
    139 	w->omit_warcinfo = 0U;
    140 	/* obtain current time for date fields */
    141 	w->now = time(NULL);
    142 	/* reset file type info */
    143 	w->typ = 0;
    144 	/* also initialise our rng */
    145 	w->rng = (unsigned int)w->now;
    146 
    147 	a->format_data = w;
    148 	a->format_name = "WARC/1.0";
    149 	a->format_options = _warc_options;
    150 	a->format_write_header = _warc_header;
    151 	a->format_write_data = _warc_data;
    152 	a->format_close = _warc_close;
    153 	a->format_free = _warc_free;
    154 	a->format_finish_entry = _warc_finish_entry;
    155 	a->archive.archive_format = ARCHIVE_FORMAT_WARC;
    156 	a->archive.archive_format_name = "WARC/1.0";
    157 	return (ARCHIVE_OK);
    158 }
    159 
    160 
    161 /* archive methods */
    163 static int
    164 _warc_options(struct archive_write *a, const char *key, const char *val)
    165 {
    166 	struct warc_s *w = a->format_data;
    167 
    168 	if (strcmp(key, "omit-warcinfo") == 0) {
    169 		if (val == NULL || strcmp(val, "true") == 0) {
    170 			/* great */
    171 			w->omit_warcinfo = 1U;
    172 			return (ARCHIVE_OK);
    173 		}
    174 	}
    175 
    176 	/* Note: The "warn" return is just to inform the options
    177 	 * supervisor that we didn't handle it.  It will generate
    178 	 * a suitable error if no one used this option. */
    179 	return (ARCHIVE_WARN);
    180 }
    181 
    182 static int
    183 _warc_header(struct archive_write *a, struct archive_entry *entry)
    184 {
    185 	struct warc_s *w = a->format_data;
    186 	struct archive_string hdr;
    187 #define MAX_HDR_SIZE 512
    188 
    189 	/* check whether warcinfo record needs outputting */
    190 	if (!w->omit_warcinfo) {
    191 		ssize_t r;
    192 		warc_essential_hdr_t wi = {
    193 			WT_INFO,
    194 			/*uri*/NULL,
    195 			/*urn*/NULL,
    196 			/*rtm*/0,
    197 			/*mtm*/0,
    198 			/*cty*/"application/warc-fields",
    199 			/*len*/sizeof(warcinfo) - 1U,
    200 		};
    201 		wi.rtime = w->now;
    202 		wi.mtime = w->now;
    203 
    204 		archive_string_init(&hdr);
    205 		r = _popul_ehdr(&hdr, MAX_HDR_SIZE, wi);
    206 		if (r >= 0) {
    207 			/* jackpot! */
    208 			/* now also use HDR buffer for the actual warcinfo */
    209 			archive_strncat(&hdr, warcinfo, sizeof(warcinfo) -1);
    210 
    211 			/* append end-of-record indicator */
    212 			archive_strncat(&hdr, "\r\n\r\n", 4);
    213 
    214 			/* write to output stream */
    215 			__archive_write_output(a, hdr.s, archive_strlen(&hdr));
    216 		}
    217 		/* indicate we're done with file header writing */
    218 		w->omit_warcinfo = 1U;
    219 		archive_string_free(&hdr);
    220 	}
    221 
    222 	if (archive_entry_pathname(entry) == NULL) {
    223 		archive_set_error(&a->archive, EINVAL,
    224 		    "Invalid filename");
    225 		return (ARCHIVE_WARN);
    226 	}
    227 
    228 	w->typ = archive_entry_filetype(entry);
    229 	w->populz = 0U;
    230 	if (w->typ == AE_IFREG) {
    231 		warc_essential_hdr_t rh = {
    232 			WT_RSRC,
    233 			/*uri*/NULL,
    234 			/*urn*/NULL,
    235 			/*rtm*/0,
    236 			/*mtm*/0,
    237 			/*cty*/NULL,
    238 			/*len*/0,
    239 		};
    240 		ssize_t r;
    241 		rh.tgturi = archive_entry_pathname(entry);
    242 		rh.rtime = w->now;
    243 		rh.mtime = archive_entry_mtime(entry);
    244 		rh.cntlen = (size_t)archive_entry_size(entry);
    245 
    246 		archive_string_init(&hdr);
    247 		r = _popul_ehdr(&hdr, MAX_HDR_SIZE, rh);
    248 		if (r < 0) {
    249 			/* don't bother */
    250 			archive_set_error(
    251 				&a->archive,
    252 				ARCHIVE_ERRNO_FILE_FORMAT,
    253 				"cannot archive file");
    254 			return (ARCHIVE_WARN);
    255 		}
    256 		/* otherwise append to output stream */
    257 		__archive_write_output(a, hdr.s, r);
    258 		/* and let subsequent calls to _data() know about the size */
    259 		w->populz = rh.cntlen;
    260 		archive_string_free(&hdr);
    261 		return (ARCHIVE_OK);
    262 	}
    263 	/* just resort to erroring as per Tim's advice */
    264 	__archive_write_entry_filetype_unsupported(
    265 	    &a->archive, entry, "WARC");
    266 	return (ARCHIVE_FAILED);
    267 }
    268 
    269 static ssize_t
    270 _warc_data(struct archive_write *a, const void *buf, size_t len)
    271 {
    272 	struct warc_s *w = a->format_data;
    273 
    274 	if (w->typ == AE_IFREG) {
    275 		int rc;
    276 
    277 		/* never write more bytes than announced */
    278 		if (len > w->populz) {
    279 			len = (size_t)w->populz;
    280 		}
    281 
    282 		/* now then, out we put the whole shebang */
    283 		rc = __archive_write_output(a, buf, len);
    284 		if (rc != ARCHIVE_OK) {
    285 			return rc;
    286 		}
    287 	}
    288 	return len;
    289 }
    290 
    291 static int
    292 _warc_finish_entry(struct archive_write *a)
    293 {
    294 	static const char _eor[] = "\r\n\r\n";
    295 	struct warc_s *w = a->format_data;
    296 
    297 	if (w->typ == AE_IFREG) {
    298 		int rc = __archive_write_output(a, _eor, sizeof(_eor) - 1U);
    299 
    300 		if (rc != ARCHIVE_OK) {
    301 			return rc;
    302 		}
    303 	}
    304 	/* reset type info */
    305 	w->typ = 0;
    306 	return (ARCHIVE_OK);
    307 }
    308 
    309 static int
    310 _warc_close(struct archive_write *a)
    311 {
    312 	(void)a; /* UNUSED */
    313 	return (ARCHIVE_OK);
    314 }
    315 
    316 static int
    317 _warc_free(struct archive_write *a)
    318 {
    319 	struct warc_s *w = a->format_data;
    320 
    321 	free(w);
    322 	a->format_data = NULL;
    323 	return (ARCHIVE_OK);
    324 }
    325 
    326 
    327 /* private routines */
    329 static void
    330 xstrftime(struct archive_string *as, const char *fmt, time_t t)
    331 {
    332 /** like strftime(3) but for time_t objects */
    333 	struct tm *rt;
    334 #if defined(HAVE_GMTIME_R) || defined(HAVE_GMTIME_S)
    335 	struct tm timeHere;
    336 #endif
    337 	char strtime[100];
    338 	size_t len;
    339 
    340 #if defined(HAVE_GMTIME_S)
    341 	rt = gmtime_s(&timeHere, &t) ? NULL : &timeHere;
    342 #elif defined(HAVE_GMTIME_R)
    343 	rt = gmtime_r(&t, &timeHere);
    344 #else
    345 	rt = gmtime(&t);
    346 #endif
    347 	if (!rt)
    348 		return;
    349 	/* leave the hard yacker to our role model strftime() */
    350 	len = strftime(strtime, sizeof(strtime)-1, fmt, rt);
    351 	archive_strncat(as, strtime, len);
    352 }
    353 
    354 static ssize_t
    355 _popul_ehdr(struct archive_string *tgt, size_t tsz, warc_essential_hdr_t hdr)
    356 {
    357 	static const char _ver[] = "WARC/1.0\r\n";
    358 	static const char * const _typ[LAST_WT] = {
    359 		NULL, "warcinfo", "metadata", "resource", NULL
    360 	};
    361 	char std_uuid[48U];
    362 
    363 	if (hdr.type == WT_NONE || hdr.type > WT_RSRC) {
    364 		/* brilliant, how exactly did we get here? */
    365 		return -1;
    366 	}
    367 
    368 	archive_strcpy(tgt, _ver);
    369 
    370 	archive_string_sprintf(tgt, "WARC-Type: %s\r\n", _typ[hdr.type]);
    371 
    372 	if (hdr.tgturi != NULL) {
    373 		/* check if there's a xyz:// */
    374 		static const char _uri[] = "";
    375 		static const char _fil[] = "file://";
    376 		const char *u;
    377 		char *chk = strchr(hdr.tgturi, ':');
    378 
    379 		if (chk != NULL && chk[1U] == '/' && chk[2U] == '/') {
    380 			/* yep, it's definitely a URI */
    381 			u = _uri;
    382 		} else {
    383 			/* hm, best to prepend file:// then */
    384 			u = _fil;
    385 		}
    386 		archive_string_sprintf(tgt,
    387 			"WARC-Target-URI: %s%s\r\n", u, hdr.tgturi);
    388 	}
    389 
    390 	/* record time is usually when the http is sent off,
    391 	 * just treat the archive writing as such for a moment */
    392 	xstrftime(tgt, "WARC-Date: %Y-%m-%dT%H:%M:%SZ\r\n", hdr.rtime);
    393 
    394 	/* while we're at it, record the mtime */
    395 	xstrftime(tgt, "Last-Modified: %Y-%m-%dT%H:%M:%SZ\r\n", hdr.mtime);
    396 
    397 	if (hdr.recid == NULL) {
    398 		/* generate one, grrrr */
    399 		warc_uuid_t u;
    400 
    401 		_gen_uuid(&u);
    402 		/* Unfortunately, archive_string_sprintf does not
    403 		 * handle the minimum number following '%'.
    404 		 * So we have to use snprintf function here instead
    405 		 * of archive_string_snprintf function. */
    406 #if defined(_WIN32) && !defined(__CYGWIN__) && !( defined(_MSC_VER) && _MSC_VER >= 1900)
    407 #define snprintf _snprintf
    408 #endif
    409 		snprintf(
    410 			std_uuid, sizeof(std_uuid),
    411 			"<urn:uuid:%08x-%04x-%04x-%04x-%04x%08x>",
    412 			u.u[0U],
    413 			u.u[1U] >> 16U, u.u[1U] & 0xffffU,
    414 			u.u[2U] >> 16U, u.u[2U] & 0xffffU,
    415 			u.u[3U]);
    416 		hdr.recid = std_uuid;
    417 	}
    418 
    419 	/* record-id is mandatory, fingers crossed we won't fail */
    420 	archive_string_sprintf(tgt, "WARC-Record-ID: %s\r\n", hdr.recid);
    421 
    422 	if (hdr.cnttyp != NULL) {
    423 		archive_string_sprintf(tgt, "Content-Type: %s\r\n", hdr.cnttyp);
    424 	}
    425 
    426 	/* next one is mandatory */
    427 	archive_string_sprintf(tgt, "Content-Length: %ju\r\n", (uintmax_t)hdr.cntlen);
    428 	/**/
    429 	archive_strncat(tgt, "\r\n", 2);
    430 
    431 	return (archive_strlen(tgt) >= tsz)? -1: (ssize_t)archive_strlen(tgt);
    432 }
    433 
    434 static int
    435 _gen_uuid(warc_uuid_t *tgt)
    436 {
    437 	archive_random(tgt->u, sizeof(tgt->u));
    438 	/* obey uuid version 4 rules */
    439 	tgt->u[1U] &= 0xffff0fffU;
    440 	tgt->u[1U] |= 0x4000U;
    441 	tgt->u[2U] &= 0x3fffffffU;
    442 	tgt->u[2U] |= 0x80000000U;
    443 	return 0;
    444 }
    445 
    446 /* archive_write_set_format_warc.c ends here */
    447