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