All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.protocol.ProtocolUtils Maven / Gradle / Ivy

The newest version!
/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property of
 * Adobe Systems Incorporated and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Adobe Systems
 * Incorporated and its suppliers and may be covered by U.S. and Foreign
 * Patents, patents in process, and are protected by trade secret or copyright
 * law. Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained from
 * Adobe Systems Incorporated.
 */
package com.adobe.xfa.protocol;


import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.security.SecureRandom;
import java.util.Random;

import com.adobe.xfa.Node;
import com.adobe.xfa.TextNode;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.Resolver;
import com.adobe.xfa.ut.StringHolder;
import com.adobe.xfa.ut.StringUtils;


/**
 * This class provides some utility methods to support some
 * protocol I/O capabilities.
 * 
 * @exclude from published api.
 */
public final class ProtocolUtils {
	
	private ProtocolUtils() {		
	}
	
	private final static byte[] CRLF = asciiBytes("\r\n");
	private final static byte[] DASH_DASH = asciiBytes("--");
	private final static byte[] CONTENT_DISPOSITION_FORM_DATA = asciiBytes("Content-Disposition: form-data; ");
	private final static byte[] NAME_EQUALS_QUOTE = asciiBytes("name=\"");
	private final static byte[] QUOTE = asciiBytes("\"");
	private final static byte[] SEMICOLON_SPACE = asciiBytes("; ");
	private final static byte[] FILENAME_EQUALS_QUOTE = asciiBytes("filename=\"");
	private final static byte[] CONTENT_TYPE = asciiBytes("nContent-Type: ");
	private final static byte[] CONTENT_TRANSFER_ENCODING_BINARY = asciiBytes("Content-Transfer-Encoding: binary");
	private final static byte[] IMAGE_GIF = asciiBytes("image/gif");
	private final static byte[] IMAGE_JPG = asciiBytes("image/jpeg");
	private final static byte[] TEXT_PLAIN = asciiBytes("text/plain");
	private final static byte[] FILE_NOT_FOUND = asciiBytes("[File not found]");
	
	private static byte[] asciiBytes(String s) {
		byte[] bytes = new byte[s.length()];
		for (int i = 0; i < s.length(); i++)
			bytes[i] = (byte)s.charAt(i);
		
		return bytes;
	}

	/**
	 * Reads bytes from the input stream into the given array of bytes.
	 * This method blocks until all input data is available, end of file is
	 * detected, or an exception is thrown.  Contrary to 
	 * {@link java.io.InputStream#read(byte[])}, this method will block
	 * until a full buffer's worth of data is read.
	 * @param b the buffer into which the data is read.
	 * @return the number of bytes read, or -1 when
	 * end of the stream has been reached.
	 * @see java.io.InputStream#read(byte[])
	 */
	public static int read(InputStream is, byte [] b) throws IOException {
		int offset = 0;
		do {
			int n = is.read(b, offset, b.length - offset);
			if (n != -1)
				offset += n;
			else /* if (n == -1) */
				break;
		} while (offset < b.length);
		if (offset == 0)
			return -1;
		return offset;
	}

	public static InputStream checkUrl(String sBaseUrl, String sPath,
								boolean bTrusted, StringHolder sRealUrl) {
		if (sRealUrl != null)
			sRealUrl.value = null;

		StringBuilder sBase = new StringBuilder(sBaseUrl);
		if (sBase.length() > 0) {
			if (sBase.charAt(sBase.length() - 1) != '/')
				sBase.append('/');
		}
		
		sPath = sPath.replace('\\', '/');
		
		URI uri = null;
		//
		// If path is relative then concatenate the url and path.
		//
		if (! isAbsolute(sPath)) {
			if (!StringUtils.isEmpty(sBaseUrl)) {
				// JavaPort: use URI.resolve() to concatenate the base url and path.
				URI baseURI = getUriForString(sBase.toString());
				uri = baseURI.resolve(sPath);
			}
			else if (bTrusted) {
				uri = getUriForString(sPath);
			}
		}
		//
		// Else path is absolute So only allow when trusted.
		//
		else if (bTrusted) {
			uri = getUriForString(sPath);
		}
		if (uri == null) 
			return null;

		// Javaport: hereon, the following differs from C++.
		URL url = null;
		String sScheme = uri.getScheme();
		Protocol protocol = Resolver.getProtocol(sScheme);
		URLStreamHandler streamHandler = null;
		if (protocol != null)
			streamHandler = protocol.getURLStreamHandler();
		
		try {
			if (streamHandler != null)
				url = new URL(null, uri.toString(), streamHandler);
			else
				url = uri.toURL();
		}
		catch (MalformedURLException ex) {
			return null;
		}
		
		InputStream resolved = null;
		try {
			resolved = url.openStream();
		}
		catch (IOException ex) {
			return null;
		}
	
		if (sRealUrl != null)
			sRealUrl.value = url.toString();

		return resolved;
	}
	
