amiga-rhash/hash_print.c

680 lines
18 KiB
C

/* hash_print.c - functions to output hash sums using printf-like format */
#include "common_func.h" /* should be included before the C library files */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include "librhash/rhash.h"
#include "calc_sums.h"
#include "parse_cmdline.h"
#include "win_utils.h"
#include "hash_print.h"
/*=========================================================================
* Formatted output functions and structures
*=========================================================================*/
/**
* The table with information about hash functions.
*/
print_hash_info hash_info_table[32];
/**
* Possible types of a print_item.
*/
enum {
PRINT_ED2K_LINK = 0x100000,
PRINT_FLAG_UPPERCASE = 0x200000,
PRINT_FLAG_RAW = 0x0400000,
PRINT_FLAG_HEX = 0x0800000,
PRINT_FLAG_BASE32 = 0x1000000,
PRINT_FLAG_BASE64 = 0x2000000,
PRINT_FLAG_PAD_WITH_ZERO = 0x4000000,
PRINT_FLAGS_ALL = PRINT_FLAG_UPPERCASE | PRINT_FLAG_PAD_WITH_ZERO | PRINT_FLAG_RAW
| PRINT_FLAG_HEX | PRINT_FLAG_BASE32 | PRINT_FLAG_BASE64,
PRINT_STR = 0x10000000,
PRINT_ZERO,
PRINT_FILEPATH,
PRINT_BASENAME,
PRINT_URLNAME,
PRINT_SIZE,
PRINT_MTIME /*PRINT_ATIME, PRINT_CTIME*/
};
/* parse a token following a percent sign '%' */
static print_item* parse_percent_item(const char** str);
/**
* Allocate new print_item.
*
* @param flags the print_item flags
* @param hash_id optional hash_id
* @param data optional string to store
* @return allocated print_item
*/
static print_item* new_print_item(unsigned flags, unsigned hash_id, const char *data)
{
print_item* item = (print_item*)rsh_malloc(sizeof(print_item));
item->flags = flags;
item->hash_id = hash_id;
item->width = 0;
item->data = (data ? rsh_strdup(data) : NULL);
item->next = NULL;
return item;
}
/**
* Parse an escaped sequence in a printf-like format string.
*
* @param pformat pointer to the sequence, the pointer
* is changed to point to the next symbol after parsed sequence
* @return result character
*/
static char parse_escaped_char(const char **pformat)
{
const char* start = *pformat;
switch ( *((*pformat)++) ) {
case 't': return '\t';
case 'r': return '\r';
case 'n': return '\n';
case '\\': return '\\';
case 'x':
/* \xNN byte with hexadecimal value NN (1 to 2 digits) */
if ( IS_HEX(**pformat) ) {
int ch;
ch = (**pformat <= '9' ? **pformat & 15 : (**pformat + 9) & 15);
(*pformat) ++;
if (IS_HEX(**pformat)) {
/* read the second digit */
ch = 16 * ch + (**pformat <= '9' ? **pformat & 15 : (**pformat + 9) & 15);
(*pformat)++;
}
if (ch) return ch;
}
break;
default:
(*pformat)--;
/* \NNN - character with octal value NNN (1 to 3 digits) */
if ('0' < **pformat && **pformat <= '7') {
int ch = *((*pformat)++) - '0';
if ('0' <= **pformat && **pformat <= '7') {
ch = ch * 8 + *((*pformat)++) - '0';
if ('0' <= **pformat && **pformat <= '7')
ch = ch * 8 + *((*pformat)++) - '0';
}
return (char)ch;
}
}
*pformat = start;
return '\\';
}
/**
* Parse format string.
*
* @return a print_item list with parsed information
*/
print_item* parse_print_string(const char* format, unsigned *sum_mask)
{
char *buf, *p;
print_item *list = NULL, **tail, *item = NULL;
buf = p = (char*)rsh_malloc( strlen(format) + 1 );
tail = &list;
*sum_mask = 0;
for (;;) {
while (*format && *format != '%' && *format != '\\')
*(p++) = *(format++);
if (*format == '\\') {
if (*(++format) == '0') {
item = new_print_item(PRINT_ZERO, 0, NULL);
format++;
} else {
*p++ = parse_escaped_char(&format);
continue;
}
} else if (*format == '%') {
if ( *(++format) == '%' ) {
*(p++) = *format++;
continue;
} else {
item = parse_percent_item(&format);
if (!item) {
*(p++) = '%';
continue;
}
if (item->hash_id)
*sum_mask |= item->hash_id;
}
}
if (p > buf || (!*format && list == NULL && item == NULL)) {
*p = '\0';
*tail = new_print_item(PRINT_STR, 0, buf);
tail = &(*tail)->next;
p = buf;
}
if (item) {
*tail = item;
tail = &item->next;
item = NULL;
}
if (!*format)
break;
};
free(buf);
return list;
}
/**
* Convert given case-insensitive name to a printf directive id
*
* @param name printf directive name (not a 0-terminated)
* @param length name length
* @return directive id on success, 0 on fail
*/
static unsigned printf_name_to_id(const char* name, size_t length, unsigned *flags)
{
char buf[20];
size_t i;
print_hash_info *info = hash_info_table;
unsigned bit;
if (length > (sizeof(buf)-1)) return 0;
for (i = 0; i < length; i++) buf[i] = tolower(name[i]);
/* check for old '%{urlname}' directive for compatibility */
if (length == 7 && memcmp(buf, "urlname", 7) == 0) {
*flags = PRINT_URLNAME;
return 0;
} else if (length == 5 && memcmp(buf, "mtime", 5) == 0) {
*flags = PRINT_MTIME;
return 0;
}
for (bit = 1; bit <= RHASH_ALL_HASHES; bit = bit << 1, info++) {
if (memcmp(buf, info->short_name, length) == 0 &&
info->short_name[length] == 0) return bit;
}
return 0;
}
/**
* Parse a token followed by a percent sign in a printf-like format string.
*
* @return a print_item with parsed information
*/
print_item* parse_percent_item(const char** str)
{
const char* format = *str;
const char* p = NULL;
unsigned hash_id = 0;
unsigned modifier_flags = 0;
int id_found = 0;
int width = 0;
int pad_with_zero_bit = 0;
print_item* item = NULL;
static const char *short_hash = "CMHTGWRAE";
static const char *short_other = "Llpfus";
static const unsigned hash_ids[] = {
RHASH_CRC32, RHASH_MD5, RHASH_SHA1, RHASH_TTH, RHASH_GOST,
RHASH_WHIRLPOOL, RHASH_RIPEMD160, RHASH_AICH, RHASH_ED2K
};
static const unsigned other_flags[] = {
PRINT_ED2K_LINK, PRINT_ED2K_LINK, PRINT_FILEPATH, PRINT_BASENAME,
PRINT_URLNAME, PRINT_SIZE
};
/* detect the padding by zeros */
if (*format == '0') {
pad_with_zero_bit = PRINT_FLAG_PAD_WITH_ZERO;
format++;
}
/* parse the 'b','B','x' and '@' flags */
if (*format == 'x') {
modifier_flags |= PRINT_FLAG_HEX;
format++;
} else if (*format == 'b') {
modifier_flags |= PRINT_FLAG_BASE32;
format++;
} else if (*format == 'B') {
modifier_flags |= PRINT_FLAG_BASE64;
format++;
} else if (*format == '@') {
modifier_flags |= PRINT_FLAG_RAW;
format++;
}
for (; isdigit((unsigned char)*format); format++) width = 10 * width + (*format - '0');
/* if a complicated token enconuntered */
if (*format == '{') {
/* parse the token of the kind "%{some-token}" */
const char* p = ++format;
for (; isalnum((unsigned char)*p) || (*p == '-'); p++);
if (*p == '}') {
hash_id = printf_name_to_id(format, (int)(p - (format)), &modifier_flags);
format--;
if (hash_id || modifier_flags == PRINT_URLNAME || modifier_flags == PRINT_MTIME) {
/* set uppercase flag if the first letter of printf-entity is uppercase */
modifier_flags |= (format[1] & 0x20 ? 0 : PRINT_FLAG_UPPERCASE);
format = p;
id_found = 1;
}
} else {
format--;
}
}
/* if still not found a token denoting a hash function */
if (!id_found) {
const char upper = *format & ~0x20;
/* if the string terminated just after the '%' character */
if ( *format == '\0' ) return NULL;
/* look for a known token */
if ( (p = strchr(short_hash, upper)) ) {
assert( (p - short_hash) < (int)(sizeof(hash_ids) / sizeof(unsigned)) );
hash_id = hash_ids[p - short_hash];
modifier_flags |= (*format & 0x20 ? 0 : PRINT_FLAG_UPPERCASE);
}
else if ( (p = strchr(short_other, *format)) ) {
assert( (p - short_other) < (int)(sizeof(other_flags) / sizeof(unsigned)) );
modifier_flags = other_flags[p - short_other];
if (modifier_flags == PRINT_ED2K_LINK) {
modifier_flags |= (*p & 0x20 ? 0 : PRINT_FLAG_UPPERCASE);
hash_id = RHASH_ED2K | RHASH_AICH;
}
} else {
return 0; /* no valid token found */
}
}
modifier_flags |= pad_with_zero_bit;
item = new_print_item(modifier_flags, hash_id, NULL);
item->width = width;
*str = ++format;
return item;
}
/**
* Print EDonkey 2000 url for given file to a stream.
*
* @param out the stream where to print url to
* @param filename the file name
* @param filesize the file size
* @param sums the file hash sums
*/
static void fprint_ed2k_url(FILE* out, struct file_info *info, int print_type)
{
const char *filename = get_basename(file_info_get_utf8_print_path(info));
int upper_case = (print_type & PRINT_FLAG_UPPERCASE ? RHPR_UPPERCASE : 0);
int len = urlencode(NULL, filename) + int_len(info->size) + (info->sums_flags & RHASH_AICH ? 84 : 49);
char* buf = (char*)rsh_malloc( len + 1 );
char* dst = buf;
assert(info->sums_flags & (RHASH_ED2K|RHASH_AICH));
assert(info->rctx);
strcpy(dst, "ed2k://|file|");
dst += 13;
dst += urlencode(dst, filename);
*dst++ = '|';
sprintI64(dst, info->size, 0);
dst += strlen(dst);
*dst++ = '|';
rhash_print(dst, info->rctx, RHASH_ED2K, upper_case);
dst += 32;
if ((info->sums_flags & RHASH_AICH) != 0) {
strcpy(dst, "|h=");
rhash_print(dst += 3, info->rctx, RHASH_AICH, RHPR_BASE32 | upper_case);
dst += 32;
}
strcpy(dst, "|/");
fprintf(out, "%s", buf);
free(buf);
}
/**
* Output aligned uint64_t number to specified output stream.
*
* @param out the stream to output to
* @param filesize the 64-bit integer to output, usually a file size
* @param width minimal width of integer to output
* @param flag =1 if the integer shall be prepended by zeros
*/
static void fprintI64(FILE* out, uint64_t filesize, int width, int zero_pad)
{
char *buf = (char*)rsh_malloc(width > 40 ? width + 1 : 41);
int len = int_len(filesize);
sprintI64(buf, filesize, width);
if (len < width && zero_pad) {
memset(buf, '0', width-len);
}
fprintf(out, "%s", buf);
free(buf);
}
/**
* Print time formatted as hh:mm.ss YYYY-MM-DD to a file stream.
*
* @param out the stream to print the time to
* @param time the time to print
*/
static void print_time(FILE *out, time_t time)
{
struct tm *t = localtime(&time);
static struct tm zero_tm;
if (t == NULL) {
/* if a strange day, then print `00:00.00 1900-01-00' */
t = &zero_tm;
t->tm_hour = t->tm_min = t->tm_sec =
t->tm_year = t->tm_mon = t->tm_mday = 0;
}
fprintf(out, "%02u:%02u.%02u %4u-%02u-%02u", t->tm_hour, t->tm_min,
t->tm_sec, (1900 + t->tm_year), t->tm_mon + 1, t->tm_mday);
}
/**
* Print time formatted as hh:mm.ss YYYY-MM-DD to a file stream.
*
* @param out the stream to print the time to
* @param time the time to print
*/
static void print_time64(FILE *out, uint64_t time)
{
print_time(out, (time_t)time);
}
/**
* Print formatted file information to given output stream.
*
* @param out the stream to print information to
* @param list the format according to which information shall be printed
* @param info the file information
*/
void print_line(FILE* out, print_item* list, struct file_info *info)
{
const char* basename = get_basename(info->print_path), *tmp;
char *url = NULL, *ed2k_url = NULL;
char buffer[130];
for (; list; list = list->next) {
int print_type = list->flags & ~(PRINT_FLAGS_ALL);
size_t len;
/* output a hash function digest */
if (list->hash_id && print_type != PRINT_ED2K_LINK) {
unsigned hash_id = list->hash_id;
int print_flags = (list->flags & PRINT_FLAG_UPPERCASE ? RHPR_UPPERCASE : 0)
| (list->flags & PRINT_FLAG_RAW ? RHPR_RAW : 0)
| (list->flags & PRINT_FLAG_BASE32 ? RHPR_BASE32 : 0)
| (list->flags & PRINT_FLAG_BASE64 ? RHPR_BASE64 : 0)
| (list->flags & PRINT_FLAG_HEX ? RHPR_HEX : 0);
if ((hash_id == RHASH_GOST || hash_id == RHASH_GOST_CRYPTOPRO) && (opt.flags & OPT_GOST_REVERSE))
print_flags |= RHPR_REVERSE;
len = rhash_print(buffer, info->rctx, hash_id, print_flags);
assert(len < sizeof(buffer));
/* output the hash, exit on fail */
if (fwrite(buffer, 1, len, out) != len) break;
continue;
}
/* output other special items: filepath, URL-encoded filename etc. */
switch (print_type) {
case PRINT_STR:
fprintf(out, "%s", list->data);
break;
case PRINT_ZERO: /* the '\0' character */
fprintf(out, "%c", 0);
break;
case PRINT_FILEPATH:
fprintf(out, "%s", info->print_path);
break;
case PRINT_BASENAME: /* the filename without directory */
fprintf(out, "%s", basename);
break;
case PRINT_URLNAME: /* URL-encoded filename */
if (!url) {
tmp = get_basename(file_info_get_utf8_print_path(info));
url = (char*)rsh_malloc(urlencode(NULL, tmp) + 1);
urlencode(url, tmp);
}
fprintf(out, "%s", url);
break;
case PRINT_MTIME: /* the last-modified tine of the filename */
print_time64(out, info->file->mtime);
break;
case PRINT_SIZE: /* file size */
fprintI64(out, info->size, list->width, (list->flags & PRINT_FLAG_PAD_WITH_ZERO));
break;
case PRINT_ED2K_LINK:
fprint_ed2k_url(out, info, list->flags);
break;
}
}
free(url);
free(ed2k_url);
}
/**
* Release memory allocated by given print_item list.
*
* @param list the list to free
*/
void free_print_list(print_item* list)
{
while (list) {
print_item* next = list->next;
free((char*)list->data);
free(list);
list = next;
}
}
/*=========================================================================
* initialization of internal data
*=========================================================================*/
/**
* Initialize information about hashes, stored in the
* hash_info_table global variable.
*/
void init_hash_info_table(void)
{
unsigned index, bit;
unsigned short_opt_mask = RHASH_CRC32 | RHASH_MD5 | RHASH_SHA1 | RHASH_TTH | RHASH_ED2K |
RHASH_AICH | RHASH_WHIRLPOOL | RHASH_RIPEMD160 | RHASH_GOST | OPT_ED2K_LINK;
char* short_opt = "cmhteawrgl";
print_hash_info *info = hash_info_table;
unsigned fullmask = RHASH_ALL_HASHES | OPT_ED2K_LINK;
memset(hash_info_table, 0, sizeof(hash_info_table));
for (index = 0, bit = 1; bit <= fullmask; index++, bit = bit << 1, info++) {
const char *p;
char *e, *d;
info->short_char = ((bit & short_opt_mask) != 0 && *short_opt ?
*(short_opt++) : 0);
info->name = (bit & RHASH_ALL_HASHES ? rhash_get_name(bit) : "ED2K-LINK");
assert(strlen(info->name) < 15);
p = info->name;
d = info->short_name;
e = info->short_name + 15; /* buffer overflow protection */
if (memcmp(info->name, "SHA", 3) == 0) {
strcpy(d, p);
for (; *d && d < e; d++) {
if ('A' <= *d && *d <= 'Z') {
*d |= 0x20;
}
}
} else {
for (; *p && d < e; p++) {
if (*p != '-' || p[1] >= '9') {
*(d++) = (*p | 0x20);
}
}
}
*d = 0;
}
}
/**
* Initialize printf string according to program options.
* The function is called only when a printf format string is not specified
* from command line, so it should be constructed from other options.
*
* @param out a string buffer to place the resulting format string into.
*/
void init_printf_format(strbuf_t* out)
{
const char* fmt, *tail = 0;
unsigned bit, index = 0;
int uppercase;
char up_flag;
unsigned force_base32_mask = 0;
if (!opt.fmt) {
/* print SFV header for CRC32 or if no hash sums options specified */
opt.fmt = (opt.sum_flags == RHASH_CRC32 || !opt.sum_flags ? FMT_SFV : FMT_SIMPLE);
}
uppercase = ((opt.flags & OPT_UPPERCASE) ||
(!(opt.flags & OPT_LOWERCASE) && (opt.fmt & FMT_SFV)));
up_flag = (uppercase ? ~0x20 : 0xFF);
rsh_str_ensure_size(out, 1024); /* allocate big enough buffer */
if (opt.sum_flags & OPT_ED2K_LINK) {
rsh_str_append_n(out, "%l", 2);
out->str[1] &= up_flag;
return;
}
if (opt.sum_flags == 0) return;
if (opt.fmt == FMT_BSD) {
fmt = "\003(%p) = \001\\n";
} else if (opt.fmt == FMT_MAGNET) {
rsh_str_append(out, "magnet:?xl=%s&dn=%{urlname}");
fmt = "&xt=urn:\002:\001";
force_base32_mask = (RHASH_SHA1 | RHASH_BTIH);
tail = "\\n";
} else if (opt.fmt == FMT_SIMPLE && 0 == (opt.sum_flags & (opt.sum_flags - 1))) {
fmt = "\001 %p\\n";
} else {
rsh_str_append_n(out, "%p", 2);
fmt = (opt.fmt == FMT_SFV ? " \001" : " \001");
tail = "\\n";
}
/* loop by hashes */
for (bit = 1 << index; bit <= opt.sum_flags; bit = bit << 1, index++) {
const char *p;
print_hash_info *info;
if ((bit & opt.sum_flags) == 0) continue;
p = fmt;
info = &hash_info_table[index];
/* ensure the output buffer have enough space */
rsh_str_ensure_size(out, out->len + 256);
for (;;) {
int i;
while (*p >= 0x20) out->str[out->len++] = *(p++);
if (*p == 0) break;
switch ((int)*(p++)) {
case 1:
out->str[out->len++] = '%';
if ( (bit & force_base32_mask) != 0 ) {
out->str[out->len++] = 'b';
}
if (info->short_char) out->str[out->len++] = info->short_char & up_flag;
else {
char *letter;
out->str[out->len++] = '{';
letter = out->str + out->len;
rsh_str_append(out, info->short_name);
*letter &= up_flag;
out->str[out->len++] = '}';
}
break;
case 2:
rsh_str_append(out, rhash_get_magnet_name(bit));
break;
case 3:
rsh_str_append(out, info->name);
i = (int)strlen(info->name);
for (i = (i < 5 ? 6 - i : 1); i > 0; i--) out->str[out->len++] = ' ';
break;
}
}
}
if (tail) {
rsh_str_append(out, tail);
}
out->str[out->len] = '\0';
}
/*=========================================================================
* SFV format output functions
*=========================================================================*/
/**
* Format file information into SFV line and print it to the specified stream.
*
* @param out the stream to print the file information to
* @param file the file info to print
* @return 0 on success, -1 on fail with error code stored in errno
*/
int print_sfv_header_line(FILE* out, file_t* file, const char* printpath)
{
char buf[24];
/* skip stdin stream */
if ((file->mode & FILE_IFSTDIN) != 0) return 0;
#ifdef _WIN32
/* skip file if it can't be opened with exclusive sharing rights */
if (!can_open_exclusive(file->path)) {
return 0;
}
#endif
if (!printpath) printpath = file->path;
if (printpath[0] == '.' && IS_PATH_SEPARATOR(printpath[1])) printpath += 2;
sprintI64(buf, file->size, 12);
fprintf(out, "; %s ", buf);
print_time64(out, file->mtime);
fprintf(out, " %s\n", printpath);
return 0;
}
/**
* Print an SFV header banner. The banner consist of 3 comment lines,
* with the program description and current time.
*
* @param out a stream to print to
*/
void print_sfv_banner(FILE* out)
{
time_t cur_time = time(NULL);
struct tm *t = localtime(&cur_time);
if (t) {
fprintf(out, _("; Generated by %s v%s on %4u-%02u-%02u at %02u:%02u.%02u\n"),
PROGRAM_NAME, get_version_string(),
(1900 + t->tm_year), t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
fprintf(out, _("; Written by Aleksey (Akademgorodok) - http://rhash.sourceforge.net/\n;\n"));
}
}