#define _GNU_SOURCE /* We want strcasestr() */ #include "config.h" #include #if HAVE_SYS_TYPES_H # include #endif #if HAVE_SYS_SOCKET_H # include #endif #if HAVE_NETDB_H # include #endif #include #include #if HAVE_NETINET_IN_H # include #endif #if HAVE_STDLIB_H # include #endif #if HAVE_UNISTD_H # include #endif #include #include #include #if HAVE_STRINGS_H # include #endif #if HAVE_STRING_H # include #endif #include #include #include #include "export.h" #include "heap.e" #include "types.e" #include "errexit.e" #include "scan.e" #include "html.e" #include "dict.e" #include "connectsock.e" #include "openurl.e" #include "tree.e" #include "class.e" #include "unent.e" #include "headers.e" #define BUFLEN 4096 /* Maximum length of an HTTP header */ #if !HAVE_STRCASECMP #error To do: implement strcasecmp() #endif /* icalendar data types */ typedef enum {P_DATE, P_TEXT, P_LIST, P_URL} PropertyType; static Tree tree; bool has_errors = false; bool iso_8859_1; #ifdef DEBUG /* debug -- print debugging info */ static void debug(char *format,...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); } #else #define debug(...) #endif /* handle_error -- called when a parse error occurred */ void handle_error(void *clientdata, const string s, int lineno) { debug("+ Error on line %d: %s\n", lineno, s); has_errors = true; } /* start -- called before the first event is reported */ void* start(void) { debug("+ Start parsing\n"); tree = create(); return NULL; } /* end -- called after the last event is reported */ void end(void *clientdata) { debug("+ End parsing\n"); /* skip */ } /* handle_comment -- called after a comment is parsed */ void handle_comment(void *clientdata, string commenttext) { /* We don't need comments, otherwise the statement would be: */ /* tree = append_comment(tree, commenttext); */ } /* handle_text -- called after a text chunk is parsed */ void handle_text(void *clientdata, string text) { tree = append_text(tree, text); } /* handle_decl -- called after a declaration is parsed */ void handle_decl(void *clientdata, string gi, string fpi, string url) { /* We don't need doctypes, otherwise the statement would be: */ /* tree = append_declaration(tree, gi, fpi, url); */ } /* handle_pi -- called after a PI is parsed */ void handle_pi(void *clientdata, string pi_text) { /* We don't need processing instructions, otherwise the statement would be: */ /* tree = html_push(tree, name, attribs); */ } /* handle_starttag -- called after a start tag is parsed */ void handle_starttag(void *clientdata, string name, pairlist attribs) { tree = html_push(tree, name, attribs); } /* handle_endtag -- called after an endtag is parsed (name may be "") */ void handle_endtag(void *clientdata, string name) { tree = html_pop(tree, name); dispose(name); } /* handle_emptytag -- called after an empty tag is parsed */ void handle_emptytag(void *clientdata, string name, pairlist attribs) { tree = html_push(tree, name, attribs); } /* append_utf8 -- append the UTF-8 sequence for code n */ static void append_utf8(const int n, string t, int *j) { if (n <= 0x7F) { t[(*j)++] = n; } else if (n <= 0x7FF) { t[(*j)++] = (char)(0xC0 | (n >> 6)); t[(*j)++] = (char)(0x80 | (n & 0x3F)); } else if (n <= 0xFFFF) { t[(*j)++] = (char)(0xE0 | (n >> 12)); t[(*j)++] = (char)(0x80 | ((n >> 6) & 0x3F)); t[(*j)++] = (char)(0x80 | (n & 0x3F)); } else if (n <= 0x1FFFFF) { t[(*j)++] = (char)(0xF0 | (n >> 18)); t[(*j)++] = (char)(0x80 | ((n >> 12) & 0x3F)); t[(*j)++] = (char)(0x80 | ((n >> 6) & 0x3F)); t[(*j)++] = (char)(0x80 | (n & 0x3F)); } else if (n <= 0x3FFFFFF) { t[(*j)++] = (char)(0xF0 | (n >> 24)); t[(*j)++] = (char)(0x80 | ((n >> 18) & 0x3F)); t[(*j)++] = (char)(0x80 | ((n >> 12) & 0x3F)); t[(*j)++] = (char)(0x80 | ((n >> 6) & 0x3F)); t[(*j)++] = (char)(0x80 | (n & 0x3F)); } else { t[(*j)++] = (char)(0xF0 | (n >> 30)); t[(*j)++] = (char)(0x80 | ((n >> 24) & 0x3F)); t[(*j)++] = (char)(0x80 | ((n >> 18) & 0x3F)); t[(*j)++] = (char)(0x80 | ((n >> 12) & 0x3F)); t[(*j)++] = (char)(0x80 | ((n >> 6) & 0x3F)); t[(*j)++] = (char)(0x80 | (n & 0x3F)); } } /* get_contents -- collect all text content of an elt into a single string */ static string get_contents(Tree t) { Node *h; string contents = NULL, k; assert(t->tp == Element); for (h = t->children; h; h = h->sister) { if (h->tp == Text) { strapp(&contents, h->text, NULL); } else if (h->tp == Element && (k = get_contents(h))) { strapp(&contents, k, NULL); dispose(k); } } return contents; } /* toint -- interpret at most n digits from *s in decimal, advance *s */ static int toint(conststring *s, int n) { int h; for (h = 0; n && isdigit(**s); (*s)++, n--) h = 10 * h + **s - '0'; return h; } /* convert_date -- convert ISO date to icalendar date */ static string convert_date(const conststring isodate) { string icaldate; long offset = 0; conststring s; struct tm tm; time_t t; newarray(icaldate, 17); /* yyyymmddThhmmssZ */ for (s = isodate; isspace(*s); s++); tm.tm_year = toint(&s, 4) - 1900; if (*s == '-') s++; tm.tm_mon = toint(&s, 2) - 1; if (*s == '-') s++; tm.tm_mday = toint(&s, 2); if (*s == 'T') s++; tm.tm_hour = toint(&s, 2); if (*s == ':') s++; tm.tm_min = toint(&s, 2); if (*s == ':') s++; tm.tm_sec = toint(&s, 2); if (*s == '+' || *s == '-') { bool neg = *(s++) == '-'; offset = 60 * 60 * toint(&s, 2); if (*s == ':') s++; offset += 60 * toint(&s, 2); if (neg) offset = -offset; } t = timegm(&tm) - offset; if (strftime(icaldate, 17, "%Y%m%dT%H%M%SZ", gmtime(&t)) != 0); /* ??? */ return icaldate; } /* decode -- decode an HTML text: convert charset and expand entities */ static string decode(conststring s) { const struct _Entity *e; int i, j, n; string t; if (!(t = malloc((2 * strlen(s) + 1) * sizeof(*s)))) return NULL; for (i = 0, j = 0; s[i];) { if (s[i] != '&') { /* Literal character */ if (s[i] > 0 || !iso_8859_1) t[j++] = s[i++]; /* Don't convert */ else append_utf8(0xFF & s[i++], t, &j); } else if (s[i+1] != '#') { /* Named entity, eg. é */ for (i++, n = 0; isalnum(s[i+n]); n++) ; if (! (e = lookup_entity(s + i, n))) { /* Unknown entity */ t[j++] = '&'; } else { /* Expand to UTF-8 */ append_utf8(e->code, t, &j); i += n; if (s[i] == ';') i++; } } else if (s[i+2] != 'x') { /* Decimal entity, eg. F */ for (n = 0, i += 2; isdigit(s[i]); i++) n = 10 * n + s[i] - '0'; append_utf8(n, t, &j); if (s[i] == ';') i++; } else { /* Hex entity, eg. _ */ for (n = 0, i += 3; isxdigit(s[i]); i++) if (isdigit(s[i])) n = 16 * n + s[i] - '0'; else n = 16 * n + toupper(s[i]) - 'A' + 10; append_utf8(n, t, &j); if (s[i] == ';') i++; } } t[j] = '\0'; return t; /* SGML says also that a record-end (i.e., an end-of-line) may be * used instead of a semicolon to end an entity reference. But the * record-end is not suppressed in HTML and such an entity reference * is invalid in XML, so we don't implement that rule here. Instead, * the end-of-line is treated as any other character (other than * semicolon) and left in the document. */ } /* escape -- escape icalendar reserved characters */ static string escape(const conststring value) { string t; int i, j; newarray(t, 2 * strlen(value) + 1); /* Worst case is twice as long */ for (i = 0, j = 0; value[i]; i++) { switch (value[i]) { case ' ': case '\t': case '\r': case '\v': case '\f': if (i != 0 && !isspace(value[i-1])) t[j++] = ' '; break; case '\\': t[j++] = '\\'; t[j++] = '\\'; break; case ';': t[j++] = '\\'; t[j++] = ';'; break; case ',': t[j++] = '\\'; t[j++] = ','; break; case '\n': if (i != 0 && !isspace(value[i-1])) t[j++] = ' '; break; case '\177': break; default: if (0xE0 & value[i]) t[j++] = value[i]; /* Remove control chars */ } } t[j] = '\0'; return t; } /* find_prop -- check if element sets a value for property and if so store it */ static void find_prop(PropertyType type, const conststring p, const conststring class, const conststring value, Tree tree, Dictionary d) { conststring v; string s, t; if (!contains(class, p)) return; if (value) t = decode(value); else if ((s = get_contents(tree))) {t = decode(s); dispose(s);} else return; if (type == P_DATE) { s = t; t = convert_date(t); dispose(s); } else if (type != P_URL && (v = dict_find(d, p))) { s = escape(t); dispose(t); t = strapp(NULL, v, type == P_LIST ? "," : "", s, NULL); dispose(s); } else { s = t; t = escape(t); dispose(s); } dict_add(d, p, t); dispose(t); /* To do: handle error if dict_add returns 0 */ } /* parse_geo -- extract latitude/longitude properties from a subtree */ static void parse_geo(Tree t, Dictionary props, bool do_includes) { conststring class, title = NULL, data = NULL; Tree h; assert(t->tp == Element); if ((class = get_attrib(t, "class"))) { if (strcasecmp(t->name, "abbr") == 0) title = get_attrib(t, "title"); if (strcasecmp(t->name, "object") == 0) data = get_attrib(t, "data"); find_prop(P_TEXT, "LATITUDE", class, title, t, props); find_prop(P_TEXT, "LONGITUDE", class, title, t, props); if (do_includes && contains(class, "include") && hasprefix(data, "#")) { /* Should this be recursive instead? How about infinite loops? */ h = get_elt_by_id(t, data + 1); if (h) parse_geo(h, props, false); } } for (h = t->children; h != NULL; h = h->sister) if (h->tp == Element) parse_geo(h, props, do_includes); } /* parse_event -- extract event properties from a subtree */ static void parse_event(Tree t, Dictionary props, bool do_includes) { conststring class, rel, url = NULL, title = NULL, data = NULL; Tree h; assert(t->tp == Element); if ((class = get_attrib(t, "class"))) { if (strcasecmp(t->name, "abbr") == 0) title = get_attrib(t, "title"); if (strcasecmp(t->name, "a") == 0) url = get_attrib(t, "href"); if (strcasecmp(t->name, "object") == 0) data = get_attrib(t, "data"); /* To do: Can values be spread over disjoint elements? Use a Dictionary? */ find_prop(P_DATE, "DTSTART", class, title, t, props); find_prop(P_TEXT, "SUMMARY", class, title, t, props); find_prop(P_TEXT, "LOCATION", class, title, t, props); find_prop(P_DATE, "DTEND", class, title, t, props); find_prop(P_DATE, "RDATE", class, title, t, props); find_prop(P_DATE, "LAST-MODIFIED", class, title, t, props); find_prop(P_DATE, "CREATED", class, title, t, props); find_prop(P_DATE, "DTSTAMP", class, title, t, props); find_prop(P_TEXT, "RRULE", class, title, t, props); find_prop(P_LIST, "CATEGORIES", class, title, t, props); find_prop(P_TEXT, "DESCRIPTION", class, title, t, props); find_prop(P_TEXT, "UID", class, title, t, props); find_prop(P_TEXT, "ATTENDEE", class, title, t, props); find_prop(P_TEXT, "CONTACT", class, title, t, props); find_prop(P_TEXT, "ORGANIZER", class, title, t, props); find_prop(P_TEXT, "DURATION", class, title, t, props); find_prop(P_TEXT, "CLASS", class, title, t, props); find_prop(P_TEXT, "COMMENT", class, title, t, props); find_prop(P_TEXT, "PRIORITY", class, title, t, props); find_prop(P_TEXT, "RESOURCES", class, title, t, props); find_prop(P_TEXT, "STATUS", class, title, t, props); find_prop(P_TEXT, "TRANSP", class, title, t, props); find_prop(P_URL, "URL", class, url, t, props); find_prop(P_URL, "ATTACHMENT", class, url, t, props); if (contains(class, "GEO")) { find_prop(P_TEXT, "GEO", class, title, t, props); /* Init from title/content */ if (!title) { Dictionary geo = dict_create(2); conststring lat, lon; parse_geo(t, geo, do_includes); /* Look for lat/lon */ if ((lat = dict_find(geo, "LATITUDE")) && (lon = dict_find(geo, "LONGITUDE"))) { string s = strapp(NULL, lat, ";", lon, NULL); dict_add(props, "GEO", s); dispose(s); } dict_delete(geo); } } if (do_includes && contains(class, "include") && hasprefix(data, "#")) { /* Should this be recursive instead? How about infinite loops? */ h = get_elt_by_id(t, data + 1); if (h) parse_event(h, props, false); } } if ((rel = get_attrib(t, "rel"))) { if (strcasecmp(t->name, "a") == 0) url = get_attrib(t, "href"); find_prop(P_URL, "REL", rel, url, t, props); find_prop(P_URL, "ATTACHMENT", rel, url, t, props); } for (h = t->children; h != NULL; h = h->sister) if (h->tp == Element) parse_event(h, props, do_includes); } static void print_props(FILE *f, Dictionary props) { conststring k; fprintf(f, "BEGIN:VEVENT\r\n"); for (k = dict_next(props, NULL); k; k = dict_next(props, k)) fprintf(f, "%s:%s\r\n", k, dict_find(props, k)); fprintf(f, "END:VEVENT\r\n\r\n"); } /* walk_r -- walk recursively through the HTML tree looking for vevent */ static void walk_r(Tree t, FILE *f, const conststring last_mod_date, const conststring category) { Dictionary props; Tree h; assert(t->tp == Element); if (has_class(t->attribs, "vevent")) { props = dict_create(100); if (last_mod_date) dict_add(props, "DTSTAMP", last_mod_date); /* Default */ if (category) dict_add(props, "CATEGORIES", category); /* Category to add */ parse_event(t, props, true); debug("+ SUMMARY:%s\n", dict_find(props, "SUMMARY")); print_props(f, props); dict_delete(props); } else { for (h = t->children; h != NULL; h = h->sister) if (h->tp == Element) walk_r(h, f, last_mod_date, category); } } /* walk -- walk through the HTML tree looking for vevent */ static void walk(Tree t, FILE *f, const conststring last_mod_date, const conststring category) { Tree h; assert(t->tp == Root); for (h = t->children; h != NULL; h = h->sister) if (h->tp == Element) walk_r(h, f, last_mod_date, category); } /* http_err -- print HTTP error code and message, return code */ static int http_err(FILE *f, int status, const conststring msg, const conststring headers, const conststring body) { debug("+ %d %s\n", status, msg); assert(headers && (headers[0] == '\0' || hasaffix(headers, "\r\n"))); fprintf(f, "HTTP/1.1 %d %s\r\n%s\r\n%s", status, msg, headers, body); fclose(f); return status; } /* add_via -- add a Via header */ static void add_via(int sock, Dictionary headers, const char *proto_version) { #if HAVE_SYS_SOCKET_H && HAVE_NETDB_H char host[BUFLEN], port[10], *s; struct sockaddr sa; unsigned int salen = sizeof(sa); const char *v; if (getsockname(sock, &sa, &salen) != 0 || getnameinfo(&sa, salen, host, sizeof(host), port, sizeof(port), NI_NUMERICSERV) != 0) { strcpy(host, "unknown-host"); strcpy(port, "??"); } v = dict_find(headers, "via"); s = v ? strapp(NULL, v, ", ", NULL) : NULL; strapp(&s, proto_version, " ", host, ":", port, " (", PACKAGE_NAME, "/", PACKAGE_VERSION, ")", NULL); (void) dict_add(headers, "via", s); #endif } /* trace -- handle an HTTP TRACE request, return 0 on success */ static int trace(FILE *in, FILE *out, char buf[BUFLEN]) { char val[20], url[BUFLEN]; Dictionary response, request; const char *v, *key, *proto_version; int ttl, i, j, c, status; FILE *remote; /* Extract the URL */ /* Todo: Handle requests that are longer than BUFLEN */ for (i = 6, j = 0; buf[i] && buf[i] != ' '; i++, j++) url[j] = buf[i]; url[j] = '\0'; /* Check that we understand this HTTP version */ if (hasprefix(buf + i + 1, "HTTP/1.0\r\n")) proto_version = "1.0"; else if (hasprefix(buf + i + 1, "HTTP/1.1\r\n")) proto_version = "1.1"; else return http_err(out, 505, "HTTP version not supported", "Content-Type: text/plain\r\n", "Only HTTP versions HTTP 1.0 and 1.1 are supported.\r\n"); /* Read the request headers */ request = dict_create(50); if (!request) return http_err(out, 500, "Internal error: out of memory", "", ""); if (!read_mail_headers(in, request)) return http_err(out, 400, "Error in request headers", "", ""); /* Close the socket for reading */ shutdown(fileno(in), 0); /* Check Max-Forwards to see if we should forward or reply immediately */ if (!(v = dict_find(request, "max-forwards"))) { ttl = -1; /* Assume unlimited forwards */ } else if ((ttl = atoi(v)) > 0) { sprintf(val, "%d", ttl - 1); if (!dict_add(request, "max-forwards", val)) return http_err(out, 500, "Internal error: out of memory", "", ""); } debug("+ Max-Forwards: %d\n", ttl); if (!(response = dict_create(50))) return http_err(out, 500, "Internal error: out of memory", "", ""); if (ttl == 0) { /* We're the recipient, send echo back */ /* Print response headers */ fprintf(out, "HTTP/1.1 200 OK\r\n"); fprintf(out, "Content-Type: message/http\r\n"); add_via(fileno(in), response, proto_version); if ((v = dict_find(response, "via"))) fprintf(out, "Via: %s\r\n", v); fprintf(out, "\r\n"); /* Echo back the (parsed) request as entity body */ fprintf(out, "%s", buf); for (key = dict_next(request, NULL); key; key = dict_next(request, key)) fprintf(out, "%s: %s\r\n", key, dict_find(request, key)); } else { /* Forward the request */ /* Add a Via header */ add_via(fileno(in), request, proto_version); /* Connect to remote server */ remote = fopenurl3("TRACE", url, "r", request, response, 10, &status); if (!remote) { if (errno == EACCES) return http_err(out, 400, "Error in URL", "", ""); else if (errno == EFAULT) return http_err(out, 404, "Not found", "", ""); else return http_err(out, 502, "Proxy error", "", ""); } /* Print response headers */ fprintf(out, "HTTP/1.1 200 OK\r\n"); fprintf(out, "Content-Type: message/http\r\n"); add_via(fileno(in), response, proto_version); if ((v = dict_find(response, "via"))) fprintf(out, "Via: %s\r\n", v); fprintf(out, "\r\n"); /* Copy the body as-is */ while ((c = fgetc(remote)) != EOF) fputc(c, out); } if (fileno(in) != fileno(out)) fclose(in); fclose(out); debug("+ Done!\n"); return 0; } /* proxy -- proxy a request, convert resulting hcalendar to icalendar */ EXPORT int proxy(const int fd_in, const int fd_out, const conststring category) { char buf[BUFLEN], url[BUFLEN]; FILE *remote, *client_in, *client_out; string s, last_mod_date = NULL; const char *v, *proto_version; int i, j, c, status; Dictionary response, requestheaders; struct tm tm; bool is_head; /* GET or HEAD */ if (fd_in == fd_out) { /* A socket for reading *and* writing */ if (!(client_in = fdopen(fd_in, "r+"))) return -1; client_out = client_in; } else { /* Separate streams for reading & writing */ if (!(client_in = fdopen(fd_in, "r"))) return -1; if (!(client_out = fdopen(fd_out, "w"))) return -1; } /* Read the HTTP request */ if (! fgets(buf, sizeof(buf), client_in)) return ferror(client_in); debug("* %s", buf); if (hasprefix(buf, "TRACE ")) return trace(client_in, client_out, buf); if (hasprefix(buf, "GET ")) is_head = false; else if (hasprefix(buf, "HEAD ")) is_head = true; else return http_err(client_out, 501, "Method not implemented", "", ""); /* If the URL starts with http://, skip until after our own server name */ /* Todo: Handle requests that are longer than BUFLEN */ i = is_head ? 5 : 4; if (!hasprefix(buf + i, "http://")) i++; else if ((s = strchr(buf + i + 7, '/'))) i = s - buf + 1; else return http_err(client_out, 400, "Error in URL", "", ""); /* Extract the URL */ for (j = 0; buf[i] && buf[i] != ' '; i++, j++) url[j] = buf[i]; url[j] = '\0'; /* Check that we understand this HTTP version */ if (hasprefix(buf + i + 1, "HTTP/1.0\r\n")) proto_version = "1.0"; else if (hasprefix(buf + i + 1, "HTTP/1.1\r\n")) proto_version = "1.1"; else return http_err(client_out, 505, "HTTP version not supported", "Content-Type: text/plain\r\n", "Only HTTP versions HTTP 1.0 and 1.1 are supported.\r\n"); /* Read the request headers */ if (!(requestheaders = dict_create(50))) return http_err(client_out, 500, "Internal error: out of memory", "", ""); if (!read_mail_headers(client_in, requestheaders)) return http_err(client_out, 400, "Error in request headers", "", ""); /* Close the socket for reading */ shutdown(fd_in, 0); /* We don't handle Expect headers at all */ if (dict_find(requestheaders, "expect")) return http_err(client_out, 417, "Expectation failed", "Content-Type: text/plain\r\n", "This hcalproxy cannot handle HTTP Expect headers.\r\n"); /* Remove hop-by-hop headers and headers we don't use */ dict_destroy(requestheaders, "connection"); dict_destroy(requestheaders, "keep-alive"); dict_destroy(requestheaders, "proxy-authenticate"); dict_destroy(requestheaders, "proxy-authorization"); dict_destroy(requestheaders, "te"); dict_destroy(requestheaders, "trailers"); dict_destroy(requestheaders, "transfer-encoding"); dict_destroy(requestheaders, "upgrade"); dict_destroy(requestheaders, "host"); /* fopenurl2() will set a new one */ dict_destroy(requestheaders, "accept-charset"); /* Todo */ dict_destroy(requestheaders, "accept-encoding"); /* Todo */ /* Add or modify the Accept header */ if (!dict_add(requestheaders, "accept", "text/html,application/xhtml+xml")) return http_err(client_out, 500, "Internal error: out of memory", "", ""); /* Add or modify the Via header */ add_via(fd_in, requestheaders, proto_version); /* libcurl can handle decompression for us, otherwise request uncompressed */ #if !HAVE_LIBCURL if (!dict_add(requestheaders, "accept-encoding", "")) return http_err(client_out, 500, "Internal error: out of memory", "", ""); #endif /* Issue request to remote server */ response = dict_create(50); if (!response) return http_err(client_out, 500, "Internal error: out of memory", "", ""); remote = fopenurl2(url, "r", requestheaders, response, 10, &status); if (!remote) { if (errno == EACCES) return http_err(client_out, 400, "Error in URL", "", ""); else if (errno == EFAULT) return http_err(client_out, 404, "Not found", "", ""); else if (errno == EPROTONOSUPPORT) return http_err(client_out, 405, "Method not allowed", "", ""); else return http_err(client_out, 502, "Proxy error", "", ""); } if (status != 0 && status != 200) { /* Not OK, just copy what we got */ fprintf(client_out, "HTTP/1.1 %d Error\r\n", status); for (v = dict_next(response, NULL); v; v = dict_next(response, v)) fprintf(client_out, "%s: %s\r\n", v, dict_find(response, v)); fprintf(client_out, "\r\n"); /* Copy the body as-is */ while ((c = fgetc(remote)) != EOF) fputc(c, client_out); } else { /* 200 OK */ /* Find content type, check if it is (X)HTML */ v = dict_find(response, "content-type"); /* Assume HTML unless it's explicitly something else... */ if (v && (!hasprefix(v, "text/html") && !hasprefix(v, "application/xhtml+xml"))) return http_err(client_out, 502, "Content type was not (X)HTML", "", ""); debug("+ is (X)HTML\n", s); iso_8859_1 = !v || !(s = strcasestr(v, "charset")) || strcasestr(s, "iso-8859-1"); debug("+ charset assumed %s\n", iso_8859_1 ? "iso-8859-1" : "utf-8"); /* To do: handle more character encodings, use iconv(3) */ /* Set up the tokenizer and parser */ set_error_handler(handle_error); set_start_handler(start); set_end_handler(end); set_comment_handler(handle_comment); set_text_handler(handle_text); set_decl_handler(handle_decl); set_pi_handler(handle_pi); set_starttag_handler(handle_starttag); set_emptytag_handler(handle_emptytag); set_endtag_handler(handle_endtag); yyin = remote; if (yyparse() != 0 || has_errors) return http_err(client_out, 502, "Errors in remote HTML", "", ""); /* Print response headers */ fprintf(client_out, "HTTP/1.1 200 OK\r\n"); fprintf(client_out, "Content-Type: text/calendar;charset=utf-8\r\n"); add_via(fd_in, response, "1.1"); if ((v = dict_find(response, "via"))) fprintf(client_out, "Via: %s\r\n", v); /* Check if there is a Last-Modified header */ if ((v = dict_find(response, "last-modified"))) { /* Copy Last-Modified header */ fprintf(client_out, "Last-Modified: %s\r\n", v); /* Also use it as default DTSTAMP */ newarray(last_mod_date, 17); /* yyyymmddThhmmssZ */ (void) strptime(v, "%a,%n%d%n%b%n%Y%n%T%n%Z", &tm); (void) strftime(last_mod_date, 17, "%Y%m%dT%H%M%SZ", &tm); } fprintf(client_out, "\r\n"); if (!is_head) { /* Look for events */ debug("+ Start walking...\n"); fprintf(client_out, "BEGIN:VCALENDAR\r\n"); fprintf(client_out, "VERSION:2.0\r\n"); fprintf(client_out, "PRODID:-//W3C//NONSGML %s//EN\r\n", PACKAGE_STRING); fprintf(client_out, "METHOD:PUBLISH\r\n"); fprintf(client_out, "X-ORIGINAL-URL:%s\r\n", url); /*Todo: escape \\ \, */ fprintf(client_out, "\r\n"); walk(get_root(tree), client_out, last_mod_date, category); fprintf(client_out, "END:VCALENDAR\r\n"); } } fclose(client_in); if (fd_in != fd_out) fclose(client_out); debug("+ Done!\n"); return 0; }