]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.81 2014/07/24 08:26:57 schwarze Exp $ */
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32 #include "mandoc_aux.h"
35 #include "mansearch.h"
39 * A query as passed to the search function.
42 const char *manpath
; /* desired manual directory */
43 const char *arch
; /* architecture */
44 const char *sec
; /* manual section */
45 const char *expr
; /* unparsed expression string */
46 int equal
; /* match whole names, not substrings */
51 char **p
; /* array of available manpaths */
52 size_t psz
; /* number of available manpaths */
55 static void catman(const struct req
*, const char *);
56 static int cmp(const void *, const void *);
57 static void format(const struct req
*, const char *);
58 static void html_print(const char *);
59 static void html_printquery(const struct req
*);
60 static void html_putchar(char);
61 static int http_decode(char *);
62 static void http_parse(struct req
*, char *);
63 static void http_print(const char *);
64 static void http_putchar(char);
65 static void http_printquery(const struct req
*);
66 static void pathgen(struct req
*);
67 static void pg_error_badrequest(const char *);
68 static void pg_error_internal(void);
69 static void pg_index(const struct req
*);
70 static void pg_noresult(const struct req
*, const char *);
71 static void pg_search(const struct req
*);
72 static void pg_searchres(const struct req
*,
73 struct manpage
*, size_t);
74 static void pg_show(struct req
*, const char *);
75 static void resp_begin_html(int, const char *);
76 static void resp_begin_http(int, const char *);
77 static void resp_end_html(void);
78 static void resp_searchform(const struct req
*);
79 static void resp_show(const struct req
*, const char *);
81 static const char *scriptname
; /* CGI script name */
83 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
84 static const char *const sec_numbers
[] = {
85 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
87 static const char *const sec_names
[] = {
89 "1 - General Commands",
92 "3p - Perl Subroutines",
96 "7 - Macros and Conventions",
97 "8 - Maintenance Commands",
98 "9 - Kernel Interface"
100 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
102 static const char *const arch_names
[] = {
103 "amd64", "alpha", "armish", "armv7",
104 "aviion", "hppa", "hppa64", "i386",
105 "ia64", "landisk", "loongson", "luna88k",
106 "macppc", "mips64", "octeon", "sgi",
107 "socppc", "solbourne", "sparc", "sparc64",
109 "amiga", "arc", "arm32", "atari",
110 "beagle", "cats", "hp300", "mac68k",
111 "mvme68k", "mvme88k", "mvmeppc", "palm",
112 "pc532", "pegasos", "pmax", "powerpc",
113 "sun3", "wgrisc", "x68k"
115 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
118 * Print a character, escaping HTML along the way.
119 * This will pass non-ASCII straight to output: be warned!
139 putchar((unsigned char)c
);
145 http_printquery(const struct req
*req
)
148 if (NULL
!= req
->q
.manpath
) {
150 http_print(req
->q
.manpath
);
152 if (NULL
!= req
->q
.sec
) {
154 http_print(req
->q
.sec
);
156 if (NULL
!= req
->q
.arch
) {
158 http_print(req
->q
.arch
);
160 if (NULL
!= req
->q
.expr
) {
162 http_print(req
->q
.expr
);
164 if (0 == req
->q
.equal
)
165 printf("&apropos=1");
169 html_printquery(const struct req
*req
)
172 if (NULL
!= req
->q
.manpath
) {
173 printf("&manpath=");
174 html_print(req
->q
.manpath
);
176 if (NULL
!= req
->q
.sec
) {
178 html_print(req
->q
.sec
);
180 if (NULL
!= req
->q
.arch
) {
181 printf("&arch=");
182 html_print(req
->q
.arch
);
184 if (NULL
!= req
->q
.expr
) {
185 printf("&query=");
186 html_print(req
->q
.expr
);
188 if (0 == req
->q
.equal
)
189 printf("&apropos=1");
193 http_print(const char *p
)
203 * Call through to html_putchar().
204 * Accepts NULL strings.
207 html_print(const char *p
)
217 * Parse out key-value pairs from an HTTP request variable.
218 * This can be either a cookie or a POST/GET string, although man.cgi
219 * uses only GET for simplicity.
222 http_parse(struct req
*req
, char *p
)
226 memset(&req
->q
, 0, sizeof(struct query
));
227 req
->q
.manpath
= req
->p
[0];
234 p
+= (int)strcspn(p
, ";&");
237 if (NULL
!= (val
= strchr(key
, '=')))
240 if ('\0' == *key
|| NULL
== val
|| '\0' == *val
)
243 /* Just abort handling. */
245 if ( ! http_decode(key
))
247 if (NULL
!= val
&& ! http_decode(val
))
250 if (0 == strcmp(key
, "query"))
252 else if (0 == strcmp(key
, "manpath")) {
254 if (0 == strncmp(val
, "OpenBSD ", 8)) {
260 req
->q
.manpath
= val
;
261 } else if (0 == strcmp(key
, "apropos"))
262 req
->q
.equal
= !strcmp(val
, "0");
263 else if (0 == strcmp(key
, "sec")) {
264 if (strcmp(val
, "0"))
267 } else if (0 == strcmp(key
, "sektion")) {
268 if (strcmp(val
, "0"))
271 } else if (0 == strcmp(key
, "arch")) {
272 if (strcmp(val
, "default"))
282 if (isalnum((unsigned char)c
)) {
283 putchar((unsigned char)c
);
285 } else if (' ' == c
) {
293 * HTTP-decode a string. The standard explanation is that this turns
294 * "%4e+foo" into "n foo" in the regular way. This is done in-place
295 * over the allocated string.
307 for ( ; '\0' != *p
; p
++, q
++) {
309 if ('\0' == (hex
[0] = *(p
+ 1)))
311 if ('\0' == (hex
[1] = *(p
+ 2)))
313 if (1 != sscanf(hex
, "%x", &c
))
321 *q
= '+' == *p
? ' ' : *p
;
329 resp_begin_http(int code
, const char *msg
)
333 printf("Status: %d %s\r\n", code
, msg
);
335 printf("Content-Type: text/html; charset=utf-8\r\n"
336 "Cache-Control: no-cache\r\n"
337 "Pragma: no-cache\r\n"
344 resp_begin_html(int code
, const char *msg
)
347 resp_begin_http(code
, msg
);
349 printf("<!DOCTYPE HTML PUBLIC "
350 " \"-//W3C//DTD HTML 4.01//EN\""
351 " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
354 "<META HTTP-EQUIV=\"Content-Type\""
355 " CONTENT=\"text/html; charset=utf-8\">\n"
356 "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
357 " TYPE=\"text/css\" media=\"all\">\n"
358 "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
359 " TYPE=\"text/css\" media=\"all\">\n"
360 "<TITLE>%s</TITLE>\n"
363 "<!-- Begin page content. //-->\n",
364 CSS_DIR
, CSS_DIR
, CUSTOMIZE_TITLE
);
376 resp_searchform(const struct req
*req
)
380 puts(CUSTOMIZE_BEGIN
);
381 puts("<!-- Begin search form. //-->");
382 printf("<DIV ID=\"mancgi\">\n"
383 "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
385 "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
388 /* Write query input box. */
390 printf( "<TABLE><TR><TD>\n"
391 "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
392 if (NULL
!= req
->q
.expr
)
393 html_print(req
->q
.expr
);
394 puts("\" SIZE=\"40\">");
396 /* Write submission and reset buttons. */
398 printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
399 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
401 /* Write show radio button */
403 printf( "</TD><TD>\n"
404 "<INPUT TYPE=\"radio\" ");
407 printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
408 "<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
410 /* Write section selector. */
412 printf( "</TD></TR><TR><TD>\n"
413 "<SELECT NAME=\"sec\">");
414 for (i
= 0; i
< sec_MAX
; i
++) {
415 printf("<OPTION VALUE=\"%s\"", sec_numbers
[i
]);
416 if (NULL
!= req
->q
.sec
&&
417 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
419 printf(">%s</OPTION>\n", sec_names
[i
]);
423 /* Write architecture selector. */
425 printf( "<SELECT NAME=\"arch\">\n"
426 "<OPTION VALUE=\"default\"");
427 if (NULL
== req
->q
.arch
)
429 puts(">All Architectures</OPTION>");
430 for (i
= 0; i
< arch_MAX
; i
++) {
431 printf("<OPTION VALUE=\"%s\"", arch_names
[i
]);
432 if (NULL
!= req
->q
.arch
&&
433 0 == strcmp(arch_names
[i
], req
->q
.arch
))
435 printf(">%s</OPTION>\n", arch_names
[i
]);
439 /* Write manpath selector. */
442 puts("<SELECT NAME=\"manpath\">");
443 for (i
= 0; i
< (int)req
->psz
; i
++) {
445 if (NULL
== req
->q
.manpath
? 0 == i
:
446 0 == strcmp(req
->q
.manpath
, req
->p
[i
]))
449 html_print(req
->p
[i
]);
451 html_print(req
->p
[i
]);
457 /* Write search radio button */
459 printf( "</TD><TD>\n"
460 "<INPUT TYPE=\"radio\" ");
461 if (0 == req
->q
.equal
)
463 printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
464 "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
466 puts("</TD></TR></TABLE>\n"
470 puts("<!-- End search form. //-->");
474 validate_urifrag(const char *frag
)
477 while ('\0' != *frag
) {
478 if ( ! (isalnum((unsigned char)*frag
) ||
479 '-' == *frag
|| '.' == *frag
||
480 '/' == *frag
|| '_' == *frag
))
488 validate_manpath(const struct req
*req
, const char* manpath
)
492 if ( ! strcmp(manpath
, "mandoc"))
495 for (i
= 0; i
< req
->psz
; i
++)
496 if ( ! strcmp(manpath
, req
->p
[i
]))
503 validate_filename(const char *file
)
506 if ('.' == file
[0] && '/' == file
[1])
509 return ( ! (strstr(file
, "../") || strstr(file
, "/..") ||
510 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3))));
514 pg_index(const struct req
*req
)
517 resp_begin_html(200, NULL
);
518 resp_searchform(req
);
520 "This web interface is documented in the "
521 "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A> "
523 "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A> "
524 "manual explains the query syntax.\n"
526 scriptname
, scriptname
);
531 pg_noresult(const struct req
*req
, const char *msg
)
533 resp_begin_html(200, NULL
);
534 resp_searchform(req
);
542 pg_error_badrequest(const char *msg
)
545 resp_begin_html(400, "Bad Request");
546 puts("<H1>Bad Request</H1>\n"
549 printf("Try again from the\n"
550 "<A HREF=\"%s\">main page</A>.\n"
556 pg_error_internal(void)
558 resp_begin_html(500, "Internal Server Error");
559 puts("<P>Internal Server Error</P>");
564 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
566 char *arch
, *archend
;
567 size_t i
, iuse
, isec
;
568 int archprio
, archpriouse
;
572 for (i
= 0; i
< sz
; i
++) {
573 if (validate_filename(r
[i
].file
))
575 fprintf(stderr
, "invalid filename %s in %s database\n",
576 r
[i
].file
, req
->q
.manpath
);
583 * If we have just one result, then jump there now
586 printf("Status: 303 See Other\r\n");
587 printf("Location: http://%s%s/%s/%s?",
588 HTTP_HOST
, scriptname
, req
->q
.manpath
, r
[0].file
);
589 http_printquery(req
);
591 "Content-Type: text/html; charset=utf-8\r\n"
596 qsort(r
, sz
, sizeof(struct manpage
), cmp
);
598 resp_begin_html(200, NULL
);
599 resp_searchform(req
);
600 puts("<DIV CLASS=\"results\">");
603 for (i
= 0; i
< sz
; i
++) {
605 "<TD CLASS=\"title\">\n"
606 "<A HREF=\"%s/%s/%s?",
607 scriptname
, req
->q
.manpath
, r
[i
].file
);
608 html_printquery(req
);
610 html_print(r
[i
].names
);
613 "<TD CLASS=\"desc\">");
614 html_print(r
[i
].output
);
623 * In man(1) mode, show one of the pages
624 * even if more than one is found.
632 for (i
= 0; i
< sz
; i
++) {
633 isec
= strcspn(r
[i
].file
, "123456789");
634 sec
= r
[i
].file
[isec
];
637 prio
= sec_prios
[sec
- '1'];
638 if (NULL
== req
->q
.arch
) {
640 (NULL
== (arch
= strchr(
641 r
[i
].file
+ isec
, '/'))) ? 3 :
642 (NULL
== (archend
= strchr(
643 arch
+ 1, '/'))) ? 0 :
644 strncmp(arch
, "amd64/",
645 archend
- arch
) ? 2 : 1;
646 if (archprio
< archpriouse
) {
647 archpriouse
= archprio
;
652 if (archprio
> archpriouse
)
660 resp_show(req
, r
[iuse
].file
);
667 catman(const struct req
*req
, const char *file
)
675 if (NULL
== (f
= fopen(file
, "r"))) {
676 puts("<P>You specified an invalid manual file.</P>");
680 puts("<DIV CLASS=\"catman\">\n"
683 while (NULL
!= (p
= fgetln(f
, &len
))) {
685 for (i
= 0; i
< (int)len
- 1; i
++) {
687 * This means that the catpage is out of state.
688 * Ignore it and keep going (although the
692 if ('\b' == p
[i
] || '\n' == p
[i
])
696 * Print a regular character.
697 * Close out any bold/italic scopes.
698 * If we're in back-space mode, make sure we'll
699 * have something to enter when we backspace.
702 if ('\b' != p
[i
+ 1]) {
710 } else if (i
+ 2 >= (int)len
)
728 * Handle funny behaviour troff-isms.
729 * These grok'd from the original man2html.c.
732 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
733 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
734 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
735 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
736 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
737 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
738 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
739 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
748 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
749 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
750 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
751 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
752 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
753 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
777 * Clean up the last character.
778 * We can get to a newline; don't print that.
786 if (i
== (int)len
- 1 && '\n' != p
[i
])
799 format(const struct req
*req
, const char *file
)
807 char opts
[PATH_MAX
+ 128];
809 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
810 puts("<P>You specified an invalid manual file.</P>");
814 mp
= mparse_alloc(MPARSE_SO
, MANDOCLEVEL_FATAL
, NULL
,
816 rc
= mparse_readfd(mp
, fd
, file
);
819 if (rc
>= MANDOCLEVEL_FATAL
) {
820 fprintf(stderr
, "fatal mandoc error: %s/%s\n",
821 req
->q
.manpath
, file
);
826 snprintf(opts
, sizeof(opts
), "fragment,man=%s?"
827 "manpath=%s&query=%%N&sec=%%S&arch=%s",
828 scriptname
, req
->q
.manpath
,
829 req
->q
.arch
? req
->q
.arch
: "");
831 mparse_result(mp
, &mdoc
, &man
, NULL
);
832 if (NULL
== man
&& NULL
== mdoc
) {
833 fprintf(stderr
, "fatal mandoc error: %s/%s\n",
834 req
->q
.manpath
, file
);
840 vp
= html_alloc(opts
);
852 resp_show(const struct req
*req
, const char *file
)
855 if ('.' == file
[0] && '/' == file
[1])
865 pg_show(struct req
*req
, const char *path
)
869 if (NULL
== path
|| NULL
== (sub
= strchr(path
, '/'))) {
871 "You did not specify a page to show.");
876 if ( ! validate_manpath(req
, path
)) {
878 "You specified an invalid manpath.");
883 * Begin by chdir()ing into the manpath.
884 * This way we can pick up the database files, which are
885 * relative to the manpath root.
888 if (-1 == chdir(path
)) {
889 fprintf(stderr
, "chdir %s: %s\n",
890 path
, strerror(errno
));
895 if ( ! validate_filename(sub
)) {
897 "You specified an invalid manual file.");
901 if (strcmp(path
, "mandoc"))
902 req
->q
.manpath
= path
;
904 resp_begin_html(200, NULL
);
905 resp_searchform(req
);
911 pg_search(const struct req
*req
)
913 struct mansearch search
;
914 struct manpaths paths
;
917 const char *ep
, *start
;
922 * Begin by chdir()ing into the root of the manpath.
923 * This way we can pick up the database files, which are
924 * relative to the manpath root.
927 if (-1 == (chdir(req
->q
.manpath
))) {
928 fprintf(stderr
, "chdir %s: %s\n",
929 req
->q
.manpath
, strerror(errno
));
934 search
.arch
= req
->q
.arch
;
935 search
.sec
= req
->q
.sec
;
936 search
.deftype
= req
->q
.equal
? TYPE_Nm
: (TYPE_Nm
| TYPE_Nd
);
937 search
.flags
= req
->q
.equal
? MANSEARCH_MAN
: 0;
940 paths
.paths
= mandoc_malloc(sizeof(char *));
941 paths
.paths
[0] = mandoc_strdup(".");
944 * Poor man's tokenisation: just break apart by spaces.
945 * Yes, this is half-ass. But it works for now.
949 while (ep
&& isspace((unsigned char)*ep
))
954 while (ep
&& '\0' != *ep
) {
955 cp
= mandoc_reallocarray(cp
, sz
+ 1, sizeof(char *));
957 while ('\0' != *ep
&& ! isspace((unsigned char)*ep
))
959 cp
[sz
] = mandoc_malloc((ep
- start
) + 1);
960 memcpy(cp
[sz
], start
, ep
- start
);
961 cp
[sz
++][ep
- start
] = '\0';
962 while (isspace((unsigned char)*ep
))
966 if (0 == mansearch(&search
, &paths
, sz
, cp
, "Nd", &res
, &ressz
))
967 pg_noresult(req
, "You entered an invalid query.");
969 pg_noresult(req
, "No results found.");
971 pg_searchres(req
, res
, ressz
);
973 for (i
= 0; i
< sz
; i
++)
977 for (i
= 0; i
< (int)ressz
; i
++) {
984 free(paths
.paths
[0]);
996 /* Scan our run-time environment. */
998 if (NULL
== (scriptname
= getenv("SCRIPT_NAME")))
1001 if ( ! validate_urifrag(scriptname
)) {
1002 fprintf(stderr
, "unsafe SCRIPT_NAME \"%s\"\n",
1004 pg_error_internal();
1005 return(EXIT_FAILURE
);
1009 * First we change directory into the MAN_DIR so that
1010 * subsequent scanning for manpath directories is rooted
1011 * relative to the same position.
1014 if (-1 == chdir(MAN_DIR
)) {
1015 fprintf(stderr
, "MAN_DIR: %s: %s\n",
1016 MAN_DIR
, strerror(errno
));
1017 pg_error_internal();
1018 return(EXIT_FAILURE
);
1021 memset(&req
, 0, sizeof(struct req
));
1024 /* Next parse out the query string. */
1026 if (NULL
!= (querystring
= getenv("QUERY_STRING")))
1027 http_parse(&req
, querystring
);
1029 if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1030 pg_error_badrequest(
1031 "You specified an invalid manpath.");
1032 return(EXIT_FAILURE
);
1035 if ( ! (NULL
== req
.q
.arch
|| validate_urifrag(req
.q
.arch
))) {
1036 pg_error_badrequest(
1037 "You specified an invalid architecture.");
1038 return(EXIT_FAILURE
);
1041 /* Dispatch to the three different pages. */
1043 path
= getenv("PATH_INFO");
1046 else if ('/' == *path
)
1050 pg_show(&req
, path
);
1051 else if (NULL
!= req
.q
.expr
)
1056 for (i
= 0; i
< (int)req
.psz
; i
++)
1059 return(EXIT_SUCCESS
);
1063 cmp(const void *p1
, const void *p2
)
1066 return(strcasecmp(((const struct manpage
*)p1
)->names
,
1067 ((const struct manpage
*)p2
)->names
));
1071 * Scan for indexable paths.
1074 pathgen(struct req
*req
)
1080 if (NULL
== (fp
= fopen("manpath.conf", "r"))) {
1081 fprintf(stderr
, "%s/manpath.conf: %s\n",
1082 MAN_DIR
, strerror(errno
));
1083 pg_error_internal();
1087 while (NULL
!= (dp
= fgetln(fp
, &dpsz
))) {
1088 if ('\n' == dp
[dpsz
- 1])
1090 req
->p
= mandoc_realloc(req
->p
,
1091 (req
->psz
+ 1) * sizeof(char *));
1092 dp
= mandoc_strndup(dp
, dpsz
);
1093 if ( ! validate_urifrag(dp
)) {
1094 fprintf(stderr
, "%s/manpath.conf contains "
1095 "unsafe path \"%s\"\n", MAN_DIR
, dp
);
1096 pg_error_internal();
1099 if (NULL
!= strchr(dp
, '/')) {
1100 fprintf(stderr
, "%s/manpath.conf contains "
1101 "path with slash \"%s\"\n", MAN_DIR
, dp
);
1102 pg_error_internal();
1105 req
->p
[req
->psz
++] = dp
;
1108 if ( req
->p
== NULL
) {
1109 fprintf(stderr
, "%s/manpath.conf is empty\n", MAN_DIR
);
1110 pg_error_internal();