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