/* * Copyright (C) 2016 Robin Krahl * * dbfp.c */ #include "dbfp.h" #include #include #include #include #include #include #define DBFP_BASE_URL "http://open-api.bahn.de/bin/rest.exe" #define DBFP_URL_FORMAT "%s/%s?authKey=%s&%s" #define DBFP_URL_LEN 8000 struct parse_data { int error; void *data; }; struct location_data { size_t n; struct dbfp_location *locations; }; struct departure_data { size_t n; struct dbfp_departure *departures; }; struct object_data { const char *name; int (*read)(const char **, size_t, void *); void *value; int required; }; static int dbfp_parse(void *contents, size_t len, size_t n, void *data); static int dbfp_request(struct dbfp *dbfp, char *service, char *query, XML_Parser parser, struct parse_data *pd); static int dbfp_url(struct dbfp *dbfp, char *service, char *query, char **url); static int dbfp_format_url(char **out, const char *format, ...); static int dbfp_format_time(char **out, size_t n, const char *format, const struct tm *tm); static int check_ele_error(const char *name, const char **atts); static int read_string(const char **atts, size_t idx, void *value); static int read_float(const char **atts, size_t idx, void *value); static int read_datetime(const char **atts, size_t idx, void *value, const char *format); static int read_date(const char **atts, size_t idx, void *value); static int read_time(const char **atts, size_t idx, void *value); static int read_object_indizes(const char **atts, struct object_data *data, size_t *indizes); static int read_object(const char **atts, struct object_data *data); static void location_start(void *data, const char *name, const char **atts); static void location_end(void *data, const char *name); static void departure_start(void *data, const char *name, const char **atts); static void departure_end(void *data, const char *name); int dbfp_init(struct dbfp *dbfp, char *key) { int err = 0; char *keydup = NULL; CURL *curl = NULL; if (!dbfp || !key) return EINVAL; keydup = strdup(key); if (!keydup) { err = ENOMEM; goto cleanup; } curl_global_init(CURL_GLOBAL_ALL ^ CURL_GLOBAL_SSL); curl = curl_easy_init(); if (!curl) { err = DBFP_ERROR_CURL; goto cleanup; } dbfp->key = keydup; dbfp->curl = curl; cleanup: if (err) { free(keydup); if (curl) curl_easy_cleanup(curl); } return err; } void dbfp_close(struct dbfp *dbfp) { if (!dbfp) return; if (dbfp->curl) { curl_easy_cleanup(dbfp->curl); dbfp->curl = NULL; } curl_global_cleanup(); free(dbfp->key); dbfp->key = NULL; } void dbfp_location_close(struct dbfp_location *location) { if (!location) return; free(location->name); free(location->id); } int dbfp_query_location(struct dbfp *dbfp, char *input, size_t *n, struct dbfp_location **locations) { int err = 0; char *input_enc = NULL; char *query = NULL; XML_Parser parser = NULL; struct location_data ld = { .n = 0, .locations = NULL }; struct parse_data pd = { .error = 0, .data = &ld }; if (!dbfp || !input || !n || !locations) return EINVAL; *n = 0; *locations = NULL; input_enc = curl_easy_escape(dbfp->curl, input, 0); if (!input_enc) { err = DBFP_ERROR_CURL; goto cleanup; } err = dbfp_format_url(&query, "input=%s", input_enc); if (err) goto cleanup; parser = XML_ParserCreateNS(NULL, '\0'); XML_SetElementHandler(parser, location_start, location_end); err = dbfp_request(dbfp, "location.name", query, parser, &pd); if (err) goto cleanup; *n = ld.n; *locations = ld.locations; cleanup: // TODO check whether the elements of ld.locations should be closed if (err) free(ld.locations); if (input_enc) curl_free(input_enc); XML_ParserFree(parser); free(query); return err; } void dbfp_departure_close(struct dbfp_departure *departure) { if (!departure) return; free(departure->name); free(departure->type); free(departure->stopid); free(departure->stop); free(departure->direction); free(departure->track); } int dbfp_query_departure(struct dbfp *dbfp, struct dbfp_location *location, char *location_id, time_t time, size_t *n, struct dbfp_departure **departures) { int err = 0; char *query = 0; char *query_time = NULL; char *query_date = NULL; XML_Parser parser = NULL; struct departure_data dd = { .n = 0, .departures = NULL }; struct parse_data pd = { .error = 0, .data = &dd }; struct tm *tm; if (!dbfp || !n || !departures) return EINVAL; if ((!location && !location_id) || (location && !location->id)) return EINVAL; *n = 0; *departures = 0; if (time) { tm = localtime(&time); if (!tm) { err = DBFP_ERROR_FORMAT; goto cleanup; } err = dbfp_format_time(&query_time, 5, "%H:%M", tm); if (err) goto cleanup; err = dbfp_format_time(&query_date, 10, "%Y-%m-%d", tm); if (err) goto cleanup; err = dbfp_format_url(&query, "id=%s&date=%s&time=%s", location ? location->id : location_id, query_date, query_time); } else { err = dbfp_format_url(&query, "id=%s", location ? location->id : location_id); } if (err) goto cleanup; parser = XML_ParserCreateNS(NULL, '\0'); XML_SetElementHandler(parser, departure_start, departure_end); err = dbfp_request(dbfp, "departureBoard", query, parser, &pd); if (err) goto cleanup; *n = dd.n; *departures = dd.departures; cleanup: // TODO check wheterh the elements of dd.departures should be closed if (err) free(dd.departures); XML_ParserFree(parser); free(query_time); free(query_date); free(query); return err; } static int dbfp_parse(void *contents, size_t len, size_t n, void *data) { XML_Parser parser = (XML_Parser)data; struct parse_data *pd; size_t size = len * n; pd = (struct parse_data *)XML_GetUserData(parser); if (!pd->error && !XML_Parse(parser, contents, size, 0)) pd->error = DBFP_ERROR_PARSE; return size; } static int dbfp_request(struct dbfp *dbfp, char *service, char *query, XML_Parser parser, struct parse_data *pd) { int err = 0; char *url = NULL; err = dbfp_url(dbfp, service, query, &url); if (err) goto cleanup; XML_SetUserData(parser, pd); curl_easy_setopt(dbfp->curl, CURLOPT_URL, url); curl_easy_setopt(dbfp->curl, CURLOPT_WRITEFUNCTION, dbfp_parse); curl_easy_setopt(dbfp->curl, CURLOPT_WRITEDATA, (void *)parser); if (curl_easy_perform(dbfp->curl) != CURLE_OK) err = DBFP_ERROR_CURL; if (pd->error) err = pd->error; cleanup: free(url); return err; } static int dbfp_url(struct dbfp *dbfp, char *service, char *query, char **url) { int len; *url = malloc(DBFP_URL_LEN); if (!*url) return ENOMEM; len = snprintf(*url, DBFP_URL_LEN, DBFP_URL_FORMAT, DBFP_BASE_URL, service, dbfp->key, query); if (len < 0 || len >= DBFP_URL_LEN) { free(*url); *url = NULL; return DBFP_ERROR_FORMAT; } return 0; } static int dbfp_format_url(char **out, const char *format, ...) { va_list args; int len; *out = malloc(DBFP_URL_LEN); if (!*out) return ENOMEM; va_start(args, format); len = vsnprintf(*out, DBFP_URL_LEN, format, args); va_end(args); if (len < 0 || len >= DBFP_URL_LEN) { free(*out); *out = NULL; return DBFP_ERROR_FORMAT; } return 0; } static int dbfp_format_time(char **out, size_t n, const char *format, const struct tm *tm) { size_t len; *out = malloc((n + 1) * sizeof(**out)); if (!*out) return ENOMEM; /* TODO: check timezone */ len = strftime(*out, n + 1, format, tm); if (len != n) { free(*out); *out = NULL; return DBFP_ERROR_FORMAT; } return 0; } static int check_ele_error(const char *name, const char **atts) { if (strcmp(name, "Error") == 0) { return DBFP_ERROR_API; } return 0; } static int read_string(const char **atts, size_t idx, void *value) { char **str = (char **)value; *str = strdup(atts[idx]); if (!*str) return ENOMEM; return 0; } static int read_float(const char **atts, size_t idx, void *value) { float *f = (float *)value; char *endptr = NULL; errno = 0; *f = strtof(atts[idx], &endptr); if (atts[idx] == endptr || errno != 0) return DBFP_ERROR_STRUCTURE; return 0; } static int read_datetime(const char **atts, size_t idx, void *value, const char *format) { struct tm *tm = (struct tm *)value; /* * TODO: check whether it is safe to rely on the fact that strptime * does not touch the unhandled values */ if (!strptime(atts[idx], format, tm)) return DBFP_ERROR_STRUCTURE; return 0; } static int read_date(const char **atts, size_t idx, void *value) { return read_datetime(atts, idx, value, "%Y-%m-%d"); } static int read_time(const char **atts, size_t idx, void *value) { return read_datetime(atts, idx, value, "%H:%M"); } static int read_object_indizes(const char **atts, struct object_data *data, size_t *indizes) { size_t i; size_t j; for (i = 0; data[i].name; i++) indizes[i] = 0; for (i = 0; atts[i]; i += 2) { for (j = 0; data[j].name; j++) { if (strcmp(atts[i], data[j].name) == 0) { indizes[j] = i + 1; break; } } } for (i = 0; data[i].name; i++) { /* * allow some items to be missing due to the type/tyoe/tyte * confusion */ if (!indizes[i] && data[i].required) return DBFP_ERROR_STRUCTURE; } return 0; } static int read_object(const char **atts, struct object_data *data) { int err = 0; size_t i = 0; size_t n = 0; size_t *indizes; for (i = 0; data[i].name; i++) n++; indizes = malloc(n * sizeof(*indizes)); if (!indizes) return ENOMEM; err = read_object_indizes(atts, data, indizes); if (err) goto cleanup; for (i = 0; data[i].name; i++) { if (!indizes[i]) continue; err = data[i].read(atts, indizes[i], data[i].value); if (err) goto cleanup; } cleanup: free(indizes); return err; } static void location_start(void *data, const char *name, const char **atts) { int err = 0; struct parse_data *pd = (struct parse_data *)data; struct location_data *ld = (struct location_data *)pd->data; struct dbfp_location location = { NULL, NULL, 0, 0 }; struct object_data od[] = { { "name", &read_string, &location.name, 1 }, { "id", &read_string, &location.id, 1 }, { "lon", &read_float, &location.lon, 1 }, { "lat", &read_float, &location.lat, 1 }, { NULL, NULL, NULL, 0 } }; err = check_ele_error(name, atts); if (err) goto cleanup; if (strcmp(name, "StopLocation") != 0) goto cleanup; err = read_object(atts, od); if (err) goto cleanup; ld->locations = realloc(ld->locations, (ld->n + 1) * sizeof(*ld->locations)); if (!ld->locations) { err = ENOMEM; goto cleanup; } ld->locations[ld->n] = location; ld->n++; cleanup: if (err) { dbfp_location_close(&location); pd->error = err; } } static void location_end(void *data, const char *name) { } static void departure_start(void *data, const char *name, const char **atts) { int err = 0; struct parse_data *pd = (struct parse_data *)data; struct departure_data *dd = (struct departure_data *)pd->data; struct tm tm; struct dbfp_departure departure = { NULL, NULL, NULL, NULL, 0, NULL, NULL }; struct object_data od[] = { { "name", &read_string, &departure.name, 1 }, { "type", &read_string, &departure.type, 0 }, { "tyte", &read_string, &departure.type, 0 }, { "tyoe", &read_string, &departure.type, 0 }, { "stopid", &read_string, &departure.stopid, 1 }, { "stop", &read_string, &departure.stop, 1 }, { "date", &read_date, &tm, 1 }, { "time", &read_time, &tm, 1 }, { "direction", &read_string, &departure.direction, 1 }, { "track", &read_string, &departure.track, 1 }, { NULL, NULL, NULL, 0 } }; err = check_ele_error(name, atts); if (err) goto cleanup; if (strcmp(name, "Departure") != 0) goto cleanup; memset(&tm, 0, sizeof(tm)); err = read_object(atts, od); if (err) goto cleanup; dd->departures = realloc(dd->departures, (dd->n + 1) * sizeof(*dd->departures)); if (!dd->departures) { err = ENOMEM; goto cleanup; } /* TODO: check timezone */ departure.time = mktime(&tm); dd->departures[dd->n] = departure; dd->n++; cleanup: if (err) { dbfp_departure_close(&departure); pd->error = err; } } static void departure_end(void *data, const char *name) { }