Path: blob/dev/pkg/protocols/headless/operators_test.go
2846 views
package headless12import (3"testing"45"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"6"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"7"github.com/stretchr/testify/require"8)910func TestRequest_ExtractXPath(t *testing.T) {11request := &Request{}1213// Test HTML content extraction14htmlContent := `<!doctype html>15<html>16<head>17<title>Test Page</title>18</head>19<body>20<div class="container">21<h1>Welcome</h1>22<p>This is a test page</p>23<a href="https://example.com" id="test-link">Click here</a>24<ul>25<li>Item 1</li>26<li>Item 2</li>27<li>Item 3</li>28</ul>29</div>30</body>31</html>`3233data := map[string]interface{}{34"data": htmlContent,35}3637// Test extracting text content38extractor := &extractors.Extractor{39Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},40XPath: []string{"/html/body/div/h1"},41}42err := extractor.CompileExtractors()43require.Nil(t, err)4445result := request.Extract(data, extractor)46expected := map[string]struct{}{"Welcome": {}}47require.Equal(t, expected, result)4849// Test extracting attribute value50extractor = &extractors.Extractor{51Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},52XPath: []string{"/html/body/div/a"},53Attribute: "href",54}55err = extractor.CompileExtractors()56require.Nil(t, err)5758result = request.Extract(data, extractor)59expected = map[string]struct{}{"https://example.com": {}}60require.Equal(t, expected, result)6162// Test extracting multiple items63extractor = &extractors.Extractor{64Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},65XPath: []string{"/html/body/div/ul/li"},66}67err = extractor.CompileExtractors()68require.Nil(t, err)6970result = request.Extract(data, extractor)71expected = map[string]struct{}{72"Item 1": {},73"Item 2": {},74"Item 3": {},75}76require.Equal(t, expected, result)7778// Test with non-existent XPath79extractor = &extractors.Extractor{80Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},81XPath: []string{"/html/body/div/nonexistent"},82}83err = extractor.CompileExtractors()84require.Nil(t, err)8586result = request.Extract(data, extractor)87require.Equal(t, map[string]struct{}{}, result)88}8990func TestRequest_ExtractJSON(t *testing.T) {91request := &Request{}9293// Test JSON content extraction94jsonContent := `{95"users": [96{"id": 1, "name": "John", "email": "[email protected]"},97{"id": 2, "name": "Jane", "email": "[email protected]"},98{"id": 3, "name": "Bob", "email": "[email protected]"}99],100"metadata": {101"total": 3,102"page": 1103}104}`105106data := map[string]interface{}{107"data": jsonContent,108}109110// Test extracting user IDs111extractor := &extractors.Extractor{112Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},113JSON: []string{".users[].id"},114}115err := extractor.CompileExtractors()116require.Nil(t, err)117118result := request.Extract(data, extractor)119expected := map[string]struct{}{120"1": {},121"2": {},122"3": {},123}124require.Equal(t, expected, result)125126// Test extracting user names127extractor = &extractors.Extractor{128Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},129JSON: []string{".users[].name"},130}131err = extractor.CompileExtractors()132require.Nil(t, err)133134result = request.Extract(data, extractor)135expected = map[string]struct{}{136"John": {},137"Jane": {},138"Bob": {},139}140require.Equal(t, expected, result)141142// Test extracting nested values143extractor = &extractors.Extractor{144Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},145JSON: []string{".metadata.total"},146}147err = extractor.CompileExtractors()148require.Nil(t, err)149150result = request.Extract(data, extractor)151expected = map[string]struct{}{"3": {}}152require.Equal(t, expected, result)153154// Test extracting emails155extractor = &extractors.Extractor{156Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},157JSON: []string{".users[].email"},158}159err = extractor.CompileExtractors()160require.Nil(t, err)161162result = request.Extract(data, extractor)163expected = map[string]struct{}{164"[email protected]": {},165"[email protected]": {},166"[email protected]": {},167}168require.Equal(t, expected, result)169170// Test with invalid JSON171invalidJSON := `{"invalid": json}`172data = map[string]interface{}{173"data": invalidJSON,174}175176extractor = &extractors.Extractor{177Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},178JSON: []string{".invalid"},179}180err = extractor.CompileExtractors()181require.Nil(t, err)182183result = request.Extract(data, extractor)184require.Equal(t, map[string]struct{}{}, result)185186// Test with non-existent path187extractor = &extractors.Extractor{188Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},189JSON: []string{".nonexistent"},190}191err = extractor.CompileExtractors()192require.Nil(t, err)193194result = request.Extract(data, extractor)195require.Equal(t, map[string]struct{}{}, result)196}197198func TestRequest_MatchXPath(t *testing.T) {199request := &Request{}200201htmlContent := `<!doctype html>202<html>203<head>204<title>Test Page</title>205</head>206<body>207<div class="container">208<h1>Welcome</h1>209<p>This is a test page</p>210<a href="https://example.com" id="test-link">Click here</a>211</div>212</body>213</html>`214215data := map[string]interface{}{216"data": htmlContent,217}218219// Test XPath matcher with existing element220matcher := &matchers.Matcher{221Type: matchers.MatcherTypeHolder{MatcherType: matchers.XPathMatcher},222XPath: []string{"/html/body/div/h1"},223Condition: "and",224}225err := matcher.CompileMatchers()226require.Nil(t, err)227228matched, snippets := request.Match(data, matcher)229require.True(t, matched)230require.Empty(t, snippets)231232// Test XPath matcher with non-existent element233matcher = &matchers.Matcher{234Type: matchers.MatcherTypeHolder{MatcherType: matchers.XPathMatcher},235XPath: []string{"/html/body/div/nonexistent"},236Condition: "and",237}238err = matcher.CompileMatchers()239require.Nil(t, err)240241matched, snippets = request.Match(data, matcher)242require.False(t, matched)243require.Empty(t, snippets)244}245246func TestRequest_getMatchPart(t *testing.T) {247request := &Request{}248249data := map[string]interface{}{250"data": "body content",251"header": "header content",252"history": "history content",253}254255// Test default part (should map to "data")256part, ok := request.getMatchPart("", data)257require.True(t, ok)258require.Equal(t, "body content", part)259260// Test "body" part (should map to "data")261part, ok = request.getMatchPart("body", data)262require.True(t, ok)263require.Equal(t, "body content", part)264265// Test "resp" part (should map to "data")266part, ok = request.getMatchPart("resp", data)267require.True(t, ok)268require.Equal(t, "body content", part)269270// Test "header" part271part, ok = request.getMatchPart("header", data)272require.True(t, ok)273require.Equal(t, "header content", part)274275// Test "history" part276part, ok = request.getMatchPart("history", data)277require.True(t, ok)278require.Equal(t, "history content", part)279280// Test non-existent part281part, ok = request.getMatchPart("nonexistent", data)282require.False(t, ok)283require.Equal(t, "", part)284}285286func TestRequest_ExtractWithDifferentParts(t *testing.T) {287request := &Request{}288289// Test extracting from different parts290htmlContent := `<!doctype html><html><body><div><h1>Title</h1></div></body></html>`291jsonContent := `{"id": 123}`292293data := map[string]interface{}{294"data": htmlContent,295"header": jsonContent,296"history": htmlContent,297}298299// Test XPath extractor from "data" part300extractor := &extractors.Extractor{301Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},302XPath: []string{"/html/body/div/h1"},303Part: "data",304}305err := extractor.CompileExtractors()306require.Nil(t, err)307308result := request.Extract(data, extractor)309expected := map[string]struct{}{"Title": {}}310require.Equal(t, expected, result)311312// Test JSON extractor from "header" part313extractor = &extractors.Extractor{314Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},315JSON: []string{".id"},316Part: "header",317}318err = extractor.CompileExtractors()319require.Nil(t, err)320321result = request.Extract(data, extractor)322expected = map[string]struct{}{"123": {}}323require.Equal(t, expected, result)324325// Test XPath extractor from "history" part326extractor = &extractors.Extractor{327Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},328XPath: []string{"/html/body/div/h1"},329Part: "history",330}331err = extractor.CompileExtractors()332require.Nil(t, err)333334result = request.Extract(data, extractor)335expected = map[string]struct{}{"Title": {}}336require.Equal(t, expected, result)337}338339func TestRequest_ExtractWithComplexJSON(t *testing.T) {340request := &Request{}341342// Test with complex nested JSON structure343jsonContent := `{344"api": {345"version": "1.0",346"endpoints": [347{348"path": "/users",349"method": "GET",350"responses": [351{"code": 200, "description": "Success"},352{"code": 404, "description": "Not Found"}353]354},355{356"path": "/posts",357"method": "POST",358"responses": [359{"code": 201, "description": "Created"},360{"code": 400, "description": "Bad Request"}361]362}363]364}365}`366367data := map[string]interface{}{368"data": jsonContent,369}370371// Test extracting API version372extractor := &extractors.Extractor{373Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},374JSON: []string{".api.version"},375}376err := extractor.CompileExtractors()377require.Nil(t, err)378379result := request.Extract(data, extractor)380expected := map[string]struct{}{"1.0": {}}381require.Equal(t, expected, result)382383// Test extracting all endpoint paths384extractor = &extractors.Extractor{385Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},386JSON: []string{".api.endpoints[].path"},387}388err = extractor.CompileExtractors()389require.Nil(t, err)390391result = request.Extract(data, extractor)392expected = map[string]struct{}{393"/users": {},394"/posts": {},395}396require.Equal(t, expected, result)397398// Test extracting all response codes399extractor = &extractors.Extractor{400Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},401JSON: []string{".api.endpoints[].responses[].code"},402}403err = extractor.CompileExtractors()404require.Nil(t, err)405406result = request.Extract(data, extractor)407expected = map[string]struct{}{408"200": {},409"404": {},410"201": {},411"400": {},412}413require.Equal(t, expected, result)414415// Test extracting response descriptions416extractor = &extractors.Extractor{417Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},418JSON: []string{".api.endpoints[].responses[].description"},419}420err = extractor.CompileExtractors()421require.Nil(t, err)422423result = request.Extract(data, extractor)424expected = map[string]struct{}{425"Success": {},426"Not Found": {},427"Created": {},428"Bad Request": {},429}430require.Equal(t, expected, result)431}432433func TestRequest_ExtractWithComplexHTML(t *testing.T) {434request := &Request{}435436// Test with complex HTML structure437htmlContent := `<!doctype html>438<html>439<head>440<title>E-commerce Site</title>441<meta name="description" content="Online shopping platform">442</head>443<body>444<header>445<nav>446<ul class="nav-menu">447<li><a href="/home">Home</a></li>448<li><a href="/products">Products</a></li>449<li><a href="/about">About</a></li>450</ul>451</nav>452</header>453<main>454<section class="products">455<h2>Featured Products</h2>456<div class="product-grid">457<div class="product" data-id="1">458<h3>Laptop</h3>459<p class="price">$999</p>460<span class="rating">4.5</span>461</div>462<div class="product" data-id="2">463<h3>Phone</h3>464<p class="price">$599</p>465<span class="rating">4.2</span>466</div>467<div class="product" data-id="3">468<h3>Tablet</h3>469<p class="price">$399</p>470<span class="rating">4.0</span>471</div>472</div>473</section>474</main>475<footer>476<p>© 2024 E-commerce Site</p>477</footer>478</body>479</html>`480481data := map[string]interface{}{482"data": htmlContent,483}484485// Test extracting navigation links486extractor := &extractors.Extractor{487Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},488XPath: []string{"/html/body/header/nav/ul/li/a"},489}490err := extractor.CompileExtractors()491require.Nil(t, err)492493result := request.Extract(data, extractor)494expected := map[string]struct{}{495"Home": {},496"Products": {},497"About": {},498}499require.Equal(t, expected, result)500501// Test extracting product names502extractor = &extractors.Extractor{503Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},504XPath: []string{"/html/body/main/section/div/div/h3"},505}506err = extractor.CompileExtractors()507require.Nil(t, err)508509result = request.Extract(data, extractor)510expected = map[string]struct{}{511"Laptop": {},512"Phone": {},513"Tablet": {},514}515require.Equal(t, expected, result)516517// Test extracting product prices518extractor = &extractors.Extractor{519Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},520XPath: []string{"/html/body/main/section/div/div/p[@class='price']"},521}522err = extractor.CompileExtractors()523require.Nil(t, err)524525result = request.Extract(data, extractor)526expected = map[string]struct{}{527"$999": {},528"$599": {},529"$399": {},530}531require.Equal(t, expected, result)532533// Test extracting product ratings534extractor = &extractors.Extractor{535Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},536XPath: []string{"/html/body/main/section/div/div/span[@class='rating']"},537}538err = extractor.CompileExtractors()539require.Nil(t, err)540541result = request.Extract(data, extractor)542expected = map[string]struct{}{543"4.5": {},544"4.2": {},545"4.0": {},546}547require.Equal(t, expected, result)548549// Test extracting data attributes550extractor = &extractors.Extractor{551Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},552XPath: []string{"/html/body/main/section/div/div[@class='product']"},553Attribute: "data-id",554}555err = extractor.CompileExtractors()556require.Nil(t, err)557558result = request.Extract(data, extractor)559expected = map[string]struct{}{560"1": {},561"2": {},562"3": {},563}564require.Equal(t, expected, result)565}566567568