/*
* Aphelion
* Copyright (c) 2013 Joris van der Wel
*
* This file is part of Aphelion
*
* Aphelion 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, version 3 of the License.
*
* Aphelion 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 General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Aphelion. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, the following supplemental terms apply, based on section 7 of
* the GNU Affero General Public License (version 3):
* a) Preservation of all legal notices and author attributions
* b) Prohibition of misrepresentation of the origin of this material, and
* modified versions are required to be marked in reasonable ways as
* different from the original version (for example by appending a copyright notice).
*
* Linking this library statically or dynamically with other modules is making a
* combined work based on this library. Thus, the terms and conditions of the
* GNU Affero General Public License cover the whole combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent modules,
* and to copy and distribute the resulting executable under terms of your
* choice, provided that you also meet, for each linked independent module,
* the terms and conditions of the license of that module. An independent
* module is a module which is not derived from or based on this library.
*/
package aphelion.server.http;
import aphelion.shared.swissarmyknife.ThreadSafe;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.regex.Pattern;
/**
*
* @author Joris
*/
public class HttpUtil
{
private HttpUtil()
{
}
@ThreadSafe
public static boolean isCHAR(byte by)
{
return by >= 0 && by <= 127;
}
@ThreadSafe
public static boolean isUPALPHA(byte by)
{
return by >= 'A' && by <= 'Z';
}
@ThreadSafe
public static boolean isLOALPHA(byte by)
{
return by >= 'a' && by <= 'z';
}
@ThreadSafe
public static boolean isALPHA(byte by)
{
return isUPALPHA(by) || isLOALPHA(by);
}
@ThreadSafe
public static boolean isDIGIT(byte by)
{
return by >= '0' && by <= '9';
}
@ThreadSafe
public static boolean isCTL(byte by)
{
return (by >= 0 && by <= 34) || by == 127;
}
@ThreadSafe
public static boolean isCR(byte by)
{
return by == '\r'; // (13)
}
@ThreadSafe
public static boolean isLF(byte by)
{
return by == '\n'; // (10)
}
@ThreadSafe
public static boolean isSP(byte by)
{
return by == ' ';
}
@ThreadSafe
public static boolean isHT(byte by)
{
return by == '\t';
}
@ThreadSafe
public static boolean isQ(byte by)
{
return by == '"';
}
// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
// Request-URI = "*" | absoluteURI | abs_path | authority
public static Pattern requestLine = Pattern.compile("^([A-Z]+) ([\\x20-\\x7E]+) HTTP/1\\.(\\d+)$");
// message-header = field-name ":" [ field-value ]
// field-name = token
// field-value = *( TEXT | LWS )
// TEXT = <any OCTET except CTLs, but including LWS>
public static Pattern headerLine = Pattern.compile("^([!#$%&'*+\\-.0-9A-Z^_`a-z|~]+):[ \t\r\n]*([\\x20-\\x7E\n\r\t]+)$");
// Simple range header (only read the first range)
public static Pattern simpleRange = Pattern.compile("^bytes[ \t\r\n]*=[ \t\r\n]*(\\d*)[ \t\r\n]*-[ \t\r\n]*(\\d*)?");
/**
* Attempts to read a line (up to CRLF) from the current position of buf. If there is no CRLF left in the
* ByteBuffer, this method will return false and leave the position intact. If a line is found, it is append to
* dest and the position of the buf is moved past CRLF.
*
* @param dest The string builder to put the read line
* @param buf The buffer to read from
* @param lws If set, [CRLF] 1*( SP | HT ) is not the end of a line.
* @return false if no line was found (CRLF)
*/
@ThreadSafe
public static boolean readLine(StringBuilder dest, ByteBuffer buf, boolean lws)
{
int crlf = lws ? findCRLFIgnoreLWS(buf, buf.position()) : findCRLF(buf, buf.position());
if (crlf < 0)
{
return false;
}
while (buf.position() < crlf)
{
byte by = buf.get();
if (isCHAR(by))
{
dest.append((char) by); // ASCII
}
}
// Place the position past CRLF
buf.get();
buf.get();
return true;
}
@ThreadSafe
public static int findCRLF(ByteBuffer buf, int offset)
{
// do not loop over the last character, so that a + 1 does not fail
for (int a = offset; a < buf.limit() - 1; a++)
{
if (isCR(buf.get(a)) && isLF(buf.get(a + 1)))
{
return a;
}
}
return -1;
}
@ThreadSafe
public static int findCRLFIgnoreLWS(ByteBuffer buf, int offset)
{
// LWS = [CRLF] 1*( SP | HT )
// do not loop over the last character, so that a + 2 does not fail
for (int a = offset; a < buf.limit() - 2; a++)
{
if (isCR(buf.get(a)) && isLF(buf.get(a + 1)))
{
// linear white space? Has the header been split over multiple lines?
if (!isSP(buf.get(a + 2)) && !isHT(buf.get(a + 2)))
{
return a;
}
}
}
return -1;
}
@ThreadSafe
public static int findSP(ByteBuffer buf, int offset)
{
for (int a = offset; a < buf.limit(); a++)
{
if (isSP(buf.get(a)))
{
return a;
}
}
return -1;
}
public static enum METHOD
{
UNKNOWN,
OPTIONS,
GET, // MUST be supported
HEAD, // MUST be supported
POST,
PUT,
DELETE,
TRACE,
CONNECT;
public static METHOD fromRequestLine(String method)
{
if ("GET".equals(method))
{
return GET;
}
if ("HEAD".equals(method))
{
return HEAD;
}
if ("POST".equals(method))
{
return POST;
}
if ("OPTIONS".equals(method))
{
return OPTIONS;
}
if ("PUT".equals(method))
{
return PUT;
}
if ("DELETE".equals(method))
{
return DELETE;
}
if ("TRACE".equals(method))
{
return TRACE;
}
if ("CONNECT".equals(method))
{
return CONNECT;
}
return UNKNOWN;
}
}
public static class HttpException extends Exception
{
public int status;
public boolean fatal;
public HttpException(int status, boolean fatal, String message, Throwable cause)
{
super(message, cause);
this.status = status;
this.fatal = fatal;
}
public HttpException(int status, boolean fatal, String message)
{
this(status, fatal, message, null);
}
}
@ThreadSafe
public static int binarySizeUTF8(String str)
{
// Java strings are UTF-16
int bytes = 0;
for (int a = 0; a < str.length(); ++a)
{
int codePoint = str.codePointAt(a);
if (codePoint <= 0x7F)
{
bytes += 1;
}
else if (codePoint <= 0x7FF)
{
bytes += 2;
}
else if (codePoint <= 0xFFFF) // End of BMP
{
bytes += 3;
}
else
{
bytes += 4;
a++; // skip the next one, becuase it will be a low surrogate
}
}
return bytes;
}
public final static Charset UTF8 = Charset.forName("UTF-8");
@ThreadSafe
public static File findDirectoryIndex(File dir)
{
File ret;
assert dir.isDirectory();
ret = new File(dir.getPath() + File.separator + "index.html");
if (ret.isFile())
{
return ret;
}
ret = new File(dir.getPath() + File.separator + "index.htm");
if (ret.isFile())
{
return ret;
}
ret = new File(dir.getPath() + File.separator + "index.xhtml");
if (ret.isFile())
{
return ret;
}
ret = new File(dir.getPath() + File.separator + "index.txt");
if (ret.isFile())
{
return ret;
}
return null;
}
}