/*
* Copyright (c) 1998-2010 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.quercus.lib;
import com.caucho.quercus.QuercusContext;
import com.caucho.quercus.QuercusException;
import com.caucho.quercus.QuercusModuleException;
import com.caucho.quercus.annotation.NotNull;
import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.annotation.Reference;
import com.caucho.quercus.annotation.ReturnNullAsFalse;
import com.caucho.quercus.annotation.UsesSymbolTable;
import com.caucho.quercus.env.*;
import com.caucho.quercus.lib.file.BinaryStream;
import com.caucho.quercus.lib.file.FileInput;
import com.caucho.quercus.lib.file.FileModule;
import com.caucho.quercus.lib.file.FileOutput;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.quercus.program.QuercusProgram;
import com.caucho.util.L10N;
import com.caucho.util.RandomUtil;
import com.caucho.vfs.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* PHP misc functiomn.
*/
public class MiscModule extends AbstractQuercusModule {
private static final L10N L = new L10N(MiscModule.class);
private static final Logger log = Logger.getLogger(MiscModule.class.getName());
public static final int CONNECTION_NORMAL = 0;
public static final int CONNECTION_ABORTED = 1;
public static final int CONNECTION_TIMEOUT = 2;
/**
* Return true on a client disconnect
*/
public static int connection_aborted(Env env) {
return env.getConnectionStatus();
}
/**
* Returns the status
*/
public static int connection_status(Env env) {
return env.getConnectionStatus();
}
/**
* Escapes characters in a string.
*/
public static String escapeshellcmd(String command) {
StringBuilder sb = new StringBuilder();
int len = command.length();
boolean hasApos = false;
boolean hasQuot = false;
for (int i = 0; i < len; i++) {
char ch = command.charAt(i);
switch (ch) {
case '#':
case '&':
case ';':
case '`':
case '|':
case '*':
case '?':
case '~':
case '<':
case '>':
case '^':
case '(':
case ')':
case '[':
case ']':
case '{':
case '}':
case '$':
case '\\':
case ',':
case 0x0a:
case 0xff:
sb.append('\\');
sb.append(ch);
break;
case '\'':
hasApos = !hasApos;
sb.append(ch);
break;
case '\"':
hasQuot = !hasQuot;
sb.append(ch);
break;
default:
sb.append(ch);
}
}
String result = sb.toString();
if (hasApos) {
int p = result.lastIndexOf('\'');
result = result.substring(0, p) + "\\" + result.substring(p);
}
if (hasQuot) {
int p = result.lastIndexOf('\"');
result = result.substring(0, p) + "\\" + result.substring(p);
}
return result;
}
/**
* Escapes characters in a string.
*/
public static StringValue escapeshellarg(Env env, StringValue arg) {
boolean isWindows = Path.isWindows();
char quote;
if (isWindows) {
quote = '"';
} else {
quote = '\'';
}
StringValue sb = env.createStringBuilder();
sb.append(quote);
int len = arg.length();
for (int i = 0; i < len; i++) {
char ch = arg.charAt(i);
if (ch == quote) {
sb.append('\\');
sb.append(ch);
} else if (ch == '%' && isWindows) {
sb.append(' ');
} else {
sb.append(ch);
}
}
sb.append(quote);
return sb;
}
/**
* Comples and evaluates an expression.
*/
@UsesSymbolTable
public Value eval(Env env, String code) {
try {
if (log.isLoggable(Level.FINER)) {
log.finer("quercus eval: [[" + code + "]]");
}
QuercusContext quercus = env.getQuercus();
QuercusProgram program = quercus.parseCode(code);
Value value = program.execute(env);
if (value == null) {
value = NullValue.NULL;
}
return value;
} catch (IOException e) {
throw new QuercusException(e);
}
}
/**
* Execute a system command.
*/
public static String exec(Env env, String command,
@Optional Value output,
@Optional @Reference Value result) {
try {
String[] args = new String[3];
if (Path.isWindows()) {
args[0] = "cmd";
args[1] = "/c";
} else {
args[0] = "sh";
args[1] = "-c";
}
args[2] = command;
ProcessBuilder processBuilder = new ProcessBuilder(args);
processBuilder.redirectErrorStream(false);
// TODO: security issues?
processBuilder.directory(new File(env.getShellPwd()));
final Process process = processBuilder.start();
InputStream is = process.getInputStream();
InputStream es = process.getErrorStream();
OutputStream os = process.getOutputStream();
os.close();
StringBuilder sb = new StringBuilder();
String line = "";
int ch;
boolean hasCr = false;
while ((ch = is.read()) >= 0) {
if (ch == '\n') {
if (!hasCr) {
line = sb.toString();
sb.setLength(0);
if (output != null) {
output.put(env.createString(line));
}
}
hasCr = false;
} else if (ch == '\r') {
line = sb.toString();
sb.setLength(0);
output.put(env.createString(line));
hasCr = true;
} else {
sb.append((char) ch);
}
}
if (sb.length() > 0) {
line = sb.toString();
sb.setLength(0);
output.put(env.createString(line));
}
is.close();
env.getOut().writeStream(es);
es.close();
int status = process.waitFor();
result.set(LongValue.create(status));
return line;
} catch (Exception e) {
log.log(Level.FINE, e.getMessage(), e);
env.warning(e.getMessage(), e);
return null;
}
}
/**
* Returns an array detailing what the browser is capable of.
* A general browscap.ini file can be used.
*
* @param env
* @param user_agent
* @param return_array
*/
public static Value get_browser(
Env env,
@Optional() String user_agent,
@Optional() boolean return_array) {
if (user_agent == null
|| user_agent.length() == 0) {
user_agent = env.getRequest().getHeader("User-Agent");
}
if (user_agent == null) {
env.warning(L.l("HTTP_USER_AGENT not set."));
return BooleanValue.FALSE;
}
Value browscap = env.getConfigVar("browscap");
if (browscap == null) {
env.warning(L.l("Browscap path not set in PHP.ini."));
return BooleanValue.FALSE;
}
Path path = env.lookup(browscap.toStringValue());
if (path == null) {
env.warning(L.l("Browscap file not found."));
return BooleanValue.FALSE;
}
Value ini = FileModule.parse_ini_file(env, path, true);
if (ini == BooleanValue.FALSE) {
return BooleanValue.FALSE;
}
return getBrowserReport(
env, ini.toArrayValue(env), user_agent, return_array);
}
private static Value getBrowserReport(
Env env,
ArrayValue browsers,
String user_agent,
boolean return_array) {
StringValue patternMatched = env.getEmptyString();
String regExpMatched = null;
for (Map.Entry<Value, Value> entry : browsers.entrySet()) {
StringValue pattern = entry.getKey().toStringValue();
if (pattern.toString().equals(user_agent)) {
patternMatched = pattern;
regExpMatched = null;
break;
}
String regExp = formatBrowscapRegexp(pattern);
Matcher m = Pattern.compile(regExp).matcher(user_agent);
// Want the longest matching pattern.
if (m.matches()) {
if (pattern.length() > patternMatched.length()) {
patternMatched = pattern;
regExpMatched = regExp;
}
}
}
if (patternMatched.length() == 0) {
return BooleanValue.FALSE;
}
return prepareBrowserReport(env, browsers, patternMatched, regExpMatched,
user_agent, return_array);
}
private static Value prepareBrowserReport(
Env env,
ArrayValue browsers,
StringValue patternMatched,
String regExpMatched,
String user_agent,
boolean return_array) {
ArrayValue capabilities = browsers.get(patternMatched).toArrayValue(env);
if (regExpMatched == null) {
capabilities.put(env.createString("browser_name_regex"),
patternMatched);
} else {
capabilities.put("browser_name_regex", regExpMatched);
}
capabilities.put(env.createString("browser_name_pattern"), patternMatched);
addBrowserCapabilities(env, browsers,
capabilities.get(env.createString("parent")),
capabilities);
if (return_array) {
ArrayValue array = new ArrayValueImpl();
array.put(env.createString(user_agent), capabilities);
return array;
}
ObjectValue object = env.createObject();
for (Map.Entry<Value, Value> entry : capabilities.entrySet()) {
object.putField(env, entry.getKey().toString(), entry.getValue());
}
return object;
}
private static void addBrowserCapabilities(
Env env,
ArrayValue browsers,
Value browser,
ArrayValue cap) {
if (browser == UnsetValue.UNSET) {
return;
}
Value field = null;
if ((field = browsers.get(browser)) == UnsetValue.UNSET) {
return;
}
ArrayValue browserCapabilities = field.toArrayValue(env);
StringValue parentString = env.createString("parent");
for (Map.Entry<Value, Value> entry : browserCapabilities.entrySet()) {
Value key = entry.getKey();
if (key.equals(parentString)) {
addBrowserCapabilities(
env, browsers, entry.getValue(), cap);
} else if (cap.containsKey(key) == null) {
cap.put(key, entry.getValue());
}
}
}
private static String formatBrowscapRegexp(StringValue key) {
int length = key.length();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
char ch = key.charAt(i);
switch (ch) {
case '*':
sb.append('.');
sb.append('*');
break;
case '?':
sb.append('.');
break;
case '.':
sb.append('\\');
sb.append('.');
break;
case '+':
sb.append('\\');
sb.append('+');
break;
case '(':
sb.append('\\');
sb.append('(');
break;
case ')':
sb.append('\\');
sb.append(')');
break;
case '{':
sb.append('\\');
sb.append('{');
break;
case '}':
sb.append('\\');
sb.append('}');
break;
case ']':
sb.append('\\');
sb.append(']');
break;
case '[':
sb.append('\\');
sb.append('[');
break;
case '\\':
sb.append('\\');
sb.append('\\');
break;
case '^':
sb.append('\\');
sb.append('^');
break;
case '$':
sb.append('\\');
sb.append('$');
break;
case '&':
sb.append('\\');
sb.append('&');
break;
case '|':
sb.append('\\');
sb.append('|');
break;
default:
sb.append(ch);
}
}
return sb.toString();
}
/**
* packs the format into a binary.
*/
public Value pack(Env env, String format, Value[] args) {
try {
ArrayList<PackSegment> segments = parsePackFormat(env, format, false);
if (segments == null) {
return BooleanValue.FALSE;
}
StringValue bb = env.createBinaryBuilder();
int i = 0;
for (PackSegment segment : segments) {
i = segment.pack(env, bb, i, args);
}
return bb;
} catch (IOException e) {
throw new QuercusModuleException(e);
}
}
/**
* packs the format into a binary.
*/
public Value unpack(Env env, String format, StringValue s) {
if (format == null) {
return NullValue.NULL;
}
try {
ArrayList<PackSegment> segments = parsePackFormat(env, format, true);
if (segments == null) {
return BooleanValue.FALSE;
}
ArrayValue array = new ArrayValueImpl();
int length = s.length();
int offset = 0;
for (PackSegment segment : segments) {
offset = segment.unpack(env, array, s, offset, length);
}
return array;
} catch (IOException e) {
throw new QuercusModuleException(e);
}
}
/**
* Logs the expression.
*/
public Value resin_debug(String code) {
log.info(code);
return NullValue.NULL;
}
/**
* Dumps the Java stack to standard out.
*/
public Value resin_thread_dump() {
Thread.dumpStack();
return NullValue.NULL;
}
/**
* Dumps the stack.
*/
public static Value dump_stack(Env env) {
try {
Exception e = new Exception("Stack trace");
e.fillInStackTrace();
WriteStream out = env.getPwd().lookup("stderr:").openWrite();
try {
e.printStackTrace(out.getPrintWriter());
//ScriptStackTrace.printStackTrace(e, out.getPrintWriter());
} finally {
out.close();
}
return NullValue.NULL;
} catch (IOException e) {
throw new QuercusModuleException(e);
}
}
/**
* Execute a system command.
*/
public static Value shell_exec(Env env, String command) {
String[] args = new String[3];
try {
if (Path.isWindows()) {
args[0] = "cmd";
args[1] = "/c";
} else {
args[0] = "sh";
args[1] = "-c";
}
args[2] = command;
ProcessBuilder processBuilder = new ProcessBuilder(args);
processBuilder.redirectErrorStream(false);
// TODO: security issues?
processBuilder.directory(new File(env.getShellPwd()));
final Process process = processBuilder.start();
InputStream is = process.getInputStream();
InputStream es = process.getErrorStream();
OutputStream os = process.getOutputStream();
os.close();
StringValue sb = env.createUnicodeBuilder();
int ch;
while ((ch = is.read()) >= 0) {
sb.append((char) ch);
}
is.close();
if ((ch = es.read()) >= 0) {
env.print((char) ch);
while ((ch = es.read()) >= 0) {
env.print((char) ch);
}
return NullValue.NULL;
}
es.close();
int status = process.waitFor();
return sb;
} catch (Exception e) {
log.log(Level.FINE, e.getMessage(), e);
env.warning(e.getMessage(), e);
return NullValue.NULL;
}
}
/**
* Execute a system command.
*/
public static void passthru(Env env, String command,
@Optional @Reference Value result) {
try {
String[] args = new String[3];
if (Path.isWindows()) {
args[0] = "cmd";
args[1] = "/c";
} else {
args[0] = "sh";
args[1] = "-c";
}
args[2] = command;
ProcessBuilder processBuilder = new ProcessBuilder(args);
processBuilder.redirectErrorStream(true);
// TODO: security issues?
processBuilder.directory(new File(env.getShellPwd()));
final Process process = processBuilder.start();
try {
InputStream is = process.getInputStream();
OutputStream os = process.getOutputStream();
os.close();
env.getOut().writeStream(is);
is.close();
int status = process.waitFor();
if (result != null) {
result.set(LongValue.create(status));
}
} finally {
process.destroy();
}
} catch (Exception e) {
env.warning(e.getMessage(), e);
}
}
/*
* Basic implementation of proc_open.
* XXX: options
*/
@ReturnNullAsFalse
public static ProcOpenResource proc_open(Env env,
String command,
ArrayValue descriptorArray,
@Reference Value pipes,
@Optional Path pwd,
@Optional ArrayValue envArray,
@Optional ArrayValue options) {
String[] args = new String[3];
try {
if (Path.isWindows()) {
args[0] = "cmd";
args[1] = "/c";
} else {
args[0] = "sh";
args[1] = "-c";
}
args[2] = command;
String[] envStrings = null;
File pwdFile = null;
if (envArray != null) {
int size = envArray.getSize();
envStrings = new String[size];
int i = 0;
for (Map.Entry<Value, Value> entry : envArray.entrySet()) {
envStrings[i++] = entry.getKey() + "=" + entry.getValue();
}
}
if (pwd != null) {
pwdFile = new File(pwd.getFullPath());
}
Process process = Runtime.getRuntime().exec(args, envStrings, pwdFile);
ProcOpenOutput in = null;
ProcOpenInput out = null;
ProcOpenInput es = null;
ArrayValue array = pipes.toAutoArray().toArrayValue(env);
pipes.set(array);
array.clear();
for (Map.Entry<Value, Value> entry : descriptorArray.entrySet()) {
Value key = entry.getKey();
Value val = entry.getValue();
String type = val.get(LongValue.ZERO).toString();
StringValue name = val.get(LongValue.ONE).toStringValue();
String mode = val.get(LongValue.create(2)).toString();
// input to the command
if (key.equals(LongValue.ZERO)) {
if (type.equals("pipe")) {
in = new ProcOpenOutput(env, process.getOutputStream());
array.put(LongValue.ZERO, env.wrapJava(in));
} else if (type.equals("file")) {
OutputStream processOut = process.getOutputStream();
BinaryStream stream = FileModule.fopen(
env, name, mode, false, null);
if (stream instanceof FileInput) {
FileInput file = (FileInput) stream;
int ch;
while ((ch = file.read()) >= 0) {
processOut.write(ch);
}
}
stream.close();
processOut.close();
}
} // place to put output from the command
else if (key.equals(LongValue.ONE)) {
if (type.equals("pipe")) {
out = new ProcOpenInput(env, process.getInputStream());
array.put(LongValue.ONE, env.wrapJava(out));
} else if (type.equals("file")) {
BinaryStream stream = FileModule.fopen(
env, name, mode, false, null);
if (stream instanceof FileOutput) {
FileOutput file = (FileOutput) stream;
out = new ProcOpenInput(env, process.getInputStream(), file);
} else if (stream != null) {
stream.close();
}
}
} // place to put error output from the command
else if (key.equals(LongValue.create(2))) {
if (type.equals("pipe")) {
es = new ProcOpenInput(env, process.getErrorStream());
array.put(LongValue.create(2), env.wrapJava(es));
} else if (type.equals("file")) {
BinaryStream stream = FileModule.fopen(
env, name, mode, false, null);
if (stream instanceof FileOutput) {
FileOutput file = (FileOutput) stream;
es = new ProcOpenInput(env, process.getErrorStream(), file);
} else if (stream != null) {
stream.close();
}
}
}
}
return new ProcOpenResource(env, process, in, out, es, command);
} catch (Exception e) {
log.log(Level.FINE, e.getMessage(), e);
env.warning(e);
return null;
}
}
/*
* Closes the process opened by proc_open.
*/
public static int proc_close(Env env,
@NotNull ProcOpenResource stream) {
if (stream == null) {
env.warning("input to proc_close must not be null");
return -1;
}
return stream.pclose();
}
/*
* Forcibly terminates the process opened by proc_open.
*/
public static boolean proc_terminate(Env env,
@NotNull ProcOpenResource stream) {
if (stream == null) {
log.log(Level.FINE, "input to proc_close must not be null");
env.warning("input to proc_close must not be null");
return false;
}
return stream.terminate();
}
public static Value proc_get_status(Env env,
@NotNull ProcOpenResource stream) {
if (stream == null) {
env.warning("input to proc_get_status() must not be null");
return BooleanValue.FALSE;
}
ArrayValue array = new ArrayValueImpl();
array.put("command", stream.getCommand());
array.put("running", stream.isRunning());
array.put("exitcode", stream.getExitCode());
return array;
}
/**
* Returns the disconnect ignore setting
*/
public static int ignore_user_abort(@Optional boolean set) {
return 0;
}
/**
* Returns a unique id.
*/
public String uniqid(@Optional String prefix, @Optional boolean moreEntropy) {
StringBuilder sb = new StringBuilder();
if (prefix != null) {
sb.append(prefix);
}
addUnique(sb);
if (moreEntropy) {
addUnique(sb);
}
return sb.toString();
}
private void addUnique(StringBuilder sb) {
long value = RandomUtil.getRandomLong();
if (value < 0) {
value = -value;
}
int limit = 13;
for (; limit > 0; limit--) {
long digit = value % 26;
value = value / 26;
sb.append((char) ('a' + digit));
}
}
/**
* Sleep for a number of microseconds.
*/
public static Value usleep(long microseconds) {
try {
Thread.sleep(microseconds / 1000);
} catch (Throwable e) {
}
return NullValue.NULL;
}
/**
* Sleep for a number of seconds.
*/
public static long sleep(long seconds) {
try {
Thread.sleep(seconds * 1000);
} catch (Throwable e) {
}
return seconds;
}
/**
* Execute a system command.
*/
public static String system(Env env, String command,
@Optional @Reference Value result) {
return exec(env, command, null, result);
}
private static ArrayList<PackSegment> parsePackFormat123(
Env env, String format) {
ArrayList<PackSegment> segments = new ArrayList<PackSegment>();
int length = format.length();
for (int i = 0; i < length; i++) {
char ch = format.charAt(i);
int count = 0;
boolean isEnd = false;
char ch1 = ' ';
for (i++;
i < length && '0' <= (ch1 = format.charAt(i)) && ch1 <= '9';
i++) {
count = 10 * count + ch1 - '0';
}
if (ch1 == '*' && count == 0) {
i++;
isEnd = true;
} else if (count == 0) {
count = 1;
}
if (i < length) {
i--;
}
switch (ch) {
case 'a':
if (isEnd) {
segments.add(new SpaceEndPackSegment(env, (byte) 0));
} else {
segments.add(new SpacePackSegment(env, count, (byte) 0));
}
break;
case 'A':
segments.add(new SpacePackSegment(env, count, (byte) 0x20));
break;
case 'h':
segments.add(new RevHexPackSegment(count));
break;
case 'H':
segments.add(new HexPackSegment(env, count));
break;
case 'c':
case 'C':
segments.add(new BigEndianPackSegment(count, 1, ch == 'c'));
break;
case 's':
case 'n':
case 'S':
segments.add(new BigEndianPackSegment(count, 2, ch != 'S'));
break;
case 'v':
segments.add(new LittleEndianPackSegment(count, 2));
break;
case 'l':
case 'L':
case 'N':
segments.add(new BigEndianPackSegment(count, 4, ch == 'l'));
break;
case 'V':
segments.add(new LittleEndianPackSegment(count, 4));
break;
case 'i':
case 'I':
segments.add(new BigEndianPackSegment(count, 8, ch == 'i'));
break;
case 'd':
segments.add(new DoublePackSegment(count));
break;
case 'f':
segments.add(new FloatPackSegment(count));
break;
case 'x':
segments.add(new NullPackSegment(count));
break;
case '@':
segments.add(new PositionPackSegment(count));
break;
}
}
return segments;
}
private static ArrayList<PackSegment> parsePackFormat(Env env,
String format,
boolean hasName) {
ArrayList<PackSegment> segments = new ArrayList<PackSegment>();
int length = format.length();
int i = 0;
while (i < length) {
char ch = format.charAt(i++);
int count = 0;
boolean isEnd = false;
if (i < length && format.charAt(i) == '*') {
count = Integer.MAX_VALUE;
isEnd = true;
i++;
} else {
while (i < length) {
char ch1 = format.charAt(i);
if ('0' <= ch1 && ch1 <= '9') {
count = count * 10 + ch1 - '0';
i++;
} else {
break;
}
}
if (count == 0) {
count = 1;
}
}
String name = "";
if (hasName && i < length) {
StringBuilder sb = new StringBuilder();
while (i < length) {
char ch1 = format.charAt(i++);
if (ch1 == '/') {
break;
} else {
sb.append(ch1);
}
}
name = sb.toString();
}
switch (ch) {
case 'a':
if (isEnd) {
segments.add(new SpaceEndPackSegment(env, name, (byte) 0));
} else {
segments.add(new SpacePackSegment(env, name, count, (byte) 0));
}
break;
case 'A':
if (isEnd) {
segments.add(new SpaceEndPackSegment(env, name, (byte) 0x20));
} else {
segments.add(new SpacePackSegment(env, name, count, (byte) 0x20));
}
break;
case 'h':
segments.add(new RevHexPackSegment(name, count));
break;
case 'H':
segments.add(new HexPackSegment(env, name, count));
break;
case 'c':
segments.add(new BigEndianPackSegment(name, count, 1, true));
break;
case 'C':
segments.add(new BigEndianPackSegment(name, count, 1, false));
break;
case 's':
segments.add(new BigEndianPackSegment(name, count, 2, true));
break;
case 'S':
case 'n':
segments.add(new BigEndianPackSegment(name, count, 2, false));
break;
case 'v':
segments.add(new LittleEndianPackSegment(name, count, 2));
break;
case 'l':
segments.add(new BigEndianPackSegment(name, count, 4, true));
break;
case 'L':
case 'N':
segments.add(new BigEndianPackSegment(name, count, 4, false));
break;
case 'V':
segments.add(new LittleEndianPackSegment(name, count, 4));
break;
case 'i':
case 'I':
segments.add(new BigEndianPackSegment(name, count, 8, false));
break;
case 'd':
segments.add(new DoublePackSegment(name, count));
break;
case 'f':
segments.add(new FloatPackSegment(name, count));
break;
case 'x':
segments.add(new NullPackSegment(name, count));
break;
case '@':
segments.add(new PositionPackSegment(name, count));
break;
default:
env.warning(L.l("invalid format '{0}'", String.valueOf(ch)));
return null;
}
}
return segments;
}
abstract static class PackSegment {
abstract public int pack(Env env, StringValue bb,
int i, Value[] args)
throws IOException;
abstract public int unpack(Env env,
ArrayValue array,
StringValue s,
int offset,
int strLen)
throws IOException;
}
static class SpacePackSegment extends PackSegment {
private final StringValue _name;
private final int _length;
private final byte _pad;
SpacePackSegment(Env env, int length, byte pad) {
this(env, "", length, pad);
}
SpacePackSegment(Env env, String name, int length, byte pad) {
_name = env.createString(name);
_length = length;
_pad = pad;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
Value arg;
if (i < args.length) {
arg = args[i];
i++;
} else {
env.warning("a: not enough arguments");
return i;
}
InputStream is = arg.toInputStream();
int length = _length;
for (int j = 0; j < length; j++) {
int ch = is.read();
if (ch >= 0) {
bb.appendByte(ch);
} else if (length == Integer.MAX_VALUE) {
return i;
} else {
bb.appendByte(_pad);
}
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
if (strLen - offset < _length) {
return offset;
}
StringValue bb = env.createBinaryBuilder();
int j = offset;
for (int i = 0; i < _length; i++) {
char ch = s.charAt(offset++);
if (ch != _pad) {
if (j + 1 != offset) {
bb.append(s, j, offset);
} else {
bb.append(ch);
}
j = offset;
}
}
if (_name.length() == 0) {
result.put(env.createString('1'), bb);
} else {
result.put(_name, bb);
}
return offset;
}
}
static class SpaceEndPackSegment extends PackSegment {
private final StringValue _name;
private final byte _pad;
SpaceEndPackSegment(Env env, byte pad) {
this(env, "", pad);
}
SpaceEndPackSegment(Env env, String name, byte pad) {
_name = env.createString(name);
_pad = pad;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
Value arg;
if (i < args.length) {
arg = args[i];
i++;
} else {
env.warning("a: not enough arguments");
return i;
}
StringValue s = arg.toStringValue(env);
bb.append(s);
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
StringValue bb = env.createBinaryBuilder();
int j = offset;
while (offset < strLen) {
char ch = s.charAt(offset++);
if (ch != _pad) {
if (j + 1 != offset) {
bb.append(s, j, offset);
} else {
bb.append(ch);
}
j = offset;
}
}
if (_name.length() == 0) {
result.put(env.createString('1'), bb);
} else {
result.put(_name, bb);
}
return offset;
}
}
static class HexPackSegment extends PackSegment {
private final StringValue _name;
private final int _length;
HexPackSegment(Env env, int length) {
this(env, "", length);
}
HexPackSegment(Env env, String name, int length) {
_name = env.createString(name);
_length = length;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
Value arg;
if (i < args.length) {
arg = args[i];
i++;
} else {
env.warning("a: not enough arguments");
return i;
}
StringValue s = arg.toStringValue();
int strlen = s.length();
if (_length == Integer.MAX_VALUE) {
} else if (strlen < _length) {
env.warning("not enough characters in hex string");
return i;
} else if (_length < strlen) {
strlen = _length;
}
int tail = strlen / 2;
for (int j = 0; j < tail; j++) {
int d = 0;
char ch = s.charAt(2 * j);
d += 16 * hexToDigit(env, ch);
ch = s.charAt(2 * j + 1);
d += hexToDigit(env, ch);
bb.appendByte(d);
}
if ((strlen & 1) == 1) {
int d = 16 * hexToDigit(env, s.charAt(strlen - 1));
bb.appendByte(d);
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
if (offset + (long) (_length / 2 - 1) >= strLen) {
return offset;
}
StringValue sb = env.createStringBuilder();
for (int i = _length / 2 - 1; i >= 0; i--) {
char ch = s.charAt(offset++);
sb.append(digitToHex(ch >> 4));
sb.append(digitToHex(ch));
}
result.put(_name, sb);
return offset;
}
}
static class RevHexPackSegment extends PackSegment {
private final StringValue _name;
private final int _length;
RevHexPackSegment(int length) {
this("", length);
}
RevHexPackSegment(String name, int length) {
_name = new StringBuilderValue(name);
_length = length;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
Value arg;
if (i < args.length) {
arg = args[i];
i++;
} else {
env.warning("a: not enough arguments");
return i;
}
StringValue s = arg.toStringValue();
int strlen = s.length();
if (_length == Integer.MAX_VALUE) {
} else if (strlen < _length) {
env.warning("not enough characters in hex string");
return i;
} else if (_length < strlen) {
strlen = _length;
}
int tail = strlen / 2;
for (int j = 0; j < tail; j++) {
int d = 0;
char ch = s.charAt(2 * j);
d += hexToDigit(env, ch);
ch = s.charAt(2 * j + 1);
d += 16 * hexToDigit(env, ch);
bb.appendByte(d);
}
if ((strlen & 1) == 1) {
int d = hexToDigit(env, s.charAt(strlen - 1));
bb.appendByte(d);
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
if (offset + (long) (_length / 2 - 1) >= strLen) {
return offset;
}
StringValue sb = env.createStringBuilder();
for (int i = _length / 2 - 1; i >= 0; i--) {
char ch = s.charAt(offset++);
sb.append(digitToHex(ch));
sb.append(digitToHex(ch >> 4));
}
result.put(_name, sb);
return offset;
}
}
static class BigEndianPackSegment extends PackSegment {
private final String _name;
private final int _length;
private final int _bytes;
private final boolean _isSigned;
BigEndianPackSegment(int length, int bytes, boolean isSigned) {
_name = "";
_length = length;
_bytes = bytes;
_isSigned = isSigned;
}
BigEndianPackSegment(String name, int length, int bytes, boolean isSigned) {
_name = name;
_length = length;
_bytes = bytes;
_isSigned = isSigned;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
for (int j = 0; j < _length; j++) {
Value arg;
if (i < args.length) {
arg = args[i];
i++;
} else if (_length == Integer.MAX_VALUE) {
return i;
} else {
env.warning("a: not enough arguments");
return i;
}
long v = arg.toLong();
for (int k = 0; k < _bytes; k++) {
bb.appendByte((int) (v >> (8 * k)));
}
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
outer:
for (int j = 0; j < _length; j++) {
Value key;
// TODO: check key type with unicode semantics
if (_name.length() == 0) {
key = LongValue.create(j + 1);
} else if (_length == 1) {
key = env.createString(_name);
} else {
StringValue sb = env.createStringBuilder();
sb.append(_name);
sb.append(j + 1);
key = sb;
}
long v = 0;
for (int k = 0; k < _bytes; k++) {
if (strLen <= offset) {
break outer;
}
char ch = s.charAt(offset++);
long d = ch & 0xff;
v = (v << 8) + d;
}
if (_isSigned) {
switch (_bytes) {
case 1:
v = (byte) v;
break;
case 2:
v = (short) v;
break;
case 4:
v = (int) v;
break;
}
}
result.put(key, LongValue.create(v));
}
return offset;
}
}
static class LittleEndianPackSegment extends PackSegment {
private final String _name;
private final int _length;
private final int _bytes;
LittleEndianPackSegment(int length, int bytes) {
_name = "";
_length = length;
_bytes = bytes;
}
LittleEndianPackSegment(String name, int length, int bytes) {
_name = name;
_length = length;
_bytes = bytes;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
for (int j = 0; j < _length; j++) {
Value arg;
if (i < args.length) {
arg = args[i];
i++;
} else if (_length == Integer.MAX_VALUE) {
return i;
} else {
env.warning("a: not enough arguments");
return i;
}
long v = arg.toLong();
for (int k = 0; k < _bytes; k++) {
bb.appendByte((int) (v >> (8 * k)));
}
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
outer:
for (int j = 0; j < _length; j++) {
Value key;
if (_name.length() == 0) {
key = LongValue.create(j + 1);
} else if (_length == 1) {
key = env.createString(_name);
} else {
StringValue sb = env.createStringBuilder();
sb.append(_name);
sb.append(j + 1);
key = sb;
}
long v = 0;
for (int k = 0; k < _bytes; k++) {
if (offset >= strLen) {
break outer;
}
char ch = s.charAt(offset++);
long d = ch & 0xff;
v |= d << 8 * k;
}
result.put(key, LongValue.create(v));
}
return offset;
}
}
static class DoublePackSegment extends PackSegment {
private final String _name;
private final int _length;
DoublePackSegment(int length) {
this("", length);
}
DoublePackSegment(String name, int length) {
_name = name;
_length = length;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
for (int j = 0; j < _length; j++) {
Value arg;
if (i < args.length) {
arg = args[i];
i++;
} else if (_length == Integer.MAX_VALUE) {
return i;
} else {
env.warning("a: not enough arguments");
return i;
}
double d = arg.toDouble();
long v = Double.doubleToLongBits(d);
for (int k = 7; k >= 0; k--) {
bb.appendByte((int) (v >> (8 * k) & 0xff));
}
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
outer:
for (int j = 0; j < _length; j++) {
Value key;
if (_name.length() == 0) {
key = LongValue.create(j + 1);
} else if (_length == 1) {
key = env.createString(_name);
} else {
StringValue sb = env.createBinaryBuilder();
sb.append(_name);
sb.append(j + 1);
key = sb;
}
long v = 0;
for (int k = 0; k < 8; k++) {
if (offset >= strLen) {
break outer;
}
char ch = s.charAt(offset++);
long d = ch & 0xff;
v = 256 * v + d;
}
result.put(key, new DoubleValue(Double.longBitsToDouble(v)));
}
return offset;
}
}
static class FloatPackSegment extends PackSegment {
private final String _name;
private final int _length;
FloatPackSegment(int length) {
this("", length);
}
FloatPackSegment(String name, int length) {
_name = name;
_length = length;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
for (int j = 0; j < _length; j++) {
Value arg;
if (i < args.length) {
arg = args[i];
i++;
} else if (_length == Integer.MAX_VALUE) {
return i;
} else {
env.warning("a: not enough arguments");
return i;
}
double d = arg.toDouble();
int v = Float.floatToIntBits((float) d);
for (int k = 3; k >= 0; k--) {
bb.appendByte((int) (v >> (8 * k) & 0xff));
}
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
outer:
for (int j = 0; j < _length; j++) {
Value key;
if (_name.length() == 0) {
key = LongValue.create(j + 1);
} else if (_length == 1) {
key = env.createString(_name);
} else {
StringValue sb = env.createBinaryBuilder();
sb.append(_name);
sb.append(j + 1);
key = sb;
}
int v = 0;
for (int k = 0; k < 4; k++) {
if (offset >= strLen) {
break outer;
}
char ch = s.charAt(offset++);
int d = ch & 0xff;
v = 256 * v + d;
}
result.put(key, new DoubleValue(Float.intBitsToFloat(v)));
}
return offset;
}
}
static class NullPackSegment extends PackSegment {
private final String _name;
private final int _length;
NullPackSegment(int length) {
this("", length);
}
NullPackSegment(String name, int length) {
_name = name;
if (length == Integer.MAX_VALUE) {
length = 0;
}
_length = length;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
for (int j = 0; j < _length; j++) {
bb.appendByte(0);
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
return (int) Math.min(offset + (long) _length, strLen);
}
}
static class PositionPackSegment extends PackSegment {
private final int _length;
PositionPackSegment(int length) {
this("", length);
}
PositionPackSegment(String name, int length) {
if (length == Integer.MAX_VALUE) {
length = 0;
}
_length = length;
}
@Override
public int pack(Env env, StringValue bb, int i, Value[] args)
throws IOException {
while (bb.length() < _length) {
bb.appendByte(0);
}
return i;
}
@Override
public int unpack(Env env, ArrayValue result,
StringValue s, int offset, int strLen) {
throw new UnsupportedOperationException("'@' skip to position");
}
}
static int hexToDigit(Env env, char ch) {
if ('0' <= ch && ch <= '9') {
return (ch - '0');
} else if ('a' <= ch && ch <= 'f') {
return (ch - 'a' + 10);
} else if ('A' <= ch && ch <= 'F') {
return (ch - 'A' + 10);
} else {
env.warning("pack: non hex digit: " + (char) ch);
return 0;
}
}
static char digitToHex(int d) {
d &= 0xf;
if (d < 10) {
return (char) ('0' + d);
} else {
return (char) ('a' + d - 10);
}
}
}