HTML-OSM

 view release on metacpan or  search on metacpan

t/edge_cases.t  view on Meta::CPAN

	dies_ok { $m->add_geojson('{invalid json}') }
		'add_geojson("{invalid json}") dies';
};

subtest 'add_geojson: circular reference in data dies in onload_render' => sub {
	# encode_json will die when it encounters a cycle; the module does not
	# pre-validate GeoJSON structure.
	my $m = HTML::OSM->new();
	$m->center([$C{LAT_LONDON}, $C{LON_LONDON}]);
	my %circ;
	$circ{self} = \%circ;
	$m->add_geojson(\%circ);
	dies_ok { $m->onload_render() }
		'circular-reference GeoJSON dies in onload_render (encode_json rejects cycle)';
};

subtest 'add_geojson: return value is always 1' => sub {
	my $m = HTML::OSM->new();
	returns_ok(
		$m->add_geojson({ type => 'FeatureCollection', features => [] }),
		{ type => 'integer' }, 'returns integer');
	is($m->add_geojson({ type => 'FeatureCollection', features => [] }), 1,
		'return value is 1');
};

# ── Security: </script> injection via encode_json ──────────────────────────
# encode_json does NOT escape forward slashes by default, so embedding its
# output raw in a <script> block allows an attacker to close the block early.
# The module must escape </  →  <\/ in all JSON output destined for <script>.

subtest 'security: GeoJSON style with </script> is escaped in rendered output' => sub {
	my $m = HTML::OSM->new();
	$m->center([$C{LAT_LONDON}, $C{LON_LONDON}]);
	$m->add_geojson(
		{ type => 'FeatureCollection', features => [] },
		style => { color => $C{SCRIPT_CLOSE} },
	);
	my (undef, $body) = $m->onload_render();
	unlike($body, qr|</script><script>|,
		'raw </script><script> absent from rendered body (style injection blocked)');
	diag("style segment: " . substr($body, index($body, 'style:'), 120))
		if $ENV{TEST_VERBOSE};
};

subtest 'security: GeoJSON feature property with </script> is escaped in rendered output' => sub {
	my $m = HTML::OSM->new();
	$m->center([$C{LAT_LONDON}, $C{LON_LONDON}]);
	$m->add_geojson({
		type     => 'FeatureCollection',
		features => [{
			type       => 'Feature',
			properties => { name => $C{SCRIPT_CLOSE} },
			geometry   => { type => 'Point', coordinates => [0, 0] },
		}],
	});
	my (undef, $body) = $m->onload_render();
	unlike($body, qr|</script><script>|,
		'raw </script><script> absent when feature property contains injection');
};

subtest 'security: GeoJSON popup property name with </script> is escaped' => sub {
	my $m = HTML::OSM->new();
	$m->center([$C{LAT_LONDON}, $C{LON_LONDON}]);
	# The popup property name goes through _js_string — test that the data
	# path (the whole GeoJSON blob) is also safe.
	$m->add_geojson(
		{ type => 'FeatureCollection', features => [] },
		popup => 'name',
		style => { fillOpacity => 0.5 },
	);
	my (undef, $body) = $m->onload_render();
	unlike($body, qr|</script><script>|,
		'rendered output does not contain script-close injection');
};

# ─────────────────────────────────────────────────────────────────────────────
# 6. add_heatmap() — hostile inputs and XSS via points JSON
# ─────────────────────────────────────────────────────────────────────────────

subtest 'add_heatmap: undef points croaks' => sub {
	my $m = HTML::OSM->new();
	throws_ok { $m->add_heatmap(undef) }
		qr/add_heatmap: points must be an arrayref/,
		'add_heatmap(undef) croaks';
};

subtest 'add_heatmap: hashref points croaks' => sub {
	my $m = HTML::OSM->new();
	throws_ok { $m->add_heatmap({}) }
		qr/add_heatmap: points must be an arrayref/,
		'add_heatmap({}) croaks';
};

subtest 'add_heatmap: scalar string croaks' => sub {
	my $m = HTML::OSM->new();
	throws_ok { $m->add_heatmap('string') }
		qr/add_heatmap: points must be an arrayref/,
		'add_heatmap("string") croaks';
};

subtest 'add_heatmap: empty arrayref is accepted (renders empty layer)' => sub {
	my $m = HTML::OSM->new();
	is($m->add_heatmap([]), 1, 'empty arrayref → 1');
	$m->center([$C{LAT_LONDON}, $C{LON_LONDON}]);
	my (undef, $body) = $m->onload_render();
	like($body, qr/L\.heatLayer\(\[\]/, 'empty heatmap rendered as L.heatLayer([])');
};

subtest 'security: heatmap points with </script> in intensity escaped in output' => sub {
	# Intensity is a string — encode_json would normally embed it raw.
	my $m = HTML::OSM->new();
	$m->add_marker([$C{LAT_LONDON}, $C{LON_LONDON}]);
	$m->add_heatmap([[$C{LAT_LONDON}, $C{LON_LONDON}, $C{SCRIPT_CLOSE}]]);
	my (undef, $body) = $m->onload_render();
	unlike($body, qr|</script><script>|,
		'raw </script><script> absent when heatmap intensity contains injection');
};

subtest 'add_heatmap: return value is 1' => sub {
	my $m = HTML::OSM->new();
	returns_ok($m->add_heatmap([[51.5, -0.1]]), { type => 'integer' }, 'returns integer');
	is($m->add_heatmap([[51.5, -0.1]]), 1, 'returns 1');
};

# ─────────────────────────────────────────────────────────────────────────────
# 7. add_gpx() — hostile inputs
# ─────────────────────────────────────────────────────────────────────────────



( run in 0.757 second using v1.01-cache-2.11-cpan-364913b4093 )