Imported Upstream version 1.6.4
[platform/upstream/cups.git] / notifier / rss.c
1 /*
2  * "$Id: rss.c 11173 2013-07-23 12:31:34Z msweet $"
3  *
4  *   RSS notifier for CUPS.
5  *
6  *   Copyright 2007-2012 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 #include <cups/ipp-private.h>   /* TODO: Update so we don't need this */
37
38
39 /*
40  * Structures...
41  */
42
43 typedef 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
57 static char             *rss_password;  /* Password for remote RSS */
58
59
60 /*
61  * Local functions...
62  */
63
64 static int              compare_rss(_cups_rss_t *a, _cups_rss_t *b);
65 static void             delete_message(_cups_rss_t *rss);
66 static void             load_rss(cups_array_t *rss, const char *filename);
67 static _cups_rss_t      *new_message(int sequence_number, char *subject,
68                                      char *text, char *link_url,
69                                      time_t event_time);
70 static const char       *password_cb(const char *prompt);
71 static int              save_rss(cups_array_t *rss, const char *filename,
72                                  const char *baseurl);
73 static char             *xml_escape(const char *s);
74
75
76 /*
77  * 'main()' - Main entry for the test notifier.
78  */
79
80 int                                     /* O - Exit status */
81 main(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 */
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 */
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
237   changed = cupsArrayCount(rss) == 0;
238
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
249   for (exit_status = 0, event = NULL;;)
250   {
251     if (changed)
252     {
253      /*
254       * Save the messages to the file again, uploading as needed...
255       */
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
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)
317       break;
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));
360         exit_status = 1;
361         break;
362       }
363
364      /*
365       * Add it to the array...
366       */
367
368       cupsArrayAdd(rss, msg);
369
370       changed = 1;
371
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       }
384     }
385
386     if (subject)
387       free(subject);
388
389     if (text)
390       free(text);
391
392     ippDelete(event);
393     event = NULL;
394   }
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);
409 }
410
411
412 /*
413  * 'compare_rss()' - Compare two messages.
414  */
415
416 static int                              /* O - Result of comparison */
417 compare_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
428 static void
429 delete_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
448 static void
449 load_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
557   if (subject)
558     free(subject);
559
560   if (text)
561     free(text);
562
563   if (link_url)
564     free(link_url);
565
566   fclose(fp);
567 }
568
569
570 /*
571  * 'new_message()' - Create a new RSS message.
572  */
573
574 static _cups_rss_t *                    /* O - New message */
575 new_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
601 static const char *                     /* O - Cached password */
602 password_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
614 static int                              /* O - 1 on success, 0 on failure */
615 save_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 */
622   char          *href;                  /* Escaped base URL */
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);
636
637   href = xml_escape(baseurl);
638   fprintf(fp, "    <link>%s</link>\n", href);
639   free(href);
640
641   fputs("    <description>CUPS RSS Feed</description>\n", fp);
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
674 static char *                           /* O - Escaped string */
675 xml_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 /*
740  * End of "$Id: rss.c 11173 2013-07-23 12:31:34Z msweet $".
741  */