/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd) * * Copyright (C) 2005 Stelian Pop * Portions Copyright (C) 2006 Vidar Tysse * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_INTERNAL_SENDFILE #include #else #ifdef Linux #include #endif #endif #include "dvdread.h" #include "kissdx.h" #include "utils.h" #include "sendfile.h" #include "piccache.h" #define C_PSEUDOFOLDER_PREFIX_FIRSTCHAR "{" #define C_ANY_PSEUDOFOLDER_PREFIX C_PSEUDOFOLDER_PREFIX_FIRSTCHAR"kissdx-" #define C_PSEUDOFOLDER_PREFIX_LASTCHAR "}" #define C_DIR_PSEUDOFOLDER_TYPE "DIR" #define C_ISO_PSEUDOFOLDER_TYPE "ISO" #define C_M3U_PSEUDOFOLDER_TYPE "M3U" #define C_PLS_PSEUDOFOLDER_TYPE "PLS" #define C_PSEUDOFOLDER_TYPE_LEN 3 #define C_DIR_PSEUDOFOLDER_PREFIX C_ANY_PSEUDOFOLDER_PREFIX C_DIR_PSEUDOFOLDER_TYPE C_PSEUDOFOLDER_PREFIX_LASTCHAR #define C_ISO_PSEUDOFOLDER_PREFIX C_ANY_PSEUDOFOLDER_PREFIX C_ISO_PSEUDOFOLDER_TYPE C_PSEUDOFOLDER_PREFIX_LASTCHAR #define C_M3U_PSEUDOFOLDER_PREFIX C_ANY_PSEUDOFOLDER_PREFIX C_M3U_PSEUDOFOLDER_TYPE C_PSEUDOFOLDER_PREFIX_LASTCHAR #define C_PLS_PSEUDOFOLDER_PREFIX C_ANY_PSEUDOFOLDER_PREFIX C_PLS_PSEUDOFOLDER_TYPE C_PSEUDOFOLDER_PREFIX_LASTCHAR #define C_ISO_PSEUDOFILE_SUFFIX ".mpg" #define C_PSEUDODVD_TITLE_PART1 "Title no. " #define C_PSEUDODVD_TITLE_PART2 " of " const char *subtitle_file_extensions = "srt,aqt,sub,rt,smi,ssa,txt"; const char *JPEG_extensions = "jpeg,jpg"; /* Prototypes for external functions */ int handle_playlist(int sock, char *path); int CachedSizeResizedJPEG(const char *filename); int CachedSendResizedJPEG(int fd, const char *filename, off_t offset, size_t chunksize); /* Prototypes for internal functions */ static int store_recent(char * filename); static int transform_request_for_recent_file(char *request); int will_resize_jpeg_file(const char *path) { return (picturetargetwidth && picturetargetheight && strstr(path, picturepath) == path && list_contains_extension(JPEG_extensions, path)); } static int my_filelist_compare(const void *ap, const void *bp) { struct dirent **a = (struct dirent**)ap; struct dirent **b = (struct dirent**)bp; // Files and directories should be listed alphabetically, insensitive to character case return strcasecmp((*a)->d_name, (*b)->d_name); } /// Get the next picture for possible pre-caching static int get_next_picture(char *next_picture_filename, const char *current_picture_filename) { char first_picture_basename[PATH_MAX] = "", last_picture_basename[PATH_MAX] = ""; char current_picture_dirname[PATH_MAX] = "", current_picture_basename[PATH_MAX] = ""; int fileno = -1, current_picture_fileno = -1, next_picture_fileno = -1; int scandir_selector(const struct dirent *entry) { const char *thisname = entry->d_name; if (*thisname != '.' && picturefileextensions && *picturefileextensions && list_contains_extension(picturefileextensions, thisname)) { char fullname[PATH_MAX]; struct stat statbuf; snprintf(fullname, sizeof fullname, "%s/%s", current_picture_dirname, thisname); if (lstat(fullname, &statbuf) < 0) log("get_next_picture.scandir_selector: Couldn't stat %s", fullname); else if (!S_ISDIR(statbuf.st_mode)) { strcpy(last_picture_basename, thisname); if (++fileno == 0) strcpy(first_picture_basename, thisname); if (!strcmp(thisname, current_picture_basename)) current_picture_fileno = fileno; else if (current_picture_fileno > -1 && fileno > current_picture_fileno && next_picture_fileno == -1 && will_resize_jpeg_file(fullname)) { next_picture_fileno = fileno; return 1; } } } return 0; } char filename_buf[PATH_MAX], next_picture_basename[PATH_MAX] = ""; struct dirent **namelist; int num_files, n; strcpy(filename_buf, current_picture_filename); strcpy(current_picture_basename, basename(filename_buf)); strcpy(current_picture_dirname, dirname(filename_buf)); if ((num_files = scandir(current_picture_dirname, &namelist, scandir_selector, my_filelist_compare)) < 0) { log("get_next_picture: scandir %s: %s", current_picture_dirname, strerror(errno)); return -1; } else if (num_files > 1) { log("get_next_picture: scandir %s returned %d files. Expected 0 or 1.", current_picture_dirname, num_files); return -1; } else if (num_files == 0) { if (*last_picture_basename && *first_picture_basename && !strcmp(current_picture_basename, last_picture_basename)) strcpy(next_picture_basename, first_picture_basename); else { log("get_next_picture: scandir '%s' didn't find next picture for '%s'.", current_picture_dirname, current_picture_filename); return -1; } } else strcpy(next_picture_basename, namelist[0]->d_name); for (n = 0; n < num_files; n++) { free(namelist[n]); } free(namelist); snprintf(next_picture_filename, PATH_MAX, "%s/%s", current_picture_dirname, next_picture_basename); return 0; } static void precache_next_picture(const char *current_picture_filename) { char next_picture_filename[PATH_MAX] = "", cached_filename[PATH_MAX]; if (piccache_is_precaching_in_progress()) return; if (get_next_picture(next_picture_filename, current_picture_filename)) return; if (!piccache_get_entry_filename(cached_filename, next_picture_filename, picturetargetwidth, picturetargetheight, picturemaxzoompercent)) return; // Already cached if (!piccache_set_precaching_status(next_picture_filename)) { CachedSizeResizedJPEG(next_picture_filename); piccache_clear_precaching_status(); } } /// Transform a subtitle filename according to the subtitlefilemapping configuration option static void transform_subtitle_filename(char *filename) { char subtitle_dirname[PATH_MAX] = ""; char subtitle_basename_pattern[PATH_MAX] = ""; int scandir_selector(const struct dirent *entry) { const char *thisname = entry->d_name; if (*thisname != '.' && list_contains_extension(subtitle_file_extensions, thisname)) { char fullname[PATH_MAX]; struct stat statbuf; snprintf(fullname, sizeof fullname, "%s/%s", subtitle_dirname, thisname); if (lstat(fullname, &statbuf) < 0) log("scandir_selector: Couldn't stat %s", fullname); else if (!S_ISDIR(statbuf.st_mode)) return !fnmatch(subtitle_basename_pattern, thisname, FNM_CASEFOLD); } return 0; } char pattern[PATH_MAX], fullpattern[PATH_MAX], recentpath[PATH_MAX]; char filename_buf[PATH_MAX], path[PATH_MAX] = "", name[PATH_MAX] = "", name_and_ext[PATH_MAX] = "", ext[PATH_MAX] = "", *p; struct dirent **namelist; int num_files, n, ext_index, isPathInPattern, isRecent; if (!*subtitle_catchall_pattern) return; // Subtitle filename mapping has not been configured // Populate our substitutes for this file strcpy(filename_buf, filename); strcpy(name_and_ext, basename(filename_buf)); strcpy(name, name_and_ext); strcpy(path, dirname(filename_buf)); if (*(path + strlen(path) - 1) == '/') *(path + strlen(path) - 1) = '\0'; if ((p = strrchr(name, '.'))) { strcpy(ext, p + 1); *p = '\0'; } else *ext = '\0'; // Find this file's index into the list of subtitle filename extensions. This will be used to pick the appropriate subtitle file if (!*ext || !(p = (char *)list_pointer(subtitle_file_extensions, ext, ","))) return; // Unknown extension for (ext_index = 0; p > subtitle_file_extensions; p--) if (*p == ',') ext_index++; // If playing from the [Recently used] folder, get the real path from the symlink for the video file being played strcpy(recentpath, videopath); strcat(recentpath, "/.recent"); isRecent = !strcmp(path, recentpath); if (isRecent) { char videofilename[PATH_MAX], target[PATH_MAX]; struct stat statbuf; strcpy(videofilename, filename); char *pExt = strrchr(videofilename, '.') + 1; strcpy(pExt, "avi"); if (casestat(videofilename, &statbuf) < 0) { log("transform_subtitle_filename: Cannot find video file symlink for subtitle file '%s'", filename); return; } if ((n = readlink(videofilename, target, PATH_MAX)) > 0) { *(target + n) = '\0'; strcpy(path, dirname(target)); } else { log("transform_subtitle_filename: Cannot find real file for symlink '%s'", videofilename); return; } } // Replace the special tokens in the pattern with our substitutes for this file strcpy(pattern, subtitle_catchall_pattern); isPathInPattern = string_substitute(pattern, "{path}", path, sizeof pattern); string_substitute(pattern, "{name}", name, sizeof pattern); string_substitute(pattern, "{ext}" , ext , sizeof pattern); // Construct the full pattern, including the path if (isPathInPattern) { *fullpattern = '\0'; } else { strcpy(fullpattern, path); strcat(fullpattern, "/"); } strcat(fullpattern, pattern); // Traverse a directory listing based on the pattern and pick the appropriate subtitle file p = strrchr(fullpattern, '/'); strcpy(subtitle_basename_pattern, p + 1); *p = '\0'; strcpy(subtitle_dirname, fullpattern); if ((num_files = scandir(fullpattern, &namelist, scandir_selector, my_filelist_compare)) < 0) { log("transform_subtitle_filename: scandir %s: %s", fullpattern, strerror(errno)); return; } for (n = 0; n < num_files; n++) { if (n == ext_index) { string_substitute(filename, name_and_ext, namelist[n]->d_name, PATH_MAX); if (isRecent) string_substitute(filename, recentpath, path, PATH_MAX); } free(namelist[n]); } free(namelist); } /* * Name: timesort * * Description: Sort function for scandir which sorts by time. * Most recent files first. */ static int timesort(const void * f1, const void * f2) { char fname1[PATH_MAX]; char fname2[PATH_MAX]; char cwd[PATH_MAX]; struct stat st1, st2; struct dirent *d1; struct dirent *d2; if (f1 == NULL || f2 == NULL) return 0; d1 = *(struct dirent **)f1; d2 = *(struct dirent **)f2; if (getcwd(cwd, PATH_MAX) == NULL) return 0; snprintf(fname1, PATH_MAX, "%s/%s", cwd, d1->d_name); snprintf(fname2, PATH_MAX, "%s/%s", cwd, d2->d_name); if (lstat(fname1, &st1) < 0) log("Couldn't stat %s", fname1); if (lstat(fname2, &st2) < 0) log("Couldn't stat %s", fname2); if (st1.st_ctime > st2.st_ctime) return -1; /* if timestamp is bigger, file is newer */ if (st1.st_ctime < st2.st_ctime) return 1; /* if timestamp is smaller, file is older */ return 0; } int run_trigger(char *command, char *filename, size_t fsize, char *result, size_t size) { int pid, status; int p[2]; size_t len; if (!command[0]) { if (result) memcpy(result, filename, min(fsize, size)); return 0; } if (pipe(p) < 0) { log("trigger pipe() failed: %s", strerror(errno)); return -1; } pid = fork(); if (pid < 0) { log("trigger fork() failed: %s", strerror(errno)); close(p[0]); close(p[1]); return -1; } if (pid == 0) { setpgid(pid, 0); close(p[0]); close(STDOUT_FILENO); if (dup2(p[1], STDOUT_FILENO) < 0) { log("trigger dup2() failed: %s", strerror(errno)); exit(1); } execlp(command, command, filename, NULL); log("trigger exec() failed: %s", strerror(errno)); exit(1); } close(p[1]); if (waitpid(pid, &status, 0) < 0) { log("trigger waitpid() failed: %s", strerror(errno)); close(p[0]); return -1; } if (!WIFEXITED(status)) { log("trigger child exited abnormally: %x", status); close(p[0]); return -1; } if (WEXITSTATUS(status)) { log("trigger child returned non 0: %d", WEXITSTATUS(status)); close(p[0]); return -1; } if (result) { if ((len = read(p[0], result, size)) < 0) { log("trigger read() failed: %s", strerror(errno)); close(p[0]); return -1; } while (len > 0 && (result[len - 1] == '\r' || result[len - 1] == '\n')) len--; result[len < size ? len : len - 1] = '\0'; } close(p[0]); return 0; } static int do_recv(int sock, char *buffer, int size) { int len; if ((len = recv(sock, buffer, size, 0)) < 0) { log("recv: %s", strerror(errno)); return len; } if (len == 0) return -1; while (len > 0 && (buffer[len - 1] == '\r' || buffer[len - 1] == '\n')) len--; /* properly end the string and prevent buffer overflows */ buffer[len < size ? len : len - 1] = '\0'; if (v_opt) log("<-- [%s]", buffer); return len; } int do_send(int sock, char *buffer, int size) { int len; logv("--> [%s]", buffer); if ((len = send(sock, buffer, size, 0)) < 0) log("send: %s", strerror(errno)); return len; } /* * 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'; } static int verify_path(char *p) { char path[PATH_MAX]; int len; if ((len = snprintf(path, PATH_MAX, "/%s", p) < PATH_MAX)) { clean_pathname(path); if (strstr(path, audiopath) == path || strstr(path, videopath) == path || strstr(path, picturepath) == path) return 0; } else log("Buffer too small: snprintf returned %d, expected < %d for verify_path(\"%s\")", len, PATH_MAX, p); return -1; } /// Open a pseudo-folder as a DVD static dvd_reader_t* open_DVD_cached(char *foldername) { char realFullName[PATH_MAX]; // Will store the real foldername for a DVD folder, or the real filename for an ISO file. This is the name that we will send to libdvdread. int len; // We expect, and can only handle, a request for a path/filename in one of the following formats: // [/path]/ // [/path]/ char basepath[PATH_MAX] = {'\0'}, pseudoFolderType[16] = {'\0'}, real_filename[PATH_MAX] = {'\0'}; const char *formatString = "%[^"C_PSEUDOFOLDER_PREFIX_FIRSTCHAR"]"C_ANY_PSEUDOFOLDER_PREFIX"%[^"C_PSEUDOFOLDER_PREFIX_LASTCHAR"]"C_PSEUDOFOLDER_PREFIX_LASTCHAR"%[^|]"; int assignedCount = sscanf(foldername, formatString, &basepath, &pseudoFolderType, &real_filename); if (assignedCount != 3) { log("invalid request for pseudo-folder. Assigned %d, expected 3. Path/name must be like \"%s\", but was: \"%s\"", assignedCount, formatString, foldername); return NULL; }; if (strcasecmp(pseudoFolderType, C_ISO_PSEUDOFOLDER_TYPE) && strcasecmp(pseudoFolderType, C_DIR_PSEUDOFOLDER_TYPE)) { log("invalid request for pseudo-folder, folder type must be "C_ISO_PSEUDOFOLDER_TYPE" or "C_DIR_PSEUDOFOLDER_TYPE", but was: \"%s\"", pseudoFolderType); return NULL; }; // Construct the real fully qualified filename if ((len = snprintf(realFullName, sizeof(realFullName), "%s%s", basepath, real_filename)) < sizeof(realFullName)) { // Open the DVD image / folder dvd_reader_t* dvd = CachedDvdOpen(realFullName, 1); if (!dvd) log("open_DVD_cached: CachedDvdOpen failed for %s, title %d", realFullName, 1); return dvd; // Normal return } else log("Buffer too small: snprintf returned %d, expected < %d for open_DVD_cached(\"%s\")", len, sizeof(realFullName), foldername); return NULL; // Error return } /// Open a pseudo-file (a DVD title) as a DVD media file static dvd_reader_t* open_DVD_title_cached(char *foldername) { char realFullName[PATH_MAX]; // Will store the real foldername for a DVD folder, or the real filename for an ISO file. This is the name that we will send to libdvdread. int len; // We expect, and can only handle, a request for a path/filename in one of the following formats: // [/path]//Title no. nn of nn // [/path]//Title no. nn of nn char basepath[PATH_MAX], pseudoFolderType[16], real_filename[PATH_MAX]; int titleNo, titleCount; const char *formatString = "%[^"C_PSEUDOFOLDER_PREFIX_FIRSTCHAR"]"C_ANY_PSEUDOFOLDER_PREFIX"%[^"C_PSEUDOFOLDER_PREFIX_LASTCHAR"]"C_PSEUDOFOLDER_PREFIX_LASTCHAR"%[^/]/"C_PSEUDODVD_TITLE_PART1"%d"C_PSEUDODVD_TITLE_PART2"%d"C_ISO_PSEUDOFILE_SUFFIX; int assignedCount = sscanf(foldername, formatString, &basepath, &pseudoFolderType, &real_filename, &titleNo, &titleCount); if (assignedCount != 5) { log("invalid request for pseudo-file title. Assigned %d, expected 5. Path/name must be like \"%s\", but was: \"%s\"", assignedCount, formatString, foldername); return NULL; }; if (strcasecmp(pseudoFolderType, C_ISO_PSEUDOFOLDER_TYPE) && strcasecmp(pseudoFolderType, C_DIR_PSEUDOFOLDER_TYPE)) { log("invalid request for pseudo-file title. Folder type must be "C_ISO_PSEUDOFOLDER_TYPE" or "C_DIR_PSEUDOFOLDER_TYPE", but was: \"%s\"", pseudoFolderType); return NULL; }; // Construct the real fully qualified filename if ((len = snprintf(realFullName, sizeof(realFullName), "%s%s", basepath, real_filename)) < sizeof(realFullName)) { // Open the DVD image / folder dvd_reader_t* dvd = CachedDvdOpen(realFullName, titleNo); if (!dvd) log("open_DVD_title_cached: CachedDvdOpen failed for %s, title %d", realFullName, titleNo); return dvd; // Normal return } else log("Buffer too small: snprintf returned %d, expected < %d for open_DVD_title_cached(\"%s\")", len, sizeof(realFullName), foldername); return NULL; // Error return } /// Send a list of all titles in the DVD or playlist that a pseudo-folder represents static void list_pseudofolder(int sock, char *base, char *path) { dvd_reader_t *dvd; char fullpath[PATH_MAX], line[2*PATH_MAX]; int pseudoFolderPrefixLen = strlen(C_ANY_PSEUDOFOLDER_PREFIX); char *pFileName = strrchr(path, '/'), *pConverted; pFileName = pFileName? pFileName + 1 : path; if (!strncmp(pFileName, C_ANY_PSEUDOFOLDER_PREFIX, pseudoFolderPrefixLen)) { int len; if ((len = snprintf(fullpath, PATH_MAX, "%s/%s", base, path)) < PATH_MAX) { clean_pathname(fullpath); if (verify_path(fullpath) < 0) { log("access denied: %s", fullpath); return; } if (!strncmp(pFileName + pseudoFolderPrefixLen, C_ISO_PSEUDOFOLDER_TYPE, C_PSEUDOFOLDER_TYPE_LEN) || !strncmp(pFileName + pseudoFolderPrefixLen, C_DIR_PSEUDOFOLDER_TYPE, C_PSEUDOFOLDER_TYPE_LEN)) { // *** List a DVD folder *** if ((dvd = open_DVD_cached(fullpath))) { int i; int titleCount = CachedDvdGetTitleCount(); for (i = 0; i < titleCount; i++) { // Send this title as a filename to the player char titleName[PATH_MAX], fpath[PATH_MAX]; snprintf(titleName, sizeof titleName, "%s%02d%s%02d%s", C_PSEUDODVD_TITLE_PART1, i+1, C_PSEUDODVD_TITLE_PART2, titleCount, C_ISO_PSEUDOFILE_SUFFIX); if ((len = snprintf(fpath, sizeof fpath, "%s/%s", fullpath, titleName)) < sizeof fpath) { if ((len = snprintf(line, sizeof line, "%s|%s|0|", titleName, fpath)) < sizeof line - 1) { pConverted = (char*)convert_charset(line, cdt_to_client); strcat(pConverted, "\n"); do_send(sock, pConverted, strlen(pConverted)); } else log("Buffer too small: snprintf to fpath in list_pseudofolder() returned %d, expected < %d for titlename \"%s\"", len, sizeof line, titleName); } else log("Buffer too small: snprintf in list_pseudofolder() returned %d, expected < %d for titlename \"%s\"", len, sizeof fpath, titleName); } } } else if (!strncmp(pFileName + pseudoFolderPrefixLen, C_M3U_PSEUDOFOLDER_TYPE, C_PSEUDOFOLDER_TYPE_LEN) || !strncmp(pFileName + pseudoFolderPrefixLen, C_PLS_PSEUDOFOLDER_TYPE, C_PSEUDOFOLDER_TYPE_LEN)) { // *** List a PLAYLIST folder *** char realPlaylistPath[PATH_MAX]; strcpy(realPlaylistPath, fullpath); char *pLastSlash = strrchr(realPlaylistPath, '/'); char *pRealPlaylistFileName = pLastSlash + 1 + strlen(C_M3U_PSEUDOFOLDER_PREFIX); memmove(pLastSlash + 1, pRealPlaylistFileName, strlen(pRealPlaylistFileName) + 1); handle_playlist(sock, realPlaylistPath); } else log("Internal error: list_pseudofolder() inexplicably wrecked pseudofolder filename: %s", pFileName); } else log("Buffer too small: snprintf in list_pseudofolder() returned %d, expected < %d for path \"%s\"", len, PATH_MAX, path); } else log("Internal error: list_pseudofolder() was called with non-pseudofolder argument: %s", path); do_send(sock, "EOL\n", 4); } static void list_folder(int sock, char *base, char *path, enum mediastore_t mediastore) { char fullpath[PATH_MAX], line[2*PATH_MAX], *thisname, pseudoname[PATH_MAX], *pConverted; int i, n, to_send, len; struct dirent **namelist; char fpath[PATH_MAX]; struct stat statbuf; const char *validExtensions = mediastore == mst_audio? audiofileextensions : mediastore == mst_video? videofileextensions : mediastore == mst_picture? picturefileextensions : NULL; const char *recentlyUsedFolderName = "[Recently used]"; int (*sortmethod)(const void *,const void *) = my_filelist_compare; if (max_recent_files && strstr(path, recentlyUsedFolderName)) { path = ".recent"; sortmethod = timesort; } // Check whether this is a pseudo-path char *pFileName = strrchr(path, '/'); pFileName = pFileName? pFileName + 1 : path; if (!strncmp(pFileName, C_ANY_PSEUDOFOLDER_PREFIX, strlen(C_ANY_PSEUDOFOLDER_PREFIX))) { list_pseudofolder(sock, base, path); return; } if ((len = snprintf(fullpath, sizeof fullpath, "%s/%s", base, path)) < sizeof fullpath) { clean_pathname(fullpath); if (verify_path(fullpath) < 0) { log("access denied: %s", fullpath); } else { chdir(fullpath); /* scandir does not provide path info, so set it */ if ((n = scandir(fullpath, &namelist, 0, sortmethod)) < 0) { log("scandir %s: %s", fullpath, strerror(errno)); } else { /* * do this only in root and only if no -a option set * to avoid duplicate entry */ if (max_recent_files && !a_opt && path[0] == '\0') { if ((to_send = snprintf(line, PATH_MAX, "%s|%s|1|\n", recentlyUsedFolderName, recentlyUsedFolderName)) < PATH_MAX) do_send(sock, line, to_send); else log("Buffer too small: snprintf returned %d, expected < %d for recent list name \"%s\"", to_send, PATH_MAX, recentlyUsedFolderName); } // First, send directory names (including pseudo-folders) for (i = 0; i < n; i++) { int list_this_entry = -1; thisname = namelist[i]->d_name; if (!a_opt) { // Don't list hidden directories unless specified by option if (*thisname == '.') list_this_entry = 0; } else { // Never list the "this level" and "previous level" pseudo directory entries if (!strcmp(thisname, ".") || !strcmp(thisname, "..")) list_this_entry = 0; } if (list_this_entry) { if ((len = snprintf(fpath, sizeof fpath, "%s/%s/%s", base, path, thisname)) < sizeof fpath) { clean_pathname(fpath); // Get the filetype so we can test whether this is a directory mode_t filetype = 0; if (stat(fpath, &statbuf) == 0) filetype = statbuf.st_mode & S_IFMT; // Set up a pointer to the filename extension, if there is one char *ext = strrchr(thisname, '.'); if (ext) ext++; // *** Substitute a pseudo-folder for each audio playlist file *** // We add our special prefix so we will recognize the folder when the player asks for it later int isPlayablePlaylistFile = (filetype != S_IFDIR && mediastore == mst_audio && ext && (!strcasecmp(ext, "m3u") || !strcasecmp(ext, "pls"))); if (isPlayablePlaylistFile) { const char *prefix = !strcasecmp(ext, "M3U")? C_M3U_PSEUDOFOLDER_PREFIX : C_PLS_PSEUDOFOLDER_PREFIX; int prefix_len = strlen(prefix); int thisname_len = strlen(thisname); if (prefix_len + thisname_len + strlen(base) + strlen(path) + 7 <= PATH_MAX) { memmove(pseudoname + prefix_len, thisname, thisname_len + 1); memmove(pseudoname, prefix, prefix_len); thisname = pseudoname; } else { log("Path too long: cannot process playlist file \"%s\"", fpath); isPlayablePlaylistFile = 0; // We cannot process this playlist file: Path would be too long } } // *** Substitute a pseudo-folder for each playable ISO file *** // We add our special prefix so we will recognize the folder when the player asks for it later int isPlayableIsoFile = (filetype != S_IFDIR && mediastore == mst_video && ext && list_contains_extension(isofileextensions, thisname)); if (isPlayableIsoFile) { const char *prefix = C_ISO_PSEUDOFOLDER_PREFIX; int prefix_len = strlen(prefix); int thisname_len = strlen(thisname); if (prefix_len + thisname_len + strlen(base) + strlen(path) + 7 <= PATH_MAX) { memmove(pseudoname + prefix_len, thisname, thisname_len + 1); memmove(pseudoname, prefix, prefix_len); thisname = pseudoname; } else { log("Path too long: cannot process ISO file \"%s\"", fpath); isPlayableIsoFile = 0; // We cannot process this ISO file: Path would be too long } } // *** Substitute a pseudo-folder for any directory that is a DVD root *** // We add our special prefix so we will recognize the folder when the player asks for it later if (filetype == S_IFDIR && mediastore == mst_video) { char statpath[PATH_MAX], statdir[PATH_MAX]; if ((len = snprintf(statdir, sizeof statdir, "%s/VIDEO_TS", fpath)) < sizeof statdir) { if (casestat(statdir, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { if ((len = snprintf(statpath, sizeof statpath, "%s/VIDEO_TS.IFO", statdir)) < sizeof statpath) { if (casestat(statpath, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) { if ((len = snprintf(statpath, sizeof statpath, "%s/VIDEO_TS.BUP", statdir)) < sizeof statpath) { if (casestat(statpath, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) { // That's good enough. We assume that this is a DVD root folder const char *prefix = C_DIR_PSEUDOFOLDER_PREFIX; int prefix_len = strlen(prefix); int thisname_len = strlen(thisname); if (prefix_len + thisname_len + strlen(base) + strlen(path) + 7 <= PATH_MAX) { memmove(pseudoname + prefix_len, thisname, thisname_len + 1); memmove(pseudoname, prefix, prefix_len); thisname = pseudoname; } } } else log("Buffer too small: snprintf returned %d, expected < %d for statpath 2 of path \"%s\"", len, sizeof statpath, statdir); } } else log("Buffer too small: snprintf returned %d, expected < %d for statpath 1 of path \"%s\"", len, sizeof statpath, statdir); } } else log("Buffer too small: snprintf returned %d, expected < %d for statdir of path \"%s\"", len, sizeof statdir, fpath); } if (filetype == S_IFDIR || isPlayableIsoFile || isPlayablePlaylistFile) { // Send this directory name to the player if ((to_send = snprintf(line, sizeof line, "%s|%s|1|", thisname, thisname)) < sizeof line - 1) { pConverted = (char*)convert_charset(line, cdt_to_client); strcat(pConverted, "\n"); do_send(sock, pConverted, strlen(pConverted)); } else log("Buffer too small: snprintf returned %d, expected < %d for sending thisname \"%s\"", to_send, sizeof line, thisname); // Mark this entry as processed (the filename loop below will skip these) *(namelist[i]->d_name) = '\0'; } } else log("Buffer too small: snprintf returned %d, expected < %d for thisname \"%s\"", len, sizeof fpath, thisname); } } // Next, send the file names for (i = 0; i < n; i++) { int list_this_entry = -1; thisname = namelist[i]->d_name; // Don't list hidden files unless specified by option if (!a_opt && *thisname == '.') list_this_entry = 0; // Don't process the directories; they were processed in the loop above if (*thisname == '\0') list_this_entry = 0; // Only list files with extensions that match the configuration for this media store (no config means list all files) if (list_this_entry && validExtensions && *validExtensions) if (!list_contains_extension(validExtensions, thisname)) list_this_entry = 0; if (list_this_entry) { // Send this file name to the player if ((len = snprintf(fpath, sizeof fpath, "%s/%s/%s", base, path, thisname)) < sizeof fpath) { clean_pathname(fpath); if ((to_send = snprintf(line, sizeof line, "%s|%s|0|", thisname, fpath)) < sizeof line - 1) { pConverted = (char*)convert_charset(line, cdt_to_client); strcat(pConverted, "\n"); do_send(sock, pConverted, strlen(pConverted)); } else log("Buffer too small: snprintf returned %d, expected < %d for sending file \"%s\" (2)", to_send, sizeof line, thisname); } else log("Buffer too small: snprintf returned %d, expected < %d for sending file \"%s\" (1)", len, sizeof fpath, thisname); } free(namelist[i]); } free(namelist); } } } else log("Buffer too small: snprintf returned %d, expected < %d for path \"%s\"", len, sizeof fullpath, path); do_send(sock, "EOL\n", 4); } static void handle_list(int sock, char *request, size_t reqsize) { char *p1, *p2; p1 = index(request, '|'); p2 = rindex(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid LIST command \"%s\"", request); return; } *p2 = '\0'; char path[PATH_MAX]; memset(path, 0, sizeof path); strncpy(path, (char *) convert_charset(p1 + 1, cdt_from_client), sizeof path); if (strstr(request, "AUDIO") == request + 5) list_folder(sock, audiopath, path, mst_audio); else if (strstr(request, "VIDEO") == request + 5) list_folder(sock, videopath, path, mst_video); else if (strstr(request, "PICTURE") == request + 5) list_folder(sock, picturepath, path, mst_picture); else log("unknown LIST command \"%s\"", request); } /// Send the size of the DVD title that a pseudo-file represents static void handle_size_pseudofile(int sock, char *request, size_t reqsize) { char response[20]; char *p1, *filename; dvd_reader_t *dvd; p1 = index(request, '|'); if (p1 == NULL) { log("invalid SIZE command for pseudo-file: \"%s\"", request); return; } *p1 = '\0'; filename = (char *) convert_charset(request + 5, cdt_from_client); if (verify_path(filename) < 0) { log("access denied for pseudo-file: %s", filename); return; } // Find the size of the entire DVD movie if (!(dvd = open_DVD_title_cached(filename))) return; u_int64_t size = CachedDvdGetSize(); snprintf(response, sizeof(response), "%015lld", (long long)size); do_send(sock, response, 15); } /// Send the requested chunk of media content from the DVD title that a pseudo-file represents static void handle_get_pseudofile(int sock, char *request, size_t reqsize, dvd_reader_t *dvd) { char *filename; char *p1, *p2; long long o; long c; off_t offset; size_t chunk; ssize_t sent; p1 = index(request, '/'); p2 = index(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid GET command for pseudo-file: \"%s\"", request); return; } *p2 = '\0'; filename = (char *) convert_charset(p1, cdt_from_client); if (sscanf(p2 + 1, "%lld %ld", &o, &c) != 2) { log("invalid GET parameters for pseudo-file: \"%s\"", p2 + 1); return; }; offset = o; chunk = c; if (!dvd) { if (verify_path(filename) < 0) { log("access denied for pseudo-file: %s", filename); return; } if (!(dvd = open_DVD_title_cached(filename))) return; } if (max_recent_files) store_recent(filename); if ((sent = CachedDvdSendChunk(sock, offset, chunk)) < 0) { log0("handle_get_pseudofile: CachedDvdSendChunk failed"); return; } /* The client asked for more data than we have, send padding bytes */ while (sent++ < chunk) write(sock, "\0", 1); } /// Send 200 if the requested DVD title can be opened, send 404 otherwise. /// Then enter a tight loop that will serve the DVD contents to the player. static void handle_action1_pseudofile(int sock, char *request, size_t reqsize) { char *p1, *p2; dvd_reader_t *dvd; int len; p1 = strchr(request, '/'); p2 = strrchr(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid ACTION command for pseudo-file: \"%s\"", request); return; } *p2 = '\0'; char *path = (char *) convert_charset(p1, cdt_from_client); if (!(dvd = open_DVD_title_cached(path))) { do_send(sock, "404", 3); return; } if (do_send(sock, "200", 3) < 0) { CachedDvdClose(); return; } while (1) { char newreq[512]; if ((len = do_recv(sock, newreq, sizeof(newreq))) < 0) { CachedDvdClose(); return; } transform_request_for_recent_file(newreq); if (strstr(newreq, "SIZE") == newreq) handle_size_pseudofile(sock, newreq, 512); else if (strstr(newreq, "GET") == newreq) handle_get_pseudofile(sock, newreq, 512, dvd); else log("unknown ACTION command for pseudo-file: \"%s\"", newreq); } } static void handle_size(int sock, char *request, size_t reqsize) { struct stat st; char response[20]; char *p1; char tfilename[PATH_MAX]; long long filesize; p1 = index(request, '|'); if (p1 == NULL) { log("invalid SIZE command \"%s\"", request); return; } *p1 = '\0'; char *path = (char *) convert_charset(request + 5, cdt_from_client); int isSubtitleFile = list_contains_extension(subtitle_file_extensions, path); if (isSubtitleFile) transform_subtitle_filename(path); if (verify_path(path) < 0) { log("access denied: %s", path); return; } if (run_trigger(pretrigger, path, reqsize - 5, tfilename, sizeof(tfilename)) < 0) { log("pretrigger %s failed", tfilename); return; } if (stat(tfilename, &st) < 0) { log("stat %s: %s", tfilename, strerror(errno)); return; } if (will_resize_jpeg_file(tfilename)) { if (picturecachesize && picturecachesize != 1) piccache_wait_if_precaching_this(tfilename); filesize = (long long)CachedSizeResizedJPEG(tfilename); } else filesize = (long long)st.st_size; snprintf(response, sizeof(response), "%015lld", filesize); do_send(sock, response, 15); run_trigger(posttrigger, tfilename, sizeof(tfilename), NULL, 0); } static void handle_get(int sock, char *request, size_t reqsize, int fd) { char *filename; char *p1, *p2; off_t offset; size_t chunk; int lfd = -1; long long o; long c; char tfilename[PATH_MAX]; #if defined(USE_INTERNAL_SENDFILE) || defined(Linux) ssize_t sent; #else /* FreeBSD */ soff_t sent; #endif p1 = index(request, '/'); p2 = index(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid GET command \"%s\"", request); return; } // Special handling of pseudo files for playing ISO images / VIDEO_TS folders and audio playlists char *p3 = strrchr(request, '/'); if (p3) { *p3 = '\0'; char *p4 = strrchr(request, '/'); if (p4 && strncmp(p4+1, C_ANY_PSEUDOFOLDER_PREFIX, strlen(C_ANY_PSEUDOFOLDER_PREFIX)) == 0) { *p3 = '/'; // We temporarily had a '\0' here if (fd == -1) { handle_get_pseudofile(sock, request, reqsize, NULL); } else { log("Unexpected handle_get with fd=%d for request %s", fd, request); } return; } *p3 = '/'; // We temporarily had a '\0' here } // Normal (real file) sending follows *p2 = '\0'; filename = (char *) convert_charset(p1, cdt_from_client); if (sscanf(p2 + 1, "%lld %ld", &o, &c) != 2) { log("invalid GET parameters \"%s\"", p2 + 1); return; }; offset = o; chunk = c; int willResizeJPEGFile = will_resize_jpeg_file(filename); if (fd == -1) { int isSubtitleFile = list_contains_extension(subtitle_file_extensions, filename); if (isSubtitleFile) transform_subtitle_filename(filename); if (verify_path(filename) < 0) { log("access denied: %s", filename); return; } if (run_trigger(pretrigger, filename, reqsize - (p1 - request), tfilename, sizeof(tfilename)) < 0) { log("pretrigger %s failed", filename); return; } filename = tfilename; if (!willResizeJPEGFile) { if ((lfd = open(filename, O_RDONLY)) < 0) { if (!isSubtitleFile) log("open %s: %s", filename, strerror(errno)); return; } } fd = lfd; if (max_recent_files && !isSubtitleFile) store_recent(filename); } if (willResizeJPEGFile) { if (offset == 0 && picturecachesize && picturecachesize != 1) piccache_wait_if_precaching_this(filename); if ((sent = CachedSendResizedJPEG(sock, filename, offset, chunk)) < 0) { log("CachedSendResizedJPEG: %s", strerror(errno)); run_trigger(posttrigger, filename, reqsize - (p1 - request), NULL, 0); return; } } else { if (chunk == 0) { struct stat st; if (stat(filename, &st) < 0) { log("stat %s: %s", filename, strerror(errno)); if (lfd != -1) { run_trigger(posttrigger, filename, reqsize - (p1 - request), NULL, 0); close(lfd); } return; } chunk = st.st_size; } #ifdef USE_INTERNAL_SENDFILE if ((sent = my_internal_sendfile(sock, fd, &offset, chunk)) < 0) { #else #ifdef Linux if ((sent = sendfile(sock, fd, &offset, chunk)) < 0) { #else /* FreeBSD */ if (sendfile(fd, sock, offset, chunk, NULL, &sent, 0) < 0) { #endif #endif log("sendfile: %s", strerror(errno)); if (lfd != -1) { run_trigger(posttrigger, filename, reqsize - (p1 - request), NULL, 0); close(lfd); } return; } } /* The client asked for more data than we have, send padding bytes */ while (sent++ < chunk) write(sock, "\0", 1); if (lfd != -1 || willResizeJPEGFile) { run_trigger(posttrigger, filename, reqsize - (p1 - request), NULL, 0); close(lfd); } } static void handle_action1(int sock, char *request, size_t reqsize) { char *p1, *p2; int fd; int len; char tfilename[PATH_MAX]; p1 = index(request, '/'); p2 = rindex(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid ACTION command \"%s\"", request); return; } // Special handling of pseudo files for playing ISO images and VIDEO_TS folders char *p3 = strrchr(request, '/'); if (p3) { *p3 = '\0'; char *p4 = strrchr(request, '/'); if (p4 && strncmp(p4+1, C_ANY_PSEUDOFOLDER_PREFIX, strlen(C_ANY_PSEUDOFOLDER_PREFIX)) == 0) { *p3 = '/'; // We temporarily had a '\0' here handle_action1_pseudofile(sock, request, reqsize); return; } *p3 = '/'; // We temporarily had a '\0' here } // Normal (real file) ACTION handling follows *p2 = '\0'; char *path = (char *) convert_charset(p1, cdt_from_client); int isSubtitleFile = list_contains_extension(subtitle_file_extensions, path); if (isSubtitleFile) transform_subtitle_filename(path); if (verify_path(path) < 0) { log("access denied: %s", path); return; } if (run_trigger(pretrigger, path, reqsize - (p1 - request), tfilename, sizeof(tfilename)) < 0) { log("pretrigger %s failed", path); do_send(sock, "404", 3); return; } path = tfilename; if (!will_resize_jpeg_file(path)) { if ((fd = open(path, O_RDONLY)) < 0) { if (!isSubtitleFile) log("open %s: %s", path, strerror(errno)); do_send(sock, "404", 3); run_trigger(posttrigger, path, reqsize - (p1 - request), NULL, 0); return; } } else fd = -1; if (do_send(sock, "200", 3) < 0) { close(fd); run_trigger(posttrigger, path, reqsize - (p1 - request), NULL, 0); return; } if (max_recent_files && !isSubtitleFile) store_recent(path); while (1) { static char newreq[512]; char prevcmd = *newreq; if ((len = do_recv(sock, newreq, sizeof(newreq))) < 0) { if (fd != -1) close(fd); run_trigger(posttrigger, path, reqsize - (p1 - request), NULL, 0); if (prevcmd == 'G' && picturecachesize && picturecachesize != 1) precache_next_picture(path); // After last GET of current picture, try caching next one return; } transform_request_for_recent_file(newreq); if (strstr(newreq, "SIZE") == newreq) handle_size(sock, newreq, 512); else if (strstr(newreq, "GET") == newreq) handle_get(sock, newreq, 512, fd); else log("unknown ACTION command \"%s\"", newreq); } } void handle_request(int sock) { while (1) { char request[512]; int len; if ((len = do_recv(sock, request, sizeof(request))) < 0) { CachedDvdClose(); return; } // // ############# DEBUGGING - REMOVE BELOW!!!!! ########### 2006-10-22/VT // static int isFirstRequest = 1; // if (isFirstRequest) { // char userinputbuf[255]; // fputs("New process started. Press Enter to continue (after attaching debugger).", stderr); // fgets(userinputbuf, sizeof userinputbuf, stdin); // isFirstRequest = 0; // } // // ############# DEBUGGING - REMOVE ABOVE!!!!! ########### 2006-10-22/VT transform_request_for_recent_file(request); if (strstr(request, "LIST") == request) handle_list(sock, request, 512); else if (strstr(request, "ACTION 1") == request) handle_action1(sock, request, 512); else if (strstr(request, "ACTION 2") == request) handle_action1(sock, request, 512); else if (strstr(request, "SIZE") == request) handle_size(sock, request, 512); else if (strstr(request, "GET") == request) handle_get(sock, request, 512, -1); else log("unknown KiSS command \"%s\"", request); } } void handle_kmlrequest(int sock) { char request[512]; int len; char *ok1 = "\n\n\n\n"; char *error = "\n501 Method Not Implemented\n\n

