/* * Copyright 1998-2014 University Corporation for Atmospheric Research/Unidata * * Portions of this software were developed by the Unidata Program at the * University Corporation for Atmospheric Research. * * Access and use of this software shall impose the following obligations * and understandings on the user. The user is granted the right, without * any fee or cost, to use, copy, modify, alter, enhance and distribute * this software, and any derivative works thereof, and its supporting * documentation for any purpose whatsoever, provided that this entire * notice appears in all copies of the software, derivative works and * supporting documentation. Further, UCAR requests that the user credit * UCAR/Unidata in any publications that result from the use of this * software or in any product that includes this software. The names UCAR * and/or Unidata, however, may not be used in any advertising or publicity * to endorse or promote any products or commercial entity unless specific * written permission is obtained from UCAR/Unidata. The user also * understands that UCAR/Unidata is not obligated to provide the user with * any support, consulting, training or assistance of any kind with regard * to the use, operation and performance of this software nor to provide * the user with any updates, revisions, new versions or "bug fixes." * * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. */ package thredds.server.cdmvalidator; import com.coverity.security.Escape; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItem; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.springframework.web.servlet.mvc.AbstractController; import org.springframework.web.servlet.ModelAndView; import org.slf4j.MDC; import org.jdom2.Document; import org.jdom2.output.XMLOutputter; import org.jdom2.output.Format; import org.jdom2.transform.XSLTransformer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import thredds.servlet.UsageLog; import thredds.util.ContentType; import ucar.nc2.constants.CDM; import ucar.nc2.dataset.NetcdfDatasetInfo; import ucar.unidata.util.StringUtil2; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.Map; import java.util.HashMap; import java.net.URI; import java.net.URISyntaxException; /** * CdmValidator Spring Controller * * @author edavis * @since 4.0 */ public class CdmValidatorController extends AbstractController { private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CdmValidatorController.class); private CdmValidatorContext cdmValidatorContext; public void setCdmValidatorContext(CdmValidatorContext cdmValidatorContext) { this.cdmValidatorContext = cdmValidatorContext; } protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { log.info("handleRequestInternal(): " + UsageLog.setupRequestContext(request)); // Get the request path. String reqPath = request.getServletPath(); if (reqPath == null) { log.info("handleRequestInternal(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0)); response.sendError(HttpServletResponse.SC_NOT_FOUND); return null; } if (request.getMethod().equalsIgnoreCase("GET")) { if (reqPath.equals("/validate.html")) { Map<String, Object> model = new HashMap<>(); model.put("contextPath", request.getContextPath()); model.put("servletPath", request.getServletPath()); this.cdmValidatorContext.getHtmlConfig().addHtmlConfigInfoToModel(model); log.info("handleRequestInternal(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, -1)); return new ModelAndView("/thredds/server/cdmvalidator/cdmValidate", model); } else if (reqPath.equals("/validateHelp.html")) { Map<String, Object> model = new HashMap<>(); model.put("contextPath", request.getContextPath()); model.put("servletPath", request.getServletPath()); this.cdmValidatorContext.getHtmlConfig().addHtmlConfigInfoToModel(model); log.info("handleRequestInternal(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, -1)); return new ModelAndView("/thredds/server/cdmvalidator/cdmValidateHelp", model); } else if (reqPath.equals("/validate")) { this.doGet(request, response); } else { log.info("handleRequestInternal(): Unsupported path [" + reqPath + "] - " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0)); response.sendError(HttpServletResponse.SC_NOT_FOUND); return null; } } else if (request.getMethod().equalsIgnoreCase("POST")) { if (reqPath.equals("/validate")) { this.doPost(request, response); } else { log.info("handleRequestInternal(): Unsupported path [" + reqPath + "] - " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0)); response.sendError(HttpServletResponse.SC_NOT_FOUND); return null; } } return null; } /** * GET handles the case where its a remote URL (dods or http) * * @param req request * @param res response * @throws javax.servlet.ServletException * @throws java.io.IOException */ public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { log.info("doGet(): " + UsageLog.setupRequestContext(req)); String urlString = req.getParameter("URL"); if (urlString == null) { log.info("doGet(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, 0)); res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Must have a URL parameter"); return; } // validate the url String try { URI uri = new URI(urlString); urlString = uri.toASCIIString(); // LOOK do we want just toString() ? Is this useful "input validation" ? } catch (URISyntaxException e) { log.info("doGet(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, 0)); res.sendError(HttpServletResponse.SC_BAD_REQUEST, "URISyntaxException on URU parameter"); return; } String xml = req.getParameter("xml"); boolean wantXml = (xml != null) && xml.equals("true"); try { int len = showValidatorResults(res, urlString, wantXml); log.info("doGet(): URL = " + urlString); log.info("doGet(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, len)); } catch (Exception e) { log.info("doGet(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, 0)); res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid input"); } catch (Throwable e) { log.error("doGet(): Validator internal error", e); log.info("doGet(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 0)); res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Validator internal error"); } } /** * POST handles uploaded files * * @param req request * @param res response * @throws ServletException * @throws IOException */ public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { log.info("doPost(): " + UsageLog.setupRequestContext(req)); // Check that we have a file upload request boolean isMultipart = ServletFileUpload.isMultipartContent(req); if (!isMultipart) { log.info("doPost(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, 0)); res.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } //Create a new file upload handler ServletFileUpload upload = new ServletFileUpload(this.cdmValidatorContext.getFileuploadFileItemFactory()); upload.setSizeMax(this.cdmValidatorContext.getMaxFileUploadSize()); // maximum bytes before a FileUploadException will be thrown List<FileItem> fileItems; try { fileItems = (List<FileItem>) upload.parseRequest(req); } catch (FileUploadException e) { log.info("doPost(): Validator FileUploadException", e); log.info("doPost(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, 0)); if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } //Process the uploaded items String username = null; boolean wantXml = false; for (FileItem item : fileItems) { if (item.isFormField()) { if ("username".equals(item.getFieldName())) username = item.getString(); if ("xml".equals(item.getFieldName())) wantXml = item.getString().equals("true"); } } for (FileItem item : fileItems) { if (!item.isFormField()) { try { processUploadedFile(req, res, (DiskFileItem) item, username, wantXml); return; } catch (Exception e) { log.info("doPost(): Validator processUploadedFile", e); log.info("doPost(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, 0)); res.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } } } } private void processUploadedFile(HttpServletRequest req, HttpServletResponse res, DiskFileItem item, String username, boolean wantXml) throws Exception { if ((username == null) || (username.length() == 0)) username = "none"; username = Escape.html(StringUtil2.filter(username, "_")); String filename = Escape.html(item.getName()); filename = StringUtil2.replace(filename, "/", "-"); filename = StringUtil2.filter(filename, ".-_"); File uploadedFile = new File(this.cdmValidatorContext.getCacheDir() + "/" + username + "/" + filename); uploadedFile.getParentFile().mkdirs(); item.write(uploadedFile); int len = showValidatorResults(res, uploadedFile.getPath(), wantXml); if (this.cdmValidatorContext.isDeleteImmediately()) { try { uploadedFile.delete(); } catch (Exception e) { log.error("processUploadedFile(): Uploaded File = " + uploadedFile.getPath() + " delete failed = " + e.getMessage()); } } if (req.getRemoteUser() == null) { if (username != null) MDC.put("userid", username); } log.info("processUploadedFile(): Uploaded File = " + item.getName() + " sent to " + uploadedFile.getPath() + " size= " + uploadedFile.length()); log.info("processUploadedFile(): " + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, len)); } private int showValidatorResults(HttpServletResponse res, String location, boolean wantXml) throws Exception { try (NetcdfDatasetInfo info = new NetcdfDatasetInfo(location)) { String infoString; if (wantXml) { infoString = info.writeXML(); res.setContentLength(infoString.getBytes(CDM.utf8Charset).length); res.setContentType(ContentType.xml.getContentHeader()); } else { Document xml = info.makeDocument(); InputStream is = getXSLT(); XSLTransformer transformer = new XSLTransformer(is); Document html = transformer.transform(xml); XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat()); infoString = fmt.outputString(html); res.setContentType(ContentType.html.getContentHeader()); } res.setContentLength(infoString.getBytes(CDM.utf8Charset).length); OutputStream out = res.getOutputStream(); out.write(infoString.getBytes(CDM.utf8Charset)); out.flush(); return infoString.length(); } } private InputStream getXSLT() { Class c = CdmValidatorController.class; //String resource = "/WEB-INF/classes/resources/xsl/cdmValidation.xsl"; String resource = "/resources/xsl/cdmValidation.xsl"; InputStream is = c.getResourceAsStream(resource); if (null == is) log.error("getXSLT(): Cant load XSLT resource = " + resource); return is; } }