]>
git.ipfire.org Git - thirdparty/cups.git/blob - notifier/rss.c
2 * "$Id: rss.c 7824 2008-08-01 21:11:55Z mike $"
4 * RSS notifier for CUPS.
6 * Copyright 2007-2011 by Apple Inc.
7 * Copyright 2007 by Easy Software Products.
9 * These coded instructions, statements, and computer programs are the
10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
17 * main() - Main entry for the test notifier.
18 * compare_rss() - Compare two messages.
19 * delete_message() - Free all memory used by a message.
20 * load_rss() - Load an existing RSS feed file.
21 * new_message() - Create a new RSS message.
22 * password_cb() - Return the cached password.
23 * save_rss() - Save messages to a RSS file.
24 * xml_escape() - Copy a string, escaping &, <, and > as needed.
28 * Include necessary headers...
31 #include <cups/cups.h>
32 #include <cups/language.h>
33 #include <cups/string-private.h>
34 #include <cups/array.h>
35 #include <sys/select.h>
36 #include <cups/ipp-private.h> /* TODO: Update so we don't need this */
43 typedef struct _cups_rss_s
/**** RSS message data ****/
45 int sequence_number
; /* notify-sequence-number */
46 char *subject
, /* Message subject/summary */
47 *text
, /* Message text */
48 *link_url
; /* Link to printer */
49 time_t event_time
; /* When the event occurred */
57 static char *rss_password
; /* Password for remote RSS */
64 static int compare_rss(_cups_rss_t
*a
, _cups_rss_t
*b
);
65 static void delete_message(_cups_rss_t
*rss
);
66 static void load_rss(cups_array_t
*rss
, const char *filename
);
67 static _cups_rss_t
*new_message(int sequence_number
, char *subject
,
68 char *text
, char *link_url
,
70 static const char *password_cb(const char *prompt
);
71 static int save_rss(cups_array_t
*rss
, const char *filename
,
73 static char *xml_escape(const char *s
);
77 * 'main()' - Main entry for the test notifier.
80 int /* O - Exit status */
81 main(int argc
, /* I - Number of command-line arguments */
82 char *argv
[]) /* I - Command-line arguments */
84 int i
; /* Looping var */
85 ipp_t
*event
; /* Event from scheduler */
86 ipp_state_t state
; /* IPP event state */
87 char scheme
[32], /* URI scheme ("rss") */
88 username
[256], /* Username for remote RSS */
89 host
[1024], /* Hostname for remote RSS */
90 resource
[1024], /* RSS file */
91 *options
; /* Options */
92 int port
, /* Port number for remote RSS */
93 max_events
; /* Maximum number of events */
94 http_t
*http
; /* Connection to remote server */
95 http_status_t status
; /* HTTP GET/PUT status code */
96 char filename
[1024], /* Local filename */
97 newname
[1024]; /* filename.N */
98 cups_lang_t
*language
; /* Language information */
99 ipp_attribute_t
*printer_up_time
, /* Timestamp on event */
100 *notify_sequence_number
,/* Sequence number */
101 *notify_printer_uri
; /* Printer URI */
102 char *subject
, /* Subject for notification message */
103 *text
, /* Text for notification message */
104 link_url
[1024], /* Link to printer */
105 link_scheme
[32], /* Scheme for link */
106 link_username
[256], /* Username for link */
107 link_host
[1024], /* Host for link */
108 link_resource
[1024]; /* Resource for link */
109 int link_port
; /* Link port */
110 cups_array_t
*rss
; /* RSS message array */
111 _cups_rss_t
*msg
; /* RSS message */
112 char baseurl
[1024]; /* Base URL */
113 fd_set input
; /* Input set for select() */
114 struct timeval timeout
; /* Timeout for select() */
115 int changed
; /* Has the RSS data changed? */
116 int exit_status
; /* Exit status */
119 fprintf(stderr
, "DEBUG: argc=%d\n", argc
);
120 for (i
= 0; i
< argc
; i
++)
121 fprintf(stderr
, "DEBUG: argv[%d]=\"%s\"\n", i
, argv
[i
]);
124 * See whether we are publishing this RSS feed locally or remotely...
127 if (httpSeparateURI(HTTP_URI_CODING_ALL
, argv
[1], scheme
, sizeof(scheme
),
128 username
, sizeof(username
), host
, sizeof(host
), &port
,
129 resource
, sizeof(resource
)) < HTTP_URI_OK
)
131 fprintf(stderr
, "ERROR: Bad RSS URI \"%s\"!\n", argv
[1]);
137 if ((options
= strchr(resource
, '?')) != NULL
)
141 if (!strncmp(options
, "max_events=", 11))
143 max_events
= atoi(options
+ 11);
150 rss
= cupsArrayNew((cups_array_func_t
)compare_rss
, NULL
);
155 * Remote feed, see if we can get the current file...
158 int fd
; /* Temporary file */
161 if ((rss_password
= strchr(username
, ':')) != NULL
)
162 *rss_password
++ = '\0';
164 cupsSetPasswordCB(password_cb
);
165 cupsSetUser(username
);
167 if ((fd
= cupsTempFd(filename
, sizeof(filename
))) < 0)
169 fprintf(stderr
, "ERROR: Unable to create temporary file: %s\n",
175 if ((http
= httpConnect(host
, port
)) == NULL
)
177 fprintf(stderr
, "ERROR: Unable to connect to %s on port %d: %s\n",
178 host
, port
, strerror(errno
));
186 status
= cupsGetFd(http
, resource
, fd
);
190 if (status
!= HTTP_OK
&& status
!= HTTP_NOT_FOUND
)
192 fprintf(stderr
, "ERROR: Unable to GET %s from %s on port %d: %d %s\n",
193 resource
, host
, port
, status
, httpStatus(status
));
201 strlcpy(newname
, filename
, sizeof(newname
));
203 httpAssembleURI(HTTP_URI_CODING_ALL
, baseurl
, sizeof(baseurl
), "http",
204 NULL
, host
, port
, resource
);
208 const char *cachedir
, /* CUPS_CACHEDIR */
209 *server_name
, /* SERVER_NAME */
210 *server_port
; /* SERVER_PORT */
215 if ((cachedir
= getenv("CUPS_CACHEDIR")) == NULL
)
216 cachedir
= CUPS_CACHEDIR
;
218 if ((server_name
= getenv("SERVER_NAME")) == NULL
)
219 server_name
= "localhost";
221 if ((server_port
= getenv("SERVER_PORT")) == NULL
)
224 snprintf(filename
, sizeof(filename
), "%s/rss%s", cachedir
, resource
);
225 snprintf(newname
, sizeof(newname
), "%s.N", filename
);
227 httpAssembleURIf(HTTP_URI_CODING_ALL
, baseurl
, sizeof(baseurl
), "http",
228 NULL
, server_name
, atoi(server_port
), "/rss%s", resource
);
232 * Load the previous RSS file, if any...
235 load_rss(rss
, filename
);
237 changed
= cupsArrayCount(rss
) == 0;
240 * Localize for the user's chosen language...
243 language
= cupsLangDefault();
246 * Read events and update the RSS file until we are out of events.
249 for (exit_status
= 0, event
= NULL
;;)
254 * Save the messages to the file again, uploading as needed...
257 if (save_rss(rss
, newname
, baseurl
))
262 * Upload the RSS file...
265 if ((status
= cupsPutFile(http
, resource
, filename
)) != HTTP_CREATED
)
266 fprintf(stderr
, "ERROR: Unable to PUT %s from %s on port %d: %d %s\n",
267 resource
, host
, port
, status
, httpStatus(status
));
272 * Move the new RSS file over top the old one...
275 if (rename(newname
, filename
))
276 fprintf(stderr
, "ERROR: Unable to rename %s to %s: %s\n",
277 newname
, filename
, strerror(errno
));
285 * Wait up to 30 seconds for an event...
294 if (select(1, &input
, NULL
, NULL
, &timeout
) < 0)
296 else if (!FD_ISSET(0, &input
))
298 fprintf(stderr
, "DEBUG: %s is bored, exiting...\n", argv
[1]);
303 * Read the next event...
307 while ((state
= ippReadFile(0, event
)) != IPP_DATA
)
309 if (state
<= IPP_IDLE
)
313 if (state
== IPP_ERROR
)
314 fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr
);
316 if (state
<= IPP_IDLE
)
320 * Collect the info from the event...
323 printer_up_time
= ippFindAttribute(event
, "printer-up-time",
325 notify_sequence_number
= ippFindAttribute(event
, "notify-sequence-number",
327 notify_printer_uri
= ippFindAttribute(event
, "notify-printer-uri",
329 subject
= cupsNotifySubject(language
, event
);
330 text
= cupsNotifyText(language
, event
);
332 if (printer_up_time
&& notify_sequence_number
&& subject
&& text
)
335 * Create a new RSS message...
338 if (notify_printer_uri
)
340 httpSeparateURI(HTTP_URI_CODING_ALL
,
341 notify_printer_uri
->values
[0].string
.text
,
342 link_scheme
, sizeof(link_scheme
),
343 link_username
, sizeof(link_username
),
344 link_host
, sizeof(link_host
), &link_port
,
345 link_resource
, sizeof(link_resource
));
346 httpAssembleURI(HTTP_URI_CODING_ALL
, link_url
, sizeof(link_url
),
347 "http", link_username
, link_host
, link_port
,
351 msg
= new_message(notify_sequence_number
->values
[0].integer
,
352 xml_escape(subject
), xml_escape(text
),
353 notify_printer_uri
? xml_escape(link_url
) : NULL
,
354 printer_up_time
->values
[0].integer
);
358 fprintf(stderr
, "ERROR: Unable to create message: %s\n",
365 * Add it to the array...
368 cupsArrayAdd(rss
, msg
);
373 * Trim the array as needed...
376 while (cupsArrayCount(rss
) > max_events
)
378 msg
= cupsArrayFirst(rss
);
380 cupsArrayRemove(rss
, msg
);
397 * We only get here when idle or error...
408 return (exit_status
);
413 * 'compare_rss()' - Compare two messages.
416 static int /* O - Result of comparison */
417 compare_rss(_cups_rss_t
*a
, /* I - First message */
418 _cups_rss_t
*b
) /* I - Second message */
420 return (a
->sequence_number
- b
->sequence_number
);
425 * 'delete_message()' - Free all memory used by a message.
429 delete_message(_cups_rss_t
*msg
) /* I - RSS message */
445 * 'load_rss()' - Load an existing RSS feed file.
449 load_rss(cups_array_t
*rss
, /* I - RSS messages */
450 const char *filename
) /* I - File to load */
452 FILE *fp
; /* File pointer */
453 char line
[4096], /* Line from file */
454 *subject
, /* Subject */
456 *link_url
, /* Link URL */
457 *start
, /* Start of element */
458 *end
; /* End of element */
459 time_t event_time
; /* Event time */
460 int sequence_number
; /* Sequence number */
461 int in_item
; /* In an item */
462 _cups_rss_t
*msg
; /* New message */
465 if ((fp
= fopen(filename
, "r")) == NULL
)
468 fprintf(stderr
, "ERROR: Unable to open %s: %s\n", filename
,
481 while (fgets(line
, sizeof(line
), fp
))
483 if (strstr(line
, "<item>"))
485 else if (strstr(line
, "</item>") && in_item
)
489 msg
= new_message(sequence_number
, subject
, text
, link_url
,
493 cupsArrayAdd(rss
, msg
);
517 else if ((start
= strstr(line
, "<title>")) != NULL
)
520 if ((end
= strstr(start
, "</title>")) != NULL
)
523 subject
= strdup(start
);
526 else if ((start
= strstr(line
, "<description>")) != NULL
)
529 if ((end
= strstr(start
, "</description>")) != NULL
)
532 text
= strdup(start
);
535 else if ((start
= strstr(line
, "<link>")) != NULL
)
538 if ((end
= strstr(start
, "</link>")) != NULL
)
541 link_url
= strdup(start
);
544 else if ((start
= strstr(line
, "<pubDate>")) != NULL
)
547 if ((end
= strstr(start
, "</pubDate>")) != NULL
)
550 event_time
= httpGetDateTime(start
);
553 else if ((start
= strstr(line
, "<guid>")) != NULL
)
554 sequence_number
= atoi(start
+ 6);
562 * 'new_message()' - Create a new RSS message.
565 static _cups_rss_t
* /* O - New message */
566 new_message(int sequence_number
, /* I - notify-sequence-number */
567 char *subject
, /* I - Subject/summary */
568 char *text
, /* I - Text */
569 char *link_url
, /* I - Link to printer */
570 time_t event_time
) /* I - Date/time of event */
572 _cups_rss_t
*msg
; /* New message */
575 if ((msg
= calloc(1, sizeof(_cups_rss_t
))) == NULL
)
578 msg
->sequence_number
= sequence_number
;
579 msg
->subject
= subject
;
581 msg
->link_url
= link_url
;
582 msg
->event_time
= event_time
;
589 * 'password_cb()' - Return the cached password.
592 static const char * /* O - Cached password */
593 password_cb(const char *prompt
) /* I - Prompt string, unused */
597 return (rss_password
);
602 * 'save_rss()' - Save messages to a RSS file.
605 static int /* O - 1 on success, 0 on failure */
606 save_rss(cups_array_t
*rss
, /* I - RSS messages */
607 const char *filename
, /* I - File to save to */
608 const char *baseurl
) /* I - Base URL */
610 FILE *fp
; /* File pointer */
611 _cups_rss_t
*msg
; /* Current message */
612 char date
[1024]; /* Current date */
613 char *href
; /* Escaped base URL */
616 if ((fp
= fopen(filename
, "w")) == NULL
)
618 fprintf(stderr
, "ERROR: Unable to create %s: %s\n", filename
,
623 fputs("<?xml version=\"1.0\"?>\n", fp
);
624 fputs("<rss version=\"2.0\">\n", fp
);
625 fputs(" <channel>\n", fp
);
626 fputs(" <title>CUPS RSS Feed</title>\n", fp
);
628 href
= xml_escape(baseurl
);
629 fprintf(fp
, " <link>%s</link>\n", href
);
632 fputs(" <description>CUPS RSS Feed</description>\n", fp
);
633 fputs(" <generator>" CUPS_SVERSION
"</generator>\n", fp
);
634 fputs(" <ttl>1</ttl>\n", fp
);
636 fprintf(fp
, " <pubDate>%s</pubDate>\n",
637 httpGetDateString2(time(NULL
), date
, sizeof(date
)));
639 for (msg
= (_cups_rss_t
*)cupsArrayLast(rss
);
641 msg
= (_cups_rss_t
*)cupsArrayPrev(rss
))
643 fputs(" <item>\n", fp
);
644 fprintf(fp
, " <title>%s</title>\n", msg
->subject
);
645 fprintf(fp
, " <description>%s</description>\n", msg
->text
);
647 fprintf(fp
, " <link>%s</link>\n", msg
->link_url
);
648 fprintf(fp
, " <pubDate>%s</pubDate>\n",
649 httpGetDateString2(msg
->event_time
, date
, sizeof(date
)));
650 fprintf(fp
, " <guid>%d</guid>\n", msg
->sequence_number
);
651 fputs(" </item>\n", fp
);
654 fputs(" </channel>\n", fp
);
655 fputs("</rss>\n", fp
);
657 return (!fclose(fp
));
662 * 'xml_escape()' - Copy a string, escaping &, <, and > as needed.
665 static char * /* O - Escaped string */
666 xml_escape(const char *s
) /* I - String to escape */
668 char *e
, /* Escaped string */
669 *eptr
; /* Pointer into escaped string */
670 const char *sptr
; /* Pointer into string */
671 size_t bytes
; /* Bytes needed for string */
675 * First figure out how many extra bytes we need...
678 for (bytes
= 0, sptr
= s
; *sptr
; sptr
++)
680 bytes
+= 4; /* & */
681 else if (*sptr
== '<' || *sptr
== '>')
682 bytes
+= 3; /* < and > */
685 * If there is nothing to escape, just strdup() it...
692 * Otherwise allocate memory and copy...
695 if ((e
= malloc(bytes
+ 1 + strlen(s
))) == NULL
)
698 for (eptr
= e
, sptr
= s
; *sptr
; sptr
++)
707 else if (*sptr
== '<')
714 else if (*sptr
== '>')
731 * End of "$Id: rss.c 7824 2008-08-01 21:11:55Z mike $".