n_html regression: link, form, and sitemap extraction.
n_html regression: link, form, and sitemap extraction.
#include <stdio.h>
#include <string.h>
int opt = 0;
while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
switch (opt) {
case 'V':
if (!strncmp("LOG_NULL", optarg, 8))
else if (!strncmp("LOG_NOTICE", optarg, 10))
else if (!strncmp("LOG_INFO", optarg, 8))
else if (!strncmp("LOG_ERR", optarg, 7))
else if (!strncmp("LOG_DEBUG", optarg, 9))
else {
fprintf(stderr, "Unknown log level %s\n", optarg);
exit(1);
}
break;
case 'v':
fprintf(stderr, "ex_html\n");
exit(1);
case 'h':
default:
fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
break;
}
}
}
if (!links) return 0;
if (s && s->data && strcmp(s->data, want) == 0) return 1;
}
return 0;
}
} else {
}
}
}
}
const char* html =
"<!DOCTYPE html>\n"
"<html><head>\n"
" <link rel=\"stylesheet\" HREF=\"/css/main.css\">\n"
" <script src='/js/app.js'></script>\n"
" <!-- a comment with <a href=\"/in/comment\">trap</a> -->\n"
"</head><body>\n"
" <a href=\"/about\">about</a>\n"
" <a HREF=/contact>contact</a>\n"
" <a href = \"/help\" target=_blank>help</a>\n"
" <a>no href</a>\n"
" <form action=\"/login\" method=POST><input name=u></form>\n"
" <img src=\"/img/logo.png\"/>\n"
" <iframe src=\"//cdn.example.com/frame\"></iframe>\n"
" <area shape=rect href=\"/map/area1\"/>\n"
" <script>var x = '<a href=\"/in/script\">trap</a>';</script>\n"
" <style>a[href=\"/in/style\"] { color: red; }</style>\n"
"</body></html>";
size_t len = strlen(html);
if (!links) {
return;
}
if (links) {
n_log(
LOG_ERR,
"n_html_links_free did not NULL the pointer");
}
}
const char* html =
"<form action=\"/signup\" method=\"post\">"
" <input type='text' name='user' value=\"alice\" required>"
" <input type=password name=\"pass\">"
" <textarea name=bio></textarea>"
" <select name=country><option>fr</option></select>"
" <button type=submit name=go value=1>Go</button>"
"</form>"
"<form>"
" <input name=q>"
"</form>";
size_t len = strlen(html);
return;
}
if (!f0->method || strcmp(f0->method, "POST") != 0) {
n_log(
LOG_ERR,
"form0 method: got '%s' expected 'POST'", f0->method ? f0->method :
"(null)");
}
if (!f0->action || strcmp(f0->action, "/signup") != 0) {
n_log(
LOG_ERR,
"form0 action: got '%s'", f0->action ? f0->action :
"(null)");
}
if (!f0->fields || f0->fields->nb_items != 5) {
n_log(
LOG_ERR,
"form0 fields: got %zu expected 5", f0->fields ? f0->fields->nb_items : (size_t)0);
} else {
if (strcmp(user->name, "user") != 0 || strcmp(user->type, "text") != 0 || strcmp(user->value, "alice") != 0 || user->required != 1) {
n_log(
LOG_ERR,
"form0 field0 wrong: name=%s type=%s value=%s required=%d", user->name, user->type, user->value, user->required);
}
fn = fn->next;
if (strcmp(pass->name, "pass") != 0 || strcmp(pass->type, "password") != 0 || pass->required != 0) {
n_log(
LOG_ERR,
"form0 field1 wrong: name=%s type=%s required=%d", pass->name, pass->type, pass->required);
}
fn = fn->next;
if (strcmp(bio->name, "bio") != 0 || strcmp(bio->type, "text") != 0) {
n_log(
LOG_ERR,
"form0 field2 wrong: name=%s type=%s", bio->name, bio->type);
}
}
if (!f1->method || strcmp(f1->method, "GET") != 0) {
n_log(
LOG_ERR,
"form1 method: got '%s' expected 'GET'", f1->method ? f1->method :
"(null)");
}
if (!f1->action || strcmp(f1->action, "") != 0) {
n_log(
LOG_ERR,
"form1 action: got '%s' expected ''", f1->action ? f1->action :
"(null)");
}
if (!f1->fields || f1->fields->nb_items != 1) {
n_log(
LOG_ERR,
"form1 fields: got %zu expected 1", f1->fields ? f1->fields->nb_items : (size_t)0);
}
if (forms) {
n_log(
LOG_ERR,
"n_html_forms_free did not NULL the pointer");
}
}
const char* xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">"
" <url><loc>https://example.com/a</loc><lastmod>2026-01-01</lastmod></url>"
" <url>\n <LOC>\n https://example.com/b\n </LOC>\n</url>"
" <url><loc>https://example.com/c</loc></url>"
"</urlset>";
size_t len = strlen(xml);
return;
}
const char* want[3] = {"https://example.com/a", "https://example.com/b", "https://example.com/c"};
for (int i = 0; i < 3; i++) {
if (!s || !s->data || strcmp(s->data, want[i]) != 0) {
n_log(
LOG_ERR,
"sitemap url %d: got '%s' expected '%s'", i, s && s->data ? s->data :
"(null)", want[i]);
}
}
}
}
const char* html =
"<form action=\"/up\" method=\"post\" enctype=\"multipart/form-data\"><input name=f></form>"
"<form action=\"/x\"><input name=g></form>";
return;
}
{
if (!f0->enctype || strcmp(f0->enctype, "multipart/form-data") != 0) {
n_log(
LOG_ERR,
"form0 enctype: got '%s'", f0->enctype ? f0->enctype :
"(null)");
}
if (!f1->enctype || strcmp(f1->enctype, "application/x-www-form-urlencoded") != 0) {
n_log(
LOG_ERR,
"form1 default enctype: got '%s'", f1->enctype ? f1->enctype :
"(null)");
}
}
}
static void expect_text(
const char* label,
N_STR* txt,
const char* needle,
int want_present) {
int present = (txt && txt->
data && strstr(txt->
data, needle) != NULL);
if (present != want_present) {
n_log(
LOG_ERR,
"to_text %s: '%s' should be %s in '%s'", label, needle, want_present ?
"present" :
"absent", (txt && txt->data) ? txt->data :
"(null)");
}
}
{
const char* html = "<html><body><h1>Title</h1><p>Hello <b>world</b></p></body></html>";
}
{
const char* html = "<p>a & b <c> dABe "q" n m</p>";
expect_text(
"entities", t,
"a & b <c> dABe \"q\" n m", 1);
}
{
const char* html = "<p>x</p><script>var a = 1 < 2;</script><style>.c{color:red}</style><p>y</p>";
}
{
const char* html = "<p> a b\n\t c </p>";
if (!t || !t->
data || strcmp(t->
data,
"a b c") != 0) {
}
}
{
const char* html = "<p>a<!-- secret comment -->b</p>";
}
{
}
}
size_t n = 0;
if (!l) return 0;
(void)node;
n++;
}
return n;
}
const char* js =
"const API = '/api/v1';\n"
"fetch(\"/api/v1/users?id=1\").then(r => r.json());\n"
"axios.get('https://api.example.com/orders');\n"
"const u = `/api/v1/item/${id}/detail`;\n"
"xhr.open('GET', '//cdn.example.com/data.json');\n"
"const rel = './partials/menu.html';\n"
"const mime = 'text/html';\n"
"const re = '/^\\\\d+$/';\n"
"const cls = 'btn btn-primary';\n";
if (!urls) {
return;
}
{
}
}
const char* html =
"<html><head>\n"
"<script src='/js/app.js'></script>\n"
"<script>const a = '/inline/one';</script>\n"
"<SCRIPT type='text/javascript'>fetch('/inline/two');</SCRIPT>\n"
"</head><body>text</body></html>";
if (!scripts) {
return;
}
}
}
}
int main(
int argc,
char** argv) {
return 1;
}
return 0;
}
void process_args(int argc, char **argv)
static size_t list_count(LIST *l)
static void test_scripts(void)
static void test_js_urls(void)
static void test_empty_and_null(void)
static void test_enctype(void)
static void test_to_text(void)
static void expect_link_present(LIST *links, const char *want)
static void test_links(void)
static void expect_link_absent(LIST *links, const char *probe)
static void test_forms(void)
static void expect_text(const char *label, N_STR *txt, const char *needle, int want_present)
static void test_sitemap(void)
static int find_link(LIST *links, const char *want)
void * ptr
void pointer to store
LIST_NODE * start
pointer to the start of the list
size_t nb_items
number of item currently in the list
struct LIST_NODE * next
pointer to the next node
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper, safe for node removal during iteration.
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_DEBUG
debug-level messages
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
#define LOG_NOTICE
normal but significant condition
#define LOG_NULL
no log output
#define LOG_INFO
informational
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
#define nstrprintf_cat(__nstr_var, __format,...)
Macro to quickly allocate and sprintf and cat to a N_STR.
N_STR * new_nstr(NSTRBYTE size)
create a new N_STR string
A box including a string and his lenght.
Common headers and low-level functions & define.
void n_html_links_free(LIST **links)
free a list returned by n_html_extract_links or n_sitemap_extract_urls
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,...
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
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...
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,...
void n_html_forms_free(LIST **forms)
free a list returned by n_html_extract_forms
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...
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
Lightweight HTML/XML extraction: links, forms, and sitemap URLs.
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.