blob: eaeb0a8413b6866607c2daf9b59d9513f0fd9e2c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.net.examples.mail;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.net.imap.IMAPClient;
/**
* This is an example program demonstrating how to use the IMAP[S]Client class.
* This program connects to a IMAP[S] server and imports messages into the folder from an mbox file.
* <p>
* Usage: IMAPImportMbox imap[s]://user:password@host[:port]/folder/path <mboxfile> [selectors]
* <p>
* An example selector might be:
* <ul>
* <li>1,2,3,7-10</li>
* <li>-142986- : this is useful for retrieving messages by apmail number, which appears as From xyz-return-142986-apmail-...</li>
* </ul>
* <p>
* For example:<br>
* IMAPImportMbox imaps://user:pass@imap.googlemail.com/imported_messages 201401.mbox 1-10,20 -142986-
*/
public final class IMAPImportMbox
{
private static final String CRLF = "\r\n";
private static final Pattern PATFROM = Pattern.compile(">+From "); // escaped From
public static void main(String[] args) throws IOException
{
if (args.length < 2)
{
System.err.println("Usage: IMAPImportMbox imap[s]://user:password@host[:port]/folder/path <mboxfile> [selectors]");
System.err.println("\tWhere: a selector is a list of numbers/number ranges - 1,2,3-10" +
" - or a list of strings to match in the initial From line");
System.exit(1);
}
final URI uri = URI.create(args[0]);
final String file = args[1];
final File mbox = new File(file);
if (!mbox.isFile() || !mbox.canRead()) {
throw new IOException("Cannot read mailbox file: " + mbox);
}
String path = uri.getPath();
if (path == null || path.length() < 1) {
throw new IllegalArgumentException("Invalid folderPath: '" + path + "'");
}
String folder = path.substring(1); // skip the leading /
List<String> contains = new ArrayList<String>(); // list of strings to find
BitSet msgNums = new BitSet(); // list of message numbers
for(int i = 2; i < args.length; i++) {
String arg = args[i];
if (arg.matches("\\d+(-\\d+)?(,\\d+(-\\d+)?)*")) { // number,m-n
for(String entry : arg.split(",")) {
String []parts = entry.split("-");
if (parts.length == 2) { // m-n
int low = Integer.parseInt(parts[0]);
int high = Integer.parseInt(parts[1]);
for(int j=low; j <= high; j++) {
msgNums.set(j);
}
} else {
msgNums.set(Integer.parseInt(entry));
}
}
} else {
contains.add(arg); // not a number/number range
}
}
// System.out.println(msgNums.toString());
// System.out.println(java.util.Arrays.toString(contains.toArray()));
// Connect and login
final IMAPClient imap = IMAPUtils.imapLogin(uri, 10000, null);
int total = 0;
int loaded = 0;
try {
imap.setSoTimeout(6000);
final BufferedReader br = new BufferedReader(new FileReader(file)); // TODO charset?
String line;
StringBuilder sb = new StringBuilder();
boolean wanted = false; // Skip any leading rubbish
while((line=br.readLine())!=null) {
if (line.startsWith("From ")) { // start of message; i.e. end of previous (if any)
if (process(sb, imap, folder, total)) { // process previous message (if any)
loaded++;
}
sb.setLength(0);
total ++;
wanted = wanted(total, line, msgNums, contains);
} else if (startsWith(line, PATFROM)) { // Unescape ">+From " in body text
line = line.substring(1);
}
// TODO process first Received: line to determine arrival date?
if (wanted) {
sb.append(line);
sb.append(CRLF);
}
}
br.close();
if (wanted && process(sb, imap, folder, total)) { // last message (if any)
loaded++;
}
} catch (IOException e) {
System.out.println("Error processing msg: " + total + " " + imap.getReplyString());
e.printStackTrace();
System.exit(10);
return;
} finally {
imap.logout();
imap.disconnect();
}
System.out.println("Processed " + total + " messages, loaded " + loaded);
}
private static boolean startsWith(String input, Pattern pat) {
Matcher m = pat.matcher(input);
return m.lookingAt();
}
private static String getDate(String msg) {
final Pattern FROM_RE = Pattern.compile("From \\S+ +\\S+ (\\S+) ?(\\S+) (\\S+) (\\S+)"); // From SENDER Fri Sep 13 17:04:01 2019
// [Fri] Sep 13 HMS 2019
// output date: 13-Sep-2019 17:04:01 +0000
String date = null;
Matcher m = FROM_RE.matcher(msg);
if (m.lookingAt()) {
date = m.group(2)+"-"+m.group(1)+"-"+m.group(4)+" "+m.group(3)+" +0000";
}
return date;
}
private static boolean process(final StringBuilder sb, final IMAPClient imap, final String folder
,final int msgNum) throws IOException {
final int length = sb.length();
boolean haveMessage = length > 2;
if (haveMessage) {
System.out.println("MsgNum: " + msgNum +" Length " + length);
sb.setLength(length-2); // drop trailing CRLF (mbox format has trailing blank line)
String msg = sb.toString();
if (!imap.append(folder, null, getDate(msg), msg)) {
throw new IOException("Failed to import message: " + msgNum + " " + imap.getReplyString());
}
}
return haveMessage;
}
/**
* Is the message wanted?
*
* @param msgNum the message number
* @param line the From line
* @param msgNums the list of wanted message numbers
* @param contains the list of strings to be contained
* @return true if the message is wanted
*/
private static boolean wanted(int msgNum, String line, BitSet msgNums, List<String> contains) {
return (msgNums.isEmpty() && contains.isEmpty()) // no selectors
|| msgNums.get(msgNum) // matches message number
|| listContains(contains, line); // contains string
}
/**
* Is at least one entry in the list contained in the string?
* @param contains the list of strings to look for
* @param string the String to check against
* @return true if at least one entry in the contains list is contained in the string
*/
private static boolean listContains(List<String> contains, String string) {
for(String entry : contains) {
if (string.contains(entry)) {
return true;
}
}
return false;
}
}