snownews/io-internal.c

458 lines
14 KiB
C

// This file is part of Snownews - A lightweight console RSS newsreader
//
// Copyright (c) 2003-2004 Oliver Feiler <kiza@kcore.de>
//
// Snownews is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3
// as published by the Free Software Foundation.
//
// Snownews 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 Snownews. If not, see http://www.gnu.org/licenses/.
#include "io-internal.h"
#include "conversions.h"
#include "filters.h"
#include "io-internal.h"
#include "netio.h"
#include "ui-support.h"
#include "xmlparse.h"
#include <errno.h>
#include <ncurses.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libxml/parser.h>
#include <syslog.h>
struct feed* newFeedStruct (void)
{
struct feed* new = calloc (1, sizeof (struct feed));
new->netio_error = NET_ERR_OK;
return new;
}
static void GetHTTPErrorString (char* errorstring, size_t size, unsigned httpstatus)
{
const char* statusmsg = "HTTP %u!";
switch (httpstatus) {
case 400:
statusmsg = "Bad request";
break;
case 402:
statusmsg = "Payment required";
break;
case 403:
statusmsg = "Access denied";
break;
case 500:
statusmsg = "Internal server error";
break;
case 501:
statusmsg = "Not implemented";
break;
case 502:
case 503:
statusmsg = "Service unavailable";
break;
}
snprintf (errorstring, size, statusmsg, httpstatus);
}
static void PrintUpdateError (const struct feed* cur_ptr)
{
enum netio_error err = cur_ptr->netio_error;
char errstr[256];
switch (err) {
case NET_ERR_URL_INVALID:
snprintf (errstr, sizeof (errstr), _("%s: Invalid URL!"), cur_ptr->title);
break;
case NET_ERR_SOCK_ERR:
snprintf (errstr, sizeof (errstr), _("%s: Couldn't create network socket!"), cur_ptr->title);
break;
case NET_ERR_HOST_NOT_FOUND:
snprintf (errstr, sizeof (errstr), _("%s: Can't resolve host!"), cur_ptr->title);
break;
case NET_ERR_CONN_REFUSED:
snprintf (errstr, sizeof (errstr), _("%s: Connection refused!"), cur_ptr->title);
break;
case NET_ERR_CONN_FAILED:
snprintf (errstr, sizeof (errstr), _("%s: Couldn't connect to server: %s"), cur_ptr->title, (strerror (cur_ptr->connectresult) ? strerror (cur_ptr->connectresult) : "(null)"));
break;
case NET_ERR_TIMEOUT:
snprintf (errstr, sizeof (errstr), _("%s: Connection timed out."), cur_ptr->title);
break;
case NET_ERR_UNKNOWN:
break;
case NET_ERR_REDIRECT_COUNT_ERR:
snprintf (errstr, sizeof (errstr), _("%s: Too many HTTP redirects encountered! Giving up."), cur_ptr->title);
break;
case NET_ERR_REDIRECT_ERR:
snprintf (errstr, sizeof (errstr), _("%s: Server sent an invalid redirect!"), cur_ptr->title);
break;
case NET_ERR_HTTP_410:
case NET_ERR_HTTP_404:
snprintf (errstr, sizeof (errstr), _("%s: This feed no longer exists. Please unsubscribe!"), cur_ptr->title);
break;
case NET_ERR_HTTP_NON_200:{
char httperrstr[64];
GetHTTPErrorString (httperrstr, sizeof (httperrstr), cur_ptr->lasthttpstatus);
snprintf (errstr, sizeof (errstr), _("%s: Could not download feed: %s"), cur_ptr->title, httperrstr);
}
break;
case NET_ERR_HTTP_PROTO_ERR:
snprintf (errstr, sizeof (errstr), _("%s: Error in server reply."), cur_ptr->title);
break;
case NET_ERR_AUTH_FAILED:
snprintf (errstr, sizeof (errstr), _("%s: Authentication failed!"), cur_ptr->title);
break;
case NET_ERR_AUTH_NO_AUTHINFO:
snprintf (errstr, sizeof (errstr), _("%s: URL does not contain authentication information!"), cur_ptr->title);
break;
case NET_ERR_AUTH_GEN_AUTH_ERR:
snprintf (errstr, sizeof (errstr), _("%s: Could not generate authentication information!"), cur_ptr->title);
break;
case NET_ERR_AUTH_UNSUPPORTED:
snprintf (errstr, sizeof (errstr), _("%s: Unsupported authentication method requested by server!"), cur_ptr->title);
break;
case NET_ERR_GZIP_ERR:
snprintf (errstr, sizeof (errstr), _("%s: Error decompressing server reply!"), cur_ptr->title);
break;
case NET_ERR_CHUNKED:
snprintf (errstr, sizeof (errstr), _("%s: Error in server reply. Chunked encoding is not supported."), cur_ptr->title);
break;
default:
snprintf (errstr, sizeof (errstr), _("%s: Some error occured for which no specific error message was written."), cur_ptr->title);
break;
}
UIStatus (errstr, 2, 1);
syslog (LOG_ERR, "%s", errstr);
}
// Update given feed from server.
// Reload XML document and replace in memory cur_ptr->xmltext with it.
int UpdateFeed (struct feed* cur_ptr)
{
if (cur_ptr == NULL)
return 1;
// Smart feeds are generated in the fly.
if (cur_ptr->smartfeed == 1)
return 0;
// If current feed is an exec URL, run that command, otherwise fetch resource from network.
if (cur_ptr->execurl)
FilterExecURL (cur_ptr);
else {
// Need to work on a copy of ->feedurl, because DownloadFeed() changes the pointer.
char* feedurl = strdup (cur_ptr->feedurl);
free (cur_ptr->xmltext);
cur_ptr->xmltext = DownloadFeed (feedurl, cur_ptr, 0);
free (feedurl);
// Set title and link structure to something.
// To the feedurl in this case so the program show something
// as placeholder instead of crash.
if (cur_ptr->title == NULL)
cur_ptr->title = strdup (cur_ptr->feedurl);
if (cur_ptr->link == NULL)
cur_ptr->link = strdup (cur_ptr->feedurl);
// If the download function returns a NULL pointer return from here.
if (!cur_ptr->xmltext) {
if (cur_ptr->problem)
PrintUpdateError (cur_ptr);
return 1;
}
}
// Feed downloaded content through the defined filter.
if (cur_ptr->perfeedfilter != NULL)
FilterPipeNG (cur_ptr);
// If there is no feed, return.
if (!cur_ptr->xmltext)
return 1;
if (DeXML (cur_ptr)) {
UIStatus (_("Invalid XML! Cannot parse this feed!"), 2, 1);
// Activate feed problem flag.
cur_ptr->problem = true;
return 1;
}
// We don't need these anymore. Free the raw XML to save some memory.
free (cur_ptr->xmltext);
cur_ptr->xmltext = NULL;
// Mark the time to detect modifications
cur_ptr->mtime = time (NULL);
return 0;
}
int UpdateAllFeeds (void)
{
for (struct feed * f = _feed_list; f; f = f->next)
if (0 != UpdateFeed (f))
continue;
return 0;
}
// Load feed from disk. And call UpdateFeed if neccessary.
int LoadFeed (struct feed* cur_ptr)
{
// Smart feeds are generated in the fly.
if (cur_ptr->smartfeed == 1)
return 0;
char* hashme = Hashify (cur_ptr->feedurl);
char cachefilename[PATH_MAX];
snprintf (cachefilename, sizeof (cachefilename), SNOWNEWS_CACHE_DIR "%s", getenv ("HOME"), hashme);
free (hashme);
FILE* cache = fopen (cachefilename, "r");
if (cache == NULL) {
char msgbuf[128];
snprintf (msgbuf, sizeof (msgbuf), _("Cache for %s is toast. Reloading from server..."), cur_ptr->feedurl);
UIStatus (msgbuf, 0, 0);
if (UpdateFeed (cur_ptr) != 0)
return 1;
return 0;
}
struct stat cachest;
fstat (fileno (cache), &cachest);
// Read complete cachefile.
int len = 0; // Internal usage for realloc.
char filebuf[BUFSIZ]; // File I/O block buffer.
while (!feof (cache)) {
// Use binary read, UTF-8 data!
size_t retval = fread (filebuf, 1, sizeof (filebuf), cache);
if (retval == 0)
break;
cur_ptr->xmltext = realloc (cur_ptr->xmltext, len + retval + 1);
memcpy (cur_ptr->xmltext + len, filebuf, retval);
len += retval;
if (retval != sizeof (filebuf))
break;
}
fclose (cache);
cur_ptr->content_length = len;
cur_ptr->xmltext[len] = '\0';
if (!cur_ptr->xmltext)
return 1;
// After loading DeXMLize the mess.
// If loading the feed from the disk fails, try downloading from the net.
if (DeXML (cur_ptr) != 0 && UpdateFeed (cur_ptr) != 0) {
// And if that fails as well, just continue without this feed.
char msgbuf[64];
snprintf (msgbuf, sizeof (msgbuf), _("Could not load %s!"), cur_ptr->feedurl);
UIStatus (msgbuf, 2, 1);
}
free (cur_ptr->xmltext);
cur_ptr->xmltext = NULL;
cur_ptr->mtime = cachest.st_mtime;
return 0;
}
int LoadAllFeeds (unsigned numfeeds)
{
if (!numfeeds)
return 0;
UIStatus (_("Loading cache ["), 0, 0);
unsigned titlestrlen = strlen (_("Loading cache ["));
int oldnumobjects = 0;
unsigned count = 1;
for (struct feed * f = _feed_list; f; f = f->next) {
// Progress bar
int numobjects = count * (COLS - titlestrlen - 2) / numfeeds - 2;
if (numobjects < 1)
numobjects = 1;
if (numobjects > oldnumobjects) {
DrawProgressBar (numobjects, titlestrlen);
oldnumobjects = numobjects;
}
if (LoadFeed (f) != 0)
continue;
++count;
}
return 0;
}
static void WriteFeedUrls (void)
{
if (!_feed_list_changed)
return;
// Make a backup of urls.
char urlsfilename[PATH_MAX];
snprintf (urlsfilename, sizeof (urlsfilename), SNOWNEWS_CONFIG_DIR "urls", getenv ("HOME"));
// Write urls
FILE* urlfile = fopen (urlsfilename, "w");
if (!urlfile) {
syslog (LOG_ERR, "error saving urls: %s", strerror (errno));
return;
}
for (const struct feed * f = _feed_list; f; f = f->next) {
fputs (f->feedurl, urlfile);
fputc ('|', urlfile);
if (f->custom_title)
fputs (f->title, urlfile);
fputc ('|', urlfile);
for (const struct feedcategories * c = f->feedcategories; c; c = c->next) {
fputs (c->name, urlfile);
if (c->next) // Only add a colon of we run the loop again!
fputc (',', urlfile);
}
fputc ('|', urlfile);
if (f->perfeedfilter != NULL)
fputs (f->perfeedfilter, urlfile);
fputc ('\n', urlfile); // Add newline character.
}
fclose (urlfile);
_feed_list_changed = false;
}
static void WriteFeedCache (const struct feed* feed)
{
char* hashme = Hashify (feed->feedurl);
char cachefilename[PATH_MAX];
snprintf (cachefilename, sizeof (cachefilename), SNOWNEWS_CACHE_DIR "%s", getenv ("HOME"), hashme);
free (hashme);
// Check if the feed has been modified since last loaded from this file
struct stat cachest;
if (0 == stat (cachefilename, &cachest) && cachest.st_mtime >= feed->mtime)
return;
FILE* cache = fopen (cachefilename, "w");
if (!cache) {
syslog (LOG_ERR, "error writing cache file '%s': %s", cachefilename, strerror (errno));
return;
}
fputs ("<?xml version=\"1.0\" ?>\n\n<rdf:RDF\n xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n xmlns=\"http://purl.org/rss/1.0/\"\n xmlns:snow=\"http://snownews.kcore.de/ns/1.0/\">\n\n", cache);
if (feed->lastmodified != NULL) {
fputs ("<snow:lastmodified>", cache);
fputs (feed->lastmodified, cache);
fputs ("</snow:lastmodified>\n", cache);
}
fputs ("<channel rdf:about=\"", cache);
char* encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) feed->feedurl);
fputs (encoded, cache);
free (encoded);
fputs ("\">\n<title>", cache);
if (feed->original != NULL) {
encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) feed->original);
fputs (encoded, cache);
free (encoded);
} else if (feed->title != NULL) {
encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) feed->title);
fputs (encoded, cache);
free (encoded);
}
fputs ("</title>\n<link>", cache);
if (feed->link != NULL) {
encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) feed->link);
fputs (encoded, cache);
free (encoded);
}
fputs ("</link>\n<description>", cache);
if (feed->description != NULL) {
encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) feed->description);
fputs (encoded, cache);
free (encoded);
}
fputs ("</description>\n</channel>\n\n", cache);
for (const struct newsitem * item = feed->items; item; item = item->next) {
fputs ("<item rdf:about=\"", cache);
if (item->data->link != NULL) {
encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) item->data->link);
fputs (encoded, cache);
free (encoded);
}
fputs ("\">\n<title>", cache);
if (item->data->title != NULL) {
encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) item->data->title);
fputs (encoded, cache);
free (encoded);
}
fputs ("</title>\n<link>", cache);
if (item->data->link != NULL) {
encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) item->data->link);
fputs (encoded, cache);
free (encoded);
}
fputs ("</link>\n<description>", cache);
if (item->data->description != NULL) {
encoded = (char *) xmlEncodeEntitiesReentrant (NULL, (xmlChar *) item->data->description);
fputs (encoded, cache);
free (encoded);
}
fputs ("</description>\n<snow:readstatus>", cache);
putc ('0' + item->data->readstatus, cache);
fputs ("</snow:readstatus>\n<snow:hash>", cache);
if (item->data->hash)
fputs (item->data->hash, cache);
fputs ("</snow:hash>\n<snow:date>", cache);
fprintf (cache, "%u", item->data->date);
fputs ("</snow:date>\n</item>\n\n", cache);
}
fputs ("</rdf:RDF>", cache);
fclose (cache);
}
// Write in memory structures to disk cache.
// Usually called before program exit.
void WriteCache (void)
{
UIStatus (_("Saving settings ["), 0, 0);
WriteFeedUrls();
// Save feed cache
unsigned numfeeds = 0;
for (const struct feed * f = _feed_list; f; f = f->next)
++numfeeds;
unsigned count = 1;
int oldnumobjects = 0;
unsigned titlestrlen = strlen (_("Saving settings ["));
for (const struct feed * cur_ptr = _feed_list; cur_ptr; cur_ptr = cur_ptr->next) {
// Progress bar
int numobjects = count * (COLS - titlestrlen - 2) / numfeeds - 2;
if (numobjects < 1)
numobjects = 1;
if (numobjects > oldnumobjects) {
DrawProgressBar (numobjects, titlestrlen);
oldnumobjects = numobjects;
}
++count;
// Discard smart feeds from cache.
if (cur_ptr->smartfeed)
continue;
// Write cache.
WriteFeedCache (cur_ptr);
}
return;
}