VYPR
Moderate severityNVD Advisory· Published Aug 9, 2024· Updated Aug 9, 2024

CometVisu Backend for openHAB has a sensitive information disclosure vulnerability

CVE-2024-42470

Description

openHAB, a provider of open-source home automation software, has add-ons including the visualization add-on CometVisu. Several endpoints in versions prior to 4.2.1 of the CometVisu add-on of openHAB don't require authentication. This makes it possible for unauthenticated attackers to modify or to steal sensitive data. This issue may lead to sensitive information disclosure. Users should upgrade to version 4.2.1 of the CometVisu add-on of openHAB to receive a patch.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Unauthenticated endpoints in openHAB's CometVisu add-on allow attackers to modify or steal sensitive data; upgrade to version 4.2.1 to patch.

Vulnerability

Overview

The CometVisu visualization add-on for openHAB, an open-source home automation platform, fails to enforce authentication on several REST endpoints in versions prior to 4.2.1 [2]. This missing authentication controls allow any unauthenticated network attacker to interact with these endpoints without valid credentials. The root cause lies in the backend implementation, as demonstrated by a commit that added security fixes and cleanup for the CometVisu component [3].

Attack

Scenario and Exploitation

Because the vulnerable endpoints do not require any form of authentication, an attacker who can reach the openHAB server over the network can directly send requests to those endpoints [2]. The attack surface is broadened by the fact that CometVisu endpoints may handle sensitive data and configuration settings. No prior authentication or special privileges are needed; the attacker only needs network access to the openHAB instance where the CometVisu add-on is installed [1][4].

Impact and

Consequences

Successful exploitation allows an unauthenticated attacker to modify or steal sensitive data managed by the CometVisu add-on [2]. This includes potential disclosure of private home automation configurations, device states, and other user-specific information. The advisory from openHAB confirms that this issue can lead to sensitive information disclosure [4]. Such data could be used for further attacks against the smart home environment or to compromise user privacy.

Mitigation

Status

The vulnerability has been addressed in version 4.2.1 of the CometVisu add-on for openHAB [2][4]. Users are strongly advised to upgrade to this patched version. No workarounds are mentioned in the available references; the recommended and only mitigation is to update the add-on. The fix is implemented in a commit that enforces proper authentication and adds code hardening to the backend [3].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.openhab.ui.bundles:org.openhab.ui.cometvisuMaven
< 4.2.14.2.1

Affected products

1

Patches

1
630e8525835c

