95 if (!out)
return NULL;
96 for (
size_t i = 0; i < n; i++) {
97 unsigned char c = (
unsigned char)s[i];
98 out[i] = (char)((c >=
'A' && c <=
'Z') ? (c -
'A' +
'a') : c);
108 if (!out)
return NULL;
109 if (n > 0) memcpy(out, s, n);
117 size_t n = strlen(s);
120 if (!out)
return NULL;
121 for (
size_t i = 0; i < n; i++) {
122 unsigned char c = (
unsigned char)s[i];
123 out[i] = (char)((c >=
'a' && c <=
'z') ? (c -
'a' +
'A') : c);
132 unsigned char ca = (
unsigned char)*a;
133 unsigned char cb = (
unsigned char)*b;
134 if (ca >=
'A' && ca <=
'Z') ca = (
unsigned char)(ca -
'A' +
'a');
135 if (cb >=
'A' && cb <=
'Z') cb = (
unsigned char)(cb -
'A' +
'a');
136 if (ca != cb)
return 0;
140 return *a ==
'\0' && *b ==
'\0';
145 return (c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z') || (c >=
'0' && c <=
'9') || c ==
'-' || c ==
'_' || c ==
':' || c ==
'.';
150 if (!tag || !tag->
attrs)
return NULL;
164 if (i >= len)
return;
167 if (html[i] ==
'/') {
172 size_t name_start = i;
174 if (i == name_start) {
175 while (i < len && html[i] !=
'>') i++;
182 while (i < len && html[i] !=
'>') i++;
192 while (i < len && html[i] !=
'>') i++;
202 while (i < len && html[i] !=
'>') {
203 while (i < len && isspace((
unsigned char)html[i])) i++;
205 if (html[i] ==
'>')
break;
206 if (html[i] ==
'/') {
213 while (i < len && html[i] !=
'=' && html[i] !=
'>' && html[i] !=
'/' && !isspace((
unsigned char)html[i])) i++;
222 while (j < len && isspace((
unsigned char)html[j])) j++;
225 if (j < len && html[j] ==
'=') {
227 while (j < len && isspace((
unsigned char)html[j])) j++;
228 if (j < len && (html[j] ==
'"' || html[j] ==
'\'')) {
232 while (j < len && html[j] != q) j++;
237 while (j < len && html[j] !=
'>' && !isspace((
unsigned char)html[j])) j++;
259 if (i < len && html[i] ==
'>') i++;
268 size_t clen = strlen(close_tag);
270 while (j + clen <= len) {
271 if (html[j] ==
'<') {
273 for (
size_t k = 0; k < clen; k++) {
274 unsigned char a = (
unsigned char)html[j + k];
275 unsigned char b = (
unsigned char)close_tag[k];
276 if (a >=
'A' && a <=
'Z') a = (
unsigned char)(a -
'A' +
'a');
292 if (!tags)
return NULL;
296 if (html[i] !=
'<') {
303 if (i + 2 < len && html[i] ==
'!' && html[i + 1] ==
'-' && html[i + 2] ==
'-') {
305 while (i + 2 < len && !(html[i] ==
'-' && html[i + 1] ==
'-' && html[i + 2] ==
'>')) i++;
312 if (html[i] ==
'!') {
313 if (i + 7 < len && memcmp(html + i,
"![CDATA[", 8) == 0) {
315 while (i + 2 < len && !(html[i] ==
']' && html[i + 1] ==
']' && html[i + 2] ==
'>')) i++;
322 while (i < len && html[i] !=
'>') i++;
326 if (html[i] ==
'?') {
328 while (i + 1 < len && !(html[i] ==
'?' && html[i + 1] ==
'>')) i++;
339 if (tail && tail->
ptr) {
342 if (strcmp(t->
name,
"script") == 0) {
344 }
else if (strcmp(t->
name,
"style") == 0) {
355 if (!url || !*url)
return;
362 if (!out)
return NULL;
363 if (!html || len == 0)
return out;
366 if (!tags)
return out;
371 const char* attr = NULL;
372 if (strcmp(t->
name,
"a") == 0)
374 else if (strcmp(t->
name,
"area") == 0)
376 else if (strcmp(t->
name,
"form") == 0)
378 else if (strcmp(t->
name,
"img") == 0)
380 else if (strcmp(t->
name,
"script") == 0)
382 else if (strcmp(t->
name,
"link") == 0)
384 else if (strcmp(t->
name,
"iframe") == 0)
394 if (!out)
return NULL;
395 if (!html || len == 0)
return out;
398 if (!tags)
return out;
403 if (!t || !t->
name)
continue;
405 if (strcmp(t->
name,
"form") == 0) {
433 int is_field = (strcmp(t->
name,
"input") == 0 || strcmp(t->
name,
"select") == 0 || strcmp(t->
name,
"textarea") == 0 || strcmp(t->
name,
"button") == 0);
434 if (!is_field)
continue;
443 if (!ty_attr || !*ty_attr) ty_attr =
"text";
447 f->
required = (req_attr != NULL) ? 1 : 0;
458 if (!out)
return NULL;
459 if (!xml || len == 0)
return out;
464 const char* content = NULL;
465 while (i + 5 <= len) {
467 unsigned char c1 = (
unsigned char)xml[i + 1];
468 unsigned char c2 = (
unsigned char)xml[i + 2];
469 unsigned char c3 = (
unsigned char)xml[i + 3];
470 unsigned char c4 = (
unsigned char)xml[i + 4];
471 if ((c1 ==
'l' || c1 ==
'L') && (c2 ==
'o' || c2 ==
'O') && (c3 ==
'c' || c3 ==
'C') && (c4 ==
'>' || c4 ==
' ' || c4 ==
'\t' || c4 ==
'\r' || c4 ==
'\n' || c4 ==
'/')) {
473 while (i < len && xml[i] !=
'>') i++;
484 const char* close = NULL;
485 while (i + 6 <= len) {
486 if (xml[i] ==
'<' && xml[i + 1] ==
'/') {
487 unsigned char c1 = (
unsigned char)xml[i + 2];
488 unsigned char c2 = (
unsigned char)xml[i + 3];
489 unsigned char c3 = (
unsigned char)xml[i + 4];
490 if ((c1 ==
'l' || c1 ==
'L') && (c2 ==
'o' || c2 ==
'O') && (c3 ==
'c' || c3 ==
'C') && (xml[i + 5] ==
'>' || xml[i + 5] ==
' ' || xml[i + 5] ==
'\t' || xml[i + 5] ==
'\r' || xml[i + 5] ==
'\n')) {
499 const char* s = content;
500 const char* e = close;
501 while (s < e && isspace((
unsigned char)*s)) s++;
502 while (e > s && isspace((
unsigned char)*(e - 1))) e--;
504 size_t n = (size_t)(e - s);
513 while (i < len && xml[i] !=
'>') i++;
521 if (c >=
'A' && c <=
'Z')
return (
char)(c -
'A' +
'a');
527 if (cp == 0 || cp > 0x10FFFFUL)
return;
529 if (*o + 1 <= cap) buf[(*o)++] = (char)cp;
530 }
else if (cp < 0x800UL) {
532 buf[(*o)++] = (char)(0xC0UL | (cp >> 6));
533 buf[(*o)++] = (char)(0x80UL | (cp & 0x3FUL));
535 }
else if (cp < 0x10000UL) {
537 buf[(*o)++] = (char)(0xE0UL | (cp >> 12));
538 buf[(*o)++] = (char)(0x80UL | ((cp >> 6) & 0x3FUL));
539 buf[(*o)++] = (char)(0x80UL | (cp & 0x3FUL));
543 buf[(*o)++] = (char)(0xF0UL | (cp >> 18));
544 buf[(*o)++] = (char)(0x80UL | ((cp >> 12) & 0x3FUL));
545 buf[(*o)++] = (char)(0x80UL | ((cp >> 6) & 0x3FUL));
546 buf[(*o)++] = (char)(0x80UL | (cp & 0x3FUL));
553 while (*s && *o < cap) buf[(*o)++] = *s++;
598 static const char* para[] = {
"p",
"div",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"hr",
"table",
"ul",
"ol",
"blockquote",
"pre",
"section",
"article",
"header",
"footer",
"nav",
"aside",
"main",
"form",
"fieldset",
"dl",
"figure",
"figcaption",
"address",
"title",
"body", NULL};
599 static const char* line[] = {
"br",
"li",
"tr",
"dt",
"dd",
"option",
"caption",
"label",
"legend",
"thead",
"tbody",
"tfoot", NULL};
601 for (i = 0; para[i]; i++)
602 if (strcmp(name, para[i]) == 0)
return 3;
603 for (i = 0; line[i]; i++)
604 if (strcmp(name, line[i]) == 0)
return 2;
605 if (strcmp(name,
"td") == 0 || strcmp(name,
"th") == 0)
return 1;
611static void _n_html_flush(
char* buf,
size_t cap,
size_t* o,
int* pending_nl,
int* pending_sp,
int* line_content) {
612 if (*pending_nl > 0) {
614 int n = (*pending_nl > 2) ? 2 : *pending_nl;
616 for (t = 0; t < n; t++)
617 if (*o < cap) buf[(*o)++] =
'\n';
621 }
else if (*pending_sp && *line_content) {
622 if (*o < cap) buf[(*o)++] =
' ';
629 size_t cap, o = 0, i = 0;
630 int pending_nl = 0, pending_sp = 0, line_content = 0;
633 if (!html)
return NULL;
635 Malloc(buf,
char, cap + 1);
637 n_log(
LOG_ERR,
"n_html_to_text: out of memory for %zu bytes", cap + 1);
645 if (i + 3 < len && html[i + 1] ==
'!' && html[i + 2] ==
'-' && html[i + 3] ==
'-') {
647 while (i + 2 < len && !(html[i] ==
'-' && html[i + 1] ==
'-' && html[i + 2] ==
'>'))
649 i = (i + 3 <= len) ? i + 3 : len;
653 if (i + 1 < len && html[i + 1] ==
'!') {
654 while (i < len && html[i] !=
'>') i++;
664 if (j < len && html[j] ==
'/') {
668 while (j < len && nlen <
sizeof(name) - 1) {
670 if ((tc >=
'a' && tc <=
'z') || (tc >=
'A' && tc <=
'Z') || (tc >=
'0' && tc <=
'9')) {
679 int is_script = !closing && strcmp(name,
"script") == 0;
680 int is_style = !closing && strcmp(name,
"style") == 0;
681 if (is_script || is_style) {
682 const char* endtag = is_script ?
"</script" :
"</style";
683 size_t tlen = strlen(endtag);
684 while (j < len && html[j] !=
'>') j++;
687 if (j + tlen <= len) {
689 while (k < tlen &&
_n_html_lc(html[j + k]) == endtag[k]) k++;
692 while (j < len && html[j] !=
'>') j++;
699 if (pending_nl < 2) pending_nl = 2;
707 if (pending_nl < 2) pending_nl = 2;
709 }
else if (bk == 2) {
710 if (pending_nl < 1) pending_nl = 1;
712 }
else if (bk == 1) {
713 if (line_content) pending_sp = 1;
717 while (j < len && html[j] !=
'>') j++;
722 }
else if (c ==
'&') {
725 if (j < len && html[j] ==
'#') {
727 unsigned long cp = 0;
729 int hex = 0, digits = 0;
731 if (j < len && (html[j] ==
'x' || html[j] ==
'X')) {
736 while (j < len && html[j] !=
';' && (j - start) < 8) {
739 if (d >=
'0' && d <=
'9')
740 cp = cp * 16UL + (
unsigned long)(d -
'0');
741 else if (d >=
'a' && d <=
'f')
742 cp = cp * 16UL + (
unsigned long)(d -
'a' + 10);
743 else if (d >=
'A' && d <=
'F')
744 cp = cp * 16UL + (
unsigned long)(d -
'A' + 10);
748 if (d >=
'0' && d <=
'9')
749 cp = cp * 10UL + (
unsigned long)(d -
'0');
756 if (digits > 0 && j < len && html[j] ==
';') {
757 _n_html_flush(buf, cap, &o, &pending_nl, &pending_sp, &line_content);
768 while (k < len && en <
sizeof(ename) - 1) {
770 if ((d >=
'a' && d <=
'z') || (d >=
'A' && d <=
'Z') || (d >=
'0' && d <=
'9')) {
778 if (en > 0 && k < len && html[k] ==
';') {
782 _n_html_flush(buf, cap, &o, &pending_nl, &pending_sp, &line_content);
793 _n_html_flush(buf, cap, &o, &pending_nl, &pending_sp, &line_content);
794 if (o < cap) buf[o++] =
'&';
799 }
else if (c ==
' ' || c ==
'\t' || c ==
'\r' || c ==
'\n' || c ==
'\f' || c ==
'\v') {
800 if (line_content) pending_sp = 1;
804 _n_html_flush(buf, cap, &o, &pending_nl, &pending_sp, &line_content);
805 if (o < cap) buf[o++] = c;
820static const char*
_n_html_find(
const char* hay,
size_t haylen,
const char* needle,
size_t nlen) {
822 if (!hay || !needle || nlen == 0 || nlen > haylen)
824 for (i = 0; i + nlen <= haylen; i++)
825 if (memcmp(hay + i, needle, nlen) == 0)
831static const char*
_n_html_find_ci(
const char* hay,
size_t haylen,
const char* needle,
size_t nlen) {
833 if (!hay || !needle || nlen == 0 || nlen > haylen)
835 for (i = 0; i + nlen <= haylen; i++) {
836 for (j = 0; j < nlen; j++)
837 if (tolower((
unsigned char)hay[i + j]) != needle[j])
850 for (i = 0; i < plen; i++)
851 if (tolower((
unsigned char)s[i]) != pre[i])
869 (s[0] ==
'/' && s[1] ==
'/') ||
871 (s[0] ==
'.' && s[1] ==
'/') ||
872 (n >= 3 && s[0] ==
'.' && s[1] ==
'.' && s[2] ==
'/'))
876 for (i = 0; i < n; i++) {
877 unsigned char c = (
unsigned char)s[i];
878 if (c <=
' ' || c ==
'"' || c ==
'\'' || c ==
'`' || c ==
'<' || c ==
'>' ||
879 c ==
'{' || c ==
'}' || c ==
'|' || c ==
'^' || c ==
'\\' || c == 127)
892 if (!tok || len == 0)
897 use = (size_t)(tmpl - tok);
902 if (e && e->
data && strlen(e->
data) == use && strncmp(e->
data, tok, use) == 0)
905 buf = malloc(use + 1);
908 for (i = 0; i < use; i++)
918 if (!out)
return NULL;
919 if (!js || len == 0)
return out;
920 for (i = 0; i < len; i++) {
923 if (q !=
'"' && q !=
'\'' && q !=
'`')
926 for (j = start; j < len; j++) {
934 if (j <= len && j > start)
936 i = (j < len) ? j : len;
944 if (!out)
return NULL;
945 if (!html || len == 0)
return out;
948 size_t tag_start, body_start, k;
953 tag_start = (size_t)(open - html);
955 body_start = tag_start + 7;
956 while (body_start < len && html[body_start] !=
'>')
958 if (body_start >= len)
961 if (
_n_html_find_ci(html + tag_start, body_start - tag_start,
"src", 3) != NULL) {
966 close =
_n_html_find_ci(html + body_start, len - body_start,
"</script", 8);
967 k = close ? (size_t)(close - html) : len;
968 if (k > body_start) {
974 i = close ? k + 8 : len;
980 if (!links || !*links)
return;
985 if (!forms || !*forms)
return;
#define FreeNoLog(__ptr)
Free Handler without log.
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
#define Free(__ptr)
Free Handler to get errors.
LIST_NODE * end
pointer to the end of the list
void * ptr
void pointer to store
int list_push(LIST *list, void *ptr, void(*destructor)(void *ptr))
Add a pointer to the end of the list.
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper, safe for node removal during iteration.
int list_destroy(LIST **list)
Empty and Free a list container.
LIST * new_generic_list(size_t max_items)
Initialiaze a generic list container to max_items pointers.
#define MAX_LIST_ITEMS
flag to pass to new_generic_list for the maximum possible number of item in a list
Structure of a generic LIST container.
Structure of a generic list node.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
void free_nstr_ptr(void *ptr)
Free a N_STR pointer structure.
N_STR * char_to_nstr(const char *src)
Convert a char into a N_STR, short version.
int char_to_nstr_ex(const char *from, NSTRBYTE nboct, N_STR **to)
Convert a char into a N_STR, extended version.
A box including a string and his lenght.
Common headers and low-level functions & define.
static int _n_html_ci_eq(const char *a, const char *b)
static int _n_html_starts_ci(const char *s, size_t n, const char *pre, size_t plen)
static char * _n_html_strndup(const char *s, size_t n)
static int _n_html_is_name_char(int c)
static void _n_html_push_js_url(LIST *out, const char *tok, size_t len)
void n_html_links_free(LIST **links)
free a list returned by n_html_extract_links or n_sitemap_extract_urls
char * value
raw value, "" when the attribute has no '='
static void _n_html_push_link(LIST *out, const char *url)
LIST * n_html_extract_js_urls(const char *js, size_t len)
extract URL/path tokens from JavaScript source (quoted string literals that look like an http(s) URL,...
static void _n_html_put_str(char *buf, size_t cap, size_t *o, const char *s)
static void _n_html_put_cp(char *buf, size_t cap, size_t *o, unsigned long cp)
static const char * _n_html_find_ci(const char *hay, size_t haylen, const char *needle, size_t nlen)
static char _n_html_lc(char c)
LIST * n_sitemap_extract_urls(const char *xml, size_t len)
extract <loc> URLs from a sitemap.xml as a LIST of N_STR*; free with n_html_links_free
char * name
lowercased attribute name
LIST * n_html_extract_scripts(const char *html, size_t len)
extract the inline <script> bodies (those without a src attribute) from an HTML document as a LIST of...
static void _n_html_flush(char *buf, size_t cap, size_t *o, int *pending_nl, int *pending_sp, int *line_content)
N_STR * n_html_to_text(const char *html, size_t len)
render HTML to readable plain text: drop tags, skip script/style, decode common entities,...
static size_t _n_html_skip_rawtext(const char *html, size_t len, size_t start, const char *close_tag)
static const struct @0 _n_html_entities[]
static char * _n_html_strndup_lower(const char *s, size_t n)
static int _n_html_block_kind(const char *name)
static void _n_html_form_free(void *p)
void n_html_forms_free(LIST **forms)
free a list returned by n_html_extract_forms
static const char * _n_html_attr_lookup(const N_HTML_TAG_ *tag, const char *name)
static void _n_html_tag_free(void *p)
static const char * _n_html_find(const char *hay, size_t haylen, const char *needle, size_t nlen)
LIST * attrs
attributes (only populated for open tags)
static LIST * _n_html_scan_tags(const char *html, size_t len)
char * name
lowercased tag name
static int _n_html_js_url_like(const char *s, size_t n)
static void _n_html_parse_tag(const char *html, size_t len, size_t *pos, LIST *out)
LIST * n_html_extract_links(const char *html, size_t len)
extract link URLs (a/href, form/action, img/src, script/src, link/href, iframe/src) as a LIST of N_ST...
static char * _n_html_strdup_upper(const char *s)
int self_closing
1 if the tag ended with '/>'
LIST * n_html_extract_forms(const char *html, size_t len)
extract forms as a LIST of N_HTML_FORM*; free with n_html_forms_free
static void _n_html_attr_free(void *p)
static void _n_form_field_free(void *p)
int closing
1 if the tag started with '/'
attribute parsed off an HTML start tag (name lowercased, value raw)
a single tag picked out by the scanner
Lightweight HTML/XML extraction: links, forms, and sitemap URLs.
char * action
action attribute (may be "" for self)
LIST * fields
list of N_FORM_FIELD*
char * value
default value attribute, or ""
char * type
field type (lowercased), default "text"
char * name
field name attribute, or ""
int required
1 when the required attribute is present
char * enctype
lower-case enctype, default "application/x-www-form-urlencoded"
char * method
upper-case method, default "GET"
a single form field (input, select, textarea, button)
a parsed HTML form with its fields
List structures and definitions.
N_STR and string function declaration.