1 /***************************************************************************
2                           cabrillo.cpp  -  description
3                              -------------------
4     begin                : Thu Dec 5 2002
5     copyright            : (C) 2002 by ARRL
6     author               : Jon Bloom
7     email                : jbloom@arrl.org
8     revision             : $Id$
9  ***************************************************************************/
10 
11 
12 #define TQSLLIB_DEF
13 
14 #include <ctype.h>
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <cstdio>
18 #include <cstring>
19 #include "tqsllib.h"
20 #include "tqslerrno.h"
21 
22 #include "winstrdefs.h"
23 
24 #define TQSL_CABRILLO_MAX_RECORD_LENGTH 120
25 
26 DLLEXPORTDATA TQSL_CABRILLO_ERROR_TYPE tQSL_Cabrillo_Error;
27 
28 static char errmsgbuf[256];
29 static char errmsgdata[128];
30 
31 struct TQSL_CABRILLO;
32 
33 static int freq_to_band(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp);
34 static int freq_to_mhz(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp);
35 static int mode_xlat(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp);
36 static int time_fixer(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp);
37 
38 struct cabrillo_field_def {
39 	const char *name;
40 	int loc;
41 	int (*process)(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp);
42 };
43 
44 static cabrillo_field_def cabrillo_dummy[] = {
45 	 { "CALL", 6, 0 },
46 	 { "BAND", 0, freq_to_band },
47 	 { "MODE", 1, mode_xlat },
48 	 { "QSO_DATE", 2, 0 },
49 	 { "TIME_ON", 3, time_fixer },
50 	 { "FREQ", 0, freq_to_mhz },
51 	 { "MYCALL", 4, 0 },
52 };
53 
54 /*
55 
56 // Cabrillo QSO template specs
57 
58 // Call in field 6
59 static cabrillo_field_def cabrillo_c6[] = {
60 	{ "BAND", 0, freq_to_band },
61 	{ "MODE", 1, mode_xlat },
62 	{ "QSO_DATE", 2, 0 },
63 	{ "TIME_ON", 3, time_fixer },
64 	{ "CALL", 6, 0 },
65 	{ "FREQ", 0, 0 },
66 	{ "MYCALL", 4, 0 },
67 };
68 
69 // Call in field 7
70 static cabrillo_field_def cabrillo_c7[] = {
71 	{ "BAND", 0, freq_to_band },
72 	{ "MODE", 1, mode_xlat },
73 	{ "QSO_DATE", 2, 0 },
74 	{ "TIME_ON", 3, time_fixer },
75 	{ "CALL", 7, 0 },
76 	{ "FREQ", 0, 0 },
77 	{ "MYCALL", 4, 0 },
78 };
79 
80 // Call in field 8
81 static cabrillo_field_def cabrillo_c8[] = {
82 	{ "BAND", 0, freq_to_band },
83 	{ "MODE", 1, mode_xlat },
84 	{ "QSO_DATE", 2, 0 },
85 	{ "TIME_ON", 3, time_fixer },
86 	{ "CALL", 8, 0 },
87 	{ "FREQ", 0, 0 },
88 	{ "MYCALL", 4, 0 },
89 };
90 
91 // Call in field 9
92 static cabrillo_field_def cabrillo_c9[] = {
93 	{ "BAND", 0, freq_to_band },
94 	{ "MODE", 1, mode_xlat },
95 	{ "QSO_DATE", 2, 0 },
96 	{ "TIME_ON", 3, time_fixer },
97 	{ "CALL", 9, 0 },
98 	{ "FREQ", 0, 0 },
99 	{ "MYCALL", 4, 0 },
100 };
101 
102 */
103 
104 struct cabrillo_contest {
105 	char *contest_name;
106 	TQSL_CABRILLO_FREQ_TYPE type;
107 	cabrillo_field_def *fields;
108 	int nfields;
109 };
110 
111 struct TQSL_CABRILLO {
112 	int sentinel;
113 	FILE *fp;
114 	char *filename;
115 	cabrillo_contest *contest;
116 	int field_idx;
117 	char rec[TQSL_CABRILLO_MAX_RECORD_LENGTH+1];
118 	char *datap;
119 	int line_no;
120 	char *fields[TQSL_CABRILLO_MAX_FIELDS];
121 };
122 
123 #define CAST_TQSL_CABRILLO(p) ((struct TQSL_CABRILLO *)p)
124 
125 static TQSL_CABRILLO *
check_cabrillo(tQSL_Cabrillo cabp)126 check_cabrillo(tQSL_Cabrillo cabp) {
127 	if (tqsl_init())
128 		return 0;
129 	if (cabp == 0) {
130 		tQSL_Error = TQSL_ARGUMENT_ERROR;
131 		return 0;
132 	}
133 	if (CAST_TQSL_CABRILLO(cabp)->sentinel != 0x2449)
134 		return 0;
135 	return CAST_TQSL_CABRILLO(cabp);
136 }
137 
138 static char *
tqsl_parse_cabrillo_record(char * rec)139 tqsl_parse_cabrillo_record(char *rec) {
140 	char *cp = strchr(rec, ':');
141 	if (!cp)
142 		return 0;
143 	*cp++ = 0;
144 	if (strlen(rec) > TQSL_CABRILLO_FIELD_NAME_LENGTH_MAX)
145 		return 0;
146 	while (isspace(*cp))
147 		cp++;
148 	char *sp;
149 	if ((sp = strchr(cp, '\r')) != 0)
150 		*sp = '\0';
151 	if ((sp = strchr(cp, '\n')) != 0)
152 		*sp = '\0';
153 	for (sp = cp + strlen(cp); sp != cp; ) {
154 		sp--;
155 		if (isspace(*sp))
156 			*sp = '\0';
157 		else
158 			break;
159 	}
160 	for (sp = rec; *sp; sp++)
161 		*sp = toupper(*sp);
162 	return cp;
163 }
164 
165 static int
freq_to_band(TQSL_CABRILLO * cab,tqsl_cabrilloField * fp)166 freq_to_band(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp) {
167 	if (!strcasecmp(fp->value, "light")) {
168 		strncpy(fp->value, "SUBMM", sizeof fp->value);
169 		return 0;
170 	}
171 	int freq = strtol(fp->value, NULL, 10);
172 	const char *band = 0;
173 	if (freq < 30) {
174 		// Handle known CT misbehavior
175 		if (!strcmp(fp->value, "7"))
176 			freq = 7000;
177 		if (!strcmp(fp->value, "14"))
178 			freq = 14000;
179 		if (!strcmp(fp->value, "21"))
180 			freq = 21000;
181 		if (!strcmp(fp->value, "28"))
182 			freq = 28000;
183 	}
184 	if (freq >= 1800 && freq <= 2000)
185 		band = "160M";
186 	else if (freq >= 3500 && freq <= 4000)
187 		band = "80M";
188 	else if (freq >= 7000 && freq <= 7300)
189 		band = "40M";
190 	else if (freq >= 10100 && freq <= 10150)
191 		band = "30M";
192 	else if (freq >= 14000 && freq <= 14350)
193 		band = "20M";
194 	else if (freq >= 18068 && freq <= 18168)
195 		band = "17M";
196 	else if (freq >= 21000 && freq <= 21450)
197 		band = "15M";
198 	else if (freq >= 24890 && freq <= 24990)
199 		band = "12M";
200 	else if (freq >= 28000 && freq <= 29700)
201 		band = "10M";
202 	else if (freq == 50)
203 		band = "6M";
204 	else if (freq == 70)
205 		band = "4M";
206 	else if (freq == 144)
207 		band = "2M";
208 	else if (freq == 222)
209 		band = "1.25M";
210 	else if (freq == 432)
211 		band = "70CM";
212 	else if (freq == 902 || freq == 903)
213 		band = "33CM";
214 	else if (!strcasecmp(fp->value, "1.2G") || !strcasecmp(fp->value, "1.2"))
215 		band = "23CM";
216 	else if (!strcasecmp(fp->value, "2.3G") || !strcasecmp(fp->value, "2.3"))
217 		band = "13CM";
218 	else if (!strcasecmp(fp->value, "3.4G") || !strcasecmp(fp->value, "3.4"))
219 		band = "9CM";
220 	else if (!strcasecmp(fp->value, "5.7G") || !strcasecmp(fp->value, "5.7"))
221 		band = "6CM";
222 	else if (!strcasecmp(fp->value, "10G") || !strcasecmp(fp->value, "10"))
223 		band = "3CM";
224 	else if (!strcasecmp(fp->value, "24G") || !strcasecmp(fp->value, "24"))
225 		band = "1.25CM";
226 	else if (!strcasecmp(fp->value, "47G") || !strcasecmp(fp->value, "47"))
227 		band = "6MM";
228 	else if (!strcasecmp(fp->value, "75G") || !strcasecmp(fp->value, "75") ||
229 		 !strcasecmp(fp->value, "76G") || !strcasecmp(fp->value, "76"))
230 		band = "4MM";
231 	else if (!strcasecmp(fp->value, "119G") || !strcasecmp(fp->value, "119"))
232 		band = "2.5MM";
233 	else if (!strcasecmp(fp->value, "142G") || !strcasecmp(fp->value, "142"))
234 		band = "2MM";
235 	else if (!strcasecmp(fp->value, "241G")  || !strcasecmp(fp->value, "241")||
236 		 !strcasecmp(fp->value, "242G") || !strcasecmp(fp->value, "242"))
237 		band = "1MM";
238 	else if (!strcasecmp(fp->value, "300G") || !strcasecmp(fp->value, "300") || !strcasecmp(fp->value, "LIGHT"))
239 		band = "SUBMM";
240 
241 	if (band && cab->contest->type ==  TQSL_CABRILLO_UNKNOWN) {
242 		if (freq < 1000)
243 			cab->contest->type = TQSL_CABRILLO_VHF;
244 		else
245 			cab->contest->type = TQSL_CABRILLO_HF;
246 	}
247 	if (band == 0)
248 		return 1;
249 	strncpy(fp->value, band, sizeof fp->value);
250 	return 0;
251 }
252 
253 static int
freq_to_mhz(TQSL_CABRILLO * cab,tqsl_cabrilloField * fp)254 freq_to_mhz(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp) {
255 	if (!strcasecmp(fp->value, "light")) {
256 		return 0;
257 	}
258 	int freq = strtol(fp->value, NULL, 10);
259 	double freqmhz = freq;
260 	freqmhz /= 1000;
261 
262 	if (freq < 30) {
263 		// Handle known CT misbehavior
264 		if (freq == 7)
265 			freqmhz = 7.0;
266 		if (freq == 14)
267 			freqmhz = 14.0;
268 		if (freq == 21)
269 			freqmhz = 21.0;
270 		if (freq == 28)
271 			freqmhz = 28.0;
272 	}
273 	// VHF+
274 	if (!strcasecmp(fp->value, "50"))
275 		freqmhz = 50.0;
276 	else if (!strcasecmp(fp->value, "70"))
277 		freqmhz = 70.0;
278 	else if (!strcasecmp(fp->value, "144"))
279 		freqmhz = 144.0;
280 	else if (!strcasecmp(fp->value, "222"))
281 		freqmhz = 222.0;
282 	else if (!strcasecmp(fp->value, "432"))
283 		freqmhz = 432.0;
284 	else if (!strcasecmp(fp->value, "902") ||
285 		 !strcasecmp(fp->value, "903"))
286 		freqmhz = 902.0;
287 	else if (!strcasecmp(fp->value, "1.2G") || !strcasecmp(fp->value, "1.2"))
288 		freqmhz = 1240.0;
289 	else if (!strcasecmp(fp->value, "2.3G") || !strcasecmp(fp->value, "2.3"))
290 		freqmhz = 2300.0;
291 	else if (!strcasecmp(fp->value, "3.4G") || !strcasecmp(fp->value, "3.4"))
292 		freqmhz = 3300.0;
293 	else if (!strcasecmp(fp->value, "5.7G") || !strcasecmp(fp->value, "5.7"))
294 		freqmhz = 5650.0;
295 	else if (!strcasecmp(fp->value, "10G")  || !strcasecmp(fp->value, "10"))
296 		freqmhz = 10000.0;
297 	else if (!strcasecmp(fp->value, "24G")  || !strcasecmp(fp->value, "24"))
298 		freqmhz = 24000.0;
299 	else if (!strcasecmp(fp->value, "47G")  || !strcasecmp(fp->value, "47"))
300 		freqmhz = 47000.0;
301 	else if (!strcasecmp(fp->value, "75G")  || !strcasecmp(fp->value, "75") ||
302 		 !strcasecmp(fp->value, "76G")  || !strcasecmp(fp->value, "76"))
303 		freqmhz = 75500.0;
304 	else if (!strcasecmp(fp->value, "119G") || !strcasecmp(fp->value, "119"))
305 		freqmhz = 119980.0;
306 	else if (!strcasecmp(fp->value, "142G") || !strcasecmp(fp->value, "142"))
307 		freqmhz = 142000.0;
308 	else if (!strcasecmp(fp->value, "241G") || !strcasecmp(fp->value, "241") ||
309 		 !strcasecmp(fp->value, "242G") || !strcasecmp(fp->value, "242"))
310 		freqmhz = 241000.0;
311 	else if (!strcasecmp(fp->value, "300G") || !strcasecmp(fp->value, "300"))
312 		freqmhz = 300000.0;
313 
314 	if (freqmhz > 0 && cab->contest->type ==  TQSL_CABRILLO_UNKNOWN) {
315 		if (freqmhz >= 50.0)		// VHF
316 			cab->contest->type = TQSL_CABRILLO_VHF;
317 		else
318 			cab->contest->type = TQSL_CABRILLO_HF;
319 	}
320 
321 	snprintf(fp->value, sizeof fp->value, "%#f", freqmhz);
322 	return 0;
323 }
324 
325 static int
mode_xlat(TQSL_CABRILLO * cab,tqsl_cabrilloField * fp)326 mode_xlat(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp) {
327 	static struct {
328 		const char *cmode;
329 		const char *gmode;
330 	} modes[] = {
331 		 { "CW", "CW"}, {"PH", "SSB"}, {"FM", "FM"}, {"RY", "RTTY" }
332 	};
333 	for (int i = 0; i < static_cast<int>(sizeof modes / sizeof modes[0]); i++) {
334 		if (!strcasecmp(fp->value, modes[i].cmode)) {
335 			strncpy(fp->value, modes[i].gmode, sizeof fp->value);
336 			return 0;
337 		}
338 	}
339 	return 1;
340 }
341 
342 static int
time_fixer(TQSL_CABRILLO * cab,tqsl_cabrilloField * fp)343 time_fixer(TQSL_CABRILLO *cab, tqsl_cabrilloField *fp) {
344 	if (strlen(fp->value) == 0)
345 		return 0;
346 	char *cp;
347 	for (cp = fp->value; *cp; cp++)
348 		if (!isdigit(*cp))
349 			break;
350 	if (*cp)
351 		return 1;
352 	snprintf(fp->value, sizeof fp->value, "%04d", static_cast<int>(strtol(fp->value, NULL, 10)));
353 	return 0;
354 }
355 
356 static void
tqsl_free_cabrillo_contest(struct cabrillo_contest * c)357 tqsl_free_cabrillo_contest(struct cabrillo_contest *c) {
358 		if (c->contest_name)
359 			free(c->contest_name);
360 		if (c->fields)
361 			free(c->fields);
362 		free(c);
363 }
364 
365 static struct cabrillo_contest *
tqsl_new_cabrillo_contest(const char * contest_name,int call_field,int contest_type)366 tqsl_new_cabrillo_contest(const char *contest_name, int call_field, int contest_type) {
367 	cabrillo_contest *c = static_cast<cabrillo_contest *>(calloc(1, sizeof(struct cabrillo_contest)));
368 	if (c == NULL)
369 		return NULL;
370 	if ((c->contest_name = strdup(contest_name)) == NULL) {
371 		tqsl_free_cabrillo_contest(c);
372 		return NULL;
373 	}
374 	c->type = (TQSL_CABRILLO_FREQ_TYPE)contest_type;
375 	if ((c->fields = (struct cabrillo_field_def *)calloc(1, sizeof cabrillo_dummy)) == NULL) {
376 		tqsl_free_cabrillo_contest(c);
377 		return NULL;
378 	}
379 	memcpy(c->fields, cabrillo_dummy, sizeof cabrillo_dummy);
380 	c->fields[0].loc = call_field-1;
381 	c->nfields = sizeof cabrillo_dummy / sizeof cabrillo_dummy[0];
382 	return c;
383 }
384 
385 static void
tqsl_free_cab(struct TQSL_CABRILLO * cab)386 tqsl_free_cab(struct TQSL_CABRILLO *cab) {
387 	if (!cab || cab->sentinel != 0x2449)
388 		return;
389 	cab->sentinel = 0;
390 	if (cab->filename)
391 		free(cab->filename);
392 	if (cab->fp)
393 		fclose(cab->fp);
394 	if (cab->contest)
395 		tqsl_free_cabrillo_contest(cab->contest);
396 	free(cab);
397 }
398 
399 DLLEXPORT int CALLCONVENTION
tqsl_beginCabrillo(tQSL_Cabrillo * cabp,const char * filename)400 tqsl_beginCabrillo(tQSL_Cabrillo *cabp, const char *filename) {
401 	tqslTrace("tqsl_beginCabrillo", "cabp=0x%lx, filename=%s", cabp, filename);
402 	TQSL_CABRILLO_ERROR_TYPE terrno;
403 	if (filename == NULL) {
404 		tQSL_Error = TQSL_ARGUMENT_ERROR;
405 		return 1;
406 	}
407 	struct TQSL_CABRILLO *cab;
408 	cab = (struct TQSL_CABRILLO *)calloc(1, sizeof(struct TQSL_CABRILLO));
409 	if (cab == NULL) {
410 		tQSL_Error = TQSL_ALLOC_ERROR;
411 		goto err;
412 	}
413 	cab->sentinel = 0x2449;
414 	cab->field_idx = -1;
415 #ifdef _WIN32
416 	wchar_t * wfilename = utf8_to_wchar(filename);
417 	if ((cab->fp = _wfopen(wfilename, L"rb, ccs=UTF-8")) == NULL) {
418 		free_wchar(wfilename);
419 #else
420 	if ((cab->fp = fopen(filename, "r")) == NULL) {
421 #endif
422 		tQSL_Error = TQSL_SYSTEM_ERROR;
423 		tQSL_Errno = errno;
424 		tqslTrace("tqsl_beginCabrillo", "open error, errno=%d, error=%s", errno, strerror(errno));
425 		goto err;
426 	}
427 #ifdef _WIN32
428 	free(wfilename);
429 #endif
430 	char *cp;
431 	terrno = TQSL_CABRILLO_NO_START_RECORD;
432 	while ((cp = fgets(cab->rec, sizeof cab->rec, cab->fp)) != 0) {
433 		cab->line_no++;
434 		if (tqsl_parse_cabrillo_record(cab->rec) != 0
435 			&& !strcmp(cab->rec, "START-OF-LOG"))
436 			break;
437 	}
438 	if (cp != 0) {
439 		terrno = TQSL_CABRILLO_NO_CONTEST_RECORD;
440 		while ((cp = fgets(cab->rec, sizeof cab->rec, cab->fp)) != 0) {
441 			cab->line_no++;
442 			char *vp;
443 			if ((vp = tqsl_parse_cabrillo_record(cab->rec)) != 0
444 				&& !strcmp(cab->rec, "CONTEST")
445 				&& strtok(vp, " \t\r\n") != 0) {
446 				terrno = TQSL_CABRILLO_UNKNOWN_CONTEST;
447 				int callfield, contest_type;
448 				if (tqsl_getCabrilloMapEntry(vp, &callfield, &contest_type)) {
449 					// No defined contest with this name.
450 					// callfield comes back as 0
451 					contest_type = TQSL_CABRILLO_UNKNOWN;
452 				}
453 				cab->contest = tqsl_new_cabrillo_contest(vp, callfield, contest_type);
454 				if (cab->contest == 0) {
455 					strncpy(errmsgdata, vp, sizeof errmsgdata);
456 					cp = 0;
457 				}
458 				break;
459 			}
460 		}
461 	}
462 	if (cp == 0) {
463 		if (ferror(cab->fp)) {
464 			tQSL_Error = TQSL_SYSTEM_ERROR;
465 			tQSL_Errno = errno;
466 			tqslTrace("tqsl_beginCabrillo", "read error, errno=%d, error=%s", errno, strerror(errno));
467 			goto err;
468 		}
469 		tQSL_Cabrillo_Error = terrno;
470 		tQSL_Error = TQSL_CABRILLO_ERROR;
471 		goto err;
472 	}
473 	if ((cab->filename = strdup(filename)) == NULL) {
474 		tQSL_Error = TQSL_ALLOC_ERROR;
475 		goto err;
476 	}
477 	*((struct TQSL_CABRILLO **)cabp) = cab;
478 	return 0;
479  err:
480 	strncpy(tQSL_ErrorFile, filename, sizeof tQSL_ErrorFile);
481 	tQSL_ErrorFile[sizeof tQSL_ErrorFile-1] = 0;
482 	tqsl_free_cab(cab);
483 	return 1;
484 }
485 
486 DLLEXPORT int CALLCONVENTION
487 tqsl_endCabrillo(tQSL_Cabrillo *cabp) {
488 	tqslTrace("tqsl_endCabrillo", "cabp=0x%lx", cabp);
489 	TQSL_CABRILLO *cab;
490 	if (cabp == 0)
491 		return 0;
492 	cab = CAST_TQSL_CABRILLO(*cabp);
493 	if (!cab || cab->sentinel != 0x2449)
494 		return 0;
495 	tqsl_free_cab(cab);
496 	*cabp = 0;
497 	return 0;
498 }
499 
500 DLLEXPORT const char* CALLCONVENTION
501 tqsl_cabrilloGetError(TQSL_CABRILLO_ERROR_TYPE err) {
502 	const char *msg = 0;
503 	switch (err) {
504 		case TQSL_CABRILLO_NO_ERROR:
505 			msg = "Cabrillo success";
506 			break;
507 		case TQSL_CABRILLO_EOF:
508 			msg = "Cabrillo end-of-file";
509 			break;
510 		case TQSL_CABRILLO_EOR:
511 			msg = "Cabrillo end-of-record";
512 			break;
513 		case TQSL_CABRILLO_NO_START_RECORD:
514 			msg = "Cabrillo missing START-OF-LOG record";
515 			break;
516 		case TQSL_CABRILLO_NO_CONTEST_RECORD:
517 			msg = "Cabrillo missing CONTEST record";
518 			break;
519 		case TQSL_CABRILLO_UNKNOWN_CONTEST:
520 			snprintf(errmsgbuf, sizeof errmsgbuf, "Cabrillo unknown CONTEST: %s", errmsgdata);
521 			msg = errmsgbuf;
522 			break;
523 		case TQSL_CABRILLO_BAD_FIELD_DATA:
524 			snprintf(errmsgbuf, sizeof errmsgbuf, "Cabrillo field data error in %s field", errmsgdata);
525 			msg = errmsgbuf;
526 			break;
527 	}
528 	if (!msg) {
529 		snprintf(errmsgbuf, sizeof errmsgbuf, "Cabrillo unknown error: %d", err);
530 		if (errmsgdata[0] != '\0')
531 			snprintf(errmsgbuf + strlen(errmsgbuf), sizeof errmsgbuf - strlen(errmsgbuf),
532 				" (%s)", errmsgdata);
533 		msg = errmsgbuf;
534 	}
535 	tqslTrace("tqsl_cabrilloGetError", "msg=%s", msg);
536 	errmsgdata[0] = '\0';
537 	return msg;
538 }
539 
540 DLLEXPORT int CALLCONVENTION
541 tqsl_getCabrilloField(tQSL_Cabrillo cabp, tqsl_cabrilloField *field, TQSL_CABRILLO_ERROR_TYPE *error) {
542 	TQSL_CABRILLO *cab;
543 	cabrillo_field_def *fp;
544 	const char *fielddat;
545 
546 	if ((cab = check_cabrillo(cabp)) == 0)
547 		return 1;
548 	if (field == 0 || error == 0) {
549 		tQSL_Error = TQSL_ARGUMENT_ERROR;
550 		return 1;
551 	}
552 	if (cab->datap == 0 || cab->field_idx < 0 || cab->field_idx >= cab->contest->nfields) {
553 		char *cp;
554 		while ((cp = fgets(cab->rec, sizeof cab->rec, cab->fp)) != 0) {
555 			cab->line_no++;
556 			cab->datap = tqsl_parse_cabrillo_record(cab->rec);
557 			if (cab->datap != 0) {
558 				if (!strcasecmp(cab->rec, "QSO")) {
559 					cab->field_idx = 0;
560 					char *fieldp = strtok(cab->datap, " \t\r\n");
561 					memset(cab->fields, 0, sizeof cab->fields);
562 					for (int i = 0; fieldp && i < static_cast<int>(sizeof cab->fields / sizeof cab->fields[0]); i++) {
563 						cab->fields[i] = fieldp;
564 						fieldp = strtok(0, " \t\r\n");
565 					}
566 					break;
567 				} else if (!strcasecmp(cab->rec, "END-OF-LOG")) {
568 					*error = TQSL_CABRILLO_EOF;
569 					return 0;
570 				}
571 			}
572 		}
573 		if (cp == 0) {
574 			if (ferror(cab->fp)) {
575 				tQSL_Error = TQSL_SYSTEM_ERROR;
576 				tQSL_Errno = errno;
577 				goto err;
578 			} else {
579 				*error = TQSL_CABRILLO_EOF;
580 				return 0;
581 			}
582 		}
583 	}
584 	// Record data is okay and field index is valid.
585 
586 	fp = cab->contest->fields + cab->field_idx;
587 	strncpy(field->name, fp->name, sizeof field->name);
588 	if (fp->loc < 0) {  // New contest
589 		// try to guess which field has the 'call-worked'
590 		for (int i = 6; i < TQSL_CABRILLO_MAX_FIELDS && cab->fields[i]; i++) {
591 			char *p = cab->fields[i];
592 			// Simple parse: a callsign is at least 4 chars long
593 			// and has at least one digit and at least one letter
594 			// Nothing but alphnumeric and '/' allowed.
595 
596 
597 			// First, eliminate grid squares
598 			if (strlen(p) == 4) {
599 				if (isalpha(p[0]) && isalpha(p[1]) &&
600 				    isdigit(p[2]) && isdigit(p[3]))
601 					continue;
602 			}
603 			int nlet = 0, ndig = 0;
604 			for (; *p; p++) {
605 				if (isdigit(*p)) {
606 					 ndig++;
607 				} else if (isalpha(*p)) {
608 					 nlet++;
609 				} else if (*p != '/') {
610 					ndig = 0;
611 					nlet = 0;
612 					break;
613 				}
614 			}
615 			if (nlet > 0 && ndig > 0 && nlet+ndig > 3) {
616 				// OK, looks like a callsign. Is it possibly a gridsquare?
617 				if (strlen(p) == 6) {
618 					if ((isalpha(p[0]) && toupper(p[0]) < 'S') &&
619 					    (isalpha(p[1]) && toupper(p[1]) < 'S') &&
620 					    (isdigit(p[2]) && isdigit(p[3])) &&
621 					    (isalpha(p[4]) && toupper(p[4]) < 'Y') &&
622 					    (isalpha(p[5]) && toupper(p[5]) < 'Y'))
623 						continue;	// Gridsquare. Don't use it.
624 				}
625 				if (fp->loc < 0) {		// No callsign candidate yet
626 					fp->loc = i;
627 				} else {
628 					tQSL_Cabrillo_Error = TQSL_CABRILLO_UNKNOWN_CONTEST;
629 					tQSL_Error = TQSL_CABRILLO_ERROR;
630 					snprintf(errmsgdata, sizeof errmsgdata, "%s\nUnable to find a unique call-worked field.\n"
631 						"Please define a custom Cabrillo entry for this contest.\n", cab->contest->contest_name);
632 					goto err;
633 				}
634 			}
635 		}
636 		if (fp->loc < 0) {		// Still can't find a call. Have to bail.
637 			tQSL_Cabrillo_Error = TQSL_CABRILLO_UNKNOWN_CONTEST;
638 			tQSL_Error = TQSL_CABRILLO_ERROR;
639 			snprintf(errmsgdata, sizeof errmsgdata, "%s\nUnable to find a valid call-worked field.\n"
640 				"Please define a custom Cabrillo entry for this contest.\n", cab->contest->contest_name);
641 			goto err;
642 		}
643 	}
644 	fielddat = cab->fields[fp->loc];
645 	if (fielddat == 0) {
646 		tQSL_Cabrillo_Error = TQSL_CABRILLO_BAD_FIELD_DATA;
647 		tQSL_Error = TQSL_CABRILLO_ERROR;
648 		strncpy(errmsgdata, field->name, sizeof errmsgdata);
649 		goto err;
650 	}
651 	strncpy(field->value, fielddat, sizeof field->value);
652 
653 	if (fp->process && fp->process(cab, field)) {
654 		tQSL_Cabrillo_Error = TQSL_CABRILLO_BAD_FIELD_DATA;
655 		tQSL_Error = TQSL_CABRILLO_ERROR;
656 		strncpy(errmsgdata, field->name, sizeof errmsgdata);
657 		goto err;
658 	}
659 	cab->field_idx++;
660 	if (cab->field_idx >= cab->contest->nfields)
661 		*error = TQSL_CABRILLO_EOR;
662 	else
663 		*error = TQSL_CABRILLO_NO_ERROR;
664 	return 0;
665  err:
666 	strncpy(tQSL_ErrorFile, cab->filename, sizeof tQSL_ErrorFile);
667 	tQSL_ErrorFile[sizeof tQSL_ErrorFile-1] = 0;
668 	return 1;
669 }
670 
671 DLLEXPORT int CALLCONVENTION
672 tqsl_getCabrilloContest(tQSL_Cabrillo cabp, char *buf, int bufsiz) {
673 	TQSL_CABRILLO *cab;
674 	if ((cab = check_cabrillo(cabp)) == 0)
675 		return 1;
676 	if (buf == 0 || bufsiz <= 0) {
677 		tQSL_Error = TQSL_ARGUMENT_ERROR;
678 		return 1;
679 	}
680 	if (bufsiz < static_cast<int>(strlen(cab->contest->contest_name))+1) {
681 		tQSL_Error = TQSL_BUFFER_ERROR;
682 		return 1;
683 	}
684 	strncpy(buf, cab->contest->contest_name, bufsiz);
685 	return 0;
686 }
687 
688 DLLEXPORT int CALLCONVENTION
689 tqsl_getCabrilloFreqType(tQSL_Cabrillo cabp, TQSL_CABRILLO_FREQ_TYPE *type) {
690 	TQSL_CABRILLO *cab;
691 	if ((cab = check_cabrillo(cabp)) == 0)
692 		return 1;
693 	if (type == 0) {
694 		tQSL_Error = TQSL_ARGUMENT_ERROR;
695 		return 1;
696 	}
697 	*type = cab->contest->type;
698 	return 0;
699 }
700 
701 DLLEXPORT int CALLCONVENTION
702 tqsl_getCabrilloLine(tQSL_Cabrillo cabp, int *lineno) {
703 	TQSL_CABRILLO *cab;
704 	if ((cab = check_cabrillo(cabp)) == 0)
705 		return 1;
706 	if (lineno == 0) {
707 		tQSL_Error = TQSL_ARGUMENT_ERROR;
708 		return 1;
709 	}
710 	*lineno = cab->line_no;
711 	return 0;
712 }
713 
714 DLLEXPORT const char* CALLCONVENTION
715 tqsl_getCabrilloRecordText(tQSL_Cabrillo cabp) {
716 	TQSL_CABRILLO *cab;
717 	if ((cab = check_cabrillo(cabp)) == 0)
718 		return 0;
719 	return cab->rec;
720 }
721