![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.james.imap.decode.DecoderUtils Maven / Gradle / Ivy
/****************************************************************
* 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.james.imap.decode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import javax.mail.Flags;
import org.apache.james.imap.api.display.HumanReadableText;
import org.apache.james.imap.api.message.MessageFlags;
/**
* Utility procedures.
*/
public final class DecoderUtils {
private static final int ASCII_ZERO = '0';
private static final int JAN_BIT = 0x1;
private static final int FEB_BIT = 0x2;
private static final int MAR_BIT = 0x4;
private static final int APR_BIT = 0x8;
private static final int MAY_BIT = 0x10;
private static final int JUN_BIT = 0x20;
private static final int JUL_BIT = 0x40;
private static final int AUG_BIT = 0x80;
private static final int SEP_BIT = 0x100;
private static final int OCT_BIT = 0x200;
private static final int NOV_BIT = 0x400;
private static final int DEC_BIT = 0x800;
private static final int ALL_MONTH_BITS = JAN_BIT | FEB_BIT | MAR_BIT | APR_BIT | MAY_BIT | JUN_BIT | JUL_BIT | AUG_BIT | SEP_BIT | OCT_BIT | NOV_BIT | DEC_BIT;
public static void setFlag(String flagString, Flags flags) throws DecodingException {
if (flagString.equalsIgnoreCase(MessageFlags.ANSWERED_ALL_CAPS)) {
flags.add(Flags.Flag.ANSWERED);
} else if (flagString.equalsIgnoreCase(MessageFlags.DELETED_ALL_CAPS)) {
flags.add(Flags.Flag.DELETED);
} else if (flagString.equalsIgnoreCase(MessageFlags.DRAFT_ALL_CAPS)) {
flags.add(Flags.Flag.DRAFT);
} else if (flagString.equalsIgnoreCase(MessageFlags.FLAGGED_ALL_CAPS)) {
flags.add(Flags.Flag.FLAGGED);
} else if (flagString.equalsIgnoreCase(MessageFlags.SEEN_ALL_CAPS)) {
flags.add(Flags.Flag.SEEN);
} else {
if (flagString.equalsIgnoreCase(MessageFlags.RECENT_ALL_CAPS)) {
// RFC3501 specifically excludes \Recent
// The \Recent flag should be set automatically by the server so throw Exception
//
// See IMAP-316
throw new DecodingException(HumanReadableText.INVALID_SYSTEM_FLAG, "\\Recent flag is now allowed to set.");
} else {
// RFC3501 allows novel flags
flags.add(flagString);
}
}
}
/**
* Decodes the given string as a standard IMAP date-time.
*
* @param chars
* standard IMAP date-time
* @return Date
with time component, not null
* @throws DecodingException
* when this conversion fails
*/
public static LocalDateTime decodeDateTime(CharSequence chars) throws DecodingException {
if (isDateTime(chars)) {
char dayHigh = chars.charAt(0);
char dayLow = chars.charAt(1);
int day = decodeFixedDay(dayHigh, dayLow);
char monthFirstChar = chars.charAt(3);
char monthSecondChar = chars.charAt(4);
char monthThirdChar = chars.charAt(5);
int month = decodeMonth(monthFirstChar, monthSecondChar, monthThirdChar);
char milleniumChar = chars.charAt(7);
char centuryChar = chars.charAt(8);
char decadeChar = chars.charAt(9);
char yearChar = chars.charAt(10);
int year = decodeYear(milleniumChar, centuryChar, decadeChar, yearChar);
char zoneDeterminent = chars.charAt(21);
char zoneDigitOne = chars.charAt(22);
char zoneDigitTwo = chars.charAt(23);
char zoneDigitThree = chars.charAt(24);
char zoneDigitFour = chars.charAt(25);
int offset = decodeZone(zoneDeterminent, zoneDigitOne, zoneDigitTwo, zoneDigitThree, zoneDigitFour);
char hourHigh = chars.charAt(12);
char hourLow = chars.charAt(13);
int hour = applyHourOffset(offset, decodeNumber(hourHigh, hourLow));
char minuteHigh = chars.charAt(15);
char minuteLow = chars.charAt(16);
int minute = applyMinuteOffset(offset, decodeNumber(minuteHigh, minuteLow));
char secondHigh = chars.charAt(18);
char secondLow = chars.charAt(19);
int second = decodeNumber(secondHigh, secondLow);
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.US);
calendar.clear();
calendar.set(year, month, day, hour, minute, second);
return LocalDateTime.ofInstant(calendar.getTime().toInstant(), ZoneId.systemDefault());
} else {
final String message;
if (chars == null) {
message = "Expected a date-time but was nothing.";
} else {
message = new StringBuffer("Expected a date-time but was ").append(chars.toString()).toString();
}
throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, message);
}
}
private static boolean isDateTime(CharSequence chars) {
boolean result;
if (chars == null) {
result = false;
} else if (chars.length() < 26) {
// Be liberal in what you accept
result = false;
} else {
result = true;
}
return result;
}
private static int applyMinuteOffset(int offset, int minutes) {
return minutes - ((Math.abs(offset) % 100) * (offset == 0 ? 0 : offset > 0 ? 1 : -1));
}
private static int applyHourOffset(int offset, int hours) {
return hours - (offset / 100);
}
public static int decodeNumber(char high, char low) throws DecodingException {
return (10 * decodeDigit(high)) + decodeDigit(low);
}
public static int decodeZone(char zoneDeterminent, char zoneDigitOne, char zoneDigitTwo, char zoneDigitThree, char zoneDigitFour) throws DecodingException {
if (isInvalidZone(zoneDeterminent, zoneDigitOne, zoneDigitTwo, zoneDigitThree, zoneDigitFour)) {
throw createTimeZoneException(zoneDeterminent, zoneDigitOne, zoneDigitTwo, zoneDigitThree, zoneDigitFour);
}
int sign;
if (zoneDeterminent == '+') {
sign = 1;
} else if (zoneDeterminent == '-') {
sign = -1;
} else {
throw createTimeZoneException(zoneDeterminent, zoneDigitOne, zoneDigitTwo, zoneDigitThree, zoneDigitFour);
}
return sign * ((1000 * decodeDigit(zoneDigitOne)) + (100 * decodeDigit(zoneDigitTwo)) + (10 * decodeDigit(zoneDigitThree)) + decodeDigit(zoneDigitFour));
}
private static DecodingException createTimeZoneException(char zoneDeterminent, char zoneDigitOne, char zoneDigitTwo, char zoneDigitThree, char zoneDigitFour) {
return new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Expected time-zone but was " + zoneDeterminent + zoneDigitOne + zoneDigitTwo + zoneDigitThree + zoneDigitFour);
}
private static boolean isInvalidZone(char zoneDeterminent, char zoneDigitOne, char zoneDigitTwo, char zoneDigitThree, char zoneDigitFour) {
final boolean result;
if (zoneDeterminent == '+' || zoneDeterminent == '-') {
result = !(isSimpleDigit(zoneDigitOne) && isSimpleDigit(zoneDigitTwo) && isSimpleDigit(zoneDigitThree) && isSimpleDigit(zoneDigitFour));
} else {
result = true;
}
return result;
}
/**
* Is the given character an ASCII digit.
*
* @param character
* character
* @return true if ASCII 0-9, false otherwise
*/
public static boolean isSimpleDigit(char character) {
return !(character < '0' || character > '9');
}
/**
* Decodes a year.
*
* @param milleniumChar
* first digit
* @param centuryChar
* second digit
* @param decadeChar
* third digit
* @param yearChar
* forth digit
* @return {@link Calendar} year
*/
public static int decodeYear(char milleniumChar, char centuryChar, char decadeChar, char yearChar) throws DecodingException {
return (decodeDigit(milleniumChar) * 1000) + (decodeDigit(centuryChar) * 100) + (decodeDigit(decadeChar) * 10) + decodeDigit(yearChar);
}
/**
* Decodes an IMAP date-month
to a {@link Calendar} month.
*
* @param monthFirstChar
* first character in a month triple
* @param monthSecondChar
* second character in a month triple
* @param monthThirdChar
* third character in a month triple
* @return {@link Calendar} month (JAN
=0)
*/
public static int decodeMonth(char monthFirstChar, char monthSecondChar, char monthThirdChar) throws DecodingException {
final int result;
// Bitwise magic! Eliminate possibility by three switches
int possibleMonths = ALL_MONTH_BITS;
switch (monthFirstChar) {
case 'J':
case 'j':
possibleMonths &= (JAN_BIT | JUN_BIT | JUL_BIT);
break;
case 'F':
case 'f':
possibleMonths &= FEB_BIT;
break;
case 'M':
case 'm':
possibleMonths &= (MAR_BIT | MAY_BIT);
break;
case 'A':
case 'a':
possibleMonths &= (APR_BIT | AUG_BIT);
break;
case 'S':
case 's':
possibleMonths &= SEP_BIT;
break;
case 'O':
case 'o':
possibleMonths &= OCT_BIT;
break;
case 'N':
case 'n':
possibleMonths &= NOV_BIT;
break;
case 'D':
case 'd':
possibleMonths &= DEC_BIT;
break;
default:
possibleMonths = 0;
break;
}
switch (monthSecondChar) {
case 'A':
case 'a':
possibleMonths &= (JAN_BIT | MAR_BIT | MAY_BIT);
break;
case 'E':
case 'e':
possibleMonths &= (FEB_BIT | SEP_BIT | DEC_BIT);
break;
case 'P':
case 'p':
possibleMonths &= (APR_BIT);
break;
case 'U':
case 'u':
possibleMonths &= (JUN_BIT | JUL_BIT | AUG_BIT);
break;
case 'C':
case 'c':
possibleMonths &= OCT_BIT;
break;
case 'O':
case 'o':
possibleMonths &= NOV_BIT;
break;
default:
possibleMonths = 0;
break;
}
switch (monthThirdChar) {
case 'N':
case 'n':
possibleMonths &= (JAN_BIT | JUN_BIT);
break;
case 'B':
case 'b':
possibleMonths &= FEB_BIT;
break;
case 'R':
case 'r':
possibleMonths &= (MAR_BIT | APR_BIT);
break;
case 'Y':
case 'y':
possibleMonths &= MAY_BIT;
break;
case 'L':
case 'l':
possibleMonths &= JUL_BIT;
break;
case 'G':
case 'g':
possibleMonths &= AUG_BIT;
break;
case 'P':
case 'p':
possibleMonths &= SEP_BIT;
break;
case 'T':
case 't':
possibleMonths &= OCT_BIT;
break;
case 'V':
case 'v':
possibleMonths &= NOV_BIT;
break;
case 'C':
case 'c':
possibleMonths &= DEC_BIT;
break;
default:
possibleMonths = 0;
break;
}
switch (possibleMonths) {
case JAN_BIT:
result = Calendar.JANUARY;
break;
case FEB_BIT:
result = Calendar.FEBRUARY;
break;
case MAR_BIT:
result = Calendar.MARCH;
break;
case APR_BIT:
result = Calendar.APRIL;
break;
case MAY_BIT:
result = Calendar.MAY;
break;
case JUN_BIT:
result = Calendar.JUNE;
break;
case JUL_BIT:
result = Calendar.JULY;
break;
case AUG_BIT:
result = Calendar.AUGUST;
break;
case SEP_BIT:
result = Calendar.SEPTEMBER;
break;
case OCT_BIT:
result = Calendar.OCTOBER;
break;
case NOV_BIT:
result = Calendar.NOVEMBER;
break;
case DEC_BIT:
result = Calendar.DECEMBER;
break;
default:
throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Expected month name but was " + monthFirstChar + monthSecondChar + monthThirdChar);
}
return result;
}
public static int decodeFixedDay(char dayHigh, char dayLow) throws DecodingException {
int result = decodeDigit(dayLow);
switch (dayHigh) {
case '0':
return result;
case '1':
return result += 10;
case '2':
return result += 20;
case '3':
return result += 30;
case ' ':
return result;
}
throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Expected SP, 0, 1, 2, or 3 but was " + dayHigh);
}
/**
* Decodes a number character into a 0-9
digit.
*
* @return a digit
* @throws DecodingException
* if the char is not a digit
*/
public static int decodeDigit(char character) throws DecodingException {
final int result = character - ASCII_ZERO;
if (result < 0 || result > 9) {
throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Expected a digit but was '" + character + "'");
}
return result;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy