#include <cups/cups.h>
#include <cups/language.h>
#include <cups/string-private.h>
#include <cups/array.h>
#include <sys/select.h>
#include <cups/ipp-private.h>
typedef struct _cups_rss_s
{
int sequence_number;
char *subject,
*text,
*link_url;
time_t event_time;
} _cups_rss_t;
static char *rss_password;
static int compare_rss(_cups_rss_t *a, _cups_rss_t *b);
static void delete_message(_cups_rss_t *rss);
static void load_rss(cups_array_t *rss, const char *filename);
static _cups_rss_t *new_message(int sequence_number, char *subject,
char *text, char *link_url,
time_t event_time);
static const char *password_cb(const char *prompt);
static int save_rss(cups_array_t *rss, const char *filename,
const char *baseurl);
static char *xml_escape(const char *s);
int
main(int argc,
char *argv[])
{
int i;
ipp_t *event;
ipp_state_t state;
char scheme[32],
username[256],
host[1024],
resource[1024],
*options;
int port,
max_events;
http_t *http;
http_status_t status;
char filename[1024],
newname[1024];
cups_lang_t *language;
ipp_attribute_t *printer_up_time,
*notify_sequence_number,
*notify_printer_uri;
char *subject,
*text,
link_url[1024],
link_scheme[32],
link_username[256],
link_host[1024],
link_resource[1024];
int link_port;
cups_array_t *rss;
_cups_rss_t *msg;
char baseurl[1024];
fd_set input;
struct timeval timeout;
int changed;
int exit_status;
fprintf(stderr, "DEBUG: argc=%d\n", argc);
for (i = 0; i < argc; i ++)
fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
if (httpSeparateURI(HTTP_URI_CODING_ALL, argv[1], scheme, sizeof(scheme),
username, sizeof(username), host, sizeof(host), &port,
resource, sizeof(resource)) < HTTP_URI_OK)
{
fprintf(stderr, "ERROR: Bad RSS URI \"%s\"!\n", argv[1]);
return (1);
}
max_events = 20;
if ((options = strchr(resource, '?')) != NULL)
{
*options++ = '\0';
if (!strncmp(options, "max_events=", 11))
{
max_events = atoi(options + 11);
if (max_events <= 0)
max_events = 20;
}
}
rss = cupsArrayNew((cups_array_func_t)compare_rss, NULL);
if (host[0])
{
int fd;
if ((rss_password = strchr(username, ':')) != NULL)
*rss_password++ = '\0';
cupsSetPasswordCB(password_cb);
cupsSetUser(username);
if ((fd = cupsTempFd(filename, sizeof(filename))) < 0)
{
fprintf(stderr, "ERROR: Unable to create temporary file: %s\n",
strerror(errno));
return (1);
}
if ((http = httpConnect(host, port)) == NULL)
{
fprintf(stderr, "ERROR: Unable to connect to %s on port %d: %s\n",
host, port, strerror(errno));
close(fd);
unlink(filename);
return (1);
}
status = cupsGetFd(http, resource, fd);
close(fd);
if (status != HTTP_OK && status != HTTP_NOT_FOUND)
{
fprintf(stderr, "ERROR: Unable to GET %s from %s on port %d: %d %s\n",
resource, host, port, status, httpStatus(status));
httpClose(http);
unlink(filename);
return (1);
}
strlcpy(newname, filename, sizeof(newname));
httpAssembleURI(HTTP_URI_CODING_ALL, baseurl, sizeof(baseurl), "http",
NULL, host, port, resource);
}
else
{
const char *cachedir,
*server_name,
*server_port;
http = NULL;
if ((cachedir = getenv("CUPS_CACHEDIR")) == NULL)
cachedir = CUPS_CACHEDIR;
if ((server_name = getenv("SERVER_NAME")) == NULL)
server_name = "localhost";
if ((server_port = getenv("SERVER_PORT")) == NULL)
server_port = "631";
snprintf(filename, sizeof(filename), "%s/rss%s", cachedir, resource);
snprintf(newname, sizeof(newname), "%s.N", filename);
httpAssembleURIf(HTTP_URI_CODING_ALL, baseurl, sizeof(baseurl), "http",
NULL, server_name, atoi(server_port), "/rss%s", resource);
}
load_rss(rss, filename);
changed = cupsArrayCount(rss) == 0;
language = cupsLangDefault();
for (exit_status = 0, event = NULL;;)
{
if (changed)
{
if (save_rss(rss, newname, baseurl))
{
if (http)
{
if ((status = cupsPutFile(http, resource, filename)) != HTTP_CREATED)
fprintf(stderr, "ERROR: Unable to PUT %s from %s on port %d: %d %s\n",
resource, host, port, status, httpStatus(status));
}
else
{
if (rename(newname, filename))
fprintf(stderr, "ERROR: Unable to rename %s to %s: %s\n",
newname, filename, strerror(errno));
}
changed = 0;
}
}
timeout.tv_sec = 30;
timeout.tv_usec = 0;
FD_ZERO(&input);
FD_SET(0, &input);
if (select(1, &input, NULL, NULL, &timeout) < 0)
continue;
else if (!FD_ISSET(0, &input))
{
fprintf(stderr, "DEBUG: %s is bored, exiting...\n", argv[1]);
break;
}
event = ippNew();
while ((state = ippReadFile(0, event)) != IPP_DATA)
{
if (state <= IPP_IDLE)
break;
}
if (state == IPP_ERROR)
fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr);
if (state <= IPP_IDLE)
break;
printer_up_time = ippFindAttribute(event, "printer-up-time",
IPP_TAG_INTEGER);
notify_sequence_number = ippFindAttribute(event, "notify-sequence-number",
IPP_TAG_INTEGER);
notify_printer_uri = ippFindAttribute(event, "notify-printer-uri",
IPP_TAG_URI);
subject = cupsNotifySubject(language, event);
text = cupsNotifyText(language, event);
if (printer_up_time && notify_sequence_number && subject && text)
{
if (notify_printer_uri)
{
httpSeparateURI(HTTP_URI_CODING_ALL,
notify_printer_uri->values[0].string.text,
link_scheme, sizeof(link_scheme),
link_username, sizeof(link_username),
link_host, sizeof(link_host), &link_port,
link_resource, sizeof(link_resource));
httpAssembleURI(HTTP_URI_CODING_ALL, link_url, sizeof(link_url),
"http", link_username, link_host, link_port,
link_resource);
}
msg = new_message(notify_sequence_number->values[0].integer,
xml_escape(subject), xml_escape(text),
notify_printer_uri ? xml_escape(link_url) : NULL,
printer_up_time->values[0].integer);
if (!msg)
{
fprintf(stderr, "ERROR: Unable to create message: %s\n",
strerror(errno));
exit_status = 1;
break;
}
cupsArrayAdd(rss, msg);
changed = 1;
while (cupsArrayCount(rss) > max_events)
{
msg = cupsArrayFirst(rss);
cupsArrayRemove(rss, msg);
delete_message(msg);
}
}
if (subject)
free(subject);
if (text)
free(text);
ippDelete(event);
event = NULL;
}
ippDelete(event);
if (http)
{
unlink(filename);
httpClose(http);
}
return (exit_status);
}
static int
compare_rss(_cups_rss_t *a,
_cups_rss_t *b)
{
return (a->sequence_number - b->sequence_number);
}
static void
delete_message(_cups_rss_t *msg)
{
if (msg->subject)
free(msg->subject);
if (msg->text)
free(msg->text);
if (msg->link_url)
free(msg->link_url);
free(msg);
}
static void
load_rss(cups_array_t *rss,
const char *filename)
{
FILE *fp;
char line[4096],
*subject,
*text,
*link_url,
*start,
*end;
time_t event_time;
int sequence_number;
int in_item;
_cups_rss_t *msg;
if ((fp = fopen(filename, "r")) == NULL)
{
if (errno != ENOENT)
fprintf(stderr, "ERROR: Unable to open %s: %s\n", filename,
strerror(errno));
return;
}
subject = NULL;
text = NULL;
link_url = NULL;
event_time = 0;
sequence_number = 0;
in_item = 0;
while (fgets(line, sizeof(line), fp))
{
if (strstr(line, "<item>"))
in_item = 1;
else if (strstr(line, "</item>") && in_item)
{
if (subject && text)
{
msg = new_message(sequence_number, subject, text, link_url,
event_time);
if (msg)
cupsArrayAdd(rss, msg);
}
else
{
if (subject)
free(subject);
if (text)
free(text);
if (link_url)
free(link_url);
}
subject = NULL;
text = NULL;
link_url = NULL;
event_time = 0;
sequence_number = 0;
in_item = 0;
}
else if (!in_item)
continue;
else if ((start = strstr(line, "<title>")) != NULL)
{
start += 7;
if ((end = strstr(start, "</title>")) != NULL)
{
*end = '\0';
subject = strdup(start);
}
}
else if ((start = strstr(line, "<description>")) != NULL)
{
start += 13;
if ((end = strstr(start, "</description>")) != NULL)
{
*end = '\0';
text = strdup(start);
}
}
else if ((start = strstr(line, "<link>")) != NULL)
{
start += 6;
if ((end = strstr(start, "</link>")) != NULL)
{
*end = '\0';
link_url = strdup(start);
}
}
else if ((start = strstr(line, "<pubDate>")) != NULL)
{
start += 9;
if ((end = strstr(start, "</pubDate>")) != NULL)
{
*end = '\0';
event_time = httpGetDateTime(start);
}
}
else if ((start = strstr(line, "<guid>")) != NULL)
sequence_number = atoi(start + 6);
}
if (subject)
free(subject);
if (text)
free(text);
if (link_url)
free(link_url);
fclose(fp);
}
static _cups_rss_t *
new_message(int sequence_number,
char *subject,
char *text,
char *link_url,
time_t event_time)
{
_cups_rss_t *msg;
if ((msg = calloc(1, sizeof(_cups_rss_t))) == NULL)
return (NULL);
msg->sequence_number = sequence_number;
msg->subject = subject;
msg->text = text;
msg->link_url = link_url;
msg->event_time = event_time;
return (msg);
}
static const char *
password_cb(const char *prompt)
{
(void)prompt;
return (rss_password);
}
static int
save_rss(cups_array_t *rss,
const char *filename,
const char *baseurl)
{
FILE *fp;
_cups_rss_t *msg;
char date[1024];
char *href;
if ((fp = fopen(filename, "w")) == NULL)
{
fprintf(stderr, "ERROR: Unable to create %s: %s\n", filename,
strerror(errno));
return (0);
}
fputs("<?xml version=\"1.0\"?>\n", fp);
fputs("<rss version=\"2.0\">\n", fp);
fputs(" <channel>\n", fp);
fputs(" <title>CUPS RSS Feed</title>\n", fp);
href = xml_escape(baseurl);
fprintf(fp, " <link>%s</link>\n", href);
free(href);
fputs(" <description>CUPS RSS Feed</description>\n", fp);
fputs(" <generator>" CUPS_SVERSION "</generator>\n", fp);
fputs(" <ttl>1</ttl>\n", fp);
fprintf(fp, " <pubDate>%s</pubDate>\n",
httpGetDateString2(time(NULL), date, sizeof(date)));
for (msg = (_cups_rss_t *)cupsArrayLast(rss);
msg;
msg = (_cups_rss_t *)cupsArrayPrev(rss))
{
fputs(" <item>\n", fp);
fprintf(fp, " <title>%s</title>\n", msg->subject);
fprintf(fp, " <description>%s</description>\n", msg->text);
if (msg->link_url)
fprintf(fp, " <link>%s</link>\n", msg->link_url);
fprintf(fp, " <pubDate>%s</pubDate>\n",
httpGetDateString2(msg->event_time, date, sizeof(date)));
fprintf(fp, " <guid>%d</guid>\n", msg->sequence_number);
fputs(" </item>\n", fp);
}
fputs(" </channel>\n", fp);
fputs("</rss>\n", fp);
return (!fclose(fp));
}
static char *
xml_escape(const char *s)
{
char *e,
*eptr;
const char *sptr;
size_t bytes;
for (bytes = 0, sptr = s; *sptr; sptr ++)
if (*sptr == '&')
bytes += 4;
else if (*sptr == '<' || *sptr == '>')
bytes += 3;
if (bytes == 0)
return (strdup(s));
if ((e = malloc(bytes + 1 + strlen(s))) == NULL)
return (NULL);
for (eptr = e, sptr = s; *sptr; sptr ++)
if (*sptr == '&')
{
*eptr++ = '&';
*eptr++ = 'a';
*eptr++ = 'm';
*eptr++ = 'p';
*eptr++ = ';';
}
else if (*sptr == '<')
{
*eptr++ = '&';
*eptr++ = 'l';
*eptr++ = 't';
*eptr++ = ';';
}
else if (*sptr == '>')
{
*eptr++ = '&';
*eptr++ = 'g';
*eptr++ = 't';
*eptr++ = ';';
}
else
*eptr++ = *sptr;
*eptr = '\0';
return (e);
}