	private static URI getUriForString(String sPath) {
		
		try {
			URI uri = new URI(sPath);
			if (uri.getScheme() != null &&
				uri.getScheme().length() > 1)	// Windows drive letter? Assume no single-letter schemes.
				return uri;
		}
		catch (URISyntaxException e) {
		}
		
		File oPath = new File(sPath);
		return oPath.toURI();
	}
	
	public static boolean isAbsolute(String sPath) {		 
		try {
			URI uri = new URI(sPath);
			if (uri.getScheme() != null &&
				uri.getScheme().length() > 1)	// Windows drive letter? Assume no single-letter schemes.)
				return uri.isAbsolute();
		}
		catch (URISyntaxException e) {
		}
		
		File oPath = new File(sPath);
		return oPath.isAbsolute();
	}


	/**
	 * Normalizes the given base URL.  Base URLs are those from which
	 * relative URIs can be resolved.
	 * @param sBaseUrl a base URL.
	 */
	public static String normalizeBaseUrl(String sBaseUrl) {
		
		if (StringUtils.isEmpty(sBaseUrl))
			return sBaseUrl;
		
		//
		// Ensure msBaseUrl is terminated with a '/' for otherwise
		// relative URIs won't resolve correctly.
		//
		char cSep = getFileOrUriSeparator(sBaseUrl);
		int nLen = sBaseUrl.length();
		if (sBaseUrl.charAt(nLen - 1) != cSep)
			sBaseUrl += cSep;
		
		URI uri = null;
		try {
			uri = new URI(sBaseUrl);
		} 
		catch (URISyntaxException e) {
		}
		
		if (uri != null && uri.isOpaque()) {
			// A base path that looks like this: 'dest:somedir/'
			// will fail to resolve when combined with another file uri.
			// Adding a slash after the schema name will fix this. 
			String sScheme = uri.getScheme();
			String sSchemeSpecificPart = uri.getSchemeSpecificPart();
			if (sScheme != null && sSchemeSpecificPart != null) {
				sBaseUrl = sScheme + ":/" + sSchemeSpecificPart;
			}
		}

		return sBaseUrl;
	}

	/**
	 * Convenience method for opening an input URL without worrying about
	 * all the additional parameters.
	 * @param sUrl Name of resource to open.  This may be a local file or a
	 * remote location.
	 * @return Input stream corresponding to the URL opened; null if the
	 * operation fails.
	 */
	public static InputStream openUrl (String sUrl) {
		return checkUrl ("", sUrl, true, null);
	}

//	/*
//	 * Parse a HTTP header and extract its Content-Type value.
//	 *
//	 * Space for the returned string is malloc()'ed.  If there's
//	 * no Content-Type, an empty string is returned.
//	 */
//	public static String parserHeader(String src) {
//		throw new ExFull(ResId.UNSUPPORTED_OPERATION, "ProtocolUtils.parserHeader");
//	// Javaport: TODO
//	//	char* q = strstr(src, "\r\nContent-Type: ");
//	//	if (! q)
//	//		q = strstr(src, "\nContent-Type: ");
//	//	if (! q)
//	//		q = strstr(src, "\nContent-type: ");
//	//	char* p = q;
//	//	if (q) {
//	//		//
//	//		// Advance to start of Content-Type value.
//	//		//
//	//		while (*p++ != ' ')
//	//			;
//	//		//
//	//		// Advance to end of Content-Type value.
//	//		//
//	//		for (q = p; *q; q++) {
//	//			if (*q == '\r' || *q == '\n')
//	//				break;
//	//		}
//	//	}
//	//	//
//	//	// Strdup Content-Type value.
//	//	//
//	//	char* res = (char*) Malloc(q - p + 1);
//	//	strncpy(res, p, q - p);
//	//	res[q - p] = '\0';
//	//	return res;
//	}

