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

org.xsocket.connection.http.AbstractMessageHeader Maven / Gradle / Ivy

There is a newer version: 2.0-beta-1
Show newest version
/*
 *  Copyright (c) xsocket.org, 2006 - 2008. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
 * The latest copy of this software may be found on http://www.xsocket.org/
 */
package org.xsocket.connection.http;


import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.xsocket.IDataSink;




/**
 * Implementation base for a message header
 * 
 * @author [email protected]
 */
abstract class AbstractMessageHeader implements IHttpMessageHeader {

	
	static final String HOST = "Host";
	static final String USER_AGENT = "User-Agent";
	static final String SERVER = "Server";
	static final String CONNECTION = "connection";
	static final String KEEP_ALIVE = "Keep-Alive";
	static final String CONTENT_LENGTH = "Content-Length";
	static final String CONTENT_TYPE = "Content-Type";
	static final String TRANSFER_ENCODING = "Transfer-Encoding";

	static final String HEADER_ENCODING = "iso-8859-1";
	static final String DEFAULT_BODY_ENCODING = "iso-8859-1";
	//static final String DEFAULT_BODY_ENCODING = "UTF-8";



	private String characterEncoding = DEFAULT_BODY_ENCODING;

	private final Map headers = new HashMap();

	private boolean isModified = false;
	

	
	// performance optimization
	private int contentLength = -1;
	private String contentType = null;
	private String transferEncoding = null;
	private String connection = null;
	private String keepAlive = null;
	
	
	
