From bdc70c6561287ccc14986bce1031613e772c7d7e Mon Sep 17 00:00:00 2001 From: hunbernd Date: Wed, 4 Nov 2015 22:02:02 +0100 Subject: [PATCH 1/5] Allow arbitrary name in fstream url after hash. /fstream/fileshash/name Useful for creating file links that contains the original file name. --- libresapi/src/api/ApiServerMHD.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/libresapi/src/api/ApiServerMHD.cpp b/libresapi/src/api/ApiServerMHD.cpp index 898105b60..36d7a2c1b 100644 --- a/libresapi/src/api/ApiServerMHD.cpp +++ b/libresapi/src/api/ApiServerMHD.cpp @@ -274,12 +274,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); From ce4076079123be7a2dc6dd6a59872712875c78f6 Mon Sep 17 00:00:00 2001 From: hunbernd Date: Fri, 6 Nov 2015 22:05:35 +0100 Subject: [PATCH 2/5] Added support for all common content-types into MHDFilestreamerHandler File extension --> content-type associations are read from mime.types file. --- libresapi/src/api/ApiServerMHD.cpp | 10 ++++- libresapi/src/libresapi.pro | 6 ++- libresapi/src/util/ContentTypes.cpp | 63 +++++++++++++++++++++++++++++ libresapi/src/util/ContentTypes.h | 21 ++++++++++ 4 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 libresapi/src/util/ContentTypes.cpp create mode 100644 libresapi/src/util/ContentTypes.h diff --git a/libresapi/src/api/ApiServerMHD.cpp b/libresapi/src/api/ApiServerMHD.cpp index 36d7a2c1b..18afe449e 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 @@ -302,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::ContentTypes::cTypeFromExt(ext).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 d814219db..708515439 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..c8915e482 --- /dev/null +++ b/libresapi/src/util/ContentTypes.cpp @@ -0,0 +1,63 @@ +#include "ContentTypes.h" +#include + +RsMutex ContentTypes::ctmtx = RsMutex("CTMTX"); +std::map ContentTypes::cache; + +#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); + + //looking into the cache + std::map::iterator it; + it = cache.find(extension); + if (it != cache.end()) + { + std::cout << "Mime " + it->second + " for extension ." + extension + " 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(extension == ext) + { + std::string mime = line.substr(0, line.find_first_of("\t ")); + cache[extension] = mime; + std::cout << "Mime " + mime + " for extension ." + extension + " was found in mime.types" << std::endl; + return mime; + } + } + } + + //nothing found + std::cout << "Mime for " + extension + " was not found in " + filename + " falling back to " << DEFAULTCT << std::endl; + cache[extension] = DEFAULTCT; + return DEFAULTCT; +} + diff --git a/libresapi/src/util/ContentTypes.h b/libresapi/src/util/ContentTypes.h new file mode 100644 index 000000000..b0c5d0e65 --- /dev/null +++ b/libresapi/src/util/ContentTypes.h @@ -0,0 +1,21 @@ +#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; +}; + +#endif // CONTENTTYPES_H From 88e1dc006bcc28b393cd22d2be77d06574d611e8 Mon Sep 17 00:00:00 2001 From: hunbernd Date: Fri, 6 Nov 2015 22:51:52 +0100 Subject: [PATCH 3/5] Webui: added links on file names Browsers can play partial files with built-in player, or save files to disk. --- libresapi/src/webfiles/green-black.css | 4 ++++ libresapi/src/webfiles/gui.jsx | 2 +- libresapi/src/webui/green-black.css | 4 ++++ libresapi/src/webui/gui.jsx | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) 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..9bc47c471 100644 --- a/libresapi/src/webfiles/gui.jsx +++ b/libresapi/src/webfiles/gui.jsx @@ -571,7 +571,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 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..9bc47c471 100644 --- a/libresapi/src/webui/gui.jsx +++ b/libresapi/src/webui/gui.jsx @@ -571,7 +571,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 From b847eea57e7b482d5927ade73ad1602b7f5c213c Mon Sep 17 00:00:00 2001 From: hunbernd Date: Sat, 7 Nov 2015 00:33:37 +0100 Subject: [PATCH 4/5] Webui: Added video support into MediaPlayerWidget --- libresapi/src/webfiles/gui.jsx | 46 ++++++++++++++++++++++++++-------- libresapi/src/webui/gui.jsx | 46 ++++++++++++++++++++++++++-------- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/libresapi/src/webfiles/gui.jsx b/libresapi/src/webfiles/gui.jsx index 9bc47c471..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 =
; @@ -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/gui.jsx b/libresapi/src/webui/gui.jsx index 9bc47c471..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 =
; @@ -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}
; From 0ef9b22a7e636d605913962a7b4a0ec6b0378130 Mon Sep 17 00:00:00 2001 From: hunbernd Date: Sun, 8 Nov 2015 01:52:23 +0100 Subject: [PATCH 5/5] Some fixes: * using ContentTypes to resolve static files too * added some default content types, in the case of mime.types file not present * resolve extensions with upper case letters in them --- libresapi/src/api/ApiServerMHD.cpp | 23 +++-------------- libresapi/src/util/ContentTypes.cpp | 39 +++++++++++++++++++++++------ libresapi/src/util/ContentTypes.h | 2 ++ 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/libresapi/src/api/ApiServerMHD.cpp b/libresapi/src/api/ApiServerMHD.cpp index 18afe449e..2feed2d42 100644 --- a/libresapi/src/api/ApiServerMHD.cpp +++ b/libresapi/src/api/ApiServerMHD.cpp @@ -308,7 +308,7 @@ public: 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::ContentTypes::cTypeFromExt(ext).c_str()); + MHD_add_response_header(resp, "Content-Type", ContentTypes::cTypeFromExt(ext).c_str()); secure_queue_response(connection, MHD_HTTP_OK, resp); MHD_destroy_response(resp); @@ -650,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/util/ContentTypes.cpp b/libresapi/src/util/ContentTypes.cpp index c8915e482..7354e2b1f 100644 --- a/libresapi/src/util/ContentTypes.cpp +++ b/libresapi/src/util/ContentTypes.cpp @@ -1,8 +1,11 @@ #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 @@ -18,12 +21,18 @@ std::string ContentTypes::cTypeFromExt(const std::string &extension) 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(extension); + it = cache.find(extension2); if (it != cache.end()) { - std::cout << "Mime " + it->second + " for extension ." + extension + " was found in cache" << std::endl; + std::cout << "Mime " + it->second + " for extension ." + extension2 + " was found in cache" << std::endl; return it->second; } @@ -45,19 +54,35 @@ std::string ContentTypes::cTypeFromExt(const std::string &extension) else ext = line.substr(j+1, i-j-1); - if(extension == ext) + if(extension2 == ext) { std::string mime = line.substr(0, line.find_first_of("\t ")); - cache[extension] = mime; - std::cout << "Mime " + mime + " for extension ." + extension + " was found in mime.types" << std::endl; + 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 " + extension + " was not found in " + filename + " falling back to " << DEFAULTCT << std::endl; - cache[extension] = DEFAULTCT; + 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 index b0c5d0e65..0e6b71e73 100644 --- a/libresapi/src/util/ContentTypes.h +++ b/libresapi/src/util/ContentTypes.h @@ -16,6 +16,8 @@ private: static std::map cache; static RsMutex ctmtx; static const char* filename; + static bool inited; + static void addBasic(); }; #endif // CONTENTTYPES_H