diff --git a/config.h b/config.h index 367d917..1f970fb 100644 --- a/config.h +++ b/config.h @@ -395,4 +395,13 @@ */ #define MIN_WOULDBLOCK_DELAY 100L +/* CONFIGURE: MIME types and encodings can be get from i-node extended + * attributes. If not defined, metedata will be determined from file name + * extension. Otherwise metadata will be read from EA first. If MIME type + * can't be read from EA, warning will be logged and file name based method + * will be used as backup solution. + */ +#define USE_XATTR + + #endif /* _CONFIG_H_ */ diff --git a/libhttpd.c b/libhttpd.c index f8eefa8..b250df0 100644 --- a/libhttpd.c +++ b/libhttpd.c @@ -39,6 +39,10 @@ #include #include +#ifdef USE_XATTR +#include +#endif + #include #include #include @@ -1684,6 +1688,10 @@ httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc ) httpd_realloc_str( &hc->hostdir, &hc->maxhostdir, 0 ); httpd_realloc_str( &hc->remoteuser, &hc->maxremoteuser, 0 ); httpd_realloc_str( &hc->response, &hc->maxresponse, 0 ); +#ifdef USE_XATTR + hc->maxxattr_type=0; + httpd_realloc_str( &hc->xattr_type, &hc->maxxattr_type, 0 ); +#endif #ifdef TILDE_MAP_2 httpd_realloc_str( &hc->altdir, &hc->maxaltdir, 0 ); #endif /* TILDE_MAP_2 */ @@ -1739,6 +1747,9 @@ httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc ) hc->authorization = ""; hc->remoteuser[0] = '\0'; hc->response[0] = '\0'; +#ifdef USE_XATTR + hc->xattr_type[0] = '\0'; +#endif #ifdef TILDE_MAP_2 hc->altdir[0] = '\0'; #endif /* TILDE_MAP_2 */ @@ -2468,6 +2479,9 @@ httpd_destroy_conn( httpd_conn* hc ) free( (void*) hc->hostdir ); free( (void*) hc->remoteuser ); free( (void*) hc->response ); +#ifdef USE_XATTR + free( (void*) hc->xattr_type ); +#endif #ifdef TILDE_MAP_2 free( (void*) hc->altdir ); #endif /* TILDE_MAP_2 */ @@ -2475,7 +2489,6 @@ httpd_destroy_conn( httpd_conn* hc ) } } - struct mime_entry { char* ext; size_t ext_len; @@ -2526,9 +2539,147 @@ init_mime( void ) } +#ifdef USE_XATTR +/* Retrieve value of POSIX extended attribute (e.g. MIME type or encoding) + * @param filename Name of the file we examine + * @param type Extended attribute we are interested in (e.g. "user.mime_type", + * "user.encoding") + * @param buffer Memory where value of the attribute can be stored + * @param important_miss Missing xattr is significant and should be logged + * @return On success 1, on PEA miss or missing PEA support 0. on error -1. + * In case of no success, **buffer and its content is unspecified. + * */ +static int getuserxattr(const char *filename, const char *type, char **buffer, + size_t *size, const important_miss) { + int saved_errno; + int xattrsize; + + /* Check for NULL arguments */ + if (!filename || !type || !buffer || !size) + return -1; + + /* Check for NULL or empty buffer. This should not occure due to + * initiatiation during accepting connection. However for sure.*/ + if (!*buffer || *size == 0) { + /* 0 means 200. See httpd_realloc_str() */ + httpd_realloc_str(buffer, size, 0); + if (!*buffer) + return -1; + } + + /* XXX: getxattr(1) describes positive or -1 return value. It doesn't + * specify if 0 can be returned. OTOH attr(5) accpets 0-length value. */ + if (0 < (xattrsize=getxattr(filename, type, *buffer, *size))) { + /* XXX: PEA value is NOT null terminated string. Append null. */ + if (xattrsize >= *size) { + httpd_realloc_str(buffer, size, 1 + xattrsize); + if (!*buffer) return -1; + } + (*buffer)[xattrsize]='\x0'; + /*syslog(LOG_DEBUG, "File: %s, %s: `%s' [%d B]\n", filename, type, *buffer, xattrsize);*/ + return 1; + } else { + saved_errno=errno; + + if (saved_errno == ENODATA || saved_errno == ENOTSUP) { + /* NO PEA */ + if (important_miss) + syslog(LOG_INFO, "File: %s: Cannot get xattr %s: %s\n", + filename, type, strerror(errno)); + return 0; + } + + if (saved_errno == ERANGE) { + /* Grow &hc->encodings as needed */ + /*syslog(LOG_DEBUG, "Attribute is too long, buffer is %d long only.\n" + "\tTrying to reallocate to get enought memory\n", *size);*/ + httpd_realloc_str(buffer, size, 1 + getxattr(filename, type, *buffer, 0)); + + if (*buffer) { + if (0 < (xattrsize=getxattr(filename, type, *buffer, *size))) { + /* XXX: PEA value is NOT null terminated string. Append null. */ + if (xattrsize >= *size) { + httpd_realloc_str(buffer, size, 1 + xattrsize); + if (!*buffer) return -1; + } + (*buffer)[xattrsize]='\x0'; + /*syslog(LOG_DEBUG, "File: %s, %s: `%s' [%d B]\n", filename, type, + *buffer, xattrsize);*/ + return 1; + } else { + syslog(LOG_ERR, + "File: %s: Even after buffer reallocation, cannot get xattr %s:\n" + "\t%s. Giving it up\n", filename, type, strerror(errno)); + } + } else { + syslog(LOG_CRIT, "Reallocation failed. Buy more memory.\n"); + } + return -1; + } + if (important_miss) + syslog(LOG_WARNING, "File: %s: Cannot get xattr %s: %s\n", + filename, type, strerror(errno)); + } + return -1; +} + + +/* Retrieve value of POSIX extended attribute and check it for validity as + * HTTP header value. Currently, only end of line is considered harmfull. + * @param filename Name of the file we examine + * @param type Extended attribute we are interested in (e.g. "user.mime_type", + * "user.encoding") + * @param buffer Memory where value of the attribute can be stored + * @param important_miss Missing xattr is significant and should be logged + * @return On success 1, on PEA miss or missing PEA support 0. on error -1. + * In case of no success, **buffer and its content is unspecified. + * */ +static int checkxattrforhttp(const char *filename, const char *type, + char **buffer, size_t *size, int important_miss) { + int retval; + retval = getuserxattr(filename, type, buffer, size, important_miss); + if (retval > 0) { + /* Validating */ + if (index(*buffer, '\015') || index(*buffer, '\012')) { + retval = -1; + syslog(LOG_ERR, "PEA `%s' of file `%s' contains character disrupting HTTP. Ignoring.\n", + type, filename); + } + } + return retval; +} + +/** + * Simulates sprintf("%s; charset=\"%s\"", *header, charset) where *header + * grows as needed and completed string is return in header pointer. + * Advantage of this function is smaller footprint (sprintf(3) would require + * printing into newly allocated buffer. + * Disandvantage is possible loss of header in case memory shortcome. + * @param header Existing header httpd_reallocatable + * @param maxheader Originnal and new size of reallocated header + * @param charset Valuse of the charset + * + * The only error is reallocation error which is signaled by *header==NULL. + */ +void append_charset_param(char **header, size_t *maxheader, const char *charset) { + size_t header_len = strlen(*header); + size_t param_len = sizeof("; charset=\"\"") + strlen(charset); + + httpd_realloc_str(header, maxheader, header_len + param_len); + if (*header) { + strcpy(*header + header_len, "; charset=\""); + strcpy(*header + header_len + 11, charset); + strcpy(*header + header_len + param_len - 2, "\""); + } +} +#endif + + /* Figure out MIME encodings and type based on the filename. Multiple ** encodings are separated by commas, and are listed in the order in ** which they were applied to the file. +** Optinally, the values are derived from POSIX Extended Attributes +** (see USE_XATTR macro). */ static void figure_mime( httpd_conn* hc ) @@ -2542,6 +2693,53 @@ figure_mime( httpd_conn* hc ) int r; char* default_type = "text/plain; charset=%s"; +#ifdef USE_XATTR + /* Retrieve MIME type and encoding from extended attribute */ + /* TODO: PEA can contain bytes which can have special meaning in HTTP. + * Thus user can break the HTTP protocol via specially crafted PEA value. + * We should sanity or discard them. */ + /* Get MIME type */ + if (0expnfilename, "user.mime_type", + &hc->xattr_type, &hc->maxxattr_type, 1)) { + + /* Get charset */ + char *xattr_charset=NULL; + size_t maxxattr_charset=0; + if (0expnfilename, "user.charset", + &xattr_charset, &maxxattr_charset, 0)) { + /* Charset from PEA is appended to mime type always */ + if (index(xattr_charset, '"')) + syslog(LOG_ERR, "File `%s': xattr `user.charset' contains " + "quote sign which can not be embeded into HTTP " + "resposne.\n", hc->expnfilename); + else + append_charset_param(&hc->xattr_type, &hc->maxxattr_type, + xattr_charset); + } else + if (hc->xattr_type && !strncmp(hc->xattr_type, "text/", 5)) + /* Append default charset via %s for any text main type only */ + append_charset_param(&hc->xattr_type, &hc->maxxattr_type, + "%s"); + free(xattr_charset); + + /* Save Content-Type header value */ + hc->type = hc->xattr_type; + + /* Get encoding */ + if (0>=checkxattrforhttp(hc->expnfilename, "user.mime_encoding", + &hc->encodings, &hc->maxencodings, 0)) + if (hc->encodings) + hc->encodings[0]='\x0'; + + /* PEA metadata take precedense over suffix mapping */ + /* XXX: Due to no memory in append_charset_param() we could lose + * hc->type. Then fall back to extension based mime type guess. + * However it's highly probable we fail too because of no + * memory. */ + if (hc->type) return; + } +#endif + /* Peel off encoding extensions until there aren't any more. */ n_me_indexes = 0; for ( prev_dot = &hc->expnfilename[strlen(hc->expnfilename)]; ; prev_dot = dot ) @@ -4237,3 +4435,6 @@ httpd_logstats( long secs ) str_alloc_count, (unsigned long) str_alloc_size, (float) str_alloc_size / str_alloc_count ); } + +/* vim: ts=8 sw=4 noet + */ diff --git a/libhttpd.h b/libhttpd.h index 150b983..fd509bf 100644 --- a/libhttpd.h +++ b/libhttpd.h @@ -131,6 +131,10 @@ typedef struct { time_t if_modified_since, range_if; size_t contentlength; char* type; /* not malloc()ed */ +#ifdef USE_XATTR + char *xattr_type; + size_t maxxattr_type; +#endif char* hostname; /* not malloc()ed */ int mime_flag; int one_one; /* HTTP/1.1 or better */