Validate etc/gerrit.config file upon PUT request

Make sure that the content of an etc/gerrit.config file that is uploaded
by this plugin is syntactically correct by asserting that the
config can be loaded. No semantic validation is performed, however.

Change-Id: I400cca45ddb47e44aa304a304aabc00109736ab3
Signed-off-by: Adrian Görler <adrian.goerler@sap.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/serverconfig/ServerConfigServlet.java b/src/main/java/com/googlesource/gerrit/plugins/serverconfig/ServerConfigServlet.java
index 07f081d..da7c9c8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/serverconfig/ServerConfigServlet.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/serverconfig/ServerConfigServlet.java
@@ -14,6 +14,7 @@
 
 package com.googlesource.gerrit.plugins.serverconfig;
 
+import com.google.common.base.Charsets;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.io.ByteStreams;
@@ -29,7 +30,13 @@
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -37,6 +44,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.text.MessageFormat;
 
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -45,6 +53,8 @@
 @Singleton
 public class ServerConfigServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;
+  private static final Logger log = LoggerFactory
+      .getLogger(ServerConfigServlet.class);
 
   private final File site_path;
   private final File etc_dir;
@@ -94,20 +104,53 @@
     }
   }
 
-  private void writeFileAndFireAuditEvent(HttpServletRequest req, HttpServletResponse res)
-      throws IOException {
+  private void writeFileAndFireAuditEvent(HttpServletRequest req,
+      HttpServletResponse res) throws IOException {
     File oldFile = configFile(req);
     File dir = oldFile.getParentFile();
     File newFile = File.createTempFile(oldFile.getName(), ".new", dir);
     streamRequestToFile(req, newFile);
 
-    String diff = diff(oldFile, newFile);
-    audit("about to change config file", oldFile.getPath(), diff);
+    try {
+      FileBasedConfig config = new FileBasedConfig(newFile, FS.DETECTED);
+      config.load();
 
-    newFile.renameTo(oldFile);
-    audit("changed config file", oldFile.getPath(), diff);
+      String diff = diff(oldFile, newFile);
+      audit("about to change config file", oldFile.getPath(), diff);
 
-    res.setStatus(HttpServletResponse.SC_NO_CONTENT);
+      newFile.renameTo(oldFile);
+      audit("changed config file", oldFile.getPath(), diff);
+
+      res.setStatus(HttpServletResponse.SC_NO_CONTENT);
+    } catch (ConfigInvalidException e) {
+      log.warn("Configuration file is invalid", e);
+
+      Throwable cause = e.getCause();
+      final String msg =
+          cause instanceof ConfigInvalidException ? cause.getMessage()
+              : e.getMessage();
+
+      newFile.delete();
+      respondInvalidConfig(req, res, msg);
+    }
+  }
+
+  private void respondInvalidConfig(HttpServletRequest req,
+      HttpServletResponse res, String messageTxt) throws IOException {
+    String message =
+        MessageFormat.format("Invalid config file {0}: {1}", req.getPathInfo(),
+            messageTxt);
+    res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+    res.setContentType("application/octet-stream");
+    res.setContentLength(message.length());
+    byte[] bytes = message.getBytes(Charsets.UTF_8);
+    ByteArrayInputStream in = new ByteArrayInputStream(bytes);
+    OutputStream out = res.getOutputStream();
+    try {
+      ByteStreams.copy(in, out);
+    } finally {
+      in.close();
+    }
   }
 
   private static String diff(File oldFile, File newFile) throws IOException {