1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* weather-metar.c - Weather server functions (METAR)
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, see
16  * <https://www.gnu.org/licenses/>.
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/types.h>
26 
27 #include "gweather-private.h"
28 
29 enum {
30     TIME_RE,
31     WIND_RE,
32     VIS_RE,
33     COND_RE,
34     CLOUD_RE,
35     TEMP_RE,
36     PRES_RE,
37 
38     RE_NUM
39 };
40 
41 /* Return time of weather report as secs since epoch UTC */
42 static time_t
make_time(gint utcDate,gint utcHour,gint utcMin)43 make_time (gint utcDate, gint utcHour, gint utcMin)
44 {
45     const time_t now = time (NULL);
46     struct tm tm;
47 
48     localtime_r (&now, &tm);
49 
50     /* If last reading took place just before midnight UTC on the
51      * first, adjust the date downward to allow for the month
52      * change-over.  This ASSUMES that the reading won't be more than
53      * 24 hrs old! */
54     if ((utcDate > tm.tm_mday) && (tm.tm_mday == 1)) {
55         tm.tm_mday = 0; /* mktime knows this is the last day of the previous
56 			 * month. */
57     } else {
58         tm.tm_mday = utcDate;
59     }
60     tm.tm_hour = utcHour;
61     tm.tm_min  = utcMin;
62     tm.tm_sec  = 0;
63 
64     /* mktime() assumes value is local, not UTC.  Use tm_gmtoff to compensate */
65 #ifdef HAVE_TM_TM_GMOFF
66     return tm.tm_gmtoff + mktime (&tm);
67 #elif defined HAVE_TIMEZONE
68     return timezone + mktime (&tm);
69 #endif
70 }
71 
72 static void
metar_tok_time(gchar * tokp,GWeatherInfo * info)73 metar_tok_time (gchar *tokp, GWeatherInfo *info)
74 {
75     gint day, hr, min;
76 
77     g_debug ("metar_tok_time: %s", tokp);
78 
79     sscanf (tokp, "%2u%2u%2u", &day, &hr, &min);
80     info->update = make_time (day, hr, min);
81 }
82 
83 static void
metar_tok_wind(gchar * tokp,GWeatherInfo * info)84 metar_tok_wind (gchar *tokp, GWeatherInfo *info)
85 {
86     gchar sdir[4], sspd[4], sgust[4];
87     gint dir, spd = -1;
88     gchar *gustp;
89     size_t glen;
90 
91     g_debug ("metar_tok_wind: %s", tokp);
92 
93     strncpy (sdir, tokp, 3);
94     sdir[3] = 0;
95     dir = (!strcmp (sdir, "VRB")) ? -1 : atoi (sdir);
96 
97     memset (sspd, 0, sizeof (sspd));
98     glen = strspn (tokp + 3, CONST_DIGITS);
99     strncpy (sspd, tokp + 3, glen);
100     spd = atoi (sspd);
101     tokp += glen + 3;
102 
103     gustp = strchr (tokp, 'G');
104     if (gustp) {
105         memset (sgust, 0, sizeof (sgust));
106 	glen = strspn (gustp + 1, CONST_DIGITS);
107         strncpy (sgust, gustp + 1, glen);
108 	tokp = gustp + 1 + glen;
109     }
110 
111     if (!strcmp (tokp, "MPS"))
112 	info->windspeed = WINDSPEED_MS_TO_KNOTS ((GWeatherWindSpeed)spd);
113     else
114 	info->windspeed = (GWeatherWindSpeed)spd;
115 
116     if ((349 <= dir) || (dir <= 11))
117         info->wind = GWEATHER_WIND_N;
118     else if ((12 <= dir) && (dir <= 33))
119         info->wind = GWEATHER_WIND_NNE;
120     else if ((34 <= dir) && (dir <= 56))
121         info->wind = GWEATHER_WIND_NE;
122     else if ((57 <= dir) && (dir <= 78))
123         info->wind = GWEATHER_WIND_ENE;
124     else if ((79 <= dir) && (dir <= 101))
125         info->wind = GWEATHER_WIND_E;
126     else if ((102 <= dir) && (dir <= 123))
127         info->wind = GWEATHER_WIND_ESE;
128     else if ((124 <= dir) && (dir <= 146))
129         info->wind = GWEATHER_WIND_SE;
130     else if ((147 <= dir) && (dir <= 168))
131         info->wind = GWEATHER_WIND_SSE;
132     else if ((169 <= dir) && (dir <= 191))
133         info->wind = GWEATHER_WIND_S;
134     else if ((192 <= dir) && (dir <= 213))
135         info->wind = GWEATHER_WIND_SSW;
136     else if ((214 <= dir) && (dir <= 236))
137         info->wind = GWEATHER_WIND_SW;
138     else if ((237 <= dir) && (dir <= 258))
139         info->wind = GWEATHER_WIND_WSW;
140     else if ((259 <= dir) && (dir <= 281))
141         info->wind = GWEATHER_WIND_W;
142     else if ((282 <= dir) && (dir <= 303))
143         info->wind = GWEATHER_WIND_WNW;
144     else if ((304 <= dir) && (dir <= 326))
145         info->wind = GWEATHER_WIND_NW;
146     else if ((327 <= dir) && (dir <= 348))
147         info->wind = GWEATHER_WIND_NNW;
148 }
149 
150 static void
metar_tok_vis(gchar * tokp,GWeatherInfo * info)151 metar_tok_vis (gchar *tokp, GWeatherInfo *info)
152 {
153     gchar *pfrac, *pend, *psp;
154     gchar sval[6];
155     gint num, den, val;
156 
157     g_debug ("metar_tok_vis: %s", tokp);
158 
159     memset (sval, 0, sizeof (sval));
160 
161     if (!strcmp (tokp,"CAVOK")) {
162         // "Ceiling And Visibility OK": visibility >= 10 KM
163         info->visibility=10000. / VISIBILITY_SM_TO_M (1.);
164         info->sky = GWEATHER_SKY_CLEAR;
165     } else if (0 != (pend = strstr (tokp, "SM"))) {
166         // US observation: field ends with "SM"
167         pfrac = strchr (tokp, '/');
168         if (pfrac) {
169 	    if (*tokp == 'M') {
170 	        info->visibility = 0.001;
171 	    } else {
172 	        num = (*(pfrac - 1) - '0');
173 		strncpy (sval, pfrac + 1, pend - pfrac - 1);
174 		den = atoi (sval);
175 		info->visibility =
176 		    ((GWeatherVisibility)num / ((GWeatherVisibility)den));
177 
178 		psp = strchr (tokp, ' ');
179 		if (psp) {
180 		    *psp = '\0';
181 		    val = atoi (tokp);
182 		    info->visibility += (GWeatherVisibility)val;
183 		}
184 	    }
185 	} else {
186 	    strncpy (sval, tokp, pend - tokp);
187             val = atoi (sval);
188             info->visibility = (GWeatherVisibility)val;
189 	}
190     } else {
191         // International observation: NNNN(DD NNNNDD)?
192         // For now: use only the minimum visibility and ignore its direction
193         strncpy (sval, tokp, strspn (tokp, CONST_DIGITS));
194 	val = atoi (sval);
195 	info->visibility = (GWeatherVisibility)val / VISIBILITY_SM_TO_M (1.);
196     }
197 }
198 
199 static void
metar_tok_cloud(gchar * tokp,GWeatherInfo * info)200 metar_tok_cloud (gchar *tokp, GWeatherInfo *info)
201 {
202     gchar stype[4], salt[4];
203 
204     g_debug ("metar_tok_cloud: %s", tokp);
205 
206     strncpy (stype, tokp, 3);
207     stype[3] = 0;
208     if (strlen (tokp) == 6) {
209         strncpy (salt, tokp + 3, 3);
210         salt[3] = 0;
211     }
212 
213     if (!strcmp (stype, "CLR")) {
214         info->sky = GWEATHER_SKY_CLEAR;
215     } else if (!strcmp (stype, "SKC")) {
216         info->sky = GWEATHER_SKY_CLEAR;
217     } else if (!strcmp (stype, "NSC")) {
218         info->sky = GWEATHER_SKY_CLEAR;
219     } else if (!strcmp (stype, "BKN")) {
220         info->sky = GWEATHER_SKY_BROKEN;
221     } else if (!strcmp (stype, "SCT")) {
222         info->sky = GWEATHER_SKY_SCATTERED;
223     } else if (!strcmp (stype, "FEW")) {
224         info->sky = GWEATHER_SKY_FEW;
225     } else if (!strcmp (stype, "OVC")) {
226         info->sky = GWEATHER_SKY_OVERCAST;
227     }
228 }
229 
230 static void
metar_tok_pres(gchar * tokp,GWeatherInfo * info)231 metar_tok_pres (gchar *tokp, GWeatherInfo *info)
232 {
233     g_debug ("metar_tok_pres: %s", tokp);
234 
235     if (*tokp == 'A') {
236         gchar sintg[3], sfract[3];
237         gint intg, fract;
238 
239         strncpy (sintg, tokp + 1, 2);
240         sintg[2] = 0;
241         intg = atoi (sintg);
242 
243         strncpy (sfract, tokp + 3, 2);
244         sfract[2] = 0;
245         fract = atoi (sfract);
246 
247         info->pressure = (GWeatherPressure)intg + (((GWeatherPressure)fract)/100.0);
248     } else {  /* *tokp == 'Q' */
249         gchar spres[5];
250         gint pres;
251 
252         strncpy (spres, tokp + 1, 4);
253         spres[4] = 0;
254         pres = atoi (spres);
255 
256         info->pressure = PRESSURE_MBAR_TO_INCH ((GWeatherPressure)pres);
257     }
258 }
259 
260 static void
metar_tok_temp(gchar * tokp,GWeatherInfo * info)261 metar_tok_temp (gchar *tokp, GWeatherInfo *info)
262 {
263     gchar *ptemp, *pdew, *psep;
264 
265     g_debug ("metar_tok_temp: %s", tokp);
266 
267     psep = strchr (tokp, '/');
268     *psep = 0;
269     ptemp = tokp;
270     pdew = psep + 1;
271 
272     info->temp = (*ptemp == 'M') ? TEMP_C_TO_F (-atoi (ptemp + 1))
273 	: TEMP_C_TO_F (atoi (ptemp));
274     if (*pdew) {
275 	info->dew = (*pdew == 'M') ? TEMP_C_TO_F (-atoi (pdew + 1))
276 	    : TEMP_C_TO_F (atoi (pdew));
277     } else {
278 	info->dew = -1000.0;
279     }
280 }
281 
282 /* How "important" are the conditions to be reported to the user.
283    Indexed by GWeatherConditionPhenomenon */
284 static const int importance_scale[] = {
285     0, /* invalid */
286     0, /* none */
287     20, /* drizzle */
288     30, /* rain */
289     35, /* snow */
290     35, /* snow grains */
291     35, /* ice crystals */
292     35, /* ice pellets */
293     35, /* hail */
294     35, /* small hail */
295     20, /* unknown precipitation */
296     10, /* mist */
297     15, /* fog */
298     15, /* smoke */
299     18, /* volcanic ash */
300     18, /* sand */
301     15, /* haze */
302     15, /* spray */
303     15, /* dust */
304     40, /* squall */
305     50, /* sandstorm */
306     50, /* duststorm */
307     70, /* funnel cloud */
308     70, /* tornado */
309     50, /* dust whirls */
310 };
311 
312 static gboolean
condition_more_important(GWeatherConditions * which,GWeatherConditions * than)313 condition_more_important (GWeatherConditions *which,
314 			  GWeatherConditions *than)
315 {
316     if (!than->significant)
317 	return TRUE;
318     if (!which->significant)
319 	return FALSE;
320 
321     if (importance_scale[than->phenomenon] <
322 	importance_scale[which->phenomenon])
323 	return TRUE;
324 
325     return FALSE;
326 }
327 
328 static void
metar_tok_cond(gchar * tokp,GWeatherInfo * info)329 metar_tok_cond (gchar *tokp, GWeatherInfo *info)
330 {
331     GWeatherConditions new_cond;
332     gchar squal[3], sphen[4];
333     gchar *pphen;
334 
335     g_debug ("metar_tok_cond: %s", tokp);
336 
337     if ((strlen (tokp) > 3) && ((*tokp == '+') || (*tokp == '-')))
338         ++tokp;   /* FIX */
339 
340     if ((*tokp == '+') || (*tokp == '-'))
341         pphen = tokp + 1;
342     else if (strlen (tokp) < 4)
343         pphen = tokp;
344     else
345         pphen = tokp + 2;
346 
347     memset (squal, 0, sizeof (squal));
348     strncpy (squal, tokp, pphen - tokp);
349     squal[pphen - tokp] = 0;
350 
351     memset (sphen, 0, sizeof (sphen));
352     strncpy (sphen, pphen, sizeof (sphen));
353     sphen[sizeof (sphen)-1] = '\0';
354 
355     /* Defaults */
356     new_cond.qualifier = GWEATHER_QUALIFIER_NONE;
357     new_cond.phenomenon = GWEATHER_PHENOMENON_NONE;
358     new_cond.significant = FALSE;
359 
360     if (!strcmp (squal, "")) {
361         new_cond.qualifier = GWEATHER_QUALIFIER_MODERATE;
362     } else if (!strcmp (squal, "-")) {
363         new_cond.qualifier = GWEATHER_QUALIFIER_LIGHT;
364     } else if (!strcmp (squal, "+")) {
365         new_cond.qualifier = GWEATHER_QUALIFIER_HEAVY;
366     } else if (!strcmp (squal, "VC")) {
367         new_cond.qualifier = GWEATHER_QUALIFIER_VICINITY;
368     } else if (!strcmp (squal, "MI")) {
369         new_cond.qualifier = GWEATHER_QUALIFIER_SHALLOW;
370     } else if (!strcmp (squal, "BC")) {
371         new_cond.qualifier = GWEATHER_QUALIFIER_PATCHES;
372     } else if (!strcmp (squal, "PR")) {
373         new_cond.qualifier = GWEATHER_QUALIFIER_PARTIAL;
374     } else if (!strcmp (squal, "TS")) {
375         new_cond.qualifier = GWEATHER_QUALIFIER_THUNDERSTORM;
376     } else if (!strcmp (squal, "BL")) {
377         new_cond.qualifier = GWEATHER_QUALIFIER_BLOWING;
378     } else if (!strcmp (squal, "SH")) {
379         new_cond.qualifier = GWEATHER_QUALIFIER_SHOWERS;
380     } else if (!strcmp (squal, "DR")) {
381         new_cond.qualifier = GWEATHER_QUALIFIER_DRIFTING;
382     } else if (!strcmp (squal, "FZ")) {
383         new_cond.qualifier = GWEATHER_QUALIFIER_FREEZING;
384     } else {
385         return;
386     }
387 
388     if (!strcmp (sphen, "DZ")) {
389         new_cond.phenomenon = GWEATHER_PHENOMENON_DRIZZLE;
390     } else if (!strcmp (sphen, "RA")) {
391         new_cond.phenomenon = GWEATHER_PHENOMENON_RAIN;
392     } else if (!strcmp (sphen, "SN")) {
393         new_cond.phenomenon = GWEATHER_PHENOMENON_SNOW;
394     } else if (!strcmp (sphen, "SG")) {
395         new_cond.phenomenon = GWEATHER_PHENOMENON_SNOW_GRAINS;
396     } else if (!strcmp (sphen, "IC")) {
397         new_cond.phenomenon = GWEATHER_PHENOMENON_ICE_CRYSTALS;
398     } else if (!strcmp (sphen, "PL")) {
399         new_cond.phenomenon = GWEATHER_PHENOMENON_ICE_PELLETS;
400     } else if (!strcmp (sphen, "GR")) {
401         new_cond.phenomenon = GWEATHER_PHENOMENON_HAIL;
402     } else if (!strcmp (sphen, "GS")) {
403         new_cond.phenomenon = GWEATHER_PHENOMENON_SMALL_HAIL;
404     } else if (!strcmp (sphen, "UP")) {
405         new_cond.phenomenon = GWEATHER_PHENOMENON_UNKNOWN_PRECIPITATION;
406     } else if (!strcmp (sphen, "BR")) {
407         new_cond.phenomenon = GWEATHER_PHENOMENON_MIST;
408     } else if (!strcmp (sphen, "FG")) {
409         new_cond.phenomenon = GWEATHER_PHENOMENON_FOG;
410     } else if (!strcmp (sphen, "FU")) {
411         new_cond.phenomenon = GWEATHER_PHENOMENON_SMOKE;
412     } else if (!strcmp (sphen, "VA")) {
413         new_cond.phenomenon = GWEATHER_PHENOMENON_VOLCANIC_ASH;
414     } else if (!strcmp (sphen, "SA")) {
415         new_cond.phenomenon = GWEATHER_PHENOMENON_SAND;
416     } else if (!strcmp (sphen, "HZ")) {
417         new_cond.phenomenon = GWEATHER_PHENOMENON_HAZE;
418     } else if (!strcmp (sphen, "PY")) {
419         new_cond.phenomenon = GWEATHER_PHENOMENON_SPRAY;
420     } else if (!strcmp (sphen, "DU")) {
421         new_cond.phenomenon = GWEATHER_PHENOMENON_DUST;
422     } else if (!strcmp (sphen, "SQ")) {
423         new_cond.phenomenon = GWEATHER_PHENOMENON_SQUALL;
424     } else if (!strcmp (sphen, "SS")) {
425         new_cond.phenomenon = GWEATHER_PHENOMENON_SANDSTORM;
426     } else if (!strcmp (sphen, "DS")) {
427         new_cond.phenomenon = GWEATHER_PHENOMENON_DUSTSTORM;
428     } else if (!strcmp (sphen, "PO")) {
429         new_cond.phenomenon = GWEATHER_PHENOMENON_DUST_WHIRLS;
430     } else if (!strcmp (sphen, "+FC")) {
431         new_cond.phenomenon = GWEATHER_PHENOMENON_TORNADO;
432     } else if (!strcmp (sphen, "FC")) {
433         new_cond.phenomenon = GWEATHER_PHENOMENON_FUNNEL_CLOUD;
434     } else {
435         return;
436     }
437 
438     if ((new_cond.qualifier != GWEATHER_QUALIFIER_NONE) || (new_cond.phenomenon != GWEATHER_PHENOMENON_NONE))
439         new_cond.significant = TRUE;
440 
441     if (condition_more_important (&new_cond, &info->cond))
442 	info->cond = new_cond;
443 }
444 
445 #define TIME_RE_STR  "([0-9]{6})Z"
446 #define WIND_RE_STR  "(([0-9]{3})|VRB)([0-9]?[0-9]{2})(G[0-9]?[0-9]{2})?(KT|MPS)"
447 #define VIS_RE_STR   "((([0-9]?[0-9])|(M?([12] )?([1357]/1?[0-9])))SM)|" \
448     "([0-9]{4}(N|NE|E|SE|S|SW|W|NW( [0-9]{4}(N|NE|E|SE|S|SW|W|NW))?)?)|" \
449     "CAVOK"
450 #define COND_RE_STR  "(-|\\+)?(VC|MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PE|GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DS|PO|\\+?FC)"
451 #define CLOUD_RE_STR "((CLR|BKN|SCT|FEW|OVC|SKC|NSC)([0-9]{3}|///)?(CB|TCU|///)?)"
452 #define TEMP_RE_STR  "(M?[0-9][0-9])/(M?(//|[0-9][0-9])?)"
453 #define PRES_RE_STR  "(A|Q)([0-9]{4})"
454 
455 /* POSIX regular expressions do not allow us to express "match whole words
456  * only" in a simple way, so we have to wrap them all into
457  *   (^| )(...regex...)( |$)
458  */
459 #define RE_PREFIX "(^| )("
460 #define RE_SUFFIX ")( |$)"
461 
462 static GRegex *metar_re[RE_NUM];
463 static void (*metar_f[RE_NUM]) (gchar *tokp, GWeatherInfo *info);
464 
465 static void
metar_init_re(void)466 metar_init_re (void)
467 {
468     static gboolean initialized = FALSE;
469     if (initialized)
470         return;
471     initialized = TRUE;
472 
473     metar_re[TIME_RE] = g_regex_new (RE_PREFIX TIME_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
474     metar_re[WIND_RE] = g_regex_new (RE_PREFIX WIND_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
475     metar_re[VIS_RE] = g_regex_new (RE_PREFIX VIS_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
476     metar_re[COND_RE] = g_regex_new (RE_PREFIX COND_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
477     metar_re[CLOUD_RE] = g_regex_new (RE_PREFIX CLOUD_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
478     metar_re[TEMP_RE] = g_regex_new (RE_PREFIX TEMP_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
479     metar_re[PRES_RE] = g_regex_new (RE_PREFIX PRES_RE_STR RE_SUFFIX, G_REGEX_OPTIMIZE, 0, NULL);
480 
481     metar_f[TIME_RE] = metar_tok_time;
482     metar_f[WIND_RE] = metar_tok_wind;
483     metar_f[VIS_RE] = metar_tok_vis;
484     metar_f[COND_RE] = metar_tok_cond;
485     metar_f[CLOUD_RE] = metar_tok_cloud;
486     metar_f[TEMP_RE] = metar_tok_temp;
487     metar_f[PRES_RE] = metar_tok_pres;
488 }
489 
490 gboolean
metar_parse(gchar * metar,GWeatherInfo * info)491 metar_parse (gchar *metar, GWeatherInfo *info)
492 {
493     gchar *p;
494     //gchar *rmk;
495     gint i, i2;
496     gchar *tokp;
497 
498     g_return_val_if_fail (info != NULL, FALSE);
499     g_return_val_if_fail (metar != NULL, FALSE);
500 
501     g_debug ("About to metar_parse: %s", metar);
502 
503     metar_init_re ();
504 
505     /*
506      * Force parsing to end at "RMK" field.  This prevents a subtle
507      * problem when info within the remark happens to match an earlier state
508      * and, as a result, throws off all the remaining expression
509      */
510     if (0 != (p = strstr (metar, " RMK "))) {
511         *p = '\0';
512         //rmk = p + 5;   // uncomment this if RMK data becomes useful
513     }
514 
515     p = metar;
516     i = TIME_RE;
517     while (*p) {
518         int token_start, token_end;
519 
520         i2 = RE_NUM;
521         token_start = strlen(p);
522         token_end = token_start;
523 
524         for (i = 0; i < RE_NUM; i++) {
525             GMatchInfo *match_info;
526 
527             if (g_regex_match_full (metar_re[i], p, -1, 0, 0, &match_info, NULL))
528             {
529                 int tmp_token_start, tmp_token_end;
530                 /* Skip leading and trailing space characters, if present.
531                    (the regular expressions include those characters to
532                    only get matches limited to whole words). */
533                 g_match_info_fetch_pos (match_info, 0, &tmp_token_start, &tmp_token_end);
534                 if (p[tmp_token_start] == ' ') tmp_token_start++;
535                 if (p[tmp_token_end - 1] == ' ') tmp_token_end--;
536 
537                 /* choose the regular expression with the earliest match */
538                 if (tmp_token_start < token_start) {
539                     i2 = i;
540                     token_start = tmp_token_start;
541                     token_end = tmp_token_end;
542                 }
543             }
544 
545             g_match_info_unref (match_info);
546         }
547 
548         if (i2 != RE_NUM) {
549             tokp = g_strndup (p + token_start, token_end - token_start);
550             metar_f[i2] (tokp, info);
551             g_free (tokp);
552         }
553 
554         p += token_end;
555         p += strspn (p, " ");
556     }
557     return TRUE;
558 }
559 
560 static void
metar_finish(SoupSession * session,SoupMessage * msg,gpointer data)561 metar_finish (SoupSession *session, SoupMessage *msg, gpointer data)
562 {
563     GWeatherInfo *info;
564     WeatherLocation *loc;
565     const gchar *p, *eoln;
566     gchar *searchkey, *metar;
567     gboolean success = FALSE;
568 
569     if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
570         if (msg->status_code == SOUP_STATUS_CANCELLED) {
571 	    g_debug ("Failed to get METAR data: %d %s.\n",
572 		     msg->status_code, msg->reason_phrase);
573 	    return;
574 	}
575 
576 	info = data;
577 	if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
578 	    info->network_error = TRUE;
579 	} else {
580 	    /* Translators: %d is an error code, and %s the error string */
581 	    g_warning (_("Failed to get METAR data: %d %s.\n"),
582 		       msg->status_code, msg->reason_phrase);
583 	}
584 
585 	_gweather_info_request_done (info, msg);
586 	return;
587     }
588 
589     info = data;
590     loc = &info->location;
591 
592     g_debug ("METAR data for %s", loc->code);
593     g_debug ("%s", msg->response_body->data);
594 
595     searchkey = g_strdup_printf ("<raw_text>%s ", loc->code);
596     p = strstr (msg->response_body->data, searchkey);
597 
598     if (p) {
599 	p += strlen (searchkey);
600 	eoln = strstr (p, "</raw_text>");
601 	if (eoln)
602 	    metar = g_strndup (p, eoln - p);
603 	else
604 	    metar = g_strdup (p);
605 	success = metar_parse (metar, info);
606 	g_free (metar);
607 	if (success)
608 	  g_debug ("Successfully parsed METAR for %s", loc->code);
609 	else
610 	  g_debug ("Failed to parse raw_text METAR for %s", loc->code);
611     } else if (!strstr (msg->response_body->data, "aviationweather.gov")) {
612 	/* The response doesn't even seem to have come from NOAA...
613 	 * most likely it is a wifi hotspot login page. Call that a
614 	 * network error.
615 	 */
616 	info->network_error = TRUE;
617 	g_debug ("Response to query for %s did not come from correct server", loc->code);
618     } else {
619       g_debug ("Failed to parse METAR for %s", loc->code);
620     }
621 
622     g_free (searchkey);
623 
624     if (!info->valid)
625         info->valid = success;
626     _gweather_info_request_done (info, msg);
627 }
628 
629 /* Read current conditions and fill in info structure */
630 void
metar_start_open(GWeatherInfo * info)631 metar_start_open (GWeatherInfo *info)
632 {
633     WeatherLocation *loc;
634     SoupMessage *msg;
635 
636     g_return_if_fail (info != NULL);
637 
638     info->valid = info->network_error = FALSE;
639     loc = &info->location;
640 
641     if (!loc->latlon_valid)
642         return;
643 
644     g_debug ("metar_start_open, requesting: https://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3&mostRecent=true&fields=raw_text&stationString=%s", loc->code);
645     msg = soup_form_request_new (
646 	"GET", "https://www.aviationweather.gov/adds/dataserver_current/httpparam",
647 	"dataSource", "metars",
648 	"requestType", "retrieve",
649 	"format", "xml",
650 	"hoursBeforeNow", "3",
651 	"mostRecent", "true",
652 	"fields", "raw_text",
653 	"stationString", loc->code,
654 	NULL);
655     _gweather_info_begin_request (info, msg);
656     soup_session_queue_message (info->session, msg, metar_finish, info);
657 }
658