Methods Summary |
---|
private byte | asciiCharToNumericValue(byte c)Converts '0' => 0, 'A' => 10, etc.
if (c >= '0" && c <= '9") {
return (byte)(c - '0");
} else if (c >= 'A" && c <= 'Z") {
return (byte)(0xA + (c - 'A"));
} else if (c >= 'a" && c <= 'z") {
return (byte)(0xA + (c - 'a"));
} else {
/*
* This should never happen since all calls to this method
* are preceded by a check that c is in [0-9A-Za-z]
*/
throw new IllegalArgumentException((char) c
+ " is not a hexadecimal digit");
}
|
public void | close()Closes the underlying stream.
stream.close();
|
private void | fillBuffer()Causes the pushback queue to get populated if it is empty, then
consumes and decodes bytes out of it until one or more bytes are
in the byte queue. This decoding step performs the actual QP
decoding.
byte msdChar = 0; // first digit of escaped num
while (byteq.count() == 0) {
if (pushbackq.count() == 0) {
populatePushbackQueue();
if (pushbackq.count() == 0)
return;
}
byte b = (byte)pushbackq.dequeue();
switch (state) {
case 0: // start state, no bytes pending
if (b != '=") {
byteq.enqueue(b);
break; // state remains 0
} else {
state = 1;
break;
}
case 1: // encountered "=" so far
if (b == '\r") {
state = 2;
break;
} else if ((b >= '0" && b <= '9") || (b >= 'A" && b <= 'F") || (b >= 'a" && b <= 'f")) {
state = 3;
msdChar = b; // save until next digit encountered
break;
} else if (b == '=") {
/*
* Special case when == is encountered.
* Emit one = and stay in this state.
*/
if (log.isWarnEnabled()) {
log.warn("Malformed MIME; got ==");
}
byteq.enqueue((byte)'=");
break;
} else {
if (log.isWarnEnabled()) {
log.warn("Malformed MIME; expected \\r or "
+ "[0-9A-Z], got " + b);
}
state = 0;
byteq.enqueue((byte)'=");
byteq.enqueue(b);
break;
}
case 2: // encountered "=\r" so far
if (b == '\n") {
state = 0;
break;
} else {
if (log.isWarnEnabled()) {
log.warn("Malformed MIME; expected "
+ (int)'\n" + ", got " + b);
}
state = 0;
byteq.enqueue((byte)'=");
byteq.enqueue((byte)'\r");
byteq.enqueue(b);
break;
}
case 3: // encountered =<digit> so far; expecting another <digit> to complete the octet
if ((b >= '0" && b <= '9") || (b >= 'A" && b <= 'F") || (b >= 'a" && b <= 'f")) {
byte msd = asciiCharToNumericValue(msdChar);
byte low = asciiCharToNumericValue(b);
state = 0;
byteq.enqueue((byte)((msd << 4) | low));
break;
} else {
if (log.isWarnEnabled()) {
log.warn("Malformed MIME; expected "
+ "[0-9A-Z], got " + b);
}
state = 0;
byteq.enqueue((byte)'=");
byteq.enqueue(msdChar);
byteq.enqueue(b);
break;
}
default: // should never happen
log.error("Illegal state: " + state);
state = 0;
byteq.enqueue(b);
break;
}
}
|
private void | populatePushbackQueue()Pulls bytes out of the underlying stream and places them in the
pushback queue. This is necessary (vs. reading from the
underlying stream directly) to detect and filter out "transport
padding" whitespace, i.e., all whitespace that appears immediately
before a CRLF.
//Debug.verify(pushbackq.count() == 0, "PopulatePushbackQueue called when pushback queue was not empty!");
if (pushbackq.count() != 0)
return;
while (true) {
int i = stream.read();
switch (i) {
case -1:
// stream is done
pushbackq.clear(); // discard any whitespace preceding EOF
return;
case ' ":
case '\t":
pushbackq.enqueue((byte)i);
break;
case '\r":
case '\n":
pushbackq.clear(); // discard any whitespace preceding EOL
pushbackq.enqueue((byte)i);
return;
default:
pushbackq.enqueue((byte)i);
return;
}
}
|
public int | read()
fillBuffer();
if (byteq.count() == 0)
return -1;
else {
byte val = byteq.dequeue();
if (val >= 0)
return val;
else
return val & 0xFF;
}
|