toBe('hello world'); }); test('html_escape encodes angle brackets', function () { expect(html_escape(''))->toContain('<') ->and(html_escape(''))->toContain('>'); }); test('html_escape encodes double quotes', function () { expect(html_escape('say "hello"'))->toContain('"'); }); test('html_escape encodes single quotes', function () { $result = html_escape("it's"); // ENT_QUOTES | ENT_HTML5 produces ' expect($result)->toContain('''); }); test('html_escape replaces backtick with entity', function () { expect(html_escape('a`b'))->toContain('`'); }); test('html_escape returns empty string for empty input', function () { expect(html_escape(''))->toBe(''); }); test('html_escape preserves zero values', function () { expect(html_escape('0'))->toBe('0') ->and(html_escape(0))->toBe('0'); }); test('html_escape returns empty string for null input', function () { expect(html_escape(null))->toBe(''); }); test('html_escape does not double-encode existing entities', function () { expect(html_escape('&'))->toBe('&'); }); test('html_escape handles ampersand in plain text', function () { expect(html_escape('a&b'))->toBe('a&b'); }); test('html_escape_attr encodes attribute delimiters', function () { $result = html_escape_attr('" onmouseover="alert(1)'); expect($result)->toBe('" onmouseover="alert(1)'); }); test('html_escape_attr encodes backticks', function () { expect(html_escape_attr('` autofocus onfocus=alert(1)'))->toBe('` autofocus onfocus=alert(1)'); }); test('html_escape_attr double-encodes a pre-encoded entity', function () { // an attacker-supplied literal " must not survive into the attribute // and be decoded back into a quote by the browser; double_encode turns // the leading '&' of the existing entity into '&' so it stays inert. expect(html_escape_attr('" onmouseover="alert(1)'))->toBe('&quot; onmouseover="alert(1)'); }); test('html_attributes makes pre-encoded quotes inert', function () { expect(html_attributes(['value' => '' autofocus onfocus=alert(1)'])) ->toBe(" value='&apos; autofocus onfocus=alert(1)'"); }); test('html_escape_url encodes URL attribute delimiters', function () { expect(html_escape_url('https://example.invalid/?q=" onmouseover="alert(1)')) ->toBe('https://example.invalid/?q=" onmouseover="alert(1)'); }); test('cacti_js_encode escapes script-sensitive characters', function () { $result = cacti_js_encode(""); expect($result)->toContain('\u003C') ->and($result)->toContain('\u0027') ->and($result)->not->toContain(''); }); test('cacti_js_encode escapes U+2028 and U+2029 line terminators', function () { // left literal, these are valid JSON but JavaScript treats them as line // terminators, breaking parsing of a JSON literal embedded inline in ", ]); expect($result)->toContain("type='application/json'") ->and($result)->toContain("id='ctx" onmouseover="alert(1)'") ->and($result)->toContain('\u003C\/script\u003E') ->and($result)->not->toContain('"); }); test('html_text_input escapes value and additional attributes', function () { $result = html_text_input('rfilter', "a' autofocus", [ 'class' => 'ui-state-default', 'onChange' => 'applyFilter()', 'bad attr' => 'drop', ]); expect($result)->toContain("value='a' autofocus'") ->and($result)->toContain("onChange='applyFilter()'") ->and($result)->not->toContain("badattr='drop'"); }); test('html input helpers preserve zero values', function () { expect(html_hidden_input('selected_id', 0))->toContain("value='0'") ->and(html_text_input('rfilter', '0'))->toContain("value='0'"); }); test('html navigation encodes script values and confines callback identifiers', function () { $result = html_nav_bar( "graphs.php?filter=';&", 10, 2, 10, 100, 5, '', "page');alert(1);//", "main');alert(1);//" ); $label_result = html_nav_bar('graphs.php', 10, 1, 10, 5, 5, ''); expect($result)->toContain('function gotopage(pageNo)') ->and($result)->toContain('\\u003C\\/script\\u003E') ->and($result)->toContain('\\u0027') ->and($label_result)->toContain('<img src=x onerror=alert(1)>') ->and($result)->not->toContain('') ->and($result)->not->toContain("elementId: 'main');alert(1);//'"); }); // ===================================================================== // html_split_string tests // ===================================================================== test('html_split_string returns short string unchanged', function () { expect(html_split_string('short string', 90))->toBe('short string'); }); test('html_split_string breaks long string at word boundary', function () { $words = 'The quick brown fox jumps over the lazy dog and keeps running far away'; $result = html_split_string($words, 30, 10); expect($result)->toContain('
'); }); test('html_split_string respects forgiveness parameter', function () { // 40 chars no space, then a space, then more $str = str_repeat('a', 35) . ' ' . str_repeat('b', 20); $result = html_split_string($str, 40, 10); expect($result)->toContain('
'); }); test('html_split_string limits iterations to 5 maximum', function () { $long = str_repeat('word ', 200); $result = html_split_string($long, 20, 10); $breaks = substr_count($result, '
'); // loop runs j=0..4 (5 iterations), breaks when j>4, so up to 5
tags expect($breaks)->toBeLessThanOrEqual(5); });