Imported Upstream version 1.5.3
[platform/upstream/cups.git] / notifier / rss.c
1 /*
2  * "$Id: rss.c 10379 2012-03-23 22:16:22Z 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   if (subject)
557     free(subject);
558
559   if (text)
560     free(text);
561
562   if (link_url)
563     free(link_url);
564
565   fclose(fp);
566 }
567
568
569 /*
570  * 'new_message()' - Create a new RSS message.
571  */
572
573 static _cups_rss_t *                    /* O - New message */
574 new_message(int    sequence_number,     /* I - notify-sequence-number */
575             char   *subject,            /* I - Subject/summary */
576             char   *text,               /* I - Text */
577             char   *link_url,           /* I - Link to printer */
578             time_t event_time)          /* I - Date/time of event */
579 {
580   _cups_rss_t   *msg;                   /* New message */
581
582
583   if ((msg = calloc(1, sizeof(_cups_rss_t))) == NULL)
584     return (NULL);
585
586   msg->sequence_number = sequence_number;
587   msg->subject         = subject;
588   msg->text            = text;
589   msg->link_url        = link_url;
590   msg->event_time      = event_time;
591
592   return (msg);
593 }
594
595
596 /*
597  * 'password_cb()' - Return the cached password.
598  */
599
600 static const char *                     /* O - Cached password */
601 password_cb(const char *prompt)         /* I - Prompt string, unused */
602 {
603   (void)prompt;
604
605   return (rss_password);
606 }
607
608
609 /*
610  * 'save_rss()' - Save messages to a RSS file.
611  */
612
613 static int                              /* O - 1 on success, 0 on failure */
614 save_rss(cups_array_t *rss,             /* I - RSS messages */
615          const char   *filename,        /* I - File to save to */
616          const char   *baseurl)         /* I - Base URL */
617 {
618   FILE          *fp;                    /* File pointer */
619   _cups_rss_t   *msg;                   /* Current message */
620   char          date[1024];             /* Current date */
621   char          *href;                  /* Escaped base URL */
622
623
624   if ((fp = fopen(filename, "w")) == NULL)
625   {
626     fprintf(stderr, "ERROR: Unable to create %s: %s\n", filename,
627             strerror(errno));
628     return (0);
629   }
630
631   fputs("<?xml version=\"1.0\"?>\n", fp);
632   fputs("<rss version=\"2.0\">\n", fp);
633   fputs("  <channel>\n", fp);
634   fputs("    <title>CUPS RSS Feed</title>\n", fp);
635
636   href = xml_escape(baseurl);
637   fprintf(fp, "    <link>%s</link>\n", href);
638   free(href);
639
640   fputs("    <description>CUPS RSS Feed</description>\n", fp);
641   fputs("    <generator>" CUPS_SVERSION "</generator>\n", fp);
642   fputs("    <ttl>1</ttl>\n", fp);
643
644   fprintf(fp, "    <pubDate>%s</pubDate>\n",
645           httpGetDateString2(time(NULL), date, sizeof(date)));
646
647   for (msg = (_cups_rss_t *)cupsArrayLast(rss);
648        msg;
649        msg = (_cups_rss_t *)cupsArrayPrev(rss))
650   {
651     fputs("    <item>\n", fp);
652     fprintf(fp, "      <title>%s</title>\n", msg->subject);
653     fprintf(fp, "      <description>%s</description>\n", msg->text);
654     if (msg->link_url)
655       fprintf(fp, "      <link>%s</link>\n", msg->link_url);
656     fprintf(fp, "      <pubDate>%s</pubDate>\n",
657             httpGetDateString2(msg->event_time, date, sizeof(date)));
658     fprintf(fp, "      <guid>%d</guid>\n", msg->sequence_number);
659     fputs("    </item>\n", fp);
660   }
661
662   fputs(" </channel>\n", fp);
663   fputs("</rss>\n", fp);
664
665   return (!fclose(fp));
666 }
667
668
669 /*
670  * 'xml_escape()' - Copy a string, escaping &, <, and > as needed.
671  */
672
673 static char *                           /* O - Escaped string */
674 xml_escape(const char *s)               /* I - String to escape */
675 {
676   char          *e,                     /* Escaped string */
677                 *eptr;                  /* Pointer into escaped string */
678   const char    *sptr;                  /* Pointer into string */
679   size_t        bytes;                  /* Bytes needed for string */
680
681
682  /*
683   * First figure out how many extra bytes we need...
684   */
685
686   for (bytes = 0, sptr = s; *sptr; sptr ++)
687     if (*sptr == '&')
688       bytes += 4;                       /* &amp; */
689     else if (*sptr == '<' || *sptr == '>')
690       bytes += 3;                       /* &lt; and &gt; */
691
692  /*
693   * If there is nothing to escape, just strdup() it...
694   */
695
696   if (bytes == 0)
697     return (strdup(s));
698
699  /*
700   * Otherwise allocate memory and copy...
701   */
702
703   if ((e = malloc(bytes + 1 + strlen(s))) == NULL)
704     return (NULL);
705
706   for (eptr = e, sptr = s; *sptr; sptr ++)
707     if (*sptr == '&')
708     {
709       *eptr++ = '&';
710       *eptr++ = 'a';
711       *eptr++ = 'm';
712       *eptr++ = 'p';
713       *eptr++ = ';';
714     }
715     else if (*sptr == '<')
716     {
717       *eptr++ = '&';
718       *eptr++ = 'l';
719       *eptr++ = 't';
720       *eptr++ = ';';
721     }
722     else if (*sptr == '>')
723     {
724       *eptr++ = '&';
725       *eptr++ = 'g';
726       *eptr++ = 't';
727       *eptr++ = ';';
728     }
729     else
730       *eptr++ = *sptr;
731
732   *eptr = '\0';
733
734   return (e);
735 }
736
737
738 /*
739  * End of "$Id: rss.c 10379 2012-03-23 22:16:22Z mike $".
740  */