/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd 0.11) * * 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 #ifdef USE_INTERNAL_SENDFILE #include #else #ifdef Linux #include #endif #endif #include "dvdread.h" #include "kissd.h" #include "sendfile.h" #include "playlist.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 " 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; if (v_opt) log("--> [%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]; snprintf(path, PATH_MAX, "/%s", p); clean_pathname(path); if (strstr(path, audiopath) == path || strstr(path, videopath) == path || strstr(path, picturepath) == path) return 0; 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. // 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 snprintf(realFullName, sizeof(realFullName), "%s%s", basepath, real_filename); // 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 NULL; } return dvd; } /// 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. // 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 snprintf(realFullName, sizeof(realFullName), "%s%s", basepath, real_filename); // 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 NULL; } return dvd; } /// 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, '/'); pFileName = pFileName? pFileName + 1 : path; if (!strncmp(pFileName, C_ANY_PSEUDOFOLDER_PREFIX, pseudoFolderPrefixLen)) { snprintf(fullpath, PATH_MAX, "%s/%s", base, path); 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); snprintf(fpath, sizeof fpath, "%s/%s", fullpath, titleName); snprintf(line, sizeof line, "%s|%s|0|\n", titleName, fpath); do_send(sock, line, strlen(line)); } } } 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); playlist_SendList(sock, realPlaylistPath); } } do_send(sock, "EOL\n", 4); } 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); } 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]; int i, n; 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; // 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; } snprintf(fullpath, PATH_MAX, "%s/%s", base, path); clean_pathname(fullpath); if (verify_path(fullpath) < 0) { log("access denied: %s", fullpath); } else if ((n = scandir(fullpath, &namelist, 0, my_filelist_compare)) < 0) { log("scandir %s: %s", fullpath, strerror(errno)); } else { // First, send directory names 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) { // Get the filetype so we can test whether this is a directory mode_t filetype = 0; snprintf(fpath, PATH_MAX, "%s/%s/%s", base, path, thisname); clean_pathname(fpath); 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) { enum playlist_type_t playlistType = !strcasecmp(ext, "M3U")? plt_m3u : !strcasecmp(ext, "PLS")? plt_pls : plt_unknown; const char *prefix = playlistType == plt_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 { 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 && !strcasecmp(ext, "iso")); 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 { 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]; snprintf(statpath, sizeof statpath, "%s/VIDEO_TS/VIDEO_TS.IFO", fpath); if (stat(statpath, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) { snprintf(statpath, sizeof statpath, "%s/VIDEO_TS/VIDEO_TS.BUP", fpath); if (stat(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; } } } } if (filetype == S_IFDIR || isPlayableIsoFile || isPlayablePlaylistFile) { // Send this directory name to the player snprintf(line, sizeof line, "%s|%s|1|\n", thisname, thisname); do_send(sock, line, strlen(line)); // Mark this entry as processed (the filename loop below will skip these) *(namelist[i]->d_name) = '\0'; } } } // 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 the media store (no config means list all files) if (list_this_entry && validExtensions && *validExtensions != '\0') { char *ext = strrchr(thisname, '.'); if (ext) { char extLower[32]; strncpy(extLower, ext + 1, sizeof(extLower)); *(extLower + sizeof(extLower) - 1) = '\0'; make_lowercase(extLower); if (!strstr(validExtensions, extLower)) list_this_entry = 0; } } if (list_this_entry) { // Send this file name to the player snprintf(fpath, PATH_MAX, "%s/%s/%s", base, path, thisname); clean_pathname(fpath); snprintf(line, sizeof line, "%s|%s|0|\n", thisname, fpath); do_send(sock, line, strlen(line)); } free(namelist[i]); } free(namelist); } 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'; if (strstr(request, "AUDIO") == request + 5) list_folder(sock, audiopath, p1 + 1, mst_audio); else if (strstr(request, "VIDEO") == request + 5) list_folder(sock, videopath, p1 + 1, mst_video); else if (strstr(request, "PICTURE") == request + 5) list_folder(sock, picturepath, p1 + 1, 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 = request + 5; if (verify_path(request + 5) < 0) { log("access denied for pseudo-file: %s", request + 5); 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 = p1; 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 ((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'; if (!(dvd = open_DVD_title_cached(p1))) { 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; } 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]; p1 = index(request, '|'); if (p1 == NULL) { log("invalid SIZE command \"%s\"", request); return; } *p1 = '\0'; if (verify_path(request + 5) < 0) { log("access denied: %s", request + 5); return; } if (run_trigger(pretrigger, request + 5, 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; } snprintf(response, sizeof(response), "%015lld", (long long)st.st_size); 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) playback follows *p2 = '\0'; filename = p1; if (sscanf(p2 + 1, "%lld %ld", &o, &c) != 2) { log("invalid GET parameters \"%s\"", p2 + 1); return; }; offset = o; chunk = c; if (fd == -1) { if (verify_path(filename) < 0) { log("access denied: %s", filename); return; } if (run_trigger(pretrigger, filename, reqsize - (filename - request), tfilename, sizeof(tfilename)) < 0) { log("pretrigger %s failed", filename); return; } filename = tfilename; if ((lfd = open(filename, O_RDONLY)) < 0) { log("open %s: %s", filename, strerror(errno)); return; } fd = lfd; } 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 - (filename - 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 - (filename - 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) { run_trigger(posttrigger, filename, reqsize - (filename - 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'; if (verify_path(p1) < 0) { log("access denied: %s", p1); return; } if (run_trigger(pretrigger, p1, reqsize - (p1 - request), tfilename, sizeof(tfilename)) < 0) { log("pretrigger %s failed", p1); do_send(sock, "404", 3); return; } p1 = tfilename; if ((fd = open(p1, O_RDONLY)) < 0) { log("open %s: %s", p1, strerror(errno)); do_send(sock, "404", 3); run_trigger(posttrigger, p1, reqsize - (p1 - request), NULL, 0); return; } if (do_send(sock, "200", 3) < 0) { close(fd); run_trigger(posttrigger, p1, reqsize - (p1 - request), NULL, 0); return; } while (1) { char newreq[512]; if ((len = do_recv(sock, newreq, sizeof(newreq))) < 0) { close(fd); run_trigger(posttrigger, p1, reqsize - (p1 - request), NULL, 0); return; } 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; } 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); } }