001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package examples.mail; 019 020import java.io.BufferedReader; 021import java.io.File; 022import java.io.FileReader; 023import java.io.IOException; 024import java.net.URI; 025import java.util.ArrayList; 026import java.util.BitSet; 027import java.util.List; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import org.apache.commons.net.imap.IMAPClient; 032 033/** 034 * This is an example program demonstrating how to use the IMAP[S]Client class. 035 * This program connects to a IMAP[S] server and imports messages into the folder from an mbox file. 036 * <p> 037 * Usage: IMAPImportMbox imap[s]://user:password@host[:port]/folder/path <mboxfile> [selectors] 038 * <p> 039 * An example selector might be: 040 * <ul> 041 * <li>1,2,3,7-10</li> 042 * <li>-142986- : this is useful for retrieving messages by apmail number, which appears as From xyz-return-142986-apmail-...</li> 043 * </ul> 044 * <p> 045 * For example:<br> 046 * IMAPImportMbox imaps://user:pass@imap.googlemail.com/imported_messages 201401.mbox 1-10,20 -142986- 047 */ 048public final class IMAPImportMbox 049{ 050 051 private static final String CRLF = "\r\n"; 052 private static final Pattern PATFROM = Pattern.compile(">+From "); // escaped From 053 054 public static void main(String[] args) throws IOException 055 { 056 if (args.length < 2) 057 { 058 System.err.println("Usage: IMAPImportMbox imap[s]://user:password@host[:port]/folder/path <mboxfile> [selectors]"); 059 System.err.println("\tWhere: a selector is a list of numbers/number ranges - 1,2,3-10" + 060 " - or a list of strings to match in the initial From line"); 061 System.exit(1); 062 } 063 064 final URI uri = URI.create(args[0]); 065 final String file = args[1]; 066 067 final File mbox = new File(file); 068 if (!mbox.isFile() || !mbox.canRead()) { 069 throw new IOException("Cannot read mailbox file: " + mbox); 070 } 071 072 String path = uri.getPath(); 073 if (path == null || path.length() < 1) { 074 throw new IllegalArgumentException("Invalid folderPath: '" + path + "'"); 075 } 076 String folder = path.substring(1); // skip the leading / 077 078 List<String> contains = new ArrayList<String>(); // list of strings to find 079 BitSet msgNums = new BitSet(); // list of message numbers 080 081 for(int i = 2; i < args.length; i++) { 082 String arg = args[i]; 083 if (arg.matches("\\d+(-\\d+)?(,\\d+(-\\d+)?)*")) { // number,m-n 084 for(String entry : arg.split(",")) { 085 String []parts = entry.split("-"); 086 if (parts.length == 2) { // m-n 087 int low = Integer.parseInt(parts[0]); 088 int high = Integer.parseInt(parts[1]); 089 for(int j=low; j <= high; j++) { 090 msgNums.set(j); 091 } 092 } else { 093 msgNums.set(Integer.parseInt(entry)); 094 } 095 } 096 } else { 097 contains.add(arg); // not a number/number range 098 } 099 } 100// System.out.println(msgNums.toString()); 101// System.out.println(java.util.Arrays.toString(contains.toArray())); 102 103 // Connect and login 104 final IMAPClient imap = IMAPUtils.imapLogin(uri, 10000, null); 105 106 int total = 0; 107 int loaded = 0; 108 try { 109 imap.setSoTimeout(6000); 110 111 final BufferedReader br = new BufferedReader(new FileReader(file)); // TODO charset? 112 113 String line; 114 StringBuilder sb = new StringBuilder(); 115 boolean wanted = false; // Skip any leading rubbish 116 while((line=br.readLine())!=null) { 117 if (line.startsWith("From ")) { // start of message; i.e. end of previous (if any) 118 if (process(sb, imap, folder, total)) { // process previous message (if any) 119 loaded++; 120 } 121 sb.setLength(0); 122 total ++; 123 wanted = wanted(total, line, msgNums, contains); 124 } else if (startsWith(line, PATFROM)) { // Unescape ">+From " in body text 125 line = line.substring(1); 126 } 127 // TODO process first Received: line to determine arrival date? 128 if (wanted) { 129 sb.append(line); 130 sb.append(CRLF); 131 } 132 } 133 br.close(); 134 if (wanted && process(sb, imap, folder, total)) { // last message (if any) 135 loaded++; 136 } 137 } catch (IOException e) { 138 System.out.println(imap.getReplyString()); 139 e.printStackTrace(); 140 System.exit(10); 141 return; 142 } finally { 143 imap.logout(); 144 imap.disconnect(); 145 } 146 System.out.println("Processed " + total + " messages, loaded " + loaded); 147 } 148 149 private static boolean startsWith(String input, Pattern pat) { 150 Matcher m = pat.matcher(input); 151 return m.lookingAt(); 152 } 153 154 private static boolean process(final StringBuilder sb, final IMAPClient imap, final String folder 155 ,final int msgNum) throws IOException { 156 final int length = sb.length(); 157 boolean haveMessage = length > 2; 158 if (haveMessage) { 159 System.out.println("MsgNum: " + msgNum +" Length " + length); 160 sb.setLength(length-2); // drop trailing CRLF 161 String msg = sb.toString(); 162 if (!imap.append(folder, null, null, msg)) { 163 throw new IOException("Failed to import message: " + msgNum + " " + imap.getReplyString()); 164 } 165 } 166 return haveMessage; 167 } 168 169 /** 170 * Is the message wanted? 171 * 172 * @param msgNum the message number 173 * @param line the From line 174 * @param msgNums the list of wanted message numbers 175 * @param contains the list of strings to be contained 176 * @return true if the message is wanted 177 */ 178 private static boolean wanted(int msgNum, String line, BitSet msgNums, List<String> contains) { 179 return (msgNums.isEmpty() && contains.isEmpty()) // no selectors 180 || msgNums.get(msgNum) // matches message number 181 || listContains(contains, line); // contains string 182 } 183 184 /** 185 * Is at least one entry in the list contained in the string? 186 * @param contains the list of strings to look for 187 * @param string the String to check against 188 * @return true if at least one entry in the contains list is contained in the string 189 */ 190 private static boolean listContains(List<String> contains, String string) { 191 for(String entry : contains) { 192 if (string.contains(entry)) { 193 return true; 194 } 195 } 196 return false; 197 } 198 199}