diff --git a/common/stringhelp.c b/common/stringhelp.c index dd1711684..3424048f9 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -1,1627 +1,1657 @@ /* stringhelp.c - standard string helper functions * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007, * 2008, 2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2014 Werner Koch * Copyright (C) 2015 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #include #include #include #include #include #include #ifdef HAVE_PWD_H # include #endif #include #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif #include #include #include "util.h" #include "common-defs.h" #include "utf8conv.h" #include "sysutils.h" #include "stringhelp.h" #define tohex_lower(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'a')) /* Sometimes we want to avoid mixing slashes and backslashes on W32 and prefer backslashes. There is usual no problem with mixing them, however a very few W32 API calls can't grok plain slashes. Printing filenames with mixed slashes also looks a bit strange. This function has no effext on POSIX. */ static inline char * change_slashes (char *name) { #ifdef HAVE_DOSISH_SYSTEM char *p; if (strchr (name, '\\')) { for (p=name; *p; p++) if (*p == '/') *p = '\\'; } #endif /*HAVE_DOSISH_SYSTEM*/ return name; } /* * Check whether STRING starts with KEYWORD. The keyword is * delimited by end of string, a space or a tab. Returns NULL if not * found or a pointer into STRING to the next non-space character * after the KEYWORD (which may be end of string). */ char * has_leading_keyword (const char *string, const char *keyword) { size_t n = strlen (keyword); if (!strncmp (string, keyword, n) && (!string[n] || string[n] == ' ' || string[n] == '\t')) { string += n; while (*string == ' ' || *string == '\t') string++; return (char*)string; } return NULL; } /* * Look for the substring SUB in buffer and return a pointer to that * substring in BUFFER or NULL if not found. * Comparison is case-insensitive. */ const char * memistr (const void *buffer, size_t buflen, const char *sub) { const unsigned char *buf = buffer; const unsigned char *t = (const unsigned char *)buffer; const unsigned char *s = (const unsigned char *)sub; size_t n = buflen; for ( ; n ; t++, n-- ) { if ( toupper (*t) == toupper (*s) ) { for ( buf=t++, buflen = n--, s++; n && toupper (*t) == toupper (*s); t++, s++, n-- ) ; if (!*s) return (const char*)buf; t = buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } const char * ascii_memistr ( const void *buffer, size_t buflen, const char *sub ) { const unsigned char *buf = buffer; const unsigned char *t = (const unsigned char *)buf; const unsigned char *s = (const unsigned char *)sub; size_t n = buflen; for ( ; n ; t++, n-- ) { if (ascii_toupper (*t) == ascii_toupper (*s) ) { for ( buf=t++, buflen = n--, s++; n && ascii_toupper (*t) == ascii_toupper (*s); t++, s++, n-- ) ; if (!*s) return (const char*)buf; t = (const unsigned char *)buf; s = (const unsigned char *)sub ; n = buflen; } } return NULL; } /* This function is similar to strncpy(). However it won't copy more than N - 1 characters and makes sure that a '\0' is appended. With N given as 0, nothing will happen. With DEST given as NULL, memory will be allocated using xmalloc (i.e. if it runs out of core the function terminates). Returns DES or a pointer to the allocated memory. */ char * mem2str( char *dest , const void *src , size_t n ) { char *d; const char *s; if( n ) { if( !dest ) dest = xmalloc( n ) ; d = dest; s = src ; for(n--; n && *s; n-- ) *d++ = *s++; *d = '\0' ; } return dest ; } /**************** * remove leading and trailing white spaces */ char * trim_spaces( char *str ) { char *string, *p, *mark; string = str; /* find first non space character */ for( p=string; *p && isspace( *(byte*)p ) ; p++ ) ; /* move characters */ for( (mark = NULL); (*string = *p); string++, p++ ) if( isspace( *(byte*)p ) ) { if( !mark ) mark = string ; } else mark = NULL ; if( mark ) *mark = '\0' ; /* remove trailing spaces */ return str ; } + +/* Same as trim_spaces but only condider, space, tab, cr and lf as space. */ +char * +ascii_trim_spaces (char *str) +{ + char *string, *p, *mark; + + string = str; + + /* Find first non-ascii space character. */ + for (p=string; *p && ascii_isspace (*p); p++) + ; + /* Move characters. */ + for (mark=NULL; (*string = *p); string++, p++ ) + { + if (ascii_isspace (*p)) + { + if (!mark) + mark = string; + } + else + mark = NULL ; + } + if (mark) + *mark = '\0' ; /* Remove trailing spaces. */ + + return str ; +} + + /**************** * remove trailing white spaces */ char * trim_trailing_spaces( char *string ) { char *p, *mark; for( mark = NULL, p = string; *p; p++ ) { if( isspace( *(byte*)p ) ) { if( !mark ) mark = p; } else mark = NULL; } if( mark ) *mark = '\0' ; return string ; } unsigned trim_trailing_chars( byte *line, unsigned len, const char *trimchars ) { byte *p, *mark; unsigned n; for(mark=NULL, p=line, n=0; n < len; n++, p++ ) { if( strchr(trimchars, *p ) ) { if( !mark ) mark = p; } else mark = NULL; } if( mark ) { *mark = 0; return mark - line; } return len; } /**************** * remove trailing white spaces and return the length of the buffer */ unsigned trim_trailing_ws( byte *line, unsigned len ) { return trim_trailing_chars( line, len, " \t\r\n" ); } size_t length_sans_trailing_chars (const unsigned char *line, size_t len, const char *trimchars ) { const unsigned char *p, *mark; size_t n; for( mark=NULL, p=line, n=0; n < len; n++, p++ ) { if (strchr (trimchars, *p )) { if( !mark ) mark = p; } else mark = NULL; } if (mark) return mark - line; return len; } /* * Return the length of line ignoring trailing white-space. */ size_t length_sans_trailing_ws (const unsigned char *line, size_t len) { return length_sans_trailing_chars (line, len, " \t\r\n"); } /* * Extract from a given path the filename component. This function * terminates the process on memory shortage. */ char * make_basename(const char *filepath, const char *inputpath) { #ifdef __riscos__ return riscos_make_basename(filepath, inputpath); #else char *p; (void)inputpath; /* Only required for riscos. */ if ( !(p=strrchr(filepath, '/')) ) #ifdef HAVE_DOSISH_SYSTEM if ( !(p=strrchr(filepath, '\\')) ) #endif #ifdef HAVE_DRIVE_LETTERS if ( !(p=strrchr(filepath, ':')) ) #endif { return xstrdup(filepath); } return xstrdup(p+1); #endif } /* * Extract from a given filename the path prepended to it. If there * isn't a path prepended to the filename, a dot is returned ('.'). * This function terminates the process on memory shortage. */ char * make_dirname(const char *filepath) { char *dirname; int dirname_length; char *p; if ( !(p=strrchr(filepath, '/')) ) #ifdef HAVE_DOSISH_SYSTEM if ( !(p=strrchr(filepath, '\\')) ) #endif #ifdef HAVE_DRIVE_LETTERS if ( !(p=strrchr(filepath, ':')) ) #endif { return xstrdup("."); } dirname_length = p-filepath; dirname = xmalloc(dirname_length+1); strncpy(dirname, filepath, dirname_length); dirname[dirname_length] = 0; return dirname; } static char * get_pwdir (int xmode, const char *name) { char *result = NULL; #ifdef HAVE_PWD_H struct passwd *pwd = NULL; if (name) { #ifdef HAVE_GETPWNAM /* Fixme: We should use getpwnam_r if available. */ pwd = getpwnam (name); #endif } else { #ifdef HAVE_GETPWUID /* Fixme: We should use getpwuid_r if available. */ pwd = getpwuid (getuid()); #endif } if (pwd) { if (xmode) result = xstrdup (pwd->pw_dir); else result = xtrystrdup (pwd->pw_dir); } #else /*!HAVE_PWD_H*/ /* No support at all. */ (void)xmode; (void)name; #endif /*HAVE_PWD_H*/ return result; } /* xmode 0 := Return NULL on error 1 := Terminate on error 2 := Make sure that name is absolute; return NULL on error 3 := Make sure that name is absolute; terminate on error */ static char * do_make_filename (int xmode, const char *first_part, va_list arg_ptr) { const char *argv[32]; int argc; size_t n; int skip = 1; char *home_buffer = NULL; char *name, *home, *p; int want_abs; want_abs = !!(xmode & 2); xmode &= 1; n = strlen (first_part) + 1; argc = 0; while ( (argv[argc] = va_arg (arg_ptr, const char *)) ) { n += strlen (argv[argc]) + 1; if (argc >= DIM (argv)-1) { if (xmode) BUG (); gpg_err_set_errno (EINVAL); return NULL; } argc++; } n++; home = NULL; if (*first_part == '~') { if (first_part[1] == '/' || !first_part[1]) { /* This is the "~/" or "~" case. */ home = getenv("HOME"); if (!home) home = home_buffer = get_pwdir (xmode, NULL); if (home && *home) n += strlen (home); } else { /* This is the "~username/" or "~username" case. */ char *user; if (xmode) user = xstrdup (first_part+1); else { user = xtrystrdup (first_part+1); if (!user) return NULL; } p = strchr (user, '/'); if (p) *p = 0; skip = 1 + strlen (user); home = home_buffer = get_pwdir (xmode, user); xfree (user); if (home) n += strlen (home); else skip = 1; } } if (xmode) name = xmalloc (n); else { name = xtrymalloc (n); if (!name) { xfree (home_buffer); return NULL; } } if (home) p = stpcpy (stpcpy (name, home), first_part + skip); else p = stpcpy (name, first_part); xfree (home_buffer); for (argc=0; argv[argc]; argc++) { /* Avoid a leading double slash if the first part was "/". */ if (!argc && name[0] == '/' && !name[1]) p = stpcpy (p, argv[argc]); else p = stpcpy (stpcpy (p, "/"), argv[argc]); } if (want_abs) { #ifdef HAVE_DRIVE_LETTERS p = strchr (name, ':'); if (p) p++; else p = name; #else p = name; #endif if (*p != '/' #ifdef HAVE_DRIVE_LETTERS && *p != '\\' #endif ) { home = gnupg_getcwd (); if (!home) { if (xmode) { fprintf (stderr, "\nfatal: getcwd failed: %s\n", strerror (errno)); exit(2); } xfree (name); return NULL; } n = strlen (home) + 1 + strlen (name) + 1; if (xmode) home_buffer = xmalloc (n); else { home_buffer = xtrymalloc (n); if (!home_buffer) { xfree (home); xfree (name); return NULL; } } if (p == name) p = home_buffer; else /* Windows case. */ { memcpy (home_buffer, p, p - name + 1); p = home_buffer + (p - name + 1); } /* Avoid a leading double slash if the cwd is "/". */ if (home[0] == '/' && !home[1]) strcpy (stpcpy (p, "/"), name); else strcpy (stpcpy (stpcpy (p, home), "/"), name); xfree (home); xfree (name); name = home_buffer; /* Let's do a simple compression to catch the most common case of using "." for gpg's --homedir option. */ n = strlen (name); if (n > 2 && name[n-2] == '/' && name[n-1] == '.') name[n-2] = 0; } } return change_slashes (name); } /* Construct a filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function terminates the process on memory shortage. */ char * make_filename (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (1, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct a filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function may return NULL on error. */ char * make_filename_try (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (0, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct an absolute filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function terminates the process on memory shortage. */ char * make_absfilename (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (3, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Construct an absolute filename from the NULL terminated list of parts. Tilde expansion is done for the first argument. This function may return NULL on error. */ char * make_absfilename_try (const char *first_part, ... ) { va_list arg_ptr; char *result; va_start (arg_ptr, first_part); result = do_make_filename (2, first_part, arg_ptr); va_end (arg_ptr); return result; } /* Compare whether the filenames are identical. This is a special version of strcmp() taking the semantics of filenames in account. Note that this function works only on the supplied names without considering any context like the current directory. See also same_file_p(). */ int compare_filenames (const char *a, const char *b) { #ifdef HAVE_DOSISH_SYSTEM for ( ; *a && *b; a++, b++ ) { if (*a != *b && (toupper (*(const unsigned char*)a) != toupper (*(const unsigned char*)b) ) && !((*a == '/' && *b == '\\') || (*a == '\\' && *b == '/'))) break; } if ((*a == '/' && *b == '\\') || (*a == '\\' && *b == '/')) return 0; else return (toupper (*(const unsigned char*)a) - toupper (*(const unsigned char*)b)); #else return strcmp(a,b); #endif } /* Convert a base-10 number in STRING into a 64 bit unsigned int * value. Leading white spaces are skipped but no error checking is * done. Thus it is similar to atoi(). */ uint64_t string_to_u64 (const char *string) { uint64_t val = 0; while (spacep (string)) string++; for (; digitp (string); string++) { val *= 10; val += *string - '0'; } return val; } /* Convert 2 hex characters at S to a byte value. Return this value or -1 if there is an error. */ int hextobyte (const char *s) { int c; if ( *s >= '0' && *s <= '9' ) c = 16 * (*s - '0'); else if ( *s >= 'A' && *s <= 'F' ) c = 16 * (10 + *s - 'A'); else if ( *s >= 'a' && *s <= 'f' ) c = 16 * (10 + *s - 'a'); else return -1; s++; if ( *s >= '0' && *s <= '9' ) c += *s - '0'; else if ( *s >= 'A' && *s <= 'F' ) c += 10 + *s - 'A'; else if ( *s >= 'a' && *s <= 'f' ) c += 10 + *s - 'a'; else return -1; return c; } /* Given a string containing an UTF-8 encoded text, return the number of characters in this string. It differs from strlen in that it only counts complete UTF-8 characters. SIZE is the maximum length of the string in bytes. If SIZE is -1, then a NUL character is taken to be the end of the string. Note, that this function does not take combined characters into account. */ size_t utf8_charcount (const char *s, int len) { size_t n; if (len == 0) return 0; for (n=0; *s; s++) { if ( (*s&0xc0) != 0x80 ) /* Exclude continuation bytes: 10xxxxxx */ n++; if (len != -1) { len --; if (len == 0) break; } } return n; } /**************************************************** ********** W32 specific functions **************** ****************************************************/ #ifdef HAVE_W32_SYSTEM const char * w32_strerror (int ec) { static char strerr[256]; if (ec == -1) ec = (int)GetLastError (); #ifdef HAVE_W32CE_SYSTEM /* There is only a wchar_t FormatMessage. It does not make much sense to play the conversion game; we print only the code. */ snprintf (strerr, sizeof strerr, "ec=%d", (int)GetLastError ()); #else FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, ec, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), strerr, DIM (strerr)-1, NULL); #endif return strerr; } #endif /*HAVE_W32_SYSTEM*/ /**************************************************** ******** Locale insensitive ctype functions ******** ****************************************************/ /* FIXME: replace them by a table lookup and macros */ int ascii_isupper (int c) { return c >= 'A' && c <= 'Z'; } int ascii_islower (int c) { return c >= 'a' && c <= 'z'; } int ascii_toupper (int c) { if (c >= 'a' && c <= 'z') c &= ~0x20; return c; } int ascii_tolower (int c) { if (c >= 'A' && c <= 'Z') c |= 0x20; return c; } /* Lowercase all ASCII characters in S. */ char * ascii_strlwr (char *s) { char *p = s; for (p=s; *p; p++ ) if (isascii (*p) && *p >= 'A' && *p <= 'Z') *p |= 0x20; return s; } /* Upcase all ASCII characters in S. */ char * ascii_strupr (char *s) { char *p = s; for (p=s; *p; p++ ) if (isascii (*p) && *p >= 'a' && *p <= 'z') *p &= ~0x20; return s; } int ascii_strcasecmp( const char *a, const char *b ) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (*a != *b && ascii_toupper(*a) != ascii_toupper(*b)) break; } return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); } int ascii_strncasecmp (const char *a, const char *b, size_t n) { const unsigned char *p1 = (const unsigned char *)a; const unsigned char *p2 = (const unsigned char *)b; unsigned char c1, c2; if (p1 == p2 || !n ) return 0; do { c1 = ascii_tolower (*p1); c2 = ascii_tolower (*p2); if ( !--n || c1 == '\0') break; ++p1; ++p2; } while (c1 == c2); return c1 - c2; } int ascii_memcasecmp (const void *a_arg, const void *b_arg, size_t n ) { const char *a = a_arg; const char *b = b_arg; if (a == b) return 0; for ( ; n; n--, a++, b++ ) { if( *a != *b && ascii_toupper (*a) != ascii_toupper (*b) ) return *a == *b? 0 : (ascii_toupper (*a) - ascii_toupper (*b)); } return 0; } int ascii_strcmp( const char *a, const char *b ) { if (a == b) return 0; for (; *a && *b; a++, b++) { if (*a != *b ) break; } return *a == *b? 0 : (*(signed char *)a - *(signed char *)b); } void * ascii_memcasemem (const void *haystack, size_t nhaystack, const void *needle, size_t nneedle) { if (!nneedle) return (void*)haystack; /* finding an empty needle is really easy */ if (nneedle <= nhaystack) { const char *a = haystack; const char *b = a + nhaystack - nneedle; for (; a <= b; a++) { if ( !ascii_memcasecmp (a, needle, nneedle) ) return (void *)a; } } return NULL; } /********************************************* ********** missing string functions ********* *********************************************/ #ifndef HAVE_STPCPY char * stpcpy(char *a,const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } #endif #ifndef HAVE_STRPBRK /* Find the first occurrence in S of any character in ACCEPT. Code taken from glibc-2.6/string/strpbrk.c (LGPLv2.1+) and modified. */ char * strpbrk (const char *s, const char *accept) { while (*s != '\0') { const char *a = accept; while (*a != '\0') if (*a++ == *s) return (char *) s; ++s; } return NULL; } #endif /*!HAVE_STRPBRK*/ #ifndef HAVE_STRSEP /* Code taken from glibc-2.2.1/sysdeps/generic/strsep.c. */ char * strsep (char **stringp, const char *delim) { char *begin, *end; begin = *stringp; if (begin == NULL) return NULL; /* A frequent case is when the delimiter string contains only one character. Here we don't need to call the expensive 'strpbrk' function and instead work using 'strchr'. */ if (delim[0] == '\0' || delim[1] == '\0') { char ch = delim[0]; if (ch == '\0') end = NULL; else { if (*begin == ch) end = begin; else if (*begin == '\0') end = NULL; else end = strchr (begin + 1, ch); } } else /* Find the end of the token. */ end = strpbrk (begin, delim); if (end) { /* Terminate the token and set *STRINGP past NUL character. */ *end++ = '\0'; *stringp = end; } else /* No more delimiters; this is the last token. */ *stringp = NULL; return begin; } #endif /*HAVE_STRSEP*/ #ifndef HAVE_STRLWR char * strlwr(char *s) { char *p; for(p=s; *p; p++ ) *p = tolower(*p); return s; } #endif #ifndef HAVE_STRCASECMP int strcasecmp( const char *a, const char *b ) { for( ; *a && *b; a++, b++ ) { if( *a != *b && toupper(*a) != toupper(*b) ) break; } return *(const byte*)a - *(const byte*)b; } #endif /**************** * mingw32/cpd has a memicmp() */ #ifndef HAVE_MEMICMP int memicmp( const char *a, const char *b, size_t n ) { for( ; n; n--, a++, b++ ) if( *a != *b && toupper(*(const byte*)a) != toupper(*(const byte*)b) ) return *(const byte *)a - *(const byte*)b; return 0; } #endif #ifndef HAVE_MEMRCHR void * memrchr (const void *buffer, int c, size_t n) { const unsigned char *p = buffer; for (p += n; n ; n--) if (*--p == c) return (void *)p; return NULL; } #endif /*HAVE_MEMRCHR*/ /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL all characters in EXTRA are also escaped. */ static char * do_percent_escape (const char *str, const char *extra, int die) { int i, j; char *ptr; if (!str) return NULL; for (i=j=0; str[i]; i++) if (str[i] == ':' || str[i] == '%' || str[i] == '\n' || (extra && strchr (extra, str[i]))) j++; if (die) ptr = xmalloc (i + 2 * j + 1); else { ptr = xtrymalloc (i + 2 * j + 1); if (!ptr) return NULL; } i = 0; while (*str) { if (*str == ':') { ptr[i++] = '%'; ptr[i++] = '3'; ptr[i++] = 'a'; } else if (*str == '%') { ptr[i++] = '%'; ptr[i++] = '2'; ptr[i++] = '5'; } else if (*str == '\n') { /* The newline is problematic in a line-based format. */ ptr[i++] = '%'; ptr[i++] = '0'; ptr[i++] = 'a'; } else if (extra && strchr (extra, *str)) { ptr[i++] = '%'; ptr[i++] = tohex_lower ((*str>>4)&15); ptr[i++] = tohex_lower (*str&15); } else ptr[i++] = *str; str++; } ptr[i] = '\0'; return ptr; } /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL all characters in EXTRA are also escaped. This function terminates the process on memory shortage. */ char * percent_escape (const char *str, const char *extra) { return do_percent_escape (str, extra, 1); } /* Same as percent_escape but return NULL instead of exiting on memory error. */ char * try_percent_escape (const char *str, const char *extra) { return do_percent_escape (str, extra, 0); } static char * do_strconcat (const char *s1, va_list arg_ptr) { const char *argv[48]; size_t argc; size_t needed; char *buffer, *p; argc = 0; argv[argc++] = s1; needed = strlen (s1); while (((argv[argc] = va_arg (arg_ptr, const char *)))) { needed += strlen (argv[argc]); if (argc >= DIM (argv)-1) { gpg_err_set_errno (EINVAL); return NULL; } argc++; } needed++; buffer = xtrymalloc (needed); if (buffer) { for (p = buffer, argc=0; argv[argc]; argc++) p = stpcpy (p, argv[argc]); } return buffer; } /* Concatenate the string S1 with all the following strings up to a NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ char * strconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = xtrystrdup (""); else { va_start (arg_ptr, s1); result = do_strconcat (s1, arg_ptr); va_end (arg_ptr); } return result; } /* Same as strconcat but terminate the process with an error message if something goes wrong. */ char * xstrconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = xstrdup (""); else { va_start (arg_ptr, s1); result = do_strconcat (s1, arg_ptr); va_end (arg_ptr); } if (!result) { if (errno == EINVAL) fputs ("\nfatal: too many args for xstrconcat\n", stderr); else fputs ("\nfatal: out of memory\n", stderr); exit (2); } return result; } /* Split a string into fields at DELIM. REPLACEMENT is the character to replace the delimiter with (normally: '\0' so that each field is NUL terminated). The caller is responsible for freeing the result. Note: this function modifies STRING! If you need the original value, then you should pass a copy to this function. If malloc fails, this function returns NULL. */ char ** strsplit (char *string, char delim, char replacement, int *count) { int fields = 1; char *t; char **result; /* First, count the number of fields. */ for (t = strchr (string, delim); t; t = strchr (t + 1, delim)) fields ++; result = xtrycalloc ((fields + 1), sizeof (*result)); if (! result) return NULL; result[0] = string; fields = 1; for (t = strchr (string, delim); t; t = strchr (t + 1, delim)) { result[fields ++] = t + 1; *t = replacement; } if (count) *count = fields; return result; } /* Tokenize STRING using the set of delimiters in DELIM. Leading * spaces and tabs are removed from all tokens. The caller must xfree * the result. * * Returns: A malloced and NULL delimited array with the tokens. On * memory error NULL is returned and ERRNO is set. */ char ** strtokenize (const char *string, const char *delim) { const char *s; size_t fields; size_t bytes, n; char *buffer; char *p, *px, *pend; char **result; /* Count the number of fields. */ for (fields = 1, s = strpbrk (string, delim); s; s = strpbrk (s + 1, delim)) fields++; fields++; /* Add one for the terminating NULL. */ /* Allocate an array for all fields, a terminating NULL, and space for a copy of the string. */ bytes = fields * sizeof *result; if (bytes / sizeof *result != fields) { gpg_err_set_errno (ENOMEM); return NULL; } n = strlen (string) + 1; bytes += n; if (bytes < n) { gpg_err_set_errno (ENOMEM); return NULL; } result = xtrymalloc (bytes); if (!result) return NULL; buffer = (char*)(result + fields); /* Copy and parse the string. */ strcpy (buffer, string); for (n = 0, p = buffer; (pend = strpbrk (p, delim)); p = pend + 1) { *pend = 0; while (spacep (p)) p++; for (px = pend - 1; px >= p && spacep (px); px--) *px = 0; result[n++] = p; } while (spacep (p)) p++; for (px = p + strlen (p) - 1; px >= p && spacep (px); px--) *px = 0; result[n++] = p; result[n] = NULL; assert ((char*)(result + n + 1) == buffer); return result; } /* Split a string into space delimited fields and remove leading and * trailing spaces from each field. A pointer to each field is stored * in ARRAY. Stop splitting at ARRAYSIZE fields. The function * modifies STRING. The number of parsed fields is returned. * Example: * * char *fields[2]; * if (split_fields (string, fields, DIM (fields)) < 2) * return // Not enough args. * foo (fields[0]); * foo (fields[1]); */ int split_fields (char *string, char **array, int arraysize) { int n = 0; char *p, *pend; for (p = string; *p == ' '; p++) ; do { if (n == arraysize) break; array[n++] = p; pend = strchr (p, ' '); if (!pend) break; *pend++ = 0; for (p = pend; *p == ' '; p++) ; } while (*p); return n; } /* Split a string into colon delimited fields A pointer to each field * is stored in ARRAY. Stop splitting at ARRAYSIZE fields. The * function modifies STRING. The number of parsed fields is returned. * Note that leading and trailing spaces are not removed from the fields. * Example: * * char *fields[2]; * if (split_fields (string, fields, DIM (fields)) < 2) * return // Not enough args. * foo (fields[0]); * foo (fields[1]); */ int split_fields_colon (char *string, char **array, int arraysize) { int n = 0; char *p, *pend; p = string; do { if (n == arraysize) break; array[n++] = p; pend = strchr (p, ':'); if (!pend) break; *pend++ = 0; p = pend; } while (*p); return n; } /* Version number parsing. */ /* This function parses the first portion of the version number S and stores it in *NUMBER. On success, this function returns a pointer into S starting with the first character, which is not part of the initial number portion; on failure, NULL is returned. */ static const char* parse_version_number (const char *s, int *number) { int val = 0; if (*s == '0' && digitp (s+1)) return NULL; /* Leading zeros are not allowed. */ for (; digitp (s); s++) { val *= 10; val += *s - '0'; } *number = val; return val < 0 ? NULL : s; } /* This function breaks up the complete string-representation of the version number S, which is of the following structure: .[.]. The major, minor, and micro number components will be stored in *MAJOR, *MINOR and *MICRO. If MICRO is not given 0 is used instead. On success, the last component, the patch level, will be returned; in failure, NULL will be returned. */ static const char * parse_version_string (const char *s, int *major, int *minor, int *micro) { s = parse_version_number (s, major); if (!s || *s != '.') return NULL; s++; s = parse_version_number (s, minor); if (!s) return NULL; if (*s == '.') { s++; s = parse_version_number (s, micro); if (!s) return NULL; } else *micro = 0; return s; /* Patchlevel. */ } /* Compare the version string MY_VERSION to the version string * REQ_VERSION. Returns -1, 0, or 1 if MY_VERSION is found, * respectively, to be less than, to match, or be greater than * REQ_VERSION. This function works for three and two part version * strings; for a two part version string the micro part is assumed to * be 0. Patch levels are compared as strings. If a version number * is invalid INT_MIN is returned. If REQ_VERSION is given as NULL * the function returns 0 if MY_VERSION is parsable version string. */ int compare_version_strings (const char *my_version, const char *req_version) { int my_major, my_minor, my_micro; int rq_major, rq_minor, rq_micro; const char *my_patch, *rq_patch; int result; if (!my_version) return INT_MIN; my_patch = parse_version_string (my_version, &my_major, &my_minor, &my_micro); if (!my_patch) return INT_MIN; if (!req_version) return 0; /* MY_VERSION can be parsed. */ rq_patch = parse_version_string (req_version, &rq_major, &rq_minor,&rq_micro); if (!rq_patch) return INT_MIN; if (my_major == rq_major) { if (my_minor == rq_minor) { if (my_micro == rq_micro) result = strcmp (my_patch, rq_patch); else result = my_micro - rq_micro; } else result = my_minor - rq_minor; } else result = my_major - rq_major; return !result? 0 : result < 0 ? -1 : 1; } /* Format a string so that it fits within about TARGET_COLS columns. * TEXT_IN is copied to a new buffer, which is returned. Normally, * target_cols will be 72 and max_cols is 80. On error NULL is * returned and ERRNO is set. */ char * format_text (const char *text_in, int target_cols, int max_cols) { /* const int do_debug = 0; */ /* The character under consideration. */ char *p; /* The start of the current line. */ char *line; /* The last space that we saw. */ char *last_space = NULL; int last_space_cols = 0; int copied_last_space = 0; char *text; text = xtrystrdup (text_in); if (!text) return NULL; p = line = text; while (1) { /* The number of columns including any trailing space. */ int cols; p = p + strcspn (p, "\n "); if (! p) /* P now points to the NUL character. */ p = &text[strlen (text)]; if (*p == '\n') /* Pass through any newlines. */ { p ++; line = p; last_space = NULL; last_space_cols = 0; copied_last_space = 1; continue; } /* Have a space or a NUL. Note: we don't count the trailing space. */ cols = utf8_charcount (line, (uintptr_t) p - (uintptr_t) line); if (cols < target_cols) { if (! *p) /* Nothing left to break. */ break; last_space = p; last_space_cols = cols; p ++; /* Skip any immediately following spaces. If we break: "... foo bar ..." between "foo" and "bar" then we want: "... foo\nbar ...", which means that the left space has to be the first space after foo, not the last space before bar. */ while (*p == ' ') p ++; } else { int cols_with_left_space; int cols_with_right_space; int left_penalty; int right_penalty; cols_with_left_space = last_space_cols; cols_with_right_space = cols; /* if (do_debug) */ /* log_debug ("Breaking: '%.*s'\n", */ /* (int) ((uintptr_t) p - (uintptr_t) line), line); */ /* The number of columns away from TARGET_COLS. We prefer to underflow than to overflow. */ left_penalty = target_cols - cols_with_left_space; right_penalty = 2 * (cols_with_right_space - target_cols); if (cols_with_right_space > max_cols) /* Add a large penalty for each column that exceeds max_cols. */ right_penalty += 4 * (cols_with_right_space - max_cols); /* if (do_debug) */ /* log_debug ("Left space => %d cols (penalty: %d); " */ /* "right space => %d cols (penalty: %d)\n", */ /* cols_with_left_space, left_penalty, */ /* cols_with_right_space, right_penalty); */ if (last_space_cols && left_penalty <= right_penalty) { /* Prefer the left space. */ /* if (do_debug) */ /* log_debug ("Breaking at left space.\n"); */ p = last_space; } else { /* if (do_debug) */ /* log_debug ("Breaking at right space.\n"); */ } if (! *p) break; *p = '\n'; p ++; if (*p == ' ') { int spaces; for (spaces = 1; p[spaces] == ' '; spaces ++) ; memmove (p, &p[spaces], strlen (&p[spaces]) + 1); } line = p; last_space = NULL; last_space_cols = 0; copied_last_space = 0; } } /* Chop off any trailing space. */ trim_trailing_chars (text, strlen (text), " "); /* If we inserted the trailing newline, then remove it. */ if (! copied_last_space && *text && text[strlen (text) - 1] == '\n') text[strlen (text) - 1] = '\0'; return text; } diff --git a/common/stringhelp.h b/common/stringhelp.h index 7df6c7656..42bb19aaf 100644 --- a/common/stringhelp.h +++ b/common/stringhelp.h @@ -1,169 +1,170 @@ /* stringhelp.h * Copyright (C) 1998, 1999, 2000, 2001, 2003, * 2006, 2007, 2009 Free Software Foundation, Inc. * 2015 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #ifndef GNUPG_COMMON_STRINGHELP_H #define GNUPG_COMMON_STRINGHELP_H #include #include "types.h" /*-- stringhelp.c --*/ char *has_leading_keyword (const char *string, const char *keyword); const char *memistr (const void *buf, size_t buflen, const char *sub); char *mem2str( char *, const void *, size_t); char *trim_spaces( char *string ); +char *ascii_trim_spaces (char *string); char *trim_trailing_spaces( char *string ); unsigned int trim_trailing_chars( unsigned char *line, unsigned len, const char *trimchars); unsigned int trim_trailing_ws( unsigned char *line, unsigned len ); size_t length_sans_trailing_chars (const unsigned char *line, size_t len, const char *trimchars ); size_t length_sans_trailing_ws (const unsigned char *line, size_t len); char *make_basename(const char *filepath, const char *inputpath); char *make_dirname(const char *filepath); char *make_filename( const char *first_part, ... ) GPGRT_ATTR_SENTINEL(0); char *make_filename_try (const char *first_part, ... ) GPGRT_ATTR_SENTINEL(0); char *make_absfilename (const char *first_part, ...) GPGRT_ATTR_SENTINEL(0); char *make_absfilename_try (const char *first_part, ...) GPGRT_ATTR_SENTINEL(0); int compare_filenames( const char *a, const char *b ); uint64_t string_to_u64 (const char *string); int hextobyte (const char *s); size_t utf8_charcount (const char *s, int len); #ifdef HAVE_W32_SYSTEM const char *w32_strerror (int ec); #endif int ascii_isupper (int c); int ascii_islower (int c); int ascii_toupper (int c); int ascii_tolower (int c); char *ascii_strlwr (char *s); char *ascii_strupr (char *s); int ascii_strcasecmp( const char *a, const char *b ); int ascii_strncasecmp (const char *a, const char *b, size_t n); int ascii_memcasecmp( const void *a, const void *b, size_t n ); const char *ascii_memistr ( const void *buf, size_t buflen, const char *sub); void *ascii_memcasemem (const void *haystack, size_t nhaystack, const void *needle, size_t nneedle); #ifndef HAVE_MEMICMP int memicmp( const char *a, const char *b, size_t n ); #endif #ifndef HAVE_STPCPY char *stpcpy(char *a,const char *b); #endif #ifndef HAVE_STRPBRK char *strpbrk (const char *s, const char *accept); #endif #ifndef HAVE_STRSEP char *strsep (char **stringp, const char *delim); #endif #ifndef HAVE_STRLWR char *strlwr(char *a); #endif #ifndef HAVE_STRTOUL # define strtoul(a,b,c) ((unsigned long)strtol((a),(b),(c))) #endif #ifndef HAVE_MEMMOVE # define memmove(d, s, n) bcopy((s), (d), (n)) #endif #ifndef HAVE_STRICMP # define stricmp(a,b) strcasecmp( (a), (b) ) #endif #ifndef HAVE_MEMRCHR void *memrchr (const void *buffer, int c, size_t n); #endif #ifndef HAVE_ISASCII static inline int isascii (int c) { return (((c) & ~0x7f) == 0); } #endif /* !HAVE_ISASCII */ #ifndef STR # define STR(v) #v #endif #define STR2(v) STR(v) /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL, also replace all characters given in EXTRA. The "try_" variant fails with NULL if not enough memory can be allocated. */ char *percent_escape (const char *str, const char *extra); char *try_percent_escape (const char *str, const char *extra); /* Concatenate the string S1 with all the following strings up to a NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ char *strconcat (const char *s1, ...) GPGRT_ATTR_SENTINEL(0); /* Ditto, but die on error. */ char *xstrconcat (const char *s1, ...) GPGRT_ATTR_SENTINEL(0); char **strsplit (char *string, char delim, char replacement, int *count); /* Tokenize STRING using the set of delimiters in DELIM. */ char **strtokenize (const char *string, const char *delim); /* Split STRING into space delimited fields and store them in the * provided ARRAY. */ int split_fields (char *string, char **array, int arraysize); /* Split STRING into colon delimited fields and store them in the * provided ARRAY. */ int split_fields_colon (char *string, char **array, int arraysize); /* Return True if MYVERSION is greater or equal than REQ_VERSION. */ int compare_version_strings (const char *my_version, const char *req_version); /* Format a string so that it fits within about TARGET_COLS columns. */ char *format_text (const char *text, int target_cols, int max_cols); /*-- mapstrings.c --*/ const char *map_static_macro_string (const char *string); #endif /*GNUPG_COMMON_STRINGHELP_H*/ diff --git a/common/utf8conv.c b/common/utf8conv.c index d2c282002..01604e176 100644 --- a/common/utf8conv.c +++ b/common/utf8conv.c @@ -1,838 +1,838 @@ /* utf8conf.c - UTF8 character set conversion * Copyright (C) 1994, 1998, 1999, 2000, 2001, 2003, 2006, * 2008, 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #include #include #include #include #include #ifdef HAVE_LANGINFO_CODESET #include #endif #include #if HAVE_W32_SYSTEM # /* Tell libgpg-error to provide the iconv macros. */ # define GPGRT_ENABLE_W32_ICONV_MACROS 1 #elif HAVE_ANDROID_SYSTEM # /* No iconv support. */ #else # include #endif #include "util.h" #include "common-defs.h" #include "i18n.h" #include "stringhelp.h" #include "utf8conv.h" #ifndef MB_LEN_MAX #define MB_LEN_MAX 16 #endif static const char *active_charset_name = "iso-8859-1"; static int no_translation; /* Set to true if we let simply pass through. */ static int use_iconv; /* iconv conversion functions required. */ #ifdef HAVE_ANDROID_SYSTEM /* Fake stuff to get things building. */ typedef void *iconv_t; #define ICONV_CONST static iconv_t iconv_open (const char *tocode, const char *fromcode) { (void)tocode; (void)fromcode; return (iconv_t)(-1); } static size_t iconv (iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft) { (void)cd; (void)inbuf; (void)inbytesleft; (void)outbuf; (void)outbytesleft; return (size_t)(0); } static int iconv_close (iconv_t cd) { (void)cd; return 0; } #endif /*HAVE_ANDROID_SYSTEM*/ /* Error handler for iconv failures. This is needed to not clutter the output with repeated diagnostics about a missing conversion. */ static void handle_iconv_error (const char *to, const char *from, int use_fallback) { if (errno == EINVAL) { static int shown1, shown2; int x; if (to && !strcmp (to, "utf-8")) { x = shown1; shown1 = 1; } else { x = shown2; shown2 = 1; } if (!x) log_info (_("conversion from '%s' to '%s' not available\n"), from, to); } else { static int shown; if (!shown) log_info (_("iconv_open failed: %s\n"), strerror (errno)); shown = 1; } if (use_fallback) { /* To avoid further error messages we fallback to UTF-8 for the native encoding. Nowadays this seems to be the best bet in case of errors from iconv or nl_langinfo. */ active_charset_name = "utf-8"; no_translation = 0; use_iconv = 0; } } int set_native_charset (const char *newset) { const char *full_newset; if (!newset) { #ifdef HAVE_ANDROID_SYSTEM newset = "utf-8"; #elif defined HAVE_W32_SYSTEM static char codepage[30]; unsigned int cpno; const char *aliases; /* We are a console program thus we need to use the GetConsoleOutputCP function and not the GetACP which would give the codepage for a GUI program. Note this is not a bulletproof detection because GetConsoleCP might return a different one for console input. Not sure how to cope with that. If the console Code page is not known we fall back to the system code page. */ #ifndef HAVE_W32CE_SYSTEM cpno = GetConsoleOutputCP (); if (!cpno) #endif cpno = GetACP (); sprintf (codepage, "CP%u", cpno ); /* Resolve alias. We use a long string string and not the usual array to optimize if the code is taken to a DSO. Taken from libiconv 1.9.2. */ newset = codepage; for (aliases = ("CP936" "\0" "GBK" "\0" "CP1361" "\0" "JOHAB" "\0" "CP20127" "\0" "ASCII" "\0" "CP20866" "\0" "KOI8-R" "\0" "CP21866" "\0" "KOI8-RU" "\0" "CP28591" "\0" "ISO-8859-1" "\0" "CP28592" "\0" "ISO-8859-2" "\0" "CP28593" "\0" "ISO-8859-3" "\0" "CP28594" "\0" "ISO-8859-4" "\0" "CP28595" "\0" "ISO-8859-5" "\0" "CP28596" "\0" "ISO-8859-6" "\0" "CP28597" "\0" "ISO-8859-7" "\0" "CP28598" "\0" "ISO-8859-8" "\0" "CP28599" "\0" "ISO-8859-9" "\0" "CP28605" "\0" "ISO-8859-15" "\0" "CP65001" "\0" "UTF-8" "\0"); *aliases; aliases += strlen (aliases) + 1, aliases += strlen (aliases) + 1) { if (!strcmp (codepage, aliases) ||(*aliases == '*' && !aliases[1])) { newset = aliases + strlen (aliases) + 1; break; } } #else /*!HAVE_W32_SYSTEM && !HAVE_ANDROID_SYSTEM*/ #ifdef HAVE_LANGINFO_CODESET newset = nl_langinfo (CODESET); #else /*!HAVE_LANGINFO_CODESET*/ /* Try to get the used charset from environment variables. */ static char codepage[30]; const char *lc, *dot, *mod; strcpy (codepage, "iso-8859-1"); lc = getenv ("LC_ALL"); if (!lc || !*lc) { lc = getenv ("LC_CTYPE"); if (!lc || !*lc) lc = getenv ("LANG"); } if (lc && *lc) { dot = strchr (lc, '.'); if (dot) { mod = strchr (++dot, '@'); if (!mod) mod = dot + strlen (dot); if (mod - dot < sizeof codepage && dot != mod) { memcpy (codepage, dot, mod - dot); codepage [mod - dot] = 0; } } } newset = codepage; #endif /*!HAVE_LANGINFO_CODESET*/ #endif /*!HAVE_W32_SYSTEM && !HAVE_ANDROID_SYSTEM*/ } full_newset = newset; if (strlen (newset) > 3 && !ascii_memcasecmp (newset, "iso", 3)) { newset += 3; if (*newset == '-' || *newset == '_') newset++; } /* Note that we silently assume that plain ASCII is actually meant as Latin-1. This makes sense because many Unix system don't have their locale set up properly and thus would get annoying error messages and we have to handle all the "bug" reports. Latin-1 has traditionally been the character set used for 8 bit characters on Unix systems. */ if ( !*newset || !ascii_strcasecmp (newset, "8859-1" ) || !ascii_strcasecmp (newset, "646" ) || !ascii_strcasecmp (newset, "ASCII" ) || !ascii_strcasecmp (newset, "ANSI_X3.4-1968" ) ) { active_charset_name = "iso-8859-1"; no_translation = 0; use_iconv = 0; } else if ( !ascii_strcasecmp (newset, "utf8" ) || !ascii_strcasecmp(newset, "utf-8") ) { active_charset_name = "utf-8"; no_translation = 1; use_iconv = 0; } else { iconv_t cd; cd = iconv_open (full_newset, "utf-8"); if (cd == (iconv_t)-1) { handle_iconv_error (full_newset, "utf-8", 0); return -1; } iconv_close (cd); cd = iconv_open ("utf-8", full_newset); if (cd == (iconv_t)-1) { handle_iconv_error ("utf-8", full_newset, 0); return -1; } iconv_close (cd); active_charset_name = full_newset; no_translation = 0; use_iconv = 1; } return 0; } const char * get_native_charset () { return active_charset_name; } /* Return true if the native charset is utf-8. */ int is_native_utf8 (void) { return no_translation; } /* Convert string, which is in native encoding to UTF8 and return a new allocated UTF-8 string. This function terminates the process on memory shortage. */ char * native_to_utf8 (const char *orig_string) { const unsigned char *string = (const unsigned char *)orig_string; const unsigned char *s; char *buffer; unsigned char *p; size_t length = 0; if (no_translation) { /* Already utf-8 encoded. */ buffer = xstrdup (orig_string); } else if (!use_iconv) { /* For Latin-1 we can avoid the iconv overhead. */ for (s = string; *s; s++) { length++; if (*s & 0x80) length++; } buffer = xmalloc (length + 1); for (p = (unsigned char *)buffer, s = string; *s; s++) { if ( (*s & 0x80 )) { *p++ = 0xc0 | ((*s >> 6) & 3); *p++ = 0x80 | (*s & 0x3f); } else *p++ = *s; } *p = 0; } else { /* Need to use iconv. */ iconv_t cd; const char *inptr; char *outptr; size_t inbytes, outbytes; cd = iconv_open ("utf-8", active_charset_name); if (cd == (iconv_t)-1) { handle_iconv_error ("utf-8", active_charset_name, 1); return native_to_utf8 (string); } for (s=string; *s; s++ ) { length++; if ((*s & 0x80)) length += 5; /* We may need up to 6 bytes for the utf8 output. */ } buffer = xmalloc (length + 1); inptr = string; inbytes = strlen (string); outptr = buffer; outbytes = length; if ( iconv (cd, (ICONV_CONST char **)&inptr, &inbytes, &outptr, &outbytes) == (size_t)-1) { static int shown; if (!shown) log_info (_("conversion from '%s' to '%s' failed: %s\n"), active_charset_name, "utf-8", strerror (errno)); shown = 1; /* We don't do any conversion at all but use the strings as is. */ strcpy (buffer, string); } else /* Success. */ { *outptr = 0; /* We could realloc the buffer now but I doubt that it makes much sense given that it will get freed anyway soon after. */ } iconv_close (cd); } return buffer; } static char * do_utf8_to_native (const char *string, size_t length, int delim, int with_iconv) { int nleft; int i; unsigned char encbuf[8]; int encidx; const unsigned char *s; size_t n; char *buffer = NULL; char *p = NULL; unsigned long val = 0; size_t slen; int resync = 0; /* First pass (p==NULL): count the extended utf-8 characters. */ /* Second pass (p!=NULL): create string. */ for (;;) { for (slen = length, nleft = encidx = 0, n = 0, s = (const unsigned char *)string; slen; s++, slen--) { if (resync) { if (!(*s < 128 || (*s >= 0xc0 && *s <= 0xfd))) { /* Still invalid. */ if (p) { sprintf (p, "\\x%02x", *s); p += 4; } n += 4; continue; } resync = 0; } if (!nleft) { if (!(*s & 0x80)) { /* Plain ascii. */ if ( delim != -1 && (*s < 0x20 || *s == 0x7f || *s == delim || (delim && *s == '\\'))) { n++; if (p) *p++ = '\\'; switch (*s) { case '\n': n++; if ( p ) *p++ = 'n'; break; case '\r': n++; if ( p ) *p++ = 'r'; break; case '\f': n++; if ( p ) *p++ = 'f'; break; case '\v': n++; if ( p ) *p++ = 'v'; break; case '\b': n++; if ( p ) *p++ = 'b'; break; case 0: n++; if ( p ) *p++ = '0'; break; default: n += 3; if (p) { sprintf (p, "x%02x", *s); p += 3; } break; } } else { if (p) *p++ = *s; n++; } } else if ((*s & 0xe0) == 0xc0) /* 110x xxxx */ { val = *s & 0x1f; nleft = 1; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xf0) == 0xe0) /* 1110 xxxx */ { val = *s & 0x0f; nleft = 2; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xf8) == 0xf0) /* 1111 0xxx */ { val = *s & 0x07; nleft = 3; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xfc) == 0xf8) /* 1111 10xx */ { val = *s & 0x03; nleft = 4; encidx = 0; encbuf[encidx++] = *s; } else if ((*s & 0xfe) == 0xfc) /* 1111 110x */ { val = *s & 0x01; nleft = 5; encidx = 0; encbuf[encidx++] = *s; } else /* Invalid encoding: print as \xNN. */ { if (p) { sprintf (p, "\\x%02x", *s); p += 4; } n += 4; resync = 1; } } else if (*s < 0x80 || *s >= 0xc0) /* Invalid utf-8 */ { if (p) { for (i = 0; i < encidx; i++) { sprintf (p, "\\x%02x", encbuf[i]); p += 4; } sprintf (p, "\\x%02x", *s); p += 4; } n += 4 + 4 * encidx; nleft = 0; encidx = 0; resync = 1; } else { encbuf[encidx++] = *s; val <<= 6; val |= *s & 0x3f; if (!--nleft) /* Ready. */ { if (no_translation) { if (p) { for (i = 0; i < encidx; i++) *p++ = encbuf[i]; } n += encidx; encidx = 0; } else if (with_iconv) { /* Our strategy for using iconv is a bit strange but it better keeps compatibility with previous versions in regard to how invalid encodings are displayed. What we do is to keep the utf-8 as is and have the real translation step then at the end. Yes, I know that this is ugly. However we are short of the 1.4 release and for this branch we should not mess too much around with iconv things. One reason for this is that we don't know enough about non-GNU iconv implementation and want to minimize the risk of breaking the code on too many platforms. */ if ( p ) { for (i=0; i < encidx; i++ ) *p++ = encbuf[i]; } n += encidx; encidx = 0; } else /* Latin-1 case. */ { if (val >= 0x80 && val < 256) { /* We can simply print this character */ n++; if (p) *p++ = val; } else { /* We do not have a translation: print utf8. */ if (p) { for (i = 0; i < encidx; i++) { sprintf (p, "\\x%02x", encbuf[i]); p += 4; } } n += encidx * 4; encidx = 0; } } } } } if (!buffer) { /* Allocate the buffer after the first pass. */ buffer = p = xmalloc (n + 1); } else if (with_iconv) { /* Note: See above for comments. */ iconv_t cd; const char *inptr; char *outbuf, *outptr; size_t inbytes, outbytes; *p = 0; /* Terminate the buffer. */ cd = iconv_open (active_charset_name, "utf-8"); if (cd == (iconv_t)-1) { handle_iconv_error (active_charset_name, "utf-8", 1); xfree (buffer); return utf8_to_native (string, length, delim); } /* Allocate a new buffer large enough to hold all possible encodings. */ n = p - buffer + 1; inbytes = n - 1;; inptr = buffer; outbytes = n * MB_LEN_MAX; if (outbytes / MB_LEN_MAX != n) BUG (); /* Actually an overflow. */ outbuf = outptr = xmalloc (outbytes); if ( iconv (cd, (ICONV_CONST char **)&inptr, &inbytes, &outptr, &outbytes) == (size_t)-1) { static int shown; if (!shown) log_info (_("conversion from '%s' to '%s' failed: %s\n"), "utf-8", active_charset_name, strerror (errno)); shown = 1; /* Didn't worked out. Try again but without iconv. */ xfree (buffer); buffer = NULL; xfree (outbuf); outbuf = do_utf8_to_native (string, length, delim, 0); } else /* Success. */ { *outptr = 0; /* Make sure it is a string. */ /* We could realloc the buffer now but I doubt that it makes much sense given that it will get freed anyway soon after. */ xfree (buffer); } iconv_close (cd); return outbuf; } else /* Not using iconv. */ { *p = 0; /* Make sure it is a string. */ return buffer; } } } /* Convert string, which is in UTF-8 to native encoding. Replace illegal encodings by some "\xnn" and quote all control characters. A character with value DELIM will always be quoted, it must be a vanilla ASCII character. A DELIM value of -1 is special: it disables all quoting of control characters. This function terminates the process on memory shortage. */ char * utf8_to_native (const char *string, size_t length, int delim) { return do_utf8_to_native (string, length, delim, use_iconv); } /* Wrapper function for iconv_open, required for W32 as we dlopen that library on that system. */ jnlib_iconv_t jnlib_iconv_open (const char *tocode, const char *fromcode) { return (jnlib_iconv_t)iconv_open (tocode, fromcode); } /* Wrapper function for iconv, required for W32 as we dlopen that library on that system. */ size_t jnlib_iconv (jnlib_iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft) { return iconv ((iconv_t)cd, (ICONV_CONST char**)inbuf, inbytesleft, outbuf, outbytesleft); } /* Wrapper function for iconv_close, required for W32 as we dlopen that library on that system. */ int jnlib_iconv_close (jnlib_iconv_t cd) { return iconv_close ((iconv_t)cd); } #ifdef HAVE_W32_SYSTEM /* Return a malloced string encoded for CODEPAGE from the wide char input string STRING. Caller must free this value. Returns NULL and sets ERRNO on failure. Calling this function with STRING set to NULL is not defined. */ static char * wchar_to_cp (const wchar_t *string, unsigned int codepage) { int n; char *result; n = WideCharToMultiByte (codepage, 0, string, -1, NULL, 0, NULL, NULL); if (n < 0) { gpg_err_set_errno (EINVAL); return NULL; } result = xtrymalloc (n+1); if (!result) return NULL; n = WideCharToMultiByte (codepage, 0, string, -1, result, n, NULL, NULL); if (n < 0) { xfree (result); gpg_err_set_errno (EINVAL); result = NULL; } return result; } /* Return a malloced wide char string from a CODEPAGE encoded input string STRING. Caller must free this value. Returns NULL and sets ERRNO on failure. Calling this function with STRING set to NULL is not defined. */ static wchar_t * cp_to_wchar (const char *string, unsigned int codepage) { int n; size_t nbytes; wchar_t *result; n = MultiByteToWideChar (codepage, 0, string, -1, NULL, 0); if (n < 0) { gpg_err_set_errno (EINVAL); return NULL; } nbytes = (size_t)(n+1) * sizeof(*result); if (nbytes / sizeof(*result) != (n+1)) { gpg_err_set_errno (ENOMEM); return NULL; } result = xtrymalloc (nbytes); if (!result) return NULL; n = MultiByteToWideChar (codepage, 0, string, -1, result, n); if (n < 0) { xfree (result); gpg_err_set_errno (EINVAL); result = NULL; } return result; } /* Return a malloced string encoded in the active code page from the * wide char input string STRING. Caller must free this value. * Returns NULL and sets ERRNO on failure. Calling this function with * STRING set to NULL is not defined. */ char * wchar_to_native (const wchar_t *string) { return wchar_to_cp (string, CP_ACP); } -/* Return a malloced wide char string from an UTF-8 encoded input +/* Return a malloced wide char string from native encoded input * string STRING. Caller must free this value. Returns NULL and sets * ERRNO on failure. Calling this function with STRING set to NULL is * not defined. */ wchar_t * native_to_wchar (const char *string) { return cp_to_wchar (string, CP_ACP); } /* Return a malloced string encoded in UTF-8 from the wide char input * string STRING. Caller must free this value. Returns NULL and sets * ERRNO on failure. Calling this function with STRING set to NULL is * not defined. */ char * wchar_to_utf8 (const wchar_t *string) { return wchar_to_cp (string, CP_UTF8); } /* Return a malloced wide char string from an UTF-8 encoded input * string STRING. Caller must free this value. Returns NULL and sets * ERRNO on failure. Calling this function with STRING set to NULL is * not defined. */ wchar_t * utf8_to_wchar (const char *string) { return cp_to_wchar (string, CP_UTF8); } #endif /*HAVE_W32_SYSTEM*/ diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index a08601634..9921074a3 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -1,986 +1,1030 @@ /* gpgtar-create.c - Create a TAR archive * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # include #else /*!HAVE_W32_SYSTEM*/ # include # include # include #endif /*!HAVE_W32_SYSTEM*/ #include #include "../common/i18n.h" #include "../common/exectool.h" #include "../common/sysutils.h" #include "../common/ccparray.h" #include "gpgtar.h" #ifndef HAVE_LSTAT #define lstat(a,b) stat ((a), (b)) #endif /* Object to control the file scanning. */ struct scanctrl_s; typedef struct scanctrl_s *scanctrl_t; struct scanctrl_s { tar_header_t flist; tar_header_t *flist_tail; int nestlevel; }; /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the W32 version. */ #ifdef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_w32 (tar_header_t hdr) { char *p; wchar_t *wfname; WIN32_FILE_ATTRIBUTE_DATA fad; DWORD attr; for (p=hdr->name; *p; p++) if (*p == '/') *p = '\\'; wfname = native_to_wchar (hdr->name); for (p=hdr->name; *p; p++) if (*p == '\\') *p = '/'; if (!wfname) { log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1)); return gpg_error_from_syserror (); } if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad)) { log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1)); xfree (wfname); return gpg_error_from_syserror (); } xfree (wfname); attr = fad.dwFileAttributes; if ((attr & FILE_ATTRIBUTE_NORMAL)) hdr->typeflag = TF_REGULAR; else if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->typeflag = TF_DIRECTORY; else if ((attr & FILE_ATTRIBUTE_DEVICE)) hdr->typeflag = TF_NOTSUP; else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY))) hdr->typeflag = TF_NOTSUP; else hdr->typeflag = TF_REGULAR; /* Map some attributes to USTAR defined mode bits. */ hdr->mode = 0640; /* User may read and write, group only read. */ if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->mode |= 0110; /* Dirs are user and group executable. */ if ((attr & FILE_ATTRIBUTE_READONLY)) hdr->mode &= ~0200; /* Clear the user write bit. */ if ((attr & FILE_ATTRIBUTE_HIDDEN)) hdr->mode &= ~0707; /* Clear all user and other bits. */ if ((attr & FILE_ATTRIBUTE_SYSTEM)) hdr->mode |= 0004; /* Make it readable by other. */ /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = (fad.nFileSizeHigh * (unsigned long long)(MAXDWORD+1) + fad.nFileSizeLow); hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32) | fad.ftLastWriteTime.dwLowDateTime); if (!hdr->mtime) hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32) | fad.ftCreationTime.dwLowDateTime); hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */ return 0; } #endif /*HAVE_W32_SYSTEM*/ /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the POSIX version. */ #ifndef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_posix (tar_header_t hdr) { gpg_error_t err; struct stat sbuf; if (lstat (hdr->name, &sbuf)) { err = gpg_error_from_syserror (); log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } if (S_ISREG (sbuf.st_mode)) hdr->typeflag = TF_REGULAR; else if (S_ISDIR (sbuf.st_mode)) hdr->typeflag = TF_DIRECTORY; else if (S_ISCHR (sbuf.st_mode)) hdr->typeflag = TF_CHARDEV; else if (S_ISBLK (sbuf.st_mode)) hdr->typeflag = TF_BLOCKDEV; else if (S_ISFIFO (sbuf.st_mode)) hdr->typeflag = TF_FIFO; else if (S_ISLNK (sbuf.st_mode)) hdr->typeflag = TF_SYMLINK; else hdr->typeflag = TF_NOTSUP; /* FIXME: Save DEV and INO? */ /* Set the USTAR defined mode bits using the system macros. */ if (sbuf.st_mode & S_IRUSR) hdr->mode |= 0400; if (sbuf.st_mode & S_IWUSR) hdr->mode |= 0200; if (sbuf.st_mode & S_IXUSR) hdr->mode |= 0100; if (sbuf.st_mode & S_IRGRP) hdr->mode |= 0040; if (sbuf.st_mode & S_IWGRP) hdr->mode |= 0020; if (sbuf.st_mode & S_IXGRP) hdr->mode |= 0010; if (sbuf.st_mode & S_IROTH) hdr->mode |= 0004; if (sbuf.st_mode & S_IWOTH) hdr->mode |= 0002; if (sbuf.st_mode & S_IXOTH) hdr->mode |= 0001; #ifdef S_IXUID if (sbuf.st_mode & S_IXUID) hdr->mode |= 04000; #endif #ifdef S_IXGID if (sbuf.st_mode & S_IXGID) hdr->mode |= 02000; #endif #ifdef S_ISVTX if (sbuf.st_mode & S_ISVTX) hdr->mode |= 01000; #endif hdr->nlink = sbuf.st_nlink; hdr->uid = sbuf.st_uid; hdr->gid = sbuf.st_gid; /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = sbuf.st_size; hdr->mtime = sbuf.st_mtime; return 0; } #endif /*!HAVE_W32_SYSTEM*/ /* Add a new entry. The name of a director entry is ENTRYNAME; if that is NULL, DNAME is the name of the directory itself. Under Windows ENTRYNAME shall have backslashes replaced by standard slashes. */ static gpg_error_t add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) { gpg_error_t err; tar_header_t hdr; char *p; size_t dnamelen = strlen (dname); assert (dnamelen); hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1 + (entryname? strlen (entryname) : 0) + 1); if (!hdr) return gpg_error_from_syserror (); p = stpcpy (hdr->name, dname); if (entryname) { if (dname[dnamelen-1] != '/') *p++ = '/'; strcpy (p, entryname); } else { if (hdr->name[dnamelen-1] == '/') hdr->name[dnamelen-1] = 0; } #ifdef HAVE_DOSISH_SYSTEM err = fillup_entry_w32 (hdr); #else err = fillup_entry_posix (hdr); #endif if (err) xfree (hdr); else { if (opt.verbose) gpgtar_print_header (hdr, log_get_stream ()); *scanctrl->flist_tail = hdr; scanctrl->flist_tail = &hdr->next; } return 0; } static gpg_error_t scan_directory (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; #ifdef HAVE_W32_SYSTEM WIN32_FIND_DATAW fi; HANDLE hd = INVALID_HANDLE_VALUE; char *p; if (!*dname) return 0; /* An empty directory name has no entries. */ { char *fname; wchar_t *wfname; fname = xtrymalloc (strlen (dname) + 2 + 2 + 1); if (!fname) { err = gpg_error_from_syserror (); goto leave; } if (!strcmp (dname, "/")) strcpy (fname, "/*"); /* Trailing slash is not allowed. */ else if (!strcmp (dname, ".")) strcpy (fname, "*"); else if (*dname && dname[strlen (dname)-1] == '/') strcpy (stpcpy (fname, dname), "*"); else if (*dname && dname[strlen (dname)-1] != '*') strcpy (stpcpy (fname, dname), "/*"); else strcpy (fname, dname); for (p=fname; *p; p++) if (*p == '/') *p = '\\'; wfname = native_to_wchar (fname); xfree (fname); if (!wfname) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); goto leave; } hd = FindFirstFileW (wfname, &fi); if (hd == INVALID_HANDLE_VALUE) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); xfree (wfname); goto leave; } xfree (wfname); } do { char *fname = wchar_to_native (fi.cFileName); if (!fname) { err = gpg_error_from_syserror (); log_error ("error converting filename: %s\n", w32_strerror (-1)); break; } for (p=fname; *p; p++) if (*p == '\\') *p = '/'; if (!strcmp (fname, "." ) || !strcmp (fname, "..")) err = 0; /* Skip self and parent dir entry. */ else if (!strncmp (dname, "./", 2) && dname[2]) err = add_entry (dname+2, fname, scanctrl); else err = add_entry (dname, fname, scanctrl); xfree (fname); } while (!err && FindNextFileW (hd, &fi)); if (err) ; else if (GetLastError () == ERROR_NO_MORE_FILES) err = 0; else { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); } leave: if (hd != INVALID_HANDLE_VALUE) FindClose (hd); #else /*!HAVE_W32_SYSTEM*/ DIR *dir; struct dirent *de; if (!*dname) return 0; /* An empty directory name has no entries. */ dir = opendir (dname); if (!dir) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); return err; } while ((de = readdir (dir))) { if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, "..")) continue; /* Skip self and parent dir entry. */ err = add_entry (dname, de->d_name, scanctrl); if (err) goto leave; } leave: closedir (dir); #endif /*!HAVE_W32_SYSTEM*/ return err; } static gpg_error_t scan_recursive (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; tar_header_t hdr, *start_tail, *stop_tail; if (scanctrl->nestlevel > 200) { log_error ("directories too deeply nested\n"); return gpg_error (GPG_ERR_RESOURCE_LIMIT); } scanctrl->nestlevel++; assert (scanctrl->flist_tail); start_tail = scanctrl->flist_tail; scan_directory (dname, scanctrl); stop_tail = scanctrl->flist_tail; hdr = *start_tail; for (; hdr && hdr != *stop_tail; hdr = hdr->next) if (hdr->typeflag == TF_DIRECTORY) { if (opt.verbose > 1) log_info ("scanning directory '%s'\n", hdr->name); scan_recursive (hdr->name, scanctrl); } scanctrl->nestlevel--; return err; } /* Returns true if PATTERN is acceptable. */ static int pattern_valid_p (const char *pattern) { if (!*pattern) return 0; if (*pattern == '.' && pattern[1] == '.') return 0; if (*pattern == '/' #ifdef HAVE_DOSISH_SYSTEM || *pattern == '\\' #endif ) return 0; /* Absolute filenames are not supported. */ #ifdef HAVE_DRIVE_LETTERS if (((*pattern >= 'a' && *pattern <= 'z') || (*pattern >= 'A' && *pattern <= 'Z')) && pattern[1] == ':') return 0; /* Drive letter are not allowed either. */ #endif /*HAVE_DRIVE_LETTERS*/ return 1; /* Okay. */ } static void store_xoctal (char *buffer, size_t length, unsigned long long value) { char *p, *pend; size_t n; unsigned long long v; assert (length > 1); v = value; n = length; p = pend = buffer + length; *--p = 0; /* Nul byte. */ n--; do { *--p = '0' + (v % 8); v /= 8; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = '0'; } else /* Does not fit into the field. Store as binary number. */ { v = value; n = length; p = pend = buffer + length; do { *--p = v; v /= 256; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = 0; if (*p & 0x80) BUG (); *p |= 0x80; /* Set binary flag. */ } else BUG (); } } static void store_uname (char *buffer, size_t length, unsigned long uid) { static int initialized; static unsigned long lastuid; static char lastuname[32]; if (!initialized || uid != lastuid) { #ifdef HAVE_W32_SYSTEM mem2str (lastuname, uid? "user":"root", sizeof lastuname); #else struct passwd *pw = getpwuid (uid); lastuid = uid; initialized = 1; if (pw) mem2str (lastuname, pw->pw_name, sizeof lastuname); else { log_info ("failed to get name for uid %lu\n", uid); *lastuname = 0; } #endif } mem2str (buffer, lastuname, length); } static void store_gname (char *buffer, size_t length, unsigned long gid) { static int initialized; static unsigned long lastgid; static char lastgname[32]; if (!initialized || gid != lastgid) { #ifdef HAVE_W32_SYSTEM mem2str (lastgname, gid? "users":"root", sizeof lastgname); #else struct group *gr = getgrgid (gid); lastgid = gid; initialized = 1; if (gr) mem2str (lastgname, gr->gr_name, sizeof lastgname); else { log_info ("failed to get name for gid %lu\n", gid); *lastgname = 0; } #endif } mem2str (buffer, lastgname, length); } static gpg_error_t build_header (void *record, tar_header_t hdr) { gpg_error_t err; struct ustar_raw_header *raw = record; size_t namelen, n; unsigned long chksum; unsigned char *p; memset (record, 0, RECORDSIZE); /* Store name and prefix. */ namelen = strlen (hdr->name); if (namelen < sizeof raw->name) memcpy (raw->name, hdr->name, namelen); else { n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix; for (n--; n ; n--) if (hdr->name[n] == '/') break; if (namelen - n < sizeof raw->name) { /* Note that the N is < sizeof prefix and that the delimiting slash is not stored. */ memcpy (raw->prefix, hdr->name, n); memcpy (raw->name, hdr->name+n+1, namelen - n); } else { err = gpg_error (GPG_ERR_TOO_LARGE); log_error ("error storing file '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } } store_xoctal (raw->mode, sizeof raw->mode, hdr->mode); store_xoctal (raw->uid, sizeof raw->uid, hdr->uid); store_xoctal (raw->gid, sizeof raw->gid, hdr->gid); store_xoctal (raw->size, sizeof raw->size, hdr->size); store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime); switch (hdr->typeflag) { case TF_REGULAR: raw->typeflag[0] = '0'; break; case TF_HARDLINK: raw->typeflag[0] = '1'; break; case TF_SYMLINK: raw->typeflag[0] = '2'; break; case TF_CHARDEV: raw->typeflag[0] = '3'; break; case TF_BLOCKDEV: raw->typeflag[0] = '4'; break; case TF_DIRECTORY: raw->typeflag[0] = '5'; break; case TF_FIFO: raw->typeflag[0] = '6'; break; default: return gpg_error (GPG_ERR_NOT_SUPPORTED); } memcpy (raw->magic, "ustar", 6); raw->version[0] = '0'; raw->version[1] = '0'; store_uname (raw->uname, sizeof raw->uname, hdr->uid); store_gname (raw->gname, sizeof raw->gname, hdr->gid); #ifndef HAVE_W32_SYSTEM if (hdr->typeflag == TF_SYMLINK) { int nread; nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1); if (nread < 0) { err = gpg_error_from_syserror (); log_error ("error reading symlink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } raw->linkname[nread] = 0; } #endif /*HAVE_W32_SYSTEM*/ /* Compute the checksum. */ memset (raw->checksum, ' ', sizeof raw->checksum); chksum = 0; p = record; for (n=0; n < RECORDSIZE; n++) chksum += *p++; store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum); raw->checksum[7] = ' '; return 0; } static gpg_error_t write_file (estream_t stream, tar_header_t hdr) { gpg_error_t err; char record[RECORDSIZE]; estream_t infp; size_t nread, nbytes; int any; err = build_header (record, hdr); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) { log_info ("skipping unsupported file '%s'\n", hdr->name); err = 0; } return err; } if (hdr->typeflag == TF_REGULAR) { infp = es_fopen (hdr->name, "rb"); if (!infp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s - skipped\n", hdr->name, gpg_strerror (err)); return err; } } else infp = NULL; err = write_record (stream, record); if (err) goto leave; if (hdr->typeflag == TF_REGULAR) { hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE; any = 0; while (hdr->nrecords--) { nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE); if (!nbytes) nbytes = RECORDSIZE; nread = es_fread (record, 1, nbytes, infp); if (nread != nbytes) { err = gpg_error_from_syserror (); log_error ("error reading file '%s': %s%s\n", hdr->name, gpg_strerror (err), any? " (file shrunk?)":""); goto leave; } any = 1; err = write_record (stream, record); if (err) goto leave; } nread = es_fread (record, 1, 1, infp); if (nread) log_info ("note: file '%s' has grown\n", hdr->name); } leave: if (err) es_fclose (infp); else if ((err = es_fclose (infp))) log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } static gpg_error_t write_eof_mark (estream_t stream) { gpg_error_t err; char record[RECORDSIZE]; memset (record, 0, sizeof record); err = write_record (stream, record); if (!err) err = write_record (stream, record); return err; } /* Create a new tarball using the names in the array INPATTERN. If INPATTERN is NULL take the pattern as null terminated strings from - stdin. */ + stdin or from the file specified by FILES_FROM. If NULL_NAMES is + set the filenames in such a file are delimited by a binary Nul and + not by a LF. */ gpg_error_t -gpgtar_create (char **inpattern, int encrypt, int sign) +gpgtar_create (char **inpattern, const char *files_from, int null_names, + int encrypt, int sign) { gpg_error_t err = 0; struct scanctrl_s scanctrl_buffer; scanctrl_t scanctrl = &scanctrl_buffer; tar_header_t hdr, *start_tail; + estream_t files_from_stream = NULL; estream_t outstream = NULL; estream_t cipher_stream = NULL; int eof_seen = 0; if (!inpattern) - es_set_binary (es_stdin); + { + if (!files_from || !strcmp (files_from, "-")) + { + files_from = "-"; + files_from_stream = es_stdin; + if (null_names) + es_set_binary (es_stdin); + } + else if (!(files_from_stream=es_fopen (files_from, null_names? "rb":"r"))) + { + err = gpg_error_from_syserror (); + log_error ("error opening '%s': %s\n", + files_from, gpg_strerror (err)); + return err; + } + } memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; if (opt.directory && gnupg_chdir (opt.directory)) { err = gpg_error_from_syserror (); log_error ("chdir to '%s' failed: %s\n", opt.directory, gpg_strerror (err)); return err; } while (!eof_seen) { char *pat, *p; int skip_this = 0; if (inpattern) { const char *pattern = *inpattern; if (!pattern) break; /* End of array. */ inpattern++; if (!*pattern) continue; pat = xtrystrdup (pattern); } - else /* Read null delimited pattern from stdin. */ + else /* Read Nul or LF delimited pattern from files_from_stream. */ { int c; char namebuf[4096]; size_t n = 0; for (;;) { - if ((c = es_getc (es_stdin)) == EOF) + if ((c = es_getc (files_from_stream)) == EOF) { - if (es_ferror (es_stdin)) + if (es_ferror (files_from_stream)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", - "[stdin]", strerror (errno)); + files_from, gpg_strerror (err)); goto leave; } - /* Note: The Nul is a delimiter and not a terminator. */ - c = 0; + c = null_names ? 0 : '\n'; eof_seen = 1; } if (n >= sizeof namebuf - 1) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", - "[stdin]", "filename too long"); + files_from, "filename too long"); } } else namebuf[n++] = c; - if (!c) + + if (null_names) { - namebuf[n] = 0; - break; + if (!c) + { + namebuf[n] = 0; + break; + } + } + else /* Shall be LF delimited. */ + { + if (!c) + { + if (!skip_this) + { + skip_this = 1; + log_error ("error reading '%s': %s\n", + files_from, "filename with embedded Nul"); + } + } + else if ( c == '\n' ) + { + namebuf[n] = 0; + ascii_trim_spaces (namebuf); + n = strlen (namebuf); + break; + } } } if (skip_this || n < 2) continue; pat = xtrystrdup (namebuf); } if (!pat) { err = gpg_error_from_syserror (); log_error ("memory allocation problem: %s\n", gpg_strerror (err)); goto leave; } for (p=pat; *p; p++) if (*p == '\\') *p = '/'; if (opt.verbose > 1) log_info ("scanning '%s'\n", pat); start_tail = scanctrl->flist_tail; if (skip_this || !pattern_valid_p (pat)) log_error ("skipping invalid name '%s'\n", pat); else if (!add_entry (pat, NULL, scanctrl) && *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY)) scan_recursive (pat, scanctrl); xfree (pat); } + if (files_from_stream && files_from_stream != es_stdin) + es_fclose (files_from_stream); + if (opt.outfile) { if (!strcmp (opt.outfile, "-")) outstream = es_stdout; else outstream = es_fopen (opt.outfile, "wb"); if (!outstream) { err = gpg_error_from_syserror (); goto leave; } } else { outstream = es_stdout; } if (outstream == es_stdout) es_set_binary (es_stdout); if (encrypt || sign) { cipher_stream = outstream; outstream = es_fopenmem (0, "rwb"); if (! outstream) { err = gpg_error_from_syserror (); goto leave; } } for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { err = write_file (outstream, hdr); if (err) goto leave; } err = write_eof_mark (outstream); if (err) goto leave; if (encrypt || sign) { strlist_t arg; ccparray_t ccp; const char **argv; err = es_fseek (outstream, 0, SEEK_SET); if (err) goto leave; /* '--encrypt' may be combined with '--symmetric', but 'encrypt' is set either way. Clear it if no recipients are specified. XXX: Fix command handling. */ if (opt.symmetric && opt.recipients == NULL) encrypt = 0; ccparray_init (&ccp, 0); if (encrypt) ccparray_put (&ccp, "--encrypt"); if (sign) ccparray_put (&ccp, "--sign"); if (opt.user) { ccparray_put (&ccp, "--local-user"); ccparray_put (&ccp, opt.user); } if (opt.symmetric) ccparray_put (&ccp, "--symmetric"); for (arg = opt.recipients; arg; arg = arg->next) { ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, arg->d); } for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, outstream, NULL, cipher_stream, NULL, NULL); xfree (argv); if (err) goto leave; } leave: if (!err) { gpg_error_t first_err; if (outstream != es_stdout) first_err = es_fclose (outstream); else first_err = es_fflush (outstream); outstream = NULL; if (cipher_stream != es_stdout) err = es_fclose (cipher_stream); else err = es_fflush (cipher_stream); cipher_stream = NULL; if (! err) err = first_err; } if (err) { log_error ("creating tarball '%s' failed: %s\n", opt.outfile ? opt.outfile : "-", gpg_strerror (err)); if (outstream && outstream != es_stdout) es_fclose (outstream); if (cipher_stream && cipher_stream != es_stdout) es_fclose (cipher_stream); if (opt.outfile) gnupg_remove (opt.outfile); } scanctrl->flist_tail = NULL; while ( (hdr = scanctrl->flist) ) { scanctrl->flist = hdr->next; xfree (hdr); } return err; } diff --git a/tools/gpgtar.c b/tools/gpgtar.c index 680ed3ddd..315f8a74b 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -1,609 +1,606 @@ /* gpgtar.c - A simple TAR implementation mainly useful for Windows. * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ /* GnuPG comes with a shell script gpg-zip which creates archive files in the same format as PGP Zip, which is actually a USTAR format. That is fine and works nicely on all Unices but for Windows we don't have a compatible shell and the supply of tar programs is limited. Given that we need just a few tar option and it is an open question how many Unix concepts are to be mapped to Windows, we might as well write our own little tar customized for use with gpg. So here we go. */ #include #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/openpgpdefs.h" #include "../common/init.h" #include "../common/strlist.h" #include "gpgtar.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, aCreate = 600, aExtract, aEncrypt = 'e', aDecrypt = 'd', aSign = 's', aList = 't', oSymmetric = 'c', oRecipient = 'r', oUser = 'u', oOutput = 'o', oDirectory = 'C', oQuiet = 'q', oVerbose = 'v', oFilesFrom = 'T', oNoVerbose = 500, aSignEncrypt, oGpgProgram, oSkipCrypto, oOpenPGP, oCMS, oSetFilename, oNull, /* Compatibility with gpg-zip. */ oGpgArgs, oTarArgs, /* Debugging. */ oDryRun, }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aCreate, "create", N_("create an archive")), ARGPARSE_c (aExtract, "extract", N_("extract an archive")), ARGPARSE_c (aEncrypt, "encrypt", N_("create an encrypted archive")), ARGPARSE_c (aDecrypt, "decrypt", N_("extract an encrypted archive")), ARGPARSE_c (aSign, "sign", N_("create a signed archive")), ARGPARSE_c (aList, "list-archive", N_("list an archive")), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oSymmetric, "symmetric", N_("use symmetric encryption")), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oUser, "local-user", N_("|USER-ID|use USER-ID to sign or decrypt")), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_s (oSetFilename, "set-filename", "@"), ARGPARSE_s_n (oOpenPGP, "openpgp", "@"), ARGPARSE_s_n (oCMS, "cms", "@"), ARGPARSE_group (302, N_("@\nTar options:\n ")), ARGPARSE_s_s (oDirectory, "directory", N_("|DIRECTORY|change to DIRECTORY first")), ARGPARSE_s_s (oFilesFrom, "files-from", N_("|FILE|get names to create from FILE")), ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), ARGPARSE_s_s (oGpgArgs, "gpg-args", "@"), ARGPARSE_s_s (oTarArgs, "tar-args", "@"), ARGPARSE_end () }; /* The list of commands and options for tar that we understand. */ static gpgrt_opt_t tar_opts[] = { ARGPARSE_s_s (oDirectory, "directory", N_("|DIRECTORY|extract files into DIRECTORY")), ARGPARSE_s_s (oFilesFrom, "files-from", N_("|FILE|get names to create from FILE")), ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), ARGPARSE_end () }; /* Global flags. */ -enum cmd_and_opt_values cmd = 0; -int skip_crypto = 0; -const char *files_from = NULL; -int null_names = 0; +static enum cmd_and_opt_values cmd = 0; +static int skip_crypto = 0; +static const char *files_from = NULL; +static int null_names = 0; /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGTAR@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpgtar [options] [files] [directories] (-h for help)"); break; case 41: p = _("Syntax: gpgtar [options] [files] [directories]\n" "Encrypt or sign files into an archive\n"); break; default: p = NULL; break; } return p; } static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) { enum cmd_and_opt_values c = *ret_cmd; if (!c || c == new_cmd) c = new_cmd; else if (c == aSign && new_cmd == aEncrypt) c = aSignEncrypt; else if (c == aEncrypt && new_cmd == aSign) c = aSignEncrypt; else { log_error (_("conflicting commands\n")); exit (2); } *ret_cmd = c; } /* Shell-like argument splitting. For compatibility with gpg-zip we accept arguments for GnuPG and tar given as a string argument to '--gpg-args' and '--tar-args'. gpg-zip was implemented as a Bourne Shell script, and therefore, we need to split the string the same way the shell would. */ static int shell_parse_stringlist (const char *str, strlist_t *r_list) { strlist_t list = NULL; const char *s = str; char quoted = 0; char arg[1024]; char *p = arg; #define addchar(c) \ do { if (p - arg + 2 < sizeof arg) *p++ = (c); else return 1; } while (0) #define addargument() \ do { \ if (p > arg) \ { \ *p = 0; \ append_to_strlist (&list, arg); \ p = arg; \ } \ } while (0) #define unquoted 0 #define singlequote '\'' #define doublequote '"' for (; *s; s++) { switch (quoted) { case unquoted: if (isspace (*s)) addargument (); else if (*s == singlequote || *s == doublequote) quoted = *s; else addchar (*s); break; case singlequote: if (*s == singlequote) quoted = unquoted; else addchar (*s); break; case doublequote: assert (s > str || !"cannot be quoted at first char"); if (*s == doublequote && *(s - 1) != '\\') quoted = unquoted; else addchar (*s); break; default: assert (! "reached"); } } /* Append the last argument. */ addargument (); #undef doublequote #undef singlequote #undef unquoted #undef addargument #undef addchar *r_list = list; return 0; } /* Like shell_parse_stringlist, but returns an argv vector instead of a strlist. */ static int shell_parse_argv (const char *s, int *r_argc, char ***r_argv) { int i; strlist_t list; if (shell_parse_stringlist (s, &list)) return 1; *r_argc = strlist_length (list); *r_argv = xtrycalloc (*r_argc, sizeof **r_argv); if (*r_argv == NULL) return 1; for (i = 0; list; i++) { gpgrt_annotate_leaked_object (list); (*r_argv)[i] = list->d; list = list->next; } gpgrt_annotate_leaked_object (*r_argv); return 0; } /* Command line parsing. */ static void parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { int no_more_options = 0; while (!no_more_options && gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oOutput: opt.outfile = pargs->r.ret_str; break; case oDirectory: opt.directory = pargs->r.ret_str; break; case oSetFilename: opt.filename = pargs->r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oFilesFrom: files_from = pargs->r.ret_str; break; case oNull: null_names = 1; break; case aList: case aDecrypt: case aEncrypt: case aSign: set_cmd (&cmd, pargs->r_opt); break; case aCreate: set_cmd (&cmd, aEncrypt); skip_crypto = 1; break; case aExtract: set_cmd (&cmd, aDecrypt); skip_crypto = 1; break; case oRecipient: add_to_strlist (&opt.recipients, pargs->r.ret_str); break; case oUser: opt.user = pargs->r.ret_str; break; case oSymmetric: set_cmd (&cmd, aEncrypt); opt.symmetric = 1; break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oSkipCrypto: skip_crypto = 1; break; case oOpenPGP: /* Dummy option for now. */ break; case oCMS: /* Dummy option for now. */ break; case oGpgArgs:; { strlist_t list; if (shell_parse_stringlist (pargs->r.ret_str, &list)) log_error ("failed to parse gpg arguments '%s'\n", pargs->r.ret_str); else { if (opt.gpg_arguments) strlist_last (opt.gpg_arguments)->next = list; else opt.gpg_arguments = list; } } break; case oTarArgs: { int tar_argc; char **tar_argv; if (shell_parse_argv (pargs->r.ret_str, &tar_argc, &tar_argv)) log_error ("failed to parse tar arguments '%s'\n", pargs->r.ret_str); else { gpgrt_argparse_t tar_args; tar_args.argc = &tar_argc; tar_args.argv = &tar_argv; tar_args.flags = ARGPARSE_FLAG_ARG0; parse_arguments (&tar_args, tar_opts); gpgrt_argparse (NULL, &tar_args, NULL); if (tar_args.err) log_error ("unsupported tar arguments '%s'\n", pargs->r.ret_str); pargs->err = tar_args.err; } } break; case oDryRun: opt.dry_run = 1; break; default: pargs->err = 2; break; } } } /* gpgtar main. */ int main (int argc, char **argv) { gpg_error_t err; const char *fname; gpgrt_argparse_t pargs; gnupg_reopen_std (GPGTAR_NAME); gpgrt_set_strusage (my_strusage); log_set_prefix (GPGTAR_NAME, GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); log_assert (sizeof (struct ustar_raw_header) == 512); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); - if ((files_from && !null_names) || (!files_from && null_names)) - log_error ("--files-from and --null may only be used in conjunction\n"); - if (files_from && strcmp (files_from, "-")) - log_error ("--files-from only supports argument \"-\"\n"); - if (log_get_errorcount (0)) exit (2); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("NOTE: '%s' is not considered an option\n"), argv[i]); } if (! opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (opt.verbose > 1) opt.debug_level = 1024; switch (cmd) { case aList: if (argc > 1) gpgrt_usage (1); fname = argc ? *argv : NULL; if (opt.filename) log_info ("note: ignoring option --set-filename\n"); if (files_from) log_info ("note: ignoring option --files-from\n"); err = gpgtar_list (fname, !skip_crypto); if (err && log_get_errorcount (0) == 0) log_error ("listing archive failed: %s\n", gpg_strerror (err)); break; case aEncrypt: case aSign: case aSignEncrypt: - if ((!argc && !null_names) - || (argc && null_names)) + if ((!argc && !files_from) + || (argc && files_from)) gpgrt_usage (1); if (opt.filename) log_info ("note: ignoring option --set-filename\n"); - err = gpgtar_create (null_names? NULL :argv, + err = gpgtar_create (files_from? NULL : argv, + files_from, + null_names, !skip_crypto && (cmd == aEncrypt || cmd == aSignEncrypt), cmd == aSign || cmd == aSignEncrypt); if (err && log_get_errorcount (0) == 0) log_error ("creating archive failed: %s\n", gpg_strerror (err)); break; case aDecrypt: if (argc != 1) gpgrt_usage (1); if (opt.outfile) log_info ("note: ignoring option --output\n"); if (files_from) log_info ("note: ignoring option --files-from\n"); fname = argc ? *argv : NULL; err = gpgtar_extract (fname, !skip_crypto); if (err && log_get_errorcount (0) == 0) log_error ("extracting archive failed: %s\n", gpg_strerror (err)); break; default: log_error (_("invalid command (there is no implicit command)\n")); break; } return log_get_errorcount (0)? 1:0; } /* Read the next record from STREAM. RECORD is a buffer provided by the caller and must be at leadt of size RECORDSIZE. The function return 0 on success and error code on failure; a diagnostic printed as well. Note that there is no need for an EOF indicator because a tarball has an explicit EOF record. */ gpg_error_t read_record (estream_t stream, void *record) { gpg_error_t err; size_t nread; nread = es_fread (record, 1, RECORDSIZE, stream); if (nread != RECORDSIZE) { err = gpg_error_from_syserror (); if (es_ferror (stream)) log_error ("error reading '%s': %s\n", es_fname_get (stream), gpg_strerror (err)); else log_error ("error reading '%s': premature EOF " "(size of last record: %zu)\n", es_fname_get (stream), nread); } else err = 0; return err; } /* Write the RECORD of size RECORDSIZE to STREAM. FILENAME is the name of the file used for diagnostics. */ gpg_error_t write_record (estream_t stream, const void *record) { gpg_error_t err; size_t nwritten; nwritten = es_fwrite (record, 1, RECORDSIZE, stream); if (nwritten != RECORDSIZE) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", es_fname_get (stream), gpg_strerror (err)); } else err = 0; return err; } /* Return true if FP is an unarmored OpenPGP message. Note that this function reads a few bytes from FP but pushes them back. */ #if 0 static int openpgp_message_p (estream_t fp) { int ctb; ctb = es_getc (fp); if (ctb != EOF) { if (es_ungetc (ctb, fp)) log_fatal ("error ungetting first byte: %s\n", gpg_strerror (gpg_error_from_syserror ())); if ((ctb & 0x80)) { switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf)) { case PKT_MARKER: case PKT_SYMKEY_ENC: case PKT_ONEPASS_SIG: case PKT_PUBKEY_ENC: case PKT_SIGNATURE: case PKT_COMMENT: case PKT_OLD_COMMENT: case PKT_PLAINTEXT: case PKT_COMPRESSED: case PKT_ENCRYPTED: return 1; /* Yes, this seems to be an OpenPGP message. */ default: break; } } } return 0; } #endif diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 74bfb56e8..3e7aa5ed6 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -1,145 +1,146 @@ /* gpgtar.h - Global definitions for gpgtar * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef GPGTAR_H #define GPGTAR_H #include "../common/util.h" #include "../common/strlist.h" /* We keep all global options in the structure OPT. */ EXTERN_UNLESS_MAIN_MODULE struct { int verbose; unsigned int debug_level; int quiet; int dry_run; const char *gpg_program; strlist_t gpg_arguments; const char *outfile; strlist_t recipients; const char *user; int symmetric; const char *filename; const char *directory; } opt; /* An info structure to avoid global variables. */ struct tarinfo_s { unsigned long long nblocks; /* Count of processed blocks. */ unsigned long long headerblock; /* Number of current header block. */ }; typedef struct tarinfo_s *tarinfo_t; /* The size of a tar record. All IO is done in chunks of this size. Note that we don't care about blocking because this version of tar is not expected to be used directly on a tape drive in fact it is used in a pipeline with GPG and thus any blocking would be useless. */ #define RECORDSIZE 512 /* Description of the USTAR header format. */ struct ustar_raw_header { char name[100]; char mode[8]; char uid[8]; char gid[8]; char size[12]; char mtime[12]; char checksum[8]; char typeflag[1]; char linkname[100]; char magic[6]; char version[2]; char uname[32]; char gname[32]; char devmajor[8]; char devminor[8]; char prefix[155]; char pad[12]; }; /* Filetypes as defined by USTAR. */ typedef enum { TF_REGULAR, TF_HARDLINK, TF_SYMLINK, TF_CHARDEV, TF_BLOCKDEV, TF_DIRECTORY, TF_FIFO, TF_RESERVED, TF_UNKNOWN, /* Needs to be treated as regular file. */ TF_NOTSUP /* Not supported (used with --create). */ } typeflag_t; /* The internal representation of a TAR header. */ struct tar_header_s; typedef struct tar_header_s *tar_header_t; struct tar_header_s { tar_header_t next; /* Used to build a linked list of entries. */ unsigned long mode; /* The file mode. */ unsigned long nlink; /* Number of hard links. */ unsigned long uid; /* The user id of the file. */ unsigned long gid; /* The group id of the file. */ unsigned long long size; /* The size of the file. */ unsigned long long mtime; /* Modification time since Epoch. Note that we don't use time_t here but a type which is more likely to be larger that 32 bit and thus allows tracking times beyond 2106. */ typeflag_t typeflag; /* The type of the file. */ unsigned long long nrecords; /* Number of data records. */ char name[1]; /* Filename (dynamically extended). */ }; /*-- gpgtar.c --*/ gpg_error_t read_record (estream_t stream, void *record); gpg_error_t write_record (estream_t stream, const void *record); /*-- gpgtar-create.c --*/ -gpg_error_t gpgtar_create (char **inpattern, int encrypt, int sign); +gpg_error_t gpgtar_create (char **inpattern, const char *files_from, + int null_names, int encrypt, int sign); /*-- gpgtar-extract.c --*/ gpg_error_t gpgtar_extract (const char *filename, int decrypt); /*-- gpgtar-list.c --*/ gpg_error_t gpgtar_list (const char *filename, int decrypt); gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header); void gpgtar_print_header (tar_header_t header, estream_t out); #endif /*GPGTAR_H*/