snoopdave commented on code in PR #154: URL: https://github.com/apache/roller/pull/154#discussion_r4174859445
########## app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomServlet.java: ########## @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package org.apache.roller.weblogger.webservices.atomprotocol; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Writer; +import java.util.Collections; +import java.util.Locale; + +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.jdom2.Document; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.rometools.propono.atom.common.AtomService; +import com.rometools.propono.atom.common.Categories; +import com.rometools.propono.atom.server.AtomException; +import com.rometools.propono.atom.server.AtomHandler; +import com.rometools.propono.atom.server.AtomMediaResource; +import com.rometools.propono.atom.server.AtomRequest; +import com.rometools.rome.feed.atom.Content; +import com.rometools.rome.feed.atom.Entry; +import com.rometools.rome.feed.atom.Feed; +import com.rometools.rome.feed.atom.Link; +import com.rometools.rome.io.WireFeedOutput; +import com.rometools.rome.io.impl.Atom10Generator; +import com.rometools.rome.io.impl.Atom10Parser; +import org.apache.roller.weblogger.util.Utilities; + +/** + * Forked from rome-propono's AtomServlet to remove the dependency on propono's + * servlet class, which has no Jakarta-compatible release. This servlet directly + * creates a {@link RollerAtomHandler} instead of going through propono's + * AtomHandlerFactory/FactoryFinder lookup. + * + * <p>Handles Atom Publishing Protocol requests by parsing incoming XML into + * ROME Atom {@link Entry} objects, passing those to the handler, and + * serializing entries and feeds returned by the handler to the response.</p> + */ +public class RollerAtomServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + /** Feed type supported by this servlet */ + public static final String FEED_TYPE = "atom_1.0"; + + private static String contextDirPath = null; + + private static final Logger LOG = LoggerFactory.getLogger(RollerAtomServlet.class); + + static { + Atom10Parser.setResolveURIs(true); + } + + /** + * Create an Atom request handler directly, bypassing propono's factory lookup. + */ + private AtomHandler createAtomRequestHandler(final HttpServletRequest request, final HttpServletResponse response) { + return new RollerAtomHandler(request, response); + } + + /** + * Handles an Atom GET by calling handler and writing results to response. + */ + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException { + LOG.debug("Entering"); + final AtomHandler handler = createAtomRequestHandler(req, res); + final String userName = handler.getAuthenticatedUsername(); + if (userName != null) { + final AtomRequest areq = new RollerAtomRequestImpl(req); + try { + if (handler.isAtomServiceURI(areq)) { + // return an Atom Service document + final AtomService service = handler.getAtomService(areq); + final Document doc = service.serviceToDocument(); + res.setContentType("application/atomsvc+xml; charset=utf-8"); + final Writer writer = res.getWriter(); + final XMLOutputter outputter = new XMLOutputter(); + outputter.setFormat(Format.getPrettyFormat()); + outputter.output(doc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } else if (handler.isCategoriesURI(areq)) { + final Categories cats = handler.getCategories(areq); + res.setContentType("application/xml"); + final Writer writer = res.getWriter(); + final Document catsDoc = new Document(); + catsDoc.setRootElement(cats.categoriesToElement()); + final XMLOutputter outputter = new XMLOutputter(); + outputter.output(catsDoc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } else if (handler.isCollectionURI(areq)) { + // return a collection + final Feed col = handler.getCollection(areq); + col.setFeedType(FEED_TYPE); + final WireFeedOutput wireFeedOutput = new WireFeedOutput(); + final Document feedDoc = wireFeedOutput.outputJDom(col); + res.setContentType("application/atom+xml; charset=utf-8"); + final Writer writer = res.getWriter(); + final XMLOutputter outputter = new XMLOutputter(); + outputter.setFormat(Format.getPrettyFormat()); + outputter.output(feedDoc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } else if (handler.isEntryURI(areq)) { + // return an entry + final Entry entry = handler.getEntry(areq); + if (entry != null) { + res.setContentType("application/atom+xml; type=entry; charset=utf-8"); + final Writer writer = res.getWriter(); + Atom10Generator.serializeEntry(entry, writer); + writer.close(); + } else { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } else if (handler.isMediaEditURI(areq)) { + final AtomMediaResource entry = handler.getMediaResource(areq); + res.setContentType(entry.getContentType()); + res.setContentLength((int) entry.getContentLength()); + Utilities.copyInputToOutput(entry.getInputStream(), res.getOutputStream()); Review Comment: 🐞Claude Issue: **Blocking:** This line triggers the CodeQL `java/xss` alert (high) at `Utilities.java:471`. The code was in Propono's jar before, so CodeQL never saw it. Now that it is in Roller, it serves stored media with only the stored `Content-Type`, which differs from `ResourceServlet` and `PreviewResourceServlet`. Use the same policy here: ```java MediaTypePolicy.applyResponseHeaders(res, entry.getContentType(), <file name>); ``` This adds `nosniff` and serves non-inline-safe types as attachments. It should also clear the alert. ########## app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomServlet.java: ########## @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package org.apache.roller.weblogger.webservices.atomprotocol; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Writer; +import java.util.Collections; +import java.util.Locale; + +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.jdom2.Document; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.rometools.propono.atom.common.AtomService; +import com.rometools.propono.atom.common.Categories; +import com.rometools.propono.atom.server.AtomException; +import com.rometools.propono.atom.server.AtomHandler; +import com.rometools.propono.atom.server.AtomMediaResource; +import com.rometools.propono.atom.server.AtomRequest; +import com.rometools.rome.feed.atom.Content; +import com.rometools.rome.feed.atom.Entry; +import com.rometools.rome.feed.atom.Feed; +import com.rometools.rome.feed.atom.Link; +import com.rometools.rome.io.WireFeedOutput; +import com.rometools.rome.io.impl.Atom10Generator; +import com.rometools.rome.io.impl.Atom10Parser; +import org.apache.roller.weblogger.util.Utilities; + +/** + * Forked from rome-propono's AtomServlet to remove the dependency on propono's + * servlet class, which has no Jakarta-compatible release. This servlet directly + * creates a {@link RollerAtomHandler} instead of going through propono's + * AtomHandlerFactory/FactoryFinder lookup. + * + * <p>Handles Atom Publishing Protocol requests by parsing incoming XML into + * ROME Atom {@link Entry} objects, passing those to the handler, and + * serializing entries and feeds returned by the handler to the response.</p> + */ +public class RollerAtomServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + /** Feed type supported by this servlet */ + public static final String FEED_TYPE = "atom_1.0"; + + private static String contextDirPath = null; + + private static final Logger LOG = LoggerFactory.getLogger(RollerAtomServlet.class); + + static { + Atom10Parser.setResolveURIs(true); + } + + /** + * Create an Atom request handler directly, bypassing propono's factory lookup. + */ + private AtomHandler createAtomRequestHandler(final HttpServletRequest request, final HttpServletResponse response) { + return new RollerAtomHandler(request, response); + } + + /** + * Handles an Atom GET by calling handler and writing results to response. + */ + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException { + LOG.debug("Entering"); + final AtomHandler handler = createAtomRequestHandler(req, res); + final String userName = handler.getAuthenticatedUsername(); + if (userName != null) { + final AtomRequest areq = new RollerAtomRequestImpl(req); + try { + if (handler.isAtomServiceURI(areq)) { + // return an Atom Service document + final AtomService service = handler.getAtomService(areq); + final Document doc = service.serviceToDocument(); + res.setContentType("application/atomsvc+xml; charset=utf-8"); + final Writer writer = res.getWriter(); + final XMLOutputter outputter = new XMLOutputter(); + outputter.setFormat(Format.getPrettyFormat()); + outputter.output(doc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } else if (handler.isCategoriesURI(areq)) { + final Categories cats = handler.getCategories(areq); + res.setContentType("application/xml"); + final Writer writer = res.getWriter(); + final Document catsDoc = new Document(); + catsDoc.setRootElement(cats.categoriesToElement()); + final XMLOutputter outputter = new XMLOutputter(); + outputter.output(catsDoc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } else if (handler.isCollectionURI(areq)) { + // return a collection + final Feed col = handler.getCollection(areq); + col.setFeedType(FEED_TYPE); + final WireFeedOutput wireFeedOutput = new WireFeedOutput(); + final Document feedDoc = wireFeedOutput.outputJDom(col); + res.setContentType("application/atom+xml; charset=utf-8"); + final Writer writer = res.getWriter(); + final XMLOutputter outputter = new XMLOutputter(); + outputter.setFormat(Format.getPrettyFormat()); + outputter.output(feedDoc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } else if (handler.isEntryURI(areq)) { + // return an entry + final Entry entry = handler.getEntry(areq); + if (entry != null) { + res.setContentType("application/atom+xml; type=entry; charset=utf-8"); + final Writer writer = res.getWriter(); + Atom10Generator.serializeEntry(entry, writer); + writer.close(); + } else { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } else if (handler.isMediaEditURI(areq)) { + final AtomMediaResource entry = handler.getMediaResource(areq); + res.setContentType(entry.getContentType()); + res.setContentLength((int) entry.getContentLength()); + Utilities.copyInputToOutput(entry.getInputStream(), res.getOutputStream()); + res.getOutputStream().flush(); + res.getOutputStream().close(); + } else { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } catch (final AtomException ae) { + res.sendError(ae.getStatus(), ae.getMessage()); + LOG.debug("An error occurred while processing GET", ae); + } catch (final Exception e) { + res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + LOG.debug("An error occurred while processing GET", e); + } + } else { + res.setHeader("WWW-Authenticate", "BASIC realm=\"AtomPub\""); + res.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } + LOG.debug("Exiting"); + } + + /** + * Handles an Atom POST by calling handler to identify URI, reading/parsing + * data, calling handler and writing results to response. + */ + @Override + protected void doPost(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException { + LOG.debug("Entering"); + final AtomHandler handler = createAtomRequestHandler(req, res); + final String userName = handler.getAuthenticatedUsername(); + if (userName != null) { + final AtomRequest areq = new RollerAtomRequestImpl(req); + try { + if (handler.isCollectionURI(areq)) { + + final String contentType = req.getContentType(); + if (contentType != null && contentType.startsWith("application/atom+xml")) { + + // parse incoming entry + final Entry entry = Atom10Parser.parseEntry( Review Comment: 🐞Claude Issue: **Important:** #198 adds AtomPub request handling for `roller-6.1.x` by subclassing Propono's `AtomServlet`. On this branch, web.xml points at this fork instead, so that change cannot be ported by subclassing. Its behaviour needs to go directly into this class: the per-request `webservices.enableAtomPub` check (404 when off), and reading entry bodies on POST and PUT (here and at line 289) with Roller's shared parser configuration (`SafeSAXBuilder`) before building the `Entry`. Otherwise `master` and 6.1.7 diverge. Doing it in the fork also avoids the extra buffering pass. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