[cometvisu] Security fixes & cleanup for cometvisu backend (#2671)

https://github.com/openhab/openhab-webuiTobias BräutigamJul 17, 2024via ghsa
24 files changed · +104 1318
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/model/ConfigBean.java+0 27 removed
    @@ -1,27 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.model;
    -
    -/**
    - * This is a java bean that is used with JAXB to define the backend configurationfor the
    - * Cometvisu client.
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - *
    - */
    -public class ConfigBean {
    -    public String name = "openhab2";
    -    public String transport = "sse";
    -    public String baseURL = "/rest/cv/";
    -    public ResourcesBean resources;
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/model/LoginBean.java+0 26 removed
    @@ -1,26 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.model;
    -
    -/**
    - * This is a java bean that is used with JAXB to define the login entry
    - * page of the Cometvisu interface.
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - *
    - */
    -public class LoginBean {
    -    public String v;
    -    public String s;
    -    public ConfigBean c;
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/model/ResourcesBean.java+0 27 removed
    @@ -1,27 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.model;
    -
    -/**
    - * This is a java bean that is used with JAXB to define the resources of backend configuration for the
    - * Cometvisu client.
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - *
    - */
    -public class ResourcesBean {
    -    public String read;
    -    public String rrd;
    -    public String write;
    -    public String rest;
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/model/rest/RestBackendEnvironmentState.java+5 2 modified
    @@ -12,6 +12,7 @@
      */
     package org.openhab.ui.cometvisu.internal.backend.model.rest;
     
    +import org.eclipse.jdt.annotation.NonNullByDefault;
     import org.openhab.core.OpenHAB;
     
     /**
    @@ -21,16 +22,18 @@
      * @author Tobias Bräutigam - Initial contribution
      *
      */
    +@NonNullByDefault
     public class RestBackendEnvironmentState {
         // as we are just simulating we use a fixed version here to tell that we are compatible
         public int PHP_VERSION_ID = 80100;
         public String phpversion = "8.1.0";
     
    -    public String SERVER_SIGNATURE;
    -    public String SERVER_SOFTWARE;
    +    public String SERVER_SIGNATURE = "";
    +    public String SERVER_SOFTWARE = "";
         public String required_php_version = ">=7.4";
     
         // openHAB specific values
         public boolean isOpenHab = true;
    +    public boolean requiresAuth = true;
         public String server_release = "openHAB " + OpenHAB.getVersion();
     }
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/model/StateBean.java+0 25 removed
    @@ -1,25 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.model;
    -
    -/**
    - * Item bean for broadcasted item states.
    - * 
    - * @author Tobias Bräutigam - Initial Contribution and API
    - */
    -public class StateBean {
    -
    -    public String name;
    -
    -    public String state;
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/model/SuccessBean.java+0 24 removed
    @@ -1,24 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.model;
    -
    -/**
    - * This is a java bean that is used with JAXB to define the login entry
    - * page of the Cometvisu interface.
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - *
    - */
    -public class SuccessBean {
    -    public int success;
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/ChartResource.java+0 330 removed
    @@ -1,330 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.rest;
    -
    -import java.io.File;
    -import java.io.FileNotFoundException;
    -import java.io.IOException;
    -import java.text.DecimalFormat;
    -import java.text.NumberFormat;
    -import java.time.ZonedDateTime;
    -import java.util.ArrayList;
    -import java.util.Date;
    -import java.util.HashMap;
    -import java.util.Iterator;
    -import java.util.List;
    -import java.util.Locale;
    -import java.util.Map;
    -import java.util.Map.Entry;
    -import java.util.TimeZone;
    -import java.util.TreeMap;
    -
    -import javax.ws.rs.GET;
    -import javax.ws.rs.Path;
    -import javax.ws.rs.Produces;
    -import javax.ws.rs.QueryParam;
    -import javax.ws.rs.core.Context;
    -import javax.ws.rs.core.HttpHeaders;
    -import javax.ws.rs.core.MediaType;
    -import javax.ws.rs.core.Response;
    -import javax.ws.rs.core.UriInfo;
    -
    -import org.eclipse.jdt.annotation.NonNullByDefault;
    -import org.openhab.core.OpenHAB;
    -import org.openhab.core.io.rest.RESTConstants;
    -import org.openhab.core.io.rest.RESTResource;
    -import org.openhab.core.items.GroupItem;
    -import org.openhab.core.items.Item;
    -import org.openhab.core.items.ItemNotFoundException;
    -import org.openhab.core.items.ItemRegistry;
    -import org.openhab.core.library.types.DecimalType;
    -import org.openhab.core.persistence.FilterCriteria;
    -import org.openhab.core.persistence.FilterCriteria.Ordering;
    -import org.openhab.core.persistence.HistoricItem;
    -import org.openhab.core.persistence.PersistenceService;
    -import org.openhab.core.persistence.QueryablePersistenceService;
    -import org.openhab.ui.cometvisu.internal.Config;
    -import org.osgi.service.component.annotations.Activate;
    -import org.osgi.service.component.annotations.Component;
    -import org.osgi.service.component.annotations.Reference;
    -import org.osgi.service.component.annotations.ReferenceCardinality;
    -import org.osgi.service.component.annotations.ReferencePolicy;
    -import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
    -import org.rrd4j.ConsolFun;
    -import org.rrd4j.core.FetchData;
    -import org.rrd4j.core.FetchRequest;
    -import org.rrd4j.core.RrdDb;
    -import org.rrd4j.core.Util;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import io.swagger.v3.oas.annotations.Operation;
    -import io.swagger.v3.oas.annotations.responses.ApiResponse;
    -import io.swagger.v3.oas.annotations.tags.Tag;
    -
    -/**
    - * handles requests for chart series data from the CometVisu client
    - * used by the diagram plugin
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - * @author Wouter Born - Migrated to JAX-RS Whiteboard Specification
    - * @author Wouter Born - Migrated to OpenAPI annotations
    - *
    - * @deprecated CometVisu (>=0.12) is using openHAB's native REST API, a special backend implementation is obsolete now
    - */
    -@Component
    -@JaxrsResource
    -@JaxrsName(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CHART_ALIAS)
    -@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
    -@JSONRequired
    -@Path(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CHART_ALIAS)
    -@Tag(name = Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CHART_ALIAS)
    -@NonNullByDefault
    -@Deprecated(since = "3.4", forRemoval = true)
    -public class ChartResource implements RESTResource {
    -    private final Logger logger = LoggerFactory.getLogger(ChartResource.class);
    -
    -    // pattern RRDTool uses to format doubles in XML files
    -    private static final String PATTERN = "0.0000000000E00";
    -
    -    private static final DecimalFormat DECIMAL_FORMAT;
    -
    -    protected static final String RRD_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "persistence"
    -            + File.separator + "rrd4j";
    -
    -    static {
    -        DECIMAL_FORMAT = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ENGLISH);
    -        synchronized (DECIMAL_FORMAT) {
    -            DECIMAL_FORMAT.applyPattern(PATTERN);
    -        }
    -    }
    -
    -    private final Map<String, QueryablePersistenceService> persistenceServices = new HashMap<>();
    -
    -    private final ItemRegistry itemRegistry;
    -
    -    @Activate
    -    public ChartResource(final @Reference ItemRegistry itemRegistry) {
    -        this.itemRegistry = itemRegistry;
    -    }
    -
    -    private @Context @NonNullByDefault({}) UriInfo uriInfo;
    -
    -    @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
    -    public void addPersistenceService(PersistenceService service) {
    -        if (service instanceof QueryablePersistenceService) {
    -            persistenceServices.put(service.getId(), (QueryablePersistenceService) service);
    -        }
    -    }
    -
    -    public void removePersistenceService(PersistenceService service) {
    -        persistenceServices.remove(service.getId());
    -    }
    -
    -    @GET
    -    @Produces(MediaType.APPLICATION_JSON)
    -    @Operation(summary = "returns chart data from persistence service for an item", responses = {
    -            @ApiResponse(responseCode = "200", description = "OK"),
    -            @ApiResponse(responseCode = "500", description = "Server error") })
    -    public Response getChartSeries(@Context HttpHeaders headers, @QueryParam("rrd") String itemName,
    -            @QueryParam("ds") String consFunction, @QueryParam("start") String start, @QueryParam("end") String end,
    -            @QueryParam("res") long resolution) {
    -        if (logger.isDebugEnabled()) {
    -            logger.debug("Received GET request at '{}' for rrd '{}'.", uriInfo.getPath(), itemName);
    -        }
    -        String responseType = MediaType.APPLICATION_JSON;
    -
    -        // RRD specific: no equivalent in PersistenceService known
    -        ConsolFun consilidationFunction = ConsolFun.valueOf(consFunction);
    -
    -        // read the start/end time as they are provided in the RRD-way, we use
    -        // the RRD4j to read them
    -        long[] times = Util.getTimestamps(start, end);
    -        Date startTime = new Date();
    -        startTime.setTime(times[0] * 1000L);
    -        Date endTime = new Date();
    -        endTime.setTime(times[1] * 1000L);
    -
    -        if (itemName.endsWith(".rrd")) {
    -            itemName = itemName.substring(0, itemName.length() - 4);
    -        }
    -        String[] parts = itemName.split(":");
    -        String service = "rrd4j";
    -
    -        if (parts.length == 2) {
    -            itemName = parts[1];
    -            service = parts[0];
    -        }
    -
    -        Item item;
    -        try {
    -            item = itemRegistry.getItem(itemName);
    -            logger.debug("item '{}' found ", item);
    -
    -            // Prefer RRD-Service
    -            QueryablePersistenceService persistenceService = persistenceServices.get(service);
    -            // Fallback to first persistenceService from list
    -            if (persistenceService == null) {
    -                Iterator<Entry<String, QueryablePersistenceService>> pit = persistenceServices.entrySet().iterator();
    -                if (pit.hasNext()) {
    -                    persistenceService = pit.next().getValue();
    -                    logger.debug("required persistence service ({}) not found, using {} instead", service,
    -                            persistenceService.getId());
    -                } else {
    -                    throw new IllegalArgumentException("No Persistence service found.");
    -                }
    -            } else {
    -                logger.debug("using {} persistence for item {}", persistenceService.getId(), itemName);
    -            }
    -            Object data = null;
    -            if (persistenceService.getId().equals("rrd4j")) {
    -                data = getRrdSeries(persistenceService, item, consilidationFunction, startTime, endTime, resolution);
    -            } else {
    -                data = getPersistenceSeries(persistenceService, item, startTime, endTime, resolution);
    -            }
    -            return Response.ok(data, responseType).build();
    -        } catch (ItemNotFoundException e1) {
    -            logger.error("Item '{}' not found error while requesting series data.", itemName);
    -        }
    -        return Response.serverError().build();
    -    }
    -
    -    public Object getPersistenceSeries(QueryablePersistenceService persistenceService, Item item, Date timeBegin,
    -            Date timeEnd, long resolution) {
    -        Map<Long, List<String>> data = new HashMap<>();
    -
    -        // Define the data filter
    -        FilterCriteria filter = new FilterCriteria();
    -        filter.setBeginDate(ZonedDateTime.ofInstant(timeBegin.toInstant(), TimeZone.getDefault().toZoneId()));
    -        filter.setEndDate(ZonedDateTime.ofInstant(timeEnd.toInstant(), TimeZone.getDefault().toZoneId()));
    -        filter.setItemName(item.getName());
    -        filter.setOrdering(Ordering.ASCENDING);
    -
    -        // Get the data from the persistence store
    -        Iterable<HistoricItem> result = persistenceService.query(filter);
    -        Iterator<HistoricItem> it = result.iterator();
    -
    -        // Iterate through the data
    -        int dataCounter = 0;
    -        while (it.hasNext()) {
    -            dataCounter++;
    -            HistoricItem historicItem = it.next();
    -            org.openhab.core.types.State state = historicItem.getState();
    -            if (state instanceof DecimalType) {
    -                List<String> vals = new ArrayList<>();
    -                vals.add(formatDouble(((DecimalType) state).doubleValue(), "null", true));
    -                data.put(historicItem.getTimestamp().toInstant().toEpochMilli(), vals);
    -            }
    -        }
    -        logger.debug("'{}' querying item '{}' from '{}' to '{}' => '{}' results", persistenceService.getId(),
    -                filter.getItemName(), filter.getBeginDate(), filter.getEndDate(), dataCounter);
    -        return convertToRrd(data);
    -    }
    -
    -    /**
    -     * returns a rrd series data, an array of [[timestamp,data1,data2,...]]
    -     *
    -     * @param persistenceService
    -     * @param item
    -     * @param consilidationFunction
    -     * @param timeBegin
    -     * @param timeEnd
    -     * @param resolution
    -     * @return
    -     */
    -    public Object getRrdSeries(QueryablePersistenceService persistenceService, Item item,
    -            ConsolFun consilidationFunction, Date timeBegin, Date timeEnd, long resolution) {
    -        Map<Long, List<String>> data = new TreeMap<>();
    -        try {
    -            List<String> itemNames = new ArrayList<>();
    -
    -            if (item instanceof GroupItem groupItem) {
    -                for (Item member : groupItem.getMembers()) {
    -                    itemNames.add(member.getName());
    -                }
    -            } else {
    -                itemNames.add(item.getName());
    -            }
    -            for (String itemName : itemNames) {
    -                addRrdData(data, itemName, consilidationFunction, timeBegin, timeEnd, resolution);
    -            }
    -        } catch (FileNotFoundException e) {
    -            // rrd file does not exist, fallback to generic persistence service
    -            logger.debug("no rrd file found '{}'", (RRD_FOLDER + File.separator + item.getName() + ".rrd"));
    -            return getPersistenceSeries(persistenceService, item, timeBegin, timeEnd, resolution);
    -        } catch (Exception e) {
    -            logger.error("{}: fallback to generic persistance service", e.getLocalizedMessage());
    -            return getPersistenceSeries(persistenceService, item, timeBegin, timeEnd, resolution);
    -        }
    -        return convertToRrd(data);
    -    }
    -
    -    private List<Object> convertToRrd(Map<Long, List<String>> data) {
    -        // sort data by key
    -        Map<Long, List<String>> treeMap = new TreeMap<>(data);
    -        List<Object> rrd = new ArrayList<>();
    -        for (Long time : treeMap.keySet()) {
    -            Object[] entry = new Object[2];
    -            entry[0] = time;
    -            entry[1] = data.get(time);
    -            rrd.add(entry);
    -        }
    -        return rrd;
    -    }
    -
    -    private Map<Long, List<String>> addRrdData(Map<Long, List<String>> data, String itemName,
    -            ConsolFun consilidationFunction, Date timeBegin, Date timeEnd, long resolution) throws IOException {
    -        RrdDb rrdDb = new RrdDb(RRD_FOLDER + File.separator + itemName + ".rrd");
    -        FetchRequest fetchRequest = rrdDb.createFetchRequest(consilidationFunction, Util.getTimestamp(timeBegin),
    -                Util.getTimestamp(timeEnd), resolution);
    -        FetchData fetchData = fetchRequest.fetchData();
    -        long[] timestamps = fetchData.getTimestamps();
    -        double[][] values = fetchData.getValues();
    -
    -        logger.debug("RRD fetch returned '{}' rows and '{}' columns", fetchData.getRowCount(),
    -                fetchData.getColumnCount());
    -
    -        for (int row = 0; row < fetchData.getRowCount(); row++) {
    -            // change to microseconds
    -            long time = timestamps[row] * 1000;
    -
    -            if (!data.containsKey(time)) {
    -                data.put(time, new ArrayList<>());
    -            }
    -            List<String> vals = data.get(time);
    -            int indexOffset = vals.size();
    -            for (int dsIndex = 0; dsIndex < fetchData.getColumnCount(); dsIndex++) {
    -                vals.add(dsIndex + indexOffset, formatDouble(values[dsIndex][row], "null", true));
    -            }
    -        }
    -        rrdDb.close();
    -
    -        return data;
    -    }
    -
    -    static String formatDouble(double x, String nanString, boolean forceExponents) {
    -        if (Double.isNaN(x)) {
    -            return nanString;
    -        }
    -        if (forceExponents) {
    -            synchronized (DECIMAL_FORMAT) {
    -                return DECIMAL_FORMAT.format(x);
    -            }
    -        }
    -        return "" + x;
    -    }
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/CheckResource.java+1 1 modified
    @@ -60,7 +60,7 @@
     public class CheckResource implements RESTResource {
     
         /**
    -     * Checks some files and folders for existance and access rights.
    +     * Checks some files and folders for existence and access rights.
          *
          * @return the check result that contains a bitfield with check results for each entity
          */
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/ConfigResource.java+10 42 modified
    @@ -19,6 +19,7 @@
     import java.util.regex.Matcher;
     import java.util.regex.Pattern;
     
    +import javax.annotation.security.RolesAllowed;
     import javax.ws.rs.Consumes;
     import javax.ws.rs.DELETE;
     import javax.ws.rs.GET;
    @@ -32,6 +33,7 @@
     import javax.ws.rs.core.Response.Status;
     
     import org.eclipse.jdt.annotation.NonNullByDefault;
    +import org.openhab.core.auth.Role;
     import org.openhab.core.io.rest.RESTConstants;
     import org.openhab.core.io.rest.RESTResource;
     import org.openhab.ui.cometvisu.internal.Config;
    @@ -55,6 +57,7 @@
     import io.swagger.v3.oas.annotations.Operation;
     import io.swagger.v3.oas.annotations.Parameter;
     import io.swagger.v3.oas.annotations.responses.ApiResponse;
    +import io.swagger.v3.oas.annotations.security.SecurityRequirement;
     import io.swagger.v3.oas.annotations.tags.Tag;
     
     /**
    @@ -69,6 +72,8 @@
     @JaxrsName(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CONFIG_ALIAS)
     @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
     @JSONRequired
    +@RolesAllowed({ Role.ADMIN })
    +@SecurityRequirement(name = "oauth2", scopes = { "admin" })
     @Path(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CONFIG_ALIAS)
     @Tag(name = Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_CONFIG_ALIAS)
     @NonNullByDefault
    @@ -263,18 +268,7 @@ public static HiddenConfig loadHiddenConfig() {
                 java.nio.file.Path hiddenConfigPath = ManagerSettings.getInstance().getConfigPath().resolve("hidden.php");
                 if (hiddenConfigPath.toFile().exists()) {
                     List<String> content = Files.readAllLines(hiddenConfigPath);
    -                boolean isPhpVersion = true;
    -                for (int i = content.size() - 1; i >= 0; i++) {
    -                    if (content.get(i).contains("json_decode")) {
    -                        isPhpVersion = false;
    -                        break;
    -                    }
    -                }
    -                if (isPhpVersion) {
    -                    return loadPhpConfig(config, content);
    -                } else {
    -                    return loadJson(String.join("\n", content));
    -                }
    +                return loadJson(String.join("\n", content));
                 }
             } catch (IOException e) {
             }
    @@ -289,41 +283,15 @@ private static HiddenConfig loadJson(String content) {
             return Objects.requireNonNull(gson.fromJson(rawContent, HiddenConfig.class));
         }
     
    -    private static HiddenConfig loadPhpConfig(HiddenConfig config, List<String> content) {
    -        boolean inHidden = false;
    -
    -        for (final String line : content) {
    -            if (!inHidden) {
    -                if ("$hidden = array(".equalsIgnoreCase(line)) {
    -                    inHidden = true;
    -                }
    -            } else if (");".equalsIgnoreCase(line)) {
    -                break;
    -            } else {
    -                Matcher m = sectionPattern.matcher(line);
    -                if (m.find()) {
    -                    boolean commented = m.group(1) != null;
    -                    if (!commented) {
    -                        String options = m.group(3);
    -                        Matcher om = optionPattern.matcher(options);
    -                        ConfigSection section = new ConfigSection();
    -                        while (om.find()) {
    -                            section.put(om.group(1), om.group(2));
    -                        }
    -                        config.put(m.group(2), section);
    -                    }
    -                }
    -            }
    -        }
    -        return config;
    -    }
    -
         private void writeHiddenConfig(HiddenConfig hidden) throws IOException {
             java.nio.file.Path hiddenConfigPath = ManagerSettings.getInstance().getConfigPath().resolve("hidden.php");
             Gson gson = new GsonBuilder().setPrettyPrinting().create();
             StringBuilder content = new StringBuilder().append("<?php\n")
                     .append("// File for configurations that shouldn't be shared with the user\n").append("$data = '")
    -                .append(gson.toJson(hidden)).append("';\n").append("$hidden = json_decode($data, true);\n");
    +                .append(gson.toJson(hidden)).append("';\n").append("try {\n")
    +                .append("  $hidden = json_decode($data, true, 512, JSON_THROW_ON_ERROR);\n")
    +                .append("} catch (JsonException $e) {\n")
    +                .append("  $hidden = [\"error\" => $e->getMessage(), \"data\" => $data];\n").append("}\n");
             Files.writeString(hiddenConfigPath, content);
         }
     }
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/FsResource.java+22 14 modified
    @@ -19,6 +19,7 @@
     import java.nio.file.Paths;
     import java.nio.file.StandardCopyOption;
     
    +import javax.annotation.security.RolesAllowed;
     import javax.servlet.http.HttpServletRequest;
     import javax.ws.rs.Consumes;
     import javax.ws.rs.DELETE;
    @@ -36,6 +37,7 @@
     import javax.ws.rs.core.Response.Status;
     
     import org.eclipse.jdt.annotation.NonNullByDefault;
    +import org.openhab.core.auth.Role;
     import org.openhab.core.io.rest.RESTConstants;
     import org.openhab.core.io.rest.RESTResource;
     import org.openhab.ui.cometvisu.internal.Config;
    @@ -79,6 +81,7 @@ public class FsResource implements RESTResource {
         private final Logger logger = LoggerFactory.getLogger(FsResource.class);
     
         @POST
    +    @RolesAllowed({ Role.USER, Role.ADMIN })
         @Consumes("text/*")
         @Produces(MediaType.APPLICATION_JSON)
         @Operation(summary = "Create a text file", responses = { @ApiResponse(responseCode = "200", description = "OK"),
    @@ -153,6 +156,7 @@ public Response createBinary(@Context HttpServletRequest request,
     
         @DELETE
         @Produces(MediaType.APPLICATION_JSON)
    +    @RolesAllowed({ Role.USER, Role.ADMIN })
         @Operation(summary = "Deletes a file/folder", responses = { @ApiResponse(responseCode = "200", description = "OK"),
                 @ApiResponse(responseCode = "403", description = "not allowed"),
                 @ApiResponse(responseCode = "404", description = "File/Folder not found"),
    @@ -242,33 +246,37 @@ public Response read(
         }
     
         @PUT
    +    @RolesAllowed({ Role.USER, Role.ADMIN })
         @Produces(MediaType.APPLICATION_JSON)
         @Consumes({ MediaType.TEXT_PLAIN, MediaType.TEXT_XML })
         @Operation(summary = "Update an existing file", responses = {
                 @ApiResponse(responseCode = "200", description = "OK"),
    -            @ApiResponse(responseCode = "403", description = "not allowed"),
    +            @ApiResponse(responseCode = "403", description = "forbidden"),
                 @ApiResponse(responseCode = "404", description = "File does not exist") })
         public Response update(
                 @Parameter(description = "Relative path inside the config folder", required = true) @QueryParam("path") String path,
                 @Parameter(description = "file content") String body,
                 @Parameter(description = "CRC32 hash value of the file content", content = @Content(schema = @Schema(implementation = String.class, defaultValue = "ignore"))) @DefaultValue("ignore") @QueryParam("hash") String hash) {
    -        File target = new File(
    -                ManagerSettings.getInstance().getConfigFolder().getAbsolutePath() + File.separator + path);
    -        if (target.exists()) {
    -            if (target.canWrite()) {
    -                try {
    -                    FsUtil.getInstance().saveFile(target, body, hash);
    -                    return Response.ok().build();
    -                } catch (FileOperationException e) {
    -                    return FsUtil.createErrorResponse(e);
    -                } catch (Exception e) {
    +        try {
    +            MountedFile target = new MountedFile(path);
    +            if (target.exists()) {
    +                if (target.canWrite()) {
    +                    try {
    +                        FsUtil.getInstance().saveFile(target.toFile(), body, hash);
    +                        return Response.ok().build();
    +                    } catch (FileOperationException e) {
    +                        return FsUtil.createErrorResponse(e);
    +                    } catch (Exception e) {
    +                        return FsUtil.createErrorResponse(Status.FORBIDDEN, "forbidden");
    +                    }
    +                } else {
                         return FsUtil.createErrorResponse(Status.FORBIDDEN, "forbidden");
                     }
                 } else {
    -                return FsUtil.createErrorResponse(Status.FORBIDDEN, "forbidden");
    +                return FsUtil.createErrorResponse(Status.NOT_FOUND, "not found");
                 }
    -        } else {
    -            return FsUtil.createErrorResponse(Status.NOT_FOUND, "not found");
    +        } catch (FileOperationException e) {
    +            return FsUtil.createErrorResponse(e);
             }
         }
     }
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/LoginResource.java+0 92 removed
    @@ -1,92 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.rest;
    -
    -import javax.ws.rs.GET;
    -import javax.ws.rs.Path;
    -import javax.ws.rs.Produces;
    -import javax.ws.rs.QueryParam;
    -import javax.ws.rs.core.Context;
    -import javax.ws.rs.core.HttpHeaders;
    -import javax.ws.rs.core.MediaType;
    -import javax.ws.rs.core.Response;
    -import javax.ws.rs.core.UriInfo;
    -
    -import org.eclipse.jdt.annotation.NonNullByDefault;
    -import org.openhab.core.io.rest.RESTConstants;
    -import org.openhab.core.io.rest.RESTResource;
    -import org.openhab.ui.cometvisu.internal.Config;
    -import org.openhab.ui.cometvisu.internal.backend.model.ConfigBean;
    -import org.openhab.ui.cometvisu.internal.backend.model.LoginBean;
    -import org.openhab.ui.cometvisu.internal.backend.model.ResourcesBean;
    -import org.osgi.service.component.annotations.Component;
    -import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
    -
    -import io.swagger.v3.oas.annotations.Operation;
    -import io.swagger.v3.oas.annotations.media.Content;
    -import io.swagger.v3.oas.annotations.media.Schema;
    -import io.swagger.v3.oas.annotations.responses.ApiResponse;
    -import io.swagger.v3.oas.annotations.tags.Tag;
    -
    -/**
    - * handles login request from the CometVisu client
    - * currently this is just a placeholder and does no real authentification
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - * @author Wouter Born - Migrated to JAX-RS Whiteboard Specification
    - * @author Wouter Born - Migrated to OpenAPI annotations
    - *
    - * @deprecated CometVisu (>=0.12) is using openHAB's native REST API, a special backend implementation is obsolete now
    - */
    -@Component
    -@JaxrsResource
    -@JaxrsName(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_LOGIN_ALIAS)
    -@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
    -@JSONRequired
    -@Path(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_LOGIN_ALIAS)
    -@Tag(name = Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_LOGIN_ALIAS)
    -@NonNullByDefault
    -@Deprecated(since = "3.4", forRemoval = true)
    -public class LoginResource implements RESTResource {
    -    @GET
    -    @Produces(MediaType.APPLICATION_JSON)
    -    @Operation(summary = "returns the login response with backend configuration information", responses = {
    -            @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoginBean.class))) })
    -    public Response getLogin(@Context UriInfo uriInfo, @Context HttpHeaders headers, @QueryParam("u") String user,
    -            @QueryParam("p") String password, @QueryParam("d") String device) {
    -        LoginBean bean = new LoginBean();
    -        bean.v = "0.0.1";
    -        bean.s = "0"; // Session-ID not needed with SSE
    -        ConfigBean conf = new ConfigBean();
    -        ResourcesBean res = new ResourcesBean();
    -        String origin = headers.getHeaderString("Origin");
    -        String serverHost = uriInfo.getBaseUri().getScheme() + "://" + uriInfo.getBaseUri().getHost();
    -        if (uriInfo.getBaseUri().getPort() != 80) {
    -            serverHost += ":" + uriInfo.getBaseUri().getPort();
    -        }
    -        String host = origin == null || serverHost.compareToIgnoreCase(origin) == 0 ? "" : serverHost;
    -
    -        conf.baseURL = host + "/rest/" + Config.COMETVISU_BACKEND_ALIAS + "/";
    -        conf.resources = res;
    -        res.read = Config.COMETVISU_BACKEND_READ_ALIAS;
    -        res.rrd = Config.COMETVISU_BACKEND_CHART_ALIAS;
    -        res.write = Config.COMETVISU_BACKEND_WRITE_ALIAS;
    -        res.rest = conf.baseURL.substring(0, conf.baseURL.length() - 1); // no trailing slash
    -        bean.c = conf;
    -        return Response.ok(bean, MediaType.APPLICATION_JSON).build();
    -    }
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/MoveResource.java+3 0 modified
    @@ -15,6 +15,7 @@
     import java.io.IOException;
     import java.nio.file.Files;
     
    +import javax.annotation.security.RolesAllowed;
     import javax.ws.rs.PUT;
     import javax.ws.rs.Path;
     import javax.ws.rs.Produces;
    @@ -24,6 +25,7 @@
     import javax.ws.rs.core.Response.Status;
     
     import org.eclipse.jdt.annotation.NonNullByDefault;
    +import org.openhab.core.auth.Role;
     import org.openhab.core.io.rest.RESTConstants;
     import org.openhab.core.io.rest.RESTResource;
     import org.openhab.ui.cometvisu.internal.Config;
    @@ -56,6 +58,7 @@
     @JaxrsName(Config.COMETVISU_BACKEND_ALIAS + "/fs/move")
     @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
     @JSONRequired
    +@RolesAllowed({ Role.USER, Role.ADMIN })
     @Path(Config.COMETVISU_BACKEND_ALIAS + "/fs/move")
     @Tag(name = Config.COMETVISU_BACKEND_ALIAS + "/fs/move")
     @NonNullByDefault
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/ProxyResource.java+29 1 modified
    @@ -21,6 +21,9 @@
     import java.net.http.HttpResponse.BodyHandlers;
     import java.time.Duration;
     import java.util.Base64;
    +import java.util.Map;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
     
     import javax.ws.rs.GET;
     import javax.ws.rs.Path;
    @@ -78,17 +81,19 @@ public class ProxyResource implements RESTResource {
         @Produces({ MediaType.APPLICATION_JSON, MediaType.MEDIA_TYPE_WILDCARD })
         @Operation(summary = "proxy a request", responses = { @ApiResponse(responseCode = "200", description = "OK"),
                 @ApiResponse(responseCode = "400", description = "Bad request"),
    +            @ApiResponse(responseCode = "403", description = "Forbidden"),
                 @ApiResponse(responseCode = "404", description = "Not found"),
    +            @ApiResponse(responseCode = "406", description = "Not Acceptable"),
                 @ApiResponse(responseCode = "500", description = "Internal server error") })
         public Response proxy(
                 @Parameter(description = "URL this request should be sent to", content = @Content(schema = @Schema(implementation = String.class, defaultValue = ""))) @QueryParam("url") @Nullable String url,
                 @Parameter(description = "optional authorization token", content = @Content(schema = @Schema(implementation = String.class, defaultValue = ""))) @QueryParam("auth-type") @Nullable String authType,
                 @Parameter(description = "use information from hidden config section", content = @Content(schema = @Schema(implementation = String.class, defaultValue = ""))) @QueryParam("config-section") @Nullable String configSection) {
             ConfigSection sec = null;
             String queryUrl = url != null ? url : "";
    +        HiddenConfig config = ConfigResource.loadHiddenConfig();
             if (configSection != null && !configSection.isBlank()) {
                 // read URI and further information
    -            HiddenConfig config = ConfigResource.loadHiddenConfig();
                 sec = config.get(configSection);
                 if (sec != null) {
                     String configUrl = sec.get("uri");
    @@ -101,6 +106,29 @@ public Response proxy(
                 }
             } else if (url == null || url.isBlank()) {
                 return Response.status(Status.BAD_REQUEST).build();
    +        } else {
    +            ConfigSection whiteList = config.get("proxy.whitelist");
    +            boolean allowed = false;
    +            if (whiteList != null) {
    +                for (Map.Entry<String, String> entry : whiteList.entrySet()) {
    +                    String value = entry.getValue();
    +                    if (value.startsWith("/") && value.endsWith("/")) {
    +                        Pattern pattern = Pattern.compile(value.substring(1, value.length() - 1),
    +                                Pattern.CASE_INSENSITIVE);
    +                        Matcher matcher = pattern.matcher(queryUrl);
    +                        if (matcher.find()) {
    +                            allowed = true;
    +                            break;
    +                        }
    +                    } else if (value.equalsIgnoreCase(queryUrl)) {
    +                        allowed = true;
    +                        break;
    +                    }
    +                }
    +            }
    +            if (!allowed) {
    +                return Response.status(Status.FORBIDDEN).build();
    +            }
             }
             logger.info("proxying request to {}", queryUrl);
     
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/ReadResource.java+0 254 removed
    @@ -1,254 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.rest;
    -
    -import java.io.IOException;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Objects;
    -import java.util.concurrent.CopyOnWriteArrayList;
    -import java.util.concurrent.ExecutorService;
    -import java.util.concurrent.Executors;
    -
    -import javax.ws.rs.GET;
    -import javax.ws.rs.Path;
    -import javax.ws.rs.Produces;
    -import javax.ws.rs.QueryParam;
    -import javax.ws.rs.core.Context;
    -import javax.ws.rs.core.MediaType;
    -import javax.ws.rs.sse.Sse;
    -import javax.ws.rs.sse.SseEventSink;
    -
    -import org.eclipse.jdt.annotation.NonNullByDefault;
    -import org.eclipse.jdt.annotation.Nullable;
    -import org.openhab.core.io.rest.RESTConstants;
    -import org.openhab.core.io.rest.RESTResource;
    -import org.openhab.core.io.rest.SseBroadcaster;
    -import org.openhab.core.items.GenericItem;
    -import org.openhab.core.items.Item;
    -import org.openhab.core.items.ItemFactory;
    -import org.openhab.core.items.ItemNotFoundException;
    -import org.openhab.core.items.ItemRegistry;
    -import org.openhab.core.types.State;
    -import org.openhab.ui.cometvisu.internal.Config;
    -import org.openhab.ui.cometvisu.internal.backend.model.StateBean;
    -import org.openhab.ui.cometvisu.internal.listeners.StateEventListener;
    -import org.openhab.ui.cometvisu.internal.util.SseUtil;
    -import org.osgi.service.component.annotations.Activate;
    -import org.osgi.service.component.annotations.Component;
    -import org.osgi.service.component.annotations.Deactivate;
    -import org.osgi.service.component.annotations.Reference;
    -import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import io.swagger.v3.oas.annotations.Operation;
    -import io.swagger.v3.oas.annotations.responses.ApiResponse;
    -import io.swagger.v3.oas.annotations.tags.Tag;
    -
    -/**
    - * handles read request from the CometVisu client every request initializes a
    - * SSE communication
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - * @author Wouter Born - Migrated to JAX-RS Whiteboard Specification
    - * @author Wouter Born - Migrated to OpenAPI annotations
    - *
    - * @deprecated CometVisu (>=0.12) is using openHAB's native REST API, a special backend implementation is obsolete now
    - */
    -@Component(immediate = true)
    -@JaxrsResource
    -@JaxrsName(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_READ_ALIAS)
    -@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
    -@JSONRequired
    -@Path(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_READ_ALIAS)
    -@Tag(name = Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_READ_ALIAS)
    -@NonNullByDefault
    -@Deprecated(since = "3.4", forRemoval = true)
    -public class ReadResource implements EventBroadcaster, RESTResource {
    -    private final Logger logger = LoggerFactory.getLogger(ReadResource.class);
    -
    -    private SseBroadcaster<SseSinkInfo> broadcaster = new SseBroadcaster<>();
    -
    -    private final ExecutorService executorService = Executors.newSingleThreadExecutor();
    -
    -    private final ItemRegistry itemRegistry;
    -
    -    private final StateEventListener stateEventListener = new StateEventListener(this);
    -
    -    private List<String> itemNames = new ArrayList<>();
    -    private Map<Item, Map<String, @Nullable Class<? extends State>>> items = new HashMap<>();
    -
    -    private @NonNullByDefault({}) Sse sse;
    -
    -    private Collection<ItemFactory> itemFactories = new CopyOnWriteArrayList<>();
    -
    -    @Activate
    -    public ReadResource(@Reference ItemRegistry itemRegistry) {
    -        this.itemRegistry = itemRegistry;
    -    }
    -
    -    @Deactivate
    -    public void deactivate() {
    -        broadcaster.close();
    -    }
    -
    -    @Context
    -    public void setSse(final Sse sse) {
    -        this.sse = sse;
    -    }
    -
    -    protected void addItemFactory(ItemFactory itemFactory) {
    -        itemFactories.add(itemFactory);
    -    }
    -
    -    protected void removeItemFactory(ItemFactory itemFactory) {
    -        itemFactories.remove(itemFactory);
    -    }
    -
    -    /**
    -     * Subscribes the connecting client to the stream of events filtered by the
    -     * given eventFilter.
    -     *
    -     * @throws IOException
    -     * @throws InterruptedException
    -     */
    -    @GET
    -    @Produces(MediaType.SERVER_SENT_EVENTS)
    -    @Operation(summary = "Creates the SSE stream for item states, sends all requested states once and then only changes states", responses = {
    -            @ApiResponse(responseCode = "200", description = "OK") })
    -    public void getStates(@Context final SseEventSink sseEventSink, @QueryParam("a") List<String> itemNames,
    -            @QueryParam("i") long index, @QueryParam("t") long time) throws IOException, InterruptedException {
    -        this.itemNames = itemNames;
    -
    -        broadcaster.add(sseEventSink, new SseSinkInfo(itemNames, index, time));
    -
    -        // get all requested items and send their states to the client
    -        items = new HashMap<>();
    -        // send the current states of all items to the client
    -        List<StateBean> states = new ArrayList<>();
    -        for (String cvItemName : itemNames) {
    -            try {
    -                String[] parts = cvItemName.split(":");
    -                String ohItemName = cvItemName;
    -                Class<? extends State> stateClass = null;
    -                if (parts.length == 2) {
    -                    String classPrefix = parts[0].toLowerCase();
    -                    if (Config.itemTypeMapper.containsKey(classPrefix)) {
    -                        stateClass = Config.itemTypeMapper.get(classPrefix);
    -                        classPrefix += ":";
    -                    } else {
    -                        logger.debug("no type found for '{}'", classPrefix);
    -                        classPrefix = "";
    -                    }
    -                    ohItemName = parts[1];
    -                }
    -                Item item = this.itemRegistry.getItem(ohItemName);
    -                if (!items.containsKey(item)) {
    -                    items.put(item, new HashMap<>());
    -                }
    -                items.get(item).put(cvItemName, stateClass);
    -                StateBean itemState = new StateBean();
    -                itemState.name = cvItemName;
    -
    -                if (stateClass != null) {
    -                    itemState.state = item.getStateAs(stateClass).toString();
    -                    logger.trace("get state of '{}' as '{}' == '{}'", item, stateClass, itemState.state);
    -                } else {
    -                    itemState.state = item.getState().toString();
    -                }
    -                states.add(itemState);
    -            } catch (ItemNotFoundException e) {
    -                logger.error("{}", e.getLocalizedMessage());
    -            }
    -        }
    -
    -        logger.debug("initially broadcasting {}/{} item states", states.size(), itemNames.size());
    -        broadcaster.send(SseUtil.buildEvent(sse.newEventBuilder(), states));
    -
    -        // listen to state changes of the requested items
    -        registerItems();
    -    }
    -
    -    /**
    -     * listen for state changes from the requested items
    -     */
    -    @Override
    -    public void registerItems() {
    -        for (Item item : items.keySet()) {
    -            if (item instanceof GenericItem) {
    -                ((GenericItem) item).addStateChangeListener(stateEventListener);
    -            }
    -        }
    -    }
    -
    -    /**
    -     * listens to state changes of the given item, if it is part of the requested items
    -     *
    -     * @param item the new item, that should be listened to
    -     */
    -    @Override
    -    public void registerItem(Item item) {
    -        if (items.containsKey(item) || !itemNames.contains(item.getName())) {
    -            return;
    -        }
    -        if (item instanceof GenericItem) {
    -            ((GenericItem) item).addStateChangeListener(stateEventListener);
    -        }
    -    }
    -
    -    /**
    -     * listens to state changes of the given item, if it is part of the
    -     * requested items
    -     *
    -     * @param item the new item, that should be listened to
    -     */
    -    @Override
    -    public void unregisterItem(Item item) {
    -        if (items.containsKey(item) || !itemNames.contains(item.getName())) {
    -            return;
    -        }
    -        if (item instanceof GenericItem) {
    -            ((GenericItem) item).removeStateChangeListener(stateEventListener);
    -            items.remove(item);
    -        }
    -    }
    -
    -    /**
    -     * Broadcasts an event described by the given parameters to all currently
    -     * listening clients.
    -     *
    -     * @param eventObject bean that can be converted to a JSON object.
    -     */
    -    @Override
    -    public void broadcastEvent(final Object eventObject) {
    -        if (sse == null) {
    -            logger.trace("broadcast skipped (no one listened since activation)");
    -            return;
    -        }
    -
    -        executorService.execute(() -> broadcaster.send(SseUtil.buildEvent(sse.newEventBuilder(), eventObject)));
    -    }
    -
    -    @Override
    -    public Map<String, @Nullable Class<? extends State>> getClientItems(Item item) {
    -        return Objects.requireNonNullElse(items.get(item), Map.of());
    -    }
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/rest/WriteResource.java+0 116 removed
    @@ -1,116 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.backend.rest;
    -
    -import javax.ws.rs.GET;
    -import javax.ws.rs.Path;
    -import javax.ws.rs.Produces;
    -import javax.ws.rs.QueryParam;
    -import javax.ws.rs.core.Context;
    -import javax.ws.rs.core.HttpHeaders;
    -import javax.ws.rs.core.MediaType;
    -import javax.ws.rs.core.Response;
    -import javax.ws.rs.core.Response.Status;
    -import javax.ws.rs.core.UriInfo;
    -
    -import org.eclipse.jdt.annotation.NonNullByDefault;
    -import org.openhab.core.events.EventPublisher;
    -import org.openhab.core.io.rest.RESTConstants;
    -import org.openhab.core.io.rest.RESTResource;
    -import org.openhab.core.items.Item;
    -import org.openhab.core.items.ItemNotFoundException;
    -import org.openhab.core.items.ItemRegistry;
    -import org.openhab.core.items.events.ItemEventFactory;
    -import org.openhab.core.types.Command;
    -import org.openhab.core.types.TypeParser;
    -import org.openhab.ui.cometvisu.internal.Config;
    -import org.openhab.ui.cometvisu.internal.backend.model.SuccessBean;
    -import org.osgi.service.component.annotations.Activate;
    -import org.osgi.service.component.annotations.Component;
    -import org.osgi.service.component.annotations.Reference;
    -import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JSONRequired;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsName;
    -import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import io.swagger.v3.oas.annotations.Operation;
    -import io.swagger.v3.oas.annotations.Parameter;
    -import io.swagger.v3.oas.annotations.responses.ApiResponse;
    -import io.swagger.v3.oas.annotations.tags.Tag;
    -
    -/**
    - * handles state updates send by the CometVisu client and forwars them to the EventPublisher
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - * @author Wouter Born - Migrated to JAX-RS Whiteboard Specification
    - * @author Wouter Born - Migrated to OpenAPI annotations
    - *
    - * @deprecated CometVisu (>=0.12) is using openHAB's native REST API, a special backend implementation is obsolete now
    - */
    -@Component
    -@JaxrsResource
    -@JaxrsName(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_WRITE_ALIAS)
    -@JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + RESTConstants.JAX_RS_NAME + ")")
    -@JSONRequired
    -@Path(Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_WRITE_ALIAS)
    -@Tag(name = Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_WRITE_ALIAS)
    -@NonNullByDefault
    -@Deprecated(since = "3.4", forRemoval = true)
    -public class WriteResource implements RESTResource {
    -    private final Logger logger = LoggerFactory.getLogger(WriteResource.class);
    -
    -    private final EventPublisher eventPublisher;
    -    private final ItemRegistry itemRegistry;
    -
    -    private @Context @NonNullByDefault({}) UriInfo uriInfo;
    -
    -    @Activate
    -    public WriteResource(final @Reference EventPublisher eventPublisher, final @Reference ItemRegistry itemRegistry) {
    -        this.eventPublisher = eventPublisher;
    -        this.itemRegistry = itemRegistry;
    -    }
    -
    -    @GET
    -    @Produces(MediaType.APPLICATION_JSON)
    -    @Operation(summary = "starts defined actions e.g. downloading the CometVisu client", responses = {
    -            @ApiResponse(responseCode = "200", description = "OK"),
    -            @ApiResponse(responseCode = "404", description = "Item not found") })
    -    public Response setState(@Context HttpHeaders headers,
    -            @Parameter(description = "Item name", required = true) @QueryParam("a") String itemName,
    -            @Parameter(description = "Item value", required = true) @QueryParam("v") String value,
    -            @Parameter(description = "timestamp") @QueryParam("ts") long timestamp) {
    -        if (logger.isDebugEnabled()) {
    -            logger.debug("Received CV write request at '{}' for item '{}' with value '{}'.", uriInfo.getPath(),
    -                    itemName, value);
    -        }
    -        Item item;
    -        try {
    -            item = itemRegistry.getItem(itemName);
    -            Command command = TypeParser.parseCommand(item.getAcceptedCommandTypes(), value);
    -            SuccessBean bean = new SuccessBean();
    -            if (command != null) {
    -                eventPublisher.post(ItemEventFactory.createCommandEvent(item.getName(), command));
    -                bean.success = 1;
    -            } else {
    -                bean.success = 0;
    -            }
    -            return Response.ok(bean, MediaType.APPLICATION_JSON).build();
    -        } catch (ItemNotFoundException e) {
    -            logger.error("{}", e.getLocalizedMessage());
    -            return Response.status(Status.NOT_FOUND).build();
    -        }
    -    }
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/backend/sitemap/ConfigHelper.java+1 1 modified
    @@ -767,7 +767,7 @@ public String getLabel(Widget widget) {
         public void addSeparatorToNavbar(Page page, NavbarPositionType position, boolean ifNotEmpty) {
             Navbar navbar = getNavbar(page, position);
             if (navbar != null) {
    -            if (!ifNotEmpty || navbar.getPageOrGroupOrLine().size() > 0) {
    +            if (!ifNotEmpty || !navbar.getPageOrGroupOrLine().isEmpty()) {
                     Line line = new Line();
                     line.setLayout(createLayout(0));
                     navbar.getPageOrGroupOrLine().add(factory.createNavbarLine(line));
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/listeners/ItemRegistryEventListener.java+0 77 removed
    @@ -1,77 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.listeners;
    -
    -import java.util.Collection;
    -
    -import org.openhab.core.items.Item;
    -import org.openhab.core.items.ItemRegistry;
    -import org.openhab.core.items.ItemRegistryChangeListener;
    -import org.openhab.ui.cometvisu.internal.backend.rest.EventBroadcaster;
    -import org.osgi.service.component.annotations.Component;
    -import org.osgi.service.component.annotations.Reference;
    -
    -/**
    - * Listener responsible for notifying the CometVisu backend about changes
    - * in the ItemRegistry
    - *
    - * @author Tobias Bräutigam - Initial Contribution and API
    - */
    -@Component(immediate = true)
    -public class ItemRegistryEventListener implements ItemRegistryChangeListener {
    -    private ItemRegistry itemRegistry;
    -
    -    private EventBroadcaster eventBroadcaster;
    -
    -    @Reference
    -    protected void setEventBroadcaster(EventBroadcaster eventBroadcaster) {
    -        this.eventBroadcaster = eventBroadcaster;
    -    }
    -
    -    protected void unsetEventBroadcaster(EventBroadcaster eventBroadcaster) {
    -        this.eventBroadcaster = null;
    -    }
    -
    -    @Reference
    -    protected void setItemRegistry(ItemRegistry itemRegistry) {
    -        this.itemRegistry = itemRegistry;
    -        this.itemRegistry.addRegistryChangeListener(this);
    -    }
    -
    -    protected void unsetItemRegistry(ItemRegistry itemRegistry) {
    -        this.itemRegistry.removeRegistryChangeListener(this);
    -        this.itemRegistry = null;
    -    }
    -
    -    @Override
    -    public void added(Item element) {
    -        eventBroadcaster.registerItem(element);
    -    }
    -
    -    @Override
    -    public void removed(Item element) {
    -        eventBroadcaster.unregisterItem(element);
    -    }
    -
    -    @Override
    -    public void updated(Item oldElement, Item element) {
    -        eventBroadcaster.unregisterItem(oldElement);
    -        eventBroadcaster.registerItem(element);
    -    }
    -
    -    @Override
    -    public void allItemsChanged(Collection<String> oldItemNames) {
    -        // All items have changed, StateListener needs to be registered to the new Items
    -        eventBroadcaster.registerItems();
    -    }
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/listeners/StateEventListener.java+0 87 removed
    @@ -1,87 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.listeners;
    -
    -import java.util.Map;
    -
    -import org.eclipse.jdt.annotation.Nullable;
    -import org.openhab.core.items.GroupItem;
    -import org.openhab.core.items.Item;
    -import org.openhab.core.items.StateChangeListener;
    -import org.openhab.core.types.State;
    -import org.openhab.ui.cometvisu.internal.backend.model.StateBean;
    -import org.openhab.ui.cometvisu.internal.backend.rest.EventBroadcaster;
    -
    -/**
    - * listens to state changes on items and send them to an EventBroadcaster
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - */
    -public class StateEventListener implements StateChangeListener {
    -
    -    private EventBroadcaster eventBroadcaster;
    -
    -    public StateEventListener(EventBroadcaster eventBroadcaster) {
    -        this.eventBroadcaster = eventBroadcaster;
    -    }
    -
    -    public void setEventBroadcaster(EventBroadcaster eventBroadcaster) {
    -        this.eventBroadcaster = eventBroadcaster;
    -    }
    -
    -    protected void unsetEventBroadcaster(EventBroadcaster eventBroadcaster) {
    -        this.eventBroadcaster = null;
    -    }
    -
    -    @Override
    -    public void stateChanged(Item item, State oldState, State newState) {
    -        Map<String, @Nullable Class<? extends State>> clientItems = eventBroadcaster.getClientItems(item);
    -        if (!clientItems.isEmpty()) {
    -            for (String cvItemName : clientItems.keySet()) {
    -                Class<? extends State> stateClass = clientItems.get(cvItemName);
    -                StateBean stateBean = new StateBean();
    -                stateBean.name = cvItemName;
    -                if (stateClass != null) {
    -                    stateBean.state = item.getStateAs(stateClass).toString();
    -                } else {
    -                    stateBean.state = item.getState().toString();
    -                }
    -                eventBroadcaster.broadcastEvent(stateBean);
    -            }
    -        } else {
    -            StateBean stateBean = new StateBean();
    -            stateBean.name = item.getName();
    -            stateBean.state = newState.toString();
    -            eventBroadcaster.broadcastEvent(stateBean);
    -        }
    -    }
    -
    -    @Override
    -    public void stateUpdated(Item item, State state) {
    -        if (item instanceof GroupItem) {
    -            // group item update could be relevant for the client, although the state of switch group does not change
    -            // wenn more the one are on, the number-groupFunction changes
    -            Map<String, @Nullable Class<? extends State>> clientItems = eventBroadcaster.getClientItems(item);
    -            for (String cvItemName : clientItems.keySet()) {
    -                Class<? extends State> stateClass = clientItems.get(cvItemName);
    -                if (stateClass != null) {
    -                    StateBean stateBean = new StateBean();
    -                    stateBean.name = cvItemName;
    -                    stateBean.state = item.getStateAs(stateClass).toString();
    -
    -                    eventBroadcaster.broadcastEvent(stateBean);
    -                }
    -            }
    -        }
    -    }
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/ManagerSettings.java+11 9 modified
    @@ -109,16 +109,18 @@ private void refreshMounts() {
             for (final String target : Config.mountPoints.keySet()) {
                 if (!target.contains("..") && !"demo".equalsIgnoreCase(target)) {
                     String value = (String) Config.mountPoints.get(target);
    -                String[] parts = value.split(":");
    -                String source = parts[0];
    -                if (!source.contains("..") || (allowLookup && lookupMount.matcher(source).find())) {
    -                    boolean writeable = parts.length > 1 && parts[1].contains("w");
    -                    boolean showSubDirs = parts.length > 1 && parts[1].contains("s");
    -                    if (source.startsWith(File.separator)) {
    -                        source = source.substring(1);
    +                if (value != null) {
    +                    String[] parts = value.split(":");
    +                    String source = parts[0];
    +                    if (!source.contains("..") || (allowLookup && lookupMount.matcher(source).find())) {
    +                        boolean writeable = parts.length > 1 && parts[1].contains("w");
    +                        boolean showSubDirs = parts.length > 1 && parts[1].contains("s");
    +                        if (source.startsWith(File.separator)) {
    +                            source = source.substring(1);
    +                        }
    +                        MountPoint mount = new MountPoint(Paths.get(target), Paths.get(source), showSubDirs, writeable);
    +                        mounts.add(mount);
                         }
    -                    MountPoint mount = new MountPoint(Paths.get(target), Paths.get(source), showSubDirs, writeable);
    -                    mounts.add(mount);
                     }
                 }
             }
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/servlet/CometVisuServlet.java+17 2 modified
    @@ -20,7 +20,6 @@
     import java.io.OutputStream;
     import java.io.PrintWriter;
     import java.io.RandomAccessFile;
    -import java.io.UnsupportedEncodingException;
     import java.net.URLDecoder;
     import java.nio.charset.StandardCharsets;
     import java.text.DateFormat;
    @@ -45,6 +44,7 @@
     import javax.servlet.http.HttpServletResponse;
     import javax.ws.rs.core.MediaType;
     
    +import org.eclipse.jdt.annotation.NonNullByDefault;
     import org.eclipse.jdt.annotation.Nullable;
     import org.openhab.core.OpenHAB;
     import org.openhab.core.items.Item;
    @@ -72,6 +72,7 @@
      *
      * @author Tobias Bräutigam - Initial contribution
      */
    +@NonNullByDefault
     public class CometVisuServlet extends HttpServlet {
         private static final long serialVersionUID = 4448918908615003303L;
         private final Logger logger = LoggerFactory.getLogger(CometVisuServlet.class);
    @@ -138,6 +139,10 @@ public final void init(@Nullable ServletConfig config) throws ServletException {
         @Override
         protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
             File requestedFile = getRequestedFile(req);
    +        if (requestedFile == null) {
    +            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
    +            return;
    +        }
     
             String path = req.getPathInfo();
             if (path == null) {
    @@ -168,6 +173,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
                     }
                 }
             }
    +        if (requestedFile.getName().equalsIgnoreCase("version")) {
    +            // tell client that its been served by openhab
    +            resp.setHeader("X-CometVisu-Backend-Name", "openhab");
    +        }
             if (requestedFile.getName().equalsIgnoreCase("hidden.php")) {
                 // do not deliver the hidden php
                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
    @@ -178,7 +187,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
             }
         }
     
    -    protected File getRequestedFile(HttpServletRequest req) throws UnsupportedEncodingException {
    +    protected @Nullable File getRequestedFile(HttpServletRequest req) throws IOException {
             String requestedFile = req.getPathInfo();
             File file = null;
     
    @@ -188,12 +197,18 @@ protected File getRequestedFile(HttpServletRequest req) throws UnsupportedEncodi
                     requestedFile = requestedFile.substring(0, requestedFile.length() - 1);
                 }
                 file = new File(userFileFolder, URLDecoder.decode(requestedFile, StandardCharsets.UTF_8));
    +            if (!file.getCanonicalPath().startsWith(userFileFolder.getCanonicalPath() + File.separator)) {
    +                return null;
    +            }
             }
             // serve the file from the cometvisu src directory
             if (file == null || !file.exists() || file.isDirectory()) {
                 file = requestedFile != null
                         ? new File(rootFolder, URLDecoder.decode(requestedFile, StandardCharsets.UTF_8))
                         : rootFolder;
    +            if (!file.getCanonicalPath().startsWith(rootFolder.getCanonicalPath() + File.separator)) {
    +                return null;
    +            }
             }
             if (file.isDirectory()) {
                 // search for an index file
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/StateBeanMessageBodyWriter.java+0 89 removed
    @@ -1,89 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal;
    -
    -import java.io.DataOutputStream;
    -import java.io.IOException;
    -import java.io.OutputStream;
    -import java.lang.annotation.Annotation;
    -import java.lang.reflect.Type;
    -import java.util.ArrayList;
    -import java.util.List;
    -
    -import javax.ws.rs.Produces;
    -import javax.ws.rs.WebApplicationException;
    -import javax.ws.rs.core.MediaType;
    -import javax.ws.rs.core.MultivaluedMap;
    -import javax.ws.rs.ext.MessageBodyWriter;
    -import javax.ws.rs.ext.Provider;
    -
    -import org.openhab.ui.cometvisu.internal.backend.model.StateBean;
    -
    -/**
    - * {@link StateBeanMessageBodyWriter} is used to serialize state update messages
    - * for the CometVisu client
    - *
    - * @author Tobias Bräutigam - Initial contribution
    - */
    -@Provider
    -@Produces(MediaType.APPLICATION_JSON)
    -public class StateBeanMessageBodyWriter implements MessageBodyWriter<Object> {
    -
    -    @Override
    -    public long getSize(Object arg0, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4) {
    -        // deprecated by JAX-RS 2.0 and ignored by Jersey runtime
    -        return 0;
    -    }
    -
    -    @Override
    -    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] arg2, MediaType arg3) {
    -        return (type == StateBean.class || genericType == StateBean.class);
    -    }
    -
    -    @Override
    -    public void writeTo(Object stateBean, Class<?> type, Type genericType, Annotation[] annotations,
    -            MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
    -            throws IOException, WebApplicationException {
    -        StringBuilder sb = new StringBuilder();
    -        sb.append(serialize(stateBean));
    -        try (DataOutputStream dos = new DataOutputStream(entityStream)) {
    -            dos.writeUTF(sb.toString());
    -        }
    -    }
    -
    -    /**
    -     *
    -     * @param bean
    -     *            - StateBean or List<StateBean>
    -     * @return String
    -     *         - CV-Protocol state update json format {d:{item:state,...}}
    -     */
    -    public String serialize(Object bean) {
    -        String msg = "{\"d\":{";
    -        if (bean instanceof StateBean stateBean) {
    -            msg += "\"" + stateBean.name + "\":\"" + stateBean.state + "\"";
    -        } else if (bean instanceof List<?>) {
    -            List<String> states = new ArrayList<>();
    -            for (Object bo : (List<?>) bean) {
    -                if (bo instanceof StateBean stateBean) {
    -                    states.add("\"" + stateBean.name + "\":\"" + stateBean.state + "\"");
    -                }
    -            }
    -            if (!states.isEmpty()) {
    -                msg += String.join(",", states);
    -            }
    -        }
    -        msg += "}}";
    -        return msg;
    -    }
    -}
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/util/FsUtil.java+1 1 modified
    @@ -287,7 +287,7 @@ public FsEntry getEntry(MountedFile mfile, boolean recursive) {
         }
     
         public static Response createErrorResponse(FileOperationException e) {
    -        return FsUtil.createErrorResponse(e.getStatus(), e.getCause().toString());
    +        return FsUtil.createErrorResponse(e.getStatus(), e.getMessage());
         }
     
         public static Response createErrorResponse(Status status, String message) {
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/util/MountedFile.java+4 0 modified
    @@ -82,6 +82,10 @@ public boolean exists() {
             return toFile().exists();
         }
     
    +    public boolean canWrite() {
    +        return toFile().canWrite();
    +    }
    +
         public boolean isDirectory() {
             return toFile().isDirectory();
         }
    
  • bundles/org.openhab.ui.cometvisu/src/main/java/org/openhab/ui/cometvisu/internal/util/SseUtil.java+0 71 removed
    @@ -1,71 +0,0 @@
    -/**
    - * Copyright (c) 2010-2024 Contributors to the openHAB project
    - *
    - * See the NOTICE file(s) distributed with this work for additional
    - * information.
    - *
    - * This program and the accompanying materials are made available under the
    - * terms of the Eclipse Public License 2.0 which is available at
    - * http://www.eclipse.org/legal/epl-2.0
    - *
    - * SPDX-License-Identifier: EPL-2.0
    - */
    -package org.openhab.ui.cometvisu.internal.util;
    -
    -import java.util.Date;
    -
    -import javax.ws.rs.core.MediaType;
    -import javax.ws.rs.sse.OutboundSseEvent;
    -
    -import org.eclipse.jdt.annotation.NonNullByDefault;
    -import org.openhab.ui.cometvisu.internal.StateBeanMessageBodyWriter;
    -import org.openhab.ui.cometvisu.internal.backend.model.StateBean;
    -
    -/**
    - * Utility class containing helper methods for the SSE implementation.
    - *
    - * @author Tobias Bräutigam - Initial Contribution and API
    - * @author Wouter Born - Migrated to JAX-RS Whiteboard Specification
    - */
    -@NonNullByDefault
    -public class SseUtil {
    -
    -    /**
    -     * Creates a new {@link OutboundSseEvent} object containing an
    -     * {@link StateBean} created for the given eventType, objectIdentifier,
    -     * eventObject.
    -     *
    -     * @param eventBuilder the builder used for building the event
    -     * @param eventObject the eventObject to be included
    -     * @return a new OutboundSseEvent
    -     */
    -    public static OutboundSseEvent buildEvent(OutboundSseEvent.Builder eventBuilder, Object eventObject) {
    -        StateBeanMessageBodyWriter writer = new StateBeanMessageBodyWriter();
    -        Date date = new Date();
    -        return eventBuilder.mediaType(MediaType.TEXT_PLAIN_TYPE).data(writer.serialize(eventObject))
    -                .id(String.valueOf(date.getTime())).build();
    -    }
    -
    -    /**
    -     * Used to mark our current thread(request processing) that SSE blocking
    -     * should be enabled.
    -     */
    -    private static ThreadLocal<Boolean> blockingSseEnabled = ThreadLocal.withInitial(() -> false);
    -
    -    /**
    -     * Returns true if the current thread is processing an SSE request that
    -     * should block.
    -     *
    -     * @return
    -     */
    -    public static boolean shouldAsyncBlock() {
    -        return blockingSseEnabled.get().booleanValue();
    -    }
    -
    -    /**
    -     * Marks the current thread as processing a blocking sse request.
    -     */
    -    public static void enableBlockingSse() {
    -        blockingSseEnabled.set(true);
    -    }
    -}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.