use of i2p.susi.util.MemoryBuffer in project i2p.i2p by i2p.
the class HeaderLine method decode.
/**
* Decode all the header lines, up through \r\n\r\n,
* and puts them in the ReadBuffer, including the \r\n\r\n
*/
public void decode(InputStream in, Buffer bout) throws IOException {
OutputStream out = bout.getOutputStream();
boolean linebreak = false;
boolean lastCharWasQuoted = false;
byte[] encodedWord = null;
// we support one char of pushback,
// to catch some simple malformed input
int pushbackChar = 0;
boolean hasPushback = false;
while (true) {
int c;
if (hasPushback) {
c = pushbackChar;
hasPushback = false;
// Debug.debug(Debug.DEBUG, "Loop " + count + " Using pbchar(dec) " + c);
} else {
c = in.read();
if (c < 0)
break;
}
if (c == '=') {
// Sadly, base64 can be a lot longer
if (encodedWord == null)
encodedWord = new byte[DECODE_MAX];
int offset = 0;
int f1 = 0, f2 = 0, f3 = 0, f4 = 0;
encodedWord[offset++] = (byte) c;
// but for the most part it gets thrown out, as RFC 2047 allows
for (; offset < DECODE_MAX; offset++) {
c = in.read();
if (c == '?') {
if (f1 == 0)
f1 = offset;
else if (f2 == 0)
f2 = offset;
else if (f3 == 0)
f3 = offset;
else if (f4 == 0)
f4 = offset;
} else if (c == -1) {
break;
} else if (c == '\r' || c == '\n') {
pushbackChar = c;
hasPushback = true;
break;
} else if (offset == 1) {
// no '?' after '='
out.write('=');
pushbackChar = c;
hasPushback = true;
break;
}
encodedWord[offset] = (byte) c;
// store one past the 4th '?', presumably the '='
if (f4 > 0 && offset >= f4 + 1) {
if (c == '=') {
offset++;
} else {
pushbackChar = c;
hasPushback = true;
}
break;
}
}
// net.i2p.util.HexDump.dump(encodedWord, 0, offset));
if (f4 == 0) {
// at most 1 byte is pushed back
if (f1 == 0) {
// This is normal
continue;
} else if (f2 == 0) {
// =? but no more ?
// output what we buffered
Debug.debug(Debug.DEBUG, "2nd '?' not found");
for (int i = 0; i < offset; i++) {
out.write(encodedWord[i] & 0xff);
}
continue;
} else if (f3 == 0) {
// discard what we buffered
Debug.debug(Debug.DEBUG, "3rd '?' not found");
continue;
} else {
// probably just too long, but could be end of line without the "?=".
// synthesize a 4th '?' in an attempt to output
// something, probably with some trailing garbage
Debug.debug(Debug.DEBUG, "4th '?' not found");
f4 = offset + 1;
// keep going and output what we have
}
}
/*
* 4th question mark found, we are complete, so lets start
*/
String enc = (encodedWord[f2 + 1] == 'Q' || encodedWord[f2 + 1] == 'q') ? "quoted-printable" : ((encodedWord[f2 + 1] == 'B' || encodedWord[f2 + 1] == 'b') ? "base64" : null);
// System.err.println( "4th ? found at " + f4 + ", encoding=" + enc );
if (enc != null) {
Encoding e = EncodingFactory.getEncoding(enc);
if (e != null) {
try {
// System.err.println( "decode(" + (f3 + 1) + "," + ( f4 - f3 - 1 ) + ")" );
ReadBuffer tmpIn = new ReadBuffer(encodedWord, f3 + 1, f4 - f3 - 1);
// decoded won't be longer than encoded
MemoryBuffer tmp = new MemoryBuffer(f4 - f3 - 1);
try {
e.decode(tmpIn, tmp);
} catch (EOFException eof) {
// Keep going and output what we got, if any
if (Debug.getLevel() >= Debug.DEBUG) {
Debug.debug(Debug.DEBUG, "q-w " + enc, eof);
Debug.debug(Debug.DEBUG, net.i2p.util.HexDump.dump(encodedWord));
}
}
tmp.writeComplete(true);
// get charset
String charset = new String(encodedWord, f1 + 1, f2 - f1 - 1, "ISO-8859-1");
String clc = charset.toLowerCase(Locale.US);
if (clc.equals("utf-8") || clc.equals("utf8")) {
// FIXME could be more efficient?
InputStream tis = tmp.getInputStream();
if (enc.equals("quoted-printable")) {
int d;
while ((d = tis.read()) != -1) {
out.write(d == '_' ? 32 : d);
}
} else {
DataHelper.copy(tis, out);
}
} else {
// FIXME could be more efficient?
// decode string
String decoded = new String(tmp.getContent(), tmp.getOffset(), tmp.getLength(), charset);
// encode string
byte[] utf8 = DataHelper.getUTF8(decoded);
if (enc.equals("quoted-printable")) {
for (int j = 0; j < utf8.length; j++) {
byte d = utf8[j];
out.write(d == '_' ? 32 : d);
}
} else {
out.write(utf8);
}
}
lastCharWasQuoted = true;
continue;
} catch (IOException e1) {
Debug.debug(Debug.ERROR, "q-w " + enc, e1);
if (Debug.getLevel() >= Debug.DEBUG) {
Debug.debug(Debug.DEBUG, net.i2p.util.HexDump.dump(encodedWord));
}
} catch (RuntimeException e1) {
Debug.debug(Debug.ERROR, "q-w " + enc, e1);
if (Debug.getLevel() >= Debug.DEBUG) {
Debug.debug(Debug.DEBUG, net.i2p.util.HexDump.dump(encodedWord));
}
}
} else {
// can't happen
Debug.debug(Debug.DEBUG, "No decoder for " + enc);
}
// e != null
} else {
Debug.debug(Debug.DEBUG, "Invalid encoding '" + (char) encodedWord[f2 + 1] + '\'');
}
// enc != null
} else // c == '='
if (c == '\r') {
if ((c = in.read()) == '\n') {
/*
* delay linebreak in case of long line
*/
linebreak = true;
} else {
// pushback?
Debug.debug(Debug.DEBUG, "No \\n after \\r");
}
}
// swallow whitespace here if lastCharWasQuoted
if (linebreak) {
linebreak = false;
for (int i = 0; ; i++) {
c = in.read();
if (c == -1)
break;
if (c != ' ' && c != '\t') {
if (i == 0) {
/*
* new line does not start with whitespace, so its not a new part of a
* long line
*/
out.write('\r');
out.write('\n');
if (c == '\r') {
linebreak = true;
// \n
in.read();
break;
}
} else {
// treat all preceding whitespace as a single one
if (!lastCharWasQuoted)
out.write(' ');
}
pushbackChar = c;
hasPushback = true;
break;
}
/*
* skip whitespace
*/
}
// if \r\n\r\n, we are done
if (linebreak)
break;
} else {
/*
* print out everything else literally
*/
out.write(c);
lastCharWasQuoted = false;
}
}
// while true
if (linebreak) {
out.write('\r');
out.write('\n');
}
bout.writeComplete(true);
}
use of i2p.susi.util.MemoryBuffer in project i2p.i2p by i2p.
the class Encoding method decode.
/**
* This implementation just calls decode(in.content, in.offset, in.length).
* Most classes will not need to override.
*
* @param in
* @see Encoding#decode(byte[], int, int)
* @throws DecodingException
* @since 0.9.33 implementation moved from subclasses
*/
public Buffer decode(Buffer in) throws IOException {
MemoryBuffer rv = new MemoryBuffer(4096);
decode(in, rv);
return rv;
}
use of i2p.susi.util.MemoryBuffer in project i2p.i2p by i2p.
the class Mail method parseHeaders.
/**
* @return all headers, to pass to MailPart, or null on error
*/
private String[] parseHeaders(InputStream in) {
String[] headerLines = null;
error = "";
if (header != null) {
boolean ok = true;
Encoding html = EncodingFactory.getEncoding("HTML");
if (html == null) {
error += "HTML encoder not found.\n";
ok = false;
}
Encoding hl = EncodingFactory.getEncoding("HEADERLINE");
if (hl == null) {
error += "Header line encoder not found.\n";
ok = false;
}
if (ok) {
try {
EOFOnMatchInputStream eofin = new EOFOnMatchInputStream(in, HEADER_MATCH);
MemoryBuffer decoded = new MemoryBuffer(4096);
hl.decode(eofin, decoded);
if (!eofin.wasFound())
Debug.debug(Debug.DEBUG, "EOF hit before \\r\\n\\r\\n in Mail");
// Fixme UTF-8 to bytes to UTF-8
headerLines = DataHelper.split(new String(decoded.getContent(), decoded.getOffset(), decoded.getLength()), "\r\n");
for (int j = 0; j < headerLines.length; j++) {
String line = headerLines[j];
if (line.length() == 0)
break;
String hlc = line.toLowerCase(Locale.US);
if (hlc.startsWith("from:")) {
sender = line.substring(5).trim();
// formattedSender = getAddress( sender );
shortSender = sender.replace("\"", "").trim();
int lt = shortSender.indexOf('<');
if (lt > 0)
shortSender = shortSender.substring(0, lt).trim();
else if (lt < 0 && shortSender.contains("@"))
// add missing <> (but thunderbird doesn't...)
shortSender = '<' + shortSender + '>';
boolean trim = shortSender.length() > 45;
if (trim)
shortSender = ServletUtil.truncate(shortSender, 42).trim();
shortSender = html.encode(shortSender);
if (trim)
// must be after html encode
shortSender += "…";
} else if (hlc.startsWith("date:")) {
dateString = line.substring(5).trim();
long dateLong = RFC822Date.parse822Date(dateString);
if (dateLong > 0)
setDate(dateLong);
} else if (hlc.startsWith("subject:")) {
subject = line.substring(8).trim();
shortSubject = subject;
boolean trim = subject.length() > 75;
if (trim)
shortSubject = ServletUtil.truncate(subject, 72).trim();
shortSubject = html.encode(shortSubject);
if (trim)
// must be after html encode
shortSubject += "…";
} else if (hlc.startsWith("reply-to:")) {
reply = getAddress(line.substring(9).trim());
} else if (hlc.startsWith("to:")) {
ArrayList<String> list = new ArrayList<String>();
getRecipientsFromList(list, line.substring(3).trim(), true);
if (list.isEmpty()) {
// don't set
} else if (to == null) {
to = list.toArray(new String[list.size()]);
} else if (cc == null) {
// Susimail bug before 0.9.33, sent 2nd To line that was really Cc
cc = list.toArray(new String[list.size()]);
} else {
// add to the array, shouldn't happen
for (int i = 0; i < to.length; i++) {
list.add(i, to[i]);
}
to = list.toArray(new String[list.size()]);
}
} else if (hlc.startsWith("cc:")) {
ArrayList<String> list = new ArrayList<String>();
getRecipientsFromList(list, line.substring(3).trim(), true);
if (list.isEmpty()) {
// don't set
} else if (cc == null) {
cc = list.toArray(new String[list.size()]);
} else {
// add to the array, shouldn't happen
for (int i = 0; i < cc.length; i++) {
list.add(i, cc[i]);
}
cc = list.toArray(new String[list.size()]);
}
} else if (hlc.equals("x-spam-flag: yes")) {
// TODO trust.spam.headers config
isSpam = true;
} else if (hlc.startsWith("content-type:")) {
// this is duplicated in MailPart but
// we want to know if we have attachments, even if
// we haven't fetched the body
contentType = line.substring(13).trim();
} else if (hlc.startsWith("message-id:")) {
messageID = line.substring(11).trim();
}
}
} catch (Exception e) {
error += "Error parsing mail header: " + e.getClass().getName() + '\n';
Debug.debug(Debug.ERROR, "Parse error", e);
}
}
}
return headerLines;
}
use of i2p.susi.util.MemoryBuffer in project i2p.i2p by i2p.
the class MailCache method getMail.
/**
* Fetch any needed data from pop3 server.
* Mail objects are inserted into the requests.
* After this, call getUIDLs() to get all known mail UIDLs.
* MUST already be connected, otherwise returns false.
*
* Blocking.
*
* @param mode HEADER or ALL only
* @return true if any were fetched
* @since 0.9.13
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean getMail(FetchMode mode) {
if (mode == FetchMode.CACHE_ONLY)
throw new IllegalArgumentException();
boolean hOnly = mode == FetchMode.HEADER;
Collection<String> popKnown = mailbox.getUIDLs();
if (popKnown == null)
return false;
List<POP3Request> fetches = new ArrayList<POP3Request>();
// requests.to send off
for (String uidl : popKnown) {
Mail mail = null, newMail = null;
boolean headerOnly = hOnly;
/*
* synchronize update to hashtable
*/
synchronized (mails) {
mail = mails.get(uidl);
if (mail == null) {
newMail = new Mail(uidl);
mails.put(uidl, newMail);
}
}
if (mail == null) {
mail = newMail;
mail.setSize(mailbox.getSize(uidl));
}
if (mail.markForDeletion)
continue;
long sz = mail.getSize();
if (sz > 0 && sz <= FETCH_ALL_SIZE)
headerOnly = false;
if (headerOnly) {
if (!mail.hasHeader()) {
if (disk != null) {
if (disk.getMail(mail, true)) {
Debug.debug(Debug.DEBUG, "Loaded header from disk cache: " + uidl);
// note that disk loaded the full body if it had it
if (mail.hasBody() && !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
// we already have it, send delete
mailbox.queueForDeletion(mail.uidl);
}
// found on disk, woo
continue;
}
}
POP3Request pr = new POP3Request(mail, true, new MemoryBuffer(1024));
fetches.add(pr);
} else {
if (mail.hasBody() && !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
// we already have it, send delete
mailbox.queueForDeletion(mail.uidl);
}
}
} else {
if (!mail.hasBody()) {
if (disk != null) {
if (disk.getMail(mail, false)) {
Debug.debug(Debug.DEBUG, "Loaded body from disk cache: " + uidl);
// note that disk loaded the full body if it had it
if (!Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
// we already have it, send delete
mailbox.queueForDeletion(mail.uidl);
}
// found on disk, woo
continue;
}
}
File file = new File(_context.getTempDir(), "susimail-new-" + _context.random().nextLong());
POP3Request pr = new POP3Request(mail, false, new FileBuffer(file));
fetches.add(pr);
} else {
if (!Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
// we already have it, send delete
mailbox.queueForDeletion(mail.uidl);
}
}
}
}
boolean rv = false;
if (!fetches.isEmpty()) {
// Send off the fetches
// gaah compiler
List foo = fetches;
List<FetchRequest> bar = foo;
mailbox.getBodies(bar);
// Process results
for (POP3Request pr : fetches) {
if (pr.getSuccess()) {
Mail mail = pr.mail;
if (!mail.hasHeader())
mail.setNew(true);
if (pr.getHeaderOnly()) {
mail.setHeader(pr.getBuffer());
} else {
mail.setBody(pr.getBuffer());
}
rv = true;
if (disk != null) {
if (disk.saveMail(mail) && mail.hasBody() && !Boolean.parseBoolean(Config.getProperty(WebMail.CONFIG_LEAVE_ON_SERVER))) {
mailbox.queueForDeletion(mail.uidl);
}
}
}
}
}
return rv;
}
use of i2p.susi.util.MemoryBuffer in project i2p.i2p by i2p.
the class POP3MailBox method getHeader.
/**
* retrieves header from pop3 server (with TOP command and RETR as fallback)
* Caller must sync.
*
* @param id message id
* @return Byte buffer containing header data or null
*/
private Buffer getHeader(int id) {
Debug.debug(Debug.DEBUG, "getHeader(" + id + ")");
Buffer header = null;
if (id >= 1 && id <= mails) {
try {
socket.setSoTimeout(120 * 1000);
} catch (IOException ioe) {
}
/*
* try 'TOP n 0' command
*/
header = sendCmdN("TOP " + id + " 0", new MemoryBuffer(1024));
if (header == null) {
/*
* try 'RETR n' command
*/
header = sendCmdN("RETR " + id, new MemoryBuffer(2048));
if (header == null)
Debug.debug(Debug.DEBUG, "RETR returned null");
}
if (socket != null)
try {
socket.setSoTimeout(300 * 1000);
} catch (IOException ioe) {
}
} else {
lastError = "Message id out of range.";
}
return header;
}
Aggregations