diff --git a/libresapi/src/api/ApiServerMHD.cpp b/libresapi/src/api/ApiServerMHD.cpp index 898105b60..2feed2d42 100644 --- a/libresapi/src/api/ApiServerMHD.cpp +++ b/libresapi/src/api/ApiServerMHD.cpp @@ -7,6 +7,7 @@ #include #include +#include "util/ContentTypes.h" // for filestreamer #include @@ -274,12 +275,21 @@ public: { sendMessage(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Error: rsFiles is null. Retroshare is probably not yet started."); return MHD_YES; - } - if(url[0] == 0 || (mHash=RsFileHash(url+strlen(FILESTREAMER_ENTRY_PATH))).isNull()) - { - sendMessage(connection, MHD_HTTP_NOT_FOUND, "Error: URL is not a valid file hash"); - return MHD_YES; - } + } + std::string urls(url); + urls = urls.substr(strlen(FILESTREAMER_ENTRY_PATH)); + size_t perpos = urls.find('/'); + if(perpos == std::string::npos){ + mHash = RsFileHash(urls); + }else{ + mHash = RsFileHash(urls.substr(0, perpos)); + } + if(urls.empty() || mHash.isNull()) + { + sendMessage(connection, MHD_HTTP_NOT_FOUND, "Error: URL is not a valid file hash"); + return MHD_YES; + } + FileInfo info; std::list dls; rsFiles->FileDownloads(dls); @@ -293,8 +303,13 @@ public: struct MHD_Response* resp = MHD_create_response_from_callback( mSize, 1024*1024, &contentReadercallback, this, NULL); - // only mp3 at the moment - MHD_add_response_header(resp, "Content-Type", "audio/mpeg3"); + // get content-type from extension + std::string ext = ""; + unsigned int i = info.fname.rfind('.'); + if(i != std::string::npos) + ext = info.fname.substr(i+1); + MHD_add_response_header(resp, "Content-Type", ContentTypes::cTypeFromExt(ext).c_str()); + secure_queue_response(connection, MHD_HTTP_OK, resp); MHD_destroy_response(resp); return MHD_YES; @@ -635,27 +650,10 @@ int ApiServerMHD::accessHandlerCallback(MHD_Connection *connection, { extension = filename[i] + extension; i--; - } - const char* type = 0; - if(extension == "html") - type = "text/html"; - else if(extension == "css") - type = "text/css"; - else if(extension == "js") - type = "text/javascript"; - else if(extension == "jsx") // react.js jsx files - type = "text/jsx"; - else if(extension == "png") - type = "image/png"; - else if(extension == "jpg" || extension == "jpeg") - type = "image/jpeg"; - else if(extension == "gif") - type = "image/gif"; - else - type = "application/octet-stream"; + }; struct MHD_Response* resp = MHD_create_response_from_fd(s.st_size, fd); - MHD_add_response_header(resp, "Content-Type", type); + MHD_add_response_header(resp, "Content-Type", ContentTypes::cTypeFromExt(extension).c_str()); secure_queue_response(connection, MHD_HTTP_OK, resp); MHD_destroy_response(resp); return MHD_YES; diff --git a/libresapi/src/libresapi.pro b/libresapi/src/libresapi.pro index 08dbfc6ac..9ad461ea2 100644 --- a/libresapi/src/libresapi.pro +++ b/libresapi/src/libresapi.pro @@ -59,7 +59,8 @@ SOURCES += \ api/GetPluginInterfaces.cpp \ api/ChatHandler.cpp \ api/LivereloadHandler.cpp \ - api/TmpBlobStore.cpp + api/TmpBlobStore.cpp \ + util/ContentTypes.cpp HEADERS += \ api/ApiServer.h \ @@ -82,4 +83,5 @@ HEADERS += \ api/GetPluginInterfaces.h \ api/ChatHandler.h \ api/LivereloadHandler.h \ - api/TmpBlobStore.h + api/TmpBlobStore.h \ + util/ContentTypes.h diff --git a/libresapi/src/util/ContentTypes.cpp b/libresapi/src/util/ContentTypes.cpp new file mode 100644 index 000000000..7354e2b1f --- /dev/null +++ b/libresapi/src/util/ContentTypes.cpp @@ -0,0 +1,88 @@ +#include "ContentTypes.h" +#include +#include +#include + +RsMutex ContentTypes::ctmtx = RsMutex("CTMTX"); +std::map ContentTypes::cache; +bool ContentTypes::inited = false; + +#ifdef WINDOWS_SYS + //Next to the executable + const char* ContentTypes::filename = ".\\mime.types"; +#else + const char* ContentTypes::filename = "/etc/mime.types"; +#endif + +std::string ContentTypes::cTypeFromExt(const std::string &extension) +{ + if(extension.empty()) + return DEFAULTCT; + + RsStackMutex mtx(ctmtx); + + if(!inited) + addBasic(); + + std::string extension2(extension); //lower case + std::transform(extension2.begin(), extension2.end(), extension2.begin(),::tolower); + + //looking into the cache + std::map::iterator it; + it = cache.find(extension2); + if (it != cache.end()) + { + std::cout << "Mime " + it->second + " for extension ." + extension2 + " was found in cache" << std::endl; + return it->second; + } + + //looking into mime.types + std::string line; + std::string ext; + std::ifstream file(filename); + while(getline(file, line)) + { + if(line.empty() || line[0] == '#') continue; + unsigned int i = line.find_first_of("\t "); + unsigned int j; + while(i != std::string::npos) //tokenize + { + j = i; + i = line.find_first_of("\t ", i+1); + if(i == std::string::npos) + ext = line.substr(j+1); + else + ext = line.substr(j+1, i-j-1); + + if(extension2 == ext) + { + std::string mime = line.substr(0, line.find_first_of("\t ")); + cache[extension2] = mime; + std::cout << "Mime " + mime + " for extension ." + extension2 + " was found in mime.types" << std::endl; + return mime; + } + } + } + + //nothing found + std::cout << "Mime for " + extension2 + " was not found in " + filename + " falling back to " << DEFAULTCT << std::endl; + cache[extension2] = DEFAULTCT; + return DEFAULTCT; +} + +//Add some basic content-types before first use. +//It keeps webui usable in the case of mime.types file not exists. +void ContentTypes::addBasic() +{ + inited = true; + + cache["html"] = "text/html"; + cache["css"] = "text/css"; + cache["js"] = "text/javascript"; + cache["jsx"] = "text/jsx"; + cache["png"] = "image/png"; + cache["jpg"] = "image/jpeg"; + cache["jpeg"] = "image/jpeg"; + cache["gif"] = "image/gif"; +} + diff --git a/libresapi/src/util/ContentTypes.h b/libresapi/src/util/ContentTypes.h new file mode 100644 index 000000000..0e6b71e73 --- /dev/null +++ b/libresapi/src/util/ContentTypes.h @@ -0,0 +1,23 @@ +#ifndef CONTENTTYPES_H +#define CONTENTTYPES_H + +#include +#include +#include + +#define DEFAULTCT "application/octet-stream" + +class ContentTypes +{ +public: + static std::string cTypeFromExt(const std::string& extension); + +private: + static std::map cache; + static RsMutex ctmtx; + static const char* filename; + static bool inited; + static void addBasic(); +}; + +#endif // CONTENTTYPES_H diff --git a/libresapi/src/webfiles/green-black.css b/libresapi/src/webfiles/green-black.css index 1d4e8da41..31778e201 100644 --- a/libresapi/src/webfiles/green-black.css +++ b/libresapi/src/webfiles/green-black.css @@ -66,6 +66,10 @@ td{ background-color: midnightblue; } +.filelink{ + color: inherit; +} + input, textarea{ color: lime; font-family: monospace; diff --git a/libresapi/src/webfiles/gui.jsx b/libresapi/src/webfiles/gui.jsx index b7445316b..9df49c877 100644 --- a/libresapi/src/webfiles/gui.jsx +++ b/libresapi/src/webfiles/gui.jsx @@ -4,7 +4,15 @@ RS.start(); var api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/api/v2/"; var filestreamer_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/fstream/"; -var upload_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/upload/"; +var upload_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/upload/"; + +var extensions = { + mp3: "audio", + ogg: "audio", + wav: "audio", + webm: "video", + mp4: "video" +}; // livereload function start_live_reload() @@ -558,7 +566,8 @@ var DownloadsWidget = React.createClass({ widget.emit("play_file", {name: file.name, hash: file.hash}) }; var playBtn =
; - if(file.name.slice(-3) === "mp3") + var splits = file.name.split("."); + if(splits[splits.length-1].toLowerCase() in extensions) playBtn =
play
; var ctrlBtn =
; @@ -571,7 +580,7 @@ var DownloadsWidget = React.createClass({ ctrlBtn =
pause
; } return( - {this.props.data.name} + {this.props.data.name} {makeFriendlyUnit(this.props.data.size)} {makeFriendlyUnit(this.props.data.transfer_rate*1e3)}/s @@ -673,7 +682,7 @@ var SearchWidget = React.createClass({ }, }); -var AudioPlayerWidget = React.createClass({ +var MediaPlayerWidget = React.createClass({ mixins: [SignalSlotMixin], getInitialState: function(){ return {file: undefined}; @@ -691,13 +700,28 @@ var AudioPlayerWidget = React.createClass({ } else { - return( -
-

{this.state.file.name}

- -
- ); + var splits = this.state.file.name.split("."); + var mediatype = extensions[splits[splits.length - 1].toLowerCase()]; + if (mediatype == "audio") { + return ( +
+

{this.state.file.name}

+ +
+ ); + } else if (mediatype == "video") { + return( +
+

{this.state.file.name}

+ +
+ ); + } else { + return(
); + } } }, }); @@ -1702,7 +1726,7 @@ var MainWidget = React.createClass({ } mainpage =
- + {mainpage}
; diff --git a/libresapi/src/webui/green-black.css b/libresapi/src/webui/green-black.css index 1d4e8da41..31778e201 100644 --- a/libresapi/src/webui/green-black.css +++ b/libresapi/src/webui/green-black.css @@ -66,6 +66,10 @@ td{ background-color: midnightblue; } +.filelink{ + color: inherit; +} + input, textarea{ color: lime; font-family: monospace; diff --git a/libresapi/src/webui/gui.jsx b/libresapi/src/webui/gui.jsx index b7445316b..9df49c877 100644 --- a/libresapi/src/webui/gui.jsx +++ b/libresapi/src/webui/gui.jsx @@ -4,7 +4,15 @@ RS.start(); var api_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/api/v2/"; var filestreamer_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/fstream/"; -var upload_url = window.location.protocol + "//" +window.location.hostname + ":" + window.location.port + "/upload/"; +var upload_url = window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/upload/"; + +var extensions = { + mp3: "audio", + ogg: "audio", + wav: "audio", + webm: "video", + mp4: "video" +}; // livereload function start_live_reload() @@ -558,7 +566,8 @@ var DownloadsWidget = React.createClass({ widget.emit("play_file", {name: file.name, hash: file.hash}) }; var playBtn =
; - if(file.name.slice(-3) === "mp3") + var splits = file.name.split("."); + if(splits[splits.length-1].toLowerCase() in extensions) playBtn =
play
; var ctrlBtn =
; @@ -571,7 +580,7 @@ var DownloadsWidget = React.createClass({ ctrlBtn =
pause
; } return( - {this.props.data.name} + {this.props.data.name} {makeFriendlyUnit(this.props.data.size)} {makeFriendlyUnit(this.props.data.transfer_rate*1e3)}/s @@ -673,7 +682,7 @@ var SearchWidget = React.createClass({ }, }); -var AudioPlayerWidget = React.createClass({ +var MediaPlayerWidget = React.createClass({ mixins: [SignalSlotMixin], getInitialState: function(){ return {file: undefined}; @@ -691,13 +700,28 @@ var AudioPlayerWidget = React.createClass({ } else { - return( -
-

{this.state.file.name}

- -
- ); + var splits = this.state.file.name.split("."); + var mediatype = extensions[splits[splits.length - 1].toLowerCase()]; + if (mediatype == "audio") { + return ( +
+

{this.state.file.name}

+ +
+ ); + } else if (mediatype == "video") { + return( +
+

{this.state.file.name}

+ +
+ ); + } else { + return(
); + } } }, }); @@ -1702,7 +1726,7 @@ var MainWidget = React.createClass({ } mainpage =
- + {mainpage}
;