Fix CSP issues

This commit is contained in:
Pieter Vander Vennet 2024-02-20 02:01:08 +01:00
parent 56b83cfa36
commit 06c2e2fec8
7 changed files with 227 additions and 147 deletions

View file

@ -279,6 +279,7 @@ async function generateCsp(
"https://www.openstreetmap.org", "https://www.openstreetmap.org",
"https://api.openstreetmap.org", "https://api.openstreetmap.org",
"https://pietervdvn.goatcounter.com", "https://pietervdvn.goatcounter.com",
"https://cache.mapcomplete.org",
].concat(...(await eliUrls())) ].concat(...(await eliUrls()))
SpecialVisualizations.specialVisualizations.forEach((sv) => { SpecialVisualizations.specialVisualizations.forEach((sv) => {
@ -289,7 +290,13 @@ async function generateCsp(
apiUrls.push(...(sv.needsUrls ?? [])) apiUrls.push(...(sv.needsUrls ?? []))
}) })
const usedSpecialVisualisations = [].concat(...layoutJson.layers.map(l => ValidationUtils.getAllSpecialVisualisations(<QuestionableTagRenderingConfigJson[]> (<LayerConfigJson>l).tagRenderings ?? []))) const usedSpecialVisualisations = [].concat(
...layoutJson.layers.map((l) =>
ValidationUtils.getAllSpecialVisualisations(
<QuestionableTagRenderingConfigJson[]>(<LayerConfigJson>l).tagRenderings ?? []
)
)
)
for (const usedSpecialVisualisation of usedSpecialVisualisations) { for (const usedSpecialVisualisation of usedSpecialVisualisations) {
if (typeof usedSpecialVisualisation === "string") { if (typeof usedSpecialVisualisation === "string") {
continue continue
@ -297,7 +304,7 @@ async function generateCsp(
const neededUrls = usedSpecialVisualisation.func.needsUrls ?? [] const neededUrls = usedSpecialVisualisation.func.needsUrls ?? []
if (typeof neededUrls === "function") { if (typeof neededUrls === "function") {
let needed: string | string[] = neededUrls(usedSpecialVisualisation.args) let needed: string | string[] = neededUrls(usedSpecialVisualisation.args)
if(typeof needed === "string"){ if (typeof needed === "string") {
needed = [needed] needed = [needed]
} }
apiUrls.push(...needed) apiUrls.push(...needed)
@ -317,8 +324,8 @@ async function generateCsp(
continue continue
} }
try { try {
if(!connectSource.startsWith("http")){ if (!connectSource.startsWith("http")) {
connectSource = "https://"+connectSource connectSource = "https://" + connectSource
} }
const url = new URL(connectSource) const url = new URL(connectSource)
hosts.add("https://" + url.host) hosts.add("https://" + url.host)
@ -349,7 +356,7 @@ async function generateCsp(
"default-src": "'self'", "default-src": "'self'",
"child-src": "'self' blob: ", "child-src": "'self' blob: ",
"img-src": "* data:", // maplibre depends on 'data:' to load "img-src": "* data:", // maplibre depends on 'data:' to load
"connect-src": "'self' "+connectSrc.join(" "), "connect-src": "'self' " + connectSrc.join(" "),
"report-to": "https://report.mapcomplete.org/csp", "report-to": "https://report.mapcomplete.org/csp",
"worker-src": "'self' blob:", // Vite somehow loads the worker via a 'blob' "worker-src": "'self' blob:", // Vite somehow loads the worker via a 'blob'
"style-src": "'self' 'unsafe-inline'", // unsafe-inline is needed to change the default background pin colours "style-src": "'self' 'unsafe-inline'", // unsafe-inline is needed to change the default background pin colours

View file

@ -16,9 +16,9 @@ npm run test &&
npm run prepare-deploy && npm run prepare-deploy &&
zip dist.zip -r dist/* && zip dist.zip -r dist/* &&
mv config.json.bu config.json && mv config.json.bu config.json &&
scp ./scripts/hetzner/config/* hetzner:/root/ && scp ./Docs/ServerConfig/hetzner/* hetzner:/root/ &&
rsync -rzh --progress dist.zip hetzner:/root/ && rsync -rzh --progress dist.zip hetzner:/root/ &&
echo "Upload completed, deploying config and booting" && echo "Upload completed, deploying config and booting" &&
ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" && ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" &&
rm dist.zip # rm dist.zip
npm run clean npm run clean

View file

@ -1,9 +1,9 @@
import { Feature, Geometry } from "geojson" import { Geometry } from "geojson"
import { Feature as GeojsonFeature } from "geojson"
import { Store, UIEventSource } from "../../UIEventSource" import { Store, UIEventSource } from "../../UIEventSource"
import { FeatureSourceForTile } from "../FeatureSource" import { FeatureSourceForTile } from "../FeatureSource"
import Pbf from "pbf" import Pbf from "pbf"
import * as pbfCompile from "pbf/compile"
import * as PbfSchema from "protocol-buffers-schema"
type Coords = [number, number][] type Coords = [number, number][]
@ -60,12 +60,11 @@ class MvtFeatureBuilder {
} }
const ccw = area < 0 const ccw = area < 0
if (ccw === (area < 0)) { if (ccw === area < 0) {
if (currentPolygon) { if (currentPolygon) {
polygons.push(currentPolygon) polygons.push(currentPolygon)
} }
currentPolygon = [ring] currentPolygon = [ring]
} else { } else {
currentPolygon.push(ring) currentPolygon.push(ring)
} }
@ -77,7 +76,7 @@ class MvtFeatureBuilder {
return polygons return polygons
} }
public toGeoJson(geometry: number[], typeIndex: 1 | 2 | 3, properties: any): Feature { public toGeoJson(geometry: number[], typeIndex: 1 | 2 | 3, properties: any): GeojsonFeature {
let coords: Coords[] = this.encodeGeometry(geometry) let coords: Coords[] = this.encodeGeometry(geometry)
let classified = undefined let classified = undefined
switch (typeIndex) { switch (typeIndex) {
@ -159,9 +158,9 @@ class MvtFeatureBuilder {
if (commandId === 1 || commandId === 2) { if (commandId === 1 || commandId === 2) {
for (let j = 0; j < commandCount; j++) { for (let j = 0; j < commandCount; j++) {
const dx = geometry[i + j * 2 + 1] const dx = geometry[i + j * 2 + 1]
cX += ((dx >> 1) ^ (-(dx & 1))) cX += (dx >> 1) ^ -(dx & 1)
const dy = geometry[i + j * 2 + 2] const dy = geometry[i + j * 2 + 2]
cY += ((dy >> 1) ^ (-(dy & 1))) cY += (dy >> 1) ^ -(dy & 1)
currentRing.push([cX, cY]) currentRing.push([cX, cY])
} }
i += commandCount * 2 i += commandCount * 2
@ -170,7 +169,6 @@ class MvtFeatureBuilder {
currentRing.push([...currentRing[0]]) currentRing.push([...currentRing[0]])
i++ i++
} }
} }
if (currentRing.length > 0) { if (currentRing.length > 0) {
coordss.push(currentRing) coordss.push(currentRing)
@ -189,132 +187,182 @@ class MvtFeatureBuilder {
const size = this._size const size = this._size
for (let i = 0; i < line.length; i++) { for (let i = 0; i < line.length; i++) {
let p = line[i] let p = line[i]
let y2 = 180 - (p[1] + y0) * 360 / size let y2 = 180 - ((p[1] + y0) * 360) / size
line[i] = [ line[i] = [
(p[0] + x0) * 360 / size - 180, ((p[0] + x0) * 360) / size - 180,
360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90, (360 / Math.PI) * Math.atan(Math.exp((y2 * Math.PI) / 180)) - 90,
] ]
} }
return line return line
} }
} }
export default class MvtSource implements FeatureSourceForTile { class Layer {
public static read(pbf, end) {
private static readonly schemaSpec21 = ` return pbf.readFields(
package vector_tile; Layer._readField,
{ version: 0, name: "", features: [], keys: [], values: [], extent: 0 },
option optimize_for = LITE_RUNTIME; end
)
message Tile {
// GeomType is described in section 4.3.4 of the specification
enum GeomType {
UNKNOWN = 0;
POINT = 1;
LINESTRING = 2;
POLYGON = 3;
} }
static _readField(tag, obj, pbf) {
// Variant type encoding if (tag === 15) obj.version = pbf.readVarint()
// The use of values is described in section 4.1 of the specification else if (tag === 1) obj.name = pbf.readString()
message Value { else if (tag === 2) obj.features.push(Feature.read(pbf, pbf.readVarint() + pbf.pos))
// Exactly one of these values must be present in a valid message else if (tag === 3) obj.keys.push(pbf.readString())
optional string string_value = 1; else if (tag === 4) obj.values.push(Value.read(pbf, pbf.readVarint() + pbf.pos))
optional float float_value = 2; else if (tag === 5) obj.extent = pbf.readVarint()
optional double double_value = 3;
optional int64 int_value = 4;
optional uint64 uint_value = 5;
optional sint64 sint_value = 6;
optional bool bool_value = 7;
extensions 8 to max;
} }
public static write(obj, pbf) {
// Features are described in section 4.2 of the specification if (obj.version) pbf.writeVarintField(15, obj.version)
message Feature { if (obj.name) pbf.writeStringField(1, obj.name)
optional uint64 id = 1 [ default = 0 ]; if (obj.features)
for (var i = 0; i < obj.features.length; i++)
// Tags of this feature are encoded as repeated pairs of pbf.writeMessage(2, Feature.write, obj.features[i])
// integers. if (obj.keys) for (i = 0; i < obj.keys.length; i++) pbf.writeStringField(3, obj.keys[i])
// A detailed description of tags is located in sections if (obj.values)
// 4.2 and 4.4 of the specification for (i = 0; i < obj.values.length; i++) pbf.writeMessage(4, Value.write, obj.values[i])
repeated uint32 tags = 2 [ packed = true ]; if (obj.extent) pbf.writeVarintField(5, obj.extent)
// The type of geometry stored in this feature.
optional GeomType type = 3 [ default = UNKNOWN ];
// Contains a stream of commands and parameters (vertices).
// A detailed description on geometry encoding is located in
// section 4.3 of the specification.
repeated uint32 geometry = 4 [ packed = true ];
} }
// Layers are described in section 4.1 of the specification
message Layer {
// Any compliant implementation must first read the version
// number encoded in this message and choose the correct
// implementation for this version number before proceeding to
// decode other parts of this message.
required uint32 version = 15 [ default = 1 ];
required string name = 1;
// The actual features in this tile.
repeated Feature features = 2;
// Dictionary encoding for keys
repeated string keys = 3;
// Dictionary encoding for values
repeated Value values = 4;
// Although this is an "optional" field it is required by the specification.
// See https://github.com/mapbox/vector-tile-spec/issues/47
optional uint32 extent = 5 [ default = 4096 ];
extensions 16 to max;
}
repeated Layer layers = 3;
extensions 16 to 8191;
} }
`
private static readonly tile_schema = (pbfCompile.default ?? pbfCompile)(PbfSchema.parse(MvtSource.schemaSpec21)).Tile class Feature {
public readonly features: Store<Feature<Geometry, { [name: string]: any }>[]> static read(pbf, end) {
return pbf.readFields(Feature._readField, { id: 0, tags: [], type: 0, geometry: [] }, end)
}
static _readField(tag, obj, pbf) {
if (tag === 1) obj.id = pbf.readVarint()
else if (tag === 2) pbf.readPackedVarint(obj.tags)
else if (tag === 3) obj.type = pbf.readVarint()
else if (tag === 4) pbf.readPackedVarint(obj.geometry)
}
public static write(obj, pbf) {
if (obj.id) pbf.writeVarintField(1, obj.id)
if (obj.tags) pbf.writePackedVarint(2, obj.tags)
if (obj.type) pbf.writeVarintField(3, obj.type)
if (obj.geometry) pbf.writePackedVarint(4, obj.geometry)
}
}
class Value {
public static read(pbf, end) {
return pbf.readFields(
Value._readField,
{
string_value: "",
float_value: 0,
double_value: 0,
int_value: 0,
uint_value: 0,
sint_value: 0,
bool_value: false,
},
end
)
}
static _readField = function (tag, obj, pbf) {
if (tag === 1) obj.string_value = pbf.readString()
else if (tag === 2) obj.float_value = pbf.readFloat()
else if (tag === 3) obj.double_value = pbf.readDouble()
else if (tag === 4) obj.int_value = pbf.readVarint(true)
else if (tag === 5) obj.uint_value = pbf.readVarint()
else if (tag === 6) obj.sint_value = pbf.readSVarint()
else if (tag === 7) obj.bool_value = pbf.readBoolean()
}
public static write(obj, pbf) {
if (obj.string_value) pbf.writeStringField(1, obj.string_value)
if (obj.float_value) pbf.writeFloatField(2, obj.float_value)
if (obj.double_value) pbf.writeDoubleField(3, obj.double_value)
if (obj.int_value) pbf.writeVarintField(4, obj.int_value)
if (obj.uint_value) pbf.writeVarintField(5, obj.uint_value)
if (obj.sint_value) pbf.writeSVarintField(6, obj.sint_value)
if (obj.bool_value) pbf.writeBooleanField(7, obj.bool_value)
}
}
class Tile {
// code generated by pbf v3.2.1
public static read(pbf, end) {
return pbf.readFields(Tile._readField, { layers: [] }, end)
}
static _readField(tag, obj, pbf) {
if (tag === 3) obj.layers.push(Layer.read(pbf, pbf.readVarint() + pbf.pos))
}
static write(obj, pbf) {
if (obj.layers)
for (var i = 0; i < obj.layers.length; i++)
pbf.writeMessage(3, Layer.write, obj.layers[i])
}
static GeomType = {
UNKNOWN: {
value: 0,
options: {},
},
POINT: {
value: 1,
options: {},
},
LINESTRING: {
value: 2,
options: {},
},
POLYGON: {
value: 3,
options: {},
},
}
}
export default class MvtSource implements FeatureSourceForTile {
public readonly features: Store<GeojsonFeature<Geometry, { [name: string]: any }>[]>
private readonly _url: string private readonly _url: string
private readonly _layerName: string private readonly _layerName: string
private readonly _features: UIEventSource<Feature<Geometry, { private readonly _features: UIEventSource<
GeojsonFeature<
Geometry,
{
[name: string]: any [name: string]: any
}>[]> = new UIEventSource<Feature<Geometry, { [p: string]: any }>[]>([]) }
>[]
> = new UIEventSource<GeojsonFeature<Geometry, { [p: string]: any }>[]>([])
public readonly x: number public readonly x: number
public readonly y: number public readonly y: number
public readonly z: number public readonly z: number
constructor(url: string, x: number, y: number, z: number, layerName?: string, isActive?: Store<boolean>) { constructor(
url: string,
x: number,
y: number,
z: number,
layerName?: string,
isActive?: Store<boolean>
) {
this._url = url this._url = url
this._layerName = layerName this._layerName = layerName
this.x = x this.x = x
this.y = y this.y = y
this.z = z this.z = z
this.downloadSync() this.downloadSync()
this.features = this._features.map(fs => { this.features = this._features.map(
(fs) => {
if (fs === undefined || isActive?.data === false) { if (fs === undefined || isActive?.data === false) {
return [] return []
} }
return fs return fs
}, [isActive]) },
[isActive]
)
} }
private getValue(v: { private getValue(v: {
// Exactly one of these values must be present in a valid message // Exactly one of these values must be present in a valid message
string_value?: string, string_value?: string
float_value?: number, float_value?: number
double_value?: number, double_value?: number
int_value?: number, int_value?: number
uint_value?: number, uint_value?: number
sint_value?: number, sint_value?: number
bool_value?: boolean bool_value?: boolean
}): string | number | undefined | boolean { }): string | number | undefined | boolean {
if (v.string_value !== "") { if (v.string_value !== "") {
@ -339,41 +387,42 @@ message Tile {
return v.bool_value return v.bool_value
} }
return undefined return undefined
} }
private downloadSync() { private downloadSync() {
this.download().then(d => { this.download()
.then((d) => {
if (d.length === 0) { if (d.length === 0) {
return return
} }
return this._features.setData(d) return this._features.setData(d)
}).catch(e => { })
.catch((e) => {
console.error(e) console.error(e)
}) })
} }
private async download(): Promise<Feature[]> { private async download(): Promise<GeojsonFeature[]> {
const result = await fetch(this._url) const result = await fetch(this._url)
if (result.status !== 200) { if (result.status !== 200) {
console.error("Could not download tile " + this._url) console.error("Could not download tile " + this._url)
return [] return []
} }
const buffer = await result.arrayBuffer() const buffer = await result.arrayBuffer()
const data = MvtSource.tile_schema.read(new Pbf(buffer)) const data = Tile.read(new Pbf(buffer), undefined)
const layers = data.layers const layers = data.layers
let layer = data.layers[0] let layer = data.layers[0]
if (layers.length > 1) { if (layers.length > 1) {
if (!this._layerName) { if (!this._layerName) {
throw "Multiple layers in the downloaded tile, but no layername is given to choose from" throw "Multiple layers in the downloaded tile, but no layername is given to choose from"
} }
layer = layers.find(l => l.name === this._layerName) layer = layers.find((l) => l.name === this._layerName)
} }
if (!layer) { if (!layer) {
return [] return []
} }
const builder = new MvtFeatureBuilder(layer.extent, this.x, this.y, this.z) const builder = new MvtFeatureBuilder(layer.extent, this.x, this.y, this.z)
const features: Feature[] = [] const features: GeojsonFeature[] = []
for (const feature of layer.features) { for (const feature of layer.features) {
const properties = this.inflateProperties(feature.tags, layer.keys, layer.values) const properties = this.inflateProperties(feature.tags, layer.keys, layer.values)
@ -383,7 +432,6 @@ message Tile {
return features return features
} }
private inflateProperties(tags: number[], keys: string[], values: { string_value: string }[]) { private inflateProperties(tags: number[], keys: string[], values: { string_value: string }[]) {
const properties = {} const properties = {}
for (let i = 0; i < tags.length; i += 2) { for (let i = 0; i < tags.length; i += 2) {
@ -407,5 +455,4 @@ message Tile {
return properties return properties
} }
} }

View file

@ -1,5 +1,8 @@
<script lang="ts"> <script lang="ts">
</script> </script>
No tests No tests

File diff suppressed because one or more lines are too long

View file

@ -4,6 +4,8 @@ import ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte"
import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig";
import MetaTagging from "./src/Logic/MetaTagging"; import MetaTagging from "./src/Logic/MetaTagging";
import { FixedUiElement } from "./src/UI/Base/FixedUiElement"; import { FixedUiElement } from "./src/UI/Base/FixedUiElement";
import { Utils } from "./src/Utils"
import Constants from "./src/Models/Constants"
function webgl_support() { function webgl_support() {
try { try {
@ -16,13 +18,27 @@ function webgl_support() {
return false return false
} }
} }
async function getAvailableLayers(): Promise<Set<string>> {
try {
const host = new URL(Constants.VectorTileServer).host
const status = await Utils.downloadJson("https://" + host + "/summary/status.json")
return new Set<string>(status.layers)
} catch (e) {
console.error("Could not get MVT available layers due to", e)
return new Set<string>()
}
}
if (!webgl_support()) { async function main() {
if (!webgl_support()) {
new FixedUiElement("WebGL is not supported or not enabled. This is essential for MapComplete to function, please enable this.").SetClass("block alert").AttachTo("maindiv") new FixedUiElement("WebGL is not supported or not enabled. This is essential for MapComplete to function, please enable this.").SetClass("block alert").AttachTo("maindiv")
}else{ }else{
const availableLayers = await getAvailableLayers()
MetaTagging.setThemeMetatagging(new ThemeMetaTagging()) MetaTagging.setThemeMetatagging(new ThemeMetaTagging())
// LAYOUT.ADD_LAYERS // LAYOUT.ADD_LAYERS
const state = new ThemeViewState(new LayoutConfig(<any> layout)) const state = new ThemeViewState(new LayoutConfig(<any> layout), availableLayers)
const main = new SvelteUIElement(ThemeViewGUI, { state }) const main = new SvelteUIElement(ThemeViewGUI, { state })
main.AttachTo("maindiv") main.AttachTo("maindiv")
}
} }
main()

View file

@ -1,4 +1,7 @@
import SvelteUIElement from "./UI/Base/SvelteUIElement" import SvelteUIElement from "./UI/Base/SvelteUIElement"
import Test from "./UI/Test.svelte" import Test from "./UI/Test.svelte"
import MvtSource from "./Logic/FeatureSource/Sources/MvtSource"
new MvtSource("https://example.org", undefined, undefined, undefined)
new SvelteUIElement(Test, {}).AttachTo("maindiv") new SvelteUIElement(Test, {}).AttachTo("maindiv")