diff --git a/code/nel/include/nel/gui/curl_certificates.h b/code/nel/include/nel/gui/curl_certificates.h new file mode 100644 index 000000000..021b13360 --- /dev/null +++ b/code/nel/include/nel/gui/curl_certificates.h @@ -0,0 +1,35 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef CL_CURL_CERTIFICATES_HTML_H +#define CL_CURL_CERTIFICATES_HTML_H + +#include + +#include "nel/misc/types_nl.h" + +namespace NLGUI +{ +#if defined(NL_OS_WINDOWS) + class CCurlCertificates { + public: + // cURL SSL certificate loading + static CURLcode sslCtxFunction(CURL *curl, void *sslctx, void *parm); + }; +#endif // NL_OS_WINDOWS + +} // namespace +#endif diff --git a/code/nel/include/nel/gui/group_editbox.h b/code/nel/include/nel/gui/group_editbox.h index 771bdc482..14d9d55a5 100644 --- a/code/nel/include/nel/gui/group_editbox.h +++ b/code/nel/include/nel/gui/group_editbox.h @@ -264,6 +264,7 @@ namespace NLGUI // because of multiline, thz parent container will be moved to top // The good position can be restored by a press on enter then bool _WantReturn : 1; // Want return char, don't call the enter action handler + bool _ClearOnEscape : 1; // clear content when ESC is pressed? bool _Savable : 1; // should content be saved ? bool _DefaultInputString : 1; // Is the current input string the default one (should not be edited) bool _Frozen : 1; // is the control frozen? (cannot edit in it) diff --git a/code/nel/include/nel/gui/group_html.h b/code/nel/include/nel/gui/group_html.h index da90095a3..969eac1f7 100644 --- a/code/nel/include/nel/gui/group_html.h +++ b/code/nel/include/nel/gui/group_html.h @@ -793,7 +793,7 @@ namespace NLGUI void doBrowseLocalFile(const std::string &filename); // load remote content using either GET or POST - void doBrowseRemoteUrl(const std::string &url, const std::string &referer, bool doPost = false, const SFormFields &formfields = SFormFields()); + void doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost = false, const SFormFields &formfields = SFormFields()); // render html string as new browser page bool renderHtmlString(const std::string &html); @@ -861,7 +861,7 @@ namespace NLGUI // BnpDownload system void initBnpDownload(); void checkBnpDownload(); - bool addBnpDownload(const std::string &url, const std::string &action, const std::string &script, const std::string &md5sum); + bool addBnpDownload(std::string url, const std::string &action, const std::string &script, const std::string &md5sum); std::string localBnpName(const std::string &url); void releaseDownloads(); diff --git a/code/nel/include/nel/gui/http_hsts.h b/code/nel/include/nel/gui/http_hsts.h new file mode 100644 index 000000000..2693461cd --- /dev/null +++ b/code/nel/include/nel/gui/http_hsts.h @@ -0,0 +1,73 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef CL_HTTP_HSTS_H +#define CL_HTTP_HSTS_H + +#include "nel/misc/types_nl.h" + +namespace NLGUI +{ + // ******************************************************************************** + struct SHSTSObject + { + public: + SHSTSObject(uint64 expires = 0, bool includeSubDomains = false) + : Expires(expires) + , IncludeSubDomains(includeSubDomains) + { } + + uint64 Expires; + bool IncludeSubDomains; + }; + + /** + * Keeping track of HSTS header + * \author Meelis Mägi (nimetu) + * \date 2017 + */ + class CStrictTransportSecurity + { + public: + typedef std::map THSTSObject; + + static CStrictTransportSecurity* getInstance(); + static void release(); + + public: + bool isSecureHost(const std::string &domain) const; + + // ************************************************************************ + void init(const std::string& fname); + void save(); + + void erase(const std::string &domain); + void set(const std::string &domain, uint64 expires, bool includeSubDomains); + bool get(const std::string &domain, SHSTSObject &hsts) const; + void setFromHeader(const std::string &domain, const std::string &header); + + void serial(NLMISC::IStream& f); + private: + static CStrictTransportSecurity* instance; + + ~CStrictTransportSecurity(); + + std::string _Filename; + THSTSObject _Domains; + }; + +} +#endif diff --git a/code/nel/src/gui/curl_certificates.cpp b/code/nel/src/gui/curl_certificates.cpp new file mode 100644 index 000000000..6cf34c8b8 --- /dev/null +++ b/code/nel/src/gui/curl_certificates.cpp @@ -0,0 +1,123 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//#include + +#include "stdpch.h" +#include "nel/gui/curl_certificates.h" + +#include +#include + +#if defined(NL_OS_WINDOWS) +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") +#endif + +using namespace std; +using namespace NLMISC; + +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + +namespace NLGUI +{ +#if defined(NL_OS_WINDOWS) + static std::vector x509CertList; + + // + // x509CertList lifetime manager + // + class SX509Certificates { + public: + SX509Certificates() + { + curl_version_info_data *data; + data = curl_version_info(CURLVERSION_NOW); + if (!(data && data->features & CURL_VERSION_SSPI)) + { + addCertificatesFrom("CA"); + addCertificatesFrom("AuthRoot"); + addCertificatesFrom("ROOT"); + } + } + + ~SX509Certificates() + { + for (uint i = 0; i < x509CertList.size(); ++i) + { + X509_free(x509CertList[i]); + } + + x509CertList.clear(); + } + + void addCertificatesFrom(LPCSTR root) + { + HCERTSTORE hStore; + PCCERT_CONTEXT pContext = NULL; + X509 *x509; + hStore = CertOpenSystemStore(NULL, root); + if (hStore) + { + while (pContext = CertEnumCertificatesInStore(hStore, pContext)) + { + x509 = NULL; + x509 = d2i_X509(NULL, (const unsigned char **)&pContext->pbCertEncoded, pContext->cbCertEncoded); + if (x509) + { + x509CertList.push_back(x509); + } + } + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + } + + // this is called before debug context is set and log ends up in log.log + //nlinfo("Loaded %d certificates from '%s' certificate store", List.size(), root); + } + }; + + /// this will be initialized on startup and cleared on exit + static SX509Certificates x509CertListManager; + + // *************************************************************************** + // static + CURLcode CCurlCertificates::sslCtxFunction(CURL *curl, void *sslctx, void *parm) + { + if (x509CertList.size() > 0) + { + SSL_CTX *ctx = (SSL_CTX*)sslctx; + X509_STORE *x509store = SSL_CTX_get_cert_store(ctx); + if (x509store) + { + for (uint i = 0; i < x509CertList.size(); ++i) + { + X509_STORE_add_cert(x509store, x509CertList[i]); + } + } + else + { + nlwarning("SSL_CTX_get_cert_store returned NULL"); + } + } + return CURLE_OK; + } +#endif // NL_OS_WINDOWS + +}// namespace + diff --git a/code/nel/src/gui/group_editbox.cpp b/code/nel/src/gui/group_editbox.cpp index f0a41c8a0..e7ed22583 100644 --- a/code/nel/src/gui/group_editbox.cpp +++ b/code/nel/src/gui/group_editbox.cpp @@ -77,6 +77,7 @@ namespace NLGUI _ResetFocusOnHide(false), _BackupFatherContainerPos(false), _WantReturn(false), + _ClearOnEscape(false), _Savable(true), _DefaultInputString(false), _Frozen(false), @@ -239,6 +240,11 @@ namespace NLGUI return toString( _WantReturn ); } else + if( name == "clear_on_escape" ) + { + return toString( _ClearOnEscape ); + } + else if( name == "savable" ) { return toString( _Savable ); @@ -413,6 +419,14 @@ namespace NLGUI return; } else + if( name == "clear_on_escape" ) + { + bool b; + if( fromString( value, b ) ) + _ClearOnEscape = b; + return; + } + else if( name == "savable" ) { bool b; @@ -514,6 +528,7 @@ namespace NLGUI xmlSetProp( node, BAD_CAST "backup_father_container_pos", BAD_CAST toString( _BackupFatherContainerPos ).c_str() ); xmlSetProp( node, BAD_CAST "want_return", BAD_CAST toString( _WantReturn ).c_str() ); + xmlSetProp( node, BAD_CAST "clear_on_escape", BAD_CAST toString( _ClearOnEscape ).c_str() ); xmlSetProp( node, BAD_CAST "savable", BAD_CAST toString( _Savable ).c_str() ); xmlSetProp( node, BAD_CAST "max_float_prec", BAD_CAST toString( _MaxFloatPrec ).c_str() ); @@ -620,6 +635,9 @@ namespace NLGUI prop = (char*) xmlGetProp( cur, (xmlChar*)"want_return" ); if (prop) _WantReturn = convertBool(prop); + prop = (char*) xmlGetProp( cur, (xmlChar*)"clear_on_escape" ); + if (prop) _ClearOnEscape = convertBool(prop); + prop = (char*) xmlGetProp( cur, (xmlChar*)"savable" ); if (prop) _Savable = convertBool(prop); @@ -991,6 +1009,11 @@ namespace NLGUI // stop selection _CurrSelection = NULL; _CursorAtPreviousLineEnd = false; + if (_ClearOnEscape) + { + setInputString(ucstring("")); + triggerOnChangeAH(); + } break; case KeyTAB: makeTopWindow(); diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index 8c309aa46..0c082ae58 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -46,6 +46,8 @@ #include "nel/misc/big_file.h" #include "nel/gui/url_parser.h" #include "nel/gui/http_cache.h" +#include "nel/gui/http_hsts.h" +#include "nel/gui/curl_certificates.h" using namespace std; using namespace NLMISC; @@ -70,6 +72,25 @@ namespace NLGUI CGroupHTML::SWebOptions CGroupHTML::options; + // Return URL with https is host is in HSTS list + static std::string upgradeInsecureUrl(const std::string &url) + { + if (toLower(url.substr(0, 7)) != "http://") { + return url; + } + + CUrlParser uri(url); + if (!CStrictTransportSecurity::getInstance()->isSecureHost(uri.host)){ + return url; + } + + #ifdef LOG_DL + nlwarning("HSTS url : '%s', using https", url.c_str()); + #endif + uri.scheme = "https"; + + return uri.toString(); + } // Active cURL www transfer class CCurlWWWData @@ -149,6 +170,27 @@ namespace NLGUI return ""; } + bool hasHSTSHeader() + { + // ignore header if not secure connection + if (toLower(Url.substr(0, 8)) != "https://") + { + return false; + } + + return HeadersRecv.count("strict-transport-security") > 0; + } + + const std::string getHSTSHeader() + { + if (hasHSTSHeader()) + { + return HeadersRecv["strict-transport-security"]; + } + + return ""; + } + public: CURL *Request; @@ -356,6 +398,14 @@ namespace NLGUI return false; } +#if defined(NL_OS_WINDOWS) + // https:// + if (toLower(download.url.substr(0, 8)) == "https://") + { + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction); + } +#endif + download.data = new CCurlWWWData(curl, download.url); download.fp = fp; @@ -398,7 +448,7 @@ namespace NLGUI // Add a image download request in the multi_curl void CGroupHTML::addImageDownload(const string &url, CViewBase *img, const CStyleParams &style, TImageType type) { - string finalUrl = getAbsoluteUrl(url); + string finalUrl = upgradeInsecureUrl(getAbsoluteUrl(url)); // Search if we are not already downloading this url. for(uint i = 0; i < Curls.size(); i++) @@ -413,7 +463,7 @@ namespace NLGUI } } - // use requested url for local name + // use requested url for local name (cache) string dest = localImageName(url); #ifdef LOG_DL nlwarning("add to download '%s' dest '%s' img %p", finalUrl.c_str(), dest.c_str(), img); @@ -459,8 +509,10 @@ namespace NLGUI } // Add a bnp download request in the multi_curl, return true if already downloaded - bool CGroupHTML::addBnpDownload(const string &url, const string &action, const string &script, const string &md5sum) + bool CGroupHTML::addBnpDownload(string url, const string &action, const string &script, const string &md5sum) { + url = upgradeInsecureUrl(getAbsoluteUrl(url)); + // Search if we are not already downloading this url. for(uint i = 0; i < Curls.size(); i++) { @@ -569,6 +621,12 @@ namespace NLGUI #ifdef LOG_DL nlwarning("(%s) web transfer '%p' completed with status %d, http %d, url (len %d) '%s'", _Id.c_str(), _CurlWWW->Request, res, code, _CurlWWW->Url.size(), _CurlWWW->Url.c_str()); #endif + // save HSTS header from all requests regardless of HTTP code + if (res == CURLE_OK && _CurlWWW->hasHSTSHeader()) + { + CUrlParser uri(_CurlWWW->Url); + CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, _CurlWWW->getHSTSHeader()); + } if (res != CURLE_OK) { @@ -655,6 +713,12 @@ namespace NLGUI #endif curl_multi_remove_handle(MultiCurl, it->data->Request); + // save HSTS header from all requests regardless of HTTP code + if (res == CURLE_OK && it->data->hasHSTSHeader()) + { + CStrictTransportSecurity::getInstance()->setFromHeader(uri.host, it->data->getHSTSHeader()); + } + string tmpfile = it->dest + ".tmp"; if(res != CURLE_OK || r < 200 || r >= 300 || (!it->md5sum.empty() && (it->md5sum != getMD5(tmpfile).toString()))) { @@ -5231,7 +5295,7 @@ namespace NLGUI } // *************************************************************************** - void CGroupHTML::doBrowseRemoteUrl(const std::string &url, const std::string &referer, bool doPost, const SFormFields &formfields) + void CGroupHTML::doBrowseRemoteUrl(std::string url, const std::string &referer, bool doPost, const SFormFields &formfields) { // Stop previous request and remove content stopBrowse (); @@ -5245,6 +5309,8 @@ namespace NLGUI else setTitle (_TitlePrefix + " - " + CI18N::get("uiPleaseWait")); + url = upgradeInsecureUrl(url); + #if LOG_DL nlwarning("(%s) browse url (trusted=%s) '%s', referer='%s', post='%s', nb form values %d", _Id.c_str(), (_TrustedDomain ? "true" :"false"), url.c_str(), referer.c_str(), (doPost ? "true" : "false"), formfields.Values.size()); @@ -5264,6 +5330,14 @@ namespace NLGUI return; } +#if defined(NL_OS_WINDOWS) + // https:// + if (toLower(url.substr(0, 8)) == "https://") + { + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction); + } +#endif + // do not follow redirects, we have own handler curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0); // after redirect diff --git a/code/nel/src/gui/http_hsts.cpp b/code/nel/src/gui/http_hsts.cpp new file mode 100644 index 000000000..980bdabda --- /dev/null +++ b/code/nel/src/gui/http_hsts.cpp @@ -0,0 +1,245 @@ +// Ryzom - MMORPG Framework +// Copyright (C) 2010 Winch Gate Property Limited +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "stdpch.h" +#include "nel/gui/http_hsts.h" + +using namespace std; +using namespace NLMISC; + +#ifdef DEBUG_NEW +#define new DEBUG_NEW +#endif + +namespace NLGUI { + CStrictTransportSecurity* CStrictTransportSecurity::instance = NULL; + CStrictTransportSecurity* CStrictTransportSecurity::getInstance() + { + if (!instance) + { + instance= new CStrictTransportSecurity(); + } + return instance; + } + + void CStrictTransportSecurity::release() + { + delete instance; + instance = NULL; + } + + CStrictTransportSecurity::~CStrictTransportSecurity() + { + save(); + } + + // ************************************************************************ + bool CStrictTransportSecurity::isSecureHost(const std::string &domain) const + { + SHSTSObject hsts; + if (get(domain, hsts)) + { + time_t currentTime; + time(¤tTime); + + return (hsts.Expires < currentTime); + } + + return false; + } + + // ************************************************************************ + void CStrictTransportSecurity::erase(const std::string &domain) + { + if (_Domains.count(domain) > 0) + { + _Domains.erase(domain); + } + } + + void CStrictTransportSecurity::set(const std::string &domain, uint64 expires, bool includeSubDomains) + { + if (expires == 0) + { + erase(domain); + return; + } + + _Domains[domain].Expires = expires; + _Domains[domain].IncludeSubDomains = includeSubDomains; + } + + bool CStrictTransportSecurity::get(const std::string &domain, SHSTSObject &hsts) const + { + if (domain.empty() || _Domains.empty()) + return false; + + if (_Domains.count(domain) > 0) + { + hsts = _Domains.at(domain); + return true; + } + + size_t firstOf = domain.find_first_of("."); + size_t lastOf = domain.find_last_of("."); + while(firstOf != lastOf) + { + std::string tmp; + tmp = domain.substr(firstOf+1); + if (_Domains.count(tmp)) + { + if (_Domains.at(tmp).IncludeSubDomains) + { + hsts = _Domains.at(tmp); + return true; + } + + return false; + } + + firstOf = domain.find_first_of(".", firstOf + 1); + } + + return false; + } + + void CStrictTransportSecurity::init(const std::string &fname) + { + _Domains.clear(); + _Filename = fname; + + if (_Filename.empty() || !CFile::fileExists(_Filename)) + { + return; + } + + CIFile in; + if (!in.open(_Filename)) + { + nlwarning("Unable to open %s for reading", _Filename.c_str()); + return; + } + + serial(in); + } + + void CStrictTransportSecurity::save() + { + if (_Filename.empty()) + return; + + if (_Domains.empty()) + { + CFile::deleteFile(_Filename); + return; + } + + COFile out; + if (!out.open(_Filename)) + { + nlwarning("Unable to open %s for writing", _Filename.c_str()); + return; + } + + serial(out); + out.close(); + } + + void CStrictTransportSecurity::serial(NLMISC::IStream& f) + { + try + { + f.serialVersion(1); + // HSTS + f.serialCheck(NELID("STSH")); + + if (f.isReading()) + { + uint32 nbItems; + f.serial(nbItems); + for(uint32 k = 0; k < nbItems; ++k) + { + std::string domain; + f.serial(domain); + f.serial(_Domains[domain].Expires); + f.serial(_Domains[domain].IncludeSubDomains); + } + } + else + { + uint32 nbItems = _Domains.size(); + f.serial(nbItems); + for (THSTSObject::iterator it = _Domains.begin(); it != _Domains.end(); ++it) + { + std::string domain(it->first); + f.serial(domain); + f.serial(_Domains[domain].Expires); + f.serial(_Domains[domain].IncludeSubDomains); + } + } + } + catch (...) + { + _Domains.clear(); + nlwarning("Invalid HTST file format (%s)", _Filename.c_str()); + } + } + + // *************************************************************************** + void CStrictTransportSecurity::setFromHeader(const std::string &domain, const std::string &header) + { + // max-age=; includeSubdomains; preload; + std::vector elements; + NLMISC::splitString(toLower(header), ";", elements); + if (elements.empty()) return; + + time_t currentTime; + time(¤tTime); + + uint64 expire = 0; + bool includeSubDomains = false; + + for(uint i=0; i< elements.size(); ++i) + { + std::string str(trim(elements[i])); + if (str.substr(0, 8) == "max-age=") + { + uint64 ttl; + if (fromString(str.substr(8), ttl)) + { + if (ttl > 0) + { + expire = currentTime + ttl; + } + } + } + else if (str == "includesubdomains") + { + includeSubDomains = true; + } + } + + if (expire == 0) + { + erase(domain); + } + else + { + set(domain, expire, includeSubDomains); + } + } + +} diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/inventory.xml b/code/ryzom/client/data/gamedev/interfaces_v3/inventory.xml index e9ff0719b..2864894ab 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/inventory.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/inventory.xml @@ -549,7 +549,7 @@ pop_min_h="240" pop_max_w="920" pop_max_h="1600" - w="300" + w="400" h="400" movable="true" active="false" diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml b/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml index 193678ec8..e5ece5680 100644 --- a/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml +++ b/code/ryzom/client/data/gamedev/interfaces_v3/widgets.xml @@ -2655,6 +2655,7 @@ fontsize="10" backup_father_container_pos="false" want_return="false" + clear_on_escape="false" color="255 255 255 255" continuous_text_update="false" bg_texture="W_box_blank.tga" @@ -2695,6 +2696,7 @@ menu_r="#menu_r" max_historic="#max_historic" want_return="#want_return" + clear_on_escape="#clear_on_escape" backup_father_container_pos="#backup_father_container_pos" on_focus_lost="#on_focus_lost" on_focus_lost_params="#on_focus_lost_params" @@ -6587,6 +6589,18 @@ dblink="UI:SAVE:#inv_type:FILTER_ARMOR" texture="filter_armor.tga" tooltip="uittFilterArmor" /> + setCacheIndex("cache/cache.index"); CHttpCache::getInstance()->init(); + CStrictTransportSecurity::getInstance()->init("save/hsts-list.save"); + // Register the reflected classes registerInterfaceElements(); diff --git a/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp b/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp index 46fea9593..88b454279 100644 --- a/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp +++ b/code/ryzom/client/src/interface_v3/dbctrl_sheet.cpp @@ -2960,6 +2960,8 @@ void CDBCtrlSheet::swapSheet(CDBCtrlSheet *other) swapDBProps(getItemRMClassTypePtr(), other->getItemRMClassTypePtr()); swapDBProps(getItemRMFaberStatTypePtr(), other->getItemRMFaberStatTypePtr()); swapDBProps(getItemPrerequisitValidPtr(), other->getItemPrerequisitValidPtr()); + swapDBProps(getItemSerialPtr(), other->getItemSerialPtr()); + swapDBProps(getItemCreateTimePtr(), other->getItemCreateTimePtr()); } } @@ -3539,6 +3541,10 @@ void CDBCtrlSheet::copyAspect(CDBCtrlSheet *dest) dest->setItemRMFaberStatType(getItemRMFaberStatType()); // copy prerequisit valid flag dest->setItemPrerequisitValid(getItemPrerequisitValid()); + // copy item serial + dest->setItemSerial(getItemSerial()); + // copy item create time + dest->setItemCreateTime(getItemCreateTime()); } // if brick, sphrase or sphraseId if(isSBrick() || isSPhrase() || isSPhraseId()) diff --git a/code/ryzom/client/src/interface_v3/inventory_manager.cpp b/code/ryzom/client/src/interface_v3/inventory_manager.cpp index 4f882fd21..745d211d0 100644 --- a/code/ryzom/client/src/interface_v3/inventory_manager.cpp +++ b/code/ryzom/client/src/interface_v3/inventory_manager.cpp @@ -39,6 +39,7 @@ // For handlers #include "nel/gui/action_handler.h" +#include "nel/gui/group_editbox.h" #include "dbctrl_sheet.h" #include "../sheet_manager.h" @@ -2010,6 +2011,18 @@ bool SBagOptions::parse(xmlNodePtr cur, CInterfaceGroup * /* parentGroup */) return true; } +// *************************************************************************** +void SBagOptions::setSearchFilter(const ucstring &s) +{ + SearchFilter.clear(); + SearchFilterChanged = true; + + if (!s.empty()) + { + splitUCString(toLower(s), ucstring(" "), SearchFilter); + } +} + // *************************************************************************** bool SBagOptions::isSomethingChanged() { @@ -2057,6 +2070,12 @@ bool SBagOptions::isSomethingChanged() LastDbFilterTP = (DbFilterTP->getValue8() != 0); } + if (SearchFilterChanged) + { + bRet = true; + SearchFilterChanged = false; + } + return bRet; } @@ -2075,6 +2094,26 @@ bool SBagOptions::canDisplay(CDBCtrlSheet *pCS) const const CItemSheet *pIS = pCS->asItemSheet(); if (pIS != NULL) { + if (SearchFilter.size() > 0) + { + bool match = true; + ucstring lcName = toLower(pCS->getItemActualName()); + + // add item quality as a keyword to match + if (pCS->getQuality() > 1) + { + lcName += ucstring(" " + toString(pCS->getQuality())); + } + + for (uint i = 0; i< SearchFilter.size(); ++i) + { + if (lcName.find(SearchFilter[i]) == ucstring::npos) + { + return false; + } + } + } + // Armor if ((pIS->Family == ITEMFAMILY::ARMOR) || (pIS->Family == ITEMFAMILY::JEWELRY)) @@ -2455,6 +2494,30 @@ class CHandlerInvDrag : public IActionHandler }; REGISTER_ACTION_HANDLER( CHandlerInvDrag, "inv_drag" ); +// ********************************************************************************************************** +class CHandlerInvSetSearch : public IActionHandler +{ + void execute (CCtrlBase *pCaller, const std::string &sParams) + { + if (!pCaller) return; + + CGroupEditBox *eb = dynamic_cast(pCaller); + if (!eb) return; + + CInterfaceManager *pIM = CInterfaceManager::getInstance(); + + // ui:interface:inventory:content:bag:iil:inv_query_eb:eb + string invId = pCaller->getParent()->getParent()->getId(); + + CDBGroupListSheetBag *pList = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(invId + ":bag_list")); + if (pList != NULL) pList->setSearchFilter(eb->getInputString()); + + CDBGroupIconListBag *pIcons = dynamic_cast(CWidgetManager::getInstance()->getElementFromId(invId + ":bag_icons")); + if (pIcons != NULL) pIcons->setSearchFilter(eb->getInputString()); + } +}; +REGISTER_ACTION_HANDLER( CHandlerInvSetSearch, "inv_set_search" ); + // *************************************************************************** // COMMON INVENTORIES Test if we can drop an item to a slot or a list class CHandlerInvCanDropTo : public IActionHandler diff --git a/code/ryzom/client/src/interface_v3/inventory_manager.h b/code/ryzom/client/src/interface_v3/inventory_manager.h index 0f6d0ea69..64114963c 100644 --- a/code/ryzom/client/src/interface_v3/inventory_manager.h +++ b/code/ryzom/client/src/interface_v3/inventory_manager.h @@ -519,18 +519,26 @@ struct SBagOptions bool LastDbFilterMP; bool LastDbFilterMissMP; bool LastDbFilterTP; + + bool SearchFilterChanged; + std::vector SearchFilter; + // ----------------------- SBagOptions() { InvType = CInventoryManager::InvUnknown; DbFilterArmor = DbFilterWeapon = DbFilterTool = DbFilterMP = DbFilterMissMP = DbFilterTP = NULL; LastDbFilterArmor = LastDbFilterWeapon = LastDbFilterTool = LastDbFilterMP = LastDbFilterMissMP = LastDbFilterTP = false; + SearchFilterChanged = false; } bool parse (xmlNodePtr cur, CInterfaceGroup *parentGroup); bool isSomethingChanged(); // From last call ? + bool isSearchFilterChanged() const { return SearchFilterChanged; } + void setSearchFilter(const ucstring &s); + bool getFilterArmor() const { if (DbFilterArmor == NULL) return true; @@ -621,6 +629,8 @@ public: // Return true if the sheet can be displayed due to filters bool canDisplay(CDBCtrlSheet *pCS) { return _BO.canDisplay(pCS); } + void setSearchFilter(const ucstring &s) { _BO.setSearchFilter(s); } + private: SBagOptions _BO; @@ -652,6 +662,8 @@ public: // Return true if the sheet can be displayed due to filters bool canDisplay(CDBCtrlSheet *pCS) const { return _BO.canDisplay(pCS); } + void setSearchFilter(const ucstring &s) { _BO.setSearchFilter(s); } + ////////////////////////////////////////////////////////////////////////// // A child node diff --git a/code/ryzom/client/src/item_group_manager.cpp b/code/ryzom/client/src/item_group_manager.cpp index bd94f4231..e72f2d730 100644 --- a/code/ryzom/client/src/item_group_manager.cpp +++ b/code/ryzom/client/src/item_group_manager.cpp @@ -73,14 +73,29 @@ bool CItemGroup::contains(CDBCtrlSheet *other, SLOT_EQUIPMENT::TSlotEquipment &s void CItemGroup::addItem(sint32 createTime, sint32 serial, SLOT_EQUIPMENT::TSlotEquipment slot) { + //Don't add an item if it already exists, this could cause issue + // It's happening either if we are creating a group with a 2 hands items (and the item is found both in handR and handL) + // Or if an user incorrectly edit his group file + for(int i=0; i::max()); - - Items.push_back(item); + if(item.createTime != 0) + { + addItem(item.createTime, item.serial, item.slot); + } + // Old load : keep for compatibility / migration reasons + else + { + Items.push_back(item); + } } if (strcmp((char*)curNode->name, "remove") == 0) { diff --git a/code/ryzom/client/src/release.cpp b/code/ryzom/client/src/release.cpp index 581d4c2b8..cd96f03b1 100644 --- a/code/ryzom/client/src/release.cpp +++ b/code/ryzom/client/src/release.cpp @@ -95,6 +95,7 @@ #include "nel/gui/lua_manager.h" #include "item_group_manager.h" #include "nel/gui/http_cache.h" +#include "nel/gui/http_hsts.h" /////////// // USING // @@ -688,6 +689,7 @@ void release() CViewRenderer::release(); CIXml::releaseLibXml(); CHttpCache::release(); + CStrictTransportSecurity::release(); #if FINAL_VERSION // openURL ("http://ryzom.com/exit/");