/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd) * * Copyright (C) 2005 Stelian Pop * Portions Copyright (C) 2006 Vidar Tysse * Portions Copyright (C) 2007 Olivier Kahn * * Heavily based on kiss4lin, * Copyright (C) 2004 Jacob Kolding * * This program 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 2, or (at your option) * any later version. * * This program 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * ************************************************************************ * * utils.c * * Purpose: A collection of general-purpose utilities in the following * categories: * - String handling, including list handling and substitution * - Character set conversion * - Case-insensitive filename operations * * ************************************************************************ */ // We use iconv by default. // On platforms that don't have it we must patch this file to remove the #define below. #define USE_ICONV #define USE_FTW #include #include #include #include #include #include #include #include // used in scandir_selector() #ifdef USE_ICONV #include #endif #ifdef USE_FTW #include // file tree traversal /* The header defines the stat structure and the symbolic names * for st_mode and the file type test macros as described in .*/ #endif #include "utils.h" #include "kissdx.h" #include "config.h" #include "piccache.h" // Global storage of configuration used by scandir() and scandir_selector() scandir_info_t scandir_info; // Variable set before any call to scandir() ftw_info_t ftw_info; // Variable set before any call to ftw() /* ***************** * Memory management * ***************** */ void * xmalloc(size_t size) { register void *value = malloc (size); if (!value) { log0("xmalloc: Virtual memory exhausted. Exiting."); exit(2); } return value; } void * xrealloc(void *ptr, size_t size) { register void *value = realloc (ptr, size); if (!value) { log0("xrealloc: Virtual memory exhausted. Exiting."); exit(2); } return value; } /* ************************ * Character set conversion * ************************ */ /// Convert the supplied string from from_character_set to to_character_set if possible. /// Always return outbuf, even when no conversion was performed. const char* convert_charset_direct(const char* s, const char *from_character_set, const char *to_character_set) { static char outbuf[2 * PATH_MAX]; #ifdef USE_ICONV char *wrptr = (char*)outbuf; size_t inbytesleft = strlen(s), outbytesleft = 2 * PATH_MAX - 1; static int isOpenErrReported = 0, isConvErrReported = 0; memset(outbuf, 0, sizeof outbuf); if (from_character_set == NULL || *from_character_set == '\0') { if (from_character_set == NULL) log0("convert_charset_direct: Unknown source endpoint."); return strcpy(outbuf, s); /* Cannot convert: Return original (but guaranteed to return outbuf) */ } if (to_character_set == NULL || *to_character_set == '\0') { if (to_character_set == NULL) log0("convert_charset_direct: Unknown target endpoint."); return strcpy(outbuf, s); /* Cannot convert: Return original (but guaranteed to return outbuf) */ } iconv_t cd = iconv_open(to_character_set, from_character_set); if (cd == (iconv_t) -1) { if (!isOpenErrReported) { log("iconv_open fails for '%s' --> '%s' conversion: %s", from_character_set, to_character_set, strerror(errno)); isOpenErrReported = -1; } return strcpy(outbuf, s); /* Cannot convert: Return original (but guaranteed to return outbuf) */ } const char *ps = s; size_t nconv = iconv(cd, (void*)&ps, &inbytesleft, &wrptr, &outbytesleft); if (nconv == (size_t) -1) { if (!isConvErrReported) { log("iconv fails for '%s' --> '%s' conversion at '%s': %s", from_character_set, to_character_set, ps, strerror(errno)); isConvErrReported = -1; } iconv_close(cd); return strcpy(outbuf, s); /* Cannot convert: Return original (but guaranteed to return outbuf) */ } if (iconv_close(cd) != 0) { log("iconv_close failed for '%s' --> '%s' conversion: %s", from_character_set, to_character_set, strerror(errno)); return strcpy(outbuf, s); /* Cannot convert: Return original (but guaranteed to return outbuf) */ } return (char*)outbuf; #else return strcpy(outbuf, s); /* Cannot convert: Return original (but guaranteed to return outbuf) */ #endif } /// Get the configured character set name for the supplied conversion endpoint static const char* endpoint_charset(enum convert_endpoint_t endpoint) { return endpoint == cept_client? config.client_character_set : endpoint == cept_server? config.server_character_set : endpoint == cept_config? config.config_character_set : endpoint == cept_playlist? config.playlist_character_set : NULL; } /// Convert the supplied string from source_endpoint to target_endpoint if possible. /// Always return the string in a static output buffer, even when string was not converted. const char* convert_charset(const char* s, enum convert_endpoint_t source_endpoint, enum convert_endpoint_t target_endpoint) { return convert_charset_direct(s, endpoint_charset(source_endpoint), endpoint_charset(target_endpoint)); } /// Return TRUE if both source and target character sets have been configured. /// Use this function to do a resource-friendly check before optionally calling convert_charset(). int can_convert_charset(enum convert_endpoint_t source_endpoint, enum convert_endpoint_t target_endpoint) { const char *source_charset = endpoint_charset(source_endpoint); const char *target_charset = endpoint_charset(target_endpoint); return source_charset != NULL && *source_charset && target_charset != NULL && *target_charset; } /* **************************************** * String handling, including list handling * **************************************** */ /// Make contents of buf lower-case void make_lowercase(char *buf) { static const char *upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char *lower = "abcdefghijklmnopqrstuvwxyz"; char *p, *q; for (p = buf; *p != '\0'; p++) if ((q = strchr(upper, *p))) *p = lower[q - upper]; } /// Return a pointer to the first occurrence of needle in haystack const char* list_pointer(const char *haystack, const char *needle, const char *delimiters, enum list_searchmode_t searchmode) { const char *p = haystack, *q; if (haystack && needle && delimiters && *haystack && *needle && *delimiters) { int needle_len = strlen(needle); while ((p = strstr(p, needle))) { if (searchmode == lsmt_ends_with || p == haystack || strchr(delimiters, *(p-1))) if (searchmode == lsmt_begins_with || *(q = p + needle_len) == '\0' || strchr(delimiters, *q)) return p; p += needle_len; } } return 0; } /// Return true if haystack contains needle, false otherwise int list_contains(const char *haystack, const char *needle, const char *delimiters) { return list_pointer(haystack, needle, delimiters, lsmt_exact) != NULL; } /// Return true if comma-delimited list contains filename's extension, false otherwise int list_contains_extension(const char *list, const char *filename) { const char *p = strrchr(filename, '.'); if (p) { char extLower[32]; memset(extLower, 0, sizeof(extLower)); strncpy(extLower, p + 1, sizeof(extLower)); *(extLower + sizeof(extLower) - 1) = '\0'; make_lowercase(extLower); return list_contains(list, extLower, ","); } return 0; } /// Substitute every occurrence of needle in haystack with substitute int string_substitute(char *haystack, const char *needle, const char *substitute, int haystack_size) { int num_substitutions = 0; if (haystack && needle && substitute && *haystack && *needle && *substitute && haystack_size > strlen(haystack)) { char *p = haystack; int needle_len = strlen(needle); int substitute_len = strlen(substitute); int shift_amount = substitute_len - needle_len; haystack_size--; // Ensure room for trailing null char while ((p = strstr(p, needle))) { int chars_free = haystack_size - strlen(haystack); if (chars_free < shift_amount) return num_substitutions; // Stop if overflow if (needle_len != substitute_len) memmove(p + substitute_len, p + needle_len, strlen(p + needle_len) + 1); // Make exact room for substitute memcpy(p, substitute, substitute_len); // Overlay substitute num_substitutions++; p += substitute_len; } } return num_substitutions; } /* ************************************ * Case-insensitive filename operations * ************************************ */ /// A case-insensitive stat() /// NOTE 1: Only the base name of filename is compared case-insensitively, the directory /// part of the supplied filename must be in correct case. /// NOTE 2: In addition to normal stat() behaviour (though case-insensitive), this function /// places the actual filename of the file found into the supplied filename buffer, /// which is thus not const char * but char * for this implementation. int casestat(char *filename, struct stat *statbuf) { char directory[PATH_MAX], name[PATH_MAX]; char fullname[PATH_MAX], foundname[PATH_MAX]; struct dirent **namelist; int num_files, n; if (!filename) return -1; strncpy(fullname, filename, sizeof fullname); if (*fullname != '/') { if (!getcwd(fullname, sizeof fullname)) { log("casestat: getcwd: %s", strerror(errno)); return -1; } if (*(fullname + strlen(fullname) - 1) != '/') { strcat(fullname, "/"); } strcat(fullname, filename); } strcpy(name, basename(fullname)); strcpy(directory, dirname(fullname)); // Goal : Populate 'foundname' with the 'name' if found in scandir (what ever the case sensitive) // what is the need of 'alphasort' due to only many different case of 'name' could occurs // and finaly, we get only the last one in 'for' loop if ((num_files = scandir(directory, &namelist, (void*)NULL, (void*)alphasort)) < 0) { log("casestat: scandir '%s': %s", directory, strerror(errno)); return -1; } for (*foundname = '\0', n = 0; n < num_files; n++) { if ( (!*foundname) && (!strcasecmp(name, namelist[n]->d_name)) ) { strcpy(foundname, namelist[n]->d_name); } free(namelist[n]); } free(namelist); if (!*foundname) return -1; strcpy(fullname, directory); strcat(fullname, "/"); strcat(fullname, foundname); if (stat(fullname, statbuf) < 0) { log("casestat: stat '%s': %s", fullname, strerror(errno)); return -1; } strcpy(filename, fullname); // We send back the actual (correct-case) filename to the caller return 0; } /// Compare function used to sort files and directories to be listed alphabetically, insensitive to character case int my_filelist_compare(const void *ap, const void *bp) { struct dirent **a = (struct dirent**)ap; struct dirent **b = (struct dirent**)bp; return strcasecmp((*a)->d_name, (*b)->d_name); } /* ************************************ * Directory browsing * ************************************ */ // ======================================================================================== int scandir_selector(const struct dirent *entry) {// The goal is to limit to files returned from the scandir() // scandir() will get only the entries for which scandir_selector() returns a non-zero value. // To ensure compliance for multiplatform: // ==> we should be as portable as possible => no use of entry->d_type // ==> we should be compliant Apple gcc options: no_nested_function // Before using scandir() and scandir_selector() the following variable should be set // A global structure variable: scandir_info // char scandir_basename_pattern[PATH_MAX] ; // ex: movie*.* // char scandir_file_extensions[PATH_MAX] ; // ex: srt,apt,txt,sub... set to '\0' if n/a // char scandir_dirname[PATH_MAX] ; // ex: /data/media/video/Children // short scandir_directory_limited ; // flag 1=Only directory name is retrieved by scandir() // --- const char *entryname = entry->d_name; char fullname[PATH_MAX]; // dirname + entryname to afford a stat() more portable than d_type==DT_REG struct stat statbuf; snprintf(fullname, sizeof fullname, "%s/%s", scandir_info.scandir_dirname, entryname); if ( (*entryname != '.') && (lstat(fullname, &statbuf) < 0) ){ loglevel(LOGERROR,"Error: scandir_selector: Couldn't stat %s: %s",fullname,strerror(errno)); return 0; } if (scandir_info.scandir_directory_limited) { // Only 'directory' name is requested to be filtered and sent back if ((*entryname != '.') && S_ISDIR(statbuf.st_mode)) { loglevel(LOGDEBUG,"Debug: scandir_selector DIR ext[%s] entry(%s) base_pattern(%s) =>1", scandir_info.scandir_file_extensions,entryname, scandir_info.scandir_basename_pattern); return 1; } else { loglevel(LOGDEBUG,"Debug: scandir_selector DIR ext[%s] entry(%s) base_pattern(%s) =>0", scandir_info.scandir_file_extensions,entryname, scandir_info.scandir_basename_pattern); } } else // Only 'standard file' name is requested to be filtered and sent back if (!S_ISDIR(statbuf.st_mode) && (strcmp(entryname, ".") && strcmp(entryname, "..")) && ( !*scandir_info.scandir_file_extensions || list_contains_extension(scandir_info.scandir_file_extensions, entryname) )) { // Debug only for developer in optimisation step /*loglevel(LOGDEBUG,"Debug: scandir_selector FILE ext[%s] entry(%s) base_pattern(%s) basename_match(%d)", scandir_info.scandir_file_extensions,entryname, scandir_info.scandir_basename_pattern, !fnmatch(scandir_info.scandir_basename_pattern, entryname, FNM_CASEFOLD) ); */ // Accept only filename matching the pattern (return 1) return !fnmatch(scandir_info.scandir_basename_pattern, entryname, FNM_CASEFOLD); } else { // Debug only for developer in optimisation step /*loglevel(LOGDEBUG,"Debug: scandir_selector FILE ext[%s] entry(%s) base_pattern(%s) result(%d)", scandir_info.scandir_file_extensions,entryname, scandir_info.scandir_basename_pattern, 0); */ } return 0; } // end scandir_selector() // ======================================================================================== void setup_scandir_selector(const char *basename_pattern, const char *file_extensions, const char *dirname, int directory_limited) { // This function is called just before using scandir(path,scandir_selector(),..) to configure the scandir() // No need to reserve memory as scandir_variable are global // Only a child (forked) is using scandir_selector() thus serving only once client at a time (no weekness) strncpy(scandir_info.scandir_basename_pattern, basename_pattern, sizeof(scandir_info.scandir_basename_pattern) - 1); strncpy(scandir_info.scandir_file_extensions, file_extensions, sizeof(scandir_info.scandir_file_extensions) - 1); strncpy(scandir_info.scandir_dirname, dirname, sizeof(scandir_info.scandir_dirname) - 1); scandir_info.scandir_directory_limited = directory_limited; } #ifdef USE_FTW // ********************************************************************** int ftw_extract_timestamp_file(const char *filename, const struct stat *filestat, int fileflag) {/* * The ftw function calls the callback function given in the parameter func for every item * which is found in the directory specified by filename * * Before using ftw() and ftw_extract_timestamp_file() the following variable should be set * A global structure variable: ftw_info * char ftw_tempfilename[PATH_MAX] ; // ex: /home/olivier/media/video/test/.piccache/tmpsEnNZH * int ftw_temp_fd ; // ex: file descriptor get with fopen() * int ftw_nb_of_cachedfile ; // ex: number of files found in cached directory */ if (fileflag == FTW_F) //return process_file(filename, filestat); if (extract_timestamp_file(filename, filestat, ftw_info.ftw_temp_fd,ftw_info.ftw_tempfilename) > 0) { return 1; } return 0; } #endif // ********************************************************************** // Migrated from a nested structure inside trim_cache(), to support MacOsX platform int extract_timestamp_file(const char *filename, const struct stat *filestat, int fd, const char *tempfilename) { // return 0 if success char filename_buf[PATH_MAX], filenamebase[PATH_MAX]; int chars_written; int line_size = PATH_MAX + 64; char line[line_size]; if (!S_ISDIR(filestat->st_mode)) { strcpy(filename_buf, filename); strcpy(filenamebase, basename(filename_buf)); if (!strncmp(filenamebase, UNIQUE_FILENAME_TEMPLATE_PREFIX, strlen(UNIQUE_FILENAME_TEMPLATE_PREFIX))) if (!strchr(filenamebase, '.')) { // This is a cache entry (the actual image file) if (!strftime(line, sizeof line, "%F%T ", gmtime(&filestat->st_mtime))) { loglevel(LOGERROR,"Error: extract_timestamp_file: strftime: %s", strerror(errno)); return -1; } loglevel(LOGDEBUG,"Debug: extract_timestamp_file (YMDHMS):%s - %s",line,filename); strcat(line, filename); strcat(line, "\n"); if ((chars_written = write(fd, line, strlen(line))) < strlen(line)) { loglevel(LOGERROR,"Error: extract_timestamp_file: cannot write %d chars to temp file: %s: %s", strlen(line), tempfilename, strerror(errno)); return -1; } else { // well done ftw_info.ftw_nb_of_cachedfile++; // if fullname is a directory, extract_timestamp return -1 //loglevel(LOGDEBUG,"Debug: extract_timestamp OK (%s)(%d) in temp: %s",filename,ftw_info.ftw_nb_of_cachedfile,tempfilename); return 0; } } } return 0; } /* ************************************ * Networking * ************************************ */ //===================================================================== int receive_line(int sock, char *buffer, int size, int timeout_seconds) { /// Receive a single line from a socket (line terminated by LF, CRLF or CR) /// NOTE: The line terminators have been stripped from the returned string int len, peeklen, readlen; char *p; static int callno = 0; callno++; // For tracking each call when debugging logv("receive_line #%d: Reading from socket %d", callno, sock); if (is_socket_timed_out) { logv("receive_line #%d: Already timed out.", callno); return -1; } /* We support timeout handling. This is needed because OGG playback on a DP-600 (pre-KNAS firware) will leave connections open (one per song played in sequence). 2006-11-18/VT: The cause may be something else but this sort of fixes it. 2007-01-06/VT: This approach doesn't actually FIX it: A KiSS DP-600 will choke (die: no audio, video output shut off) when too many (10 to 20) connections have been forced closed by us. 2007-01-21/VT: This OGG bug has been reported to KiSS and they confirmed the error in their labs. They say it should be fixed in next firmware (KNAS?). */ if (timeout_seconds > 0) { fd_set readset; int result; struct timeval tv; // Initialize the set FD_ZERO(&readset); FD_SET(sock, &readset); // Initialize timeout struct tv.tv_sec = timeout_seconds; tv.tv_usec = 0; // Wait until we have data or a timeout if ((result = select(sock+1, &readset, NULL, NULL, &tv)) < 0) { log("receive_line: select failed. %s", strerror(errno)); return -1; } else if (result == 0) { logv("receive_line #%d: Timed out waiting for data.", callno); is_socket_timed_out = 1; return -1; } } // Receive a single command line - i.e. up to and including the next EOL character // NOTE: All commands must end with an EOL character (\n) - optionally preceded by a \r character. if ((peeklen = recv(sock, buffer, size - 1, MSG_PEEK)) < 0) { log("recv (peek): %s", strerror(errno)); return -1; } else if (peeklen == 0) { // EOF: Do actual recv(), not just peek. Probably not necessary but costs nothing... logv("receive_line #%d (peek): EOF on socket %d", callno, sock); if ((len = recv(sock, buffer, size - 1, 0)) < 0) log("recv (eof): %s", strerror(errno)); else if (len > 0) log("recv (eof): Received %d chars after peek said EOF, should be 0.", len); return -1; } else { logv("receive_line #%d (peek): peeked %d chars.", callno, peeklen); if ((p = strchr(buffer, '\n')) != NULL) while (p < buffer + peeklen && (*p == '\n' || *p == '\r')) p++; // Point at the char AFTER the last of ALL \r and \n chars at the end of the line int linesize = p != NULL? p - buffer : size - 1; // Now read the command line if ((readlen = recv(sock, buffer, linesize, 0)) < 0) { log("recv: %s", strerror(errno)); return -1; } else if (readlen == 0) { logv("receive_line #%d: EOF on socket %d", callno, sock); return -1; } } // Strip trailing EOL characters, null-terminate buffer, adjust length for (len = readlen, p = buffer + len - 1; (*p == '\n' || *p == '\r') && p >= buffer; p--) { *p = '\0'; len--; } *(buffer + size - 1) = '\0'; // In case we filled the buffer logv("receive_line #%d: Read %d chars from socket %d, stripped to %d chars.", callno, readlen, sock, len); /* properly end the string and prevent buffer overflows */ buffer[len < size ? len : len - 1] = '\0'; return len; } //===================================================================== /// Send a number of bytes to a socket int send_bytes(int sock, const char *buffer, int size) { int wrote, written; // Ensure successful write by looping until the entire buffer has been written and by repeating interrupted writes. for (written = 0; written < size; written += wrote) { do { wrote = send(sock, buffer, size, 0); if (wrote < 0 && errno != EINTR) { loglevel(LOGERROR,"Error: send_bytes: send failed: %s", strerror(errno)); return -1; } else { //if (wrote < 0) { //loglevel(LOGERROR,"Error: sending %d bytes : %s",wrote, strerror(errno)); } } while (wrote < 0 && errno == EINTR); // Repeat if interrupted } return written; } //===================================================================== /// Send a character string followed by a linefeed to a socket void send_line(int sock, const char *line) { int lenToSend = strlen(line); int lenSent; lenSent = send_bytes(sock, line, lenToSend); if (lenSent == lenToSend) { send_bytes(sock, "\n", 1); } else { loglevel (LOGERROR,"ERROR %d bytes sent on total %d : %s",lenSent,lenToSend,line); } } /* ************************************ * Misc * ************************************ */ /// Given a mediapath containing a full path to a media file, return the full path to the [Recently used] /// directory for the media store (audio, video or picture) that contains the given media file. int get_recent_path(char *recentpath, const char *mediapath) { char recent[PATH_MAX]; int len; enum mediastore_t mediastore = mst_unknown; const char *mediastorepath, *persistentsubdir, *firstslash_p; // First check for a match with a primary path if (!strncmp(mediapath, config.audiopath, strlen(config.audiopath)) || (*config.persistentstoragepath && strstr(mediapath, "/audio/.recent/"))) { mediastore = mst_audio; } else if (!strncmp(mediapath, config.videopath, strlen(config.videopath)) || (*config.persistentstoragepath && strstr(mediapath, "/video/.recent/"))) { mediastore = mst_video; } else if (!strncmp(mediapath, config.picturepath, strlen(config.picturepath)) || (*config.persistentstoragepath && strstr(mediapath, "/picture/.recent/"))) { mediastore = mst_picture; } // Check for a match with an "extra folder" path for (int i = 0; i < config.extra_audiopath_list.count && mediastore == mst_unknown; i++) if (strstr(mediapath, config.extra_audiopath_list.list[i]->path) == mediapath) if (*(firstslash_p = mediapath + strlen(config.extra_audiopath_list.list[i]->path)) == '\0' || *firstslash_p == '/') mediastore = mst_audio; for (int i = 0; i < config.extra_videopath_list.count && mediastore == mst_unknown; i++) if (strstr(mediapath, config.extra_videopath_list.list[i]->path) == mediapath) if (*(firstslash_p = mediapath + strlen(config.extra_videopath_list.list[i]->path)) == '\0' || *firstslash_p == '/') mediastore = mst_video; for (int i = 0; i < config.extra_picturepath_list.count && mediastore == mst_unknown; i++) if (strstr(mediapath, config.extra_picturepath_list.list[i]->path) == mediapath) if (*(firstslash_p = mediapath + strlen(config.extra_picturepath_list.list[i]->path)) == '\0' || *firstslash_p == '/') mediastore = mst_picture; switch (mediastore) { case mst_audio: mediastorepath = config.audiopath; persistentsubdir = "audio"; break; case mst_video: mediastorepath = config.videopath; persistentsubdir = "video"; break; case mst_picture: mediastorepath = config.picturepath; persistentsubdir = "picture"; break; default: log ("get_recent_path: Unknown media path in \"%s\"", mediapath); return -1; } if (*config.persistentstoragepath) len = snprintf(recent, sizeof recent, "%s/%s/.recent", config.persistentstoragepath, persistentsubdir); else len = snprintf(recent, sizeof recent, "%s/.recent", mediastorepath); if (len >= sizeof recent) { log ("get_recent_path: Buffer too small for filename \"%s\"", mediapath); return -1; } strcpy(recentpath, recent); return 0; } /* * Name: clean_pathname * * Description: Replaces unsafe/incorrect instances of: * //[...] with / * /./ with / * /../ with / (technically not what we want, but browsers should deal with this, not servers) */ void clean_pathname(char *pathname) { char *cleanpath, c; cleanpath = pathname; while ((c = *pathname++)) { if (c == '/') { while (1) { if (*pathname == '/') pathname++; else if (*pathname == '.' && *(pathname + 1) == '/') pathname += 2; else if (*pathname == '.' && *(pathname + 1) == '.' && *(pathname + 2) == '/') { pathname += 3; } else break; } } *cleanpath++ = c; } *cleanpath = '\0'; } int verify_path(char *p) { char path[PATH_MAX]; int len; const char *firstslash_p; if ((len = snprintf(path, PATH_MAX, "/%s", p) < PATH_MAX)) { clean_pathname(path); if (strstr(path, config.audiopath) == path || strstr(path, config.videopath) == path || strstr(path, config.picturepath) == path || (*config.persistentstoragepath && strstr(path, config.persistentstoragepath) == path)) return 0; for (int i = 0; i < config.extra_audiopath_list.count; i++) if (strstr(path, config.extra_audiopath_list.list[i]->path) == path) if (*(firstslash_p = path + strlen(config.extra_audiopath_list.list[i]->path)) == '\0' || *firstslash_p == '/') return 0; for (int i = 0; i < config.extra_videopath_list.count; i++) if (strstr(path, config.extra_videopath_list.list[i]->path) == path) if (*(firstslash_p = path + strlen(config.extra_videopath_list.list[i]->path)) == '\0' || *firstslash_p == '/') return 0; for (int i = 0; i < config.extra_picturepath_list.count; i++) if (strstr(path, config.extra_picturepath_list.list[i]->path) == path) if (*(firstslash_p = path + strlen(config.extra_picturepath_list.list[i]->path)) == '\0' || *firstslash_p == '/') return 0; } else log("Buffer too small: snprintf returned %d, expected < %d for verify_path(\"%s\")", len, PATH_MAX, p); return -1; } /// Construct pseudo-filename / foldername, like {kissdx-ISO}original-filename.iso /// We construct the new filename in our internal static buffer and return that. WATCH OUT for re-entrancy issues! int construct_pseudo_name(const char* prefix, char** thisname_p, int containingpath_len) { static char pseudoname[PATH_MAX]; int prefix_len = strlen(prefix); int thisname_len = strlen(*thisname_p); if (prefix_len + thisname_len + containingpath_len + 6 > PATH_MAX) { log("Path too long: cannot create pseudo-filename for \"%s\"", *thisname_p); return 0; // We cannot process this file as a pseudo-file: Path would be too long } memmove(pseudoname + prefix_len, *thisname_p, thisname_len + 1); memmove(pseudoname, prefix, prefix_len); *thisname_p = pseudoname; return 1; } /// Rename the filetype (filename extension) of a real filename before it is sent to the player. /// This operation is governed by the renamefiletypes configuration setting. void rename_filetype_outgoing(char* filename) { if (*config.renamefiletypes) { char* pExt = strrchr(filename, '.'); if (pExt && strlen(++pExt) <= 30) { char extColon[32]; strcpy(extColon, pExt); strcat(extColon, ":"); const char* pextColon = list_pointer(config.renamefiletypes, extColon, ",", lsmt_begins_with); if (pextColon) { // This file has a filetype that should be renamed according to config.renamefiletypes char newExt[32]; const char *pnewExt = pextColon + strlen(extColon); const char *pnewExtEndingComma = strchr(pnewExt, ','); if (pnewExtEndingComma) { memset(newExt, 0, sizeof(newExt)); strncpy(newExt, pnewExt, pnewExtEndingComma - pnewExt); } else { strcpy(newExt, pnewExt); } // Replace the extension with the extension of the renamed media file strcpy(pExt, newExt); // Test whether the potentially renamed file exists already as a real file. // If it does, we will NOT rename this file after all, to avoid filename collision. struct stat statbuf; if (stat(filename, &statbuf) >= 0) { // Change the extension back to the original strncpy(pExt, extColon, strlen(extColon) - 1); *(pExt + (strlen(extColon) - 1)) = '\0'; } } } } } /// Rename back the filetype (filename extension) of a filename that was received from the player. /// This operation is governed by the renamefiletypes configuration setting. void rename_filetype_incoming(char* filename) { if (*config.renamefiletypes) { // Locate the filename's filetype in the renamefiletypes list. // NOTE: If multiple renamefiletypes entries have the same target filetype (after the colon) // then we must stat() reach renamed-back filename to find the real media file. struct stat statbuf; char statFilename[PATH_MAX]; int matchCount = 0, filenameLength = strlen(filename), extLength; const char *pFilenameEnd = filename + filenameLength, *p; extLength = 0; p = config.renamefiletypes; while ((p = strchr(p, ':'))) { p++; const char *pEnd = strchr(p, ','); extLength = pEnd? pEnd - p : strlen(p); if (extLength > 0 && extLength < filenameLength && extLength <= 30) { if (*(pFilenameEnd - extLength - 1) == '.' && !strncmp(p, pFilenameEnd - extLength, extLength)) { matchCount++; } } } int stateNeeded = (matchCount > 1), matchFound = 0; char realExt[32]; extLength = 0; p = config.renamefiletypes; while (!matchFound && (p = strchr(p, ':'))) { p++; const char *pEnd = strchr(p, ','); extLength = pEnd? pEnd - p : strlen(p); if (extLength > 0 && extLength < filenameLength && extLength <= 30) { if (*(pFilenameEnd - extLength - 1) == '.' && !strncmp(p, pFilenameEnd - extLength, extLength)) { const char *pRealExt; for (pRealExt = p - 2; pRealExt >= config.renamefiletypes && *pRealExt != ','; pRealExt--); memset(realExt, 0, sizeof(realExt)); strncpy(realExt, pRealExt + 1, p - pRealExt - 2); if (stateNeeded) { strcpy(statFilename, filename); strcpy(statFilename + filenameLength - extLength, realExt); if (stat(statFilename, &statbuf) >= 0) { matchFound = 1; } } else { matchFound = 1; } } } } if (matchFound && extLength > 0) { // The requested file has a filetype that should be renamed back. // Test whether the requested file itself exists. If it does, we won't rename it back. if (stat(filename, &statbuf) < 0) { strcpy(filename + filenameLength - extLength, realExt); } } } } // *************************************************************************************