]> git.ipfire.org Git - thirdparty/cups.git/blame - notifier/rss.c
Load cups into easysw/current.
[thirdparty/cups.git] / notifier / rss.c
CommitLineData
f7deaa1a 1/*
09a101d6 2 * "$Id: rss.c 6596 2007-06-21 22:42:25Z mike $"
f7deaa1a 3 *
4 * RSS notifier for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2007 by Easy Software Products.
7 *
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE.txt" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
13 * at:
14 *
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
19 *
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
23 *
24 * Contents:
25 *
26 * main() - Main entry for the test notifier.
27 * compare_rss() - Compare two messages.
28 * delete_message() - Free all memory used by a message.
29 * load_rss() - Load an existing RSS feed file.
30 * new_message() - Create a new RSS message.
31 * password_cb() - Return the cached password.
32 * save_rss() - Save messages to a RSS file.
33 * xml_escape() - Copy a string, escaping &, <, and > as needed.
34 */
35
36/*
37 * Include necessary headers...
38 */
39
40#include <cups/cups.h>
41#include <cups/language.h>
42#include <cups/string.h>
43#include <cups/array.h>
44#include <errno.h>
45
46
47/*
48 * Structures...
49 */
50
51typedef struct _cups_rss_s /**** RSS message data ****/
52{
53 int sequence_number; /* notify-sequence-number */
54 char *subject, /* Message subject/summary */
55 *text, /* Message text */
56 *link_url; /* Link to printer */
57 time_t event_time; /* When the event occurred */
58} _cups_rss_t;
59
60
61/*
62 * Local globals...
63 */
64
65static char *rss_password; /* Password for remote RSS */
66
67
68/*
69 * Local functions...
70 */
71
72static int compare_rss(_cups_rss_t *a, _cups_rss_t *b);
73static void delete_message(_cups_rss_t *rss);
74static void load_rss(cups_array_t *rss, const char *filename);
75static _cups_rss_t *new_message(int sequence_number, char *subject,
76 char *text, char *link_url,
77 time_t event_time);
78static const char *password_cb(const char *prompt);
79static int save_rss(cups_array_t *rss, const char *filename,
80 const char *baseurl);
81static char *xml_escape(const char *s);
82
83
84/*
85 * 'main()' - Main entry for the test notifier.
86 */
87
88int /* O - Exit status */
89main(int argc, /* I - Number of command-line arguments */
90 char *argv[]) /* I - Command-line arguments */
91{
92 int i; /* Looping var */
93 ipp_t *event; /* Event from scheduler */
94 ipp_state_t state; /* IPP event state */
95 char scheme[32], /* URI scheme ("rss") */
96 username[256], /* Username for remote RSS */
97 host[1024], /* Hostname for remote RSS */
98 resource[1024], /* RSS file */
99 *options; /* Options */
100 int port, /* Port number for remote RSS */
101 max_events; /* Maximum number of events */
102 http_t *http; /* Connection to remote server */
103 http_status_t status; /* HTTP GET/PUT status code */
104 char filename[1024], /* Local filename */
105 newname[1024]; /* filename.N */
106 cups_lang_t *language; /* Language information */
107 ipp_attribute_t *printer_up_time, /* Timestamp on event */
108 *notify_sequence_number,/* Sequence number */
109 *notify_printer_uri; /* Printer URI */
110 char *subject, /* Subject for notification message */
111 *text, /* Text for notification message */
112 link_url[1024], /* Link to printer */
113 link_scheme[32], /* Scheme for link */
114 link_username[256], /* Username for link */
115 link_host[1024], /* Host for link */
116 link_resource[1024]; /* Resource for link */
117 int link_port; /* Link port */
118 cups_array_t *rss; /* RSS message array */
119 _cups_rss_t *msg; /* RSS message */
120 char baseurl[1024]; /* Base URL */
121
122
123 fprintf(stderr, "DEBUG: argc=%d\n", argc);
124 for (i = 0; i < argc; i ++)
125 fprintf(stderr, "DEBUG: argv[%d]=\"%s\"\n", i, argv[i]);
126
127 /*
128 * See whether we are publishing this RSS feed locally or remotely...
129 */
130
131 if (httpSeparateURI(HTTP_URI_CODING_ALL, argv[1], scheme, sizeof(scheme),
132 username, sizeof(username), host, sizeof(host), &port,
133 resource, sizeof(resource)) < HTTP_URI_OK)
134 {
135 fprintf(stderr, "ERROR: Bad RSS URI \"%s\"!\n", argv[1]);
136 return (1);
137 }
138
139 max_events = 20;
140
141 if ((options = strchr(resource, '?')) != NULL)
142 {
143 *options++ = '\0';
144
145 if (!strncmp(options, "max_events=", 11))
146 {
147 max_events = atoi(options + 11);
148
149 if (max_events <= 0)
150 max_events = 20;
151 }
152 }
153
154 rss = cupsArrayNew((cups_array_func_t)compare_rss, NULL);
155
156 if (host[0])
157 {
158 /*
159 * Remote feed, see if we can get the current file...
160 */
161
162 int fd; /* Temporary file */
163
164
165 if ((rss_password = strchr(username, ':')) != NULL)
166 *rss_password++ = '\0';
167
168 cupsSetPasswordCB(password_cb);
169 cupsSetUser(username);
170
171 if ((fd = cupsTempFd(filename, sizeof(filename))) < 0)
172 {
173 fprintf(stderr, "ERROR: Unable to create temporary file: %s\n",
174 strerror(errno));
175
176 return (1);
177 }
178
179 if ((http = httpConnect(host, port)) == NULL)
180 {
181 fprintf(stderr, "ERROR: Unable to connect to %s on port %d: %s\n",
182 host, port, strerror(errno));
183
184 close(fd);
185 unlink(filename);
186
187 return (1);
188 }
189
190 status = cupsGetFd(http, resource, fd);
191
192 close(fd);
193
194 if (status != HTTP_OK && status != HTTP_NOT_FOUND)
195 {
196 fprintf(stderr, "ERROR: Unable to GET %s from %s on port %d: %d %s\n",
197 resource, host, port, status, httpStatus(status));
198
199 httpClose(http);
200 unlink(filename);
201
202 return (1);
203 }
204
205 strlcpy(newname, filename, sizeof(newname));
206
207 httpAssembleURI(HTTP_URI_CODING_ALL, baseurl, sizeof(baseurl), "http",
208 NULL, host, port, resource);
209 }
210 else
211 {
212 const char *cachedir, /* CUPS_CACHEDIR */
213 *server_name, /* SERVER_NAME */
214 *server_port; /* SERVER_PORT */
215
216
217 http = NULL;
218
219 if ((cachedir = getenv("CUPS_CACHEDIR")) == NULL)
220 cachedir = CUPS_CACHEDIR;
221
222 if ((server_name = getenv("SERVER_NAME")) == NULL)
223 server_name = "localhost";
224
225 if ((server_port = getenv("SERVER_PORT")) == NULL)
226 server_port = "631";
227
228 snprintf(filename, sizeof(filename), "%s/rss%s", cachedir, resource);
229 snprintf(newname, sizeof(newname), "%s.N", filename);
230
231 httpAssembleURIf(HTTP_URI_CODING_ALL, baseurl, sizeof(baseurl), "http",
232 NULL, server_name, atoi(server_port), "/rss%s", resource);
233 }
234
235 /*
236 * Load the previous RSS file, if any...
237 */
238
239 load_rss(rss, filename);
240
241 /*
242 * Localize for the user's chosen language...
243 */
244
245 language = cupsLangDefault();
246
247 /*
248 * Read events and update the RSS file until we are out of events.
249 */
250
251 for (;;)
252 {
253 /*
254 * Read the next event...
255 */
256
257 event = ippNew();
258 while ((state = ippReadFile(0, event)) != IPP_DATA)
259 {
260 if (state <= IPP_IDLE)
261 break;
262 }
263
264 if (state == IPP_ERROR)
265 fputs("DEBUG: ippReadFile() returned IPP_ERROR!\n", stderr);
266
267 if (state <= IPP_IDLE)
268 {
269 ippDelete(event);
270
271 if (http)
272 unlink(filename);
273
274 httpClose(http);
275
276 return (0);
277 }
278
279 /*
280 * Collect the info from the event...
281 */
282
283 printer_up_time = ippFindAttribute(event, "printer-up-time",
284 IPP_TAG_INTEGER);
285 notify_sequence_number = ippFindAttribute(event, "notify-sequence-number",
286 IPP_TAG_INTEGER);
287 notify_printer_uri = ippFindAttribute(event, "notify-printer-uri",
288 IPP_TAG_URI);
289 subject = cupsNotifySubject(language, event);
290 text = cupsNotifyText(language, event);
291
292 if (printer_up_time && notify_sequence_number && subject && text)
293 {
294 /*
295 * Create a new RSS message...
296 */
297
298 if (notify_printer_uri)
299 {
300 httpSeparateURI(HTTP_URI_CODING_ALL,
301 notify_printer_uri->values[0].string.text,
302 link_scheme, sizeof(link_scheme),
303 link_username, sizeof(link_username),
304 link_host, sizeof(link_host), &link_port,
305 link_resource, sizeof(link_resource));
306 httpAssembleURI(HTTP_URI_CODING_ALL, link_url, sizeof(link_url),
307 "http", link_username, link_host, link_port,
308 link_resource);
309 }
310
311 msg = new_message(notify_sequence_number->values[0].integer,
312 xml_escape(subject), xml_escape(text),
313 notify_printer_uri ? xml_escape(link_url) : NULL,
314 printer_up_time->values[0].integer);
315
316 if (!msg)
317 {
318 fprintf(stderr, "ERROR: Unable to create message: %s\n",
319 strerror(errno));
320
321 ippDelete(event);
322
323 if (http)
324 unlink(filename);
325
326 httpClose(http);
327
328 return (1);
329 }
330
331 /*
332 * Add it to the array...
333 */
334
335 cupsArrayAdd(rss, msg);
336
337 /*
338 * Trim the array as needed...
339 */
340
341 while (cupsArrayCount(rss) > max_events)
342 {
343 msg = cupsArrayFirst(rss);
344
345 cupsArrayRemove(rss, msg);
346
347 delete_message(msg);
348 }
349
350 /*
351 * Save the messages to the file again, uploading as needed...
352 */
353
354 if (save_rss(rss, newname, baseurl))
355 {
356 if (http)
357 {
358 /*
359 * Upload the RSS file...
360 */
361
362 if ((status = cupsPutFile(http, resource, filename)) != HTTP_CREATED)
363 fprintf(stderr, "ERROR: Unable to PUT %s from %s on port %d: %d %s\n",
364 resource, host, port, status, httpStatus(status));
365 }
366 else
367 {
368 /*
369 * Move the new RSS file over top the old one...
370 */
371
372 if (rename(newname, filename))
373 fprintf(stderr, "ERROR: Unable to rename %s to %s: %s\n",
374 newname, filename, strerror(errno));
375 }
376 }
377 }
378
379 if (subject)
380 free(subject);
381
382 if (text)
383 free(text);
384
385 ippDelete(event);
386 }
387}
388
389
390/*
391 * 'compare_rss()' - Compare two messages.
392 */
393
394static int /* O - Result of comparison */
395compare_rss(_cups_rss_t *a, /* I - First message */
396 _cups_rss_t *b) /* I - Second message */
397{
398 return (a->sequence_number - b->sequence_number);
399}
400
401
402/*
403 * 'delete_message()' - Free all memory used by a message.
404 */
405
406static void
407delete_message(_cups_rss_t *msg) /* I - RSS message */
408{
409 if (msg->subject)
410 free(msg->subject);
411
412 if (msg->text)
413 free(msg->text);
414
415 if (msg->link_url)
416 free(msg->link_url);
417
418 free(msg);
419}
420
421
422/*
423 * 'load_rss()' - Load an existing RSS feed file.
424 */
425
426static void
427load_rss(cups_array_t *rss, /* I - RSS messages */
428 const char *filename) /* I - File to load */
429{
430 FILE *fp; /* File pointer */
431 char line[4096], /* Line from file */
432 *subject, /* Subject */
433 *text, /* Text */
434 *link_url, /* Link URL */
435 *start, /* Start of element */
436 *end; /* End of element */
437 time_t event_time; /* Event time */
438 int sequence_number; /* Sequence number */
439 int in_item; /* In an item */
440 _cups_rss_t *msg; /* New message */
441
442
443 if ((fp = fopen(filename, "r")) == NULL)
444 {
445 if (errno != ENOENT)
446 fprintf(stderr, "ERROR: Unable to open %s: %s\n", filename,
447 strerror(errno));
448
449 return;
450 }
451
452 subject = NULL;
453 text = NULL;
454 link_url = NULL;
455 event_time = 0;
456 sequence_number = 0;
457 in_item = 0;
458
459 while (fgets(line, sizeof(line), fp))
460 {
461 if (strstr(line, "<item>"))
462 in_item = 1;
463 else if (strstr(line, "</item>") && in_item)
464 {
465 if (subject && text)
466 {
467 msg = new_message(sequence_number, subject, text, link_url,
468 event_time);
469
470 if (msg)
471 cupsArrayAdd(rss, msg);
472
473 }
474 else
475 {
476 if (subject)
477 free(subject);
478
479 if (text)
480 free(text);
481
482 if (link_url)
483 free(link_url);
484 }
485
486 subject = NULL;
487 text = NULL;
488 link_url = NULL;
489 event_time = 0;
490 sequence_number = 0;
491 in_item = 0;
492 }
493 else if (!in_item)
494 continue;
495 else if ((start = strstr(line, "<title>")) != NULL)
496 {
497 start += 7;
498 if ((end = strstr(start, "</title>")) != NULL)
499 {
500 *end = '\0';
501 subject = strdup(start);
502 }
503 }
504 else if ((start = strstr(line, "<description>")) != NULL)
505 {
506 start += 13;
507 if ((end = strstr(start, "</description>")) != NULL)
508 {
509 *end = '\0';
510 text = strdup(start);
511 }
512 }
513 else if ((start = strstr(line, "<link>")) != NULL)
514 {
515 start += 6;
516 if ((end = strstr(start, "</link>")) != NULL)
517 {
518 *end = '\0';
519 link_url = strdup(start);
520 }
521 }
522 else if ((start = strstr(line, "<pubDate>")) != NULL)
523 {
524 start += 9;
525 if ((end = strstr(start, "</pubDate>")) != NULL)
526 {
527 *end = '\0';
528 event_time = httpGetDateTime(start);
529 }
530 }
531 else if ((start = strstr(line, "<guid>")) != NULL)
532 sequence_number = atoi(start + 6);
533 }
534
535 fclose(fp);
536}
537
538
539/*
540 * 'new_message()' - Create a new RSS message.
541 */
542
543static _cups_rss_t * /* O - New message */
544new_message(int sequence_number, /* I - notify-sequence-number */
545 char *subject, /* I - Subject/summary */
546 char *text, /* I - Text */
547 char *link_url, /* I - Link to printer */
548 time_t event_time) /* I - Date/time of event */
549{
550 _cups_rss_t *msg; /* New message */
551
552
553 if ((msg = calloc(1, sizeof(_cups_rss_t))) == NULL)
554 return (NULL);
555
556 msg->sequence_number = sequence_number;
557 msg->subject = subject;
558 msg->text = text;
559 msg->link_url = link_url;
560 msg->event_time = event_time;
561
562 return (msg);
563}
564
565
566/*
567 * 'password_cb()' - Return the cached password.
568 */
569
570static const char * /* O - Cached password */
571password_cb(const char *prompt) /* I - Prompt string, unused */
572{
573 (void)prompt;
574
575 return (rss_password);
576}
577
578
579/*
580 * 'save_rss()' - Save messages to a RSS file.
581 */
582
583static int /* O - 1 on success, 0 on failure */
584save_rss(cups_array_t *rss, /* I - RSS messages */
585 const char *filename, /* I - File to save to */
586 const char *baseurl) /* I - Base URL */
587{
588 FILE *fp; /* File pointer */
589 _cups_rss_t *msg; /* Current message */
590 char date[1024]; /* Current date */
591
592
593 if ((fp = fopen(filename, "w")) == NULL)
594 {
595 fprintf(stderr, "ERROR: Unable to create %s: %s\n", filename,
596 strerror(errno));
597 return (0);
598 }
599
600 fputs("<?xml version=\"1.0\"?>\n", fp);
601 fputs("<rss version=\"2.0\">\n", fp);
602 fputs(" <channel>\n", fp);
603 fputs(" <title>CUPS RSS Feed</title>\n", fp);
604 fprintf(fp, " <link>%s</link>\n", baseurl);
09a101d6 605 fputs(" <description>CUPS RSS Feed</description>\n", fp);
f7deaa1a 606 fputs(" <generator>" CUPS_SVERSION "</generator>\n", fp);
607 fputs(" <ttl>1</ttl>\n", fp);
608
609 fprintf(fp, " <pubDate>%s</pubDate>\n",
610 httpGetDateString2(time(NULL), date, sizeof(date)));
611
612 for (msg = (_cups_rss_t *)cupsArrayLast(rss);
613 msg;
614 msg = (_cups_rss_t *)cupsArrayPrev(rss))
615 {
616 fputs(" <item>\n", fp);
617 fprintf(fp, " <title>%s</title>\n", msg->subject);
618 fprintf(fp, " <description>%s</description>\n", msg->text);
619 if (msg->link_url)
620 fprintf(fp, " <link>%s</link>\n", msg->link_url);
621 fprintf(fp, " <pubDate>%s</pubDate>\n",
622 httpGetDateString2(msg->event_time, date, sizeof(date)));
623 fprintf(fp, " <guid>%d</guid>\n", msg->sequence_number);
624 fputs(" </item>\n", fp);
625 }
626
627 fputs(" </channel>\n", fp);
628 fputs("</rss>\n", fp);
629
630 return (!fclose(fp));
631}
632
633
634/*
635 * 'xml_escape()' - Copy a string, escaping &, <, and > as needed.
636 */
637
638static char * /* O - Escaped string */
639xml_escape(const char *s) /* I - String to escape */
640{
641 char *e, /* Escaped string */
642 *eptr; /* Pointer into escaped string */
643 const char *sptr; /* Pointer into string */
644 size_t bytes; /* Bytes needed for string */
645
646
647 /*
648 * First figure out how many extra bytes we need...
649 */
650
651 for (bytes = 0, sptr = s; *sptr; sptr ++)
652 if (*sptr == '&')
653 bytes += 4; /* &amp; */
654 else if (*sptr == '<' || *sptr == '>')
655 bytes += 3; /* &lt; and &gt; */
656
657 /*
658 * If there is nothing to escape, just strdup() it...
659 */
660
661 if (bytes == 0)
662 return (strdup(s));
663
664 /*
665 * Otherwise allocate memory and copy...
666 */
667
668 if ((e = malloc(bytes + 1 + strlen(s))) == NULL)
669 return (NULL);
670
671 for (eptr = e, sptr = s; *sptr; sptr ++)
672 if (*sptr == '&')
673 {
674 *eptr++ = '&';
675 *eptr++ = 'a';
676 *eptr++ = 'm';
677 *eptr++ = 'p';
678 *eptr++ = ';';
679 }
680 else if (*sptr == '<')
681 {
682 *eptr++ = '&';
683 *eptr++ = 'l';
684 *eptr++ = 't';
685 *eptr++ = ';';
686 }
687 else if (*sptr == '>')
688 {
689 *eptr++ = '&';
690 *eptr++ = 'g';
691 *eptr++ = 't';
692 *eptr++ = ';';
693 }
694 else
695 *eptr++ = *sptr;
696
697 *eptr = '\0';
698
699 return (e);
700}
701
702
703/*
09a101d6 704 * End of "$Id: rss.c 6596 2007-06-21 22:42:25Z mike $".
f7deaa1a 705 */