root/lib/common/output_html.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. html_free_priv
  2. html_init
  3. add_error_node
  4. finish_reset_common
  5. html_finish
  6. html_reset
  7. html_subprocess_output
  8. html_version
  9. G_GNUC_PRINTF
  10. G_GNUC_PRINTF
  11. html_output_xml
  12. G_GNUC_PRINTF
  13. G_GNUC_PRINTF
  14. html_increment_list
  15. html_end_list
  16. html_is_quiet
  17. pcmk__mk_html_output
  18. pcmk__output_create_html_node
  19. pcmk__html_add_header

   1 /*
   2  * Copyright 2019-2020 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #ifndef _GNU_SOURCE
  11 #  define _GNU_SOURCE
  12 #endif
  13 
  14 #include <ctype.h>
  15 #include <libxml/HTMLtree.h>
  16 #include <stdarg.h>
  17 #include <stdlib.h>
  18 #include <stdio.h>
  19 
  20 #include <crm/crm.h>
  21 #include <crm/common/output_internal.h>
  22 #include <crm/common/xml.h>
  23 
  24 static const char *stylesheet_default =
  25     ".bold { font-weight: bold }\n"
  26     ".maint { color: blue }\n"
  27     ".offline { color: red }\n"
  28     ".online { color: green }\n"
  29     ".rsc-failed { color: red }\n"
  30     ".rsc-failure-ignored { color: yellow }\n"
  31     ".rsc-managed { color: yellow }\n"
  32     ".rsc-multiple { color: orange }\n"
  33     ".rsc-ok { color: green }\n"
  34     ".standby { color: orange }\n"
  35     ".warning { color: red, font-weight: bold }";
  36 
  37 static gboolean cgi_output = FALSE;
  38 static char *stylesheet_link = NULL;
  39 static char *title = NULL;
  40 static GSList *extra_headers = NULL;
  41 
  42 GOptionEntry pcmk__html_output_entries[] = {
  43     { "html-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output,
  44       "Add CGI headers (requires --output-as=html)",
  45       NULL },
  46 
  47     { "html-stylesheet", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link,
  48       "Link to an external stylesheet (requires --output-as=html)",
  49       "URI" },
  50 
  51     { "html-title", 0, 0, G_OPTION_ARG_STRING, &title,
  52       "Specify a page title (requires --output-as=html)",
  53       "TITLE" },
  54 
  55     { NULL }
  56 };
  57 
  58 typedef struct private_data_s {
  59     xmlNode *root;
  60     GQueue *parent_q;
  61     GSList *errors;
  62 } private_data_t;
  63 
  64 static void
  65 html_free_priv(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  66     private_data_t *priv = out->priv;
  67 
  68     if (priv == NULL) {
  69         return;
  70     }
  71 
  72     xmlFreeNode(priv->root);
  73     g_queue_free(priv->parent_q);
  74     g_slist_free(priv->errors);
  75     free(priv);
  76     out->priv = NULL;
  77 }
  78 
  79 static bool
  80 html_init(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  81     private_data_t *priv = NULL;
  82 
  83     /* If html_init was previously called on this output struct, just return. */
  84     if (out->priv != NULL) {
  85         return true;
  86     } else {
  87         out->priv = calloc(1, sizeof(private_data_t));
  88         if (out->priv == NULL) {
  89             return false;
  90         }
  91 
  92         priv = out->priv;
  93     }
  94 
  95     priv->parent_q = g_queue_new();
  96 
  97     priv->root = create_xml_node(NULL, "html");
  98     xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
  99 
 100     xmlSetProp(priv->root, (pcmkXmlStr) "lang", (pcmkXmlStr) "en");
 101     g_queue_push_tail(priv->parent_q, priv->root);
 102     priv->errors = NULL;
 103 
 104     pcmk__output_xml_create_parent(out, "body");
 105 
 106     return true;
 107 }
 108 
 109 static void
 110 add_error_node(gpointer data, gpointer user_data) {
     /* [previous][next][first][last][top][bottom][index][help] */
 111     char *str = (char *) data;
 112     pcmk__output_t *out = (pcmk__output_t *) user_data;
 113     out->list_item(out, NULL, "%s", str);
 114 }
 115 
 116 static void
 117 finish_reset_common(pcmk__output_t *out, crm_exit_t exit_status, bool print) {
     /* [previous][next][first][last][top][bottom][index][help] */
 118     private_data_t *priv = out->priv;
 119     htmlNodePtr head_node = NULL;
 120     htmlNodePtr charset_node = NULL;
 121 
 122     if (cgi_output && print) {
 123         fprintf(out->dest, "Content-Type: text/html\n\n");
 124     }
 125 
 126     /* Add the head node last - it's not needed earlier because it doesn't contain
 127      * anything else that the user could add, and we want it done last to pick up
 128      * any options that may have been given.
 129      */
 130     head_node = xmlNewNode(NULL, (pcmkXmlStr) "head");
 131 
 132     if (title != NULL ) {
 133         pcmk_create_xml_text_node(head_node, "title", title);
 134     } else if (out->request != NULL) {
 135         pcmk_create_xml_text_node(head_node, "title", out->request);
 136     }
 137 
 138     charset_node = create_xml_node(head_node, "meta");
 139     xmlSetProp(charset_node, (pcmkXmlStr) "charset", (pcmkXmlStr) "utf-8");
 140 
 141     /* Add any extra header nodes the caller might have created. */
 142     for (int i = 0; i < g_slist_length(extra_headers); i++) {
 143         xmlAddChild(head_node, xmlCopyNode(g_slist_nth_data(extra_headers, i), 1));
 144     }
 145 
 146     /* Stylesheets are included two different ways.  The first is via a built-in
 147      * default (see the stylesheet_default const above).  The second is via the
 148      * html-stylesheet option, and this should obviously be a link to a
 149      * stylesheet.  The second can override the first.  At least one should be
 150      * given.
 151      */
 152     pcmk_create_xml_text_node(head_node, "style", stylesheet_default);
 153 
 154     if (stylesheet_link != NULL) {
 155         htmlNodePtr link_node = create_xml_node(head_node, "link");
 156         xmlSetProp(link_node, (pcmkXmlStr) "rel", (pcmkXmlStr) "stylesheet");
 157         xmlSetProp(link_node, (pcmkXmlStr) "href", (pcmkXmlStr) stylesheet_link);
 158     }
 159 
 160     xmlAddPrevSibling(priv->root->children, head_node);
 161 
 162     if (g_slist_length(priv->errors) > 0) {
 163         out->begin_list(out, "Errors", NULL, NULL);
 164         g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
 165         out->end_list(out);
 166     }
 167 
 168     if (print) {
 169         htmlDocDump(out->dest, priv->root->doc);
 170     }
 171 }
 172 
 173 static void
 174 html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     /* [previous][next][first][last][top][bottom][index][help] */
 175     private_data_t *priv = out->priv;
 176 
 177     /* If root is NULL, html_init failed and we are being called from pcmk__output_free
 178      * in the pcmk__output_new path.
 179      */
 180     if (priv == NULL || priv->root == NULL) {
 181         return;
 182     }
 183 
 184     finish_reset_common(out, exit_status, print);
 185 
 186     if (copy_dest != NULL) {
 187         *copy_dest = copy_xml(priv->root);
 188     }
 189 
 190     g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode);
 191 }
 192 
 193 static void
 194 html_reset(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 195     CRM_ASSERT(out != NULL);
 196 
 197     out->dest = freopen(NULL, "w", out->dest);
 198     CRM_ASSERT(out->dest != NULL);
 199 
 200     if (out->priv != NULL) {
 201         finish_reset_common(out, CRM_EX_OK, true);
 202     }
 203 
 204     html_free_priv(out);
 205     html_init(out);
 206 }
 207 
 208 static void
 209 html_subprocess_output(pcmk__output_t *out, int exit_status,
     /* [previous][next][first][last][top][bottom][index][help] */
 210                       const char *proc_stdout, const char *proc_stderr) {
 211     char *rc_buf = NULL;
 212     private_data_t *priv = out->priv;
 213     CRM_ASSERT(priv != NULL);
 214 
 215     rc_buf = crm_strdup_printf("Return code: %d", exit_status);
 216 
 217     pcmk__output_create_xml_text_node(out, "h2", "Command Output");
 218     pcmk__output_create_html_node(out, "div", NULL, NULL, rc_buf);
 219 
 220     if (proc_stdout != NULL) {
 221         pcmk__output_create_html_node(out, "div", NULL, NULL, "Stdout");
 222         pcmk__output_create_html_node(out, "div", NULL, "output", proc_stdout);
 223     }
 224     if (proc_stderr != NULL) {
 225         pcmk__output_create_html_node(out, "div", NULL, NULL, "Stderr");
 226         pcmk__output_create_html_node(out, "div", NULL, "output", proc_stderr);
 227     }
 228 
 229     free(rc_buf);
 230 }
 231 
 232 static void
 233 html_version(pcmk__output_t *out, bool extended) {
     /* [previous][next][first][last][top][bottom][index][help] */
 234     private_data_t *priv = out->priv;
 235     CRM_ASSERT(priv != NULL);
 236 
 237     pcmk__output_create_xml_text_node(out, "h2", "Version Information");
 238     pcmk__output_create_html_node(out, "div", NULL, NULL, "Program: Pacemaker");
 239     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Version: %s", PACEMAKER_VERSION));
 240     pcmk__output_create_html_node(out, "div", NULL, NULL, "Author: Andrew Beekhof");
 241     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Build: %s", BUILD_VERSION));
 242     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Features: %s", CRM_FEATURES));
 243 }
 244 
 245 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 246 static void
 247 html_err(pcmk__output_t *out, const char *format, ...) {
 248     private_data_t *priv = out->priv;
 249     int len = 0;
 250     char *buf = NULL;
 251     va_list ap;
 252 
 253     CRM_ASSERT(priv != NULL);
 254     va_start(ap, format);
 255     len = vasprintf(&buf, format, ap);
 256     CRM_ASSERT(len >= 0);
 257     va_end(ap);
 258 
 259     priv->errors = g_slist_append(priv->errors, buf);
 260 }
 261 
 262 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 263 static void
 264 html_info(pcmk__output_t *out, const char *format, ...) {
 265     /* This function intentially left blank */
 266 }
 267 
 268 static void
 269 html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 270     htmlNodePtr node = NULL;
 271     private_data_t *priv = out->priv;
 272 
 273     CRM_ASSERT(priv != NULL);
 274 
 275     node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
 276     xmlSetProp(node, (pcmkXmlStr) "lang", (pcmkXmlStr) "xml");
 277 }
 278 
 279 G_GNUC_PRINTF(4, 5)
     /* [previous][next][first][last][top][bottom][index][help] */
 280 static void
 281 html_begin_list(pcmk__output_t *out, const char *singular_noun,
 282                 const char *plural_noun, const char *format, ...) {
 283     int q_len = 0;
 284     private_data_t *priv = out->priv;
 285     xmlNodePtr node = NULL;
 286 
 287     CRM_ASSERT(priv != NULL);
 288 
 289     /* If we are already in a list (the queue depth is always at least
 290      * one because of the <html> element), first create a <li> element
 291      * to hold the <h2> and the new list.
 292      */
 293     q_len = g_queue_get_length(priv->parent_q);
 294     if (q_len > 2) {
 295         pcmk__output_xml_create_parent(out, "li");
 296     }
 297 
 298     if (format != NULL) {
 299         va_list ap;
 300         char *buf = NULL;
 301         int len;
 302 
 303         va_start(ap, format);
 304         len = vasprintf(&buf, format, ap);
 305         va_end(ap);
 306         CRM_ASSERT(len >= 0);
 307 
 308         if (q_len > 2) {
 309             pcmk__output_create_xml_text_node(out, "h3", buf);
 310         } else {
 311             pcmk__output_create_xml_text_node(out, "h2", buf);
 312         }
 313 
 314         free(buf);
 315     }
 316 
 317     node = pcmk__output_xml_create_parent(out, "ul");
 318     g_queue_push_tail(priv->parent_q, node);
 319 }
 320 
 321 G_GNUC_PRINTF(3, 4)
     /* [previous][next][first][last][top][bottom][index][help] */
 322 static void
 323 html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
 324     private_data_t *priv = out->priv;
 325     htmlNodePtr item_node = NULL;
 326     va_list ap;
 327     char *buf = NULL;
 328     int len;
 329 
 330     CRM_ASSERT(priv != NULL);
 331 
 332     va_start(ap, format);
 333     len = vasprintf(&buf, format, ap);
 334     CRM_ASSERT(len >= 0);
 335     va_end(ap);
 336 
 337     item_node = pcmk__output_create_xml_text_node(out, "li", buf);
 338     free(buf);
 339 
 340     if (name != NULL) {
 341         xmlSetProp(item_node, (pcmkXmlStr) "class", (pcmkXmlStr) name);
 342     }
 343 }
 344 
 345 static void
 346 html_increment_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 347     /* This function intentially left blank */
 348 }
 349 
 350 static void
 351 html_end_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 352     private_data_t *priv = out->priv;
 353 
 354     CRM_ASSERT(priv != NULL);
 355 
 356     /* Remove the <ul> tag. */
 357     g_queue_pop_tail(priv->parent_q);
 358     pcmk__output_xml_pop_parent(out);
 359 
 360     /* Remove the <li> created for nested lists. */
 361     if (g_queue_get_length(priv->parent_q) > 2) {
 362         pcmk__output_xml_pop_parent(out);
 363     }
 364 }
 365 
 366 static bool
 367 html_is_quiet(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 368     return false;
 369 }
 370 
 371 pcmk__output_t *
 372 pcmk__mk_html_output(char **argv) {
     /* [previous][next][first][last][top][bottom][index][help] */
 373     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 374 
 375     if (retval == NULL) {
 376         return NULL;
 377     }
 378 
 379     retval->fmt_name = "html";
 380     retval->request = argv == NULL ? NULL : g_strjoinv(" ", argv);
 381 
 382     retval->init = html_init;
 383     retval->free_priv = html_free_priv;
 384     retval->finish = html_finish;
 385     retval->reset = html_reset;
 386 
 387     retval->register_message = pcmk__register_message;
 388     retval->message = pcmk__call_message;
 389 
 390     retval->subprocess_output = html_subprocess_output;
 391     retval->version = html_version;
 392     retval->info = html_info;
 393     retval->err = html_err;
 394     retval->output_xml = html_output_xml;
 395 
 396     retval->begin_list = html_begin_list;
 397     retval->list_item = html_list_item;
 398     retval->increment_list = html_increment_list;
 399     retval->end_list = html_end_list;
 400 
 401     retval->is_quiet = html_is_quiet;
 402 
 403     return retval;
 404 }
 405 
 406 xmlNodePtr
 407 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 408                        const char *class_name, const char *text) {
 409     htmlNodePtr node = pcmk__output_create_xml_text_node(out, element_name, text);
 410 
 411     if (class_name != NULL) {
 412         xmlSetProp(node, (pcmkXmlStr) "class", (pcmkXmlStr) class_name);
 413     }
 414 
 415     if (id != NULL) {
 416         xmlSetProp(node, (pcmkXmlStr) "id", (pcmkXmlStr) id);
 417     }
 418 
 419     return node;
 420 }
 421 
 422 void
 423 pcmk__html_add_header(const char *name, ...) {
     /* [previous][next][first][last][top][bottom][index][help] */
 424     htmlNodePtr header_node;
 425     va_list ap;
 426 
 427     va_start(ap, name);
 428 
 429     header_node = xmlNewNode(NULL, (pcmkXmlStr) name);
 430     while (1) {
 431         char *key = va_arg(ap, char *);
 432         char *value;
 433 
 434         if (key == NULL) {
 435             break;
 436         }
 437 
 438         value = va_arg(ap, char *);
 439         xmlSetProp(header_node, (pcmkXmlStr) key, (pcmkXmlStr) value);
 440     }
 441 
 442     extra_headers = g_slist_append(extra_headers, header_node);
 443 
 444     va_end(ap);
 445 }

/* [previous][next][first][last][top][bottom][index][help] */