diff --git a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/crud/protocols/List.java b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/crud/protocols/List.java
new file mode 100644
index 000000000..c979388f8
--- /dev/null
+++ b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/crud/protocols/List.java
@@ -0,0 +1,222 @@
+package net.sourceforge.guacamole.net.basic.crud.protocols;
+
+/*
+ * Guacamole - Clientless Remote Desktop
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import net.sourceforge.guacamole.GuacamoleException;
+import net.sourceforge.guacamole.GuacamoleServerException;
+import net.sourceforge.guacamole.net.auth.UserContext;
+import net.sourceforge.guacamole.net.basic.AuthenticatingHttpServlet;
+import net.sourceforge.guacamole.net.basic.ProtocolInfo;
+import net.sourceforge.guacamole.net.basic.ProtocolParameter;
+import net.sourceforge.guacamole.net.basic.ProtocolParameterOption;
+import net.sourceforge.guacamole.net.basic.xml.DocumentHandler;
+import net.sourceforge.guacamole.net.basic.xml.protocol.ProtocolTagHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * Simple HttpServlet which outputs XML containing a list of all visible
+ * protocols.
+ *
+ * @author Michael Jumper
+ */
+public class List extends AuthenticatingHttpServlet {
+
+ /**
+ * Parses the given XML file, returning the parsed ProtocolInfo.
+ *
+ * @param input An input stream containing XML describing the parameters
+ * associated with a protocol supported by Guacamole.
+ * @return A new ProtocolInfo object which contains the parameters described
+ * by the XML file parsed.
+ * @throws GuacamoleException If an error occurs while parsing the XML file.
+ */
+ private ProtocolInfo getProtocol(InputStream input)
+ throws GuacamoleException {
+
+ // Parse document
+ try {
+
+ // Get handler for root element
+ ProtocolTagHandler protocolTagHandler =
+ new ProtocolTagHandler();
+
+ // Set up document handler
+ DocumentHandler contentHandler = new DocumentHandler(
+ "protocol", protocolTagHandler);
+
+ // Set up XML parser
+ XMLReader parser = XMLReaderFactory.createXMLReader();
+ parser.setContentHandler(contentHandler);
+
+ // Read and parse file
+ InputStream xml = new BufferedInputStream(input);
+ parser.parse(new InputSource(xml));
+ xml.close();
+
+ // Return parsed protocol
+ return protocolTagHandler.asProtocolInfo();
+
+ }
+ catch (IOException e) {
+ throw new GuacamoleException("Error reading basic user mapping file.", e);
+ }
+ catch (SAXException e) {
+ throw new GuacamoleException("Error parsing basic user mapping XML.", e);
+ }
+
+ }
+
+ /**
+ * Given an XML stream and a fully-populated ProtocolInfo object, writes
+ * out the corresponding protocol XML describing all available parameters.
+ *
+ * @param xml The XMLStreamWriter to use to write the XML.
+ * @param protocol The ProtocolInfo object to read parameters and protocol
+ * information from.
+ * @throws XMLStreamException If an error occurs while writing the XML.
+ */
+ private void writeProtocol(XMLStreamWriter xml, ProtocolInfo protocol)
+ throws XMLStreamException {
+
+ // Write protocol
+ xml.writeStartElement("protocol");
+ xml.writeAttribute("name", protocol.getName());
+ xml.writeAttribute("title", protocol.getTitle());
+
+ // Write parameters
+ for (ProtocolParameter param : protocol.getParameters()) {
+
+ // Write param tag
+ xml.writeStartElement("param");
+ xml.writeAttribute("name", param.getName());
+ xml.writeAttribute("title", param.getTitle());
+
+ // Write type
+ switch (param.getType()) {
+
+ // Text parameter
+ case TEXT:
+ xml.writeAttribute("type", "text");
+ break;
+
+ // Password parameter
+ case PASSWORD:
+ xml.writeAttribute("type", "password");
+ break;
+
+ // Numeric parameter
+ case NUMERIC:
+ xml.writeAttribute("type", "numeric");
+ break;
+
+ // Boolean parameter
+ case BOOLEAN:
+ xml.writeAttribute("type", "boolean");
+ break;
+
+ // Enumerated parameter
+ case ENUM:
+ xml.writeAttribute("type", "enum");
+ break;
+
+ // If unknown, fail explicitly
+ default:
+ throw new UnsupportedOperationException(
+ "Parameter type not supported: " + param.getType());
+
+ }
+
+ // Write options
+ for (ProtocolParameterOption option : param.getOptions()) {
+ xml.writeStartElement("option");
+ xml.writeAttribute("value", option.getValue());
+ xml.writeCharacters(option.getTitle());
+ xml.writeEndElement();
+ }
+
+ // End parameter
+ xml.writeEndElement();
+
+ }
+
+ // End protocol
+ xml.writeEndElement();
+
+ }
+
+ @Override
+ protected void authenticatedService(
+ UserContext context,
+ HttpServletRequest request, HttpServletResponse response)
+ throws GuacamoleException {
+
+ // Do not cache
+ response.setHeader("Cache-Control", "no-cache");
+
+ // Write actual XML
+ try {
+
+ // Write XML content type
+ response.setHeader("Content-Type", "text/xml");
+
+ XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();
+ XMLStreamWriter xml = outputFactory.createXMLStreamWriter(response.getWriter());
+
+ // Begin document
+ xml.writeStartDocument();
+ xml.writeStartElement("protocols");
+
+ // Read from classpath
+ InputStream stream = List.class.getResourceAsStream(
+ "/net/sourceforge/guacamole/net/protocols/vnc.xml");
+ if (stream == null)
+ throw new IOException("Could not read VNC XML.");
+
+ // Parse and write protocol
+ ProtocolInfo protocol = getProtocol(stream);
+ writeProtocol(xml, protocol);
+
+ // End document
+ xml.writeEndElement();
+ xml.writeEndDocument();
+
+ }
+ catch (XMLStreamException e) {
+ throw new GuacamoleServerException(
+ "Unable to write protocol list XML.", e);
+ }
+ catch (IOException e) {
+ throw new GuacamoleServerException(
+ "I/O error writing protocol list XML.", e);
+ }
+
+ }
+
+}
diff --git a/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/crud/protocols/package-info.java b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/crud/protocols/package-info.java
new file mode 100644
index 000000000..db244170f
--- /dev/null
+++ b/guacamole/src/main/java/net/sourceforge/guacamole/net/basic/crud/protocols/package-info.java
@@ -0,0 +1,6 @@
+
+/**
+ * Servlets dedicated to CRUD operations related to protocols.
+ */
+package net.sourceforge.guacamole.net.basic.crud.protocols;
+
diff --git a/guacamole/src/main/resources/net/sourceforge/guacamole/net/protocols/vnc.xml b/guacamole/src/main/resources/net/sourceforge/guacamole/net/protocols/vnc.xml
new file mode 100644
index 000000000..ce80ab1c5
--- /dev/null
+++ b/guacamole/src/main/resources/net/sourceforge/guacamole/net/protocols/vnc.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/guacamole/src/main/webapp/WEB-INF/web.xml b/guacamole/src/main/webapp/WEB-INF/web.xml
index 2a2616de2..83f6c8998 100644
--- a/guacamole/src/main/webapp/WEB-INF/web.xml
+++ b/guacamole/src/main/webapp/WEB-INF/web.xml
@@ -154,6 +154,17 @@
/permissions
+
+
+ Protocol list servlet.
+ Protocols
+ net.sourceforge.guacamole.net.basic.crud.protocols.List
+
+
+ Protocols
+ /protocols
+
+
Tunnel servlet.