/* $Id$ * $URL$ * * plugin kvv (karlsruher verkehrsverbund) * * Copyright (C) 2006 Till Harbaum * Copyright (C) 2006 The LCD4Linux Team * * This file is part of LCD4Linux. * * LCD4Linux is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * LCD4Linux is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* * exported functions: * * int plugin_init_kvv (void) * adds various functions * void plugin_exit_kvv (void) * */ /* define the include files you need */ #include "config.h" #include #include #include #include #include #include #include /* network specific includes */ #include #include #include #include #include /* these should always be included */ #include "debug.h" #include "plugin.h" #include "cfg.h" #include "thread.h" /* these can't be configured as it doesn't make sense to change them */ #define HTTP_SERVER "www.init-ka.de" #define HTTP_REQUEST "/webfgi/StopInfoInplace.aspx?ID=%s" #define USER_AGENT "lcd4linux - KVV plugin (http://ssl.bulix.org/projects/lcd4linux/wiki/plugin_kvv)" #define DEFAULT_STATION_ID "89" /* Hauptbahnhof */ /* example ids: * 89 = Hauptbahnhof * 12_701 = Berufsakademie */ /* total max values to calculate shm size */ #define MAX_LINES 4 #define MAX_LINE_LENGTH 8 #define MAX_STATION_LENGTH 40 typedef struct { char line[MAX_LINE_LENGTH + 1]; char station[MAX_STATION_LENGTH + 1]; int time; } kvv_entry_t; typedef struct { int entries, error; kvv_entry_t entry[MAX_LINES]; } kvv_shm_t; static char *station_id = NULL; static char *proxy_name = NULL; static int port = 80; static pid_t pid = -1; static int refresh = 60; static int abbreviate = 0; static int initialized = 0; static int mutex = 0; static int shmid = -1; static kvv_shm_t *shm = NULL; #define SECTION "Plugin:KVV" #define TIMEOUT_SHORT 1 /* wait this long for additional data */ #define TIMEOUT_LONG 10 /* wait this long for initial data */ /* search an element in the result string */ static int get_element(char *input, char *name, char **data) { int skip = 0; int len = 0; int state = 0; /* nothing found yet */ /* search entire string */ while (*input) { if (skip == 0) { switch (state) { case 0: if (*input == '<') state = 1; else state = 0; break; case 1: /* ignore white spaces */ if (*input != ' ') { if (strncasecmp(input, name, strlen(name)) == 0) { state = 2; skip = strlen(name) - 1; } else state = 0; } break; case 2: if (*input == ' ') { *data = ++input; while (*input && (*input++ != '>')) len++; return len; } else state = 0; break; } } else if (skip) skip--; input++; } return -1; } /* serach an attribute within an element */ static int get_attrib(char *input, char *name, char **data) { int skip = 0; int len = 0; int state = 0; /* nothing found */ /* search in this element */ while (*input != '>') { /* ignore white spaces */ if (((*input != ' ') && (*input != '\t')) && (skip == 0)) { switch (state) { case 0: if (strncasecmp(input, name, strlen(name)) == 0) { state = 1; skip = strlen(name) - 1; } break; case 1: if (*input == '=') state = 2; else state = 0; break; case 2: if (*input == '\"') { *data = ++input; while (*input++ != '\"') len++; return len; } else state = 0; break; } } else if (skip) skip--; input++; } return -1; } static int http_open(char *name) { struct sockaddr_in server; struct hostent *host_info; unsigned long addr; int sock; /* create socket */ sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("failed to create socket"); return -1; } /* Erzeuge die Socketadresse des Servers * Sie besteht aus Typ, IP-Adresse und Portnummer */ memset(&server, 0, sizeof(server)); if ((addr = inet_addr(name)) != INADDR_NONE) { memcpy((char *) &server.sin_addr, &addr, sizeof(addr)); } else { /* Wandle den Servernamen in eine IP-Adresse um */ host_info = gethostbyname(name); if (NULL == host_info) { error("[KVV] Unknown server: %s", name); return -1; } memcpy((char *) &server.sin_addr, host_info->h_addr, host_info->h_length); } server.sin_family = AF_INET; server.sin_port = htons(port); /* Baue die Verbindung zum Server auf */ if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0) { perror("can't connect to server"); return -1; } return sock; } static void get_text(char *input, char *end, char *dest, int dlen) { int state = 0; /* nothing yet, outside any element */ int cnt = 0; while (*input) { switch (state) { case 0: if (*input == '<') state = 1; else { if (cnt < (dlen - 1)) dest[cnt++] = *input; } break; case 1: if (*input == '/') state = 2; else if (*input == '>') state = 0; break; case 2: if (strncasecmp(input, end, strlen(end)) == 0) { dest[cnt++] = 0; return; } break; } input++; } } static void process_station_string(char *str) { char *p, *q; int last, i; /* some strings to replace */ char *repl[] = { "Hauptbahnhof", "Hbf.", "Bahnhof", "Bhf.", "Karlsruhe", "KA", "Schienenersatzverkehr", "Ersatzv.", "Marktplatz", "Marktpl.", }; /* decode utf8 */ p = q = str; last = 0; while (*p) { if (last) { *q++ = (last << 6) | (*p & 0x3f); last = 0; } else if ((*p & 0xe0) == 0xc0) { last = *p & 3; } else *q++ = *p; p++; } *q++ = 0; /* erase multiple spaces and replace umlauts */ p = q = str; last = 1; /* no leading spaces */ while (*p) { if ((!last) || (*p != ' ')) { /* translate from latin1 to hd44780 */ if (*p == (char) 228) /* lower a umlaut */ *q++ = (char) 0xe1; else if (*p == (char) 223) /* sz ligature */ *q++ = (char) 0xe2; else if (*p == (char) 246) /* lower o umlaut */ *q++ = (char) 0xef; else if (*p == (char) 252) /* lower u umlaut */ *q++ = (char) 0xf5; else *q++ = *p; } last = (*p == ' '); p++; } *q++ = 0; /* replace certain (long) words with e.g. abbreviations if enabled */ if (abbreviate) { for (i = 0; i < (int) (sizeof(repl) / (2 * sizeof(char *))); i++) { if ((p = strstr(str, repl[2 * i])) != NULL) { /* move new string */ memcpy(p, repl[2 * i + 1], strlen(repl[2 * i + 1])); /* move rest of string down */ memmove(p + strlen(repl[2 * i + 1]), p + strlen(repl[2 * i]), strlen(str) - (p - str) - strlen(repl[2 * i]) + 1); } } } } static void kvv_client( __attribute__ ((unused)) void *dummy) { char ibuffer[8192]; char obuffer[1024]; int count, i, sock; char server_name[] = HTTP_SERVER; char *connect_to; /* connect to proxy if given, to server otherwise */ if ((proxy_name != NULL) && (strlen(proxy_name) != 0)) connect_to = proxy_name; else connect_to = server_name; info("[KVV] Connecting to %s", connect_to); while (1) { sock = http_open(connect_to); if (sock < 0) { error("[KVV] Error accessing server/proxy: %s", strerror(errno)); return; } /* create and set get request */ if (snprintf(obuffer, sizeof(obuffer), "GET http://%s" HTTP_REQUEST " HTTP/1.1\n" "Host: %s\n" "User-Agent: " USER_AGENT "\n\n", server_name, station_id, server_name) >= (int) sizeof(obuffer)) { info("[KVV] Warning, request has been truncated!"); } info("[KVV] Sending first (GET) request ..."); send(sock, obuffer, strlen(obuffer), 0); count = 0; do { fd_set rfds; struct timeval tv; FD_ZERO(&rfds); FD_SET(sock, &rfds); tv.tv_sec = count ? TIMEOUT_SHORT : TIMEOUT_LONG; tv.tv_usec = 0; i = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); if (i < 0) { perror("select"); exit(1); } if (i != 0) { i = recv(sock, ibuffer + count, sizeof(ibuffer) - count - 1, 0); count += i; } } while (i > 0); ibuffer[count] = 0; /* terminate string */ close(sock); if (!count) info("[KVV] empty/no reply"); if (count > 0) { char *input, *cookie, *name = NULL, *value = NULL; int input_len, cookie_len, name_len, value_len; /* buffer to html encode value */ char value_enc[512]; int value_enc_len; /* find cookie */ cookie_len = 0; cookie = strstr(ibuffer, "Set-Cookie:"); if (cookie) { cookie += strlen("Set-Cookie:"); while (*cookie == ' ') cookie++; while (cookie[cookie_len] != ';') cookie_len++; } /* find input element */ input_len = get_element(ibuffer, "input", &input); if (input_len > 0) { char *input_end = input; while (*input_end != '>') input_end++; while (*input_end != '\"') input_end--; *(input_end + 1) = 0; name_len = get_attrib(input, "name", &name); value_len = get_attrib(input, "value", &value); for (value_enc_len = 0, i = 0; i < value_len; i++) { if (isalnum(value[i])) value_enc[value_enc_len++] = value[i]; else { sprintf(value_enc + value_enc_len, "%%%02X", 0xff & value[i]); value_enc_len += 3; } } if (cookie_len >= 0) cookie[cookie_len] = 0; if (name_len >= 0) name[name_len] = 0; if (value_len >= 0) value[value_len] = 0; if (value_enc_len >= 0) value_enc[value_enc_len] = 0; sock = http_open(connect_to); /* send POST */ if (snprintf(obuffer, sizeof(obuffer), "POST http://%s" HTTP_REQUEST " HTTP/1.1\n" "Host: %s\n" "User-Agent: " USER_AGENT "\n" "Cookie: %s\n" "Content-Type: application/x-www-form-urlencoded\n" "Content-Length: %d\n" "\n%s=%s", server_name, station_id, server_name, cookie, name_len + value_enc_len + 1, name, value_enc) >= (int) sizeof(obuffer)) { info("[KVV] Warning, request has been truncated!"); } info("[KVV] Sending second (POST) request ..."); send(sock, obuffer, strlen(obuffer), 0); count = 0; do { fd_set rfds; struct timeval tv; FD_ZERO(&rfds); FD_SET(sock, &rfds); tv.tv_sec = count ? TIMEOUT_SHORT : TIMEOUT_LONG; tv.tv_usec = 0; i = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); if (i > 0) { i = recv(sock, ibuffer + count, sizeof(ibuffer) - count - 1, 0); count += i; } } while (i > 0); /* leave on select or read error */ ibuffer[count] = 0; /* printf("Result (%d):\n%s\n", count, ibuffer); */ /* close connection */ close(sock); if (!count) info("[KVV] empty/no reply"); if (count > 0) { int last_was_stop = 0; char *td = ibuffer; char str[32]; int td_len, i, overflow = 0; /* lock shared memory */ mutex_lock(mutex); /* free allocated memory */ shm->entries = 0; if (strstr(ibuffer, "Die Daten konnten nicht abgefragt werden.") != NULL) { info("[KVV] Server returned error!"); /* printf("%s\n", ibuffer); */ shm->error = 1; } else shm->error = 0; /* scan through all entries and search the line nums */ do { if ((td_len = get_element(td, "td", &td)) > 0) { char *attr, *p; int attr_len; /* time does not have a class but comes immediately after stop :-( */ if (last_was_stop) { td += td_len + 1; get_text(td, "td", str, sizeof(str)); /* time needs special treatment */ if (strncasecmp(str, "sofort", strlen("sofort")) == 0) i = 0; else { /* skip everything that is not a number */ p = str; while (!isdigit(*p)) p++; /* and convert remaining to number */ i = atoi(p); } /* save time */ if (!overflow) shm->entry[shm->entries - 1].time = i; last_was_stop = 0; } /* linenum and stopname fields have proper classes */ if ((attr_len = get_attrib(td, "class", &attr)) > 0) { if (strncasecmp(attr, "lineNum", strlen("lineNum")) == 0) { td += td_len + 1; get_text(td, "td", str, sizeof(str)); if (shm->entries < MAX_LINES) { /* allocate a new slot */ shm->entries++; shm->entry[shm->entries - 1].time = -1; memset(shm->entry[shm->entries - 1].line, 0, MAX_LINE_LENGTH + 1); memset(shm->entry[shm->entries - 1].station, 0, MAX_STATION_LENGTH + 1); /* add new lines entry */ strncpy(shm->entry[shm->entries - 1].line, str, MAX_LINE_LENGTH); } else overflow = 1; /* don't add further entries */ } if (strncasecmp(attr, "stopname", strlen("stopname")) == 0) { td += td_len + 1; get_text(td, "td", str, sizeof(str)); /* stopname may need further tuning */ process_station_string(str); if (!overflow) strncpy(shm->entry[shm->entries - 1].station, str, MAX_STATION_LENGTH); last_was_stop = 1; } } } } while (td_len >= 0); mutex_unlock(mutex); } } } sleep(refresh); } } static int kvv_fork(void) { if (initialized) return 0; info("[KVV] creating client thread"); /* set this here to prevent continous retries if init fails */ initialized = 1; /* create communication buffer */ shmid = shm_create((void **) &shm, sizeof(kvv_shm_t)); /* catch error */ if (shmid < 0) { error("[KVV] Shared memory allocation failed!"); return -1; } /* attach client thread */ mutex = mutex_create(); pid = thread_create("plugin_kvv", kvv_client, NULL); if (pid < 0) { error("[KVV] Unable to fork client: %s", strerror(errno)); return -1; } info("[KVV] forked client with pid %d", pid); return 0; } static void kvv_start(void) { static int started = 0; int val; char *p; if (started) return; started = 1; /* parse parameter */ if ((p = cfg_get(SECTION, "StationID", DEFAULT_STATION_ID)) != NULL) { station_id = malloc(strlen(p) + 1); strcpy(station_id, p); } info("[KVV] Using station %s", station_id); if ((p = cfg_get(SECTION, "Proxy", NULL)) != NULL) { proxy_name = malloc(strlen(p) + 1); strcpy(proxy_name, p); info("[KVV] Using proxy \"%s\"", proxy_name); } if (cfg_number(SECTION, "Port", 0, 0, 65535, &val) > 0) { port = val; info("[KVV] Using port %d", port); } else { info("[KVV] Using default port %d", port); } if (cfg_number(SECTION, "Refresh", 0, 0, 65535, &val) > 0) { refresh = val; info("[KVV] Using %d seconds refresh interval", refresh); } else { info("[KVV] Using default refresh interval of %d seconds", refresh); } if (cfg_number(SECTION, "Abbreviate", 0, 0, 65535, &val) > 0) { abbreviate = val; info("[KVV] Abbreviation enabled: %s", abbreviate ? "on" : "off"); } else { info("[KVV] Default abbreviation setting: %s", abbreviate ? "on" : "off"); } } static void kvv_line(RESULT * result, RESULT * arg1) { int index = (int) R2N(arg1); kvv_start(); if (kvv_fork() != 0) { SetResult(&result, R_STRING, ""); return; } mutex_lock(mutex); if (index < shm->entries) { SetResult(&result, R_STRING, shm->entry[index].line); } else SetResult(&result, R_STRING, ""); mutex_unlock(mutex); } static void kvv_station(RESULT * result, RESULT * arg1) { int index = (int) R2N(arg1); kvv_start(); if (kvv_fork() != 0) { SetResult(&result, R_STRING, ""); return; } mutex_lock(mutex); if (shm->error && index == 0) SetResult(&result, R_STRING, "Server Err"); else { if (index < shm->entries) SetResult(&result, R_STRING, shm->entry[index].station); else SetResult(&result, R_STRING, ""); } mutex_unlock(mutex); } static void kvv_time(RESULT * result, RESULT * arg1) { int index = (int) R2N(arg1); double value = -1.0; kvv_start(); if (kvv_fork() != 0) { SetResult(&result, R_STRING, ""); return; } mutex_lock(mutex); if (index < shm->entries) value = shm->entry[index].time; SetResult(&result, R_NUMBER, &value); mutex_unlock(mutex); } static void kvv_time_str(RESULT * result, RESULT * arg1) { int index = (int) R2N(arg1); kvv_start(); if (kvv_fork() != 0) { SetResult(&result, R_STRING, ""); return; } mutex_lock(mutex); if (index < shm->entries) { char str[8]; sprintf(str, "%d", shm->entry[index].time); SetResult(&result, R_STRING, str); } else SetResult(&result, R_STRING, ""); mutex_unlock(mutex); } /* plugin initialization */ int plugin_init_kvv(void) { /* register all our cool functions */ AddFunction("kvv::line", 1, kvv_line); AddFunction("kvv::station", 1, kvv_station); AddFunction("kvv::time", 1, kvv_time); AddFunction("kvv::time_str", 1, kvv_time_str); return 0; } void plugin_exit_kvv(void) { /* kill client thread if it's running */ if (initialized) { /* kill client */ if (pid != -1) thread_destroy(pid); /* free shared mem and its mutex */ if (shm) { shm_destroy(shmid, shm); mutex_destroy(mutex); } } if (station_id) free(station_id); if (proxy_name) free(proxy_name); } ef='#n460'>460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679