1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 //
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
9 // respective authors.
10 //
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
15 //
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
24 //
25 
26 #include "SearchList.h"		// Interface declarations.
27 
28 #include <protocol/Protocols.h>
29 #include <protocol/kad/Constants.h>
30 #include <tags/ClientTags.h>
31 #include <tags/FileTags.h>
32 
33 #include "updownclient.h"	// Needed for CUpDownClient
34 #include "MemFile.h"		// Needed for CMemFile
35 #include "amule.h"			// Needed for theApp
36 #include "ServerConnect.h"	// Needed for theApp->serverconnect
37 #include "Server.h"			// Needed for CServer
38 #include "ServerList.h"		// Needed for theApp->serverlist
39 #include "Statistics.h"		// Needed for theStats
40 #include "ObservableQueue.h"// Needed for CQueueObserver
41 #include <common/Format.h>
42 #include "Logger.h"			// Needed for AddLogLineM/...
43 #include "Packet.h"			// Needed for CPacket
44 #include "GuiEvents.h"		// Needed for Notify_*
45 
46 
47 #ifndef AMULE_DAEMON
48 #include "amuleDlg.h"		// Needed for CamuleDlg
49 #include "SearchDlg.h"		// Needed for CSearchDlg
50 #endif
51 
52 #include "kademlia/kademlia/Kademlia.h"
53 #include "kademlia/kademlia/Search.h"
54 
55 #include "SearchExpr.h"
56 
57 #include "Scanner.h"
58 void LexInit(const wxString& pszInput);
59 void LexFree();
60 
61 #include "Parser.hpp"
62 int yyerror(wxString errstr);
63 
64 
65 static wxString s_strCurKadKeyword;
66 
67 static CSearchExpr _SearchExpr;
68 
69 wxArrayString _astrParserErrors;
70 
71 
72 // Helper function for lexer.
ParsedSearchExpression(const CSearchExpr * pexpr)73 void ParsedSearchExpression(const CSearchExpr* pexpr)
74 {
75 	int iOpAnd = 0;
76 	int iOpOr = 0;
77 	int iOpNot = 0;
78 
79 	for (unsigned int i = 0; i < pexpr->m_aExpr.GetCount(); i++) {
80 		const wxString& str = pexpr->m_aExpr[i];
81 		if (str == SEARCHOPTOK_AND) {
82 			iOpAnd++;
83 		} else if (str == SEARCHOPTOK_OR) {
84 			iOpOr++;
85 		} else if (str == SEARCHOPTOK_NOT) {
86 			iOpNot++;
87 		}
88 	}
89 
90 	// this limit (+ the additional operators which will be added later) has to match the limit in 'CreateSearchExpressionTree'
91 	//	+1 Type (Audio, Video)
92 	//	+1 MinSize
93 	//	+1 MaxSize
94 	//	+1 Avail
95 	//	+1 Extension
96 	//	+1 Complete sources
97 	//	+1 Codec
98 	//	+1 Bitrate
99 	//	+1 Length
100 	//	+1 Title
101 	//	+1 Album
102 	//	+1 Artist
103 	// ---------------
104 	//  12
105 	if (iOpAnd + iOpOr + iOpNot > 10) {
106 		yyerror(wxT("Search expression is too complex"));
107 	}
108 
109 	_SearchExpr.m_aExpr.Empty();
110 
111 	// optimize search expression, if no OR nor NOT specified
112 	if (iOpAnd > 0 && iOpOr == 0 && iOpNot == 0) {
113 		// figure out if we can use a better keyword than the one the user selected
114 		// for example most user will search like this "The oxymoronaccelerator 2", which would ask the node which indexes "the"
115 		// This causes higher traffic for such nodes and makes them a viable target to attackers, while the kad result should be
116 		// the same or even better if we ask the node which indexes the rare keyword "oxymoronaccelerator", so we try to rearrange
117 		// keywords and generally assume that the longer keywords are rarer
118 		if (/*thePrefs::GetRearrangeKadSearchKeywords() &&*/ !s_strCurKadKeyword.IsEmpty()) {
119 			for (unsigned int i = 0; i < pexpr->m_aExpr.GetCount(); i++) {
120 				if (pexpr->m_aExpr[i] != SEARCHOPTOK_AND) {
121 					if (pexpr->m_aExpr[i] != s_strCurKadKeyword
122 						&& pexpr->m_aExpr[i].find_first_of(Kademlia::CSearchManager::GetInvalidKeywordChars()) == wxString::npos
123 						&& pexpr->m_aExpr[i].Find('"') != 0 // no quoted expressions as keyword
124 						&& pexpr->m_aExpr[i].length() >= 3
125 						&& s_strCurKadKeyword.length() < pexpr->m_aExpr[i].length())
126 					{
127 						s_strCurKadKeyword = pexpr->m_aExpr[i];
128 					}
129 				}
130 			}
131 		}
132 		wxString strAndTerms;
133 		for (unsigned int i = 0; i < pexpr->m_aExpr.GetCount(); i++) {
134 			if (pexpr->m_aExpr[i] != SEARCHOPTOK_AND) {
135 				// Minor optimization: Because we added the Kad keyword to the boolean search expression,
136 				// we remove it here (and only here) again because we know that the entire search expression
137 				// does only contain (implicit) ANDed strings.
138 				if (pexpr->m_aExpr[i] != s_strCurKadKeyword) {
139 					if (!strAndTerms.IsEmpty()) {
140 						strAndTerms += ' ';
141 					}
142 					strAndTerms += pexpr->m_aExpr[i];
143 				}
144 			}
145 		}
146 		wxASSERT( _SearchExpr.m_aExpr.GetCount() == 0);
147 		_SearchExpr.m_aExpr.Add(strAndTerms);
148 	} else {
149 		if (pexpr->m_aExpr.GetCount() != 1 || pexpr->m_aExpr[0] != s_strCurKadKeyword)
150 			_SearchExpr.Add(pexpr);
151 	}
152 }
153 
154 
155 //! Helper class for packet creation
156 class CSearchExprTarget
157 {
158 public:
CSearchExprTarget(CMemFile * pData,EUtf8Str eStrEncode,bool supports64bit,bool & using64bit)159 	CSearchExprTarget(CMemFile* pData, EUtf8Str eStrEncode, bool supports64bit, bool& using64bit)
160 		: m_data(pData),
161 		  m_eStrEncode(eStrEncode),
162 		  m_supports64bit(supports64bit),
163 		  m_using64bit(using64bit)
164 	{
165 		m_using64bit = false;
166 	}
167 
WriteBooleanAND()168 	void WriteBooleanAND()
169 	{
170 		m_data->WriteUInt8(0);				// boolean operator parameter type
171 		m_data->WriteUInt8(0x00);			// "AND"
172 	}
173 
WriteBooleanOR()174 	void WriteBooleanOR()
175 	{
176 		m_data->WriteUInt8(0);				// boolean operator parameter type
177 		m_data->WriteUInt8(0x01);			// "OR"
178 	}
179 
WriteBooleanNOT()180 	void WriteBooleanNOT()
181 	{
182 		m_data->WriteUInt8(0);				// boolean operator parameter type
183 		m_data->WriteUInt8(0x02);			// "NOT"
184 	}
185 
WriteMetaDataSearchParam(const wxString & rstrValue)186 	void WriteMetaDataSearchParam(const wxString& rstrValue)
187 	{
188 		m_data->WriteUInt8(1);				// string parameter type
189 		m_data->WriteString(rstrValue, m_eStrEncode); // string value
190 	}
191 
WriteMetaDataSearchParam(uint8 uMetaTagID,const wxString & rstrValue)192 	void WriteMetaDataSearchParam(uint8 uMetaTagID, const wxString& rstrValue)
193 	{
194 		m_data->WriteUInt8(2);				// string parameter type
195 		m_data->WriteString(rstrValue, m_eStrEncode); // string value
196 		m_data->WriteUInt16(sizeof(uint8));	// meta tag ID length
197 		m_data->WriteUInt8(uMetaTagID);		// meta tag ID name
198 	}
199 
WriteMetaDataSearchParamASCII(uint8 uMetaTagID,const wxString & rstrValue)200 	void WriteMetaDataSearchParamASCII(uint8 uMetaTagID, const wxString& rstrValue)
201 	{
202 		m_data->WriteUInt8(2);				// string parameter type
203 		m_data->WriteString(rstrValue, utf8strNone); // string value
204 		m_data->WriteUInt16(sizeof(uint8));	// meta tag ID length
205 		m_data->WriteUInt8(uMetaTagID);		// meta tag ID name
206 	}
207 
WriteMetaDataSearchParam(const wxString & pszMetaTagID,const wxString & rstrValue)208 	void WriteMetaDataSearchParam(const wxString& pszMetaTagID, const wxString& rstrValue)
209 	{
210 		m_data->WriteUInt8(2);				// string parameter type
211 		m_data->WriteString(rstrValue, m_eStrEncode); // string value
212 		m_data->WriteString(pszMetaTagID);	// meta tag ID
213 	}
214 
WriteMetaDataSearchParam(uint8_t uMetaTagID,uint8_t uOperator,uint64_t value)215 	void WriteMetaDataSearchParam(uint8_t uMetaTagID, uint8_t uOperator, uint64_t value)
216 	{
217 		bool largeValue = value > wxULL(0xFFFFFFFF);
218 		if (largeValue && m_supports64bit) {
219 			m_using64bit = true;
220 			m_data->WriteUInt8(8);		// numeric parameter type (int64)
221 			m_data->WriteUInt64(value);	// numeric value
222 		} else {
223 			if (largeValue) {
224 				value = 0xFFFFFFFFu;
225 			}
226 			m_data->WriteUInt8(3);		// numeric parameter type (int32)
227 			m_data->WriteUInt32(value);	// numeric value
228 		}
229 		m_data->WriteUInt8(uOperator);		// comparison operator
230 		m_data->WriteUInt16(sizeof(uint8));	// meta tag ID length
231 		m_data->WriteUInt8(uMetaTagID);		// meta tag ID name
232 	}
233 
WriteMetaDataSearchParam(const wxString & pszMetaTagID,uint8_t uOperator,uint64_t value)234 	void WriteMetaDataSearchParam(const wxString& pszMetaTagID, uint8_t uOperator, uint64_t value)
235 	{
236 		bool largeValue = value > wxULL(0xFFFFFFFF);
237 		if (largeValue && m_supports64bit) {
238 			m_using64bit = true;
239 			m_data->WriteUInt8(8);		// numeric parameter type (int64)
240 			m_data->WriteUInt64(value);	// numeric value
241 		} else {
242 			if (largeValue) {
243 				value = 0xFFFFFFFFu;
244 			}
245 			m_data->WriteUInt8(3);		// numeric parameter type (int32)
246 			m_data->WriteUInt32(value);	// numeric value
247 		}
248 		m_data->WriteUInt8(uOperator);		// comparison operator
249 		m_data->WriteString(pszMetaTagID);	// meta tag ID
250 	}
251 
252 protected:
253 	CMemFile* m_data;
254 	EUtf8Str m_eStrEncode;
255 	bool m_supports64bit;
256 	bool& m_using64bit;
257 };
258 
259 
260 
261 
262 ///////////////////////////////////////////////////////////
263 // CSearchList
264 
BEGIN_EVENT_TABLE(CSearchList,wxEvtHandler)265 BEGIN_EVENT_TABLE(CSearchList, wxEvtHandler)
266 	EVT_MULE_TIMER(wxID_ANY, CSearchList::OnGlobalSearchTimer)
267 END_EVENT_TABLE()
268 
269 
270 CSearchList::CSearchList()
271 	: m_searchTimer(this, 0 /* Timer-id doesn't matter. */ ),
272 	  m_searchType(LocalSearch),
273 	  m_searchInProgress(false),
274 	  m_currentSearch(-1),
275 	  m_searchPacket(NULL),
276 	  m_64bitSearchPacket(false),
277 	  m_KadSearchFinished(true)
278 {}
279 
280 
~CSearchList()281 CSearchList::~CSearchList()
282 {
283 	StopSearch();
284 
285 	while (!m_results.empty()) {
286 		RemoveResults(m_results.begin()->first);
287 	}
288 }
289 
290 
RemoveResults(long searchID)291 void CSearchList::RemoveResults(long searchID)
292 {
293 	// A non-existant search id will just be ignored
294 	Kademlia::CSearchManager::StopSearch(searchID, true);
295 
296 	ResultMap::iterator it = m_results.find(searchID);
297 	if ( it != m_results.end() ) {
298 		CSearchResultList& list = it->second;
299 
300 		for (size_t i = 0; i < list.size(); ++i) {
301 			delete list.at(i);
302 		}
303 
304 		m_results.erase( it );
305 	}
306 }
307 
308 
StartNewSearch(uint32 * searchID,SearchType type,CSearchParams & params)309 wxString CSearchList::StartNewSearch(uint32* searchID, SearchType type, CSearchParams& params)
310 {
311 	// Check that we can actually perform the specified desired search.
312 	if ((type == KadSearch) && !Kademlia::CKademlia::IsRunning()) {
313 		return _("Kad search can't be done if Kad is not running");
314 	} else if ((type != KadSearch) && !theApp->IsConnectedED2K()) {
315 		return _("eD2k search can't be done if eD2k is not connected");
316 	}
317 
318 	if (params.typeText != ED2KFTSTR_PROGRAM) {
319 		if (params.typeText.CmpNoCase(wxT("Any"))) {
320 			m_resultType = params.typeText;
321 		} else {
322 			m_resultType.Clear();
323 		}
324 	} else {
325 		// No check is to be made on returned results if the
326 		// type is 'Programs', since this returns multiple types.
327 		m_resultType.Clear();
328 	}
329 
330 	if (type == KadSearch) {
331 		Kademlia::WordList words;
332 		Kademlia::CSearchManager::GetWords(params.searchString, &words);
333 		if (!words.empty()) {
334 			params.strKeyword = words.front();
335 		} else {
336 			return _("No keyword for Kad search - aborting");
337 		}
338 	}
339 
340 	bool supports64bit = type == KadSearch ? true : theApp->serverconnect->GetCurrentServer() != NULL && (theApp->serverconnect->GetCurrentServer()->GetTCPFlags() & SRV_TCPFLG_LARGEFILES);
341 	bool packetUsing64bit;
342 
343 	// This MemFile is automatically free'd
344 	CMemFilePtr data = CreateSearchData(params, type, supports64bit, packetUsing64bit);
345 
346 	if (data.get() == NULL) {
347 		wxASSERT(_astrParserErrors.GetCount());
348 		wxString error;
349 
350 		for (unsigned int i = 0; i < _astrParserErrors.GetCount(); ++i) {
351 			error += _astrParserErrors[i] + wxT("\n");
352 		}
353 
354 		return error;
355 	}
356 
357 	m_searchType = type;
358 	if (type == KadSearch) {
359 		try {
360 			if (*searchID == 0xffffffff) {
361 				Kademlia::CSearchManager::StopSearch(0xffffffff, false);
362 			}
363 
364 			// searchstring will get tokenized there
365 			// The tab must be created with the Kad search ID, so searchID is updated.
366 			Kademlia::CSearch* search = Kademlia::CSearchManager::PrepareFindKeywords(params.strKeyword, data->GetLength(), data->GetRawBuffer(), *searchID);
367 
368 			*searchID = search->GetSearchID();
369 			m_currentSearch = *searchID;
370 			m_KadSearchFinished = false;
371 		} catch (const wxString& what) {
372 			AddLogLineC(what);
373 			return _("Unexpected error while attempting Kad search: ") + what;
374 		}
375 	} else {
376 		// This is an ed2k search, local or global
377 		m_currentSearch = *(searchID);
378 		m_searchInProgress = true;
379 
380 		CPacket* searchPacket = new CPacket(*data.get(), OP_EDONKEYPROT, OP_SEARCHREQUEST);
381 
382 		theStats::AddUpOverheadServer(searchPacket->GetPacketSize());
383 		theApp->serverconnect->SendPacket(searchPacket, (type == LocalSearch));
384 
385 		if (type == GlobalSearch) {
386 			delete m_searchPacket;
387 			m_searchPacket = searchPacket;
388 			m_64bitSearchPacket = packetUsing64bit;
389 			m_searchPacket->SetOpCode(OP_GLOBSEARCHREQ); // will be changed later when actually sending the packet!!
390 		}
391 	}
392 
393 	return wxEmptyString;
394 }
395 
396 
LocalSearchEnd()397 void CSearchList::LocalSearchEnd()
398 {
399 	if (m_searchType == GlobalSearch) {
400 		wxCHECK_RET(m_searchPacket, wxT("Global search, but no packet"));
401 
402 		// Ensure that every global search starts over.
403 		theApp->serverlist->RemoveObserver(&m_serverQueue);
404 		m_searchTimer.Start(750);
405 	} else {
406 		m_searchInProgress = false;
407 		Notify_SearchLocalEnd();
408 	}
409 }
410 
411 
GetSearchProgress() const412 uint32 CSearchList::GetSearchProgress() const
413 {
414 	if (m_searchType == KadSearch) {
415 		// We cannot measure the progress of Kad searches.
416 		// But we can tell when they are over.
417 		return m_KadSearchFinished ? 0xfffe : 0;
418 	}
419 	if (m_searchInProgress == false) {	// true only for ED2K search
420 		// No search, no progress ;)
421 		return 0;
422 	}
423 
424 	switch (m_searchType) {
425 		case LocalSearch:
426 			return 0xffff;
427 
428 		case GlobalSearch:
429 			return 100 - (m_serverQueue.GetRemaining() * 100)
430 					/ theApp->serverlist->GetServerCount();
431 
432 		default:
433 			wxFAIL;
434 	}
435 	return 0;
436 }
437 
438 
OnGlobalSearchTimer(CTimerEvent & WXUNUSED (evt))439 void CSearchList::OnGlobalSearchTimer(CTimerEvent& WXUNUSED(evt))
440 {
441 	// Ensure that the server-queue contains the current servers.
442 	if (m_searchPacket == NULL) {
443 		// This was a pending event, handled after 'Stop' was pressed.
444 		return;
445 	} else if (!m_serverQueue.IsActive()) {
446 		theApp->serverlist->AddObserver(&m_serverQueue);
447 	}
448 
449 	// UDP requests must not be sent to this server.
450 	const CServer* localServer = theApp->serverconnect->GetCurrentServer();
451 	if (localServer) {
452 		uint32 localIP = localServer->GetIP();
453 		uint16 localPort = localServer->GetPort();
454 		while (m_serverQueue.GetRemaining()) {
455 			CServer* server = m_serverQueue.GetNext();
456 
457 			// Compare against the currently connected server.
458 			if ((server->GetPort() == localPort) && (server->GetIP() == localIP)) {
459 				// We've already requested from the local server.
460 				continue;
461 			} else {
462 				if (server->SupportsLargeFilesUDP() && (server->GetUDPFlags() & SRV_UDPFLG_EXT_GETFILES)) {
463 					CMemFile data(50);
464 					uint32_t tagCount = 1;
465 					data.WriteUInt32(tagCount);
466 					CTagVarInt flags(CT_SERVER_UDPSEARCH_FLAGS, SRVCAP_UDP_NEWTAGS_LARGEFILES);
467 					flags.WriteNewEd2kTag(&data);
468 					CPacket *extSearchPacket = new CPacket(OP_GLOBSEARCHREQ3, m_searchPacket->GetPacketSize() + (uint32_t)data.GetLength(), OP_EDONKEYPROT);
469 					extSearchPacket->CopyToDataBuffer(0, data.GetRawBuffer(), data.GetLength());
470 					extSearchPacket->CopyToDataBuffer(data.GetLength(), m_searchPacket->GetDataBuffer(), m_searchPacket->GetPacketSize());
471 					theStats::AddUpOverheadServer(extSearchPacket->GetPacketSize());
472 					theApp->serverconnect->SendUDPPacket(extSearchPacket, server, true);
473 					AddDebugLogLineN(logServerUDP, wxT("Sending OP_GLOBSEARCHREQ3 to server ") + Uint32_16toStringIP_Port(server->GetIP(), server->GetPort()));
474 				} else if (server->GetUDPFlags() & SRV_UDPFLG_EXT_GETFILES) {
475 					if (!m_64bitSearchPacket || server->SupportsLargeFilesUDP()) {
476 						m_searchPacket->SetOpCode(OP_GLOBSEARCHREQ2);
477 						AddDebugLogLineN(logServerUDP, wxT("Sending OP_GLOBSEARCHREQ2 to server ") + Uint32_16toStringIP_Port(server->GetIP(), server->GetPort()));
478 						theStats::AddUpOverheadServer(m_searchPacket->GetPacketSize());
479 						theApp->serverconnect->SendUDPPacket(m_searchPacket, server, false);
480 					} else {
481 						AddDebugLogLineN(logServerUDP, wxT("Skipped UDP search on server ") + Uint32_16toStringIP_Port(server->GetIP(), server->GetPort()) + wxT(": No large file support"));
482 					}
483 				} else {
484 					if (!m_64bitSearchPacket || server->SupportsLargeFilesUDP()) {
485 						m_searchPacket->SetOpCode(OP_GLOBSEARCHREQ);
486 						AddDebugLogLineN(logServerUDP, wxT("Sending OP_GLOBSEARCHREQ to server ") + Uint32_16toStringIP_Port(server->GetIP(), server->GetPort()));
487 						theStats::AddUpOverheadServer(m_searchPacket->GetPacketSize());
488 						theApp->serverconnect->SendUDPPacket(m_searchPacket, server, false);
489 					} else {
490 						AddDebugLogLineN(logServerUDP, wxT("Skipped UDP search on server ") + Uint32_16toStringIP_Port(server->GetIP(), server->GetPort()) + wxT(": No large file support"));
491 					}
492 				}
493 				CoreNotify_Search_Update_Progress(GetSearchProgress());
494 				return;
495 			}
496 		}
497 	}
498 	// No more servers left to ask.
499 	StopSearch(true);
500 }
501 
502 
ProcessSharedFileList(const byte * in_packet,uint32 size,CUpDownClient * sender,bool * moreResultsAvailable,const wxString & directory)503 void CSearchList::ProcessSharedFileList(const byte* in_packet, uint32 size,
504 	CUpDownClient* sender, bool *moreResultsAvailable, const wxString& directory)
505 {
506 	wxCHECK_RET(sender, wxT("No sender in search-results from client."));
507 
508 	long searchID = reinterpret_cast<wxUIntPtr>(sender);
509 
510 #ifndef AMULE_DAEMON
511 	if (!theApp->amuledlg->m_searchwnd->CheckTabNameExists(sender->GetUserName())) {
512 		theApp->amuledlg->m_searchwnd->CreateNewTab(sender->GetUserName() + wxT(" (0)"), searchID);
513 	}
514 #endif
515 
516 	const CMemFile packet(in_packet, size);
517 	uint32 results = packet.ReadUInt32();
518 	bool unicoded = (sender->GetUnicodeSupport() != utf8strNone);
519 	for (unsigned int i = 0; i != results; ++i){
520 		CSearchFile* toadd = new CSearchFile(packet, unicoded, searchID, 0, 0, directory);
521 		toadd->SetClientID(sender->GetUserIDHybrid());
522 		toadd->SetClientPort(sender->GetUserPort());
523 		AddToList(toadd, true);
524 	}
525 
526 	if (moreResultsAvailable)
527 		*moreResultsAvailable = false;
528 
529 	int iAddData = (int)(packet.GetLength() - packet.GetPosition());
530 	if (iAddData == 1) {
531 		uint8 ucMore = packet.ReadUInt8();
532 		if (ucMore == 0x00 || ucMore == 0x01){
533 			if (moreResultsAvailable) {
534 				*moreResultsAvailable = (ucMore == 1);
535 			}
536 		}
537 	}
538 }
539 
540 
ProcessSearchAnswer(const uint8_t * in_packet,uint32_t size,bool optUTF8,uint32_t serverIP,uint16_t serverPort)541 void CSearchList::ProcessSearchAnswer(const uint8_t* in_packet, uint32_t size, bool optUTF8, uint32_t serverIP, uint16_t serverPort)
542 {
543 	CMemFile packet(in_packet, size);
544 
545 	uint32_t results = packet.ReadUInt32();
546 	for (; results > 0; --results) {
547 		AddToList(new CSearchFile(packet, optUTF8, m_currentSearch, serverIP, serverPort), false);
548 	}
549 }
550 
551 
ProcessUDPSearchAnswer(const CMemFile & packet,bool optUTF8,uint32_t serverIP,uint16_t serverPort)552 void CSearchList::ProcessUDPSearchAnswer(const CMemFile& packet, bool optUTF8, uint32_t serverIP, uint16_t serverPort)
553 {
554 	AddToList(new CSearchFile(packet, optUTF8, m_currentSearch, serverIP, serverPort), false);
555 }
556 
557 
AddToList(CSearchFile * toadd,bool clientResponse)558 bool CSearchList::AddToList(CSearchFile* toadd, bool clientResponse)
559 {
560 	const uint64 fileSize = toadd->GetFileSize();
561 	// If filesize is 0, or file is too large for the network, drop it
562 	if ((fileSize == 0) || (fileSize > MAX_FILE_SIZE)) {
563 		AddDebugLogLineN(logSearch,
564 				CFormat(wxT("Dropped result with filesize %u: %s"))
565 					% fileSize
566 					% toadd->GetFileName());
567 
568 		delete toadd;
569 		return false;
570 	}
571 
572 	// If the result was not the type the user wanted, drop it.
573 	if ((clientResponse == false) && !m_resultType.IsEmpty()) {
574 		if (GetFileTypeByName(toadd->GetFileName()) != m_resultType) {
575 			AddDebugLogLineN(logSearch,
576 				CFormat( wxT("Dropped result type %s != %s, file %s") )
577 					% GetFileTypeByName(toadd->GetFileName())
578 					% m_resultType
579 					% toadd->GetFileName());
580 
581 			delete toadd;
582 			return false;
583 		}
584 	}
585 
586 
587 	// Get, or implictly create, the map of results for this search
588 	CSearchResultList& results = m_results[toadd->GetSearchID()];
589 
590 	for (size_t i = 0; i < results.size(); ++i) {
591 		CSearchFile* item = results.at(i);
592 
593 		if ((toadd->GetFileHash() == item->GetFileHash()) && (toadd->GetFileSize() == item->GetFileSize())) {
594 			AddDebugLogLineN(logSearch, CFormat(wxT("Received duplicate results for '%s' : %s")) % item->GetFileName() % item->GetFileHash().Encode());
595 			// Add the child, possibly updating the parents filename.
596 			item->AddChild(toadd);
597 			Notify_Search_Update_Sources(item);
598 			return true;
599 		}
600 	}
601 
602 	AddDebugLogLineN(logSearch,
603 		CFormat(wxT("Added new result '%s' : %s"))
604 			% toadd->GetFileName() % toadd->GetFileHash().Encode());
605 
606 	// New unique result, simply add and display.
607 	results.push_back(toadd);
608 	Notify_Search_Add_Result(toadd);
609 
610 	return true;
611 }
612 
613 
GetSearchResults(long searchID) const614 const CSearchResultList& CSearchList::GetSearchResults(long searchID) const
615 {
616 	ResultMap::const_iterator it = m_results.find(searchID);
617 	if (it != m_results.end()) {
618 		return it->second;
619 	}
620 
621 	// TODO: Should we assert in this case?
622 	static CSearchResultList list;
623 	return list;
624 }
625 
626 
AddFileToDownloadByHash(const CMD4Hash & hash,uint8 cat)627 void CSearchList::AddFileToDownloadByHash(const CMD4Hash& hash, uint8 cat)
628 {
629 	ResultMap::iterator it = m_results.begin();
630 	for ( ; it != m_results.end(); ++it ) {
631 		CSearchResultList& list = it->second;
632 
633 		for ( unsigned int i = 0; i < list.size(); ++i ) {
634 			if ( list[i]->GetFileHash() == hash ) {
635 				CoreNotify_Search_Add_Download( list[i], cat );
636 
637 				return;
638 			}
639 		}
640 	}
641 }
642 
643 
StopSearch(bool globalOnly)644 void CSearchList::StopSearch(bool globalOnly)
645 {
646 	if (m_searchType == GlobalSearch) {
647 		m_currentSearch = -1;
648 		delete m_searchPacket;
649 		m_searchPacket = NULL;
650 		m_searchInProgress = false;
651 
652 		// Order is crucial here: on wx_MSW an additional event can be generated during the stop.
653 		// So the packet has to be deleted first, so that OnGlobalSearchTimer() returns immediately
654 		// without calling StopGlobalSearch() again.
655 		m_searchTimer.Stop();
656 
657 		CoreNotify_Search_Update_Progress(0xffff);
658 	} else if (m_searchType == KadSearch && !globalOnly) {
659 		Kademlia::CSearchManager::StopSearch(m_currentSearch, false);
660 		m_currentSearch = -1;
661 	}
662 }
663 
664 
CreateSearchData(CSearchParams & params,SearchType type,bool supports64bit,bool & packetUsing64bit)665 CSearchList::CMemFilePtr CSearchList::CreateSearchData(CSearchParams& params, SearchType type, bool supports64bit, bool& packetUsing64bit)
666 {
667 	// Count the number of used parameters
668 	unsigned int parametercount = 0;
669 	if ( !params.typeText.IsEmpty() )	++parametercount;
670 	if ( params.minSize > 0 )			++parametercount;
671 	if ( params.maxSize > 0 )			++parametercount;
672 	if ( params.availability > 0 )		++parametercount;
673 	if ( !params.extension.IsEmpty() )	++parametercount;
674 
675 	wxString typeText = params.typeText;
676 	if (typeText == ED2KFTSTR_ARCHIVE){
677 		// eDonkeyHybrid 0.48 uses type "Pro" for archives files
678 		// www.filedonkey.com uses type "Pro" for archives files
679 		typeText = ED2KFTSTR_PROGRAM;
680 	} else if (typeText == ED2KFTSTR_CDIMAGE){
681 		// eDonkeyHybrid 0.48 uses *no* type for iso/nrg/cue/img files
682 		// www.filedonkey.com uses type "Pro" for CD-image files
683 		typeText = ED2KFTSTR_PROGRAM;
684 	}
685 
686 	// Must write parametercount - 1 parameter headers
687 	CMemFilePtr data(new CMemFile(100));
688 
689 	_astrParserErrors.Empty();
690 	_SearchExpr.m_aExpr.Empty();
691 
692 	s_strCurKadKeyword.Clear();
693 	if (type == KadSearch) {
694 		wxASSERT( !params.strKeyword.IsEmpty() );
695 		s_strCurKadKeyword = params.strKeyword;
696 	}
697 
698 	LexInit(params.searchString);
699 	int iParseResult = yyparse();
700 	LexFree();
701 
702 	if (_astrParserErrors.GetCount() > 0) {
703 		for (unsigned int i=0; i < _astrParserErrors.GetCount(); ++i) {
704 			AddLogLineNS(CFormat(wxT("Error %u: %s\n")) % i % _astrParserErrors[i]);
705 		}
706 
707 		return CMemFilePtr(nullptr);
708 	}
709 
710 	if (iParseResult != 0) {
711 		_astrParserErrors.Add(CFormat(wxT("Undefined error %i on search expression")) % iParseResult);
712 
713 		return CMemFilePtr(nullptr);
714 	}
715 
716 	if (type == KadSearch && s_strCurKadKeyword != params.strKeyword) {
717 		AddDebugLogLineN(logSearch, CFormat(wxT("Keyword was rearranged, using '%s' instead of '%s'")) % s_strCurKadKeyword % params.strKeyword);
718 		params.strKeyword = s_strCurKadKeyword;
719 	}
720 
721 	parametercount += _SearchExpr.m_aExpr.GetCount();
722 
723 	/* Leave the unicode comment there, please... */
724 	CSearchExprTarget target(data.get(), true /*I assume everyone is unicoded */ ? utf8strRaw : utf8strNone, supports64bit, packetUsing64bit);
725 
726 	unsigned int iParameterCount = 0;
727 	if (_SearchExpr.m_aExpr.GetCount() <= 1) {
728 		// lugdunummaster requested that searchs without OR or NOT operators,
729 		// and hence with no more expressions than the string itself, be sent
730 		// using a series of ANDed terms, intersecting the ANDs on the terms
731 		// (but prepending them) instead of putting the boolean tree at the start
732 		// like other searches. This type of search is supposed to take less load
733 		// on servers. Go figure.
734 		//
735 		// input:      "a" AND min=1 AND max=2
736 		// instead of: AND AND "a" min=1 max=2
737 		// we use:     AND "a" AND min=1 max=2
738 
739 		if (_SearchExpr.m_aExpr.GetCount() > 0) {
740 			if (++iParameterCount < parametercount) {
741 				target.WriteBooleanAND();
742 			}
743 			target.WriteMetaDataSearchParam(_SearchExpr.m_aExpr[0]);
744 		}
745 
746 		if (!typeText.IsEmpty()) {
747 			if (++iParameterCount < parametercount) {
748 				target.WriteBooleanAND();
749 			}
750 			// Type is always ascii string
751 			target.WriteMetaDataSearchParamASCII(FT_FILETYPE, typeText);
752 		}
753 
754 		if (params.minSize > 0) {
755 			if (++iParameterCount < parametercount) {
756 				target.WriteBooleanAND();
757 			}
758 			target.WriteMetaDataSearchParam(FT_FILESIZE, ED2K_SEARCH_OP_GREATER, params.minSize);
759 		}
760 
761 		if (params.maxSize > 0){
762 			if (++iParameterCount < parametercount) {
763 				target.WriteBooleanAND();
764 			}
765 			target.WriteMetaDataSearchParam(FT_FILESIZE, ED2K_SEARCH_OP_LESS, params.maxSize);
766 		}
767 
768 		if (params.availability > 0){
769 			if (++iParameterCount < parametercount) {
770 				target.WriteBooleanAND();
771 			}
772 			target.WriteMetaDataSearchParam(FT_SOURCES, ED2K_SEARCH_OP_GREATER, params.availability);
773 		}
774 
775 		if (!params.extension.IsEmpty()){
776 			if (++iParameterCount < parametercount) {
777 				target.WriteBooleanAND();
778 			}
779 			target.WriteMetaDataSearchParam(FT_FILEFORMAT, params.extension);
780 		}
781 
782 		//#warning TODO - I keep this here, ready if we ever allow such searches...
783 		#if 0
784 		if (complete > 0){
785 			if (++iParameterCount < parametercount) {
786 				target.WriteBooleanAND();
787 			}
788 			target.WriteMetaDataSearchParam(FT_COMPLETE_SOURCES, ED2K_SEARCH_OP_GREATER, complete);
789 		}
790 
791 		if (minBitrate > 0){
792 			if (++iParameterCount < parametercount) {
793 				target.WriteBooleanAND();
794 			}
795 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_BITRATE : FT_ED2K_MEDIA_BITRATE, ED2K_SEARCH_OP_GREATER, minBitrate);
796 		}
797 
798 		if (minLength > 0){
799 			if (++iParameterCount < parametercount) {
800 				target.WriteBooleanAND();
801 			}
802 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_LENGTH : FT_ED2K_MEDIA_LENGTH, ED2K_SEARCH_OP_GREATER, minLength);
803 		}
804 
805 		if (!codec.IsEmpty()){
806 			if (++iParameterCount < parametercount) {
807 				target.WriteBooleanAND();
808 			}
809 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_CODEC : FT_ED2K_MEDIA_CODEC, codec);
810 		}
811 
812 		if (!title.IsEmpty()){
813 			if (++iParameterCount < parametercount) {
814 				target.WriteBooleanAND();
815 			}
816 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_TITLE : FT_ED2K_MEDIA_TITLE, title);
817 		}
818 
819 		if (!album.IsEmpty()){
820 			if (++iParameterCount < parametercount) {
821 				target.WriteBooleanAND();
822 			}
823 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_ALBUM : FT_ED2K_MEDIA_ALBUM, album);
824 		}
825 
826 		if (!artist.IsEmpty()){
827 			if (++iParameterCount < parametercount) {
828 				target.WriteBooleanAND();
829 			}
830 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_ARTIST : FT_ED2K_MEDIA_ARTIST, artist);
831 		}
832 		#endif // 0
833 
834 		// If this assert fails... we're seriously fucked up
835 
836 		wxASSERT( iParameterCount == parametercount );
837 
838 	} else {
839 		if (!params.extension.IsEmpty()) {
840 			if (++iParameterCount < parametercount) {
841 				target.WriteBooleanAND();
842 			}
843 		}
844 
845 		if (params.availability > 0) {
846 			if (++iParameterCount < parametercount) {
847 				target.WriteBooleanAND();
848 			}
849 		}
850 
851 		if (params.maxSize > 0){
852 			if (++iParameterCount < parametercount) {
853 				target.WriteBooleanAND();
854 			}
855 		}
856 
857 		if (params.minSize > 0) {
858 			if (++iParameterCount < parametercount) {
859 				target.WriteBooleanAND();
860 			}
861 		}
862 
863 		if (!typeText.IsEmpty()){
864 			if (++iParameterCount < parametercount) {
865 				target.WriteBooleanAND();
866 			}
867 		}
868 
869 		//#warning TODO - same as above...
870 		#if 0
871 		if (complete > 0){
872 			if (++iParameterCount < parametercount) {
873 				target.WriteBooleanAND();
874 			}
875 		}
876 
877 		if (minBitrate > 0){
878 			if (++iParameterCount < parametercount) {
879 				target.WriteBooleanAND();
880 			}
881 		}
882 
883 		if (minLength > 0) {
884 			if (++iParameterCount < parametercount) {
885 				target.WriteBooleanAND();
886 			}
887 		}
888 
889 		if (!codec.IsEmpty()){
890 			if (++iParameterCount < parametercount) {
891 				target.WriteBooleanAND();
892 			}
893 		}
894 
895 		if (!title.IsEmpty()){
896 			if (++iParameterCount < parametercount) {
897 				target.WriteBooleanAND();
898 			}
899 		}
900 
901 		if (!album.IsEmpty()) {
902 			if (++iParameterCount < parametercount) {
903 				target.WriteBooleanAND();
904 			}
905 		}
906 
907 		if (!artist.IsEmpty()) {
908 			if (++iParameterCount < parametercount) {
909 				target.WriteBooleanAND();
910 			}
911 		}
912 		#endif // 0
913 
914 		// As above, if this fails, we're seriously fucked up.
915 		wxASSERT( iParameterCount + _SearchExpr.m_aExpr.GetCount() == parametercount );
916 
917 		for (unsigned int j = 0; j < _SearchExpr.m_aExpr.GetCount(); ++j) {
918 			if (_SearchExpr.m_aExpr[j] == SEARCHOPTOK_AND) {
919 				target.WriteBooleanAND();
920 			} else if (_SearchExpr.m_aExpr[j] == SEARCHOPTOK_OR) {
921 				target.WriteBooleanOR();
922 			} else if (_SearchExpr.m_aExpr[j] == SEARCHOPTOK_NOT) {
923 				target.WriteBooleanNOT();
924 			} else {
925 				target.WriteMetaDataSearchParam(_SearchExpr.m_aExpr[j]);
926 			}
927 		}
928 
929 		if (!params.typeText.IsEmpty()) {
930 			// Type is always ASCII string
931 			target.WriteMetaDataSearchParamASCII(FT_FILETYPE, params.typeText);
932 		}
933 
934 		if (params.minSize > 0) {
935 			target.WriteMetaDataSearchParam(FT_FILESIZE, ED2K_SEARCH_OP_GREATER, params.minSize);
936 		}
937 
938 		if (params.maxSize > 0) {
939 			target.WriteMetaDataSearchParam(FT_FILESIZE, ED2K_SEARCH_OP_LESS, params.maxSize);
940 		}
941 
942 		if (params.availability > 0) {
943 			target.WriteMetaDataSearchParam(FT_SOURCES, ED2K_SEARCH_OP_GREATER, params.availability);
944 		}
945 
946 		if (!params.extension.IsEmpty()) {
947 			target.WriteMetaDataSearchParam(FT_FILEFORMAT, params.extension);
948 		}
949 
950 		//#warning TODO - third and last warning of the same series.
951 		#if 0
952 		if (complete > 0) {
953 			target.WriteMetaDataSearchParam(FT_COMPLETE_SOURCES, ED2K_SEARCH_OP_GREATER, pParams->uComplete);
954 		}
955 
956 		if (minBitrate > 0) {
957 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_BITRATE : FT_ED2K_MEDIA_BITRATE, ED2K_SEARCH_OP_GREATER, minBitrate);
958 		}
959 
960 		if (minLength > 0) {
961 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_LENGTH : FT_ED2K_MEDIA_LENGTH, ED2K_SEARCH_OP_GREATER, minLength);
962 		}
963 
964 		if (!codec.IsEmpty()) {
965 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_CODEC : FT_ED2K_MEDIA_CODEC, codec);
966 		}
967 
968 		if (!title.IsEmpty()) {
969 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_TITLE : FT_ED2K_MEDIA_TITLE, title);
970 		}
971 
972 		if (!album.IsEmpty()) {
973 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_ALBUM : FT_ED2K_MEDIA_ALBUM, album);
974 		}
975 
976 		if (!artist.IsEmpty()) {
977 			target.WriteMetaDataSearchParam(type == KadSearch ? TAG_MEDIA_ARTIST : FT_ED2K_MEDIA_ARTIST, artist);
978 		}
979 
980 		#endif // 0
981 	}
982 
983 	// Packet ready to go.
984 	return data;
985 }
986 
987 
KademliaSearchKeyword(uint32_t searchID,const Kademlia::CUInt128 * fileID,const wxString & name,uint64_t size,const wxString & type,uint32_t kadPublishInfo,const TagPtrList & taglist)988 void CSearchList::KademliaSearchKeyword(uint32_t searchID, const Kademlia::CUInt128 *fileID,
989 	const wxString& name, uint64_t size, const wxString& type, uint32_t kadPublishInfo, const TagPtrList& taglist)
990 {
991 	EUtf8Str eStrEncode = utf8strRaw;
992 
993 	CMemFile temp(250);
994 	byte fileid[16];
995 	fileID->ToByteArray(fileid);
996 	temp.WriteHash(CMD4Hash(fileid));
997 
998 	temp.WriteUInt32(0);	// client IP
999 	temp.WriteUInt16(0);	// client port
1000 
1001 	// write tag list
1002 	unsigned int uFilePosTagCount = temp.GetPosition();
1003 	uint32 tagcount = 0;
1004 	temp.WriteUInt32(tagcount); // dummy tag count, will be filled later
1005 
1006 	// standard tags
1007 	CTagString tagName(FT_FILENAME, name);
1008 	tagName.WriteTagToFile(&temp, eStrEncode);
1009 	tagcount++;
1010 
1011 	CTagInt64 tagSize(FT_FILESIZE, size);
1012 	tagSize.WriteTagToFile(&temp, eStrEncode);
1013 	tagcount++;
1014 
1015 	if (!type.IsEmpty()) {
1016 		CTagString tagType(FT_FILETYPE, type);
1017 		tagType.WriteTagToFile(&temp, eStrEncode);
1018 		tagcount++;
1019 	}
1020 
1021 	// Misc tags (bitrate, etc)
1022 	for (TagPtrList::const_iterator it = taglist.begin(); it != taglist.end(); ++it) {
1023 		(*it)->WriteTagToFile(&temp,eStrEncode);
1024 		tagcount++;
1025 	}
1026 
1027 	temp.Seek(uFilePosTagCount, wxFromStart);
1028 	temp.WriteUInt32(tagcount);
1029 
1030 	temp.Seek(0, wxFromStart);
1031 
1032 	CSearchFile *tempFile = new CSearchFile(temp, (eStrEncode == utf8strRaw), searchID, 0, 0, wxEmptyString, true);
1033 	tempFile->SetKadPublishInfo(kadPublishInfo);
1034 
1035 	AddToList(tempFile);
1036 }
1037 
UpdateSearchFileByHash(const CMD4Hash & hash)1038 void CSearchList::UpdateSearchFileByHash(const CMD4Hash& hash)
1039 {
1040 	for (ResultMap::iterator it = m_results.begin(); it != m_results.end(); ++it) {
1041 		CSearchResultList& results = it->second;
1042 		for (size_t i = 0; i < results.size(); ++i) {
1043 			CSearchFile* item = results.at(i);
1044 
1045 			if (hash == item->GetFileHash()) {
1046 				// This covers only parent items,
1047 				// child items have to be updated separately.
1048 				Notify_Search_Update_Sources(item);
1049 			}
1050 		}
1051 	}
1052 }
1053 
1054 // File_checked_for_headers
1055