/* * Copyright (C) 2001-2008 Jacek Sieka, arnetheduck on gmail point com * * 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 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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "stdinc.h" #include "DCPlusPlus.h" #include "SearchManager.h" #include "UploadManager.h" #include "ClientManager.h" #include "ShareManager.h" namespace dcpp { SearchResult::SearchResult(Types aType, int64_t aSize, const string& aFile, const TTHValue& aTTH) : file(aFile), user(ClientManager::getInstance()->getMe()), size(aSize), type(aType), slots(SETTING(SLOTS)), freeSlots(UploadManager::getInstance()->getFreeSlots()), tth(aTTH), ref(1) { } string SearchResult::toSR(const Client& c) const { // File: "$SR %s %s%c%s %d/%d%c%s (%s)|" // Directory: "$SR %s %s %d/%d%c%s (%s)|" string tmp; tmp.reserve(128); tmp.append("$SR ", 4); tmp.append(Text::fromUtf8(c.getMyNick(), c.getEncoding())); tmp.append(1, ' '); string acpFile = Text::fromUtf8(file, c.getEncoding()); if(type == TYPE_FILE) { tmp.append(acpFile); tmp.append(1, '\x05'); tmp.append(Util::toString(size)); } else { tmp.append(acpFile, 0, acpFile.length() - 1); } tmp.append(1, ' '); tmp.append(Util::toString(freeSlots)); tmp.append(1, '/'); tmp.append(Util::toString(slots)); tmp.append(1, '\x05'); tmp.append("TTH:" + getTTH().toBase32()); tmp.append(" (", 2); tmp.append(c.getIpPort()); tmp.append(")|", 2); return tmp; } AdcCommand SearchResult::toRES(char type) const { AdcCommand cmd(AdcCommand::CMD_RES, type); cmd.addParam("SI", Util::toString(size)); cmd.addParam("SL", Util::toString(freeSlots)); cmd.addParam("FN", Util::toAdcFile(file)); cmd.addParam("TR", getTTH().toBase32()); return cmd; } SearchManager::SearchManager() : socket(NULL), port(0), stop(false), lastSearch(GET_TICK()) { } SearchManager::~SearchManager() throw() { if(socket) { stop = true; socket->disconnect(); #ifdef _WIN32 join(); #endif delete socket; } } void SearchManager::search(const string& aName, int64_t aSize, TypeModes aTypeMode /* = TYPE_ANY */, SizeModes aSizeMode /* = SIZE_ATLEAST */, const string& aToken /* = Util::emptyString */) { if(okToSearch()) { ClientManager::getInstance()->search(aSizeMode, aSize, aTypeMode, aName, aToken); lastSearch = GET_TICK(); } } void SearchManager::search(StringList& who, const string& aName, int64_t aSize /* = 0 */, TypeModes aTypeMode /* = TYPE_ANY */, SizeModes aSizeMode /* = SIZE_ATLEAST */, const string& aToken /* = Util::emptyString */) { if(okToSearch()) { ClientManager::getInstance()->search(who, aSizeMode, aSize, aTypeMode, aName, aToken); lastSearch = GET_TICK(); } } string SearchResult::getFileName() const { if(getType() == TYPE_FILE) return Util::getFileName(getFile()); if(getFile().size() < 2) return getFile(); string::size_type i = getFile().rfind('\\', getFile().length() - 2); if(i == string::npos) return getFile(); return getFile().substr(i + 1); } void SearchManager::listen() throw(SocketException) { disconnect(); socket = new Socket(); socket->create(Socket::TYPE_UDP); port = socket->bind(static_cast(SETTING(UDP_PORT)), SETTING(BIND_ADDRESS)); start(); } void SearchManager::disconnect() throw() { if(socket != NULL) { stop = true; socket->disconnect(); port = 0; join(); stop = false; } } #define BUFSIZE 8192 int SearchManager::run() { boost::scoped_array buf(new uint8_t[BUFSIZE]); int len; while(true) { string remoteAddr; try { while( (len = socket->read(&buf[0], BUFSIZE, remoteAddr)) != 0) { onData(&buf[0], len, remoteAddr); } } catch(const SocketException& e) { dcdebug("SearchManager::run Error: %s\n", e.getError().c_str()); } if(stop) { return 0; } try { socket->disconnect(); socket->create(Socket::TYPE_UDP); socket->bind(port, SETTING(BIND_ADDRESS)); } catch(const SocketException& e) { // Oops, fatal this time... dcdebug("SearchManager::run Stopped listening: %s\n", e.getError().c_str()); return 1; } } return 0; } void SearchManager::onData(const uint8_t* buf, size_t aLen, const string& remoteIp) { string x((char*)buf, aLen); if(x.compare(0, 4, "$SR ") == 0) { string::size_type i, j; // Directories: $SR <0x20><0x20>/<0x05><0x20>() // Files: $SR <0x20><0x05><0x20>/<0x05><0x20>() i = 4; if( (j = x.find(' ', i)) == string::npos) { return; } string nick = x.substr(i, j-i); i = j + 1; // A file has 2 0x05, a directory only one size_t cnt = count(x.begin() + j, x.end(), 0x05); SearchResult::Types type = SearchResult::TYPE_FILE; string file; int64_t size = 0; if(cnt == 1) { // We have a directory...find the first space beyond the first 0x05 from the back // (dirs might contain spaces as well...clever protocol, eh?) type = SearchResult::TYPE_DIRECTORY; // Get past the hubname that might contain spaces if((j = x.rfind(0x05)) == string::npos) { return; } // Find the end of the directory info if((j = x.rfind(' ', j-1)) == string::npos) { return; } if(j < i + 1) { return; } file = x.substr(i, j-i) + '\\'; } else if(cnt == 2) { if( (j = x.find((char)5, i)) == string::npos) { return; } file = x.substr(i, j-i); i = j + 1; if( (j = x.find(' ', i)) == string::npos) { return; } size = Util::toInt64(x.substr(i, j-i)); } i = j + 1; if( (j = x.find('/', i)) == string::npos) { return; } int freeSlots = Util::toInt(x.substr(i, j-i)); i = j + 1; if( (j = x.find((char)5, i)) == string::npos) { return; } int slots = Util::toInt(x.substr(i, j-i)); i = j + 1; if( (j = x.rfind(" (")) == string::npos) { return; } string hubName = x.substr(i, j-i); i = j + 2; if( (j = x.rfind(')')) == string::npos) { return; } string hubIpPort = x.substr(i, j-i); string url = ClientManager::getInstance()->findHub(hubIpPort); string encoding = ClientManager::getInstance()->findHubEncoding(url); nick = Text::toUtf8(nick, encoding); file = Text::toUtf8(file, encoding); hubName = Text::toUtf8(hubName, encoding); UserPtr user = ClientManager::getInstance()->findUser(nick, url); if(!user) { // Could happen if hub has multiple URLs / IPs user = ClientManager::getInstance()->findLegacyUser(nick); if(!user) return; } string tth; if(hubName.compare(0, 4, "TTH:") == 0) { tth = hubName.substr(4); StringList names = ClientManager::getInstance()->getHubNames(user->getCID()); hubName = names.empty() ? _("Offline") : Util::toString(names); } if(tth.empty() && type == SearchResult::TYPE_FILE) { return; } SearchResult* sr = new SearchResult(user, type, slots, freeSlots, size, file, hubName, url, remoteIp, TTHValue(tth), Util::emptyString); fire(SearchManagerListener::SR(), sr); sr->decRef(); } else if(x.compare(1, 4, "RES ") == 0 && x[x.length() - 1] == 0x0a) { AdcCommand c(x.substr(0, x.length()-1)); if(c.getParameters().empty()) return; string cid = c.getParam(0); if(cid.size() != 39) return; UserPtr user = ClientManager::getInstance()->findUser(CID(cid)); if(!user) return; // This should be handled by AdcCommand really... c.getParameters().erase(c.getParameters().begin()); onRES(c, user, remoteIp); } /*else if(x.compare(1, 4, "SCH ") == 0 && x[x.length() - 1] == 0x0a) { try { respond(AdcCommand(x.substr(0, x.length()-1))); } catch(ParseException& ) { } }*/ // Needs further DoS investigation } void SearchManager::onRES(const AdcCommand& cmd, const UserPtr& from, const string& remoteIp) { int freeSlots = -1; int64_t size = -1; string file; string tth; string token; for(StringIterC i = cmd.getParameters().begin(); i != cmd.getParameters().end(); ++i) { const string& str = *i; if(str.compare(0, 2, "FN") == 0) { file = Util::toNmdcFile(str.substr(2)); } else if(str.compare(0, 2, "SL") == 0) { freeSlots = Util::toInt(str.substr(2)); } else if(str.compare(0, 2, "SI") == 0) { size = Util::toInt64(str.substr(2)); } else if(str.compare(0, 2, "TR") == 0) { tth = str.substr(2); } else if(str.compare(0, 2, "TO") == 0) { token = str.substr(2); } } if(!file.empty() && freeSlots != -1 && size != -1) { StringList names = ClientManager::getInstance()->getHubNames(from->getCID()); string hubName = names.empty() ? _("Offline") : Util::toString(names); StringList hubs = ClientManager::getInstance()->getHubs(from->getCID()); string hub = hubs.empty() ? _("Offline") : Util::toString(hubs); SearchResult::Types type = (file[file.length() - 1] == '\\' ? SearchResult::TYPE_DIRECTORY : SearchResult::TYPE_FILE); if(type == SearchResult::TYPE_FILE && tth.empty()) return; /// @todo Something about the slots SearchResult* sr = new SearchResult(from, type, 0, freeSlots, size, file, hubName, hub, remoteIp, TTHValue(tth), token); fire(SearchManagerListener::SR(), sr); sr->decRef(); } } void SearchManager::respond(const AdcCommand& adc, const CID& from) { // Filter own searches if(from == ClientManager::getInstance()->getMe()->getCID()) return; UserPtr p = ClientManager::getInstance()->findUser(from); if(!p) return; SearchResult::List results; ShareManager::getInstance()->search(results, adc.getParameters(), 10); string token; adc.getParam("TO", 0, token); if(results.empty()) return; for(SearchResult::Iter i = results.begin(); i != results.end(); ++i) { AdcCommand cmd = (*i)->toRES(AdcCommand::TYPE_UDP); if(!token.empty()) cmd.addParam("TO", token); ClientManager::getInstance()->send(cmd, from); (*i)->decRef(); } } string SearchManager::clean(const string& aSearchString) { static const char* badChars = "$|.[]()-_+"; string::size_type i = aSearchString.find_first_of(badChars); if(i == string::npos) return aSearchString; string tmp = aSearchString; // Remove all strange characters from the search string do { tmp[i] = ' '; } while ( (i = tmp.find_first_of(badChars, i)) != string::npos); return tmp; } } // namespace dcpp