	private static final int BOUNDARYSIZE = 32;

	//private static final int BUFSIZE = 4 * 1024;

	private static final char hexdigit[] = {
		'0', '1', '2', '3', '4', '5', '6', '7',
		'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
		'0', '1', '2', '3', '4', '5', '6', '7',
		'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 
	};

	/*
	 * As per RFC 1738, URLs syntax states that only alphanumerics
	 * and the special characters, $-_.+!*'(), and the reserved
	 * characters, ;/?:@&= (when used for their reserved purposes)
	 * may be unencoded within an URL.  All others are unsafe and
	 * need to be escaped.
	 */
	private static final byte urlsafe[] = {
		/*     0 1 2 3 4 5 6 7 8 9 A B C D E F */
		/*20*/ 0,1,0,0,1,0,0,1,1,1,1,0,1,1,1,1, /*  !"#$%&'()*+,-./ */
		/*30*/ 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, /* 0123456789:;<=>? */
		/*40*/ 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* @ABCDEFGHIJKLMNO */
		/*50*/ 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1, /* PQRSTUVWXYZ[\]^_ */
		/*60*/ 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* `abcdefghijklmno */
		/*70*/ 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0  /* pqrstuvwxyz{|}~? */
	};

	private static final byte[] base64 = {
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
		'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
		'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
		'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
		'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
		'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
		'w', 'x', 'y', 'z', '0', '1', '2', '3',
		'4', '5', '6', '7', '8', '9', '+', '/' 
	};
	

	/**
	 * Generates a MIME boundary string given an optional prefix.
	 */
	@FindBugsSuppress(code="NP")
	public static byte[] mimeBoundary(String prefix) {
		byte[] res = new byte[BOUNDARYSIZE - 1];
		int n = 0;
		if (prefix != null) {
			byte[] prefixBytes = null;
			try {
				prefixBytes = prefix.getBytes("US-ASCII");
			}
			catch (UnsupportedEncodingException ignored) {
				// not possible - US-ASCII is always supported
			}
			n = prefixBytes.length;
			if (n > BOUNDARYSIZE / 4)
				n = BOUNDARYSIZE / 4;
			
			System.arraycopy(prefixBytes, 0, res, 0, n);
		}
		
		//Bug#2742678
		//Generate a seed first from current time
		byte[] seed = new byte[8];
		long currentTime = System.currentTimeMillis();
		for (int i=0;i<8;++i) seed[i]=(byte)((currentTime>>i)&0xff);
		SecureRandom randomizer = new SecureRandom(seed);
		for (int i = n; i < BOUNDARYSIZE - 1; i++)
			res[n++] = base64[randomizer.nextInt(base64.length)];
		
		return res;
	}


//	/*
//	 * Write 'nobj' bytes from the memory 'mem' into area 'arena'.
//	 * Return the number of bytes written.
//	 */
//	public static int mimeWrite(/*char* mem, int nobj, struct arena_s* arena*/) {
//		throw new ExFull(ResId.UNSUPPORTED_OPERATION, "ProtocolUtils#mimeWrite");
//	// Javaport: TODO
//	//	int nwrote = nobj;
//	//	if (arena->used + nwrote > arena->size) {
//	//		do
//	//			arena->size.append(BUFSIZE);
//	//		while (arena->used + nwrote > arena->size);
//	//		arena->pbuf = (char*) Realloc(arena->pbuf, arena->size);
//	//	}
//	//	memcpy(&arena->pbuf[arena->used], mem, nwrote);
//	//	arena->used.append(nwrote);
//	//	return nwrote;
//	}


	/*
	 * Write an RFC 1867 compliant MIME section into area 'arena'.
	 */
	public static byte[] mimeSection(byte[] boundary, byte[] name, byte[] file,
									 byte[] type, byte[] value) {
		ByteArrayOutputStream sArena = new ByteArrayOutputStream();
		assert(boundary != null);
		assert((name != null) || (file != null));
		
		try {
		
			sArena.write(CRLF);
			sArena.write(DASH_DASH);
			sArena.write(boundary);
			sArena.write(CRLF);
			sArena.write(CONTENT_DISPOSITION_FORM_DATA);
			if (name != null) {
				sArena.write(NAME_EQUALS_QUOTE);
				sArena.write(name);
				sArena.write(QUOTE);
			}
			String fileName = null;
			if (file != null) {
				fileName = new String(file, "US-ASCII");				
				
				if (name != null)
					sArena.write(SEMICOLON_SPACE);
				sArena.write(FILENAME_EQUALS_QUOTE);
				sArena.write(file);
				sArena.write(QUOTE);
			}
			
			byte[] contentType = null;
			if (type != null)
				contentType = type;
			else if (file != null)
				contentType = mimeType(fileName);
			
			if (contentType != null) {
				sArena.write(CRLF);
				sArena.write(CONTENT_TYPE);
				sArena.write(contentType);
				if (!new String(contentType, "US-ASCII").startsWith("text/")) {
					sArena.write(CRLF);
					sArena.write(CONTENT_TRANSFER_ENCODING_BINARY);
				}
			}
			sArena.write(CRLF);
			sArena.write(CRLF);
			
			if (file != null) {
				File fp = new File(fileName);
				if (!fp.exists()) {
					sArena.write(FILE_NOT_FOUND);
				}
				else {
					InputStream is = null;
					try {
						is = new BufferedInputStream(new FileInputStream(fileName));
						byte[] buffer = new byte[4096];
						int nBytesRead;
						while ((nBytesRead = is.read(buffer)) > 0)
							sArena.write(buffer, 0, nBytesRead);
					} 
					catch (FileNotFoundException e) {
						sArena.write(FILE_NOT_FOUND);
					} 
					catch (UnsupportedEncodingException e) {
						sArena.write(FILE_NOT_FOUND);
					} 
					catch (IOException e) {
						sArena.write(FILE_NOT_FOUND);
					}
					finally {
						if (is != null) {
							try { is.close(); } 
							catch (IOException ignored) {}
						}
					}
				}
			}
			else if (value != null) {
				sArena.write(value);
			}
		
		}
		catch (IOException ignored) {
			// Not possible - ByteArrayOutputStream doesn't throw IOException
		}
		
		return sArena.toByteArray();
	}


	/*
	 * Write an RFC 1867 compliant MIME trailer into area 'arena'.
	 */
	public static byte[] mimeTrailer(byte[] boundary) {
		assert(boundary != null);
		byte[] result = new byte[CRLF.length + DASH_DASH.length + boundary.length + DASH_DASH.length];
		System.arraycopy(CRLF, 0, result, 0, CRLF.length);
		System.arraycopy(DASH_DASH, 0, result, CRLF.length, DASH_DASH.length);
		System.arraycopy(boundary, 0, result, CRLF.length + DASH_DASH.length, boundary.length);
		System.arraycopy(DASH_DASH, 0, result, CRLF.length + DASH_DASH.length + boundary.length, DASH_DASH.length);
		return result;
		
	}


	/*
	 * Determine MIME type of the given 'file'.
	 */
	private static byte[] mimeType(String file) {
		assert(file != null);
		//
		// Scan through a few well-known MIME types (limited to libCurl's list
		// for compatibility).
		//
		
		if (file.endsWith(".gif"))
			return IMAGE_GIF;
		else if (file.endsWith(".jpg") || file.endsWith(".jpeg)"))
			return IMAGE_JPG;
		
		return TEXT_PLAIN;
	}


	/*
	 * URL encodes the given string to produce content encoded as type
	 * application/x-www-form-urlencoded as defined in RFC 1738 (approximately).
	 * Note that this is not a general URL encoding function and does not
	 * handle current URL encoding standards as specified in RFC 3986.
	 * In particular, non-ASCII characters may be silently truncated.
	 * @param src the string to encode.
	 * @return the encoded string.
	 */
	public static String urlEncode(String src) {
		int n = src.length();
		int needs_encoding = 0;
		for (int i = 0; i < n; i++) {
			char chr = src.charAt(i);
			if (chr < '\u0020' || '\u007F' < chr || urlsafe[chr - 32] == 0)
				needs_encoding++;
		}
		
		if (needs_encoding == 0)
			return src;
		
		StringBuilder dst = new StringBuilder(n + needs_encoding * 2);
		for (int i = 0; i < n; i++) {
			char chr = src.charAt(i);
			if (chr < '\u0020' || '\u007F' < chr || urlsafe[chr - 32] == 0) {
				dst.append('%');
				int nUCS2 = chr;
				dst.append(hexdigit[nUCS2 >> 4 & 0xF]);
				dst.append(hexdigit[nUCS2      & 0xF]);
			}
			else {
				dst.append(chr);
			}
		}
		return dst.toString();
	}


	/**
	 * URL decodes the given string.
	 * @param src the string to decode.
	 * @return the decoded string.
	 */
	public static String urlDecode(String src) {
		assert(src != null);
		StringBuilder res = new StringBuilder();
		boolean needsDecoding = false;
		for (int i = 0, n = src.length(); i < n; i++) {
			char chr = src.charAt(i);
			if (chr == '+' ) {
				res.append(' ');
				needsDecoding = true;
			}
			/* If its the first digit of hex number Then decode it. */
			else if (chr == '%') {
				if (i + 1 < n) {
					chr = src.charAt(++i);
					if ('0' <= chr && chr <= '9')
						chr -= '0';
					else if ('A' <= chr && chr <= 'F')
						chr -= 'A' - 10;
					else /* if ('a' <= chr && chr <= 'f') */
						chr -= 'a' - 10;
					int hex = chr * 16;
					if (i + 1 < n) {
						chr = src.charAt(++i);
						if ('0' <= chr && chr <= '9')
							chr -= '0';
						else if ('A' <= chr && chr <= 'F')
							chr -= 'A' - 10;
						else /* if ('a' <= chr && chr <= 'f') */
							chr -= 'a' - 10;
						hex += chr;
						res.append((char) hex);
					}
				}
				needsDecoding = true;
			}
			/* Else its just a regular character */
			else
				res.append(chr);
		}
		return needsDecoding ? res.toString() : src;
	}
	
	public static String getTemplateBasePathFromConfig(Node contextNode) {
		String sBasePath = null;		
		if (contextNode != null) {
			Node tree = contextNode.resolveNode("template.base");
			if (tree != null && tree.getXFAChildCount() == 1) {
				TextNode textNode = (TextNode) tree.getFirstXFAChild();
				sBasePath = textNode.getValue();
			}
		}
		
		return sBasePath;
	}
	
	public static String getTemplateUriPathFromConfig(Node contextNode) {
		String sUriPath = null;
		if (contextNode != null) {
			Node tree = contextNode.resolveNode("template.uri");
			if (tree != null && tree.getXFAChildCount() == 1) {
				TextNode textNode = (TextNode) tree.getFirstXFAChild();
				sUriPath = textNode.getValue();
				//
				// Strip away the filename.
				//
				if (sUriPath.length() > 0) {
					char cSep = getFileOrUriSeparator(sUriPath);
					
					//
					// Purge the filename from the path.
					//
					int nSlash = sUriPath.lastIndexOf(cSep);
					if (nSlash != sUriPath.length() - 1)
						sUriPath = sUriPath.substring(0, nSlash - 1);
				}
			}
		}
		
		return sUriPath;
	}
	
	private static char getFileOrUriSeparator(String uri) {
		return uri.indexOf('\\') >= 0 ? '\\' : '/';
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy