mirror of https://gitee.com/openkylin/cups.git
739 lines
17 KiB
C
739 lines
17 KiB
C
/*
|
|
* RSS notifier for CUPS.
|
|
*
|
|
* Copyright 2007-2015 by Apple Inc.
|
|
* Copyright 2007 by Easy Software Products.
|
|
*
|
|
* Licensed under Apache License v2.0. See the file "LICENSE" for more information.
|
|
*/
|
|
|
|
/*
|
|
* Include necessary headers...
|
|
*/
|
|
|
|
#include <cups/cups.h>
|
|
#include <sys/stat.h>
|
|
#include <cups/language.h>
|
|
#include <cups/string-private.h>
|
|
#include <cups/array.h>
|
|
#include <sys/select.h>
|
|
#include <cups/ipp-private.h> /* TODO: Update so we don't need this */
|
|
|
|
|
|
/*
|
|
* Structures...
|
|
*/
|
|
|
|
typedef struct _cups_rss_s /**** RSS message data ****/
|
|
{
|
|
int sequence_number; /* notify-sequence-number */
|
|
char *subject, /* Message subject/summary */
|
|
*text, /* Message text */
|
|
*link_url; /* Link to printer */
|
|
time_t event_time; /* When the event occurred */
|
|
} _cups_rss_t;
|
|
|
|
|
|
/*
|
|
* Local globals...
|
|
*/
|
|
|
|
static char *rss_password; /* Password for remote RSS */
|
|
|
|
|
|
/*
|
|
* Local functions...
|
|
*/
|
|
|
|
static int compare_rss(_cups_rss_t *a, _cups_rss_t *b);
|
|
static void delete_message(_cups_rss_t *msg);
|
|
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);
|
|
|
|
|
|
/*
|
|
* 'main()' - Main entry for the test notifier.
|
|
*/
|
|
|
|
int /* O - Exit status */
|
|
main(int argc, /* I - Number of command-line arguments */
|
|
char *argv[]) /* I - Command-line arguments */
|
|
{
|
|
int i; /* Looping var */
|
|
ipp_t *event; /* Event from scheduler */
|
|
ipp_state_t state; /* IPP event state */
|
|
char scheme[32], /* URI scheme ("rss") */
|
|
username[256], /* Username for remote RSS */
|
|
host[1024], /* Hostname for remote RSS */
|
|
resource[1024], /* RSS file */
|
|
*options; /* Options */
|
|
int port, /* Port number for remote RSS */
|
|
max_events; /* Maximum number of events */
|
|
http_t *http; /* Connection to remote server */
|
|
http_status_t status; /* HTTP GET/PUT status code */
|
|
char filename[1024], /* Local filename */
|
|
newname[1024]; /* filename.N */
|
|
cups_lang_t *language; /* Language information */
|
|
ipp_attribute_t *printer_up_time, /* Timestamp on event */
|
|
*notify_sequence_number,/* Sequence number */
|
|
*notify_printer_uri; /* Printer URI */
|
|
char *subject, /* Subject for notification message */
|
|
*text, /* Text for notification message */
|
|
link_url[1024], /* Link to printer */
|
|
link_scheme[32], /* Scheme for link */
|
|
link_username[256], /* Username for link */
|
|
link_host[1024], /* Host for link */
|
|
link_resource[1024]; /* Resource for link */
|
|
int link_port; /* Link port */
|
|
cups_array_t *rss; /* RSS message array */
|
|
_cups_rss_t *msg; /* RSS message */
|
|
char baseurl[1024]; /* Base URL */
|
|
fd_set input; /* Input set for select() */
|
|
struct timeval timeout; /* Timeout for select() */
|
|
int changed; /* Has the RSS data changed? */
|
|
int exit_status; /* 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]);
|
|
|
|
/*
|
|
* See whether we are publishing this RSS feed locally or remotely...
|
|
*/
|
|
|
|
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])
|
|
{
|
|
/*
|
|
* Remote feed, see if we can get the current file...
|
|
*/
|
|
|
|
int fd; /* Temporary file */
|
|
|
|
|
|
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, /* CUPS_CACHEDIR */
|
|
*server_name, /* SERVER_NAME */
|
|
*server_port; /* 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 the previous RSS file, if any...
|
|
*/
|
|
|
|
load_rss(rss, filename);
|
|
|
|
changed = cupsArrayCount(rss) == 0;
|
|
|
|
/*
|
|
* Localize for the user's chosen language...
|
|
*/
|
|
|
|
language = cupsLangDefault();
|
|
|
|
/*
|
|
* Read events and update the RSS file until we are out of events.
|
|
*/
|
|
|
|
for (exit_status = 0, event = NULL;;)
|
|
{
|
|
if (changed)
|
|
{
|
|
/*
|
|
* Save the messages to the file again, uploading as needed...
|
|
*/
|
|
|
|
if (save_rss(rss, newname, baseurl))
|
|
{
|
|
if (http)
|
|
{
|
|
/*
|
|
* Upload the RSS file...
|
|
*/
|
|
|
|
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
|
|
{
|
|
/*
|
|
* Move the new RSS file over top the old one...
|
|
*/
|
|
|
|
if (rename(newname, filename))
|
|
fprintf(stderr, "ERROR: Unable to rename %s to %s: %s\n",
|
|
newname, filename, strerror(errno));
|
|
}
|
|
|
|
changed = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Wait up to 30 seconds for an event...
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Read the next event...
|
|
*/
|
|
|
|
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;
|
|
|
|
/*
|
|
* Collect the info from the event...
|
|
*/
|
|
|
|
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)
|
|
{
|
|
/*
|
|
* Create a new RSS message...
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Add it to the array...
|
|
*/
|
|
|
|
cupsArrayAdd(rss, msg);
|
|
|
|
changed = 1;
|
|
|
|
/*
|
|
* Trim the array as needed...
|
|
*/
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* We only get here when idle or error...
|
|
*/
|
|
|
|
ippDelete(event);
|
|
|
|
if (http)
|
|
{
|
|
unlink(filename);
|
|
httpClose(http);
|
|
}
|
|
|
|
return (exit_status);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'compare_rss()' - Compare two messages.
|
|
*/
|
|
|
|
static int /* O - Result of comparison */
|
|
compare_rss(_cups_rss_t *a, /* I - First message */
|
|
_cups_rss_t *b) /* I - Second message */
|
|
{
|
|
return (a->sequence_number - b->sequence_number);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'delete_message()' - Free all memory used by a message.
|
|
*/
|
|
|
|
static void
|
|
delete_message(_cups_rss_t *msg) /* I - RSS message */
|
|
{
|
|
if (msg->subject)
|
|
free(msg->subject);
|
|
|
|
if (msg->text)
|
|
free(msg->text);
|
|
|
|
if (msg->link_url)
|
|
free(msg->link_url);
|
|
|
|
free(msg);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'load_rss()' - Load an existing RSS feed file.
|
|
*/
|
|
|
|
static void
|
|
load_rss(cups_array_t *rss, /* I - RSS messages */
|
|
const char *filename) /* I - File to load */
|
|
{
|
|
FILE *fp; /* File pointer */
|
|
char line[4096], /* Line from file */
|
|
*subject, /* Subject */
|
|
*text, /* Text */
|
|
*link_url, /* Link URL */
|
|
*start, /* Start of element */
|
|
*end; /* End of element */
|
|
time_t event_time; /* Event time */
|
|
int sequence_number; /* Sequence number */
|
|
int in_item; /* In an item */
|
|
_cups_rss_t *msg; /* New message */
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'new_message()' - Create a new RSS message.
|
|
*/
|
|
|
|
static _cups_rss_t * /* O - New message */
|
|
new_message(int sequence_number, /* I - notify-sequence-number */
|
|
char *subject, /* I - Subject/summary */
|
|
char *text, /* I - Text */
|
|
char *link_url, /* I - Link to printer */
|
|
time_t event_time) /* I - Date/time of event */
|
|
{
|
|
_cups_rss_t *msg; /* New message */
|
|
|
|
|
|
if ((msg = calloc(1, sizeof(_cups_rss_t))) == NULL)
|
|
{
|
|
#ifdef __clang_analyzer__
|
|
// These free calls are really unnecessary (a failure here ultimately causes
|
|
// an exit, which frees all memory much faster) but it makes Clang happy...
|
|
free(subject);
|
|
free(text);
|
|
free(link_url);
|
|
#endif // __clang_analyzer__
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'password_cb()' - Return the cached password.
|
|
*/
|
|
|
|
static const char * /* O - Cached password */
|
|
password_cb(const char *prompt) /* I - Prompt string, unused */
|
|
{
|
|
(void)prompt;
|
|
|
|
return (rss_password);
|
|
}
|
|
|
|
|
|
/*
|
|
* 'save_rss()' - Save messages to a RSS file.
|
|
*/
|
|
|
|
static int /* O - 1 on success, 0 on failure */
|
|
save_rss(cups_array_t *rss, /* I - RSS messages */
|
|
const char *filename, /* I - File to save to */
|
|
const char *baseurl) /* I - Base URL */
|
|
{
|
|
FILE *fp; /* File pointer */
|
|
_cups_rss_t *msg; /* Current message */
|
|
char date[1024]; /* Current date */
|
|
char *href; /* Escaped base URL */
|
|
|
|
|
|
if ((fp = fopen(filename, "w")) == NULL)
|
|
{
|
|
fprintf(stderr, "ERROR: Unable to create %s: %s\n", filename,
|
|
strerror(errno));
|
|
return (0);
|
|
}
|
|
|
|
fchmod(fileno(fp), 0644);
|
|
|
|
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))
|
|
{
|
|
char *subject = xml_escape(msg->subject);
|
|
char *text = xml_escape(msg->text);
|
|
|
|
fputs(" <item>\n", fp);
|
|
fprintf(fp, " <title>%s</title>\n", subject);
|
|
fprintf(fp, " <description>%s</description>\n", 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);
|
|
|
|
free(subject);
|
|
free(text);
|
|
}
|
|
|
|
fputs(" </channel>\n", fp);
|
|
fputs("</rss>\n", fp);
|
|
|
|
return (!fclose(fp));
|
|
}
|
|
|
|
|
|
/*
|
|
* 'xml_escape()' - Copy a string, escaping &, <, and > as needed.
|
|
*/
|
|
|
|
static char * /* O - Escaped string */
|
|
xml_escape(const char *s) /* I - String to escape */
|
|
{
|
|
char *e, /* Escaped string */
|
|
*eptr; /* Pointer into escaped string */
|
|
const char *sptr; /* Pointer into string */
|
|
size_t bytes; /* Bytes needed for string */
|
|
|
|
|
|
/*
|
|
* First figure out how many extra bytes we need...
|
|
*/
|
|
|
|
for (bytes = 0, sptr = s; *sptr; sptr ++)
|
|
if (*sptr == '&')
|
|
bytes += 4; /* & */
|
|
else if (*sptr == '<' || *sptr == '>')
|
|
bytes += 3; /* < and > */
|
|
|
|
/*
|
|
* If there is nothing to escape, just strdup() it...
|
|
*/
|
|
|
|
if (bytes == 0)
|
|
return (strdup(s));
|
|
|
|
/*
|
|
* Otherwise allocate memory and copy...
|
|
*/
|
|
|
|
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);
|
|
}
|