//: "The contents of this file are subject to the Mozilla Public License
//: Version 1.1 (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.mozilla.org/MPL/
//:
//: Software distributed under the License is distributed on an "AS IS"
//: basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//: License for the specific language governing rights and limitations
//: under the License.
//:
//: The Original Code is Guanxi (http://www.guanxi.uhi.ac.uk).
//:
//: The Initial Developer of the Original Code is Alistair Young alistair@codebrane.com
//: All Rights Reserved.
//:
package org.guanxi.common;
import java.io.*;
import java.net.URL;
import java.rmi.server.UID;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.TimeZone;
import java.util.zip.*;
import javax.servlet.http.HttpServletRequest;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.xml.security.exceptions.Base64DecodingException;
import org.apache.xml.security.utils.Base64;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.guanxi.common.definitions.Shibboleth;
import org.guanxi.xal.saml_2_0.metadata.EntitiesDescriptorDocument;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* <font size=5><b></b></font>
*
* @author Alistair Young alistair@smo.uhi.ac.uk
* @author matthew
*/
public class Utils {
/** OS dependent line engine, e.g. \n */
public static final String LINE_ENDING = System.getProperty("line.separator");
/** Doesn't provide compatibility with the compression format used by both GZIP and PKZIP */
public static final boolean RFC1951_WRAP = false;
/** Provides compatibility with the compression format used by both GZIP and PKZIP */
public static final boolean RFC1951_NO_WRAP = true;
/** Shortcut for using default compression level */
public static final int RFC1951_DEFAULT_COMPRESSION_LEVEL = Deflater.DEFAULT_COMPRESSION;
/**
* Returns the parameters and their values from an HTTP request
*
* @param request HTTP request object
* @return Hashtable of parameters and their values
*/
public static Hashtable<String, String> getRequestParameters(HttpServletRequest request) {
Hashtable<String, String> params = new Hashtable<String, String>();
Enumeration<?> e = request.getParameterNames();
String name,value;
while (e.hasMoreElements()) {
name = (String)e.nextElement();
value = request.getParameter(name);
params.put(name, value);
}
return params;
}
public static String getUniqueID() {
UID uid = new UID();
return uid.toString();
}
public static String base64(Document inDocToEncode) {
try {
DOMSource domSource = new DOMSource(inDocToEncode);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
return Base64.encode(writer.toString().getBytes());
}
catch(TransformerException te) {
return null;
}
}
public static String base64(byte[] data) {
return Base64.encode(data);
}
public static String decodeBase64(String b64Data) {
try {
return new String(Base64.decode(b64Data));
}
catch(Base64DecodingException bde) {
return null;
}
}
public static byte[] decodeBase64b(String b64Data) {
try {
return Base64.decode(b64Data);
}
catch(Base64DecodingException bde) {
return null;
}
}
/**
* Gets the text value of a particular node in the config file
*
* @param configNode The parent node of the node we're interested in
* @param option The node whose text we want
* @return Text value of the node
*/
public static String getConfigOption(Node configNode, String option) {
NodeList childNodes = null;
int childCount = 0;
NodeList nodes = configNode.getChildNodes();
if (nodes == null) return "-1";
for (int count=0; count < nodes.getLength(); count++) {
if (nodes.item(count).getLocalName() == null) continue;
// Have we found the node we're looking for?
if (nodes.item(count).getLocalName().equalsIgnoreCase(option)) {
// Get the child nodes. We're looking for the text node containing the value
childNodes = nodes.item(count).getChildNodes();
for (childCount=0; childCount < childNodes.getLength(); childCount++) {
// Got a text node?
if (childNodes.item(childCount).getNodeType() == Node.TEXT_NODE) {
// Anything in it?
if (childNodes.item(childCount).getNodeValue() != null)
// Sometimes you can get blank text nodes with Xerces
if (!childNodes.item(childCount).getNodeValue().equalsIgnoreCase("")) {
return childNodes.item(childCount).getNodeValue();
}
}
}
}
}
return "-1";
}
public static void zipDirectory(String dir, ZipOutputStream zipStream) {
try {
File dirToZip = new File(dir);
// Have a gander in the directory to see what's there
String[] dirList = dirToZip.list();
byte[] readBuffer = new byte[2156];
int bytesIn = 0;
for (int count=0; count < dirList.length; count++) {
File dirFile = new File(dirToZip, dirList[count]);
// If the next entity is a directory, call recursively
if (dirFile.isDirectory()) {
zipDirectory(dirFile.getPath(), zipStream);
continue;
}
// Get a handle to the file...
FileInputStream fis = new FileInputStream(dirFile);
// ...get an entry ready for it ...
ZipEntry zipEntry = new ZipEntry(dirFile.getPath());
// ...bung it in the ZIP stream...
zipStream.putNextEntry(zipEntry);
// ...and read the data into the ZIP stream
while ((bytesIn = fis.read(readBuffer)) != -1) {
zipStream.write(readBuffer, 0, bytesIn);
}
fis.close();
}
}
catch(Exception e) {
System.out.println(e);
}
}
/**
* Creates an ID that is compatible with XML Schema
*
* @return ID that is compatible with XML Schema
*/
public static String createNCNameID() {
// xsd:NCName is derived from xsd:Name, which can't start with a number
String id = new UID().toString();
id = id.replaceAll(":", "-");
id = "GUANXI-" + id;
return id;
}
/**
* Parses a SAML2 Metadata document
*
* @param metadataURL The url of the metadata
* @return EntitiesDescriptorDocument for the metadata
* @throws GuanxiException if an error occurs
*/
public static EntitiesDescriptorDocument parseSAML2Metadata(String metadataURL) throws GuanxiException {
try {
return EntitiesDescriptorDocument.Factory.parse(new URL(metadataURL).openStream());
}
catch(Exception e) {
throw new GuanxiException(e);
}
}
/**
* Writes a SAML2 metadata document to disk
*
* @param saml2MetadataDoc The SAML2 metadata document
* @param filenameAndPath The full path and name of the file to write
* @throws GuanxiException if an error occurs
*/
public static void writeSAML2MetadataToDisk(EntitiesDescriptorDocument saml2MetadataDoc,
String filenameAndPath) throws GuanxiException {
HashMap<String, String> namespaces = new HashMap<String, String>();
ByteArrayOutputStream buffer;
OutputStream out;
namespaces.put(Shibboleth.NS_SAML_10_PROTOCOL, Shibboleth.NS_PREFIX_SAML_10_PROTOCOL);
namespaces.put(Shibboleth.NS_SAML_10_ASSERTION, Shibboleth.NS_PREFIX_SAML_10_ASSERTION);
XmlOptions xmlOptions = new XmlOptions();
xmlOptions.setSavePrettyPrint();
xmlOptions.setSavePrettyPrintIndent(2);
xmlOptions.setUseDefaultNamespace();
xmlOptions.setSaveAggressiveNamespaces();
xmlOptions.setSaveSuggestedPrefixes(namespaces);
xmlOptions.setSaveNamespacesFirst();
buffer = new ByteArrayOutputStream();
try {
saml2MetadataDoc.save(buffer, xmlOptions); // throws IOException, but shouldnt as ByteArrayOutputStream will not
out = new FileOutputStream(filenameAndPath);
try {
out.write(buffer.toByteArray());
}
finally {
out.close();
}
}
catch (IOException ioe) {
throw new GuanxiException(ioe);
}
}
/**
* Converts a local time to UTC Zulu format:
* 2005-06-21T11:13:29Z
*
* @param obj The XmlObject that contains the local time in an attribute
* @param interval the interval in minutes to add to any other time attrbutes
*/
public static void zuluXmlObject(XmlObject obj, int interval) {
SimpleDateFormat zulu = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
zulu.setTimeZone(TimeZone.getTimeZone("GMT"));
Calendar calNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar calAfter = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
calAfter.add(Calendar.MINUTE, interval);
NamedNodeMap attrs = obj.getDomNode().getAttributes();
for (int cc=0; cc < attrs.getLength(); cc++) {
Node attr = attrs.item(cc);
if (attr.getNodeName().equals("NotBefore")) {
attr.setNodeValue(zulu.format(calNow.getTime()));
}
if (attr.getNodeName().equals("NotOnOrAfter")) {
attr.setNodeValue(zulu.format(calAfter.getTime()));
}
if (attr.getNodeName().equals("IssueInstant")) {
attr.setNodeValue(zulu.format(calNow.getTime()));
}
if (attr.getNodeName().equals("AuthenticationInstant")) {
attr.setNodeValue(zulu.format(calNow.getTime()));
}
if (attr.getNodeName().equals("AuthnInstant")) {
attr.setNodeValue(zulu.format(calNow.getTime()));
}
}
}
/**
* Generates local time in UTC Zulu format:
* 2005-06-21T11:13:29Z
*
* @return The time now in Zulu format
*/
public static String zuluNow() {
SimpleDateFormat zulu = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
zulu.setTimeZone(TimeZone.getTimeZone("GMT"));
Calendar calNow = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
return zulu.format(calNow.getTime());
}
/**
* This reads the input stream completely, returning the result. The input stream is
* always in a closed state after calling this, even if an error has occurred.
*
* @param in stream to read from
* @return array of bytes read from the stream
* @throws GuanxiException if an error occurs
*/
public static byte[] read(InputStream in) throws GuanxiException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int read;
try {
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
return out.toByteArray();
}
catch(IOException ioe) {
// Reading from the stream failed
return null;
}
finally {
try {
in.close();
}
catch(IOException ioe) {
// Closing the stream failed. Shouldn't stop us returning the data though
}
}
}
/**
* Reconstitutes data deflated according to RFC1951
*
* @param deflatedData the deflated data to reconstitute
* @param useWrap if true then the ZLIB header and checksum fields will not be used.
* This provides compatibility with the compression format used by both GZIP and PKZIP.
* @return String representing the reconstituted data
* @throws GuanxiException if an error occurs
*/
public static String inflate(byte[] deflatedData, boolean useWrap) throws GuanxiException {
try {
byte[] inflatedData = new byte[(10 * deflatedData.length)];
Inflater decompresser = new Inflater(useWrap);
decompresser.setInput(deflatedData, 0, deflatedData.length);
int inflatedBytesLength = decompresser.inflate(inflatedData);
decompresser.end();
return new String(inflatedData, 0, inflatedBytesLength);
}
catch(DataFormatException dfe) {
throw new GuanxiException(dfe);
}
}
/**
* Deflates data according to RFC1951
*
* @param data the data to deflate
* @param compressionLevel the compression level to use
* @param useWrap if true then the ZLIB header and checksum fields will not be used.
* This provides compatibility with the compression format used by both GZIP and PKZIP.
* @return String representing the deflated data
*/
public static String deflate(String data, int compressionLevel, boolean useWrap) {
byte[] deflatedData = new byte[data.length()];
Deflater deflater = new Deflater(compressionLevel, useWrap);
deflater.setInput(data.getBytes());
deflater.finish();
int deflatedBytesLength = deflater.deflate(deflatedData);
return new String(deflatedData, 0, deflatedBytesLength);
}
}