Method Not Implemented

\n\n"; if ((len = do_recv(sock, request, sizeof(request))) < 0) return; if (strstr(request, "GET /index.kml") == request) { do_send(sock, ok1, strlen(ok1)); do_send(sock, kmlurl, strlen(kmlurl)); do_send(sock, ok2, strlen(ok2)); } else { do_send(sock, error, strlen(error)); log("unknown KiSS kml command \"%s\"", request); } } /* * Name: store_recent * * Description: Writes currently requested file to recently used * direcory list. */ static int store_recent(char *filename) { char recent[PATH_MAX]; char target[PATH_MAX]; char fpath[PATH_MAX]; char *thisname; int n, num_files, len, file_no; struct dirent **namelist; int isPseudoFile = strstr(filename, "/"C_DIR_PSEUDOFOLDER_PREFIX) || strstr(filename, "/"C_ISO_PSEUDOFOLDER_PREFIX); if (!strncmp(filename, audiopath, strlen(audiopath))) len = snprintf(recent, sizeof recent, "%s/.recent", audiopath); else if (!strncmp(filename, videopath, strlen(videopath))) len = snprintf(recent, sizeof recent, "%s/.recent", videopath); else if (!strncmp(filename, picturepath, strlen(picturepath))) len = snprintf(recent, sizeof recent, "%s/.recent", picturepath); else { log ("store_recent: Unknown base path in \"%s\"", filename); return -1; } if (len >= sizeof recent) { log ("store_recent: Buffer too small for filename \"%s\"", filename); return -1; } if (mkdir(recent, standard_dir_filemode) < 0) { if (errno != EEXIST) { log("store_recent: Couldn't create directory %s", recent); return -1; } } else if (chmod(recent, standard_dir_filemode) < 0) log("store_recent: cannot chmod directory %s: %s", recent, strerror(errno)); chdir(recent); /* scandir does not provide path info to sort */ num_files = scandir(recent, &namelist, 0, timesort); if (num_files < 0) log("store_recent: scandir(%s) failed with %s", recent, strerror(errno)); else { for (file_no = 1, n = 0; n < num_files; n++) { thisname = namelist[n]->d_name; /* skip parent and current dir */ if (!strcmp (thisname, ".") || !strcmp (thisname, "..")) { free(namelist[n]); continue; } /* delete excess files */ if (file_no >= max_recent_files) { logv("Delete oldest recent entry %s", thisname); snprintf(fpath, PATH_MAX, "%s/%s", recent, thisname); unlink(fpath); } free(namelist[n]); file_no++; } free(namelist); } if (!strncmp(filename, recent, strlen(recent))) if (isPseudoFile) { int fd, charsread; if ((fd = open(filename, O_RDONLY)) < 0) { log("store_recent: cannot open link file %s: %s", filename, strerror(errno)); return -1; } if ((charsread = read(fd, target, sizeof target)) < 0) log("store_recent: cannot read from link file %s: %s", filename, strerror(errno)); else *(target + charsread) = '\0'; close(fd); } else if ((n = readlink(filename, target, PATH_MAX)) < 0) { log("store_recent: cannot read symlink %s: %s", filename, strerror(errno)); return -1; } else *(target + n) = '\0'; else strncpy(target, filename, PATH_MAX); if (isPseudoFile) { *(strrchr(target, '/')) = '\0'; // Strip the title from a DVD entry strcat(target, strrchr(filename, '.')); // And add the ".mpg" extension instead } if ((len = snprintf(fpath, sizeof fpath, "%s/%s", recent, basename(target))) >= sizeof fpath) { log ("store_recent: Buffer too small for new recent filename \"%s\"", basename(target)); return -1; } /* make sure link is created anew */ unlink(fpath); if (isPseudoFile) { // Produce a link file, since a symlink cannot link to a pseudo-file int fd, written; if ((fd = open(fpath, O_CREAT | O_WRONLY | O_TRUNC, standard_filemode)) < 0) { log("store_recent: cannot create link file %s: %s", fpath, strerror(errno)); return -1; } if ((written = write(fd, filename, strlen(filename) + 1)) <= strlen(filename)) log("store_recent: cannot write to link file %s: %s", fpath, strerror(errno)); close(fd); } else if (symlink(target, fpath) < 0) { if (errno != EEXIST) { log("store_recent: cannot create symlink '%s' to '%s', error %s", fpath, target, strerror(errno)); return -1; } } return 0; } /// Simulate symlink behaviour for pseudo-files by replacing the filename in a request for /// a link file from the .recent directory with the filename that the link file links to. /// For picture files: replace filename in request with actual symlink target, so that the /// scaled picture file will be served from the cache if it has been cached. static int transform_request_for_recent_file(char *request) { char *p1, *p2, *p3; const char *pConverted; char rest[512]; p1 = strchr(request, '/'); p2 = strchr(request, '|'); if (p1 != NULL && p2 != NULL && p2 > p1) { if ((p3 = strstr(p1, "/.recent/"))) { if (!strncmp(p3 + 9, C_DIR_PSEUDOFOLDER_PREFIX, strlen(C_DIR_PSEUDOFOLDER_PREFIX)) || !strncmp(p3 + 9, C_ISO_PSEUDOFOLDER_PREFIX, strlen(C_ISO_PSEUDOFOLDER_PREFIX))) { int fd, charsread; strcpy(rest, p2); *p2 = '\0'; pConverted = convert_charset(p1, cdt_from_client); if ((fd = open(pConverted, O_RDONLY)) < 0) { log("transform_request_for_recent_file: cannot open link file %s: %s", pConverted, strerror(errno)); *p2 = '|'; return -1; } if ((charsread = read(fd, p1, 500 - (p1 - request))) < 0) log("transform_request_for_recent_file: cannot read from link file: %s", strerror(errno)); else { *(p1 + charsread) = '\0'; strcpy(p1, convert_charset(p1, cdt_to_client)); } close(fd); strcat(p1, rest); logv("transform_request_for_recent_file: Modified request = '%s'", request); } else if (!strncmp(p1, picturepath, strlen(picturepath))) { char target[PATH_MAX]; int n; strcpy(rest, p2); *p2 = '\0'; pConverted = convert_charset(p1, cdt_from_client); if ((n = readlink(pConverted, target, PATH_MAX)) > 0) { *(target + n) = '\0'; strcpy(p1, convert_charset(target, cdt_to_client)); strcat(p1, rest); logv("transform_request_for_recent_file: Modified request = '%s'", request); } else { *p2 = '|'; // Restore request to untouched state } } } } return 0; }