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