code wiki / (root) / nx_html_extract_imgs.nx

nx_html_extract_imgs.nx source

↩ module page · 130 lines · 5672 B

1// nx_html_extract_imgs.nx -- walk a parsed HTML document, find every 2// <img src="..."> element, resolve the src URL against a base URL, 3// and emit absolute URLs into caller-supplied buffers. 4// 5// Arc B2 of NISHI_BROWSER_GOOGLE_IMAGE_SEARCH_ROADMAP. Built on top 6// of the just-shipped Arc B1 raw-text mode so <script> bodies 7// containing "<img" tokens-inside-strings don't pollute results. 8// 9// API: 10// nx_html_extract_imgs(html, html_len, 11// base_url, base_url_len, 12// url_buf, url_buf_cap, 13// offsets, lengths, max_imgs) 14// -> count of imgs emitted (or negative error) 15// 16// `offsets` and `lengths` are caller-supplied parallel i64 arrays 17// of length >= max_imgs. Each (offsets[k], lengths[k]) describes 18// where the k-th absolute URL lives within url_buf. 19// 20// If url_buf fills up before max_imgs is reached, we stop emitting 21// further URLs and return the count we did emit (truncation is 22// substrate-honest: caller sees how many imgs they got). 23// 24// nx_safety_envelope: 25// intended_use: "Image-source enumeration for the bits-up 26// browser; feeds the image fetch + decode arc." 27// sil_target: SIL1 28// evidence: [composes_b1_raw_text_mode, 29// composes_rfc3986_url_resolver] 30// hazard_register: [bug-tape-href-injection-bypassing-quoting, 31// bug-tape-base-href-attack-via-page-content] 32// residual_risk: "<base href> in document body NOT honored 33// in this revision -- caller passes base URL 34// explicitly. Adding <base href> support is 35// Arc B3 once we audit its attack surface." 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls.nx" 39import "nx_html_tokenizer.nx" 40import "nx_dom_query.nx" 41import "nx_url_resolve.nx" 42 43const NX_IMG_EXTRACT_OK_BASE: i64 = 0 44const NX_IMG_EXTRACT_BAD_BASE: i64 = -1 45 46// case-insensitive equality on a 3-byte tag name "img" / "IMG" / etc. 47func _img_name_eq_img(src: *u8, name_off: i64, name_len: i64) -> i64 { 48 if name_len != 3 { return 0 } 49 let c0: i64 = src[name_off] 50 let c1: i64 = src[name_off + 1] 51 let c2: i64 = src[name_off + 2] 52 var lc0: i64 = c0 53 var lc1: i64 = c1 54 var lc2: i64 = c2 55 if lc0 >= 65 { if lc0 <= 90 { lc0 = lc0 + 32 } } 56 if lc1 >= 65 { if lc1 <= 90 { lc1 = lc1 + 32 } } 57 if lc2 >= 65 { if lc2 <= 90 { lc2 = lc2 + 32 } } 58 if lc0 == 0x69 { if lc1 == 0x6D { if lc2 == 0x67 { return 1 } } } 59 return 0 60} 61 62func nx_html_extract_imgs(html: *u8, html_len: i64, 63 base_url: *u8, base_url_len: i64, 64 url_buf: *u8, url_buf_cap: i64, 65 offsets: *i64, lengths: *i64, 66 max_imgs: i64) -> i64 { 67 let c: *HtmlCursor = sys_mmap(24) as *HtmlCursor 68 nx_html_cursor_init(c, html, html_len) 69 let tok: *HtmlToken = sys_mmap(56) as *HtmlToken 70 let src_attr: *u8 = "src" as *u8 71 let val_off_p: *i64 = sys_mmap(8) as *i64 72 let val_len_p: *i64 = sys_mmap(8) as *i64 73 let out_len_p: *i64 = sys_mmap(8) as *i64 74 75 var buf_pos: i64 = 0 76 var count: i64 = 0 77 78 while count < max_imgs { 79 nx_html_next_token(c, tok) 80 if tok.kind == NX_HTML_TOK_EOF { return count } 81 82 // Raw-text-mode tags: skip their bodies so script/style/textarea/title 83 // bodies containing "<img" never get tokenized as tags. 84 if tok.kind == NX_HTML_TOK_START_TAG { 85 if nx_html_is_raw_text_tag(html, tok.name_off, tok.name_len) == 1 { 86 let tag_name_off: i64 = tok.name_off 87 let tag_name_len: i64 = tok.name_len 88 nx_html_consume_raw_text(c, html + tag_name_off, tag_name_len, tok) 89 // tok is now the TEXT body; the next iteration's 90 // nx_html_next_token will see the close tag and yield END_TAG. 91 } 92 } 93 94 // <img> is in the HTML5 void-element set, so it surfaces as 95 // SELF_CLOSING (the tokenizer auto-promotes per Task #81). 96 // We accept both kinds to be lenient with future tokenizer 97 // changes. 98 var is_img_tag: i64 = 0 99 if tok.kind == NX_HTML_TOK_SELF_CLOSING { is_img_tag = 1 } 100 if tok.kind == NX_HTML_TOK_START_TAG { is_img_tag = 1 } 101 102 if is_img_tag == 1 { 103 if _img_name_eq_img(html, tok.name_off, tok.name_len) == 1 { 104 let has: i64 = nx_dom_find_attr(html, tok.src_off, tok.src_len, 105 src_attr, val_off_p, val_len_p) 106 if has == 1 { 107 let rel_off: i64 = val_off_p[0] 108 let rel_len: i64 = val_len_p[0] 109 if rel_len > 0 { 110 let remaining: i64 = url_buf_cap - buf_pos 111 let rc: i64 = nx_url_resolve( 112 base_url, base_url_len, 113 html + rel_off, rel_len, 114 url_buf + buf_pos, remaining, 115 out_len_p) 116 if rc == NX_URL_RESOLVE_OK { 117 offsets[count] = buf_pos 118 lengths[count] = out_len_p[0] 119 buf_pos = buf_pos + out_len_p[0] 120 count = count + 1 121 } 122 // On TRUNC or BAD_BASE: skip this img (substrate-honest: 123 // returning early would lie about the number found). 124 } 125 } 126 } 127 } 128 } 129 return count 130}