	/**
	 * returns if the header has been modified
	 * 
	 * @return true, if the header has been modified
	 */
	final boolean isHeadersModified() {
		return isModified;
	}
	
		
	/**
	 * {@inheritDoc}
	 */
	public final int getContentLength() {
		return contentLength;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public final void setContentLength(int length) {
		if (contentLength == length) {
			return;
		}
		
		setHeader(CONTENT_LENGTH, Integer.toString(length));
	}
	
	/**
	 * {@inheritDoc}
	 */
	public final void setContentType(String type) {
		setHeader(CONTENT_TYPE, type);
		
		// contains charset encoding?
		String encoding = parseEncoding(type);
		if (encoding != null) {
			characterEncoding = encoding;
		} 
	}

	
	/**
	 * {@inheritDoc}
	 */
	public final String getCharacterEncoding() {
		return characterEncoding; 
	}

	
	/**
	 * {@inheritDoc}
	 */
	public final String getContentType() {
		return contentType;
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final String getTransferEncoding() {
		return transferEncoding;
	}

	
	/**
	 * {@inheritDoc}
	 */
	public final void setTransferEncoding(String transferEncoding) {
		setHeader(TRANSFER_ENCODING, transferEncoding);
	}
	
	
	/**
	 * adds a header line in a silence mode
	 * 
	 * @param line the header line 
	 */
	final void addHeaderLineSilence(String line) {
		HeaderLine headerline = new HeaderLine(line);
		addHeaderSilence(headerline.getOriginalHeader().toUpperCase(), headerline);
	}
	
	
	/**
	 * adds a header line
	 * 
	 * @param line the header line 
	 */
	final void addHeaderLine(String line) {
		HeaderLine headerline = new HeaderLine(line);
		addHeader(headerline.getOriginalHeader().toUpperCase(), headerline);
	}

	
	/**
	 * copy the headers 
	 * 
	 * @param otherHeader         the other header
	 * @param upperExcludenames   the header names to exclude
	 */
	final void copyHeaderFrom(AbstractMessageHeader otherHeader, String... upperExcludenames) {
		
		ol : for (Entry entry : otherHeader.headers.entrySet()) {
			for (String upperExcludename : upperExcludenames) {
				if (entry.getKey().equals(upperExcludename)) {
					continue ol;
				}
			}
			
			HeaderLine headerline = entry.getValue();
			while (headerline != null) {
				addHeader(entry.getKey(), headerline);
				headerline = headerline.getMoreLine();
			}
		}
	}
	

	/**
	 * {@inheritDoc}
	 */
	public final void addHeader(String headername, String headervalue) {
		addHeader(headername.toUpperCase(), new HeaderLine(headername, headervalue));
	}

	
	private void addHeader(String upperHeadername, HeaderLine headerLine) {
		addHeaderSilence(upperHeadername, headerLine);
		isModified = true;
	}
	
	
	
	private void addHeaderSilence(String upperHeadername, HeaderLine headerLine) {

		HeaderLine existingHeaderline = headers.get(upperHeadername);
		if (existingHeaderline == null) {
			headers.put(upperHeadername, headerLine);
			
		} else {
			while (true)  {
				if (existingHeaderline.getMoreLine() == null) {
					existingHeaderline.addMoreLine(headerLine);
					break;
					
				} else {
					existingHeaderline = existingHeaderline.getMoreLine();
				}
			}  
		}
		
		onHeaderAdded(upperHeadername, headerLine.getValue());
	}

	
	/**
	 * call back if a header has been added 
	 * 
	 * @param upperHeadername   the header name
	 * @param headervalue       the header value
	 */
	void onHeaderAdded(String upperHeadername, String headervalue) {
		
		if (upperHeadername.equals("CONTENT-TYPE")) {
			contentType = headervalue;
			
			String encoding = parseEncoding(headervalue);
			if (encoding != null) {
				characterEncoding = encoding;
			}
			
		} else if (upperHeadername.equals("CONTENT-LENGTH")) {
			contentLength = Integer.parseInt(headervalue);

		} else if (upperHeadername.equals("TRANSFER-ENCODING")) {
			transferEncoding = headervalue;

		} else if (upperHeadername.equals("CONNECTION")) {
			connection = headervalue;

		} else if (upperHeadername.equals("KEEP-ALIVE")) {
			keepAlive = headervalue;
		}
	}


	/**
	 * {@inheritDoc}
	 */
	public final void setHeader(String headername, String headervalue) {
		removeHeader(headername);
		addHeader(headername, headervalue);
	}

	
	/**
	 * {@inheritDoc}
	 */
	public final void removeHeader(String headername) {
		String upperHeadername = headername.toUpperCase();
		HeaderLine removedValue = headers.remove(upperHeadername);
		if (removedValue != null) {
			onHeaderRemoved(upperHeadername);
			isModified = true;
		}
	}

	
	/**
	 * call back if a header has been removed 
	 * 
	 * @param upperHeadername  the header name
	 */
	void onHeaderRemoved(String upperHeadername) {
		if (upperHeadername.equals("CONTENT-TYPE")) {
			contentType = null;
			characterEncoding = DEFAULT_BODY_ENCODING;
			
		} else if (upperHeadername.equals("CONTENT-LENGTH")) {
			contentLength = -1;

		} else if (upperHeadername.equals("TRANSFER-ENCODING")) {
			transferEncoding = null;

		} else if (upperHeadername.equals("CONNECTION")) {
			connection = null;

		} else if (upperHeadername.equals("KEEP-ALIVE")) {
			keepAlive = null;
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	public final void removeHopByHopHeaders() {
		removeHopByHopHeaders("Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE",
				              "Trailers", "Transfer-Encoding", "Upgrade", "Proxy-Connection");

	}

	
	/**
	 * {@inheritDoc}
	 */
	public final void removeHopByHopHeaders(String... headernames) {
		
		for (String headername : headernames) {

			if (headername.equalsIgnoreCase("Connection")) {
				String connectionHeader = getHeader("Connection");
				if (connectionHeader != null) {
					String[] values = connectionHeader.split(",");
					
					for (String value : values) {
						value = value.trim();
						removeHeader(value);
					}
				}
			}
			
			removeHeader(headername);
		}
	}

	
	
	/**
	 * {@inheritDoc}
	 */
	public final boolean containsHeader(String headername) {
		return headers.containsKey(headername.toUpperCase());
	}

	
	/**
	 * {@inheritDoc}
	 */
	public final boolean containsHeaderValue(String headername, String headervalue) {
		HeaderLine headerline = headers.get(headername.toUpperCase());
		while (headerline != null) {
			if (headerline.getValue().equalsIgnoreCase(headervalue)) {
				return true;
			}
			headerline = headerline.getMoreLine();
		}
			
		return false;
	}

	
	/**
	 * {@inheritDoc}
	 */
	public final Set getHeaderNameSet() {
		Set headernames = new HashSet();
		
		for (HeaderLine headerline : headers.values()) {
			headernames.add(headerline.getOriginalHeader());
		}
		
		return Collections.unmodifiableSet(headernames);
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public final Enumeration getHeaderNames() {
		return Collections.enumeration(getHeaderNameSet());
	}
	
	
	/**
	 * {@inheritDoc}
	 */
	public final List getHeaderList(String headername) {
		List result = new ArrayList();		
		 
		HeaderLine headerline = headers.get(headername.toUpperCase());
		if (headerline != null) {
			do {
				result.add(headerline.getValue());
				headerline = headerline.getMoreLine();
			} while(headerline != null);
		}
		
		return result;
	}
	

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public final Enumeration getHeaders(String headername) {
		return Collections.enumeration(getHeaderList(headername));
	}

		

	/**
	 * {@inheritDoc}
	 */
	public String getHeader(String headername) {
		
		String upperHeaderName = headername.toUpperCase();
		
		// performance optimization
		if (upperHeaderName.equals("CONNECTION") && (connection != null)) {
			return connection;
		}
		
		if (upperHeaderName.equals("KEEP-ALIVE") && (keepAlive != null)) {
			return keepAlive;
		}
		
		
		HeaderLine headerline = headers.get(upperHeaderName);
		
		if (headerline != null) {
			return headerline.getValue();
		} else {
			return null;
		}
	}
	
	
	
	/**
	 * return the header as string
	 * @return the headers as string
	 */
	@SuppressWarnings("unchecked")
	final String printHeaders() {

		StringBuilder sb = new StringBuilder();
		for (HeaderLine headerline : headers.values()) {
			sb.append(headerline.toString() + "\r\n");
		}
		
		return sb.toString();
	}
	
	
	
	/**
	 * writes the header to the given data sink
	 * 
	 * @param dataSink  the data sink
	 * @return the number of written bytes 
	 * @throws IOException if an exception occurs
	 */
	public abstract int writeTo(IDataSink dataSink) throws IOException;


	
	/**
	 * returns if a message body is chunked 
	 * 
	 * @param messageHeader  the message header 
	 * @return true, if the body is chunked
	 */
	static boolean hasChunkedBody(AbstractMessageHeader messageHeader) {
		String transferEncoding = messageHeader.getTransferEncoding();
		if (transferEncoding == null) {
			return false;
		} else {
			return (transferEncoding.equalsIgnoreCase("chunked"));
		}
	}

	
	/**
     * returns if a message body is defined by length  
	 * 
	 * @param messageHeader  the message header 
	 * @return true, if the body is defined by length
	 */
	static boolean hasBoundBody(AbstractMessageHeader messageHeader) {
		return (messageHeader.getContentLength() != -1);
	}

	
	
	
	/**
     * returns if a message body is connection terminated  
	 * 
	 * @param messageHeader  the message header 
	 * @return true, if the body is connection terminated
	 */
	static boolean hasConnectionTerminatedBody(AbstractMessageHeader messageHeader) {
		return !hasBoundBody(messageHeader) && !hasChunkedBody(messageHeader) && (messageHeader.getContentType() != null);
	}

	
	
	
	/**
	 * parse the encoding of a given content type  
	 * @param contentType the content type
	 * @return the encoding
	 */
	static String parseEncoding(String contentType) {

		// contains charset encoding?
        int pos = contentType.indexOf(';');
        if (pos > 0) {
        	int pos2 = contentType.indexOf("charset=", pos + 1);
        	if (pos2 > 0) {
        		String encoding = contentType.substring(pos2 + "charset=".length(), contentType.length()).trim();
        		return encoding;
        	}
        }
        
        return null;
	}

	
	/**
	 * unfolds header lines 
	 * 
	 * @param headerlines  the header lines to unfold 
	 * @return the unfloded header lines
	 */
	static String[] unfoldingHeaderlines(String[] headerlines) {
		String[] unfoldedLines = headerlines;
		int emptyLines = 0;
		
		lineLoop : for (int i = (headerlines.length - 1); i > 1; i--) {
			for (int j = 0; j < headerlines[i].length(); j++) {
				// folded line ?
				if ((headerlines[i].charAt(j) == ' ') || (headerlines[i].charAt(j) == '\t')) {	 
					do {
						j++;
					} while ((headerlines[i].charAt(j) == ' ') || (headerlines[i].charAt(j) == '\t'));
					
					String line = headerlines[i].substring(j, headerlines[i].length());
					headerlines[i-1] = headerlines[i-1] + " " + line; 
					headerlines[i] = null;
					emptyLines++;
					continue lineLoop;
					
				// no
				} else {
					continue lineLoop;
				}
			}
		}
		
		
		// compact header line array 
		if (emptyLines > 0) {
			String[] newHeaderlines = new String[headerlines.length - emptyLines];
			int cursor = 0;
			for (int i = 0; i < headerlines.length; i++) {
				if (headerlines[i] != null) {
					newHeaderlines[cursor] = headerlines[i];
					cursor++;
				}
			}
			
			unfoldedLines = newHeaderlines;
		}
		
		return unfoldedLines;
	}

	
	
	

	private static final class HeaderLine {
		
		private String originalHeader = null;
		private String value = null;
		private String line = null;
		
		private HeaderLine moreLine = null;
	
		public HeaderLine(String line) {
			this.line = line;
			
			int idx = line.indexOf(":");
			originalHeader = line.substring(0, idx);
			value = line.substring(idx + 1, line.length()).trim();
		}

		
		public HeaderLine(String originalHeader, String value) {
			this.line = originalHeader + ": " + value;
			this.originalHeader = originalHeader;
			this.value = value;
		}
		
		
		public HeaderLine(String line, String originalHeader, String value) {
			this.line = line;
			this.originalHeader = originalHeader;
			this.value = value;
		}
		
		String getOriginalHeader() {
			return originalHeader;
		}
	
		
		String getValue() {
			return value;
		}
	
		
		public void addMoreLine(HeaderLine moreLine) {
			this.moreLine = moreLine;
		}
		
		public HeaderLine getMoreLine() {
			return moreLine;
		}
		

		@Override
		public String toString() {
			if (moreLine == null) {
				return line;
				
			} else {
				return line + "\r\n" + moreLine;
			}
		}
	}	
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy