1 // ----------------------------------------------------------------------------
2 // lookupcall.cxx  -- a part of fldigi
3 //
4 // Copyright (C) 2006-2009
5 //		Dave Freese, W1HKJ
6 // Copyright (C) 2006-2007
7 //		Leigh Klotz, WA5ZNU
8 // Copyright (C) 2008-2009
9 //              Stelios Bounanos, M0GLD
10 //
11 // This file is part of fldigi.
12 //
13 // Fldigi is free software: you can redistribute it and/or modify
14 // it under the terms of the GNU General Public License as published by
15 // the Free Software Foundation, either version 3 of the License, or
16 // (at your option) any later version.
17 //
18 // Fldigi is distributed in the hope that it will be useful,
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 // GNU General Public License for more details.
22 //
23 // You should have received a copy of the GNU General Public License
24 // along with fldigi.  If not, see <http://www.gnu.org/licenses/>.
25 // ----------------------------------------------------------------------------
26 
27 #include <config.h>
28 
29 #ifdef __MINGW32__
30 #  include "compat.h"
31 #endif
32 
33 #include <sys/time.h>
34 #include "signal.h"
35 #include <string>
36 #include <iostream>
37 #include <cstring>
38 #include <cmath>
39 #include <cctype>
40 
41 #include "threads.h"
42 
43 #include "misc.h"
44 #include "configuration.h"
45 
46 #include "lookupcall.h"
47 #include "logsupport.h"
48 #include "main.h"
49 #include "confdialog.h"
50 #include "fl_digi.h"
51 #include "flmisc.h"
52 #include "qrzlib.h"
53 #include "trx.h"
54 
55 #include "xmlreader.h"
56 
57 #include "qrunner.h"
58 #include "debug.h"
59 #include "network.h"
60 #include "locator.h"
61 
62 using namespace std;
63 
64 
65 string qrzhost = "xmldata.qrz.com";
66 string qrzSessionKey;
67 string qrzalert;
68 string qrzerror;
69 
70 string callsign;
71 
72 string lookup_name;
73 string lookup_addr1;
74 string lookup_addr2;
75 string lookup_state;
76 string lookup_province;
77 string lookup_zip;
78 string lookup_country;
79 string lookup_born;
80 string lookup_fname;
81 string lookup_qth;
82 string lookup_grid;
83 string lookup_latd;
84 string lookup_lond;
85 string lookup_notes;
86 
87 qrz_xmlquery_t DB_XML_query = QRZXMLNONE;
88 qrz_webquery_t DB_WEB_query = QRZWEBNONE;
89 
90 enum TAG {
91 	QRZ_IGNORE,	QRZ_KEY,	QRZ_ALERT,	QRZ_ERROR,	QRZ_CALL,
92 	QRZ_FNAME,	QRZ_NAME,	QRZ_ADDR1,	QRZ_ADDR2,	QRZ_STATE,
93 	QRZ_ZIP,	QRZ_COUNTRY,	QRZ_LATD,	QRZ_LOND,	QRZ_GRID,
94 	QRZ_DOB
95 };
96 
97 pthread_t* QRZ_thread = 0;
98 pthread_mutex_t qrz_mutex = PTHREAD_MUTEX_INITIALIZER;
99 pthread_cond_t qrz_cond = PTHREAD_COND_INITIALIZER;
100 
101 static void *LOOKUP_loop(void *args);
102 
103 bool parseSessionKey();
104 bool parse_xml();
105 
106 bool getSessionKey(string& sessionpage);
107 bool QRZGetXML(string& xmlpage);
108 int  bearing(const char *, const char *);
109 void qra(const char *, double &, double &);
110 void QRZ_disp_result();
111 void QRZ_CD_query();
112 void Lookup_init(void);
113 void QRZclose(void);
114 void qthappend(string &qth, string &datum);
115 void QRZAlert();
116 bool QRZLogin(string& sessionpage);
117 void QRZquery();
118 void parse_html(const string& htmlpage);
119 bool HAMCALLget(string& htmlpage);
120 void HAMCALLquery();
121 
122 void QRZ_DETAILS_query();
123 
124 QRZ *qCall = 0;
125 
126 static notify_dialog *announce = 0;
127 
print_query(const string & name,const string & s)128 void print_query(const string &name, const string &s)
129 {
130 //	LOG_WARN("%s query:\n%s", name.c_str(), s.c_str());
131 	LOG_VERBOSE("%s query:\n%s", name.c_str(), s.c_str());
132 }
133 
print_data(const string & name,const string & s)134 void print_data(const string &name, const string &s) {
135 //	LOG_WARN("%s data:\n%s", name.c_str(), s.c_str());
136 	LOG_VERBOSE("%s data:\n%s", name.c_str(), s.c_str());
137 }
138 
clear_Lookup()139 void clear_Lookup()
140 {
141 	lookup_name.clear();
142 	lookup_addr1.clear();
143 	lookup_addr2.clear();
144 	lookup_state.clear();
145 	lookup_province.clear();
146 	lookup_zip.clear();
147 	lookup_born.clear();
148 	lookup_fname.clear();
149 	lookup_qth.clear();
150 	lookup_grid.clear();
151 	lookup_latd.clear();
152 	lookup_lond.clear();
153 	lookup_notes.clear();
154 	lookup_country.clear();
155 }
156 
157 // ----------------------------------------------------------------------------
158 // QRZ subscription query
159 // ----------------------------------------------------------------------------
160 
parseSessionKey(const string & sessionpage)161 bool parseSessionKey(const string& sessionpage)
162 {
163 	if (sessionpage.find("Bad Request") != string::npos) {
164 		return false;
165 	}
166 
167 	IrrXMLReader* xml = createIrrXMLReader(new IIrrXMLStringReader(sessionpage));
168 	TAG tag=QRZ_IGNORE;
169 	while(xml && xml->read()) {
170 		switch(xml->getNodeType())
171 		{
172 		case EXN_TEXT:
173 		case EXN_CDATA:
174 			switch (tag)
175 			{
176 			default:
177 				break;
178 			case QRZ_KEY:
179 				qrzSessionKey = xml->getNodeData();
180 				break;
181 			case QRZ_ALERT:
182 				qrzalert = xml->getNodeData();
183 				break;
184 			case QRZ_ERROR:
185 				qrzerror = xml->getNodeData();
186 				break;
187 			}
188 			break;
189 
190 		case EXN_ELEMENT_END:
191 			tag=QRZ_IGNORE;
192 			break;
193 
194 		case EXN_ELEMENT:
195 		{
196 			const char *nodeName = xml->getNodeName();
197 			if (!strcmp("Key", nodeName)) tag=QRZ_KEY;
198 			else if (!strcmp("Alert", nodeName)) tag=QRZ_ALERT;
199 			else if (!strcmp("Error", nodeName)) tag=QRZ_ERROR;
200 			else tag=QRZ_IGNORE;
201 			break;
202 		}
203 
204 		case EXN_NONE:
205 		case EXN_COMMENT:
206 		case EXN_UNKNOWN:
207 			break;
208 		}
209 	}
210 	delete xml;
211 	return true;
212 }
213 
214 
parse_xml(const string & xmlpage)215 bool parse_xml(const string& xmlpage)
216 {
217 	print_data("xmldata.qrz.com", xmlpage);
218 
219 	if (xmlpage.find("<Error>Not found") != std::string::npos) {
220 		if (!announce) announce = new notify_dialog;
221 		announce->notify("Not found", 2.0);
222 		REQ(show_notifier, announce);
223 		return false;
224 	}
225 
226 	IrrXMLReader* xml = createIrrXMLReader(new IIrrXMLStringReader(xmlpage));
227 
228 // If we got any result back, clear the session key so that it will be
229 // refreshed by this response, or if not present, will be removed and we'll
230 // know to log in next time.
231 	if (xml) {
232 		qrzSessionKey.clear();
233 		qrzalert.clear();
234 		qrzerror.clear();
235 		clear_Lookup();
236 	}
237 
238 	TAG tag = QRZ_IGNORE;
239 
240 // parse the file until end reached
241 	while(xml && xml->read()) {
242 		switch(xml->getNodeType()) {
243 			case EXN_TEXT:
244 			case EXN_CDATA:
245 				switch (tag) {
246 					default:
247 					case QRZ_IGNORE:
248 						break;
249 					case QRZ_CALL:
250 						break;
251 					case QRZ_FNAME:
252 						lookup_fname =  xml->getNodeData();
253 						break;
254 					case QRZ_NAME:
255 						lookup_name =  xml->getNodeData();
256 						break;
257 					case QRZ_ADDR1:
258 						{
259 						lookup_addr1 =  xml->getNodeData();
260 						size_t apt = lookup_addr1.find("#");
261 						if (apt != string::npos)
262 							lookup_addr1.erase(apt, lookup_addr1.length() - apt);
263 						break;
264 						}
265 					case QRZ_ADDR2:
266 						lookup_addr2 =  xml->getNodeData();
267 						break;
268 					case QRZ_STATE:
269 						lookup_state =  xml->getNodeData();
270 						break;
271 					case QRZ_ZIP:
272 						lookup_zip =  xml->getNodeData();
273 						break;
274 					case QRZ_COUNTRY:
275 						lookup_country =  xml->getNodeData();
276 						break;
277 					case QRZ_LATD:
278 						lookup_latd =  xml->getNodeData();
279 						break;
280 					case QRZ_LOND:
281 						lookup_lond =  xml->getNodeData();
282 						break;
283 					case QRZ_GRID:
284 						lookup_grid =  xml->getNodeData();
285 						break;
286 					case QRZ_ALERT:
287 						qrzalert = xml->getNodeData();
288 						break;
289 					case QRZ_ERROR:
290 						qrzerror = xml->getNodeData();
291 						break;
292 					case QRZ_KEY:
293 						qrzSessionKey = xml->getNodeData();
294 						break;
295 				}
296 				break;
297 
298 			case EXN_ELEMENT_END:
299 				tag=QRZ_IGNORE;
300 				break;
301 
302 			case EXN_ELEMENT:
303 				{
304 				const char *nodeName = xml->getNodeName();
305 				if (!strcmp("call", nodeName)) 			tag = QRZ_CALL;
306 				else if (!strcmp("fname", nodeName)) 	tag = QRZ_FNAME;
307 				else if (!strcmp("name", nodeName)) 	tag = QRZ_NAME;
308 				else if (!strcmp("addr1", nodeName)) 	tag = QRZ_ADDR1;
309 				else if (!strcmp("addr2", nodeName)) 	tag = QRZ_ADDR2;
310 				else if (!strcmp("state", nodeName)) 	tag = QRZ_STATE;
311 				else if (!strcmp("zip", nodeName)) 		tag = QRZ_ZIP;
312 				else if (!strcmp("country", nodeName))	tag = QRZ_COUNTRY;
313 				else if (!strcmp("lat", nodeName)) 		tag = QRZ_LATD;
314 				else if (!strcmp("lon", nodeName)) 		tag = QRZ_LOND;
315 				else if (!strcmp("grid", nodeName)) 	tag = QRZ_GRID;
316 				else if (!strcmp("dob", nodeName)) 		tag = QRZ_DOB;
317 				else if (!strcmp("Alert", nodeName)) 	tag = QRZ_ALERT;
318 				else if (!strcmp("Error", nodeName)) 	tag = QRZ_ERROR;
319 				else if (!strcmp("Key", nodeName)) 		tag = QRZ_KEY;
320 				else tag = QRZ_IGNORE;
321 				}
322 				break;
323 
324 			case EXN_NONE:
325 			case EXN_COMMENT:
326 			case EXN_UNKNOWN:
327 				break;
328 		}
329 	}
330 
331 // delete the xml parser after usage
332 	delete xml;
333 	return true;
334 }
335 
getSessionKey(string & sessionpage)336 bool getSessionKey(string& sessionpage)
337 {
338 	string html = "http://";
339 	html.append(qrzhost);
340 	html.append(" /xml/current/?username=");
341 	html.append(progdefaults.QRZusername);
342 	html.append(";password=");
343 	html.append(progdefaults.QRZuserpassword);
344 	html.append(";agent=");
345 	html.append(PACKAGE_NAME);
346 	html.append("-");
347 	html.append(PACKAGE_VERSION);
348 	return get_http(html, sessionpage, 5.0);
349 }
350 
QRZGetXML(string & xmlpage)351 bool QRZGetXML(string& xmlpage)
352 {
353 	string html;
354 	html.assign("http://").append(qrzhost);
355 	html.append(" /bin/xml?s=");
356 	html.append(qrzSessionKey);
357 	html.append(";callsign=");
358 	html.append(callsign);
359 	return get_http(html, xmlpage, 5.0);
360 }
361 
camel_case(string & s)362 void camel_case(string &s)
363 {
364 	bool first_letter = true;
365 	for (size_t n = 0; n < s.length(); n++) {
366 		if (s[n] == ' ') first_letter = true;
367 		else if (first_letter) {
368 			s[n] = toupper(s[n]);
369 			first_letter = false;
370 		} else s[n] = tolower(s[n]);
371 	}
372 }
373 
QRZ_disp_result()374 void QRZ_disp_result()
375 {
376 	ENSURE_THREAD(FLMAIN_TID);
377 
378 	if (lookup_fname.length() > 0) {
379 		camel_case(lookup_fname);
380 		string::size_type spacePos = lookup_fname.find(" ");
381 		//    if fname is "ABC" then display "ABC"
382 		// or if fname is "A BCD" then display "A BCD"
383 		if (spacePos == string::npos || (spacePos == 1)) {
384 			inpName->value(lookup_fname.c_str());
385 		}
386 		// if fname is "ABC Y" then display "ABC"
387 		else if (spacePos > 2) {
388 			string fname;
389 			fname.assign(lookup_fname, 0, spacePos);
390 			inpName->value(fname.c_str());
391 		}
392 		// fname must be "ABC DEF" so display "ABC DEF"
393 		else {
394 			inpName->value(lookup_fname.c_str());
395 		}
396 	} else if (lookup_name.length() > 0) {
397 		// only name is set; don't know first/last, so just show all
398 		inpName->value(lookup_name.c_str());
399 	}
400 
401 	inpQth->value(lookup_qth.c_str());
402 	inpQth->position (0);
403 
404 	inpState->value(lookup_state.c_str());
405 	inpState->position (0);
406 
407 	inpVEprov->value(lookup_province.c_str());
408 	inpVEprov->position (0);
409 
410 	inpLoc->value(lookup_grid.c_str());
411 	inpLoc->position (0);
412 
413 	if (!lookup_country.empty()) {
414 		cboCountry->value(lookup_country.c_str());
415 	}
416 
417 	if (!progdefaults.myLocator.empty() && !lookup_grid.empty()) {
418 		char buf[10];
419 		buf[0] = '\0';
420 		double distance, azimuth, lon[2], lat[2];
421 		if (QRB::locator2longlat(&lon[0], &lat[0], progdefaults.myLocator.c_str()) == QRB::QRB_OK &&
422 		    QRB::locator2longlat(&lon[1], &lat[1], lookup_grid.c_str()) == QRB::QRB_OK &&
423 		    QRB::qrb(lon[0], lat[0], lon[1], lat[1], &distance, &azimuth) == QRB::QRB_OK)
424 			snprintf(buf, sizeof(buf), "%03.0f", round(azimuth));
425 		inpAZ->value(buf);
426 		inpAZ->position (0);
427 	}
428 	inpNotes->value(lookup_notes.c_str());
429 	inpNotes->position (0);
430 
431 }
432 
QRZ_CD_query()433 void QRZ_CD_query()
434 {
435 	ENSURE_THREAD(QRZ_TID);
436 
437 	char srch[20];
438 	size_t snip;
439 
440 	memset( srch, 0, sizeof(srch) );
441 	strncpy( srch, callsign.c_str(), 6 );
442 	for (size_t i = 0; i < strlen(srch); i ++ )
443 		srch[i] = toupper(srch[i]);
444 
445 	string notes;
446 	if (!progdefaults.clear_notes) notes.assign(inpNotes->value());
447 	else notes.clear();
448 
449 	if( qCall->FindRecord( srch ) == 1) {
450 		lookup_fname = qCall->GetFname();
451 		camel_case(lookup_fname);
452 		snip = lookup_fname.find(' ');
453 		if (snip != string::npos)
454 			lookup_fname.erase(snip, lookup_fname.length() - snip);
455 		lookup_qth = qCall->GetCity();
456 		lookup_state = qCall->GetState();
457 		lookup_grid.clear();
458 		if (progdefaults.notes_address) {
459 			if (!notes.empty()) notes.append("\n");
460 			notes.append(lookup_fname).append(" ").append(lookup_name).append("\n");
461 			notes.append(lookup_addr1).append("\n");
462 			notes.append(lookup_addr2);
463 			if (!lookup_state.empty())
464 				notes.append(", ").append(lookup_state).append("  ").append(lookup_zip);
465 			else if (!lookup_province.empty())
466 				notes.append(", ").append(lookup_province).append("  ").append(lookup_zip);
467 			else
468 				notes.append("  ").append(lookup_country);
469 		}
470 	} else {
471 		lookup_fname.clear();
472 		lookup_qth.clear();
473 		lookup_grid.clear();
474 		lookup_born.clear();
475 		lookup_notes.append("Not found in CD database");
476 	}
477 	REQ(QRZ_disp_result);
478 }
479 
Lookup_init(void)480 void Lookup_init(void)
481 {
482 	ENSURE_THREAD(FLMAIN_TID);
483 
484 	if (QRZ_thread)
485 		return;
486 	QRZ_thread = new pthread_t;
487 	if (pthread_create(QRZ_thread, NULL, LOOKUP_loop, NULL) != 0) {
488 		LOG_PERROR("pthread_create");
489 		return;
490 	}
491 	MilliSleep(10);
492 }
493 
QRZclose(void)494 void QRZclose(void)
495 {
496 	ENSURE_THREAD(FLMAIN_TID);
497 
498 	if (!QRZ_thread)
499 		return;
500 
501 	CANCEL_THREAD(*QRZ_thread);
502 
503 	DB_XML_query = QRZXML_EXIT;
504 	DB_WEB_query = QRZWEB_EXIT;
505 
506 	pthread_mutex_lock(&qrz_mutex);
507 	pthread_cond_signal(&qrz_cond);
508 	pthread_mutex_unlock(&qrz_mutex);
509 
510 	pthread_join(*QRZ_thread, NULL);
511 	delete QRZ_thread;
512 	QRZ_thread = 0;
513 }
514 
qthappend(string & qth,string & datum)515 void qthappend(string &qth, string &datum) {
516 	if (datum.empty()) return;
517 	if (!qth.empty()) qth += ", ";
518 	qth += datum;
519 }
520 
QRZAlert()521 void QRZAlert()
522 {
523 	ENSURE_THREAD(FLMAIN_TID);
524 
525 	string qrznote;
526 	if (!qrzalert.empty()) {
527 		qrznote.append("QRZ alert:\n");
528 		qrznote.append(qrzalert);
529 		qrznote.append("\n");
530 		qrzalert.clear();
531 	}
532 	if (!qrzerror.empty()) {
533 		qrznote.append("QRZ error:\n");
534 		qrznote.append(qrzerror);
535 		qrzerror.clear();
536 	}
537 	string notes;
538 	if (!progdefaults.clear_notes) notes.assign(inpNotes->value());
539 	else notes.clear();
540 
541 	if (!qrznote.empty()) {
542 		if (!notes.empty()) notes.append("\n");
543 		notes.append(qrznote);
544 		inpNotes->value(notes.c_str());
545 	}
546 }
547 
QRZLogin(string & sessionpage)548 bool QRZLogin(string& sessionpage)
549 {
550 	bool ok = true;
551 	if (qrzSessionKey.empty()) {
552 		ok = getSessionKey(sessionpage);
553 		if (ok) ok = parseSessionKey(sessionpage);
554 	}
555 	if (!ok) {
556 		LOG_VERBOSE("failed");
557 		REQ(QRZAlert);
558 	}
559 
560 	return ok;
561 }
562 
QRZquery()563 void QRZquery()
564 {
565 	ENSURE_THREAD(QRZ_TID);
566 
567 	bool ok = true;
568 
569 	string qrzpage;
570 
571 	if (qrzSessionKey.empty())
572 		ok = QRZLogin(qrzpage);
573 	if (ok)
574 		ok = QRZGetXML(qrzpage);
575 	if (ok) {
576 		parse_xml(qrzpage);
577 		if (!qrzalert.empty() || !qrzerror.empty())
578 			REQ(QRZAlert);
579 		else {
580 			lookup_qth = lookup_addr2;
581 			if (lookup_country.find("Canada") != string::npos) {
582 				lookup_province = lookup_state;
583 				lookup_state.clear();
584 			}
585 
586 			string notes;
587 			if (!progdefaults.clear_notes) notes.assign(inpNotes->value());
588 			else notes.clear();
589 
590 			if (progdefaults.notes_address) {
591 				if (!notes.empty()) notes.append("\n");
592 				notes.append(lookup_fname).append(" ").append(lookup_name).append("\n");
593 				notes.append(lookup_addr1).append("\n");
594 				notes.append(lookup_addr2);
595 				if (!lookup_state.empty())
596 					notes.append(", ").append(lookup_state).append("  ").append(lookup_zip);
597 				else if (!lookup_province.empty())
598 					notes.append(", ").append(lookup_province).append("  ").append(lookup_zip);
599 				else
600 					notes.append("  ").append(lookup_country);
601 			}
602 			lookup_notes = notes;
603 			REQ(QRZ_disp_result);
604 		}
605 	}
606 	else {
607 		qrzerror = qrzpage;
608 		REQ(QRZAlert);
609 	}
610 }
611 
612 // ---------------------------------------------------------------------
613 // HTTP:://callook.info queries
614 // ---------------------------------------------------------------------
615 
node_data(const string & xmlpage,const string nodename)616 string node_data(const string &xmlpage, const string nodename)
617 {
618 	size_t pos1, pos2;
619 	string test;
620 	test.assign("<").append(nodename).append(">");
621 	pos1 = xmlpage.find(test);
622 	if (pos1 == string::npos) return "";
623 	pos1 += test.length();
624 	test.assign("</").append(nodename).append(">");
625 	pos2 = xmlpage.find(test);
626 	if (pos2 == string::npos) return "";
627 	return xmlpage.substr(pos1, pos2 - pos1);
628 }
629 
parse_callook(string & xmlpage)630 void parse_callook(string& xmlpage)
631 {
632 	print_data("Callook info", xmlpage);
633 
634 	if (xmlpage.find("INVALID") != std::string::npos) {
635 		if (!announce) announce = new notify_dialog;
636 		announce->notify("Call not found", 2.0);
637 		REQ(show_notifier, announce);
638 		return;
639 	}
640 
641 	string nodestr = node_data(xmlpage, "current");
642 
643 	if (nodestr.empty()) {
644 		if (!announce) announce = new notify_dialog;
645 		announce->notify("no data from callook.info", 2.0);
646 		REQ(show_notifier, announce);
647 		return;
648 	}
649 
650 	size_t start_pos = xmlpage.find("</trustee>");
651 	if (start_pos == string::npos) return;
652 
653 	start_pos += 10;
654 	xmlpage = xmlpage.substr(start_pos);
655 	lookup_name = node_data(xmlpage, "name");
656 	camel_case(lookup_name);
657 	lookup_fname = lookup_name;
658 
659 	nodestr = node_data(xmlpage, "address");
660 	if (!nodestr.empty()) {
661 		lookup_addr1 = node_data(nodestr, "line1");
662 		lookup_addr2 = node_data(nodestr, "line2");
663 	}
664 
665 	nodestr = node_data(xmlpage, "location");
666 	if (!nodestr.empty()) {
667 		lookup_lond = node_data(nodestr, "longitude");
668 		lookup_latd = node_data(nodestr, "latitude");
669 		lookup_grid = node_data(nodestr, "gridsquare");
670 	}
671 
672 	string notes;
673 	if (!progdefaults.clear_notes) notes.assign(inpNotes->value());
674 	else notes.clear();
675 
676 	if (progdefaults.notes_address) {
677 		if (!notes.empty()) notes.append("\n");
678 		notes.append(lookup_name).append("\n");
679 		notes.append(lookup_addr1).append("\n");
680 		notes.append(lookup_addr2);
681 	}
682 	lookup_notes = notes;
683 
684 	size_t p = lookup_addr2.find(",");
685 	if (p != string::npos) {
686 		lookup_qth = lookup_addr2.substr(0, p);
687 		lookup_addr2.erase(0, p+2);
688 		p = lookup_addr2.find(" ");
689 		if (p != string::npos)
690 			lookup_state = lookup_addr2.substr(0, p);
691 	}
692 
693 }
694 
CALLOOKGetXML(string & xmlpage)695 bool CALLOOKGetXML(string& xmlpage)
696 {
697 	string url = progdefaults.callookurl;
698 	size_t p = 0;
699 	if ((p = url.find("https")) != std::string::npos)
700 		url.erase(p+4,1);
701 	url.append(callsign).append("/xml");
702 	bool res = get_http(url, xmlpage, 5.0);
703 	LOG_VERBOSE("result = %d", res);
704 	return res;
705 }
706 
CALLOOKquery()707 void CALLOOKquery()
708 {
709 	ENSURE_THREAD(QRZ_TID);
710 
711 //	bool ok = true;
712 
713 	string CALLOOKpage;
714 
715 	clear_Lookup();
716 //	ok =
717 	CALLOOKGetXML(CALLOOKpage);
718 //	if (ok)
719 	parse_callook(CALLOOKpage);
720 	REQ(QRZ_disp_result);
721 }
722 
723 // ---------------------------------------------------------------------
724 // Hamcall specific functions
725 // ---------------------------------------------------------------------
726 
727 #define HAMCALL_CALL   (char)181
728 #define HAMCALL_FIRST  (char)184
729 #define HAMCALL_CITY   (char)191
730 #define HAMCALL_STATE  (char)192
731 #define HAMCALL_GRID   (char)202
732 #define HAMCALL_DOB    (char)194
733 
parse_html(const string & htmlpage)734 void parse_html(const string& htmlpage)
735 {
736 	print_data("Hamcall data", htmlpage);
737 
738 	size_t p;
739 
740 	clear_Lookup();
741 	if ((p = htmlpage.find(HAMCALL_FIRST)) != string::npos) {
742 		p++;
743 		while ((uchar)htmlpage[p] < 128 && p < htmlpage.length() )
744 			lookup_fname.append(1, htmlpage[p++]);
745 		camel_case(lookup_fname);
746 	}
747 	if ((p = htmlpage.find(HAMCALL_CITY)) != string::npos) {
748 		p++;
749 		while ((uchar)htmlpage[p] < 128 && p < htmlpage.length())
750 			lookup_qth.append(1, htmlpage[p++]);
751 	}
752 	if ((p = htmlpage.find(HAMCALL_STATE)) != string::npos) {
753 		p++;
754 		while ((uchar)htmlpage[p] < 128 && p < htmlpage.length())
755 			lookup_state.append(1, htmlpage[p++]);
756 	}
757 	if ((p = htmlpage.find(HAMCALL_GRID)) != string::npos) {
758 		p++;
759 		while ((uchar)htmlpage[p] < 128 && p < htmlpage.length())
760 			lookup_grid.append(1, htmlpage[p++]);
761 	}
762 	if ((p = htmlpage.find(HAMCALL_DOB)) != string::npos) {
763 		p++;
764 		lookup_notes.append("DOB: ");
765 		while ((uchar)htmlpage[p] < 128 && p < htmlpage.length())
766 			lookup_notes.append(1, htmlpage[p++]);
767 	}
768 }
769 
HAMCALLget(string & htmlpage)770 bool HAMCALLget(string& htmlpage)
771 {
772         string html;
773 	size_t p1;
774 
775 	html.assign(progdefaults.hamcallurl);
776 	if ((p1 = html.find("https")) != std::string::npos)
777 		html.erase(p1+4,1);
778 	if (html[html.length()-1] != '/') html += '/';
779 	html.append("/call?username=");
780 	html.append(progdefaults.QRZusername);
781 	html.append("&password=");
782 	html.append(progdefaults.QRZuserpassword);
783 	html.append("&rawlookup=1&callsign=");
784 	html.append(callsign);
785 	html.append("&program=fldigi-");
786 	html.append(VERSION);
787 	return get_http(html, htmlpage, 5.0);
788 
789 //	print_query("hamcall", url_html);
790 
791 //	string url = progdefaults.hamcallurl;
792 //	size_t p = url.find("//");
793 //	string service = url.substr(0, p);
794 //	url.erase(0, p+2);
795 //	size_t len = url.length();
796 //	if (url[len-1]=='/') url.erase(len-1, 1);
797 //	return network_query(url, service, url_html, htmlpage, 5.0);
798 }
799 
HAMCALLquery()800 void HAMCALLquery()
801 {
802 	ENSURE_THREAD(QRZ_TID);
803 
804 	string htmlpage;
805 
806 	if (HAMCALLget(htmlpage))
807 		parse_html(htmlpage);
808 	else
809 		lookup_notes = htmlpage;
810 	REQ(QRZ_disp_result);
811 }
812 
813 // ---------------------------------------------------------------------
814 // Hamcall specific functions
815 // ---------------------------------------------------------------------
816 
817 static string HAMQTH_session_id = "";
818 static string HAMQTH_reply = "";
819 
820 #define HAMQTH_DEBUG 1
821 #undef HAMQTH_DEBUG
822 /*
823  * send:
824      https://www.hamqth.com/xml.php?u=username&p=password
825  * response:
826      <?xml version="1.0"?>
827      <HamQTH version="2.7" xmlns="https://www.hamqth.com">
828      <session>
829      <session_id>09b0ae90050be03c452ad235a1f2915ad684393c</session_id>
830      </session>
831      </HamQTH>
832  *
833  * send:
834      https://www.hamqth.com/xml.php?id=09b0ae90050be03c452ad235a1f2915ad684393c\
835              &callsign=ok7an&prg=YOUR_PROGRAM_NAME
836  * response:
837 	<?xml version="1.0"?>
838 	<HamQTH version="2.7" xmlns="https://www.hamqth.com">
839 	<search>
840 	<callsign>ok7an</callsign>
841 	<nick>Petr</nick>
842 	<qth>Neratovice</qth>
843 	<country>Czech Republic</country>
844 	<adif>503</adif>
845 	<itu>28</itu>
846 	<cq>15</cq>
847 	<grid>jo70gg</grid>
848 	<adr_name>Petr Hlozek</adr_name>
849 	<adr_street1>17. listopadu 1065</adr_street1>
850 	<adr_city>Neratovice</adr_city>
851 	<adr_zip>27711</adr_zip>
852 	<adr_country>Czech Republic</adr_country>
853 	<adr_adif>503</adr_adif>
854 	<district>GZL</district>
855 	<lotw>Y</lotw>
856 	<qsl>Y</qsl>
857 	<qsldirect>Y</qsldirect>
858 	<eqsl>Y</eqsl>
859 	<email>petr@ok7an.com</email>
860 	<jabber>petr@ok7an.com</jabber>
861 	<skype>PetrHH</skype>
862 	<birth_year>1982</birth_year>
863 	<lic_year>1998</lic_year>
864 	<web>https://www.ok7an.com</web>
865 	<latitude>50.07</latitude>
866 	<longitude>14.42</longitude>
867 	<continent>EU</continent>
868 	<utc_offset>-1</utc_offset>
869 	<picture>https://www.hamqth.com/userfiles/o/ok/ok7an/_profile/ok7an_nove.jpg</picture>
870 	</search>
871 	</HamQTH>
872 */
HAMQTH_get_session_id()873 bool HAMQTH_get_session_id()
874 {
875 	string url;
876 	string retstr = "";
877 	size_t p1 = string::npos;
878 	size_t p2 = string::npos;
879 
880 	url.assign(progdefaults.hamqthurl);
881 	if ((p1 = url.find("https")) != std::string::npos)
882 		url.erase(p1+4,1);
883 	if (url[url.length()-1] != '/') url += '/';
884 	url.append("xml.php?u=").append(progdefaults.QRZusername);
885 	url.append("&p=").append(progdefaults.QRZuserpassword);
886 
887 	HAMQTH_session_id.clear();
888 
889 	int ret = get_http(url, retstr, 5.0);
890 
891 	if (ret == 0 ) {
892 		LOG_ERROR("get_http( %s, retstr, 5.0) failed\n", url.c_str());
893 		return false;
894 	}
895 
896 	LOG_VERBOSE("url: %s", url.c_str());
897 	LOG_VERBOSE("reply: %s\n", retstr.c_str());
898 	p1 = retstr.find("<error>");
899 	if (p1 != string::npos) {
900 		p2 = retstr.find("</error>");
901 		if (p2 != string::npos) {
902 			p1 += 7;
903 			lookup_notes = retstr.substr(p1, p2 - p1);
904 		}
905 		return false;
906 	}
907 	p1 = retstr.find("<session_id>");
908 	if (p1 == string::npos) {
909 		lookup_notes = "HamQTH not available";
910 		return false;
911 	}
912 	p2 = retstr.find("</session_id>");
913 	HAMQTH_session_id = retstr.substr(p1 + 12, p2 - p1 - 12);
914 	print_data("HamQTH session id", HAMQTH_session_id);
915 	return true;
916 }
917 
parse_HAMQTH_html(const string & htmlpage)918 void parse_HAMQTH_html(const string& htmlpage)
919 {
920 	print_data("HamQth html", htmlpage);
921 
922 	size_t p = string::npos;
923 	size_t p1 = string::npos;
924 	string tempstr;
925 
926 	clear_Lookup();
927 
928 	lookup_fname.clear();
929 	lookup_qth.clear();
930 	lookup_state.clear();
931 	lookup_grid.clear();
932 	lookup_notes.clear();
933 	lookup_country.clear();
934 
935 	if ((p = htmlpage.find("<error>")) != string::npos) {
936 		p += 7;
937 		p1 = htmlpage.find("</error>", p);
938 		if (p1 != string::npos) {
939 			std::string errstr = htmlpage.substr(p, p1 - p);
940 			if (!announce) announce = new notify_dialog;
941 			announce->notify(errstr.c_str(), 2.0);
942 			REQ(show_notifier, announce);
943 		}
944 		return;
945 	}
946 	if ((p = htmlpage.find("<nick>")) != string::npos) {
947 		p += 6;
948 		p1 = htmlpage.find("</nick>", p);
949 		if (p1 != string::npos) {
950 			lookup_fname = htmlpage.substr(p, p1 - p);
951 			camel_case(lookup_fname);
952 		}
953 	}
954 	if ((p = htmlpage.find("<qth>")) != string::npos) {
955 		p += 5;
956 		p1 = htmlpage.find("</qth>", p);
957 		if (p1 != string::npos)
958 			lookup_qth = htmlpage.substr(p, p1 - p);
959 	}
960 	if ((p = htmlpage.find("<country>")) != string::npos) {
961 		p += 9;
962 		p1 = htmlpage.find("</country>", p);
963 		if (p1 != string::npos) {
964 			lookup_country = htmlpage.substr(p, p1 - p);
965 			if (lookup_country == "Canada") {
966 				p = htmlpage.find("<district>");
967 				if (p != string::npos) {
968 					p += 10;
969 					p1 = htmlpage.find("</district>", p);
970 					if (p1 != string::npos)
971 						lookup_province = htmlpage.substr(p, p1 - p);
972 				}
973 			}
974 		}
975 	}
976 	if ((p = htmlpage.find("<us_state>")) != string::npos) {
977 		p += 10;
978 		p1 = htmlpage.find("</us_state>", p);
979 		if (p1 != string::npos)
980 			lookup_state = htmlpage.substr(p, p1 - p);
981 	}
982 	if ((p = htmlpage.find("<grid>")) != string::npos) {
983 		p += 6;
984 		p1 = htmlpage.find("</grid>", p);
985 		if (p1 != string::npos)
986 			lookup_grid = htmlpage.substr(p, p1 - p);
987 	}
988 	if ((p = htmlpage.find("<qsl_via>")) != string::npos) {
989 		p += 9;
990 		p1 = htmlpage.find("</qsl_via>", p);
991 		if (p1 != string::npos) {
992 			tempstr.assign(htmlpage.substr(p, p1 - p));
993 			if (!tempstr.empty())
994 				lookup_notes.append("QSL via: ").append(tempstr).append("\n");
995 		}
996 	}
997 	if (progdefaults.notes_address) {
998 		if ((p = htmlpage.find("<adr_name>")) != string::npos) {
999 			p += 10;
1000 			p1 = htmlpage.find("</adr_name>", p);
1001 			if (p1 != string::npos) {
1002 				tempstr.assign(htmlpage.substr(p, p1 - p));
1003 				if (!tempstr.empty())
1004 					lookup_notes.append(tempstr).append("\n");
1005 			}
1006 		}
1007 		if ((p = htmlpage.find("<adr_street1>")) != string::npos) {
1008 			p += 13;
1009 			p1 = htmlpage.find("</adr_street1>", p);
1010 			if (p1 != string::npos) {
1011 				tempstr.assign(htmlpage.substr(p, p1 - p));
1012 				if (!tempstr.empty())
1013 					lookup_notes.append(tempstr).append("\n");
1014 			}
1015 		}
1016 		if ((p = htmlpage.find("<adr_city>")) != string::npos) {
1017 			p += 10;
1018 			p1 = htmlpage.find("</adr_city>", p);
1019 			if (p1 != string::npos) {
1020 				tempstr.assign(htmlpage.substr(p, p1 - p));
1021 				if (!tempstr.empty())
1022 					lookup_notes.append(tempstr);
1023 				if (!lookup_state.empty())
1024 					lookup_notes.append(", ").append(lookup_state);
1025 				else if (!lookup_province.empty())
1026 					lookup_notes.append(", ").append(lookup_province);
1027 			}
1028 		}
1029 		if ((p = htmlpage.find("<adr_zip>")) != string::npos) {
1030 			p += 9;
1031 			p1 = htmlpage.find("</adr_zip>", p);
1032 			if (p1 != string::npos) {
1033 				tempstr.assign(htmlpage.substr(p, p1 - p));
1034 				if (!tempstr.empty())
1035 					lookup_notes.append("  ").append(tempstr);
1036 			}
1037 		}
1038 	}
1039 }
1040 
HAMQTHget(string & htmlpage)1041 bool HAMQTHget(string& htmlpage)
1042 {
1043 	string url;
1044 	bool ret;
1045 
1046 	if (HAMQTH_session_id.empty()) {
1047 		if (!HAMQTH_get_session_id()) {
1048 			LOG_WARN("HAMQTH session id failed!");
1049 			lookup_notes = "Get session id failed!\n";
1050 			return false;
1051 		}
1052 	}
1053 
1054 	size_t p1;
1055 	url.assign(progdefaults.hamqthurl);
1056 	if ((p1 = url.find("https")) != std::string::npos)
1057 		url.erase(p1+4,1);
1058 	if (url[url.length()-1] != '/') url += '/';
1059 	url.append("xml.php?id=").append(HAMQTH_session_id);
1060 	url.append("&callsign=").append(callsign);
1061 	url.append("&prg=FLDIGI");
1062 
1063 	print_query("HamQTH", url);
1064 
1065 	ret = get_http(url, htmlpage, 5.0);
1066 
1067 	size_t p = htmlpage.find("Session does not exist or expired");
1068 	if (p != string::npos) {
1069 		htmlpage.clear();
1070 		LOG_WARN("HAMQTH session id expired!");
1071 		HAMQTH_session_id.clear();
1072 		if (!HAMQTH_get_session_id()) {
1073 			LOG_WARN("HAMQTH get session id failed!");
1074 			lookup_notes = "Get session id failed!\n";
1075 			htmlpage.clear();
1076 			return false;
1077 		}
1078 		ret = get_http(url, htmlpage, 5.0);
1079 	}
1080 #ifdef HAMQTH_DEBUG
1081 	FILE *fetchit = fopen("fetchit.txt", "a");
1082 	fprintf(fetchit, "%s\n", htmlpage.c_str());
1083 	fclose(fetchit);
1084 #endif
1085 	return ret;
1086 }
1087 
HAMQTHquery()1088 void HAMQTHquery()
1089 {
1090 	ENSURE_THREAD(QRZ_TID);
1091 
1092 	string htmlpage;
1093 
1094 	if (!HAMQTHget(htmlpage)) return;
1095 
1096 	parse_HAMQTH_html(htmlpage);
1097 	REQ(QRZ_disp_result);
1098 
1099 }
1100 
1101 // ----------------------------------------------------------------------------
1102 
QRZ_DETAILS_query()1103 void QRZ_DETAILS_query()
1104 {
1105 	string qrz = progdefaults.qrzurl;
1106 	qrz.append("db/").append(callsign);
1107 	cb_mnuVisitURL(0, (void*)qrz.c_str());
1108 }
1109 
HAMCALL_DETAILS_query()1110 void HAMCALL_DETAILS_query()
1111 {
1112 	string hamcall = progdefaults.hamcallurl;
1113 	hamcall.append("call?callsign=").append(callsign);
1114 	cb_mnuVisitURL(0, (void*)hamcall.c_str());
1115 }
1116 
HAMQTH_DETAILS_query()1117 void HAMQTH_DETAILS_query()
1118 {
1119 	string hamqth = progdefaults.hamqthurl;
1120 	hamqth.append(callsign);
1121 	cb_mnuVisitURL(0, (void*)hamqth.c_str());
1122 }
1123 
CALLOOK_DETAILS_query()1124 void CALLOOK_DETAILS_query()
1125 {
1126 	string hamcall = progdefaults.callookurl;
1127 	hamcall.append(callsign);
1128 	cb_mnuVisitURL(0, (void *)hamcall.c_str());
1129 }
1130 
1131 // ----------------------------------------------------------------------------
1132 
LOOKUP_loop(void * args)1133 static void *LOOKUP_loop(void *args)
1134 {
1135 	SET_THREAD_ID(QRZ_TID);
1136 
1137 	SET_THREAD_CANCEL();
1138 
1139 	for (;;) {
1140 		TEST_THREAD_CANCEL();
1141 		pthread_mutex_lock(&qrz_mutex);
1142 		pthread_cond_wait(&qrz_cond, &qrz_mutex);
1143 		pthread_mutex_unlock(&qrz_mutex);
1144 
1145 		switch (DB_XML_query) {
1146 		case QRZCD :
1147 			QRZ_CD_query();
1148 			break;
1149 		case QRZNET :
1150 			QRZquery();
1151 			break;
1152 		case HAMCALLNET :
1153 			HAMCALLquery();
1154 			break;
1155 		case CALLOOK:
1156 			CALLOOKquery();
1157 			break;
1158 		case HAMQTH:
1159 			HAMQTHquery();
1160 			break;
1161 		case QRZXML_EXIT:
1162 			return NULL;
1163 		default:
1164 			break;
1165 		}
1166 
1167 		switch (DB_WEB_query) {
1168 		case QRZHTML :
1169 			QRZ_DETAILS_query();
1170 			break;
1171 		case HAMCALLHTML :
1172 			HAMCALL_DETAILS_query();
1173 			break;
1174 		case HAMQTHHTML :
1175 			HAMQTH_DETAILS_query();
1176 			break;
1177 		case CALLOOKHTML :
1178 			CALLOOK_DETAILS_query();
1179 			break;
1180 		case QRZWEB_EXIT:
1181 			return NULL;
1182 		default:
1183 			break;
1184 		}
1185 	}
1186 
1187 	return NULL;
1188 }
1189 
CALLSIGNquery()1190 void CALLSIGNquery()
1191 {
1192 	ENSURE_THREAD(FLMAIN_TID);
1193 
1194 	if (!QRZ_thread)
1195 		Lookup_init();
1196 
1197 	// Filter callsign for nonsense characters (remove all but [A-Za-z0-9/])
1198 	callsign.clear();
1199 	for (const char* p = inpCall->value(); *p; p++)
1200 		if (isalnum(*p) || *p == '/')
1201 			callsign += *p;
1202 	if (callsign.empty())
1203 		return;
1204 	if (callsign != inpCall->value())
1205 		inpCall->value(callsign.c_str());
1206 
1207 	size_t slash;
1208 	while ((slash = callsign.rfind('/')) != string::npos) {
1209 		if (((slash+1) * 2) < callsign.length())
1210 			callsign.erase(0, slash + 1);
1211 		else
1212 			callsign.erase(slash);
1213 	}
1214 
1215 	switch (DB_XML_query = static_cast<qrz_xmlquery_t>(progdefaults.QRZXML)) {
1216 	case QRZNET:
1217 		LOG_INFO("%s","Request sent to qrz.com...");
1218 		break;
1219 	case HAMCALLNET:
1220 		LOG_INFO("%s","Request sent to www.hamcall.net...");
1221 		break;
1222 	case QRZCD:
1223 		if (!qCall)
1224 			qCall = new QRZ( "callbkc" );
1225 		if (progdefaults.QRZchanged) {
1226 			qCall->NewDBpath("callbkc");
1227 			progdefaults.QRZchanged = false;
1228 		}
1229 		if (!qCall->getQRZvalid()) {
1230 			LOG_ERROR("%s","QRZ DB error");
1231 			DB_XML_query = QRZXMLNONE;
1232 			return;
1233 		}
1234 		break;
1235 	case CALLOOK:
1236 		LOG_INFO("Request sent to %s", progdefaults.callookurl.c_str());
1237 		break;
1238 	case HAMQTH:
1239 		LOG_INFO("Request sent to %s", progdefaults.hamqthurl.c_str());
1240 		break;
1241 	case QRZXMLNONE:
1242 		break;
1243 	default:
1244 		LOG_ERROR("Bad query type %d", DB_XML_query);
1245 		return;
1246 	}
1247 
1248 	DB_WEB_query = static_cast<qrz_webquery_t>(progdefaults.QRZWEB);
1249 
1250 	pthread_mutex_lock(&qrz_mutex);
1251 	pthread_cond_signal(&qrz_cond);
1252 	pthread_mutex_unlock(&qrz_mutex);
1253 }
1254 
1255 
1256 /// With this constructor, no need to declare the array mode_info[] in the calling program.
QsoHelper(int the_mode)1257 QsoHelper::QsoHelper(int the_mode)
1258 	: qso_rec( new cQsoRec )
1259 {
1260 	qso_rec->putField(ADIF_MODE, mode_info[the_mode].adif_name );
1261 }
1262 
1263 /// This saves the new record to a file and must be run in the main thread.
QsoHelperSave(cQsoRec * qso_rec_ptr)1264 static void QsoHelperSave(cQsoRec * qso_rec_ptr)
1265 {
1266 	qsodb.qsoNewRec (qso_rec_ptr);
1267 	qsodb.isdirty(0);
1268 	qsodb.isdirty(0);
1269 
1270 	loadBrowser(true);
1271 
1272 	/// It is mandatory to do this in the main thread. TODO: Crash suspected.
1273 	adifFile.writeLog (logbook_filename.c_str(), &qsodb);
1274 
1275 	/// Beware that this object is created in a thread and deleted in the main one.
1276 	delete qso_rec_ptr ;
1277 }
1278 
1279 /// This must be called from the main thread, according to writeLog().
~QsoHelper()1280 QsoHelper::~QsoHelper()
1281 {
1282 	qso_rec->setDateTime(true);
1283 	qso_rec->setDateTime(false);
1284 	qso_rec->setFrequency( wf->rfcarrier() );
1285 
1286 	REQ( QsoHelperSave, qso_rec );
1287 
1288 	// It will be deleted in the main thread.
1289 	qso_rec = NULL ;
1290 }
1291 
1292 /// Adds one key-value pair to display in the ADIF file.
Push(ADIF_FIELD_POS pos,const std::string & value)1293 void QsoHelper::Push( ADIF_FIELD_POS pos, const std::string & value )
1294 {
1295 	qso_rec->putField( pos, value.c_str() );
1296 }
1297 
1298