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