/* * Copyright (C) 2016 Robin Krahl * * dbfp.c */ #include "dbfp.h" #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; }; 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); static int dbfp_url(struct dbfp *dbfp, char *service, char *query, char **url); static int check_ele_error(const char *name, const char **atts); static int read_string(const char **atts, size_t idx, char **str); static int read_float(const char **atts, size_t idx, float *f); static int add_location(struct location_data *ld, const char **atts, size_t idx_name, size_t idx_id, size_t idx_lon, size_t idx_lat); static void location_start(void *data, const char *name, const char **atts); static void location_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_name(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; int len; 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; } query = malloc(DBFP_URL_LEN); if (!query) { err = ENOMEM; goto cleanup; } len = snprintf(query, DBFP_URL_LEN, "input=%s", input_enc); if (len < 0 || len >= DBFP_URL_LEN) { err = DBFP_ERROR_FORMAT; goto cleanup; } parser = XML_ParserCreateNS(NULL, '\0'); XML_SetUserData(parser, &pd); XML_SetElementHandler(parser, location_start, location_end); err = dbfp_request(dbfp, "location.name", query, parser); if (pd.error) err = pd.error; if (err) goto cleanup; *n = ld.n; *locations = ld.locations; cleanup: 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); } 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) { int err = 0; char *url = NULL; err = dbfp_url(dbfp, service, query, &url); if (err) goto cleanup; 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; 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 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, char **str) { *str = strdup(atts[idx]); if (!*str) return ENOMEM; return 0; } static int read_float(const char **atts, size_t idx, float *f) { char *endptr = NULL; errno = 0; *f = strtof(atts[idx], &endptr); if (atts[idx] == endptr || errno != 0) return DBFP_ERROR_STRUCTURE; return 0; } static int add_location(struct location_data *ld, const char **atts, size_t idx_name, size_t idx_id, size_t idx_lon, size_t idx_lat) { int err = 0; char *name = NULL; char *id = NULL; float lon; float lat; err = read_string(atts, idx_name, &name); if (err) goto cleanup; err = read_string(atts, idx_id, &id); if (err) goto cleanup; err = read_float(atts, idx_lon, &lon); if (err) goto cleanup; err = read_float(atts, idx_lat, &lat); if (err) goto cleanup; /* TODO(robin.krahl): don't give up the old memory if realloc fails */ ld->locations = realloc(ld->locations, (ld->n + 1) * sizeof(*ld->locations)); if (!ld->locations) { err = ENOMEM; goto cleanup; } ld->locations[ld->n].name = name; ld->locations[ld->n].id = id; ld->locations[ld->n].lon = lon; ld->locations[ld->n].lat = lat; ld->n++; cleanup: if (err) { free(name); free(id); } 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; size_t idx_name = 0; size_t idx_id = 0; size_t idx_lon = 0; size_t idx_lat = 0; size_t i; err = check_ele_error(name, atts); if (err) goto cleanup; if (strcmp(name, "StopLocation") != 0) goto cleanup; for (i = 0; atts[i]; i += 2) { if (strcmp(atts[i], "name") == 0) idx_name = i + 1; else if (strcmp(atts[i], "id") == 0) idx_id = i + 1; else if (strcmp(atts[i], "lon") == 0) idx_lon = i + 1; else if (strcmp(atts[i], "lat") == 0) idx_lat = i + 1; } if (!idx_name || !idx_id || !idx_lon || !idx_lat) { err = DBFP_ERROR_STRUCTURE; goto cleanup; } err = add_location(ld, atts, idx_name, idx_id, idx_lon, idx_lat); cleanup: if (err) pd->error = err; } static void location_end(void *data, const char *name) { }