diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 10415eba..4132afa7 100644 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -667,6 +667,12 @@ void installWebApi() { val["close"] = true; }); + API_REGIST(hook,on_http_access,{ + //能访问根目录10分钟 + val["path"] = "/"; + val["second"] = 10 * 60; + }); + } diff --git a/server/WebHook.cpp b/server/WebHook.cpp index f9f7fa84..4184c790 100644 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -38,6 +38,7 @@ #include "Http/HttpRequester.h" #include "Network/TcpSession.h" #include "Rtsp/RtspSession.h" +#include "Http/HttpSession.h" using namespace Json; using namespace toolkit; @@ -69,6 +70,7 @@ const char kOnStreamNotFound[] = HOOK_FIELD"on_stream_not_found"; const char kOnRecordMp4[] = HOOK_FIELD"on_record_mp4"; const char kOnShellLogin[] = HOOK_FIELD"on_shell_login"; const char kOnStreamNoneReader[] = HOOK_FIELD"on_stream_none_reader"; +const char kOnHttpAccess[] = HOOK_FIELD"on_http_access"; const char kAdminParams[] = HOOK_FIELD"admin_params"; onceToken token([](){ @@ -84,6 +86,7 @@ onceToken token([](){ mINI::Instance()[kOnRecordMp4] = "https://127.0.0.1/index/hook/on_record_mp4"; mINI::Instance()[kOnShellLogin] = "https://127.0.0.1/index/hook/on_shell_login"; mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader"; + mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access"; mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; },nullptr); }//namespace Hook @@ -188,6 +191,8 @@ void installWebHook(){ GET_CONFIG(string,hook_record_mp4,Hook::kOnRecordMp4); GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin); GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader); + GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess); + NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){ if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){ @@ -377,6 +382,37 @@ void installWebHook(){ }); + + NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ + if(!hook_enable || args._param_strs == hook_adminparams || hook_http_access.empty() ){ + //这种情况下随便访问,先让他随便访问1分钟,之后可能开启鉴权 + invoker("/",60); + return; + } + + ArgsType body; + body["ip"] = sender.get_peer_ip(); + body["port"] = sender.get_peer_port(); + body["id"] = sender.getIdentifier(); + body["path"] = path; + body["is_dir"] = is_dir; + body["params"] = parser.Params(); + body["content"] = parser.Content(); + for(auto &pr : parser.getValues()){ + body[string("header.") + pr.first] = pr.second; + } + //执行hook + do_http_hook(hook_http_access,body, [invoker](const Value &obj,const string &err){ + if(!err.empty()){ + //如果接口访问失败,那么10秒内该客户端都没有访问http服务器的权限 + invoker("",10); + return; + } + //path参数是该客户端能访问的根目录,该目录下的所有文件它都能访问 + //second参数规定该cookie超时时间,超过这个时间后,用户需要重新鉴权 + invoker(obj["path"].asString(),obj["second"].asInt()); + }); + }); } void unInstallWebHook(){ diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 2216639a..6bb6738d 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -57,6 +57,7 @@ namespace Broadcast { const char kBroadcastMediaChanged[] = "kBroadcastMediaChanged"; const char kBroadcastRecordMP4[] = "kBroadcastRecordMP4"; const char kBroadcastHttpRequest[] = "kBroadcastHttpRequest"; +const char kBroadcastHttpAccess[] = "kBroadcastHttpAccess"; const char kBroadcastOnGetRtspRealm[] = "kBroadcastOnGetRtspRealm"; const char kBroadcastOnRtspAuth[] = "kBroadcastOnRtspAuth"; const char kBroadcastMediaPlayed[] = "kBroadcastMediaPlayed"; diff --git a/src/Common/config.h b/src/Common/config.h index 0dc5639c..9c30021e 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -79,6 +79,10 @@ extern const char kBroadcastRecordMP4[]; extern const char kBroadcastHttpRequest[]; #define BroadcastHttpRequestArgs const Parser &parser,const HttpSession::HttpResponseInvoker &invoker,bool &consumed,TcpSession &sender +//收到http 访问文件或目录的广播 +extern const char kBroadcastHttpAccess[]; +#define BroadcastHttpAccessArgs const Parser &parser,const MediaInfo &args,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender + //该流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 extern const char kBroadcastOnGetRtspRealm[]; #define BroadcastOnGetRtspRealmArgs const MediaInfo &args,const RtspSession::onGetRealm &invoker,TcpSession &sender diff --git a/src/Http/CookieManager.cpp b/src/Http/CookieManager.cpp new file mode 100644 index 00000000..0ee38186 --- /dev/null +++ b/src/Http/CookieManager.cpp @@ -0,0 +1,279 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "Util/util.h" +#include "Util/MD5.h" +#include "Common/config.h" +#include "CookieManager.h" + +//////////////////////////////CookieData//////////////////////////////////// +CookieData::CookieData(const CookieManager::Ptr &manager, + const string &cookie, + const string &uid, + uint64_t max_elapsed, + const string &path){ + _uid = uid; + _max_elapsed = max_elapsed; + _cookie_uuid = cookie; + _path = path; + _manager = manager; + manager->onAddCookie(path,uid,cookie); +} + +CookieData::~CookieData() { + auto strongManager = _manager.lock(); + if(strongManager){ + strongManager->onDelCookie(_path,_uid,_cookie_uuid); + } +} +string CookieData::getCookie(const string &cookie_name) const { + return (StrPrinter << cookie_name << "=" << _cookie_uuid << ";expires=" << cookieExpireTime() << ";path=" << _path); +} + +bool CookieData::isExpired() { + return _ticker.elapsedTime() > _max_elapsed * 1000; +} + +void CookieData::updateTime() { + _ticker.resetTime(); +} +const string & CookieData::getUid() const{ + return _uid; +} +string CookieData::cookieExpireTime() const{ + char buf[64]; + time_t tt = time(NULL) + _max_elapsed; + strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt)); + return buf; +} + +const string& CookieData::getCookie() const { + return _cookie_uuid; +} + +const string& CookieData::getPath() const{ + return _path; +} + +//////////////////////////////CookieManager//////////////////////////////////// +INSTANCE_IMP(CookieManager); + +CookieData::Ptr CookieManager::addCookie(const string &uidIn, int max_client ,uint64_t max_elapsed, const string &path) { + lock_guard lck(_mtx_cookie); + auto cookie = _geneator.obtain(); + auto uid = uidIn.empty() ? cookie : uidIn; + auto oldCookie = getOldestCookie(uid, max_client , path); + if(!oldCookie.empty()){ + //假如该账号已经登录了,那么删除老的cookie。 + //目的是实现单账号多地登录时挤占登录 + delCookie(oldCookie,path); + } + CookieData::Ptr data(new CookieData(shared_from_this(),cookie,uid,max_elapsed,path)); + //保存该账号下的新cookie + _map_cookie[path][cookie] = data; + return data; +} + +bool CookieManager::delCookie(const CookieData::Ptr &cookie) { + if(!cookie){ + return false; + } + return delCookie(cookie->getPath(),cookie->getCookie()); +} + +bool CookieManager::delCookie(const string &path , const string &cookie) { + lock_guard lck(_mtx_cookie); + auto it = _map_cookie.find(path); + if(it == _map_cookie.end()){ + return false; + } + return it->second.erase(cookie); +} + +CookieData::Ptr CookieManager::getCookie(const string &cookie, const string &path) { + lock_guard lck(_mtx_cookie); + auto it_path = _map_cookie.find(path); + if(it_path == _map_cookie.end()){ + //该path下没有任何cookie + return nullptr; + } + auto it_cookie = it_path->second.find(cookie); + if(it_cookie == it_path->second.end()){ + //该path下没有对应的cookie + return nullptr; + } + if(it_cookie->second->isExpired()){ + //cookie过期 + it_path->second.erase(it_cookie); + return nullptr; + } + return it_cookie->second; +} + +CookieData::Ptr CookieManager::getCookie(const StrCaseMap &http_header, const string &cookie_name, const string &path) { + auto it = http_header.find("Cookie"); + if (it == http_header.end()) { + return nullptr; + } + auto cookie = FindField(it->second.data(), (cookie_name + "=").data(), ";"); + if (!cookie.size()) { + cookie = FindField(it->second.data(), (cookie_name + "=").data(), nullptr); + } + if(cookie.empty()){ + return nullptr; + } + return CookieManager::Instance().getCookie(cookie, path); +} + +CookieManager::CookieManager() { + //定时删除过期的cookie,防止内存膨胀 + _timer = std::make_shared(10,[this](){ + onManager(); + return true; + }, nullptr); +} + +CookieManager::~CookieManager() { + _timer.reset(); +} + +void CookieManager::onManager() { + lock_guard lck(_mtx_cookie); + //先遍历所有path + for(auto it_path = _map_cookie.begin() ; it_path != _map_cookie.end() ;){ + //再遍历该path下的所有cookie + for (auto it_cookie = it_path->second.begin() ; it_cookie != it_path->second.end() ; ){ + if(it_cookie->second->isExpired()){ + //cookie过期,移除记录 + WarnL << it_cookie->second->getUid() << " cookie过期"; + it_cookie = it_path->second.erase(it_cookie); + continue; + } + ++it_cookie; + } + + if(it_path->second.empty()){ + //该path下没有任何cooki记录,移除之 + WarnL << "该path下没有任何cooki记录:" << it_path->first; + it_path = _map_cookie.erase(it_path); + continue; + } + ++it_path; + } +} + +void CookieManager::onAddCookie(const string &path,const string &uid,const string &cookie){ + //添加新的cookie,我们记录下这个uid下有哪些cookie,目的是实现单账号多地登录时挤占登录 + lock_guard lck(_mtx_cookie); + //相同用户下可以存在多个cookie(意味多地登录),这些cookie根据登录时间的早晚依次排序 + _map_uid_to_cookie[path][uid][getCurrentMillisecond()] = cookie; +} +void CookieManager::onDelCookie(const string &path,const string &uid,const string &cookie){ + lock_guard lck(_mtx_cookie); + //回收随机字符串 + _geneator.release(cookie); + + auto it_path = _map_uid_to_cookie.find(path); + if(it_path == _map_uid_to_cookie.end()){ + //该path下未有任意用户登录 + return; + } + auto it_uid = it_path->second.find(uid); + if(it_uid == it_path->second.end()){ + //该用户尚未登录 + return; + } + + //遍历同一名用户下的所有客户端,移除命中的客户端 + for(auto it_cookie = it_uid->second.begin() ; it_cookie != it_uid->second.end() ; ++it_cookie ){ + if(it_cookie->second != cookie) { + //不是该cookie + continue; + } + //移除该用户名下的某个cookie,这个设备cookie将失效 + it_uid->second.erase(it_cookie); + + if(it_uid->second.size() != 0) { + break; + } + + //该用户名下没有任何设备在线,移除之 + it_path->second.erase(it_uid); + + if(it_path->second.size() != 0) { + break; + } + //该path下未有任何用户在线,移除之 + _map_uid_to_cookie.erase(it_path); + break; + } + +} + +string CookieManager::getOldestCookie(const string &uid, int max_client,const string &path){ + lock_guard lck(_mtx_cookie); + auto it_path = _map_uid_to_cookie.find(path); + if(it_path == _map_uid_to_cookie.end()){ + //该路径下未有任意cookie + return ""; + } + auto it_uid = it_path->second.find(uid); + if(it_uid == it_path->second.end()){ + //该用户从未登录过 + return ""; + } + if(it_uid->second.size() < MAX(1,max_client)){ + //同一名用户下,客户端个数还没达到限制个数 + return ""; + } + //客户端个数超过限制,移除最先登录的客户端 + return it_uid->second.begin()->second; +} + +/////////////////////////////////CookieGeneator//////////////////////////////////// +string CookieGeneator::obtain(){ + //获取唯一的防膨胀的随机字符串 + while (true){ + auto str = obtain_l(); + if(_obtained.find(str) == _obtained.end()){ + //没有重复 + _obtained.emplace(str); + return str; + } + } +} +void CookieGeneator::release(const string &str){ + //从防膨胀库中移除 + _obtained.erase(str); +} + +string CookieGeneator::obtain_l(){ + //12个伪随机字节 + 4个递增的整形字节,然后md5即为随机字符串 + auto str = makeRandStr(12,false); + str.append((char *)&_index, sizeof(_index)); + ++_index; + return MD5(str).hexdigest(); +} \ No newline at end of file diff --git a/src/Http/CookieManager.h b/src/Http/CookieManager.h new file mode 100644 index 00000000..dc86782c --- /dev/null +++ b/src/Http/CookieManager.h @@ -0,0 +1,232 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SRC_HTTP_COOKIEMANAGER_H +#define SRC_HTTP_COOKIEMANAGER_H + +#include +#include +#include "Util/mini.h" +#include "Util/TimeTicker.h" +#include "Network/Socket.h" +#include "Rtsp/Rtsp.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +#define COOKIE_DEFAULT_LIFE (7 * 24 * 60 * 60) + +class CookieManager; + +/** + * cookie对象,用于保存cookie的一些相关属性 + */ +class CookieData : public mINI , public noncopyable{ +public: + typedef std::shared_ptr Ptr; + /** + * 构建cookie + * @param manager cookie管理者对象 + * @param cookie cookie随机字符串 + * @param uid 用户唯一id + * @param max_elapsed 最大过期时间,单位秒 + * @param path http路径,譬如/index/files/ + */ + CookieData(const std::shared_ptr &manager,const string &cookie, + const string &uid,uint64_t max_elapsed,const string &path); + ~CookieData() ; + + /** + * 获取uid + * @return uid + */ + const string &getUid() const; + + /** + * 获取http中Set-Cookie字段的值 + * @param cookie_name 该cookie的名称,譬如 MY_SESSION + * @return 例如 MY_SESSION=XXXXXX;expires=Wed, Jun 12 2019 06:30:48 GMT;path=/index/files/ + */ + string getCookie(const string &cookie_name) const; + + /** + * 获取cookie随机字符串 + * @return cookie随机字符串 + */ + const string& getCookie() const; + + /** + * 获取该cookie对应的path + * @return + */ + const string& getPath() const; + + /** + * 更新该cookie的过期时间,可以让此cookie不失效 + */ + void updateTime(); + + /** + * 判断该cookie是否过期 + * @return + */ + bool isExpired(); +private: + string cookieExpireTime() const ; +private: + string _uid; + string _path; + string _cookie_uuid; + uint64_t _max_elapsed; + Ticker _ticker; + std::weak_ptr _manager; +}; + +/** + * cookie随机字符串生成器 + */ +class CookieGeneator{ +public: + CookieGeneator() = default; + ~CookieGeneator() = default; + + /** + * 获取不碰撞的随机字符串 + * @return 随机字符串 + */ + string obtain(); + + /** + * 释放随机字符串 + * @param str 随机字符串 + */ + void release(const string &str); +private: + string obtain_l(); +private: + //碰撞库 + unordered_set _obtained; + //增长index,防止碰撞用 + int _index = 0; +}; + +/** + * cookie管理器,用于管理cookie的生成以及过期管理,同时实现了同账号异地挤占登录功能 + * 该对象实现了同账号最多登录若干个设备 + */ +class CookieManager : public std::enable_shared_from_this { +public: + typedef std::shared_ptr Ptr; + friend class CookieData; + ~CookieManager(); + + /** + * 获取单例 + */ + static CookieManager &Instance(); + + /** + * 添加cookie + * @param uid 用户id,如果为空则为匿名登录 + * @param max_client 该账号最多登录多少个设备 + * @param max_elapsed 该cookie过期时间,单位秒 + * @param path 该cookie对应的http路径 + * @return cookie对象 + */ + CookieData::Ptr addCookie(const string &uid, int max_client , uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,const string &path = "/" ); + + /** + * 根据cookie随机字符串查找cookie对象 + * @param cookie cookie随机字符串 + * @param path 该cookie对应的http路径 + * @return cookie对象,可以为nullptr + */ + CookieData::Ptr getCookie(const string &cookie,const string &path = "/"); + + /** + * 从http头中获取cookie对象 + * @param http_header http头 + * @param cookie_name cookie名 + * @param path http路径 + * @return cookie对象 + */ + CookieData::Ptr getCookie(const StrCaseMap &http_header,const string &cookie_name , const string &path = "/"); + + /** + * 删除cookie,用户登出时使用 + * @param cookie cookie对象,可以为nullptr + * @return + */ + bool delCookie(const CookieData::Ptr &cookie); + + + /** + * 获取某用户名下最先登录时的cookie,目的是实现某用户下最多登录若干个设备 + * @param path http路径 + * @param uid 用户id + * @param max_client 最多登录的设备个数 + * @return 最早的cookie随机字符串 + */ + string getOldestCookie( const string &uid, int max_client ,const string &path = "/"); +private: + CookieManager(); + void onManager(); + /** + * 构造cookie对象时触发,目的是记录某账号下多个cookie + * @param path http路径 + * @param uid 用户id + * @param cookie cookie随机字符串 + */ + void onAddCookie(const string &path,const string &uid,const string &cookie); + + /** + * 析构cookie对象时触发 + * @param path http路径 + * @param uid 用户id + * @param cookie cookie随机字符串 + */ + void onDelCookie(const string &path,const string &uid,const string &cookie); + + /** + * 删除cookie + * @param path http路径 + * @param cookie cookie随机字符串 + * @return 成功true + */ + bool delCookie(const string &path,const string &cookie); +private: + unordered_map >_map_cookie; + unordered_map > >_map_uid_to_cookie; + recursive_mutex _mtx_cookie; + Timer::Ptr _timer; + CookieGeneator _geneator; +}; + + + + +#endif //SRC_HTTP_COOKIEMANAGER_H diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index b0189dfe..7720327e 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -49,6 +49,12 @@ using namespace toolkit; namespace mediakit { static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE; +static const string kCookieName = "ZL_COOKIE"; +static const string kAccessPathKey = "kAccessPathKey"; +static int kMaxClientPerUid = 1; +static const string kAccessDirUnauthorized = "你没有权限访问该目录"; +static const string kAccessFileUnauthorized = "你没有权限访问该文件"; + string dateStr() { char buf[64]; @@ -315,6 +321,81 @@ inline static string findIndexFile(const string &dir){ return ""; } +inline string HttpSession::getClientUid(){ + //该ip端口只能有一个cookie,不能重复获取cookie, + //目的是为了防止我们让客户端设置cookie,但是客户端不支持cookie导致一直重复生成cookie + //判断是否为同一个用户还可以根据url相关字段,但是这个跟具体业务逻辑相关,在这里不便实现 + //如果一个http客户端不支持cookie并且一直变换端口号,那么会导致服务器无法追踪该用户,从而导致一直触发事件并且一直生成cookie + return StrPrinter << get_peer_ip() << ":" << get_peer_port(); +} +inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function &callback_in){ + auto path = path_in; + replace(const_cast(path),"//","/"); + + auto callback = [callback_in,this](bool canAccess,const CookieData::Ptr &cookie){ + try { + callback_in(canAccess,cookie); + }catch (SockException &ex){ + if(ex){ + shutdown(ex); + } + }catch (exception &ex){ + shutdown(SockException(Err_shutdown,ex.what())); + } + }; + + //根据http头中的cookie字段获取cookie + auto cookie = CookieManager::Instance().getCookie(_parser.getValues(), kCookieName); + if (cookie) { + //判断该用户是否有权限访问该目录,并且不再设置客户端cookie + callback(!(*cookie)[kAccessPathKey].empty() && path.find((*cookie)[kAccessPathKey]) == 0, nullptr); + return; + } + + //根据该用户的用户名获取cookie + string uid = getClientUid(); + auto cookie_str = CookieManager::Instance().getOldestCookie(uid, kMaxClientPerUid); + if(!cookie_str.empty()){ + //该用户已经登录过了,但是它(http客户端)貌似不支持cookie,所以我们只能通过它的用户名获取cookie + cookie = CookieManager::Instance().getCookie(cookie_str); + if (cookie) { + //判断该用户是否有权限访问该目录,并且不再设置客户端cookie + callback(!(*cookie)[kAccessPathKey].empty() && path.find((*cookie)[kAccessPathKey]) == 0, nullptr); + return; + } + } + + //该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录 + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + HttpAccessPathInvoker accessPathInvoker = [weakSelf, callback, uid , path] (const string &accessPath, int cookieLifeSecond) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + //自己已经销毁 + return; + } + strongSelf->async([weakSelf, callback, accessPath, cookieLifeSecond, uid , path]() { + //切换到自己线程 + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + //自己已经销毁 + return; + } + //我们给用户生成追踪cookie + auto cookie = CookieManager::Instance().addCookie(uid, kMaxClientPerUid, cookieLifeSecond); + //记录用户能访问的路径 + (*cookie)[kAccessPathKey] = accessPath; + //判断该用户是否有权限访问该目录,并且设置客户端cookie + callback(!accessPath.empty() && path.find(accessPath) == 0, cookie); + }); + }; + + bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this); + if(!flag){ + //此事件无人监听,我们默认都有权限访问 + callback(true, nullptr); + } + +} inline void HttpSession::Handle_Req_GET(int64_t &content_len) { //先看看是否为WebSocket请求 @@ -357,17 +438,30 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) { if(!indexFile.empty()){ //发现该文件夹下有index文件 strFile = strFile + "/" + indexFile; + _parser.setUrl(_parser.Url() + "/" + indexFile); break; } - //生成文件夹菜单索引 string strMeun; + //生成文件夹菜单索引 if (!makeMeun(_parser.Url(),strFile,strMeun)) { //文件夹不存在 sendNotFound(bClose); throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on folder"); } - sendResponse("200 OK", makeHttpHeader(bClose,strMeun.size() ), strMeun); - throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 200 ok on folder"); + + //判断是否有权限访问该目录 + canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun](bool canAccess,const CookieData::Ptr &cookie){ + if(!canAccess){ + const_cast(strMeun) = kAccessDirUnauthorized; + } + auto headerOut = makeHttpHeader(bClose,strMeun.size()); + if(cookie){ + headerOut["Set-Cookie"] = cookie->getCookie(kCookieName); + } + sendResponse(canAccess ? "200 OK" : "401 Unauthorized" , headerOut, strMeun); + throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder"); + }); + return; } }while(0); @@ -391,97 +485,106 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) { throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on open file failed"); } - //判断是不是分节下载 - auto &strRange = _parser["Range"]; - int64_t iRangeStart = 0, iRangeEnd = 0; - iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); - iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data()); - if (iRangeEnd == 0) { - iRangeEnd = tFileStat.st_size - 1; - } - const char *pcHttpResult = NULL; - if (strRange.size() == 0) { - //全部下载 - pcHttpResult = "200 OK"; - } else { - //分节下载 - pcHttpResult = "206 Partial Content"; - fseek(pFilePtr.get(), iRangeStart, SEEK_SET); - } - auto httpHeader=makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data())); - if (strRange.size() != 0) { - //分节下载返回Content-Range头 - httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl); - } - auto Origin = _parser["Origin"]; - if(!Origin.empty()){ - httpHeader["Access-Control-Allow-Origin"] = Origin; - httpHeader["Access-Control-Allow-Credentials"] = "true"; - } - //先回复HTTP头部分 - sendResponse(pcHttpResult, httpHeader, ""); - if (iRangeEnd - iRangeStart < 0) { - //文件是空的! - throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send file partial range excpted"); - } - //回复Content部分 - std::shared_ptr piLeft(new int64_t(iRangeEnd - iRangeStart + 1)); + auto parser = _parser; + //判断是否有权限访问该文件 + canAccessPath(_parser.Url(),false,[this,parser,tFileStat,pFilePtr,bClose,strFile](bool canAccess,const CookieData::Ptr &cookie){ + //判断是不是分节下载 + auto &strRange = parser["Range"]; + int64_t iRangeStart = 0, iRangeEnd = 0; + iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); + iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data()); + if (iRangeEnd == 0) { + iRangeEnd = tFileStat.st_size - 1; + } + const char *pcHttpResult = NULL; + if (strRange.size() == 0) { + //全部下载 + pcHttpResult = "200 OK"; + } else { + //分节下载 + pcHttpResult = "206 Partial Content"; + fseek(pFilePtr.get(), iRangeStart, SEEK_SET); + } + auto httpHeader = canAccess ? makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data())) + : makeHttpHeader(bClose, kAccessFileUnauthorized.size()); + if (strRange.size() != 0) { + //分节下载返回Content-Range头 + httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl); + } + auto Origin = parser["Origin"]; + if(!Origin.empty()){ + httpHeader["Access-Control-Allow-Origin"] = Origin; + httpHeader["Access-Control-Allow-Credentials"] = "true"; + } - GET_CONFIG(uint32_t,sendBufSize,Http::kSendBufSize); + if(cookie){ + httpHeader["Set-Cookie"] = cookie->getCookie(kCookieName); + } + //先回复HTTP头部分 + sendResponse(canAccess ? pcHttpResult : "401 Unauthorized" , httpHeader,canAccess ? "" : kAccessFileUnauthorized); + if (!canAccess || iRangeEnd - iRangeStart < 0) { + //文件是空的! + throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file"); + } + //回复Content部分 + std::shared_ptr piLeft(new int64_t(iRangeEnd - iRangeStart + 1)); - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - auto onFlush = [pFilePtr,bClose,weakSelf,piLeft]() { - TimeTicker(); - auto strongSelf = weakSelf.lock(); - while(*piLeft && strongSelf){ - //更新超时定时器 - strongSelf->_ticker.resetTime(); - //从循环池获取一个内存片 - auto sendBuf = strongSelf->obtainBuffer(); - sendBuf->setCapacity(sendBufSize); - //本次需要读取文件字节数 - int64_t iReq = MIN(sendBufSize,*piLeft); - //读文件 - int iRead; - do{ - iRead = fread(sendBuf->data(), 1, iReq, pFilePtr.get()); - }while(-1 == iRead && UV_EINTR == get_uv_error(false)); - //文件剩余字节数 - *piLeft -= iRead; + GET_CONFIG(uint32_t,sendBufSize,Http::kSendBufSize); - if (iRead < iReq || !*piLeft) { - //文件读完 - if(iRead>0) { - sendBuf->setSize(iRead); - strongSelf->send(sendBuf); - } - if(bClose) { - strongSelf->shutdown(SockException(Err_shutdown,"read file eof")); - } - return false; - } - //文件还未读完 - sendBuf->setSize(iRead); - int iSent = strongSelf->send(sendBuf); - if(iSent == -1) { - //套机制销毁 - return false; - } - if(strongSelf->isSocketBusy()){ - //套接字忙,那么停止继续写 - return true; + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + auto onFlush = [pFilePtr,bClose,weakSelf,piLeft]() { + TimeTicker(); + auto strongSelf = weakSelf.lock(); + while(*piLeft && strongSelf){ + //更新超时定时器 + strongSelf->_ticker.resetTime(); + //从循环池获取一个内存片 + auto sendBuf = strongSelf->obtainBuffer(); + sendBuf->setCapacity(sendBufSize); + //本次需要读取文件字节数 + int64_t iReq = MIN(sendBufSize,*piLeft); + //读文件 + int iRead; + do{ + iRead = fread(sendBuf->data(), 1, iReq, pFilePtr.get()); + }while(-1 == iRead && UV_EINTR == get_uv_error(false)); + //文件剩余字节数 + *piLeft -= iRead; + + if (iRead < iReq || !*piLeft) { + //文件读完 + if(iRead>0) { + sendBuf->setSize(iRead); + strongSelf->send(sendBuf); + } + if(bClose) { + strongSelf->shutdown(SockException(Err_shutdown,"read file eof")); + } + return false; + } + //文件还未读完 + sendBuf->setSize(iRead); + int iSent = strongSelf->send(sendBuf); + if(iSent == -1) { + //套机制销毁 + return false; + } + if(strongSelf->isSocketBusy()){ + //套接字忙,那么停止继续写 + return true; + } + //继续写套接字 } - //继续写套接字 - } - return false; - }; - //关闭tcp_nodelay ,优化性能 - SockUtil::setNoDelay(_sock->rawFD(),false); - //设置MSG_MORE,优化性能 - (*this) << SocketFlags(kSockFlags); + return false; + }; + //关闭tcp_nodelay ,优化性能 + SockUtil::setNoDelay(_sock->rawFD(),false); + //设置MSG_MORE,优化性能 + (*this) << SocketFlags(kSockFlags); - onFlush(); - _sock->setOnFlush(onFlush); + onFlush(); + _sock->setOnFlush(onFlush); + }); } inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) { diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index df9039fb..0b050d69 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -35,6 +35,7 @@ #include "RtmpMuxer/FlvMuxer.h" #include "HttpRequestSplitter.h" #include "WebSocketSplitter.h" +#include "CookieManager.h" using namespace std; using namespace toolkit; @@ -51,6 +52,8 @@ public: const KeyValue &headerOut, const string &contentOut)> HttpResponseInvoker; + typedef std::function HttpAccessPathInvoker; + HttpSession(const Socket::Ptr &pSock); virtual ~HttpSession(); @@ -109,6 +112,17 @@ private: const string &codeOut, const KeyValue &headerOut, const string &contentOut); + + /** + * 是否有权限访问某目录或文件 + * @param path 文件或目录 + * @param is_dir path是否为目录 + * @param callback 有权限或无权限的回调 + */ + inline void canAccessPath(const string &path,bool is_dir,const function &callback); + + //获取用户唯一识别id,我们默认为ip+端口号 + inline string getClientUid(); private: Parser _parser; Ticker _ticker; diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index f0ef4e65..2c7ffb4d 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -145,7 +145,8 @@ public: auto args_pos = _strFullUrl.find('?'); if (args_pos != string::npos) { _strUrl = _strFullUrl.substr(0, args_pos); - _mapUrlArgs = parseArgs(_strFullUrl.substr(args_pos + 1)); + _params = _strFullUrl.substr(args_pos + 1); + _mapUrlArgs = parseArgs(_params); } else { _strUrl = _strFullUrl; } @@ -202,11 +203,15 @@ public: _strMethod.clear(); _strUrl.clear(); _strFullUrl.clear(); + _params.clear(); _strTail.clear(); _strContent.clear(); _mapHeaders.clear(); _mapUrlArgs.clear(); } + const string &Params() const { + return _params; + } void setUrl(const string &url) { this->_strUrl = url; @@ -242,6 +247,7 @@ private: string _strContent; string _strNull; string _strFullUrl; + string _params; mutable StrCaseMap _mapHeaders; mutable StrCaseMap _mapUrlArgs; };