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