mirror of https://github.com/M66B/FairEmail.git
commit
c1067f3f52
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,579 @@
|
||||
package eu.faircode.email;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FairEmail is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018-2020 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.OnLifecycleEvent;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
/*
|
||||
import com.android.billingclient.api.AcknowledgePurchaseParams;
|
||||
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
|
||||
import com.android.billingclient.api.BillingClient;
|
||||
import com.android.billingclient.api.BillingClientStateListener;
|
||||
import com.android.billingclient.api.BillingFlowParams;
|
||||
import com.android.billingclient.api.BillingResult;
|
||||
import com.android.billingclient.api.ConsumeParams;
|
||||
import com.android.billingclient.api.ConsumeResponseListener;
|
||||
import com.android.billingclient.api.Purchase;
|
||||
import com.android.billingclient.api.PurchaseHistoryRecord;
|
||||
import com.android.billingclient.api.PurchaseHistoryResponseListener;
|
||||
import com.android.billingclient.api.PurchasesUpdatedListener;
|
||||
import com.android.billingclient.api.SkuDetails;
|
||||
import com.android.billingclient.api.SkuDetailsParams;
|
||||
import com.android.billingclient.api.SkuDetailsResponseListener;
|
||||
*/
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedListener,*/ FragmentManager.OnBackStackChangedListener {
|
||||
//private BillingClient billingClient = null;
|
||||
//private Map<String, SkuDetails> skuDetails = new HashMap<>();
|
||||
private List<IBillingListener> listeners = new ArrayList<>();
|
||||
|
||||
static final String ACTION_PURCHASE = BuildConfig.APPLICATION_ID + ".ACTION_PURCHASE";
|
||||
static final String ACTION_PURCHASE_CHECK = BuildConfig.APPLICATION_ID + ".ACTION_PURCHASE_CHECK";
|
||||
static final String ACTION_PURCHASE_ERROR = BuildConfig.APPLICATION_ID + ".ACTION_PURCHASE_ERROR";
|
||||
|
||||
private final static long MAX_SKU_CACHE_DURATION = 24 * 3600 * 1000L; // milliseconds
|
||||
private final static long MAX_SKU_NOACK_DURATION = 24 * 3600 * 1000L; // milliseconds
|
||||
|
||||
@Override
|
||||
@SuppressLint("MissingSuperCall")
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
onCreate(savedInstanceState, true);
|
||||
}
|
||||
|
||||
protected void onCreate(Bundle savedInstanceState, boolean standalone) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (standalone) {
|
||||
setContentView(R.layout.activity_billing);
|
||||
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
fragmentTransaction.replace(R.id.content_frame, new FragmentPro()).addToBackStack("pro");
|
||||
fragmentTransaction.commit();
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
}
|
||||
|
||||
if (Helper.isPlayStoreInstall()) {
|
||||
Log.i("IAB start");
|
||||
//billingClient = BillingClient.newBuilder(this)
|
||||
// .enablePendingPurchases()
|
||||
// .setListener(this)
|
||||
// .build();
|
||||
//billingClient.startConnection(billingClientStateListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
IntentFilter iff = new IntentFilter();
|
||||
iff.addAction(ACTION_PURCHASE);
|
||||
iff.addAction(ACTION_PURCHASE_CHECK);
|
||||
iff.addAction(ACTION_PURCHASE_ERROR);
|
||||
lbm.registerReceiver(receiver, iff);
|
||||
|
||||
//if (billingClient != null && billingClient.isReady())
|
||||
// queryPurchases();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this);
|
||||
lbm.unregisterReceiver(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
//if (billingClient != null)
|
||||
// billingClient.endConnection();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static String getSkuPro() {
|
||||
if (BuildConfig.DEBUG)
|
||||
return "android.test.purchased";
|
||||
else
|
||||
return BuildConfig.APPLICATION_ID + ".pro";
|
||||
}
|
||||
|
||||
private static String getChallenge(Context context) throws NoSuchAlgorithmException {
|
||||
String android_id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
return Helper.sha256(android_id);
|
||||
}
|
||||
|
||||
private static String getResponse(Context context) throws NoSuchAlgorithmException {
|
||||
return Helper.sha256(BuildConfig.APPLICATION_ID + getChallenge(context));
|
||||
}
|
||||
|
||||
static boolean activatePro(Context context, Uri data) throws NoSuchAlgorithmException {
|
||||
String challenge = getChallenge(context);
|
||||
String response = data.getQueryParameter("response");
|
||||
Log.i("IAB challenge=" + challenge);
|
||||
Log.i("IAB response=" + response);
|
||||
String expected = getResponse(context);
|
||||
if (expected.equals(response)) {
|
||||
Log.i("IAB response valid");
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit()
|
||||
.putBoolean("pro", true)
|
||||
.putBoolean("play_store", false)
|
||||
.apply();
|
||||
|
||||
WidgetUnified.updateData(context);
|
||||
return true;
|
||||
} else {
|
||||
Log.i("IAB response invalid");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isPro(Context context) {
|
||||
if (BuildConfig.DEBUG && false)
|
||||
return true;
|
||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean("pro", false);
|
||||
}
|
||||
|
||||
private BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
|
||||
if (ACTION_PURCHASE.equals(intent.getAction()))
|
||||
onPurchase(intent);
|
||||
else if (ACTION_PURCHASE_CHECK.equals(intent.getAction()))
|
||||
;//onPurchaseCheck(intent);
|
||||
else if (ACTION_PURCHASE_ERROR.equals(intent.getAction()))
|
||||
;//onPurchaseError(intent);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void onPurchase(Intent intent) {
|
||||
if (Helper.isPlayStoreInstall()) {
|
||||
//BillingFlowParams.Builder flowParams = BillingFlowParams.newBuilder();
|
||||
//if (skuDetails.containsKey(getSkuPro())) {
|
||||
// Log.i("IAB purchase SKU=" + skuDetails.get(getSkuPro()));
|
||||
// flowParams.setSkuDetails(skuDetails.get(getSkuPro()));
|
||||
//}
|
||||
|
||||
//BillingResult result = billingClient.launchBillingFlow(this, flowParams.build());
|
||||
//if (result.getResponseCode() != BillingClient.BillingResponseCode.OK)
|
||||
// reportError(result, "IAB launch billing flow");
|
||||
} else
|
||||
try {
|
||||
Uri uri = Uri.parse(BuildConfig.PRO_FEATURES_URI + "?challenge=" + getChallenge(this));
|
||||
Helper.view(this, uri, true);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
Log.unexpectedError(getSupportFragmentManager(), ex);
|
||||
}
|
||||
}
|
||||
/*
|
||||
private void onPurchaseCheck(Intent intent) {
|
||||
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, new PurchaseHistoryResponseListener() {
|
||||
@Override
|
||||
public void onPurchaseHistoryResponse(BillingResult result, List<PurchaseHistoryRecord> records) {
|
||||
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
for (PurchaseHistoryRecord record : records)
|
||||
Log.i("IAB history=" + record.toString());
|
||||
|
||||
queryPurchases();
|
||||
|
||||
ToastEx.makeText(ActivityBilling.this, R.string.title_setup_done, Toast.LENGTH_LONG).show();
|
||||
} else
|
||||
reportError(result, "IAB history");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onPurchaseError(Intent intent) {
|
||||
String message = intent.getStringExtra("message");
|
||||
Uri uri = Uri.parse(Helper.SUPPORT_URI);
|
||||
if (!TextUtils.isEmpty(message))
|
||||
uri = uri.buildUpon().appendQueryParameter("message", "IAB: " + message).build();
|
||||
Helper.view(this, uri, true);
|
||||
}
|
||||
|
||||
private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
|
||||
private int backoff = 4; // seconds
|
||||
|
||||
@Override
|
||||
public void onBillingSetupFinished(BillingResult result) {
|
||||
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
for (IBillingListener listener : listeners)
|
||||
listener.onConnected();
|
||||
|
||||
backoff = 4;
|
||||
queryPurchases();
|
||||
} else
|
||||
reportError(result, "IAB connected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
for (IBillingListener listener : listeners)
|
||||
listener.onDisconnected();
|
||||
|
||||
backoff *= 2;
|
||||
retry(backoff);
|
||||
}
|
||||
};
|
||||
|
||||
private void retry(int backoff) {
|
||||
Log.i("IAB connect retry in " + backoff + " s");
|
||||
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!billingClient.isReady())
|
||||
billingClient.startConnection(billingClientStateListener);
|
||||
}
|
||||
}, backoff * 1000L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchasesUpdated(BillingResult result, @Nullable List<Purchase> purchases) {
|
||||
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK)
|
||||
checkPurchases(purchases);
|
||||
else
|
||||
reportError(result, "IAB purchases updated");
|
||||
}
|
||||
|
||||
private void queryPurchases() {
|
||||
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
|
||||
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK)
|
||||
checkPurchases(result.getPurchasesList());
|
||||
else
|
||||
reportError(result.getBillingResult(), "IAB query purchases");
|
||||
}
|
||||
*/
|
||||
interface IBillingListener {
|
||||
void onConnected();
|
||||
|
||||
void onDisconnected();
|
||||
|
||||
void onSkuDetails(String sku, String price);
|
||||
|
||||
void onPurchasePending(String sku);
|
||||
|
||||
void onPurchased(String sku);
|
||||
|
||||
void onError(String message);
|
||||
}
|
||||
|
||||
void addBillingListener(final IBillingListener listener, LifecycleOwner owner) {
|
||||
Log.i("IAB adding billing listener=" + listener);
|
||||
listeners.add(listener);
|
||||
|
||||
//if (billingClient != null)
|
||||
// if (billingClient.isReady()) {
|
||||
// listener.onConnected();
|
||||
// queryPurchases();
|
||||
// } else
|
||||
// listener.onDisconnected();
|
||||
|
||||
owner.getLifecycle().addObserver(new LifecycleObserver() {
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
public void onDestroyed() {
|
||||
Log.i("IAB removing billing listener=" + listener);
|
||||
listeners.remove(listener);
|
||||
}
|
||||
});
|
||||
}
|
||||
/*
|
||||
private void checkPurchases(List<Purchase> purchases) {
|
||||
Log.i("IAB purchases=" + (purchases == null ? null : purchases.size()));
|
||||
|
||||
List<String> query = new ArrayList<>();
|
||||
query.add(getSkuPro());
|
||||
|
||||
if (purchases != null) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
if (prefs.getBoolean("play_store", true)) {
|
||||
long cached = prefs.getLong(getSkuPro() + ".cached", 0);
|
||||
if (cached + MAX_SKU_CACHE_DURATION < new Date().getTime()) {
|
||||
Log.i("IAB cache expired=" + new Date(cached));
|
||||
editor.remove("pro");
|
||||
} else
|
||||
Log.i("IAB caching until=" + new Date(cached + MAX_SKU_CACHE_DURATION));
|
||||
}
|
||||
|
||||
for (Purchase purchase : purchases)
|
||||
try {
|
||||
query.remove(purchase.getSku());
|
||||
|
||||
long time = purchase.getPurchaseTime();
|
||||
Log.i("IAB SKU=" + purchase.getSku() +
|
||||
" purchased=" + isPurchased(purchase) +
|
||||
" valid=" + isPurchaseValid(purchase) +
|
||||
" time=" + new Date(time));
|
||||
|
||||
//if (new Date().getTime() - purchase.getPurchaseTime() > 3 * 60 * 1000L) {
|
||||
// consumePurchase(purchase);
|
||||
// continue;
|
||||
//}
|
||||
|
||||
for (IBillingListener listener : listeners)
|
||||
if (isPurchaseValid(purchase))
|
||||
listener.onPurchased(purchase.getSku());
|
||||
else
|
||||
listener.onPurchasePending(purchase.getSku());
|
||||
|
||||
if (isPurchased(purchase)) {
|
||||
byte[] decodedKey = Base64.decode(getString(R.string.public_key), Base64.DEFAULT);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(purchase.getOriginalJson().getBytes());
|
||||
if (sig.verify(Base64.decode(purchase.getSignature(), Base64.DEFAULT))) {
|
||||
Log.i("IAB valid signature");
|
||||
if (getSkuPro().equals(purchase.getSku())) {
|
||||
if (isPurchaseValid(purchase)) {
|
||||
editor.putBoolean("pro", true);
|
||||
editor.putLong(purchase.getSku() + ".cached", new Date().getTime());
|
||||
}
|
||||
|
||||
if (!purchase.isAcknowledged())
|
||||
acknowledgePurchase(purchase, 0);
|
||||
}
|
||||
} else {
|
||||
Log.w("IAB invalid signature");
|
||||
editor.putBoolean("pro", false);
|
||||
reportError(null, "Invalid purchase");
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
reportError(null, Log.formatThrowable(ex, false));
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
|
||||
WidgetUnified.updateData(this);
|
||||
}
|
||||
|
||||
if (query.size() > 0)
|
||||
querySkus(query);
|
||||
}
|
||||
|
||||
private void querySkus(List<String> query) {
|
||||
Log.i("IAB query SKUs");
|
||||
SkuDetailsParams.Builder builder = SkuDetailsParams.newBuilder();
|
||||
builder.setSkusList(query);
|
||||
builder.setType(BillingClient.SkuType.INAPP);
|
||||
billingClient.querySkuDetailsAsync(builder.build(),
|
||||
new SkuDetailsResponseListener() {
|
||||
@Override
|
||||
public void onSkuDetailsResponse(BillingResult result, List<SkuDetails> skuDetailsList) {
|
||||
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
for (SkuDetails skuDetail : skuDetailsList) {
|
||||
Log.i("IAB SKU detail=" + skuDetail);
|
||||
skuDetails.put(skuDetail.getSku(), skuDetail);
|
||||
for (IBillingListener listener : listeners)
|
||||
listener.onSkuDetails(skuDetail.getSku(), skuDetail.getPrice());
|
||||
}
|
||||
} else
|
||||
reportError(result, "IAB query SKUs");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void consumePurchase(final Purchase purchase) {
|
||||
Log.i("IAB SKU=" + purchase.getSku() + " consuming");
|
||||
ConsumeParams params = ConsumeParams.newBuilder()
|
||||
.setPurchaseToken(purchase.getPurchaseToken())
|
||||
.build();
|
||||
billingClient.consumeAsync(params, new ConsumeResponseListener() {
|
||||
@Override
|
||||
public void onConsumeResponse(BillingResult result, String purchaseToken) {
|
||||
if (result.getResponseCode() != BillingClient.BillingResponseCode.OK)
|
||||
reportError(result, "IAB consumed SKU=" + purchase.getSku());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void acknowledgePurchase(final Purchase purchase, int retry) {
|
||||
Log.i("IAB acknowledging purchase SKU=" + purchase.getSku());
|
||||
AcknowledgePurchaseParams params =
|
||||
AcknowledgePurchaseParams.newBuilder()
|
||||
.setPurchaseToken(purchase.getPurchaseToken())
|
||||
.build();
|
||||
billingClient.acknowledgePurchase(params, new AcknowledgePurchaseResponseListener() {
|
||||
@Override
|
||||
public void onAcknowledgePurchaseResponse(BillingResult result) {
|
||||
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityBilling.this);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean("pro", true);
|
||||
editor.putLong(purchase.getSku() + ".cached", new Date().getTime());
|
||||
editor.apply();
|
||||
|
||||
for (IBillingListener listener : listeners)
|
||||
listener.onPurchased(purchase.getSku());
|
||||
|
||||
WidgetUnified.updateData(ActivityBilling.this);
|
||||
} else {
|
||||
if (retry < 3) {
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
acknowledgePurchase(purchase, retry + 1);
|
||||
}
|
||||
}, (retry + 1) * 10 * 1000L);
|
||||
} else
|
||||
reportError(result, "IAB acknowledged SKU=" + purchase.getSku());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isPurchased(Purchase purchase) {
|
||||
return (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED);
|
||||
}
|
||||
|
||||
private boolean isPurchaseValid(Purchase purchase) {
|
||||
return (isPurchased(purchase) &&
|
||||
(purchase.isAcknowledged() ||
|
||||
purchase.getPurchaseTime() + MAX_SKU_NOACK_DURATION > new Date().getTime()));
|
||||
}
|
||||
|
||||
private void reportError(BillingResult result, String stage) {
|
||||
String message;
|
||||
if (result == null)
|
||||
message = stage;
|
||||
else {
|
||||
String debug = result.getDebugMessage();
|
||||
message = getBillingResponseText(result) + (debug == null ? "" : " " + debug) + " " + stage;
|
||||
|
||||
// https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse#service_disconnected
|
||||
if (result.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
|
||||
retry(60);
|
||||
}
|
||||
|
||||
EntityLog.log(this, message);
|
||||
|
||||
if (result.getResponseCode() != BillingClient.BillingResponseCode.USER_CANCELED)
|
||||
for (IBillingListener listener : listeners)
|
||||
listener.onError(message);
|
||||
}
|
||||
|
||||
private static String getBillingResponseText(BillingResult result) {
|
||||
switch (result.getResponseCode()) {
|
||||
case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE:
|
||||
// Billing API version is not supported for the type requested
|
||||
return "BILLING_UNAVAILABLE";
|
||||
|
||||
case BillingClient.BillingResponseCode.DEVELOPER_ERROR:
|
||||
// Invalid arguments provided to the API.
|
||||
return "DEVELOPER_ERROR";
|
||||
|
||||
case BillingClient.BillingResponseCode.ERROR:
|
||||
// Fatal error during the API action
|
||||
return "ERROR";
|
||||
|
||||
case BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED:
|
||||
// Requested feature is not supported by Play Store on the current device.
|
||||
return "FEATURE_NOT_SUPPORTED";
|
||||
|
||||
case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED:
|
||||
// Failure to purchase since item is already owned
|
||||
return "ITEM_ALREADY_OWNED";
|
||||
|
||||
case BillingClient.BillingResponseCode.ITEM_NOT_OWNED:
|
||||
// Failure to consume since item is not owned
|
||||
return "ITEM_NOT_OWNED";
|
||||
|
||||
case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE:
|
||||
// Requested product is not available for purchase
|
||||
return "ITEM_UNAVAILABLE";
|
||||
|
||||
case BillingClient.BillingResponseCode.OK:
|
||||
// Success
|
||||
return "OK";
|
||||
|
||||
case BillingClient.BillingResponseCode.SERVICE_DISCONNECTED:
|
||||
// Play Store service is not connected now - potentially transient state.
|
||||
return "SERVICE_DISCONNECTED";
|
||||
|
||||
case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE:
|
||||
// Network connection is down
|
||||
return "SERVICE_UNAVAILABLE";
|
||||
|
||||
case BillingClient.BillingResponseCode.SERVICE_TIMEOUT:
|
||||
// The request has reached the maximum timeout before Google Play responds.
|
||||
return "SERVICE_TIMEOUT";
|
||||
|
||||
case BillingClient.BillingResponseCode.USER_CANCELED:
|
||||
// User pressed back or canceled a dialog
|
||||
return "USER_CANCELED";
|
||||
|
||||
default:
|
||||
return Integer.toString(result.getResponseCode());
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0, which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
package com.sun.activation.registries;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
/**
|
||||
* Logging related methods.
|
||||
*/
|
||||
public class LogSupport {
|
||||
private static boolean debug = false;
|
||||
private static Logger logger;
|
||||
private static final Level level = Level.FINE;
|
||||
|
||||
static {
|
||||
try {
|
||||
debug = Boolean.getBoolean("javax.activation.debug");
|
||||
} catch (Throwable t) {
|
||||
// ignore any errors
|
||||
}
|
||||
logger = Logger.getLogger("javax.activation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
private LogSupport() {
|
||||
// private constructor, can't create instances
|
||||
}
|
||||
|
||||
public static void log(String msg) {
|
||||
if (debug)
|
||||
System.out.println(msg);
|
||||
logger.log(level, msg);
|
||||
}
|
||||
|
||||
public static void log(String msg, Throwable t) {
|
||||
if (debug)
|
||||
System.out.println(msg + "; Exception: " + t);
|
||||
logger.log(level, msg, t);
|
||||
}
|
||||
|
||||
public static boolean isLoggable() {
|
||||
return debug || logger.isLoggable(level);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,548 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0, which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
package com.sun.activation.registries;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class MailcapFile {
|
||||
|
||||
/**
|
||||
* A Map indexed by MIME type (string) that references
|
||||
* a Map of commands for each type. The comand Map
|
||||
* is indexed by the command name and references a List of
|
||||
* class names (strings) for each command.
|
||||
*/
|
||||
private Map type_hash = new HashMap();
|
||||
|
||||
/**
|
||||
* Another Map like above, but for fallback entries.
|
||||
*/
|
||||
private Map fallback_hash = new HashMap();
|
||||
|
||||
/**
|
||||
* A Map indexed by MIME type (string) that references
|
||||
* a List of native commands (string) corresponding to the type.
|
||||
*/
|
||||
private Map native_commands = new HashMap();
|
||||
|
||||
private static boolean addReverse = false;
|
||||
|
||||
static {
|
||||
try {
|
||||
addReverse = Boolean.getBoolean("javax.activation.addreverse");
|
||||
} catch (Throwable t) {
|
||||
// ignore any errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor that takes a filename as an argument.
|
||||
*
|
||||
* @param new_fname The file name of the mailcap file.
|
||||
*/
|
||||
public MailcapFile(String new_fname) throws IOException {
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("new MailcapFile: file " + new_fname);
|
||||
FileReader reader = null;
|
||||
try {
|
||||
reader = new FileReader(new_fname);
|
||||
parse(new BufferedReader(reader));
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ex) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The constructor that takes an input stream as an argument.
|
||||
*
|
||||
* @param is the input stream
|
||||
*/
|
||||
public MailcapFile(InputStream is) throws IOException {
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("new MailcapFile: InputStream");
|
||||
parse(new BufferedReader(new InputStreamReader(is, "iso-8859-1")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailcap file default constructor.
|
||||
*/
|
||||
public MailcapFile() {
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("new MailcapFile: default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Map of MailcapEntries based on the MIME type.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Semantics:</strong> First check for the literal mime type,
|
||||
* if that fails looks for wildcard <i>type</i>/* and return that. Return the
|
||||
* list of all that hit.
|
||||
*/
|
||||
public Map getMailcapList(String mime_type) {
|
||||
Map search_result = null;
|
||||
Map wildcard_result = null;
|
||||
|
||||
// first try the literal
|
||||
search_result = (Map)type_hash.get(mime_type);
|
||||
|
||||
// ok, now try the wildcard
|
||||
int separator = mime_type.indexOf('/');
|
||||
String subtype = mime_type.substring(separator + 1);
|
||||
if (!subtype.equals("*")) {
|
||||
String type = mime_type.substring(0, separator + 1) + "*";
|
||||
wildcard_result = (Map)type_hash.get(type);
|
||||
|
||||
if (wildcard_result != null) { // damn, we have to merge!!!
|
||||
if (search_result != null)
|
||||
search_result =
|
||||
mergeResults(search_result, wildcard_result);
|
||||
else
|
||||
search_result = wildcard_result;
|
||||
}
|
||||
}
|
||||
return search_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Map of fallback MailcapEntries based on the MIME type.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Semantics:</strong> First check for the literal mime type,
|
||||
* if that fails looks for wildcard <i>type</i>/* and return that. Return the
|
||||
* list of all that hit.
|
||||
*/
|
||||
public Map getMailcapFallbackList(String mime_type) {
|
||||
Map search_result = null;
|
||||
Map wildcard_result = null;
|
||||
|
||||
// first try the literal
|
||||
search_result = (Map)fallback_hash.get(mime_type);
|
||||
|
||||
// ok, now try the wildcard
|
||||
int separator = mime_type.indexOf('/');
|
||||
String subtype = mime_type.substring(separator + 1);
|
||||
if (!subtype.equals("*")) {
|
||||
String type = mime_type.substring(0, separator + 1) + "*";
|
||||
wildcard_result = (Map)fallback_hash.get(type);
|
||||
|
||||
if (wildcard_result != null) { // damn, we have to merge!!!
|
||||
if (search_result != null)
|
||||
search_result =
|
||||
mergeResults(search_result, wildcard_result);
|
||||
else
|
||||
search_result = wildcard_result;
|
||||
}
|
||||
}
|
||||
return search_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the MIME types known to this mailcap file.
|
||||
*/
|
||||
public String[] getMimeTypes() {
|
||||
Set types = new HashSet(type_hash.keySet());
|
||||
types.addAll(fallback_hash.keySet());
|
||||
types.addAll(native_commands.keySet());
|
||||
String[] mts = new String[types.size()];
|
||||
mts = (String[])types.toArray(mts);
|
||||
return mts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the native comands for the given MIME type.
|
||||
*/
|
||||
public String[] getNativeCommands(String mime_type) {
|
||||
String[] cmds = null;
|
||||
List v =
|
||||
(List)native_commands.get(mime_type.toLowerCase(Locale.ENGLISH));
|
||||
if (v != null) {
|
||||
cmds = new String[v.size()];
|
||||
cmds = (String[])v.toArray(cmds);
|
||||
}
|
||||
return cmds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the first hash into the second.
|
||||
* This merge will only effect the hashtable that is
|
||||
* returned, we don't want to touch the one passed in since
|
||||
* its integrity must be maintained.
|
||||
*/
|
||||
private Map mergeResults(Map first, Map second) {
|
||||
Iterator verb_enum = second.keySet().iterator();
|
||||
Map clonedHash = new HashMap(first);
|
||||
|
||||
// iterate through the verbs in the second map
|
||||
while (verb_enum.hasNext()) {
|
||||
String verb = (String)verb_enum.next();
|
||||
List cmdVector = (List)clonedHash.get(verb);
|
||||
if (cmdVector == null) {
|
||||
clonedHash.put(verb, second.get(verb));
|
||||
} else {
|
||||
// merge the two
|
||||
List oldV = (List)second.get(verb);
|
||||
cmdVector = new ArrayList(cmdVector);
|
||||
cmdVector.addAll(oldV);
|
||||
clonedHash.put(verb, cmdVector);
|
||||
}
|
||||
}
|
||||
return clonedHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* appendToMailcap: Append to this Mailcap DB, use the mailcap
|
||||
* format:
|
||||
* Comment == "# <i>comment string</i>
|
||||
* Entry == "mimetype; javabeanclass<br>
|
||||
*
|
||||
* Example:
|
||||
* # this is a comment
|
||||
* image/gif jaf.viewers.ImageViewer
|
||||
*/
|
||||
public void appendToMailcap(String mail_cap) {
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("appendToMailcap: " + mail_cap);
|
||||
try {
|
||||
parse(new StringReader(mail_cap));
|
||||
} catch (IOException ex) {
|
||||
// can't happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parse file into a hash table of MC Type Entry Obj
|
||||
*/
|
||||
private void parse(Reader reader) throws IOException {
|
||||
BufferedReader buf_reader = new BufferedReader(reader);
|
||||
String line = null;
|
||||
String continued = null;
|
||||
|
||||
while ((line = buf_reader.readLine()) != null) {
|
||||
// LogSupport.log("parsing line: " + line);
|
||||
|
||||
line = line.trim();
|
||||
|
||||
try {
|
||||
if (line.charAt(0) == '#')
|
||||
continue;
|
||||
if (line.charAt(line.length() - 1) == '\\') {
|
||||
if (continued != null)
|
||||
continued += line.substring(0, line.length() - 1);
|
||||
else
|
||||
continued = line.substring(0, line.length() - 1);
|
||||
} else if (continued != null) {
|
||||
// handle the two strings
|
||||
continued = continued + line;
|
||||
// LogSupport.log("parse: " + continued);
|
||||
try {
|
||||
parseLine(continued);
|
||||
} catch (MailcapParseException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
continued = null;
|
||||
}
|
||||
else {
|
||||
// LogSupport.log("parse: " + line);
|
||||
try {
|
||||
parseLine(line);
|
||||
// LogSupport.log("hash.size = " + type_hash.size());
|
||||
} catch (MailcapParseException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (StringIndexOutOfBoundsException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A routine to parse individual entries in a Mailcap file.
|
||||
*
|
||||
* Note that this routine does not handle line continuations.
|
||||
* They should have been handled prior to calling this routine.
|
||||
*/
|
||||
protected void parseLine(String mailcapEntry)
|
||||
throws MailcapParseException, IOException {
|
||||
MailcapTokenizer tokenizer = new MailcapTokenizer(mailcapEntry);
|
||||
tokenizer.setIsAutoquoting(false);
|
||||
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("parse: " + mailcapEntry);
|
||||
// parse the primary type
|
||||
int currentToken = tokenizer.nextToken();
|
||||
if (currentToken != MailcapTokenizer.STRING_TOKEN) {
|
||||
reportParseError(MailcapTokenizer.STRING_TOKEN, currentToken,
|
||||
tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
String primaryType =
|
||||
tokenizer.getCurrentTokenValue().toLowerCase(Locale.ENGLISH);
|
||||
String subType = "*";
|
||||
|
||||
// parse the '/' between primary and sub
|
||||
// if it's not present that's ok, we just don't have a subtype
|
||||
currentToken = tokenizer.nextToken();
|
||||
if ((currentToken != MailcapTokenizer.SLASH_TOKEN) &&
|
||||
(currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) {
|
||||
reportParseError(MailcapTokenizer.SLASH_TOKEN,
|
||||
MailcapTokenizer.SEMICOLON_TOKEN, currentToken,
|
||||
tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
|
||||
// only need to look for a sub type if we got a '/'
|
||||
if (currentToken == MailcapTokenizer.SLASH_TOKEN) {
|
||||
// parse the sub type
|
||||
currentToken = tokenizer.nextToken();
|
||||
if (currentToken != MailcapTokenizer.STRING_TOKEN) {
|
||||
reportParseError(MailcapTokenizer.STRING_TOKEN,
|
||||
currentToken, tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
subType =
|
||||
tokenizer.getCurrentTokenValue().toLowerCase(Locale.ENGLISH);
|
||||
|
||||
// get the next token to simplify the next step
|
||||
currentToken = tokenizer.nextToken();
|
||||
}
|
||||
|
||||
String mimeType = primaryType + "/" + subType;
|
||||
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log(" Type: " + mimeType);
|
||||
|
||||
// now setup the commands hashtable
|
||||
Map commands = new LinkedHashMap(); // keep commands in order found
|
||||
|
||||
// parse the ';' that separates the type from the parameters
|
||||
if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) {
|
||||
reportParseError(MailcapTokenizer.SEMICOLON_TOKEN,
|
||||
currentToken, tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
// eat it
|
||||
|
||||
// parse the required view command
|
||||
tokenizer.setIsAutoquoting(true);
|
||||
currentToken = tokenizer.nextToken();
|
||||
tokenizer.setIsAutoquoting(false);
|
||||
if ((currentToken != MailcapTokenizer.STRING_TOKEN) &&
|
||||
(currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) {
|
||||
reportParseError(MailcapTokenizer.STRING_TOKEN,
|
||||
MailcapTokenizer.SEMICOLON_TOKEN, currentToken,
|
||||
tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
|
||||
if (currentToken == MailcapTokenizer.STRING_TOKEN) {
|
||||
// have a native comand, save the entire mailcap entry
|
||||
//String nativeCommand = tokenizer.getCurrentTokenValue();
|
||||
List v = (List)native_commands.get(mimeType);
|
||||
if (v == null) {
|
||||
v = new ArrayList();
|
||||
v.add(mailcapEntry);
|
||||
native_commands.put(mimeType, v);
|
||||
} else {
|
||||
// XXX - check for duplicates?
|
||||
v.add(mailcapEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// only have to get the next token if the current one isn't a ';'
|
||||
if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) {
|
||||
currentToken = tokenizer.nextToken();
|
||||
}
|
||||
|
||||
// look for a ';' which will indicate whether
|
||||
// a parameter list is present or not
|
||||
if (currentToken == MailcapTokenizer.SEMICOLON_TOKEN) {
|
||||
boolean isFallback = false;
|
||||
do {
|
||||
// eat the ';'
|
||||
|
||||
// parse the parameter name
|
||||
currentToken = tokenizer.nextToken();
|
||||
if (currentToken != MailcapTokenizer.STRING_TOKEN) {
|
||||
reportParseError(MailcapTokenizer.STRING_TOKEN,
|
||||
currentToken, tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
String paramName = tokenizer.getCurrentTokenValue().
|
||||
toLowerCase(Locale.ENGLISH);
|
||||
|
||||
// parse the '=' which separates the name from the value
|
||||
currentToken = tokenizer.nextToken();
|
||||
if ((currentToken != MailcapTokenizer.EQUALS_TOKEN) &&
|
||||
(currentToken != MailcapTokenizer.SEMICOLON_TOKEN) &&
|
||||
(currentToken != MailcapTokenizer.EOI_TOKEN)) {
|
||||
reportParseError(MailcapTokenizer.EQUALS_TOKEN,
|
||||
MailcapTokenizer.SEMICOLON_TOKEN,
|
||||
MailcapTokenizer.EOI_TOKEN,
|
||||
currentToken, tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
|
||||
// we only have a useful command if it is named
|
||||
if (currentToken == MailcapTokenizer.EQUALS_TOKEN) {
|
||||
// eat it
|
||||
|
||||
// parse the parameter value (which is autoquoted)
|
||||
tokenizer.setIsAutoquoting(true);
|
||||
currentToken = tokenizer.nextToken();
|
||||
tokenizer.setIsAutoquoting(false);
|
||||
if (currentToken != MailcapTokenizer.STRING_TOKEN) {
|
||||
reportParseError(MailcapTokenizer.STRING_TOKEN,
|
||||
currentToken, tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
String paramValue =
|
||||
tokenizer.getCurrentTokenValue();
|
||||
|
||||
// add the class to the list iff it is one we care about
|
||||
if (paramName.startsWith("x-java-")) {
|
||||
String commandName = paramName.substring(7);
|
||||
// 7 == "x-java-".length
|
||||
|
||||
if (commandName.equals("fallback-entry") &&
|
||||
paramValue.equalsIgnoreCase("true")) {
|
||||
isFallback = true;
|
||||
} else {
|
||||
|
||||
// setup the class entry list
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log(" Command: " + commandName +
|
||||
", Class: " + paramValue);
|
||||
List classes = (List)commands.get(commandName);
|
||||
if (classes == null) {
|
||||
classes = new ArrayList();
|
||||
commands.put(commandName, classes);
|
||||
}
|
||||
if (addReverse)
|
||||
classes.add(0, paramValue);
|
||||
else
|
||||
classes.add(paramValue);
|
||||
}
|
||||
}
|
||||
|
||||
// set up the next iteration
|
||||
currentToken = tokenizer.nextToken();
|
||||
}
|
||||
} while (currentToken == MailcapTokenizer.SEMICOLON_TOKEN);
|
||||
|
||||
Map masterHash = isFallback ? fallback_hash : type_hash;
|
||||
Map curcommands =
|
||||
(Map)masterHash.get(mimeType);
|
||||
if (curcommands == null) {
|
||||
masterHash.put(mimeType, commands);
|
||||
} else {
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("Merging commands for type " + mimeType);
|
||||
// have to merge current and new commands
|
||||
// first, merge list of classes for commands already known
|
||||
Iterator cn = curcommands.keySet().iterator();
|
||||
while (cn.hasNext()) {
|
||||
String cmdName = (String)cn.next();
|
||||
List ccv = (List)curcommands.get(cmdName);
|
||||
List cv = (List)commands.get(cmdName);
|
||||
if (cv == null)
|
||||
continue;
|
||||
// add everything in cv to ccv, if it's not already there
|
||||
Iterator cvn = cv.iterator();
|
||||
while (cvn.hasNext()) {
|
||||
String clazz = (String)cvn.next();
|
||||
if (!ccv.contains(clazz))
|
||||
if (addReverse)
|
||||
ccv.add(0, clazz);
|
||||
else
|
||||
ccv.add(clazz);
|
||||
}
|
||||
}
|
||||
// now, add commands not previously known
|
||||
cn = commands.keySet().iterator();
|
||||
while (cn.hasNext()) {
|
||||
String cmdName = (String)cn.next();
|
||||
if (curcommands.containsKey(cmdName))
|
||||
continue;
|
||||
List cv = (List)commands.get(cmdName);
|
||||
curcommands.put(cmdName, cv);
|
||||
}
|
||||
}
|
||||
} else if (currentToken != MailcapTokenizer.EOI_TOKEN) {
|
||||
reportParseError(MailcapTokenizer.EOI_TOKEN,
|
||||
MailcapTokenizer.SEMICOLON_TOKEN,
|
||||
currentToken, tokenizer.getCurrentTokenValue());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void reportParseError(int expectedToken, int actualToken,
|
||||
String actualTokenValue) throws MailcapParseException {
|
||||
throw new MailcapParseException("Encountered a " +
|
||||
MailcapTokenizer.nameForToken(actualToken) + " token (" +
|
||||
actualTokenValue + ") while expecting a " +
|
||||
MailcapTokenizer.nameForToken(expectedToken) + " token.");
|
||||
}
|
||||
|
||||
protected static void reportParseError(int expectedToken,
|
||||
int otherExpectedToken, int actualToken, String actualTokenValue)
|
||||
throws MailcapParseException {
|
||||
throw new MailcapParseException("Encountered a " +
|
||||
MailcapTokenizer.nameForToken(actualToken) + " token (" +
|
||||
actualTokenValue + ") while expecting a " +
|
||||
MailcapTokenizer.nameForToken(expectedToken) + " or a " +
|
||||
MailcapTokenizer.nameForToken(otherExpectedToken) + " token.");
|
||||
}
|
||||
|
||||
protected static void reportParseError(int expectedToken,
|
||||
int otherExpectedToken, int anotherExpectedToken, int actualToken,
|
||||
String actualTokenValue) throws MailcapParseException {
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("PARSE ERROR: " + "Encountered a " +
|
||||
MailcapTokenizer.nameForToken(actualToken) + " token (" +
|
||||
actualTokenValue + ") while expecting a " +
|
||||
MailcapTokenizer.nameForToken(expectedToken) + ", a " +
|
||||
MailcapTokenizer.nameForToken(otherExpectedToken) + ", or a " +
|
||||
MailcapTokenizer.nameForToken(anotherExpectedToken) + " token.");
|
||||
throw new MailcapParseException("Encountered a " +
|
||||
MailcapTokenizer.nameForToken(actualToken) + " token (" +
|
||||
actualTokenValue + ") while expecting a " +
|
||||
MailcapTokenizer.nameForToken(expectedToken) + ", a " +
|
||||
MailcapTokenizer.nameForToken(otherExpectedToken) + ", or a " +
|
||||
MailcapTokenizer.nameForToken(anotherExpectedToken) + " token.");
|
||||
}
|
||||
|
||||
/** for debugging
|
||||
public static void main(String[] args) throws Exception {
|
||||
Map masterHash = new HashMap();
|
||||
for (int i = 0; i < args.length; ++i) {
|
||||
System.out.println("Entry " + i + ": " + args[i]);
|
||||
parseLine(args[i], masterHash);
|
||||
}
|
||||
|
||||
Enumeration types = masterHash.keys();
|
||||
while (types.hasMoreElements()) {
|
||||
String key = (String)types.nextElement();
|
||||
System.out.println("MIME Type: " + key);
|
||||
|
||||
Map commandHash = (Map)masterHash.get(key);
|
||||
Enumeration commands = commandHash.keys();
|
||||
while (commands.hasMoreElements()) {
|
||||
String command = (String)commands.nextElement();
|
||||
System.out.println(" Command: " + command);
|
||||
|
||||
Vector classes = (Vector)commandHash.get(command);
|
||||
for (int i = 0; i < classes.size(); ++i) {
|
||||
System.out.println(" Class: " +
|
||||
(String)classes.elementAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("");
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0, which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
package com.sun.activation.registries;
|
||||
|
||||
/**
|
||||
* A class to encapsulate Mailcap parsing related exceptions
|
||||
*/
|
||||
public class MailcapParseException extends Exception {
|
||||
|
||||
public MailcapParseException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MailcapParseException(String inInfo) {
|
||||
super(inInfo);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0, which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
package com.sun.activation.registries;
|
||||
|
||||
/**
|
||||
* A tokenizer for strings in the form of "foo/bar; prop1=val1; ... ".
|
||||
* Useful for parsing MIME content types.
|
||||
*/
|
||||
public class MailcapTokenizer {
|
||||
|
||||
public static final int UNKNOWN_TOKEN = 0;
|
||||
public static final int START_TOKEN = 1;
|
||||
public static final int STRING_TOKEN = 2;
|
||||
public static final int EOI_TOKEN = 5;
|
||||
public static final int SLASH_TOKEN = '/';
|
||||
public static final int SEMICOLON_TOKEN = ';';
|
||||
public static final int EQUALS_TOKEN = '=';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param inputString the string to tokenize
|
||||
*/
|
||||
public MailcapTokenizer(String inputString) {
|
||||
data = inputString;
|
||||
dataIndex = 0;
|
||||
dataLength = inputString.length();
|
||||
|
||||
currentToken = START_TOKEN;
|
||||
currentTokenValue = "";
|
||||
|
||||
isAutoquoting = false;
|
||||
autoquoteChar = ';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether auto-quoting is on or off.
|
||||
*
|
||||
* Auto-quoting means that all characters after the first
|
||||
* non-whitespace, non-control character up to the auto-quote
|
||||
* terminator character or EOI (minus any whitespace immediatley
|
||||
* preceeding it) is considered a token.
|
||||
*
|
||||
* This is required for handling command strings in a mailcap entry.
|
||||
*/
|
||||
public void setIsAutoquoting(boolean value) {
|
||||
isAutoquoting = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve current token.
|
||||
*
|
||||
* @return The current token value
|
||||
*/
|
||||
public int getCurrentToken() {
|
||||
return currentToken;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get a String that describes the given token.
|
||||
*/
|
||||
public static String nameForToken(int token) {
|
||||
String name = "really unknown";
|
||||
|
||||
switch(token) {
|
||||
case UNKNOWN_TOKEN:
|
||||
name = "unknown";
|
||||
break;
|
||||
case START_TOKEN:
|
||||
name = "start";
|
||||
break;
|
||||
case STRING_TOKEN:
|
||||
name = "string";
|
||||
break;
|
||||
case EOI_TOKEN:
|
||||
name = "EOI";
|
||||
break;
|
||||
case SLASH_TOKEN:
|
||||
name = "'/'";
|
||||
break;
|
||||
case SEMICOLON_TOKEN:
|
||||
name = "';'";
|
||||
break;
|
||||
case EQUALS_TOKEN:
|
||||
name = "'='";
|
||||
break;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve current token value.
|
||||
*
|
||||
* @return A String containing the current token value
|
||||
*/
|
||||
public String getCurrentTokenValue() {
|
||||
return currentTokenValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the next token.
|
||||
*
|
||||
* @return the next token
|
||||
*/
|
||||
public int nextToken() {
|
||||
if (dataIndex < dataLength) {
|
||||
// skip white space
|
||||
while ((dataIndex < dataLength) &&
|
||||
(isWhiteSpaceChar(data.charAt(dataIndex)))) {
|
||||
++dataIndex;
|
||||
}
|
||||
|
||||
if (dataIndex < dataLength) {
|
||||
// examine the current character and see what kind of token we have
|
||||
char c = data.charAt(dataIndex);
|
||||
if (isAutoquoting) {
|
||||
if (c == ';' || c == '=') {
|
||||
currentToken = c;
|
||||
currentTokenValue = new Character(c).toString();
|
||||
++dataIndex;
|
||||
} else {
|
||||
processAutoquoteToken();
|
||||
}
|
||||
} else {
|
||||
if (isStringTokenChar(c)) {
|
||||
processStringToken();
|
||||
} else if ((c == '/') || (c == ';') || (c == '=')) {
|
||||
currentToken = c;
|
||||
currentTokenValue = new Character(c).toString();
|
||||
++dataIndex;
|
||||
} else {
|
||||
currentToken = UNKNOWN_TOKEN;
|
||||
currentTokenValue = new Character(c).toString();
|
||||
++dataIndex;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentToken = EOI_TOKEN;
|
||||
currentTokenValue = null;
|
||||
}
|
||||
} else {
|
||||
currentToken = EOI_TOKEN;
|
||||
currentTokenValue = null;
|
||||
}
|
||||
|
||||
return currentToken;
|
||||
}
|
||||
|
||||
private void processStringToken() {
|
||||
// capture the initial index
|
||||
int initialIndex = dataIndex;
|
||||
|
||||
// skip to 1st non string token character
|
||||
while ((dataIndex < dataLength) &&
|
||||
isStringTokenChar(data.charAt(dataIndex))) {
|
||||
++dataIndex;
|
||||
}
|
||||
|
||||
currentToken = STRING_TOKEN;
|
||||
currentTokenValue = data.substring(initialIndex, dataIndex);
|
||||
}
|
||||
|
||||
private void processAutoquoteToken() {
|
||||
// capture the initial index
|
||||
int initialIndex = dataIndex;
|
||||
|
||||
// now skip to the 1st non-escaped autoquote termination character
|
||||
// XXX - doesn't actually consider escaping
|
||||
boolean foundTerminator = false;
|
||||
while ((dataIndex < dataLength) && !foundTerminator) {
|
||||
char c = data.charAt(dataIndex);
|
||||
if (c != autoquoteChar) {
|
||||
++dataIndex;
|
||||
} else {
|
||||
foundTerminator = true;
|
||||
}
|
||||
}
|
||||
|
||||
currentToken = STRING_TOKEN;
|
||||
currentTokenValue =
|
||||
fixEscapeSequences(data.substring(initialIndex, dataIndex));
|
||||
}
|
||||
|
||||
private static boolean isSpecialChar(char c) {
|
||||
boolean lAnswer = false;
|
||||
|
||||
switch(c) {
|
||||
case '(':
|
||||
case ')':
|
||||
case '<':
|
||||
case '>':
|
||||
case '@':
|
||||
case ',':
|
||||
case ';':
|
||||
case ':':
|
||||
case '\\':
|
||||
case '"':
|
||||
case '/':
|
||||
case '[':
|
||||
case ']':
|
||||
case '?':
|
||||
case '=':
|
||||
lAnswer = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return lAnswer;
|
||||
}
|
||||
|
||||
private static boolean isControlChar(char c) {
|
||||
return Character.isISOControl(c);
|
||||
}
|
||||
|
||||
private static boolean isWhiteSpaceChar(char c) {
|
||||
return Character.isWhitespace(c);
|
||||
}
|
||||
|
||||
private static boolean isStringTokenChar(char c) {
|
||||
return !isSpecialChar(c) && !isControlChar(c) && !isWhiteSpaceChar(c);
|
||||
}
|
||||
|
||||
private static String fixEscapeSequences(String inputString) {
|
||||
int inputLength = inputString.length();
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.ensureCapacity(inputLength);
|
||||
|
||||
for (int i = 0; i < inputLength; ++i) {
|
||||
char currentChar = inputString.charAt(i);
|
||||
if (currentChar != '\\') {
|
||||
buffer.append(currentChar);
|
||||
} else {
|
||||
if (i < inputLength - 1) {
|
||||
char nextChar = inputString.charAt(i + 1);
|
||||
buffer.append(nextChar);
|
||||
|
||||
// force a skip over the next character too
|
||||
++i;
|
||||
} else {
|
||||
buffer.append(currentChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private String data;
|
||||
private int dataIndex;
|
||||
private int dataLength;
|
||||
private int currentToken;
|
||||
private String currentTokenValue;
|
||||
private boolean isAutoquoting;
|
||||
private char autoquoteChar;
|
||||
|
||||
/*
|
||||
public static void main(String[] args) {
|
||||
for (int i = 0; i < args.length; ++i) {
|
||||
MailcapTokenizer tokenizer = new MailcapTokenizer(args[i]);
|
||||
|
||||
System.out.println("Original: |" + args[i] + "|");
|
||||
|
||||
int currentToken = tokenizer.nextToken();
|
||||
while (currentToken != EOI_TOKEN) {
|
||||
switch(currentToken) {
|
||||
case UNKNOWN_TOKEN:
|
||||
System.out.println(" Unknown Token: |" + tokenizer.getCurrentTokenValue() + "|");
|
||||
break;
|
||||
case START_TOKEN:
|
||||
System.out.println(" Start Token: |" + tokenizer.getCurrentTokenValue() + "|");
|
||||
break;
|
||||
case STRING_TOKEN:
|
||||
System.out.println(" String Token: |" + tokenizer.getCurrentTokenValue() + "|");
|
||||
break;
|
||||
case EOI_TOKEN:
|
||||
System.out.println(" EOI Token: |" + tokenizer.getCurrentTokenValue() + "|");
|
||||
break;
|
||||
case SLASH_TOKEN:
|
||||
System.out.println(" Slash Token: |" + tokenizer.getCurrentTokenValue() + "|");
|
||||
break;
|
||||
case SEMICOLON_TOKEN:
|
||||
System.out.println(" Semicolon Token: |" + tokenizer.getCurrentTokenValue() + "|");
|
||||
break;
|
||||
case EQUALS_TOKEN:
|
||||
System.out.println(" Equals Token: |" + tokenizer.getCurrentTokenValue() + "|");
|
||||
break;
|
||||
default:
|
||||
System.out.println(" Really Unknown Token: |" + tokenizer.getCurrentTokenValue() + "|");
|
||||
break;
|
||||
}
|
||||
|
||||
currentToken = tokenizer.nextToken();
|
||||
}
|
||||
|
||||
System.out.println("");
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0, which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
package com.sun.activation.registries;
|
||||
|
||||
import java.lang.*;
|
||||
|
||||
public class MimeTypeEntry {
|
||||
private String type;
|
||||
private String extension;
|
||||
|
||||
public MimeTypeEntry(String mime_type, String file_ext) {
|
||||
type = mime_type;
|
||||
extension = file_ext;
|
||||
}
|
||||
|
||||
public String getMIMEType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getFileExtension() {
|
||||
return extension;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "MIMETypeEntry: " + type + ", " + extension;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Distribution License v. 1.0, which is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
package com.sun.activation.registries;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class MimeTypeFile {
|
||||
private String fname = null;
|
||||
private Hashtable type_hash = new Hashtable();
|
||||
|
||||
/**
|
||||
* The construtor that takes a filename as an argument.
|
||||
*
|
||||
* @param new_fname The file name of the mime types file.
|
||||
*/
|
||||
public MimeTypeFile(String new_fname) throws IOException {
|
||||
File mime_file = null;
|
||||
FileReader fr = null;
|
||||
|
||||
fname = new_fname; // remember the file name
|
||||
|
||||
mime_file = new File(fname); // get a file object
|
||||
|
||||
fr = new FileReader(mime_file);
|
||||
|
||||
try {
|
||||
parse(new BufferedReader(fr));
|
||||
} finally {
|
||||
try {
|
||||
fr.close(); // close it
|
||||
} catch (IOException e) {
|
||||
// ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MimeTypeFile(InputStream is) throws IOException {
|
||||
parse(new BufferedReader(new InputStreamReader(is, "iso-8859-1")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty DB.
|
||||
*/
|
||||
public MimeTypeFile() {
|
||||
}
|
||||
|
||||
/**
|
||||
* get the MimeTypeEntry based on the file extension
|
||||
*/
|
||||
public MimeTypeEntry getMimeTypeEntry(String file_ext) {
|
||||
return (MimeTypeEntry)type_hash.get((Object)file_ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MIME type string corresponding to the file extension.
|
||||
*/
|
||||
public String getMIMETypeString(String file_ext) {
|
||||
MimeTypeEntry entry = this.getMimeTypeEntry(file_ext);
|
||||
|
||||
if (entry != null)
|
||||
return entry.getMIMEType();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends string of entries to the types registry, must be valid
|
||||
* .mime.types format.
|
||||
* A mime.types entry is one of two forms:
|
||||
*
|
||||
* type/subtype ext1 ext2 ...
|
||||
* or
|
||||
* type=type/subtype desc="description of type" exts=ext1,ext2,...
|
||||
*
|
||||
* Example:
|
||||
* # this is a test
|
||||
* audio/basic au
|
||||
* text/plain txt text
|
||||
* type=application/postscript exts=ps,eps
|
||||
*/
|
||||
public void appendToRegistry(String mime_types) {
|
||||
try {
|
||||
parse(new BufferedReader(new StringReader(mime_types)));
|
||||
} catch (IOException ex) {
|
||||
// can't happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a stream of mime.types entries.
|
||||
*/
|
||||
private void parse(BufferedReader buf_reader) throws IOException {
|
||||
String line = null, prev = null;
|
||||
|
||||
while ((line = buf_reader.readLine()) != null) {
|
||||
if (prev == null)
|
||||
prev = line;
|
||||
else
|
||||
prev += line;
|
||||
int end = prev.length();
|
||||
if (prev.length() > 0 && prev.charAt(end - 1) == '\\') {
|
||||
prev = prev.substring(0, end - 1);
|
||||
continue;
|
||||
}
|
||||
this.parseEntry(prev);
|
||||
prev = null;
|
||||
}
|
||||
if (prev != null)
|
||||
this.parseEntry(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse single mime.types entry.
|
||||
*/
|
||||
private void parseEntry(String line) {
|
||||
String mime_type = null;
|
||||
String file_ext = null;
|
||||
line = line.trim();
|
||||
|
||||
if (line.length() == 0) // empty line...
|
||||
return; // BAIL!
|
||||
|
||||
// check to see if this is a comment line?
|
||||
if (line.charAt(0) == '#')
|
||||
return; // then we are done!
|
||||
|
||||
// is it a new format line or old format?
|
||||
if (line.indexOf('=') > 0) {
|
||||
// new format
|
||||
LineTokenizer lt = new LineTokenizer(line);
|
||||
while (lt.hasMoreTokens()) {
|
||||
String name = lt.nextToken();
|
||||
String value = null;
|
||||
if (lt.hasMoreTokens() && lt.nextToken().equals("=") &&
|
||||
lt.hasMoreTokens())
|
||||
value = lt.nextToken();
|
||||
if (value == null) {
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("Bad .mime.types entry: " + line);
|
||||
return;
|
||||
}
|
||||
if (name.equals("type"))
|
||||
mime_type = value;
|
||||
else if (name.equals("exts")) {
|
||||
StringTokenizer st = new StringTokenizer(value, ",");
|
||||
while (st.hasMoreTokens()) {
|
||||
file_ext = st.nextToken();
|
||||
MimeTypeEntry entry =
|
||||
new MimeTypeEntry(mime_type, file_ext);
|
||||
type_hash.put(file_ext, entry);
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("Added: " + entry.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// old format
|
||||
// count the tokens
|
||||
StringTokenizer strtok = new StringTokenizer(line);
|
||||
int num_tok = strtok.countTokens();
|
||||
|
||||
if (num_tok == 0) // empty line
|
||||
return;
|
||||
|
||||
mime_type = strtok.nextToken(); // get the MIME type
|
||||
|
||||
while (strtok.hasMoreTokens()) {
|
||||
MimeTypeEntry entry = null;
|
||||
|
||||
file_ext = strtok.nextToken();
|
||||
entry = new MimeTypeEntry(mime_type, file_ext);
|
||||
type_hash.put(file_ext, entry);
|
||||
if (LogSupport.isLoggable())
|
||||
LogSupport.log("Added: " + entry.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for debugging
|
||||
/*
|
||||
public static void main(String[] argv) throws Exception {
|
||||
MimeTypeFile mf = new MimeTypeFile(argv[0]);
|
||||
System.out.println("ext " + argv[1] + " type " +
|
||||
mf.getMIMETypeString(argv[1]));
|
||||
System.exit(0);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
class LineTokenizer {
|
||||
private int currentPosition;
|
||||
private int maxPosition;
|
||||
private String str;
|
||||
private Vector stack = new Vector();
|
||||
private static final String singles = "="; // single character tokens
|
||||
|
||||
/**
|
||||
* Constructs a tokenizer for the specified string.
|
||||
* <p>
|
||||
*
|
||||
* @param str a string to be parsed.
|
||||
*/
|
||||
public LineTokenizer(String str) {
|
||||
currentPosition = 0;
|
||||
this.str = str;
|
||||
maxPosition = str.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips white space.
|
||||
*/
|
||||
private void skipWhiteSpace() {
|
||||
while ((currentPosition < maxPosition) &&
|
||||
Character.isWhitespace(str.charAt(currentPosition))) {
|
||||
currentPosition++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if there are more tokens available from this tokenizer's string.
|
||||
*
|
||||
* @return <code>true</code> if there are more tokens available from this
|
||||
* tokenizer's string; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean hasMoreTokens() {
|
||||
if (stack.size() > 0)
|
||||
return true;
|
||||
skipWhiteSpace();
|
||||
return (currentPosition < maxPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next token from this tokenizer.
|
||||
*
|
||||
* @return the next token from this tokenizer.
|
||||
* @exception NoSuchElementException if there are no more tokens in this
|
||||
* tokenizer's string.
|
||||
*/
|
||||
public String nextToken() {
|
||||
int size = stack.size();
|
||||
if (size > 0) {
|
||||
String t = (String)stack.elementAt(size - 1);
|
||||
stack.removeElementAt(size - 1);
|
||||
return t;
|
||||
}
|
||||
skipWhiteSpace();
|
||||
|
||||
if (currentPosition >= maxPosition) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
int start = currentPosition;
|
||||
char c = str.charAt(start);
|
||||
if (c == '"') {
|
||||
currentPosition++;
|
||||
boolean filter = false;
|
||||
while (currentPosition < maxPosition) {
|
||||
c = str.charAt(currentPosition++);
|
||||
if (c == '\\') {
|
||||
currentPosition++;
|
||||
filter = true;
|
||||
} else if (c == '"') {
|
||||
String s;
|
||||
|
||||
if (filter) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = start + 1; i < currentPosition - 1; i++) {
|
||||
c = str.charAt(i);
|
||||
if (c != '\\')
|
||||
sb.append(c);
|
||||
}
|
||||
s = sb.toString();
|
||||
} else
|
||||
s = str.substring(start + 1, currentPosition - 1);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
} else if (singles.indexOf(c) >= 0) {
|
||||
currentPosition++;
|
||||
} else {
|
||||
while ((currentPosition < maxPosition) &&
|
||||
singles.indexOf(str.charAt(currentPosition)) < 0 &&
|
||||
!Character.isWhitespace(str.charAt(currentPosition))) {
|
||||
currentPosition++;
|
||||
}
|
||||
}
|
||||
return str.substring(start, currentPosition);
|
||||
}
|
||||
|
||||
public void pushToken(String token) {
|
||||
stack.addElement(token);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copied from OpenJDK with permission.
|
||||
*/
|
||||
|
||||
package com.sun.mail.auth;
|
||||
|
||||
import java.security.*;
|
||||
|
||||
//import static sun.security.provider.ByteArrayAccess.*;
|
||||
|
||||
/**
|
||||
* The MD4 class is used to compute an MD4 message digest over a given
|
||||
* buffer of bytes. It is an implementation of the RSA Data Security Inc
|
||||
* MD4 algorithim as described in internet RFC 1320.
|
||||
*
|
||||
* @author Andreas Sterbenz
|
||||
* @author Bill Shannon (adapted for Jakarta Mail)
|
||||
*/
|
||||
public final class MD4 {
|
||||
|
||||
// state of this object
|
||||
private final int[] state;
|
||||
// temporary buffer, used by implCompress()
|
||||
private final int[] x;
|
||||
|
||||
// size of the input to the compression function in bytes
|
||||
private static final int blockSize = 64;
|
||||
|
||||
// buffer to store partial blocks, blockSize bytes large
|
||||
private final byte[] buffer = new byte[blockSize];
|
||||
// offset into buffer
|
||||
private int bufOfs;
|
||||
|
||||
// number of bytes processed so far.
|
||||
// also used as a flag to indicate reset status
|
||||
// -1: need to call engineReset() before next call to update()
|
||||
// 0: is already reset
|
||||
private long bytesProcessed;
|
||||
|
||||
// rotation constants
|
||||
private static final int S11 = 3;
|
||||
private static final int S12 = 7;
|
||||
private static final int S13 = 11;
|
||||
private static final int S14 = 19;
|
||||
private static final int S21 = 3;
|
||||
private static final int S22 = 5;
|
||||
private static final int S23 = 9;
|
||||
private static final int S24 = 13;
|
||||
private static final int S31 = 3;
|
||||
private static final int S32 = 9;
|
||||
private static final int S33 = 11;
|
||||
private static final int S34 = 15;
|
||||
|
||||
private static final byte[] padding;
|
||||
|
||||
static {
|
||||
padding = new byte[136];
|
||||
padding[0] = (byte)0x80;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard constructor, creates a new MD4 instance.
|
||||
*/
|
||||
public MD4() {
|
||||
state = new int[4];
|
||||
x = new int[16];
|
||||
implReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and return the message digest of the input byte array.
|
||||
*
|
||||
* @param in the input byte array
|
||||
* @return the message digest byte array
|
||||
*/
|
||||
public byte[] digest(byte[] in) {
|
||||
implReset();
|
||||
engineUpdate(in, 0, in.length);
|
||||
byte[] out = new byte[16];
|
||||
implDigest(out, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state of this object.
|
||||
*/
|
||||
private void implReset() {
|
||||
// Load magic initialization constants.
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xefcdab89;
|
||||
state[2] = 0x98badcfe;
|
||||
state[3] = 0x10325476;
|
||||
bufOfs = 0;
|
||||
bytesProcessed = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the final computations, any buffered bytes are added
|
||||
* to the digest, the count is added to the digest, and the resulting
|
||||
* digest is stored.
|
||||
*/
|
||||
private void implDigest(byte[] out, int ofs) {
|
||||
long bitsProcessed = bytesProcessed << 3;
|
||||
|
||||
int index = (int)bytesProcessed & 0x3f;
|
||||
int padLen = (index < 56) ? (56 - index) : (120 - index);
|
||||
engineUpdate(padding, 0, padLen);
|
||||
|
||||
//i2bLittle4((int)bitsProcessed, buffer, 56);
|
||||
//i2bLittle4((int)(bitsProcessed >>> 32), buffer, 60);
|
||||
buffer[56] = (byte)bitsProcessed;
|
||||
buffer[57] = (byte)(bitsProcessed>>8);
|
||||
buffer[58] = (byte)(bitsProcessed>>16);
|
||||
buffer[59] = (byte)(bitsProcessed>>24);
|
||||
buffer[60] = (byte)(bitsProcessed>>32);
|
||||
buffer[61] = (byte)(bitsProcessed>>40);
|
||||
buffer[62] = (byte)(bitsProcessed>>48);
|
||||
buffer[63] = (byte)(bitsProcessed>>56);
|
||||
implCompress(buffer, 0);
|
||||
|
||||
//i2bLittle(state, 0, out, ofs, 16);
|
||||
for (int i = 0; i < state.length; i++) {
|
||||
int x = state[i];
|
||||
out[ofs++] = (byte)x;
|
||||
out[ofs++] = (byte)(x>>8);
|
||||
out[ofs++] = (byte)(x>>16);
|
||||
out[ofs++] = (byte)(x>>24);
|
||||
}
|
||||
}
|
||||
|
||||
private void engineUpdate(byte[] b, int ofs, int len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
if ((ofs < 0) || (len < 0) || (ofs > b.length - len)) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
if (bytesProcessed < 0) {
|
||||
implReset();
|
||||
}
|
||||
bytesProcessed += len;
|
||||
// if buffer is not empty, we need to fill it before proceeding
|
||||
if (bufOfs != 0) {
|
||||
int n = Math.min(len, blockSize - bufOfs);
|
||||
System.arraycopy(b, ofs, buffer, bufOfs, n);
|
||||
bufOfs += n;
|
||||
ofs += n;
|
||||
len -= n;
|
||||
if (bufOfs >= blockSize) {
|
||||
// compress completed block now
|
||||
implCompress(buffer, 0);
|
||||
bufOfs = 0;
|
||||
}
|
||||
}
|
||||
// compress complete blocks
|
||||
while (len >= blockSize) {
|
||||
implCompress(b, ofs);
|
||||
len -= blockSize;
|
||||
ofs += blockSize;
|
||||
}
|
||||
// copy remainder to buffer
|
||||
if (len > 0) {
|
||||
System.arraycopy(b, ofs, buffer, 0, len);
|
||||
bufOfs = len;
|
||||
}
|
||||
}
|
||||
|
||||
private static int FF(int a, int b, int c, int d, int x, int s) {
|
||||
a += ((b & c) | ((~b) & d)) + x;
|
||||
return ((a << s) | (a >>> (32 - s)));
|
||||
}
|
||||
|
||||
private static int GG(int a, int b, int c, int d, int x, int s) {
|
||||
a += ((b & c) | (b & d) | (c & d)) + x + 0x5a827999;
|
||||
return ((a << s) | (a >>> (32 - s)));
|
||||
}
|
||||
|
||||
private static int HH(int a, int b, int c, int d, int x, int s) {
|
||||
a += ((b ^ c) ^ d) + x + 0x6ed9eba1;
|
||||
return ((a << s) | (a >>> (32 - s)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is where the functions come together as the generic MD4
|
||||
* transformation operation. It consumes 64
|
||||
* bytes from the buffer, beginning at the specified offset.
|
||||
*/
|
||||
private void implCompress(byte[] buf, int ofs) {
|
||||
//b2iLittle64(buf, ofs, x);
|
||||
for (int xfs = 0; xfs < x.length; xfs++) {
|
||||
x[xfs] = (buf[ofs] & 0xff) | ((buf[ofs+1] & 0xff) << 8) |
|
||||
((buf[ofs+2] & 0xff) << 16) | ((buf[ofs+3] & 0xff) << 24);
|
||||
ofs += 4;
|
||||
}
|
||||
|
||||
int a = state[0];
|
||||
int b = state[1];
|
||||
int c = state[2];
|
||||
int d = state[3];
|
||||
|
||||
/* Round 1 */
|
||||
a = FF (a, b, c, d, x[ 0], S11); /* 1 */
|
||||
d = FF (d, a, b, c, x[ 1], S12); /* 2 */
|
||||
c = FF (c, d, a, b, x[ 2], S13); /* 3 */
|
||||
b = FF (b, c, d, a, x[ 3], S14); /* 4 */
|
||||
a = FF (a, b, c, d, x[ 4], S11); /* 5 */
|
||||
d = FF (d, a, b, c, x[ 5], S12); /* 6 */
|
||||
c = FF (c, d, a, b, x[ 6], S13); /* 7 */
|
||||
b = FF (b, c, d, a, x[ 7], S14); /* 8 */
|
||||
a = FF (a, b, c, d, x[ 8], S11); /* 9 */
|
||||
d = FF (d, a, b, c, x[ 9], S12); /* 10 */
|
||||
c = FF (c, d, a, b, x[10], S13); /* 11 */
|
||||
b = FF (b, c, d, a, x[11], S14); /* 12 */
|
||||
a = FF (a, b, c, d, x[12], S11); /* 13 */
|
||||
d = FF (d, a, b, c, x[13], S12); /* 14 */
|
||||
c = FF (c, d, a, b, x[14], S13); /* 15 */
|
||||
b = FF (b, c, d, a, x[15], S14); /* 16 */
|
||||
|
||||
/* Round 2 */
|
||||
a = GG (a, b, c, d, x[ 0], S21); /* 17 */
|
||||
d = GG (d, a, b, c, x[ 4], S22); /* 18 */
|
||||
c = GG (c, d, a, b, x[ 8], S23); /* 19 */
|
||||
b = GG (b, c, d, a, x[12], S24); /* 20 */
|
||||
a = GG (a, b, c, d, x[ 1], S21); /* 21 */
|
||||
d = GG (d, a, b, c, x[ 5], S22); /* 22 */
|
||||
c = GG (c, d, a, b, x[ 9], S23); /* 23 */
|
||||
b = GG (b, c, d, a, x[13], S24); /* 24 */
|
||||
a = GG (a, b, c, d, x[ 2], S21); /* 25 */
|
||||
d = GG (d, a, b, c, x[ 6], S22); /* 26 */
|
||||
c = GG (c, d, a, b, x[10], S23); /* 27 */
|
||||
b = GG (b, c, d, a, x[14], S24); /* 28 */
|
||||
a = GG (a, b, c, d, x[ 3], S21); /* 29 */
|
||||
d = GG (d, a, b, c, x[ 7], S22); /* 30 */
|
||||
c = GG (c, d, a, b, x[11], S23); /* 31 */
|
||||
b = GG (b, c, d, a, x[15], S24); /* 32 */
|
||||
|
||||
/* Round 3 */
|
||||
a = HH (a, b, c, d, x[ 0], S31); /* 33 */
|
||||
d = HH (d, a, b, c, x[ 8], S32); /* 34 */
|
||||
c = HH (c, d, a, b, x[ 4], S33); /* 35 */
|
||||
b = HH (b, c, d, a, x[12], S34); /* 36 */
|
||||
a = HH (a, b, c, d, x[ 2], S31); /* 37 */
|
||||
d = HH (d, a, b, c, x[10], S32); /* 38 */
|
||||
c = HH (c, d, a, b, x[ 6], S33); /* 39 */
|
||||
b = HH (b, c, d, a, x[14], S34); /* 40 */
|
||||
a = HH (a, b, c, d, x[ 1], S31); /* 41 */
|
||||
d = HH (d, a, b, c, x[ 9], S32); /* 42 */
|
||||
c = HH (c, d, a, b, x[ 5], S33); /* 43 */
|
||||
b = HH (b, c, d, a, x[13], S34); /* 44 */
|
||||
a = HH (a, b, c, d, x[ 3], S31); /* 45 */
|
||||
d = HH (d, a, b, c, x[11], S32); /* 46 */
|
||||
c = HH (c, d, a, b, x[ 7], S33); /* 47 */
|
||||
b = HH (b, c, d, a, x[15], S34); /* 48 */
|
||||
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,498 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copied from OpenJDK with permission.
|
||||
*/
|
||||
|
||||
package com.sun.mail.auth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.PrintStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.DESKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.sun.mail.util.BASE64DecoderStream;
|
||||
import com.sun.mail.util.BASE64EncoderStream;
|
||||
import com.sun.mail.util.MailLogger;
|
||||
|
||||
|
||||
/**
|
||||
* NTLMAuthentication:
|
||||
*
|
||||
* @author Michael McMahon
|
||||
* @author Bill Shannon (adapted for Jakarta Mail)
|
||||
*/
|
||||
public class Ntlm {
|
||||
|
||||
private byte[] type1;
|
||||
private byte[] type3;
|
||||
|
||||
private SecretKeyFactory fac;
|
||||
private Cipher cipher;
|
||||
private MD4 md4;
|
||||
private String hostname;
|
||||
private String ntdomain;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
private Mac hmac;
|
||||
|
||||
private MailLogger logger;
|
||||
|
||||
// NTLM flags, as defined in Microsoft NTLM spec
|
||||
// https://msdn.microsoft.com/en-us/library/cc236621.aspx
|
||||
private static final int NTLMSSP_NEGOTIATE_UNICODE = 0x00000001;
|
||||
private static final int NTLMSSP_NEGOTIATE_OEM = 0x00000002;
|
||||
private static final int NTLMSSP_REQUEST_TARGET = 0x00000004;
|
||||
private static final int NTLMSSP_NEGOTIATE_SIGN = 0x00000010;
|
||||
private static final int NTLMSSP_NEGOTIATE_SEAL = 0x00000020;
|
||||
private static final int NTLMSSP_NEGOTIATE_DATAGRAM = 0x00000040;
|
||||
private static final int NTLMSSP_NEGOTIATE_LM_KEY = 0x00000080;
|
||||
private static final int NTLMSSP_NEGOTIATE_NTLM = 0x00000200;
|
||||
private static final int NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x00001000;
|
||||
private static final int NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x00002000;
|
||||
private static final int NTLMSSP_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
|
||||
private static final int NTLMSSP_TARGET_TYPE_DOMAIN = 0x00010000;
|
||||
private static final int NTLMSSP_TARGET_TYPE_SERVER = 0x00020000;
|
||||
private static final int NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x00080000;
|
||||
private static final int NTLMSSP_NEGOTIATE_IDENTIFY = 0x00100000;
|
||||
private static final int NTLMSSP_REQUEST_NON_NT_SESSION_KEY = 0x00400000;
|
||||
private static final int NTLMSSP_NEGOTIATE_TARGET_INFO = 0x00800000;
|
||||
private static final int NTLMSSP_NEGOTIATE_VERSION = 0x02000000;
|
||||
private static final int NTLMSSP_NEGOTIATE_128 = 0x20000000;
|
||||
private static final int NTLMSSP_NEGOTIATE_KEY_EXCH = 0x40000000;
|
||||
private static final int NTLMSSP_NEGOTIATE_56 = 0x80000000;
|
||||
|
||||
private static final byte RESPONSERVERSION = 1;
|
||||
private static final byte HIRESPONSERVERSION = 1;
|
||||
private static final byte[] Z6 = new byte[] { 0, 0, 0, 0, 0, 0 };
|
||||
private static final byte[] Z4 = new byte[] { 0, 0, 0, 0 };
|
||||
|
||||
private void init0() {
|
||||
type1 = new byte[256]; // hopefully large enough
|
||||
type3 = new byte[512]; // ditto
|
||||
System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,1}, 0,
|
||||
type1, 0, 9);
|
||||
System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,3}, 0,
|
||||
type3, 0, 9);
|
||||
|
||||
try {
|
||||
fac = SecretKeyFactory.getInstance("DES");
|
||||
cipher = Cipher.getInstance("DES/ECB/NoPadding");
|
||||
md4 = new MD4();
|
||||
} catch (NoSuchPaddingException e) {
|
||||
assert false;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
assert false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an NTLM authenticator.
|
||||
* Username may be specified as domain\\username in the Authenticator.
|
||||
* If this notation is not used, then the domain will be taken
|
||||
* from the ntdomain parameter.
|
||||
*
|
||||
* @param ntdomain the NT domain
|
||||
* @param hostname the host name
|
||||
* @param username the user name
|
||||
* @param password the password
|
||||
* @param logger the MailLogger
|
||||
*/
|
||||
public Ntlm(String ntdomain, String hostname, String username,
|
||||
String password, MailLogger logger) {
|
||||
int i = hostname.indexOf('.');
|
||||
if (i != -1) {
|
||||
hostname = hostname.substring(0, i);
|
||||
}
|
||||
i = username.indexOf('\\');
|
||||
if (i != -1) {
|
||||
ntdomain = username.substring(0, i).toUpperCase(Locale.ENGLISH);
|
||||
username = username.substring(i+1);
|
||||
} else if (ntdomain == null) {
|
||||
ntdomain = "";
|
||||
}
|
||||
this.ntdomain = ntdomain;
|
||||
this.hostname = hostname;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.logger = logger.getLogger(this.getClass(), "DEBUG NTLM");
|
||||
init0();
|
||||
}
|
||||
|
||||
private void copybytes(byte[] dest, int destpos, String src, String enc) {
|
||||
try {
|
||||
byte[] x = src.getBytes(enc);
|
||||
System.arraycopy(x, 0, dest, destpos, x.length);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
|
||||
// for compatibility, just in case
|
||||
public String generateType1Msg(int flags) {
|
||||
return generateType1Msg(flags, false);
|
||||
}
|
||||
|
||||
public String generateType1Msg(int flags, boolean v2) {
|
||||
int dlen = ntdomain.length();
|
||||
int type1flags =
|
||||
NTLMSSP_NEGOTIATE_UNICODE |
|
||||
NTLMSSP_NEGOTIATE_OEM |
|
||||
NTLMSSP_NEGOTIATE_NTLM |
|
||||
NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED |
|
||||
NTLMSSP_NEGOTIATE_ALWAYS_SIGN |
|
||||
flags;
|
||||
if (dlen != 0)
|
||||
type1flags |= NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED;
|
||||
if (v2)
|
||||
type1flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY;
|
||||
writeInt(type1, 12, type1flags);
|
||||
type1[28] = (byte) 0x20; // host name offset
|
||||
writeShort(type1, 16, dlen);
|
||||
writeShort(type1, 18, dlen);
|
||||
|
||||
int hlen = hostname.length();
|
||||
writeShort(type1, 24, hlen);
|
||||
writeShort(type1, 26, hlen);
|
||||
|
||||
copybytes(type1, 32, hostname, "iso-8859-1");
|
||||
copybytes(type1, hlen+32, ntdomain, "iso-8859-1");
|
||||
writeInt(type1, 20, hlen+32);
|
||||
|
||||
byte[] msg = new byte[32 + hlen + dlen];
|
||||
System.arraycopy(type1, 0, msg, 0, 32 + hlen + dlen);
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("type 1 message: " + toHex(msg));
|
||||
|
||||
String result = null;
|
||||
try {
|
||||
result = new String(BASE64EncoderStream.encode(msg), "iso-8859-1");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
assert false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a 7 byte array to an 8 byte array (for a des key with parity).
|
||||
* Input starts at offset off.
|
||||
*/
|
||||
private byte[] makeDesKey(byte[] input, int off) {
|
||||
int[] in = new int[input.length];
|
||||
for (int i = 0; i < in.length; i++) {
|
||||
in[i] = input[i] < 0 ? input[i] + 256: input[i];
|
||||
}
|
||||
byte[] out = new byte[8];
|
||||
out[0] = (byte)in[off+0];
|
||||
out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
|
||||
out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
|
||||
out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
|
||||
out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
|
||||
out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
|
||||
out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
|
||||
out[7] = (byte)((in[off+6] << 1) & 0xFF);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute hash-based message authentication code for NTLMv2.
|
||||
*/
|
||||
private byte[] hmacMD5(byte[] key, byte[] text) {
|
||||
try {
|
||||
if (hmac == null)
|
||||
hmac = Mac.getInstance("HmacMD5");
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
try {
|
||||
byte[] nk = new byte[16];
|
||||
System.arraycopy(key, 0, nk, 0, key.length > 16 ? 16 : key.length);
|
||||
SecretKeySpec skey = new SecretKeySpec(nk, "HmacMD5");
|
||||
hmac.init(skey);
|
||||
return hmac.doFinal(text);
|
||||
} catch (InvalidKeyException ex) {
|
||||
assert false;
|
||||
} catch (RuntimeException e) {
|
||||
assert false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] calcLMHash() throws GeneralSecurityException {
|
||||
byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
|
||||
byte[] pwb = null;
|
||||
try {
|
||||
pwb = password.toUpperCase(Locale.ENGLISH).getBytes("iso-8859-1");
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
// should never happen
|
||||
assert false;
|
||||
}
|
||||
byte[] pwb1 = new byte[14];
|
||||
int len = password.length();
|
||||
if (len > 14)
|
||||
len = 14;
|
||||
System.arraycopy(pwb, 0, pwb1, 0, len); /* Zero padded */
|
||||
|
||||
DESKeySpec dks1 = new DESKeySpec(makeDesKey(pwb1, 0));
|
||||
DESKeySpec dks2 = new DESKeySpec(makeDesKey(pwb1, 7));
|
||||
|
||||
SecretKey key1 = fac.generateSecret(dks1);
|
||||
SecretKey key2 = fac.generateSecret(dks2);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key1);
|
||||
byte[] out1 = cipher.doFinal(magic, 0, 8);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key2);
|
||||
byte[] out2 = cipher.doFinal(magic, 0, 8);
|
||||
|
||||
byte[] result = new byte [21];
|
||||
System.arraycopy(out1, 0, result, 0, 8);
|
||||
System.arraycopy(out2, 0, result, 8, 8);
|
||||
return result;
|
||||
}
|
||||
|
||||
private byte[] calcNTHash() throws GeneralSecurityException {
|
||||
byte[] pw = null;
|
||||
try {
|
||||
pw = password.getBytes("UnicodeLittleUnmarked");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
assert false;
|
||||
}
|
||||
byte[] out = md4.digest(pw);
|
||||
byte[] result = new byte[21];
|
||||
System.arraycopy(out, 0, result, 0, 16);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Key is a 21 byte array. Split it into 3 7 byte chunks,
|
||||
* convert each to 8 byte DES keys, encrypt the text arg with
|
||||
* each key and return the three results in a sequential [].
|
||||
*/
|
||||
private byte[] calcResponse(byte[] key, byte[] text)
|
||||
throws GeneralSecurityException {
|
||||
assert key.length == 21;
|
||||
DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
|
||||
DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
|
||||
DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
|
||||
SecretKey key1 = fac.generateSecret(dks1);
|
||||
SecretKey key2 = fac.generateSecret(dks2);
|
||||
SecretKey key3 = fac.generateSecret(dks3);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key1);
|
||||
byte[] out1 = cipher.doFinal(text, 0, 8);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key2);
|
||||
byte[] out2 = cipher.doFinal(text, 0, 8);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key3);
|
||||
byte[] out3 = cipher.doFinal(text, 0, 8);
|
||||
byte[] result = new byte[24];
|
||||
System.arraycopy(out1, 0, result, 0, 8);
|
||||
System.arraycopy(out2, 0, result, 8, 8);
|
||||
System.arraycopy(out3, 0, result, 16, 8);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the NTLMv2 response based on the nthash, additional data,
|
||||
* and the original challenge.
|
||||
*/
|
||||
private byte[] calcV2Response(byte[] nthash, byte[] blob, byte[] challenge)
|
||||
throws GeneralSecurityException {
|
||||
byte[] txt = null;
|
||||
try {
|
||||
txt = (username.toUpperCase(Locale.ENGLISH) + ntdomain).
|
||||
getBytes("UnicodeLittleUnmarked");
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
// should never happen
|
||||
assert false;
|
||||
}
|
||||
byte[] ntlmv2hash = hmacMD5(nthash, txt);
|
||||
byte[] cb = new byte[blob.length + 8];
|
||||
System.arraycopy(challenge, 0, cb, 0, 8);
|
||||
System.arraycopy(blob, 0, cb, 8, blob.length);
|
||||
byte[] result = new byte[blob.length + 16];
|
||||
System.arraycopy(hmacMD5(ntlmv2hash, cb), 0, result, 0, 16);
|
||||
System.arraycopy(blob, 0, result, 16, blob.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String generateType3Msg(String type2msg) {
|
||||
try {
|
||||
|
||||
/* First decode the type2 message to get the server challenge */
|
||||
/* challenge is located at type2[24] for 8 bytes */
|
||||
byte[] type2 = null;
|
||||
try {
|
||||
type2 = BASE64DecoderStream.decode(type2msg.getBytes("us-ascii"));
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
// should never happen
|
||||
assert false;
|
||||
}
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("type 2 message: " + toHex(type2));
|
||||
|
||||
byte[] challenge = new byte[8];
|
||||
System.arraycopy(type2, 24, challenge, 0, 8);
|
||||
|
||||
int type3flags =
|
||||
NTLMSSP_NEGOTIATE_UNICODE |
|
||||
NTLMSSP_NEGOTIATE_NTLM |
|
||||
NTLMSSP_NEGOTIATE_ALWAYS_SIGN;
|
||||
|
||||
int ulen = username.length()*2;
|
||||
writeShort(type3, 36, ulen);
|
||||
writeShort(type3, 38, ulen);
|
||||
int dlen = ntdomain.length()*2;
|
||||
writeShort(type3, 28, dlen);
|
||||
writeShort(type3, 30, dlen);
|
||||
int hlen = hostname.length()*2;
|
||||
writeShort(type3, 44, hlen);
|
||||
writeShort(type3, 46, hlen);
|
||||
|
||||
int l = 64;
|
||||
copybytes(type3, l, ntdomain, "UnicodeLittleUnmarked");
|
||||
writeInt(type3, 32, l);
|
||||
l += dlen;
|
||||
copybytes(type3, l, username, "UnicodeLittleUnmarked");
|
||||
writeInt(type3, 40, l);
|
||||
l += ulen;
|
||||
copybytes(type3, l, hostname, "UnicodeLittleUnmarked");
|
||||
writeInt(type3, 48, l);
|
||||
l += hlen;
|
||||
|
||||
byte[] msg = null;
|
||||
byte[] lmresponse = null;
|
||||
byte[] ntresponse = null;
|
||||
int flags = readInt(type2, 20);
|
||||
|
||||
// did the server agree to NTLMv2?
|
||||
if ((flags & NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0) {
|
||||
// yes, create an NTLMv2 response
|
||||
logger.fine("Using NTLMv2");
|
||||
type3flags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY;
|
||||
byte[] nonce = new byte[8];
|
||||
// XXX - allow user to specify Random instance via properties?
|
||||
(new Random()).nextBytes(nonce);
|
||||
byte[] nthash = calcNTHash();
|
||||
lmresponse = calcV2Response(nthash, nonce, challenge);
|
||||
byte[] targetInfo = new byte[0];
|
||||
if ((flags & NTLMSSP_NEGOTIATE_TARGET_INFO) != 0) {
|
||||
int tlen = readShort(type2, 40);
|
||||
int toff = readInt(type2, 44);
|
||||
targetInfo = new byte[tlen];
|
||||
System.arraycopy(type2, toff, targetInfo, 0, tlen);
|
||||
}
|
||||
byte[] blob = new byte[32 + targetInfo.length];
|
||||
blob[0] = RESPONSERVERSION;
|
||||
blob[1] = HIRESPONSERVERSION;
|
||||
System.arraycopy(Z6, 0, blob, 2, 6);
|
||||
// convert time to NT format
|
||||
long now = (System.currentTimeMillis() + 11644473600000L) * 10000L;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
blob[8 + i] = (byte)(now & 0xff);
|
||||
now >>= 8;
|
||||
}
|
||||
System.arraycopy(nonce, 0, blob, 16, 8);
|
||||
System.arraycopy(Z4, 0, blob, 24, 4);
|
||||
System.arraycopy(targetInfo, 0, blob, 28, targetInfo.length);
|
||||
System.arraycopy(Z4, 0, blob, 28 + targetInfo.length, 4);
|
||||
ntresponse = calcV2Response(nthash, blob, challenge);
|
||||
} else {
|
||||
byte[] lmhash = calcLMHash();
|
||||
lmresponse = calcResponse(lmhash, challenge);
|
||||
byte[] nthash = calcNTHash();
|
||||
ntresponse = calcResponse(nthash, challenge);
|
||||
}
|
||||
System.arraycopy(lmresponse, 0, type3, l, lmresponse.length);
|
||||
writeShort(type3, 12, lmresponse.length);
|
||||
writeShort(type3, 14, lmresponse.length);
|
||||
writeInt(type3, 16, l);
|
||||
l += 24;
|
||||
System.arraycopy(ntresponse, 0, type3, l, ntresponse.length);
|
||||
writeShort(type3, 20, ntresponse.length);
|
||||
writeShort(type3, 22, ntresponse.length);
|
||||
writeInt(type3, 24, l);
|
||||
l += ntresponse.length;
|
||||
writeShort(type3, 56, l);
|
||||
|
||||
msg = new byte[l];
|
||||
System.arraycopy(type3, 0, msg, 0, l);
|
||||
|
||||
writeInt(type3, 60, type3flags);
|
||||
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("type 3 message: " + toHex(msg));
|
||||
|
||||
String result = null;
|
||||
try {
|
||||
result = new String(BASE64EncoderStream.encode(msg), "iso-8859-1");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
assert false;
|
||||
}
|
||||
return result;
|
||||
|
||||
} catch (GeneralSecurityException ex) {
|
||||
// should never happen
|
||||
logger.log(Level.FINE, "GeneralSecurityException", ex);
|
||||
return ""; // will fail later
|
||||
}
|
||||
}
|
||||
|
||||
private static int readShort(byte[] b, int off) {
|
||||
return (((int)b[off]) & 0xff) |
|
||||
((((int)b[off+1]) & 0xff) << 8);
|
||||
}
|
||||
|
||||
private void writeShort(byte[] b, int off, int data) {
|
||||
b[off] = (byte) (data & 0xff);
|
||||
b[off+1] = (byte) ((data >> 8) & 0xff);
|
||||
}
|
||||
|
||||
private static int readInt(byte[] b, int off) {
|
||||
return (((int)b[off]) & 0xff) |
|
||||
((((int)b[off+1]) & 0xff) << 8) |
|
||||
((((int)b[off+2]) & 0xff) << 16) |
|
||||
((((int)b[off+3]) & 0xff) << 24);
|
||||
}
|
||||
|
||||
private void writeInt(byte[] b, int off, int data) {
|
||||
b[off] = (byte) (data & 0xff);
|
||||
b[off+1] = (byte) ((data >> 8) & 0xff);
|
||||
b[off+2] = (byte) ((data >> 16) & 0xff);
|
||||
b[off+3] = (byte) ((data >> 24) & 0xff);
|
||||
}
|
||||
|
||||
private static char[] hex =
|
||||
{ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
|
||||
|
||||
private static String toHex(byte[] b) {
|
||||
StringBuilder sb = new StringBuilder(b.length * 3);
|
||||
for (int i = 0; i < b.length; i++)
|
||||
sb.append(hex[(b[i]>>4)&0xF]).append(hex[b[i]&0xF]).append(' ');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<!--
|
||||
|
||||
Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
This program and the accompanying materials are made available under the
|
||||
terms of the Eclipse Public License v. 2.0, which is available at
|
||||
http://www.eclipse.org/legal/epl-2.0.
|
||||
|
||||
This Source Code may also be made available under the following Secondary
|
||||
Licenses when the conditions for such availability set forth in the
|
||||
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
version 2 with the GNU Classpath Exception, which is available at
|
||||
https://www.gnu.org/software/classpath/license.html.
|
||||
|
||||
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
|
||||
-->
|
||||
|
||||
<TITLE>com.sun.mail.auth package</TITLE>
|
||||
</HEAD>
|
||||
<BODY BGCOLOR="white">
|
||||
|
||||
<P>
|
||||
This package includes internal authentication support classes and
|
||||
<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
|
||||
</P>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.activation.*;
|
||||
|
||||
/**
|
||||
* Base class for other DataContentHandlers.
|
||||
*/
|
||||
public abstract class handler_base implements DataContentHandler {
|
||||
|
||||
/**
|
||||
* Return an array of ActivationDataFlavors that we support.
|
||||
* Usually there will be only one.
|
||||
*
|
||||
* @return array of ActivationDataFlavors that we support
|
||||
*/
|
||||
protected abstract ActivationDataFlavor[] getDataFlavors();
|
||||
|
||||
/**
|
||||
* Given the flavor that matched, return the appropriate type of object.
|
||||
* Usually there's only one flavor so just call getContent.
|
||||
*
|
||||
* @param aFlavor the ActivationDataFlavor
|
||||
* @param ds DataSource containing the data
|
||||
* @return the object
|
||||
* @exception IOException for errors reading the data
|
||||
*/
|
||||
protected Object getData(ActivationDataFlavor aFlavor, DataSource ds)
|
||||
throws IOException {
|
||||
return getContent(ds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DataFlavors for this <code>DataContentHandler</code>.
|
||||
*
|
||||
* @return The DataFlavors
|
||||
*/
|
||||
public ActivationDataFlavor[] getTransferDataFlavors() {
|
||||
return getDataFlavors().clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Transfer Data of type DataFlavor from InputStream.
|
||||
*
|
||||
* @param df The DataFlavor
|
||||
* @param ds The DataSource corresponding to the data
|
||||
* @return the object
|
||||
* @exception IOException for errors reading the data
|
||||
*/
|
||||
public Object getTransferData(ActivationDataFlavor df, DataSource ds)
|
||||
throws IOException {
|
||||
ActivationDataFlavor[] adf = getDataFlavors();
|
||||
for (int i = 0; i < adf.length; i++) {
|
||||
// use ActivationDataFlavor.equals, which properly
|
||||
// ignores Content-Type parameters in comparison
|
||||
if (adf[i].equals(df))
|
||||
return getData(adf[i], ds);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.handlers;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Properties;
|
||||
import javax.activation.*;
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author Christopher Cotton
|
||||
*/
|
||||
|
||||
|
||||
public class message_rfc822 extends handler_base {
|
||||
|
||||
private static ActivationDataFlavor[] ourDataFlavor = {
|
||||
new ActivationDataFlavor(Message.class, "message/rfc822", "Message")
|
||||
};
|
||||
|
||||
@Override
|
||||
protected ActivationDataFlavor[] getDataFlavors() {
|
||||
return ourDataFlavor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content.
|
||||
*/
|
||||
@Override
|
||||
public Object getContent(DataSource ds) throws IOException {
|
||||
// create a new MimeMessage
|
||||
try {
|
||||
Session session;
|
||||
if (ds instanceof MessageAware) {
|
||||
MessageContext mc = ((MessageAware)ds).getMessageContext();
|
||||
session = mc.getSession();
|
||||
} else {
|
||||
// Hopefully a rare case. Also hopefully the application
|
||||
// has created a default Session that can just be returned
|
||||
// here. If not, the one we create here is better than
|
||||
// nothing, but overall not a really good answer.
|
||||
session = Session.getDefaultInstance(new Properties(), null);
|
||||
}
|
||||
return new MimeMessage(session, ds.getInputStream());
|
||||
} catch (MessagingException me) {
|
||||
IOException ioex =
|
||||
new IOException("Exception creating MimeMessage in " +
|
||||
"message/rfc822 DataContentHandler");
|
||||
ioex.initCause(me);
|
||||
throw ioex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the object as a byte stream.
|
||||
*/
|
||||
@Override
|
||||
public void writeTo(Object obj, String mimeType, OutputStream os)
|
||||
throws IOException {
|
||||
if (!(obj instanceof Message))
|
||||
throw new IOException("\"" + getDataFlavors()[0].getMimeType() +
|
||||
"\" DataContentHandler requires Message object, " +
|
||||
"was given object of type " + obj.getClass().toString() +
|
||||
"; obj.cl " + obj.getClass().getClassLoader() +
|
||||
", Message.cl " + Message.class.getClassLoader());
|
||||
|
||||
// if the object is a message, we know how to write that out
|
||||
Message m = (Message)obj;
|
||||
try {
|
||||
m.writeTo(os);
|
||||
} catch (MessagingException me) {
|
||||
IOException ioex = new IOException("Exception writing message");
|
||||
ioex.initCause(me);
|
||||
throw ioex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.handlers;
|
||||
|
||||
import java.io.*;
|
||||
import javax.activation.*;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Multipart;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
|
||||
|
||||
public class multipart_mixed extends handler_base {
|
||||
private static ActivationDataFlavor[] myDF = {
|
||||
new ActivationDataFlavor(Multipart.class,
|
||||
"multipart/mixed", "Multipart")
|
||||
};
|
||||
|
||||
@Override
|
||||
protected ActivationDataFlavor[] getDataFlavors() {
|
||||
return myDF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content.
|
||||
*/
|
||||
@Override
|
||||
public Object getContent(DataSource ds) throws IOException {
|
||||
try {
|
||||
return new MimeMultipart(ds);
|
||||
} catch (MessagingException e) {
|
||||
IOException ioex =
|
||||
new IOException("Exception while constructing MimeMultipart");
|
||||
ioex.initCause(e);
|
||||
throw ioex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the object to the output stream, using the specific MIME type.
|
||||
*/
|
||||
@Override
|
||||
public void writeTo(Object obj, String mimeType, OutputStream os)
|
||||
throws IOException {
|
||||
if (!(obj instanceof Multipart))
|
||||
throw new IOException("\"" + getDataFlavors()[0].getMimeType() +
|
||||
"\" DataContentHandler requires Multipart object, " +
|
||||
"was given object of type " + obj.getClass().toString() +
|
||||
"; obj.cl " + obj.getClass().getClassLoader() +
|
||||
", Multipart.cl " + Multipart.class.getClassLoader());
|
||||
|
||||
try {
|
||||
((Multipart)obj).writeTo(os);
|
||||
} catch (MessagingException e) {
|
||||
IOException ioex =
|
||||
new IOException("Exception writing Multipart");
|
||||
ioex.initCause(e);
|
||||
throw ioex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<!--
|
||||
|
||||
Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
This program and the accompanying materials are made available under the
|
||||
terms of the Eclipse Public License v. 2.0, which is available at
|
||||
http://www.eclipse.org/legal/epl-2.0.
|
||||
|
||||
This Source Code may also be made available under the following Secondary
|
||||
Licenses when the conditions for such availability set forth in the
|
||||
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
version 2 with the GNU Classpath Exception, which is available at
|
||||
https://www.gnu.org/software/classpath/license.html.
|
||||
|
||||
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
|
||||
-->
|
||||
|
||||
<TITLE>com.sun.mail.handlers package</TITLE>
|
||||
</HEAD>
|
||||
<BODY BGCOLOR="white">
|
||||
|
||||
<P>
|
||||
This package includes internal data handler support classes and
|
||||
<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
|
||||
</P>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.handlers;
|
||||
|
||||
import javax.activation.ActivationDataFlavor;
|
||||
|
||||
/**
|
||||
* DataContentHandler for text/html.
|
||||
*
|
||||
*/
|
||||
public class text_html extends text_plain {
|
||||
private static ActivationDataFlavor[] myDF = {
|
||||
new ActivationDataFlavor(String.class, "text/html", "HTML String")
|
||||
};
|
||||
|
||||
@Override
|
||||
protected ActivationDataFlavor[] getDataFlavors() {
|
||||
return myDF;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.handlers;
|
||||
|
||||
import java.io.*;
|
||||
import javax.activation.*;
|
||||
import javax.mail.internet.ContentType;
|
||||
import javax.mail.internet.MimeUtility;
|
||||
|
||||
/**
|
||||
* DataContentHandler for text/plain.
|
||||
*
|
||||
*/
|
||||
public class text_plain extends handler_base {
|
||||
private static ActivationDataFlavor[] myDF = {
|
||||
new ActivationDataFlavor(String.class, "text/plain", "Text String")
|
||||
};
|
||||
|
||||
/**
|
||||
* An OuputStream wrapper that doesn't close the underlying stream.
|
||||
*/
|
||||
private static class NoCloseOutputStream extends FilterOutputStream {
|
||||
public NoCloseOutputStream(OutputStream os) {
|
||||
super(os);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActivationDataFlavor[] getDataFlavors() {
|
||||
return myDF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContent(DataSource ds) throws IOException {
|
||||
String enc = null;
|
||||
InputStreamReader is = null;
|
||||
|
||||
try {
|
||||
enc = getCharset(ds.getContentType());
|
||||
is = new InputStreamReader(ds.getInputStream(), enc);
|
||||
} catch (IllegalArgumentException iex) {
|
||||
/*
|
||||
* An unknown charset of the form ISO-XXX-XXX will cause
|
||||
* the JDK to throw an IllegalArgumentException. The
|
||||
* JDK will attempt to create a classname using this string,
|
||||
* but valid classnames must not contain the character '-',
|
||||
* and this results in an IllegalArgumentException, rather than
|
||||
* the expected UnsupportedEncodingException. Yikes.
|
||||
*/
|
||||
throw new UnsupportedEncodingException(enc);
|
||||
}
|
||||
|
||||
try {
|
||||
int pos = 0;
|
||||
int count;
|
||||
char buf[] = new char[1024];
|
||||
|
||||
while ((count = is.read(buf, pos, buf.length - pos)) != -1) {
|
||||
pos += count;
|
||||
if (pos >= buf.length) {
|
||||
int size = buf.length;
|
||||
if (size < 256*1024)
|
||||
size += size;
|
||||
else
|
||||
size += 256*1024;
|
||||
char tbuf[] = new char[size];
|
||||
System.arraycopy(buf, 0, tbuf, 0, pos);
|
||||
buf = tbuf;
|
||||
}
|
||||
}
|
||||
return new String(buf, 0, pos);
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ex) {
|
||||
// ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the object to the output stream, using the specified MIME type.
|
||||
*/
|
||||
@Override
|
||||
public void writeTo(Object obj, String type, OutputStream os)
|
||||
throws IOException {
|
||||
if (!(obj instanceof String))
|
||||
throw new IOException("\"" + getDataFlavors()[0].getMimeType() +
|
||||
"\" DataContentHandler requires String object, " +
|
||||
"was given object of type " + obj.getClass().toString());
|
||||
|
||||
String enc = null;
|
||||
OutputStreamWriter osw = null;
|
||||
|
||||
try {
|
||||
enc = getCharset(type);
|
||||
osw = new OutputStreamWriter(new NoCloseOutputStream(os), enc);
|
||||
} catch (IllegalArgumentException iex) {
|
||||
/*
|
||||
* An unknown charset of the form ISO-XXX-XXX will cause
|
||||
* the JDK to throw an IllegalArgumentException. The
|
||||
* JDK will attempt to create a classname using this string,
|
||||
* but valid classnames must not contain the character '-',
|
||||
* and this results in an IllegalArgumentException, rather than
|
||||
* the expected UnsupportedEncodingException. Yikes.
|
||||
*/
|
||||
throw new UnsupportedEncodingException(enc);
|
||||
}
|
||||
|
||||
String s = (String)obj;
|
||||
osw.write(s, 0, s.length());
|
||||
/*
|
||||
* Have to call osw.close() instead of osw.flush() because
|
||||
* some charset converts, such as the iso-2022-jp converter,
|
||||
* don't output the "shift out" sequence unless they're closed.
|
||||
* The NoCloseOutputStream wrapper prevents the underlying
|
||||
* stream from being closed.
|
||||
*/
|
||||
osw.close();
|
||||
}
|
||||
|
||||
private String getCharset(String type) {
|
||||
try {
|
||||
ContentType ct = new ContentType(type);
|
||||
String charset = ct.getParameter("charset");
|
||||
if (charset == null)
|
||||
// If the charset parameter is absent, use US-ASCII.
|
||||
charset = "us-ascii";
|
||||
return MimeUtility.javaCharset(charset);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.activation.ActivationDataFlavor;
|
||||
import javax.activation.DataSource;
|
||||
import javax.mail.internet.ContentType;
|
||||
import javax.mail.internet.ParseException;
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
/**
|
||||
* DataContentHandler for text/xml.
|
||||
*
|
||||
* @author Anil Vijendran
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
public class text_xml extends text_plain {
|
||||
|
||||
private static final ActivationDataFlavor[] flavors = {
|
||||
new ActivationDataFlavor(String.class, "text/xml", "XML String"),
|
||||
new ActivationDataFlavor(String.class, "application/xml", "XML String"),
|
||||
new ActivationDataFlavor(StreamSource.class, "text/xml", "XML"),
|
||||
new ActivationDataFlavor(StreamSource.class, "application/xml", "XML")
|
||||
};
|
||||
|
||||
@Override
|
||||
protected ActivationDataFlavor[] getDataFlavors() {
|
||||
return flavors;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getData(ActivationDataFlavor aFlavor, DataSource ds)
|
||||
throws IOException {
|
||||
if (aFlavor.getRepresentationClass() == String.class)
|
||||
return super.getContent(ds);
|
||||
else if (aFlavor.getRepresentationClass() == StreamSource.class)
|
||||
return new StreamSource(ds.getInputStream());
|
||||
else
|
||||
return null; // XXX - should never happen
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
@Override
|
||||
public void writeTo(Object obj, String mimeType, OutputStream os)
|
||||
throws IOException {
|
||||
if (!isXmlType(mimeType))
|
||||
throw new IOException(
|
||||
"Invalid content type \"" + mimeType + "\" for text/xml DCH");
|
||||
if (obj instanceof String) {
|
||||
super.writeTo(obj, mimeType, os);
|
||||
return;
|
||||
}
|
||||
if (!(obj instanceof DataSource || obj instanceof Source)) {
|
||||
throw new IOException("Invalid Object type = "+obj.getClass()+
|
||||
". XmlDCH can only convert DataSource or Source to XML.");
|
||||
}
|
||||
|
||||
try {
|
||||
Transformer transformer =
|
||||
TransformerFactory.newInstance().newTransformer();
|
||||
StreamResult result = new StreamResult(os);
|
||||
if (obj instanceof DataSource) {
|
||||
// Streaming transform applies only to
|
||||
// javax.xml.transform.StreamSource
|
||||
transformer.transform(
|
||||
new StreamSource(((DataSource)obj).getInputStream()),
|
||||
result);
|
||||
} else {
|
||||
transformer.transform((Source)obj, result);
|
||||
}
|
||||
} catch (TransformerException ex) {
|
||||
IOException ioex = new IOException(
|
||||
"Unable to run the JAXP transformer on a stream "
|
||||
+ ex.getMessage());
|
||||
ioex.initCause(ex);
|
||||
throw ioex;
|
||||
} catch (RuntimeException ex) {
|
||||
IOException ioex = new IOException(
|
||||
"Unable to run the JAXP transformer on a stream "
|
||||
+ ex.getMessage());
|
||||
ioex.initCause(ex);
|
||||
throw ioex;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isXmlType(String type) {
|
||||
try {
|
||||
ContentType ct = new ContentType(type);
|
||||
return ct.getSubType().equals("xml") &&
|
||||
(ct.getPrimaryType().equals("text") ||
|
||||
ct.getPrimaryType().equals("application"));
|
||||
} catch (ParseException ex) {
|
||||
return false;
|
||||
} catch (RuntimeException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,431 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import com.sun.mail.util.ASCIIUtility;
|
||||
|
||||
/**
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class Argument {
|
||||
protected List<Object> items;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public Argument() {
|
||||
items = new ArrayList<>(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given Argument to this Argument. All items
|
||||
* from the source argument are copied into this destination
|
||||
* argument.
|
||||
*
|
||||
* @param arg the Argument to append
|
||||
* @return this
|
||||
*/
|
||||
public Argument append(Argument arg) {
|
||||
items.addAll(arg.items);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given string as an ASTRING, depending on the type
|
||||
* of the characters inside the string. The string should
|
||||
* contain only ASCII characters. <p>
|
||||
*
|
||||
* XXX: Hmm .. this should really be called writeASCII()
|
||||
*
|
||||
* @param s String to write out
|
||||
* @return this
|
||||
*/
|
||||
public Argument writeString(String s) {
|
||||
items.add(new AString(ASCIIUtility.getBytes(s)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given string into bytes in the specified
|
||||
* charset, and write the bytes out as an ASTRING
|
||||
*
|
||||
* @param s String to write out
|
||||
* @param charset the charset
|
||||
* @return this
|
||||
* @exception UnsupportedEncodingException for bad charset
|
||||
*/
|
||||
public Argument writeString(String s, String charset)
|
||||
throws UnsupportedEncodingException {
|
||||
if (charset == null) // convenience
|
||||
writeString(s);
|
||||
else
|
||||
items.add(new AString(s.getBytes(charset)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given string into bytes in the specified
|
||||
* charset, and write the bytes out as an ASTRING
|
||||
*
|
||||
* @param s String to write out
|
||||
* @param charset the charset
|
||||
* @return this
|
||||
* @since JavaMail 1.6.0
|
||||
*/
|
||||
public Argument writeString(String s, Charset charset) {
|
||||
if (charset == null) // convenience
|
||||
writeString(s);
|
||||
else
|
||||
items.add(new AString(s.getBytes(charset)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given string as an NSTRING, depending on the type
|
||||
* of the characters inside the string. The string should
|
||||
* contain only ASCII characters. <p>
|
||||
*
|
||||
* @param s String to write out
|
||||
* @return this
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
public Argument writeNString(String s) {
|
||||
if (s == null)
|
||||
items.add(new NString(null));
|
||||
else
|
||||
items.add(new NString(ASCIIUtility.getBytes(s)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given string into bytes in the specified
|
||||
* charset, and write the bytes out as an NSTRING
|
||||
*
|
||||
* @param s String to write out
|
||||
* @param charset the charset
|
||||
* @return this
|
||||
* @exception UnsupportedEncodingException for bad charset
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
public Argument writeNString(String s, String charset)
|
||||
throws UnsupportedEncodingException {
|
||||
if (s == null)
|
||||
items.add(new NString(null));
|
||||
else if (charset == null) // convenience
|
||||
writeString(s);
|
||||
else
|
||||
items.add(new NString(s.getBytes(charset)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given string into bytes in the specified
|
||||
* charset, and write the bytes out as an NSTRING
|
||||
*
|
||||
* @param s String to write out
|
||||
* @param charset the charset
|
||||
* @return this
|
||||
* @since JavaMail 1.6.0
|
||||
*/
|
||||
public Argument writeNString(String s, Charset charset) {
|
||||
if (s == null)
|
||||
items.add(new NString(null));
|
||||
else if (charset == null) // convenience
|
||||
writeString(s);
|
||||
else
|
||||
items.add(new NString(s.getBytes(charset)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given byte[] as a Literal.
|
||||
* @param b byte[] to write out
|
||||
* @return this
|
||||
*/
|
||||
public Argument writeBytes(byte[] b) {
|
||||
items.add(b);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given ByteArrayOutputStream as a Literal.
|
||||
* @param b ByteArrayOutputStream to be written out.
|
||||
* @return this
|
||||
*/
|
||||
public Argument writeBytes(ByteArrayOutputStream b) {
|
||||
items.add(b);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given data as a literal.
|
||||
* @param b Literal representing data to be written out.
|
||||
* @return this
|
||||
*/
|
||||
public Argument writeBytes(Literal b) {
|
||||
items.add(b);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given string as an Atom. Note that an Atom can contain only
|
||||
* certain US-ASCII characters. No validation is done on the characters
|
||||
* in the string.
|
||||
* @param s String
|
||||
* @return this
|
||||
*/
|
||||
public Argument writeAtom(String s) {
|
||||
items.add(new Atom(s));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out number.
|
||||
* @param i number
|
||||
* @return this
|
||||
*/
|
||||
public Argument writeNumber(int i) {
|
||||
items.add(Integer.valueOf(i));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out number.
|
||||
* @param i number
|
||||
* @return this
|
||||
*/
|
||||
public Argument writeNumber(long i) {
|
||||
items.add(Long.valueOf(i));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out as parenthesised list.
|
||||
*
|
||||
* @param c the Argument
|
||||
* @return this
|
||||
*/
|
||||
public Argument writeArgument(Argument c) {
|
||||
items.add(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write out all the buffered items into the output stream.
|
||||
*/
|
||||
public void write(Protocol protocol)
|
||||
throws IOException, ProtocolException {
|
||||
int size = items != null ? items.size() : 0;
|
||||
DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
|
||||
|
||||
for (int i=0; i < size; i++) {
|
||||
if (i > 0) // write delimiter if not the first item
|
||||
os.write(' ');
|
||||
|
||||
Object o = items.get(i);
|
||||
if (o instanceof Atom) {
|
||||
os.writeBytes(((Atom)o).string);
|
||||
} else if (o instanceof Number) {
|
||||
os.writeBytes(((Number)o).toString());
|
||||
} else if (o instanceof AString) {
|
||||
astring(((AString)o).bytes, protocol);
|
||||
} else if (o instanceof NString) {
|
||||
nstring(((NString)o).bytes, protocol);
|
||||
} else if (o instanceof byte[]) {
|
||||
literal((byte[])o, protocol);
|
||||
} else if (o instanceof ByteArrayOutputStream) {
|
||||
literal((ByteArrayOutputStream)o, protocol);
|
||||
} else if (o instanceof Literal) {
|
||||
literal((Literal)o, protocol);
|
||||
} else if (o instanceof Argument) {
|
||||
os.write('('); // open parans
|
||||
((Argument)o).write(protocol);
|
||||
os.write(')'); // close parans
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given String as either an Atom, QuotedString or Literal
|
||||
*/
|
||||
private void astring(byte[] bytes, Protocol protocol)
|
||||
throws IOException, ProtocolException {
|
||||
nastring(bytes, protocol, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given String as either NIL, QuotedString, or Literal.
|
||||
*/
|
||||
private void nstring(byte[] bytes, Protocol protocol)
|
||||
throws IOException, ProtocolException {
|
||||
if (bytes == null) {
|
||||
DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
|
||||
os.writeBytes("NIL");
|
||||
} else
|
||||
nastring(bytes, protocol, true);
|
||||
}
|
||||
|
||||
private void nastring(byte[] bytes, Protocol protocol, boolean doQuote)
|
||||
throws IOException, ProtocolException {
|
||||
DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
|
||||
int len = bytes.length;
|
||||
|
||||
// If length is greater than 1024 bytes, send as literal
|
||||
if (len > 1024) {
|
||||
literal(bytes, protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
// if 0 length, send as quoted-string
|
||||
boolean quote = len == 0 ? true : doQuote;
|
||||
boolean escape = false;
|
||||
boolean utf8 = protocol.supportsUtf8();
|
||||
|
||||
byte b;
|
||||
for (int i = 0; i < len; i++) {
|
||||
b = bytes[i];
|
||||
if (b == '\0' || b == '\r' || b == '\n' ||
|
||||
(!utf8 && ((b & 0xff) > 0177))) {
|
||||
// NUL, CR or LF means the bytes need to be sent as literals
|
||||
literal(bytes, protocol);
|
||||
return;
|
||||
}
|
||||
if (b == '*' || b == '%' || b == '(' || b == ')' || b == '{' ||
|
||||
b == '"' || b == '\\' ||
|
||||
((b & 0xff) <= ' ') || ((b & 0xff) > 0177)) {
|
||||
quote = true;
|
||||
if (b == '"' || b == '\\') // need to escape these characters
|
||||
escape = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the (case-independent) string "NIL" is always quoted,
|
||||
* so as not to be confused with a real NIL (handled above in nstring).
|
||||
* This is more than is necessary, but it's rare to begin with and
|
||||
* this makes it safer than doing the test in nstring above in case
|
||||
* some code calls writeString when it should call writeNString.
|
||||
*/
|
||||
if (!quote && bytes.length == 3 &&
|
||||
(bytes[0] == 'N' || bytes[0] == 'n') &&
|
||||
(bytes[1] == 'I' || bytes[1] == 'i') &&
|
||||
(bytes[2] == 'L' || bytes[2] == 'l'))
|
||||
quote = true;
|
||||
|
||||
if (quote) // start quote
|
||||
os.write('"');
|
||||
|
||||
if (escape) {
|
||||
// already quoted
|
||||
for (int i = 0; i < len; i++) {
|
||||
b = bytes[i];
|
||||
if (b == '"' || b == '\\')
|
||||
os.write('\\');
|
||||
os.write(b);
|
||||
}
|
||||
} else
|
||||
os.write(bytes);
|
||||
|
||||
|
||||
if (quote) // end quote
|
||||
os.write('"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given byte[] as a literal
|
||||
*/
|
||||
private void literal(byte[] b, Protocol protocol)
|
||||
throws IOException, ProtocolException {
|
||||
startLiteral(protocol, b.length).write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given ByteArrayOutputStream as a literal.
|
||||
*/
|
||||
private void literal(ByteArrayOutputStream b, Protocol protocol)
|
||||
throws IOException, ProtocolException {
|
||||
b.writeTo(startLiteral(protocol, b.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out given Literal as a literal.
|
||||
*/
|
||||
private void literal(Literal b, Protocol protocol)
|
||||
throws IOException, ProtocolException {
|
||||
b.writeTo(startLiteral(protocol, b.size()));
|
||||
}
|
||||
|
||||
private OutputStream startLiteral(Protocol protocol, int size)
|
||||
throws IOException, ProtocolException {
|
||||
DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
|
||||
boolean nonSync = protocol.supportsNonSyncLiterals();
|
||||
|
||||
os.write('{');
|
||||
os.writeBytes(Integer.toString(size));
|
||||
if (nonSync) // server supports non-sync literals
|
||||
os.writeBytes("+}\r\n");
|
||||
else
|
||||
os.writeBytes("}\r\n");
|
||||
os.flush();
|
||||
|
||||
// If we are using synchronized literals, wait for the server's
|
||||
// continuation signal
|
||||
if (!nonSync) {
|
||||
for (; ;) {
|
||||
Response r = protocol.readResponse();
|
||||
if (r.isContinuation())
|
||||
break;
|
||||
if (r.isTagged())
|
||||
throw new LiteralException(r);
|
||||
// XXX - throw away untagged responses;
|
||||
// violates IMAP spec, hope no servers do this
|
||||
}
|
||||
}
|
||||
return os;
|
||||
}
|
||||
}
|
||||
|
||||
class Atom {
|
||||
String string;
|
||||
|
||||
Atom(String s) {
|
||||
string = s;
|
||||
}
|
||||
}
|
||||
|
||||
class AString {
|
||||
byte[] bytes;
|
||||
|
||||
AString(byte[] b) {
|
||||
bytes = b;
|
||||
}
|
||||
}
|
||||
|
||||
class NString {
|
||||
byte[] bytes;
|
||||
|
||||
NString(byte[] b) {
|
||||
bytes = b;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
/**
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class BadCommandException extends ProtocolException {
|
||||
|
||||
private static final long serialVersionUID = 5769722539397237515L;
|
||||
|
||||
/**
|
||||
* Constructs an BadCommandException with no detail message.
|
||||
*/
|
||||
public BadCommandException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an BadCommandException with the specified detail message.
|
||||
* @param s the detail message
|
||||
*/
|
||||
public BadCommandException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an BadCommandException with the specified Response.
|
||||
* @param r the Response
|
||||
*/
|
||||
public BadCommandException(Response r) {
|
||||
super(r);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
/**
|
||||
* A simple wrapper around a byte array, with a start position and
|
||||
* count of bytes.
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class ByteArray {
|
||||
private byte[] bytes; // the byte array
|
||||
private int start; // start position
|
||||
private int count; // count of bytes
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param b the byte array to wrap
|
||||
* @param start start position in byte array
|
||||
* @param count number of bytes in byte array
|
||||
*/
|
||||
public ByteArray(byte[] b, int start, int count) {
|
||||
bytes = b;
|
||||
this.start = start;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that creates a byte array of the specified size.
|
||||
*
|
||||
* @param size the size of the ByteArray
|
||||
* @since JavaMail 1.4.1
|
||||
*/
|
||||
public ByteArray(int size) {
|
||||
this(new byte[size], 0, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal byte array. Note that this is a live
|
||||
* reference to the actual data, not a copy.
|
||||
*
|
||||
* @return the wrapped byte array
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new byte array that is a copy of the data.
|
||||
*
|
||||
* @return a new byte array with the bytes from start for count
|
||||
*/
|
||||
public byte[] getNewBytes() {
|
||||
byte[] b = new byte[count];
|
||||
System.arraycopy(bytes, start, b, 0, count);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start position
|
||||
*
|
||||
* @return the start position
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of bytes
|
||||
*
|
||||
* @return the number of bytes
|
||||
*/
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the count of bytes.
|
||||
*
|
||||
* @param count the number of bytes
|
||||
* @since JavaMail 1.4.1
|
||||
*/
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ByteArrayInputStream.
|
||||
*
|
||||
* @return the ByteArrayInputStream
|
||||
*/
|
||||
public ByteArrayInputStream toByteArrayInputStream() {
|
||||
return new ByteArrayInputStream(bytes, start, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grow the byte array by incr bytes.
|
||||
*
|
||||
* @param incr how much to grow
|
||||
* @since JavaMail 1.4.1
|
||||
*/
|
||||
public void grow(int incr) {
|
||||
byte[] nbuf = new byte[bytes.length + incr];
|
||||
System.arraycopy(bytes, 0, nbuf, 0, bytes.length);
|
||||
bytes = nbuf;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
/**
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class CommandFailedException extends ProtocolException {
|
||||
|
||||
private static final long serialVersionUID = 793932807880443631L;
|
||||
|
||||
/**
|
||||
* Constructs an CommandFailedException with no detail message.
|
||||
*/
|
||||
public CommandFailedException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an CommandFailedException with the specified detail message.
|
||||
* @param s the detail message
|
||||
*/
|
||||
public CommandFailedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an CommandFailedException with the specified Response.
|
||||
* @param r the Response.
|
||||
*/
|
||||
public CommandFailedException(Response r) {
|
||||
super(r);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
/**
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class ConnectionException extends ProtocolException {
|
||||
private transient Protocol p;
|
||||
|
||||
private static final long serialVersionUID = 5749739604257464727L;
|
||||
|
||||
/**
|
||||
* Constructs an ConnectionException with no detail message.
|
||||
*/
|
||||
public ConnectionException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ConnectionException with the specified detail message.
|
||||
*
|
||||
* @param s the detail message
|
||||
*/
|
||||
public ConnectionException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ConnectionException with the specified Response.
|
||||
*
|
||||
* @param p the Protocol object
|
||||
* @param r the Response
|
||||
*/
|
||||
public ConnectionException(Protocol p, Response r) {
|
||||
super(r);
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
public Protocol getProtocol() {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* An interface for objects that provide data dynamically for use in
|
||||
* a literal protocol element.
|
||||
*
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public interface Literal {
|
||||
/**
|
||||
* Return the size of the data.
|
||||
*
|
||||
* @return the size of the data
|
||||
*/
|
||||
public int size();
|
||||
|
||||
/**
|
||||
* Write the data to the OutputStream.
|
||||
*
|
||||
* @param os the output stream
|
||||
* @exception IOException for I/O errors
|
||||
*/
|
||||
public void writeTo(OutputStream os) throws IOException;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
/**
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class LiteralException extends ProtocolException {
|
||||
|
||||
private static final long serialVersionUID = -6919179828339609913L;
|
||||
|
||||
/**
|
||||
* Constructs a LiteralException with the specified Response object.
|
||||
*
|
||||
* @param r the response object
|
||||
*/
|
||||
public LiteralException(Response r) {
|
||||
super(r.toString());
|
||||
response = r;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
/**
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class ParsingException extends ProtocolException {
|
||||
|
||||
private static final long serialVersionUID = 7756119840142724839L;
|
||||
|
||||
/**
|
||||
* Constructs an ParsingException with no detail message.
|
||||
*/
|
||||
public ParsingException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ParsingException with the specified detail message.
|
||||
* @param s the detail message
|
||||
*/
|
||||
public ParsingException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ParsingException with the specified Response.
|
||||
* @param r the Response
|
||||
*/
|
||||
public ParsingException(Response r) {
|
||||
super(r);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,682 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.io.*;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.*;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.sun.mail.util.PropUtil;
|
||||
import com.sun.mail.util.MailLogger;
|
||||
import com.sun.mail.util.SocketFetcher;
|
||||
import com.sun.mail.util.TraceInputStream;
|
||||
import com.sun.mail.util.TraceOutputStream;
|
||||
|
||||
/**
|
||||
* General protocol handling code for IMAP-like protocols. <p>
|
||||
*
|
||||
* The Protocol object is multithread safe.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Max Spivak
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class Protocol {
|
||||
protected String host;
|
||||
private Socket socket;
|
||||
// in case we turn on TLS, we'll need these later
|
||||
protected boolean quote;
|
||||
protected MailLogger logger;
|
||||
protected MailLogger traceLogger;
|
||||
protected Properties props;
|
||||
protected String prefix;
|
||||
|
||||
private TraceInputStream traceInput; // the Tracer
|
||||
private volatile ResponseInputStream input;
|
||||
|
||||
private TraceOutputStream traceOutput; // the Tracer
|
||||
private volatile DataOutputStream output;
|
||||
|
||||
private int tagCounter = 0;
|
||||
private final String tagPrefix;
|
||||
|
||||
private String localHostName;
|
||||
|
||||
private final List<ResponseHandler> handlers
|
||||
= new CopyOnWriteArrayList<>();
|
||||
|
||||
private volatile long timestamp;
|
||||
|
||||
// package private, to allow testing
|
||||
static final AtomicInteger tagNum = new AtomicInteger();
|
||||
|
||||
private static final byte[] CRLF = { (byte)'\r', (byte)'\n'};
|
||||
|
||||
/**
|
||||
* Constructor. <p>
|
||||
*
|
||||
* Opens a connection to the given host at given port.
|
||||
*
|
||||
* @param host host to connect to
|
||||
* @param port portnumber to connect to
|
||||
* @param props Properties object used by this protocol
|
||||
* @param prefix Prefix to prepend to property keys
|
||||
* @param isSSL use SSL?
|
||||
* @param logger log messages here
|
||||
* @exception IOException for I/O errors
|
||||
* @exception ProtocolException for protocol failures
|
||||
*/
|
||||
public Protocol(String host, int port,
|
||||
Properties props, String prefix,
|
||||
boolean isSSL, MailLogger logger)
|
||||
throws IOException, ProtocolException {
|
||||
boolean connected = false; // did constructor succeed?
|
||||
tagPrefix = computePrefix(props, prefix);
|
||||
try {
|
||||
this.host = host;
|
||||
this.props = props;
|
||||
this.prefix = prefix;
|
||||
this.logger = logger;
|
||||
traceLogger = logger.getSubLogger("protocol", null);
|
||||
|
||||
socket = SocketFetcher.getSocket(host, port, props, prefix, isSSL);
|
||||
quote = PropUtil.getBooleanProperty(props,
|
||||
"mail.debug.quote", false);
|
||||
|
||||
initStreams();
|
||||
|
||||
// Read server greeting
|
||||
processGreeting(readResponse());
|
||||
|
||||
timestamp = System.currentTimeMillis();
|
||||
|
||||
connected = true; // must be last statement in constructor
|
||||
} finally {
|
||||
/*
|
||||
* If we get here because an exception was thrown, we need
|
||||
* to disconnect to avoid leaving a connected socket that
|
||||
* no one will be able to use because this object was never
|
||||
* completely constructed.
|
||||
*/
|
||||
if (!connected)
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void initStreams() throws IOException {
|
||||
traceInput = new TraceInputStream(socket.getInputStream(), traceLogger);
|
||||
traceInput.setQuote(quote);
|
||||
input = new ResponseInputStream(traceInput);
|
||||
|
||||
traceOutput =
|
||||
new TraceOutputStream(socket.getOutputStream(), traceLogger);
|
||||
traceOutput.setQuote(quote);
|
||||
output = new DataOutputStream(new BufferedOutputStream(traceOutput));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the tag prefix to be used for this connection.
|
||||
* Start with "A" - "Z", then "AA" - "ZZ", and finally "AAA" - "ZZZ".
|
||||
* Wrap around after that.
|
||||
*/
|
||||
private String computePrefix(Properties props, String prefix) {
|
||||
// XXX - in case someone depends on the tag prefix
|
||||
if (PropUtil.getBooleanProperty(props,
|
||||
prefix + ".reusetagprefix", false))
|
||||
return "A";
|
||||
// tag prefix, wrap around after three letters
|
||||
int n = tagNum.getAndIncrement() % (26*26*26 + 26*26 + 26);
|
||||
String tagPrefix;
|
||||
if (n < 26)
|
||||
tagPrefix = new String(new char[] { (char)('A' + n) });
|
||||
else if (n < (26*26 + 26)) {
|
||||
n -= 26;
|
||||
tagPrefix = new String(new char[] {
|
||||
(char)('A' + n/26), (char)('A' + n%26) });
|
||||
} else {
|
||||
n -= (26*26 + 26);
|
||||
tagPrefix = new String(new char[] {
|
||||
(char)('A' + n/(26*26)),
|
||||
(char)('A' + (n%(26*26))/26),
|
||||
(char)('A' + n%26) });
|
||||
}
|
||||
return tagPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for debugging.
|
||||
*
|
||||
* @param in the InputStream to read from
|
||||
* @param out the PrintStream to write to
|
||||
* @param props Properties object used by this protocol
|
||||
* @param debug true to enable debugging output
|
||||
* @exception IOException for I/O errors
|
||||
*/
|
||||
public Protocol(InputStream in, PrintStream out, Properties props,
|
||||
boolean debug) throws IOException {
|
||||
this.host = "localhost";
|
||||
this.props = props;
|
||||
this.quote = false;
|
||||
tagPrefix = computePrefix(props, "mail.imap");
|
||||
logger = new MailLogger(this.getClass(), "DEBUG", debug, System.out);
|
||||
traceLogger = logger.getSubLogger("protocol", null);
|
||||
|
||||
// XXX - inlined initStreams, won't allow later startTLS
|
||||
traceInput = new TraceInputStream(in, traceLogger);
|
||||
traceInput.setQuote(quote);
|
||||
input = new ResponseInputStream(traceInput);
|
||||
|
||||
traceOutput = new TraceOutputStream(out, traceLogger);
|
||||
traceOutput.setQuote(quote);
|
||||
output = new DataOutputStream(new BufferedOutputStream(traceOutput));
|
||||
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp.
|
||||
*
|
||||
* @return the timestamp
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a response handler.
|
||||
*
|
||||
* @param h the response handler
|
||||
*/
|
||||
public void addResponseHandler(ResponseHandler h) {
|
||||
handlers.add(h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed the specified response handler.
|
||||
*
|
||||
* @param h the response handler
|
||||
*/
|
||||
public void removeResponseHandler(ResponseHandler h) {
|
||||
handlers.remove(h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify response handlers
|
||||
*
|
||||
* @param responses the responses
|
||||
*/
|
||||
public void notifyResponseHandlers(Response[] responses) {
|
||||
if (handlers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Response r : responses) {
|
||||
if (r != null) {
|
||||
for (ResponseHandler rh : handlers) {
|
||||
if (rh != null) {
|
||||
rh.handleResponse(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void processGreeting(Response r) throws ProtocolException {
|
||||
if (r.isBYE())
|
||||
throw new ConnectionException(this, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Protocol's InputStream.
|
||||
*
|
||||
* @return the input stream
|
||||
*/
|
||||
protected ResponseInputStream getInputStream() {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Protocol's OutputStream
|
||||
*
|
||||
* @return the output stream
|
||||
*/
|
||||
protected OutputStream getOutputStream() {
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this Protocol supports non-synchronizing literals
|
||||
* Default is false. Subclasses should override this if required
|
||||
*
|
||||
* @return true if the server supports non-synchronizing literals
|
||||
*/
|
||||
protected synchronized boolean supportsNonSyncLiterals() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public Response readResponse()
|
||||
throws IOException, ProtocolException {
|
||||
return new Response(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is another response available in our buffer?
|
||||
*
|
||||
* @return true if another response is in the buffer
|
||||
* @since JavaMail 1.5.4
|
||||
*/
|
||||
public boolean hasResponse() {
|
||||
/*
|
||||
* XXX - Really should peek ahead in the buffer to see
|
||||
* if there's a *complete* response available, but if there
|
||||
* isn't who's going to read more data into the buffer
|
||||
* until there is?
|
||||
*/
|
||||
try {
|
||||
return input.available() > 0;
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a buffer to be used to read a response.
|
||||
* The default implementation returns null, which causes
|
||||
* a new buffer to be allocated for every response.
|
||||
*
|
||||
* @return the buffer to use
|
||||
* @since JavaMail 1.4.1
|
||||
*/
|
||||
protected ByteArray getResponseBuffer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String writeCommand(String command, Argument args)
|
||||
throws IOException, ProtocolException {
|
||||
// assert Thread.holdsLock(this);
|
||||
// can't assert because it's called from constructor
|
||||
String tag = tagPrefix + Integer.toString(tagCounter++); // unique tag
|
||||
|
||||
output.writeBytes(tag + " " + command);
|
||||
|
||||
if (args != null) {
|
||||
output.write(' ');
|
||||
args.write(this);
|
||||
}
|
||||
|
||||
output.write(CRLF);
|
||||
output.flush();
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to the server. Collect all responses until either
|
||||
* the corresponding command completion response or a BYE response
|
||||
* (indicating server failure). Return all the collected responses.
|
||||
*
|
||||
* @param command the command
|
||||
* @param args the arguments
|
||||
* @return array of Response objects returned by the server
|
||||
*/
|
||||
public synchronized Response[] command(String command, Argument args) {
|
||||
commandStart(command);
|
||||
List<Response> v = new ArrayList<>();
|
||||
boolean done = false;
|
||||
String tag = null;
|
||||
|
||||
// write the command
|
||||
try {
|
||||
tag = writeCommand(command, args);
|
||||
} catch (LiteralException lex) {
|
||||
v.add(lex.getResponse());
|
||||
done = true;
|
||||
} catch (Exception ex) {
|
||||
// Convert this into a BYE response
|
||||
v.add(Response.byeResponse(ex));
|
||||
done = true;
|
||||
}
|
||||
|
||||
Response byeResp = null;
|
||||
while (!done) {
|
||||
Response r = null;
|
||||
try {
|
||||
r = readResponse();
|
||||
} catch (IOException ioex) {
|
||||
if (byeResp == null) // convert this into a BYE response
|
||||
byeResp = Response.byeResponse(ioex);
|
||||
// else, connection closed after BYE was sent
|
||||
break;
|
||||
} catch (ProtocolException pex) {
|
||||
logger.log(Level.FINE, "ignoring bad response", pex);
|
||||
continue; // skip this response
|
||||
}
|
||||
|
||||
if (r.isBYE()) {
|
||||
byeResp = r;
|
||||
continue;
|
||||
}
|
||||
|
||||
v.add(r);
|
||||
|
||||
// If this is a matching command completion response, we are done
|
||||
if (r.isTagged() && r.getTag().equals(tag))
|
||||
done = true;
|
||||
}
|
||||
|
||||
if (byeResp != null)
|
||||
v.add(byeResp); // must be last
|
||||
Response[] responses = new Response[v.size()];
|
||||
v.toArray(responses);
|
||||
timestamp = System.currentTimeMillis();
|
||||
commandEnd();
|
||||
return responses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience routine to handle OK, NO, BAD and BYE responses.
|
||||
*
|
||||
* @param response the response
|
||||
* @exception ProtocolException for protocol failures
|
||||
*/
|
||||
public void handleResult(Response response) throws ProtocolException {
|
||||
if (response.isOK())
|
||||
return;
|
||||
else if (response.isNO())
|
||||
throw new CommandFailedException(response);
|
||||
else if (response.isBAD())
|
||||
throw new BadCommandException(response);
|
||||
else if (response.isBYE()) {
|
||||
disconnect();
|
||||
throw new ConnectionException(this, response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience routine to handle simple IAP commands
|
||||
* that do not have responses specific to that command.
|
||||
*
|
||||
* @param cmd the command
|
||||
* @param args the arguments
|
||||
* @exception ProtocolException for protocol failures
|
||||
*/
|
||||
public void simpleCommand(String cmd, Argument args)
|
||||
throws ProtocolException {
|
||||
// Issue command
|
||||
Response[] r = command(cmd, args);
|
||||
|
||||
// dispatch untagged responses
|
||||
notifyResponseHandlers(r);
|
||||
|
||||
// Handle result of this command
|
||||
handleResult(r[r.length-1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start TLS on the current connection.
|
||||
* <code>cmd</code> is the command to issue to start TLS negotiation.
|
||||
* If the command succeeds, we begin TLS negotiation.
|
||||
* If the socket is already an SSLSocket this is a nop and the command
|
||||
* is not issued.
|
||||
*
|
||||
* @param cmd the command to issue
|
||||
* @exception IOException for I/O errors
|
||||
* @exception ProtocolException for protocol failures
|
||||
*/
|
||||
public synchronized void startTLS(String cmd)
|
||||
throws IOException, ProtocolException {
|
||||
if (socket instanceof SSLSocket)
|
||||
return; // nothing to do
|
||||
simpleCommand(cmd, null);
|
||||
socket = SocketFetcher.startTLS(socket, host, props, prefix);
|
||||
initStreams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start compression on the current connection.
|
||||
* <code>cmd</code> is the command to issue to start compression.
|
||||
* If the command succeeds, we begin compression.
|
||||
*
|
||||
* @param cmd the command to issue
|
||||
* @exception IOException for I/O errors
|
||||
* @exception ProtocolException for protocol failures
|
||||
*/
|
||||
public synchronized void startCompression(String cmd)
|
||||
throws IOException, ProtocolException {
|
||||
// XXX - check whether compression is already enabled?
|
||||
simpleCommand(cmd, null);
|
||||
|
||||
// need to create our own Inflater and Deflater in order to set nowrap
|
||||
Inflater inf = new Inflater(true);
|
||||
traceInput = new TraceInputStream(new InflaterInputStream(
|
||||
socket.getInputStream(), inf), traceLogger);
|
||||
traceInput.setQuote(quote);
|
||||
input = new ResponseInputStream(traceInput);
|
||||
|
||||
// configure the Deflater
|
||||
int level = PropUtil.getIntProperty(props, prefix + ".compress.level",
|
||||
Deflater.DEFAULT_COMPRESSION);
|
||||
int strategy = PropUtil.getIntProperty(props,
|
||||
prefix + ".compress.strategy",
|
||||
Deflater.DEFAULT_STRATEGY);
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.log(Level.FINE,
|
||||
"Creating Deflater with compression level {0} and strategy {1}",
|
||||
new Object[] { level, strategy });
|
||||
Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
|
||||
try {
|
||||
def.setLevel(level);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
logger.log(Level.FINE, "Ignoring bad compression level", ex);
|
||||
}
|
||||
try {
|
||||
def.setStrategy(strategy);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
logger.log(Level.FINE, "Ignoring bad compression strategy", ex);
|
||||
}
|
||||
traceOutput = new TraceOutputStream(new DeflaterOutputStream(
|
||||
socket.getOutputStream(), def, true), traceLogger);
|
||||
traceOutput.setQuote(quote);
|
||||
output = new DataOutputStream(new BufferedOutputStream(traceOutput));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this connection using an SSL socket?
|
||||
*
|
||||
* @return true if using SSL
|
||||
* @since JavaMail 1.4.6
|
||||
*/
|
||||
public boolean isSSL() {
|
||||
return socket instanceof SSLSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the address the socket connected to.
|
||||
*
|
||||
* @return the InetAddress the socket is connected to
|
||||
* @since JavaMail 1.5.2
|
||||
*/
|
||||
public InetAddress getInetAddress() {
|
||||
return socket.getInetAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SocketChannel associated with this connection, if any.
|
||||
*
|
||||
* @return the SocketChannel
|
||||
* @since JavaMail 1.5.2
|
||||
*/
|
||||
public SocketChannel getChannel() {
|
||||
SocketChannel ret = socket.getChannel();
|
||||
if (ret != null)
|
||||
return ret;
|
||||
|
||||
// XXX - Android is broken and SSL wrapped sockets don't delegate
|
||||
// the getChannel method to the wrapped Socket
|
||||
if (socket instanceof SSLSocket) {
|
||||
try {
|
||||
Field f = socket.getClass().getDeclaredField("socket");
|
||||
f.setAccessible(true);
|
||||
Socket s = (Socket)f.get(socket);
|
||||
ret = s.getChannel();
|
||||
} catch (Exception ex) {
|
||||
// ignore anything that might go wrong
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the local SocketAddress (host and port) for this
|
||||
* end of the connection.
|
||||
*
|
||||
* @return the SocketAddress
|
||||
* @since Jakarta Mail 1.6.4
|
||||
*/
|
||||
public SocketAddress getLocalSocketAddress() {
|
||||
return socket.getLocalSocketAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the server support UTF-8?
|
||||
* This implementation returns false.
|
||||
* Subclasses should override as appropriate.
|
||||
*
|
||||
* @return true if the server supports UTF-8
|
||||
* @since JavaMail 1.6.0
|
||||
*/
|
||||
public boolean supportsUtf8() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect.
|
||||
*/
|
||||
protected synchronized void disconnect() {
|
||||
if (socket != null) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
// ignore it
|
||||
}
|
||||
socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the local host.
|
||||
* The property <prefix>.localhost overrides
|
||||
* <prefix>.localaddress,
|
||||
* which overrides what InetAddress would tell us.
|
||||
*
|
||||
* @return the name of the local host
|
||||
*/
|
||||
protected synchronized String getLocalHost() {
|
||||
// get our hostname and cache it for future use
|
||||
if (localHostName == null || localHostName.length() <= 0)
|
||||
localHostName =
|
||||
props.getProperty(prefix + ".localhost");
|
||||
if (localHostName == null || localHostName.length() <= 0)
|
||||
localHostName =
|
||||
props.getProperty(prefix + ".localaddress");
|
||||
try {
|
||||
if (localHostName == null || localHostName.length() <= 0) {
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
localHostName = localHost.getCanonicalHostName();
|
||||
// if we can't get our name, use local address literal
|
||||
if (localHostName == null)
|
||||
// XXX - not correct for IPv6
|
||||
localHostName = "[" + localHost.getHostAddress() + "]";
|
||||
}
|
||||
} catch (UnknownHostException uhex) {
|
||||
}
|
||||
|
||||
// last chance, try to get our address from our socket
|
||||
if (localHostName == null || localHostName.length() <= 0) {
|
||||
if (socket != null && socket.isBound()) {
|
||||
InetAddress localHost = socket.getLocalAddress();
|
||||
localHostName = localHost.getCanonicalHostName();
|
||||
// if we can't get our name, use local address literal
|
||||
if (localHostName == null)
|
||||
// XXX - not correct for IPv6
|
||||
localHostName = "[" + localHost.getHostAddress() + "]";
|
||||
}
|
||||
}
|
||||
return localHostName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is protocol tracing enabled?
|
||||
*
|
||||
* @return true if protocol tracing is enabled
|
||||
*/
|
||||
protected boolean isTracing() {
|
||||
return traceLogger.isLoggable(Level.FINEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily turn off protocol tracing, e.g., to prevent
|
||||
* tracing the authentication sequence, including the password.
|
||||
*/
|
||||
protected void suspendTracing() {
|
||||
if (traceLogger.isLoggable(Level.FINEST)) {
|
||||
traceInput.setTrace(false);
|
||||
traceOutput.setTrace(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume protocol tracing, if it was enabled to begin with.
|
||||
*/
|
||||
protected void resumeTracing() {
|
||||
if (traceLogger.isLoggable(Level.FINEST)) {
|
||||
traceInput.setTrace(true);
|
||||
traceOutput.setTrace(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizer.
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
disconnect();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Probe points for GlassFish monitoring.
|
||||
*/
|
||||
private void commandStart(String command) { }
|
||||
private void commandEnd() { }
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
/**
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class ProtocolException extends Exception {
|
||||
protected transient Response response = null;
|
||||
|
||||
private static final long serialVersionUID = -4360500807971797439L;
|
||||
|
||||
/**
|
||||
* Constructs a ProtocolException with no detail message.
|
||||
*/
|
||||
public ProtocolException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ProtocolException with the specified detail message.
|
||||
*
|
||||
* @param message the detail message
|
||||
*/
|
||||
public ProtocolException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ProtocolException with the specified detail message
|
||||
* and cause.
|
||||
*
|
||||
* @param message the detail message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public ProtocolException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ProtocolException with the specified Response object.
|
||||
*
|
||||
* @param r the Response
|
||||
*/
|
||||
public ProtocolException(Response r) {
|
||||
super(r.toString());
|
||||
response = r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the offending Response object.
|
||||
*
|
||||
* @return the Response object
|
||||
*/
|
||||
public Response getResponse() {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,600 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import com.sun.mail.util.ASCIIUtility;
|
||||
|
||||
/**
|
||||
* This class represents a response obtained from the input stream
|
||||
* of an IMAP server.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class Response {
|
||||
protected int index; // internal index (updated during the parse)
|
||||
protected int pindex; // index after parse, for reset
|
||||
protected int size; // number of valid bytes in our buffer
|
||||
protected byte[] buffer = null;
|
||||
protected int type = 0;
|
||||
protected String tag = null;
|
||||
/** @since JavaMail 1.5.4 */
|
||||
protected Exception ex;
|
||||
protected boolean utf8;
|
||||
|
||||
private static final int increment = 100;
|
||||
|
||||
// The first and second bits indicate whether this response
|
||||
// is a Continuation, Tagged or Untagged
|
||||
public final static int TAG_MASK = 0x03;
|
||||
public final static int CONTINUATION = 0x01;
|
||||
public final static int TAGGED = 0x02;
|
||||
public final static int UNTAGGED = 0x03;
|
||||
|
||||
// The third, fourth and fifth bits indicate whether this response
|
||||
// is an OK, NO, BAD or BYE response
|
||||
public final static int TYPE_MASK = 0x1C;
|
||||
public final static int OK = 0x04;
|
||||
public final static int NO = 0x08;
|
||||
public final static int BAD = 0x0C;
|
||||
public final static int BYE = 0x10;
|
||||
|
||||
// The sixth bit indicates whether a BYE response is synthetic or real
|
||||
public final static int SYNTHETIC = 0x20;
|
||||
|
||||
/**
|
||||
* An ATOM is any CHAR delimited by:
|
||||
* SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\' | ']'
|
||||
* (CTL is handled in readDelimString.)
|
||||
*/
|
||||
private static String ATOM_CHAR_DELIM = " (){%*\"\\]";
|
||||
|
||||
/**
|
||||
* An ASTRING_CHAR is any CHAR delimited by:
|
||||
* SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\'
|
||||
* (CTL is handled in readDelimString.)
|
||||
*/
|
||||
private static String ASTRING_CHAR_DELIM = " (){%*\"\\";
|
||||
|
||||
public Response(String s) {
|
||||
this(s, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for testing.
|
||||
*
|
||||
* @param s the response string
|
||||
* @param supportsUtf8 allow UTF-8 in response?
|
||||
* @since JavaMail 1.6.0
|
||||
*/
|
||||
public Response(String s, boolean supportsUtf8) {
|
||||
if (supportsUtf8)
|
||||
buffer = s.getBytes(StandardCharsets.UTF_8);
|
||||
else
|
||||
buffer = s.getBytes(StandardCharsets.US_ASCII);
|
||||
size = buffer.length;
|
||||
utf8 = supportsUtf8;
|
||||
parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a new Response from the given Protocol
|
||||
*
|
||||
* @param p the Protocol object
|
||||
* @exception IOException for I/O errors
|
||||
* @exception ProtocolException for protocol failures
|
||||
*/
|
||||
public Response(Protocol p) throws IOException, ProtocolException {
|
||||
// read one response into 'buffer'
|
||||
ByteArray ba = p.getResponseBuffer();
|
||||
ByteArray response = p.getInputStream().readResponse(ba);
|
||||
buffer = response.getBytes();
|
||||
size = response.getCount() - 2; // Skip the terminating CRLF
|
||||
utf8 = p.supportsUtf8();
|
||||
|
||||
parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
*
|
||||
* @param r the Response to copy
|
||||
*/
|
||||
public Response(Response r) {
|
||||
index = r.index;
|
||||
pindex = r.pindex;
|
||||
size = r.size;
|
||||
buffer = r.buffer;
|
||||
type = r.type;
|
||||
tag = r.tag;
|
||||
ex = r.ex;
|
||||
utf8 = r.utf8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Response object that looks like a BYE protocol response.
|
||||
* Include the details of the exception in the response string.
|
||||
*
|
||||
* @param ex the exception
|
||||
* @return the synthetic Response object
|
||||
*/
|
||||
public static Response byeResponse(Exception ex) {
|
||||
String err = "* BYE Jakarta Mail Exception: " + ex.toString();
|
||||
err = err.replace('\r', ' ').replace('\n', ' ');
|
||||
Response r = new Response(err);
|
||||
r.type |= SYNTHETIC;
|
||||
r.ex = ex;
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the server support UTF-8?
|
||||
*
|
||||
* @return true if the server supports UTF-8
|
||||
* @since JavaMail 1.6.0
|
||||
*/
|
||||
public boolean supportsUtf8() {
|
||||
return utf8;
|
||||
}
|
||||
|
||||
private void parse() {
|
||||
index = 0; // position internal index at start
|
||||
|
||||
if (size == 0) // empty line
|
||||
return;
|
||||
if (buffer[index] == '+') { // Continuation statement
|
||||
type |= CONTINUATION;
|
||||
index += 1; // Position beyond the '+'
|
||||
return; // return
|
||||
} else if (buffer[index] == '*') { // Untagged statement
|
||||
type |= UNTAGGED;
|
||||
index += 1; // Position beyond the '*'
|
||||
} else { // Tagged statement
|
||||
type |= TAGGED;
|
||||
tag = readAtom(); // read the TAG, index positioned beyond tag
|
||||
if (tag == null)
|
||||
tag = ""; // avoid possible NPE
|
||||
}
|
||||
|
||||
int mark = index; // mark
|
||||
String s = readAtom(); // updates index
|
||||
if (s == null)
|
||||
s = ""; // avoid possible NPE
|
||||
if (s.equalsIgnoreCase("OK"))
|
||||
type |= OK;
|
||||
else if (s.equalsIgnoreCase("NO"))
|
||||
type |= NO;
|
||||
else if (s.equalsIgnoreCase("BAD"))
|
||||
type |= BAD;
|
||||
else if (s.equalsIgnoreCase("BYE"))
|
||||
type |= BYE;
|
||||
else
|
||||
index = mark; // reset
|
||||
|
||||
pindex = index;
|
||||
return;
|
||||
}
|
||||
|
||||
public void skipSpaces() {
|
||||
while (index < size && buffer[index] == ' ')
|
||||
index++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip past any spaces. If the next non-space character is c,
|
||||
* consume it and return true. Otherwise stop at that point
|
||||
* and return false.
|
||||
*
|
||||
* @param c the character to look for
|
||||
* @return true if the character is found
|
||||
*/
|
||||
public boolean isNextNonSpace(char c) {
|
||||
skipSpaces();
|
||||
if (index < size && buffer[index] == (byte)c) {
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip to the next space, for use in error recovery while parsing.
|
||||
*/
|
||||
public void skipToken() {
|
||||
while (index < size && buffer[index] != ' ')
|
||||
index++;
|
||||
}
|
||||
|
||||
public void skip(int count) {
|
||||
index += count;
|
||||
}
|
||||
|
||||
public byte peekByte() {
|
||||
if (index < size)
|
||||
return buffer[index];
|
||||
else
|
||||
return 0; // XXX - how else to signal error?
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next byte from this Statement.
|
||||
*
|
||||
* @return the next byte
|
||||
*/
|
||||
public byte readByte() {
|
||||
if (index < size)
|
||||
return buffer[index++];
|
||||
else
|
||||
return 0; // XXX - how else to signal error?
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an ATOM, starting at the current position. Updates
|
||||
* the internal index to beyond the Atom.
|
||||
*
|
||||
* @return an Atom
|
||||
*/
|
||||
public String readAtom() {
|
||||
return readDelimString(ATOM_CHAR_DELIM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a string stopping at control characters or any
|
||||
* character in delim.
|
||||
*/
|
||||
private String readDelimString(String delim) {
|
||||
skipSpaces();
|
||||
|
||||
if (index >= size) // already at end of response
|
||||
return null;
|
||||
|
||||
int b;
|
||||
int start = index;
|
||||
while (index < size && ((b = (((int)buffer[index])&0xff)) >= ' ') &&
|
||||
delim.indexOf((char)b) < 0 && b != 0x7f)
|
||||
index++;
|
||||
|
||||
return toString(buffer, start, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string as an arbitrary sequence of characters,
|
||||
* stopping at the delimiter Used to read part of a
|
||||
* response code inside [].
|
||||
*
|
||||
* @param delim the delimiter character
|
||||
* @return the string
|
||||
*/
|
||||
public String readString(char delim) {
|
||||
skipSpaces();
|
||||
|
||||
if (index >= size) // already at end of response
|
||||
return null;
|
||||
|
||||
int start = index;
|
||||
while (index < size && buffer[index] != delim)
|
||||
index++;
|
||||
|
||||
return toString(buffer, start, index);
|
||||
}
|
||||
|
||||
public String[] readStringList() {
|
||||
return readStringList(false);
|
||||
}
|
||||
|
||||
public String[] readAtomStringList() {
|
||||
return readStringList(true);
|
||||
}
|
||||
|
||||
private String[] readStringList(boolean atom) {
|
||||
skipSpaces();
|
||||
|
||||
if (buffer[index] != '(') { // not what we expected
|
||||
return null;
|
||||
}
|
||||
index++; // skip '('
|
||||
|
||||
// to handle buggy IMAP servers, we tolerate multiple spaces as
|
||||
// well as spaces after the left paren or before the right paren
|
||||
List<String> result = new ArrayList<>();
|
||||
while (!isNextNonSpace(')')) {
|
||||
String s = atom ? readAtomString() : readString();
|
||||
if (s == null) // not the expected string or atom
|
||||
break;
|
||||
result.add(s);
|
||||
}
|
||||
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an integer, starting at the current position. Updates the
|
||||
* internal index to beyond the number. Returns -1 if a number was
|
||||
* not found.
|
||||
*
|
||||
* @return a number
|
||||
*/
|
||||
public int readNumber() {
|
||||
// Skip leading spaces
|
||||
skipSpaces();
|
||||
|
||||
int start = index;
|
||||
while (index < size && Character.isDigit((char)buffer[index]))
|
||||
index++;
|
||||
|
||||
if (index > start) {
|
||||
try {
|
||||
return ASCIIUtility.parseInt(buffer, start, index);
|
||||
} catch (NumberFormatException nex) { }
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a long number, starting at the current position. Updates the
|
||||
* internal index to beyond the number. Returns -1 if a long number
|
||||
* was not found.
|
||||
*
|
||||
* @return a long
|
||||
*/
|
||||
public long readLong() {
|
||||
// Skip leading spaces
|
||||
skipSpaces();
|
||||
|
||||
int start = index;
|
||||
while (index < size && Character.isDigit((char)buffer[index]))
|
||||
index++;
|
||||
|
||||
if (index > start) {
|
||||
try {
|
||||
return ASCIIUtility.parseLong(buffer, start, index);
|
||||
} catch (NumberFormatException nex) { }
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a NSTRING, starting at the current position. Return it as
|
||||
* a String. The sequence 'NIL' is returned as null
|
||||
*
|
||||
* NSTRING := QuotedString | Literal | "NIL"
|
||||
*
|
||||
* @return a String
|
||||
*/
|
||||
public String readString() {
|
||||
return (String)parseString(false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a NSTRING, starting at the current position. Return it as
|
||||
* a ByteArrayInputStream. The sequence 'NIL' is returned as null
|
||||
*
|
||||
* NSTRING := QuotedString | Literal | "NIL"
|
||||
*
|
||||
* @return a ByteArrayInputStream
|
||||
*/
|
||||
public ByteArrayInputStream readBytes() {
|
||||
ByteArray ba = readByteArray();
|
||||
if (ba != null)
|
||||
return ba.toByteArrayInputStream();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a NSTRING, starting at the current position. Return it as
|
||||
* a ByteArray. The sequence 'NIL' is returned as null
|
||||
*
|
||||
* NSTRING := QuotedString | Literal | "NIL"
|
||||
*
|
||||
* @return a ByteArray
|
||||
*/
|
||||
public ByteArray readByteArray() {
|
||||
/*
|
||||
* Special case, return the data after the continuation uninterpreted.
|
||||
* It's usually a challenge for an AUTHENTICATE command.
|
||||
*/
|
||||
if (isContinuation()) {
|
||||
skipSpaces();
|
||||
return new ByteArray(buffer, index, size - index);
|
||||
}
|
||||
return (ByteArray)parseString(false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an ASTRING, starting at the current position
|
||||
* and return as a String. An ASTRING can be a QuotedString, a
|
||||
* Literal or an Atom (plus ']').
|
||||
*
|
||||
* Any errors in parsing returns null
|
||||
*
|
||||
* ASTRING := QuotedString | Literal | 1*ASTRING_CHAR
|
||||
*
|
||||
* @return a String
|
||||
*/
|
||||
public String readAtomString() {
|
||||
return (String)parseString(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic parsing routine that can parse out a Quoted-String,
|
||||
* Literal or Atom and return the parsed token as a String
|
||||
* or a ByteArray. Errors or NIL data will return null.
|
||||
*/
|
||||
private Object parseString(boolean parseAtoms, boolean returnString) {
|
||||
byte b;
|
||||
|
||||
// Skip leading spaces
|
||||
skipSpaces();
|
||||
|
||||
b = buffer[index];
|
||||
if (b == '"') { // QuotedString
|
||||
index++; // skip the quote
|
||||
int start = index;
|
||||
int copyto = index;
|
||||
|
||||
while (index < size && (b = buffer[index]) != '"') {
|
||||
if (b == '\\') // skip escaped byte
|
||||
index++;
|
||||
if (index != copyto) { // only copy if we need to
|
||||
// Beware: this is a destructive copy. I'm
|
||||
// pretty sure this is OK, but ... ;>
|
||||
buffer[copyto] = buffer[index];
|
||||
}
|
||||
copyto++;
|
||||
index++;
|
||||
}
|
||||
if (index >= size) {
|
||||
// didn't find terminating quote, something is seriously wrong
|
||||
//throw new ArrayIndexOutOfBoundsException(
|
||||
// "index = " + index + ", size = " + size);
|
||||
return null;
|
||||
} else
|
||||
index++; // skip past the terminating quote
|
||||
|
||||
if (returnString)
|
||||
return toString(buffer, start, copyto);
|
||||
else
|
||||
return new ByteArray(buffer, start, copyto-start);
|
||||
} else if (b == '{') { // Literal
|
||||
int start = ++index; // note the start position
|
||||
|
||||
while (buffer[index] != '}')
|
||||
index++;
|
||||
|
||||
int count = 0;
|
||||
try {
|
||||
count = ASCIIUtility.parseInt(buffer, start, index);
|
||||
} catch (NumberFormatException nex) {
|
||||
// throw new ParsingException();
|
||||
return null;
|
||||
}
|
||||
|
||||
start = index + 3; // skip "}\r\n"
|
||||
index = start + count; // position index to beyond the literal
|
||||
|
||||
if (returnString) // return as String
|
||||
return toString(buffer, start, start + count);
|
||||
else
|
||||
return new ByteArray(buffer, start, count);
|
||||
} else if (parseAtoms) { // parse as ASTRING-CHARs
|
||||
int start = index; // track this, so that we can use to
|
||||
// creating ByteArrayInputStream below.
|
||||
String s = readDelimString(ASTRING_CHAR_DELIM);
|
||||
if (returnString)
|
||||
return s;
|
||||
else // *very* unlikely
|
||||
return new ByteArray(buffer, start, index);
|
||||
} else if (b == 'N' || b == 'n') { // the only valid value is 'NIL'
|
||||
index += 3; // skip past NIL
|
||||
return null;
|
||||
}
|
||||
return null; // Error
|
||||
}
|
||||
|
||||
private String toString(byte[] buffer, int start, int end) {
|
||||
return utf8 ?
|
||||
new String(buffer, start, end - start, StandardCharsets.UTF_8) :
|
||||
ASCIIUtility.toString(buffer, start, end);
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isContinuation() {
|
||||
return ((type & TAG_MASK) == CONTINUATION);
|
||||
}
|
||||
|
||||
public boolean isTagged() {
|
||||
return ((type & TAG_MASK) == TAGGED);
|
||||
}
|
||||
|
||||
public boolean isUnTagged() {
|
||||
return ((type & TAG_MASK) == UNTAGGED);
|
||||
}
|
||||
|
||||
public boolean isOK() {
|
||||
return ((type & TYPE_MASK) == OK);
|
||||
}
|
||||
|
||||
public boolean isNO() {
|
||||
return ((type & TYPE_MASK) == NO);
|
||||
}
|
||||
|
||||
public boolean isBAD() {
|
||||
return ((type & TYPE_MASK) == BAD);
|
||||
}
|
||||
|
||||
public boolean isBYE() {
|
||||
return ((type & TYPE_MASK) == BYE);
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return ((type & SYNTHETIC) == SYNTHETIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tag, if this is a tagged statement.
|
||||
*
|
||||
* @return tag of this tagged statement
|
||||
*/
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rest of the response as a string, usually used to
|
||||
* return the arbitrary message text after a NO response.
|
||||
*
|
||||
* @return the rest of the response
|
||||
*/
|
||||
public String getRest() {
|
||||
skipSpaces();
|
||||
return toString(buffer, index, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the exception for a synthetic BYE response.
|
||||
*
|
||||
* @return the exception
|
||||
* @since JavaMail 1.5.4
|
||||
*/
|
||||
public Exception getException() {
|
||||
return ex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset pointer to beginning of response.
|
||||
*/
|
||||
public void reset() {
|
||||
index = pindex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(buffer, 0, size);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
/**
|
||||
* This class
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public interface ResponseHandler {
|
||||
public void handleResponse(Response r);
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.iap;
|
||||
|
||||
import java.io.*;
|
||||
import com.sun.mail.iap.ByteArray;
|
||||
import com.sun.mail.util.ASCIIUtility;
|
||||
|
||||
/**
|
||||
*
|
||||
* Inputstream that is used to read a Response.
|
||||
*
|
||||
* @author Arun Krishnan
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class ResponseInputStream {
|
||||
|
||||
private static final int minIncrement = 256;
|
||||
private static final int maxIncrement = 256 * 1024;
|
||||
private static final int incrementSlop = 16;
|
||||
|
||||
// where we read from
|
||||
private BufferedInputStream bin;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param in the InputStream to wrap
|
||||
*/
|
||||
public ResponseInputStream(InputStream in) {
|
||||
bin = new BufferedInputStream(in, 2 * 1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Response from the InputStream.
|
||||
*
|
||||
* @return ByteArray that contains the Response
|
||||
* @exception IOException for I/O errors
|
||||
*/
|
||||
public ByteArray readResponse() throws IOException {
|
||||
return readResponse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Response from the InputStream.
|
||||
*
|
||||
* @param ba the ByteArray in which to store the response, or null
|
||||
* @return ByteArray that contains the Response
|
||||
* @exception IOException for I/O errors
|
||||
*/
|
||||
public ByteArray readResponse(ByteArray ba) throws IOException {
|
||||
if (ba == null)
|
||||
ba = new ByteArray(new byte[128], 0, 128);
|
||||
|
||||
byte[] buffer = ba.getBytes();
|
||||
int idx = 0;
|
||||
for (;;) { // read until CRLF with no preceeding literal
|
||||
// XXX - b needs to be an int, to handle bytes with value 0xff
|
||||
int b = 0;
|
||||
boolean gotCRLF=false;
|
||||
|
||||
// Read a CRLF terminated line from the InputStream
|
||||
while (!gotCRLF &&
|
||||
((b = bin.read()) != -1)) {
|
||||
if (b == '\n') {
|
||||
if ((idx > 0) && buffer[idx-1] == '\r')
|
||||
gotCRLF = true;
|
||||
}
|
||||
if (idx >= buffer.length) {
|
||||
int incr = buffer.length;
|
||||
if (incr > maxIncrement)
|
||||
incr = maxIncrement;
|
||||
ba.grow(incr);
|
||||
buffer = ba.getBytes();
|
||||
}
|
||||
buffer[idx++] = (byte)b;
|
||||
}
|
||||
|
||||
if (b == -1)
|
||||
throw new IOException("Connection dropped by server?");
|
||||
|
||||
// Now lets check for literals : {<digits>}CRLF
|
||||
// Note: index needs to >= 5 for the above sequence to occur
|
||||
if (idx < 5 || buffer[idx-3] != '}')
|
||||
break;
|
||||
|
||||
int i;
|
||||
// look for left curly
|
||||
for (i = idx - 4; i >= 0; i--)
|
||||
if (buffer[i] == '{')
|
||||
break;
|
||||
|
||||
if (i < 0) // Nope, not a literal ?
|
||||
break;
|
||||
|
||||
int count = 0;
|
||||
// OK, handle the literal ..
|
||||
try {
|
||||
count = ASCIIUtility.parseInt(buffer, i+1, idx-3);
|
||||
} catch (NumberFormatException e) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Now read 'count' bytes. (Note: count could be 0)
|
||||
if (count > 0) {
|
||||
int avail = buffer.length - idx; // available space in buffer
|
||||
if (count + incrementSlop > avail) {
|
||||
// need count-avail more bytes
|
||||
ba.grow(minIncrement > count + incrementSlop - avail ?
|
||||
minIncrement : count + incrementSlop - avail);
|
||||
buffer = ba.getBytes();
|
||||
}
|
||||
|
||||
/*
|
||||
* read() might not return all the bytes in one shot,
|
||||
* so call repeatedly till we are done
|
||||
*/
|
||||
int actual;
|
||||
while (count > 0) {
|
||||
actual = bin.read(buffer, idx, count);
|
||||
if (actual == -1)
|
||||
throw new IOException("Connection dropped by server?");
|
||||
count -= actual;
|
||||
idx += actual;
|
||||
}
|
||||
}
|
||||
// back to top of loop to read until CRLF
|
||||
}
|
||||
ba.setCount(idx);
|
||||
return ba;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much buffered data do we have?
|
||||
*
|
||||
* @return number of bytes available
|
||||
* @exception IOException if the stream has been closed
|
||||
* @since JavaMail 1.5.4
|
||||
*/
|
||||
public int available() throws IOException {
|
||||
return bin.available();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<!--
|
||||
|
||||
Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
This program and the accompanying materials are made available under the
|
||||
terms of the Eclipse Public License v. 2.0, which is available at
|
||||
http://www.eclipse.org/legal/epl-2.0.
|
||||
|
||||
This Source Code may also be made available under the following Secondary
|
||||
Licenses when the conditions for such availability set forth in the
|
||||
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
version 2 with the GNU Classpath Exception, which is available at
|
||||
https://www.gnu.org/software/classpath/license.html.
|
||||
|
||||
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
|
||||
-->
|
||||
|
||||
<TITLE>com.sun.mail.iap package</TITLE>
|
||||
</HEAD>
|
||||
<BODY BGCOLOR="white">
|
||||
|
||||
<P>
|
||||
This package includes internal IMAP support classes and
|
||||
<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
|
||||
</P>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An access control list entry for a particular authentication identifier
|
||||
* (user or group). Associates a set of Rights with the identifier.
|
||||
* See RFC 2086.
|
||||
* <p>
|
||||
*
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class ACL implements Cloneable {
|
||||
|
||||
private String name;
|
||||
private Rights rights;
|
||||
|
||||
/**
|
||||
* Construct an ACL entry for the given identifier and with no rights.
|
||||
*
|
||||
* @param name the identifier name
|
||||
*/
|
||||
public ACL(String name) {
|
||||
this.name = name;
|
||||
this.rights = new Rights();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an ACL entry for the given identifier with the given rights.
|
||||
*
|
||||
* @param name the identifier name
|
||||
* @param rights the rights
|
||||
*/
|
||||
public ACL(String name, Rights rights) {
|
||||
this.name = name;
|
||||
this.rights = rights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifier name for this ACL entry.
|
||||
*
|
||||
* @return the identifier name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rights associated with this ACL entry.
|
||||
*
|
||||
* @param rights the rights
|
||||
*/
|
||||
public void setRights(Rights rights) {
|
||||
this.rights = rights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rights associated with this ACL entry.
|
||||
* Returns the actual Rights object referenced by this ACL;
|
||||
* modifications to the Rights object will effect this ACL.
|
||||
*
|
||||
* @return the rights
|
||||
*/
|
||||
public Rights getRights() {
|
||||
return rights;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone this ACL entry.
|
||||
*/
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
ACL acl = (ACL)super.clone();
|
||||
acl.rights = (Rights)this.rights.clone();
|
||||
return acl;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* Information from the APPENDUID response code
|
||||
* defined by the UIDPLUS extension -
|
||||
* <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
|
||||
*
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class AppendUID {
|
||||
public long uidvalidity = -1;
|
||||
public long uid = -1;
|
||||
|
||||
public AppendUID(long uidvalidity, long uid) {
|
||||
this.uidvalidity = uidvalidity;
|
||||
this.uid = uid;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import com.sun.mail.imap.protocol.UIDSet;
|
||||
|
||||
/**
|
||||
* Information from the COPYUID response code
|
||||
* defined by the UIDPLUS extension -
|
||||
* <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
|
||||
*
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class CopyUID {
|
||||
public long uidvalidity = -1;
|
||||
public UIDSet[] src;
|
||||
public UIDSet[] dst;
|
||||
|
||||
public CopyUID(long uidvalidity, UIDSet[] src, UIDSet[] dst) {
|
||||
this.uidvalidity = uidvalidity;
|
||||
this.src = src;
|
||||
this.dst = dst;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.MethodNotSupportedException;
|
||||
import com.sun.mail.iap.ProtocolException;
|
||||
import com.sun.mail.imap.protocol.IMAPProtocol;
|
||||
import com.sun.mail.imap.protocol.ListInfo;
|
||||
|
||||
/**
|
||||
* The default IMAP folder (root of the naming hierarchy).
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class DefaultFolder extends IMAPFolder {
|
||||
|
||||
protected DefaultFolder(IMAPStore store) {
|
||||
super("", UNKNOWN_SEPARATOR, store, null);
|
||||
exists = true; // of course
|
||||
type = HOLDS_FOLDERS; // obviously
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String getName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Folder getParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Folder[] list(final String pattern)
|
||||
throws MessagingException {
|
||||
ListInfo[] li = null;
|
||||
|
||||
li = (ListInfo[])doCommand(new ProtocolCommand() {
|
||||
@Override
|
||||
public Object doCommand(IMAPProtocol p) throws ProtocolException {
|
||||
return p.list("", pattern);
|
||||
}
|
||||
});
|
||||
|
||||
if (li == null)
|
||||
return new Folder[0];
|
||||
|
||||
IMAPFolder[] folders = new IMAPFolder[li.length];
|
||||
for (int i = 0; i < folders.length; i++)
|
||||
folders[i] = ((IMAPStore)store).newIMAPFolder(li[i]);
|
||||
return folders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Folder[] listSubscribed(final String pattern)
|
||||
throws MessagingException {
|
||||
ListInfo[] li = null;
|
||||
|
||||
li = (ListInfo[])doCommand(new ProtocolCommand() {
|
||||
@Override
|
||||
public Object doCommand(IMAPProtocol p) throws ProtocolException {
|
||||
return p.lsub("", pattern);
|
||||
}
|
||||
});
|
||||
|
||||
if (li == null)
|
||||
return new Folder[0];
|
||||
|
||||
IMAPFolder[] folders = new IMAPFolder[li.length];
|
||||
for (int i = 0; i < folders.length; i++)
|
||||
folders[i] = ((IMAPStore)store).newIMAPFolder(li[i]);
|
||||
return folders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNewMessages() throws MessagingException {
|
||||
// Not applicable on DefaultFolder
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Folder getFolder(String name) throws MessagingException {
|
||||
return ((IMAPStore)store).newIMAPFolder(name, UNKNOWN_SEPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(boolean recurse) throws MessagingException {
|
||||
// Not applicable on DefaultFolder
|
||||
throw new MethodNotSupportedException("Cannot delete Default Folder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameTo(Folder f) throws MessagingException {
|
||||
// Not applicable on DefaultFolder
|
||||
throw new MethodNotSupportedException("Cannot rename Default Folder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendMessages(Message[] msgs) throws MessagingException {
|
||||
// Not applicable on DefaultFolder
|
||||
throw new MethodNotSupportedException("Cannot append to Default Folder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message[] expunge() throws MessagingException {
|
||||
// Not applicable on DefaultFolder
|
||||
throw new MethodNotSupportedException("Cannot expunge Default Folder");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,454 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.*;
|
||||
import javax.activation.*;
|
||||
|
||||
import com.sun.mail.util.PropUtil;
|
||||
import com.sun.mail.util.ReadableMime;
|
||||
import com.sun.mail.util.LineOutputStream;
|
||||
import com.sun.mail.util.SharedByteArrayOutputStream;
|
||||
import com.sun.mail.iap.*;
|
||||
import com.sun.mail.imap.protocol.*;
|
||||
|
||||
/**
|
||||
* An IMAP body part.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class IMAPBodyPart extends MimeBodyPart implements ReadableMime {
|
||||
private IMAPMessage message;
|
||||
private BODYSTRUCTURE bs;
|
||||
private String sectionId;
|
||||
|
||||
// processed values ..
|
||||
private String type;
|
||||
private String description;
|
||||
|
||||
private boolean headersLoaded = false;
|
||||
|
||||
private static final boolean decodeFileName =
|
||||
PropUtil.getBooleanSystemProperty("mail.mime.decodefilename", false);
|
||||
|
||||
protected IMAPBodyPart(BODYSTRUCTURE bs, String sid, IMAPMessage message) {
|
||||
super();
|
||||
this.bs = bs;
|
||||
this.sectionId = sid;
|
||||
this.message = message;
|
||||
// generate content-type
|
||||
ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams);
|
||||
type = ct.toString();
|
||||
}
|
||||
|
||||
/* Override this method to make it a no-op, rather than throw
|
||||
* an IllegalWriteException. This will permit IMAPBodyParts to
|
||||
* be inserted in newly crafted MimeMessages, especially when
|
||||
* forwarding or replying to messages.
|
||||
*/
|
||||
@Override
|
||||
protected void updateHeaders() {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() throws MessagingException {
|
||||
return bs.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineCount() throws MessagingException {
|
||||
return bs.lines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() throws MessagingException {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisposition() throws MessagingException {
|
||||
return bs.disposition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisposition(String disposition) throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEncoding() throws MessagingException {
|
||||
return bs.encoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentID() throws MessagingException {
|
||||
return bs.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentMD5() throws MessagingException {
|
||||
return bs.md5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentMD5(String md5) throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() throws MessagingException {
|
||||
if (description != null) // cached value ?
|
||||
return description;
|
||||
|
||||
if (bs.description == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
description = MimeUtility.decodeText(bs.description);
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
description = bs.description;
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description, String charset)
|
||||
throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() throws MessagingException {
|
||||
String filename = null;
|
||||
if (bs.dParams != null)
|
||||
filename = bs.dParams.get("filename");
|
||||
if ((filename == null || filename.isEmpty()) && bs.cParams != null)
|
||||
filename = bs.cParams.get("name");
|
||||
if (decodeFileName && filename != null) {
|
||||
try {
|
||||
filename = MimeUtility.decodeText(filename);
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
throw new MessagingException("Can't decode filename", ex);
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFileName(String filename) throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream getContentStream() throws MessagingException {
|
||||
InputStream is = null;
|
||||
boolean pk = message.getPeek(); // acquire outside of message cache lock
|
||||
|
||||
// Acquire MessageCacheLock, to freeze seqnum.
|
||||
synchronized(message.getMessageCacheLock()) {
|
||||
try {
|
||||
IMAPProtocol p = message.getProtocol();
|
||||
|
||||
// Check whether this message is expunged
|
||||
message.checkExpunged();
|
||||
|
||||
if (p.isREV1() && (message.getFetchBlockSize() != -1))
|
||||
return new IMAPInputStream(message, sectionId,
|
||||
message.ignoreBodyStructureSize() ? -1 : bs.size, pk);
|
||||
|
||||
// Else, vanila IMAP4, no partial fetch
|
||||
|
||||
int seqnum = message.getSequenceNumber();
|
||||
BODY b;
|
||||
if (pk)
|
||||
b = p.peekBody(seqnum, sectionId);
|
||||
else
|
||||
b = p.fetchBody(seqnum, sectionId);
|
||||
if (b != null)
|
||||
is = b.getByteArrayInputStream();
|
||||
} catch (ConnectionException cex) {
|
||||
throw new FolderClosedException(
|
||||
message.getFolder(), cex.getMessage());
|
||||
} catch (ProtocolException pex) {
|
||||
throw new MessagingException(pex.getMessage(), pex);
|
||||
}
|
||||
}
|
||||
|
||||
if (is == null) {
|
||||
message.forceCheckExpunged(); // may throw MessageRemovedException
|
||||
// nope, the server doesn't think it's expunged.
|
||||
// can't tell the difference between the server returning NIL
|
||||
// and some other error that caused null to be returned above,
|
||||
// so we'll just assume it was empty content.
|
||||
is = new ByteArrayInputStream(new byte[0]);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MIME format stream of headers for this body part.
|
||||
*/
|
||||
private InputStream getHeaderStream() throws MessagingException {
|
||||
if (!message.isREV1())
|
||||
loadHeaders(); // will be needed below
|
||||
|
||||
// Acquire MessageCacheLock, to freeze seqnum.
|
||||
synchronized(message.getMessageCacheLock()) {
|
||||
try {
|
||||
IMAPProtocol p = message.getProtocol();
|
||||
|
||||
// Check whether this message got expunged
|
||||
message.checkExpunged();
|
||||
|
||||
if (p.isREV1()) {
|
||||
int seqnum = message.getSequenceNumber();
|
||||
BODY b = p.peekBody(seqnum, sectionId + ".MIME");
|
||||
|
||||
if (b == null)
|
||||
throw new MessagingException("Failed to fetch headers");
|
||||
|
||||
ByteArrayInputStream bis = b.getByteArrayInputStream();
|
||||
if (bis == null)
|
||||
throw new MessagingException("Failed to fetch headers");
|
||||
return bis;
|
||||
|
||||
} else {
|
||||
// Can't read it from server, have to fake it
|
||||
SharedByteArrayOutputStream bos =
|
||||
new SharedByteArrayOutputStream(0);
|
||||
LineOutputStream los = new LineOutputStream(bos);
|
||||
|
||||
try {
|
||||
// Write out the header
|
||||
Enumeration<String> hdrLines
|
||||
= super.getAllHeaderLines();
|
||||
while (hdrLines.hasMoreElements())
|
||||
los.writeln(hdrLines.nextElement());
|
||||
|
||||
// The CRLF separator between header and content
|
||||
los.writeln();
|
||||
} catch (IOException ioex) {
|
||||
// should never happen
|
||||
} finally {
|
||||
try {
|
||||
los.close();
|
||||
} catch (IOException cex) { }
|
||||
}
|
||||
return bos.toStream();
|
||||
}
|
||||
} catch (ConnectionException cex) {
|
||||
throw new FolderClosedException(
|
||||
message.getFolder(), cex.getMessage());
|
||||
} catch (ProtocolException pex) {
|
||||
throw new MessagingException(pex.getMessage(), pex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MIME format stream corresponding to this message part.
|
||||
*
|
||||
* @return the MIME format stream
|
||||
* @since JavaMail 1.4.5
|
||||
*/
|
||||
@Override
|
||||
public InputStream getMimeStream() throws MessagingException {
|
||||
/*
|
||||
* The IMAP protocol doesn't support returning the entire
|
||||
* part content in one operation so we have to fake it by
|
||||
* concatenating the header stream and the content stream.
|
||||
*/
|
||||
return new SequenceInputStream(getHeaderStream(), getContentStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized DataHandler getDataHandler()
|
||||
throws MessagingException {
|
||||
if (dh == null) {
|
||||
if (bs.isMulti())
|
||||
dh = new DataHandler(
|
||||
new IMAPMultipartDataSource(
|
||||
this, bs.bodies, sectionId, message)
|
||||
);
|
||||
else if (bs.isNested() && message.isREV1() && bs.envelope != null)
|
||||
dh = new DataHandler(
|
||||
new IMAPNestedMessage(message,
|
||||
bs.bodies[0],
|
||||
bs.envelope,
|
||||
sectionId),
|
||||
type
|
||||
);
|
||||
}
|
||||
|
||||
return super.getDataHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDataHandler(DataHandler content) throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(Object o, String type) throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContent(Multipart mp) throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getHeader(String name) throws MessagingException {
|
||||
loadHeaders();
|
||||
return super.getHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value)
|
||||
throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value)
|
||||
throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHeader(String name) throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<Header> getAllHeaders() throws MessagingException {
|
||||
loadHeaders();
|
||||
return super.getAllHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<Header> getMatchingHeaders(String[] names)
|
||||
throws MessagingException {
|
||||
loadHeaders();
|
||||
return super.getMatchingHeaders(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<Header> getNonMatchingHeaders(String[] names)
|
||||
throws MessagingException {
|
||||
loadHeaders();
|
||||
return super.getNonMatchingHeaders(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeaderLine(String line) throws MessagingException {
|
||||
throw new IllegalWriteException("IMAPBodyPart is read-only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAllHeaderLines() throws MessagingException {
|
||||
loadHeaders();
|
||||
return super.getAllHeaderLines();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getMatchingHeaderLines(String[] names)
|
||||
throws MessagingException {
|
||||
loadHeaders();
|
||||
return super.getMatchingHeaderLines(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getNonMatchingHeaderLines(String[] names)
|
||||
throws MessagingException {
|
||||
loadHeaders();
|
||||
return super.getNonMatchingHeaderLines(names);
|
||||
}
|
||||
|
||||
private synchronized void loadHeaders() throws MessagingException {
|
||||
if (headersLoaded)
|
||||
return;
|
||||
|
||||
// "headers" should never be null since it's set in the constructor.
|
||||
// If something did go wrong this will fix it, but is an unsynchronized
|
||||
// assignment of "headers".
|
||||
if (headers == null)
|
||||
headers = new InternetHeaders();
|
||||
|
||||
// load headers
|
||||
|
||||
// Acquire MessageCacheLock, to freeze seqnum.
|
||||
synchronized(message.getMessageCacheLock()) {
|
||||
try {
|
||||
IMAPProtocol p = message.getProtocol();
|
||||
|
||||
// Check whether this message got expunged
|
||||
message.checkExpunged();
|
||||
|
||||
if (p.isREV1()) {
|
||||
int seqnum = message.getSequenceNumber();
|
||||
BODY b = p.peekBody(seqnum, sectionId + ".MIME");
|
||||
|
||||
if (b == null)
|
||||
throw new MessagingException("Failed to fetch headers");
|
||||
|
||||
ByteArrayInputStream bis = b.getByteArrayInputStream();
|
||||
if (bis == null)
|
||||
throw new MessagingException("Failed to fetch headers");
|
||||
|
||||
headers.load(bis);
|
||||
|
||||
} else {
|
||||
|
||||
// RFC 1730 does not provide for fetching BodyPart headers
|
||||
// So, just dump the RFC1730 BODYSTRUCTURE into the
|
||||
// headerStore
|
||||
|
||||
// Content-Type
|
||||
headers.addHeader("Content-Type", type);
|
||||
// Content-Transfer-Encoding
|
||||
headers.addHeader("Content-Transfer-Encoding", bs.encoding);
|
||||
// Content-Description
|
||||
if (bs.description != null)
|
||||
headers.addHeader("Content-Description",
|
||||
bs.description);
|
||||
// Content-ID
|
||||
if (bs.id != null)
|
||||
headers.addHeader("Content-ID", bs.id);
|
||||
// Content-MD5
|
||||
if (bs.md5 != null)
|
||||
headers.addHeader("Content-MD5", bs.md5);
|
||||
}
|
||||
} catch (ConnectionException cex) {
|
||||
throw new FolderClosedException(
|
||||
message.getFolder(), cex.getMessage());
|
||||
} catch (ProtocolException pex) {
|
||||
throw new MessagingException(pex.getMessage(), pex);
|
||||
}
|
||||
}
|
||||
headersLoaded = true;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.io.*;
|
||||
import javax.mail.*;
|
||||
import com.sun.mail.imap.protocol.*;
|
||||
import com.sun.mail.iap.*;
|
||||
import com.sun.mail.util.FolderClosedIOException;
|
||||
import com.sun.mail.util.MessageRemovedIOException;
|
||||
|
||||
/**
|
||||
* This class implements an IMAP data stream.
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class IMAPInputStream extends InputStream {
|
||||
private IMAPMessage msg; // this message
|
||||
private String section; // section-id
|
||||
private int pos; // track the position within the IMAP datastream
|
||||
private int blksize; // number of bytes to read in each FETCH request
|
||||
private int max; // the total number of bytes in this section.
|
||||
// -1 indicates unknown
|
||||
private byte[] buf; // the buffer obtained from fetchBODY()
|
||||
private int bufcount; // The index one greater than the index of the
|
||||
// last valid byte in 'buf'
|
||||
private int bufpos; // The current position within 'buf'
|
||||
private boolean lastBuffer; // is this the last buffer of data?
|
||||
private boolean peek; // peek instead of fetch?
|
||||
private ByteArray readbuf; // reuse for each read
|
||||
|
||||
// Allocate this much extra space in the read buffer to allow
|
||||
// space for the FETCH response overhead
|
||||
private static final int slop = 64;
|
||||
|
||||
|
||||
/**
|
||||
* Create an IMAPInputStream.
|
||||
*
|
||||
* @param msg the IMAPMessage the data will come from
|
||||
* @param section the IMAP section/part identifier for the data
|
||||
* @param max the number of bytes in this section
|
||||
* @param peek peek instead of fetch?
|
||||
*/
|
||||
public IMAPInputStream(IMAPMessage msg, String section, int max,
|
||||
boolean peek) {
|
||||
this.msg = msg;
|
||||
this.section = section;
|
||||
this.max = max;
|
||||
this.peek = peek;
|
||||
pos = 0;
|
||||
blksize = msg.getFetchBlockSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a NOOP to force any untagged EXPUNGE responses
|
||||
* and then check if this message is expunged.
|
||||
*/
|
||||
private void forceCheckExpunged()
|
||||
throws MessageRemovedIOException, FolderClosedIOException {
|
||||
synchronized (msg.getMessageCacheLock()) {
|
||||
try {
|
||||
msg.getProtocol().noop();
|
||||
} catch (ConnectionException cex) {
|
||||
throw new FolderClosedIOException(msg.getFolder(),
|
||||
cex.getMessage());
|
||||
} catch (FolderClosedException fex) {
|
||||
throw new FolderClosedIOException(fex.getFolder(),
|
||||
fex.getMessage());
|
||||
} catch (ProtocolException pex) {
|
||||
// ignore it
|
||||
}
|
||||
}
|
||||
if (msg.isExpunged())
|
||||
throw new MessageRemovedIOException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch more data from the server. This method assumes that all
|
||||
* data has already been read in, hence bufpos > bufcount.
|
||||
*/
|
||||
private void fill() throws IOException {
|
||||
/*
|
||||
* If we've read the last buffer, there's no more to read.
|
||||
* If we know the total number of bytes available from this
|
||||
* section, let's check if we have consumed that many bytes.
|
||||
*/
|
||||
if (lastBuffer || max != -1 && pos >= max) {
|
||||
if (pos == 0)
|
||||
checkSeen();
|
||||
readbuf = null; // XXX - return to pool?
|
||||
return; // the caller of fill() will return -1.
|
||||
}
|
||||
|
||||
BODY b = null;
|
||||
if (readbuf == null)
|
||||
readbuf = new ByteArray(blksize + slop);
|
||||
|
||||
ByteArray ba;
|
||||
int cnt;
|
||||
// Acquire MessageCacheLock, to freeze seqnum.
|
||||
synchronized (msg.getMessageCacheLock()) {
|
||||
try {
|
||||
IMAPProtocol p = msg.getProtocol();
|
||||
|
||||
// Check whether this message is expunged
|
||||
if (msg.isExpunged())
|
||||
throw new MessageRemovedIOException(
|
||||
"No content for expunged message");
|
||||
|
||||
int seqnum = msg.getSequenceNumber();
|
||||
cnt = blksize;
|
||||
if (max != -1 && pos + blksize > max)
|
||||
cnt = max - pos;
|
||||
if (peek)
|
||||
b = p.peekBody(seqnum, section, pos, cnt, readbuf);
|
||||
else
|
||||
b = p.fetchBody(seqnum, section, pos, cnt, readbuf);
|
||||
} catch (ProtocolException pex) {
|
||||
forceCheckExpunged();
|
||||
throw new IOException(pex.getMessage());
|
||||
} catch (FolderClosedException fex) {
|
||||
throw new FolderClosedIOException(fex.getFolder(),
|
||||
fex.getMessage());
|
||||
}
|
||||
|
||||
if (b == null || ((ba = b.getByteArray()) == null)) {
|
||||
forceCheckExpunged();
|
||||
// nope, the server doesn't think it's expunged.
|
||||
// can't tell the difference between the server returning NIL
|
||||
// and some other error that caused null to be returned above,
|
||||
// so we'll just assume it was empty content.
|
||||
ba = new ByteArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the SEEN flag is set after reading the first chunk
|
||||
if (pos == 0)
|
||||
checkSeen();
|
||||
|
||||
// setup new values ..
|
||||
buf = ba.getBytes();
|
||||
bufpos = ba.getStart();
|
||||
int n = ba.getCount(); // will be zero, if all data has been
|
||||
// consumed from the server.
|
||||
|
||||
int origin = b != null ? b.getOrigin() : pos;
|
||||
if (origin < 0) {
|
||||
/*
|
||||
* Some versions of Exchange will return the entire message
|
||||
* body even though we only ask for a chunk, and the returned
|
||||
* data won't specify an "origin". If this happens, and we
|
||||
* get more data than we asked for, assume it's the entire
|
||||
* message body.
|
||||
*/
|
||||
if (pos == 0) {
|
||||
/*
|
||||
* If we got more or less than we asked for,
|
||||
* this is the last buffer of data.
|
||||
*/
|
||||
lastBuffer = n != cnt;
|
||||
} else {
|
||||
/*
|
||||
* We asked for data NOT starting at the beginning,
|
||||
* but we got back data starting at the beginning.
|
||||
* Possibly we could extract the needed data from
|
||||
* some part of the data we got back, but really this
|
||||
* should never happen so we just assume something is
|
||||
* broken and terminate the data here.
|
||||
*/
|
||||
n = 0;
|
||||
lastBuffer = true;
|
||||
}
|
||||
} else if (origin == pos) {
|
||||
/*
|
||||
* If we got less than we asked for,
|
||||
* this is the last buffer of data.
|
||||
*/
|
||||
lastBuffer = n < cnt;
|
||||
} else {
|
||||
/*
|
||||
* We got back data that doesn't match the request.
|
||||
* Just terminate the data here.
|
||||
*/
|
||||
n = 0;
|
||||
lastBuffer = true;
|
||||
}
|
||||
|
||||
bufcount = bufpos + n;
|
||||
pos += n;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next byte of data from this buffered input stream.
|
||||
* If no byte is available, the value <code>-1</code> is returned.
|
||||
*/
|
||||
@Override
|
||||
public synchronized int read() throws IOException {
|
||||
if (bufpos >= bufcount) {
|
||||
fill();
|
||||
if (bufpos >= bufcount)
|
||||
return -1; // EOF
|
||||
}
|
||||
return buf[bufpos++] & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to <code>len</code> bytes of data from this
|
||||
* input stream into the given buffer. <p>
|
||||
*
|
||||
* Returns the total number of bytes read into the buffer,
|
||||
* or <code>-1</code> if there is no more data. <p>
|
||||
*
|
||||
* Note that this method mimics the "weird !" semantics of
|
||||
* BufferedInputStream in that the number of bytes actually
|
||||
* returned may be less that the requested value. So callers
|
||||
* of this routine should be aware of this and must check
|
||||
* the return value to insure that they have obtained the
|
||||
* requisite number of bytes.
|
||||
*/
|
||||
@Override
|
||||
public synchronized int read(byte b[], int off, int len)
|
||||
throws IOException {
|
||||
|
||||
int avail = bufcount - bufpos;
|
||||
if (avail <= 0) {
|
||||
fill();
|
||||
avail = bufcount - bufpos;
|
||||
if (avail <= 0)
|
||||
return -1; // EOF
|
||||
}
|
||||
int cnt = (avail < len) ? avail : len;
|
||||
System.arraycopy(buf, bufpos, b, off, cnt);
|
||||
bufpos += cnt;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to <code>b.length</code> bytes of data from this input
|
||||
* stream into an array of bytes. <p>
|
||||
*
|
||||
* Returns the total number of bytes read into the buffer, or
|
||||
* <code>-1</code> is there is no more data. <p>
|
||||
*
|
||||
* Note that this method mimics the "weird !" semantics of
|
||||
* BufferedInputStream in that the number of bytes actually
|
||||
* returned may be less that the requested value. So callers
|
||||
* of this routine should be aware of this and must check
|
||||
* the return value to insure that they have obtained the
|
||||
* requisite number of bytes.
|
||||
*/
|
||||
@Override
|
||||
public int read(byte b[]) throws IOException {
|
||||
return read(b, 0, b.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that can be read from this input
|
||||
* stream without blocking.
|
||||
*/
|
||||
@Override
|
||||
public synchronized int available() throws IOException {
|
||||
return (bufcount - bufpos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally the SEEN flag will have been set by now, but if not,
|
||||
* force it to be set (as long as the folder isn't open read-only
|
||||
* and we're not peeking).
|
||||
* And of course, if there's no folder (e.g., a nested message)
|
||||
* don't do anything.
|
||||
*/
|
||||
private void checkSeen() {
|
||||
if (peek) // if we're peeking, don't set the SEEN flag
|
||||
return;
|
||||
try {
|
||||
Folder f = msg.getFolder();
|
||||
if (f != null && f.getMode() != Folder.READ_ONLY &&
|
||||
!msg.isSet(Flags.Flag.SEEN))
|
||||
msg.setFlag(Flags.Flag.SEEN, true);
|
||||
} catch (MessagingException ex) {
|
||||
// ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.*;
|
||||
|
||||
import com.sun.mail.imap.protocol.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class IMAPMultipartDataSource extends MimePartDataSource
|
||||
implements MultipartDataSource {
|
||||
private List<IMAPBodyPart> parts;
|
||||
|
||||
protected IMAPMultipartDataSource(MimePart part, BODYSTRUCTURE[] bs,
|
||||
String sectionId, IMAPMessage msg) {
|
||||
super(part);
|
||||
|
||||
parts = new ArrayList<>(bs.length);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
parts.add(
|
||||
new IMAPBodyPart(bs[i],
|
||||
sectionId == null ?
|
||||
Integer.toString(i+1) :
|
||||
sectionId + "." + Integer.toString(i+1),
|
||||
msg)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return parts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BodyPart getBodyPart(int index) throws MessagingException {
|
||||
return parts.get(index);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.io.*;
|
||||
import javax.mail.*;
|
||||
import com.sun.mail.imap.protocol.*;
|
||||
import com.sun.mail.iap.ProtocolException;
|
||||
|
||||
/**
|
||||
* This class implements a nested IMAP message
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class IMAPNestedMessage extends IMAPMessage {
|
||||
private IMAPMessage msg; // the enclosure of this nested message
|
||||
|
||||
/**
|
||||
* Package private constructor. <p>
|
||||
*
|
||||
* Note that nested messages have no containing folder, nor
|
||||
* a message number.
|
||||
*/
|
||||
IMAPNestedMessage(IMAPMessage m, BODYSTRUCTURE b, ENVELOPE e, String sid) {
|
||||
super(m._getSession());
|
||||
msg = m;
|
||||
bs = b;
|
||||
envelope = e;
|
||||
sectionId = sid;
|
||||
setPeek(m.getPeek());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the enclosing message's Protocol object. Overrides
|
||||
* IMAPMessage.getProtocol().
|
||||
*/
|
||||
@Override
|
||||
protected IMAPProtocol getProtocol()
|
||||
throws ProtocolException, FolderClosedException {
|
||||
return msg.getProtocol();
|
||||
}
|
||||
|
||||
/*
|
||||
* Is this an IMAP4 REV1 server?
|
||||
*/
|
||||
@Override
|
||||
protected boolean isREV1() throws FolderClosedException {
|
||||
return msg.isREV1();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the enclosing message's messageCacheLock. Overrides
|
||||
* IMAPMessage.getMessageCacheLock().
|
||||
*/
|
||||
@Override
|
||||
protected Object getMessageCacheLock() {
|
||||
return msg.getMessageCacheLock();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the enclosing message's sequence number. Overrides
|
||||
* IMAPMessage.getSequenceNumber().
|
||||
*/
|
||||
@Override
|
||||
protected int getSequenceNumber() {
|
||||
return msg.getSequenceNumber();
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether the enclosing message is expunged. Overrides
|
||||
* IMAPMessage.checkExpunged().
|
||||
*/
|
||||
@Override
|
||||
protected void checkExpunged() throws MessageRemovedException {
|
||||
msg.checkExpunged();
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether the enclosing message is expunged. Overrides
|
||||
* Message.isExpunged().
|
||||
*/
|
||||
@Override
|
||||
public boolean isExpunged() {
|
||||
return msg.isExpunged();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the enclosing message's fetchBlockSize.
|
||||
*/
|
||||
@Override
|
||||
protected int getFetchBlockSize() {
|
||||
return msg.getFetchBlockSize();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the enclosing message's ignoreBodyStructureSize.
|
||||
*/
|
||||
@Override
|
||||
protected boolean ignoreBodyStructureSize() {
|
||||
return msg.ignoreBodyStructureSize();
|
||||
}
|
||||
|
||||
/*
|
||||
* IMAPMessage uses RFC822.SIZE. We use the "size" field from
|
||||
* our BODYSTRUCTURE.
|
||||
*/
|
||||
@Override
|
||||
public int getSize() throws MessagingException {
|
||||
return bs.size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disallow setting flags on nested messages
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setFlags(Flags flag, boolean set)
|
||||
throws MessagingException {
|
||||
// Cannot set FLAGS on a nested IMAP message
|
||||
throw new MethodNotSupportedException(
|
||||
"Cannot set flags on this nested message");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import javax.mail.Provider;
|
||||
|
||||
import com.sun.mail.util.DefaultProvider;
|
||||
|
||||
/**
|
||||
* The IMAP protocol provider.
|
||||
*/
|
||||
@DefaultProvider // Remove this annotation if you copy this provider
|
||||
public class IMAPProvider extends Provider {
|
||||
public IMAPProvider() {
|
||||
super(Provider.Type.STORE, "imap", IMAPStore.class.getName(),
|
||||
"Oracle", null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import javax.mail.Provider;
|
||||
|
||||
import com.sun.mail.util.DefaultProvider;
|
||||
|
||||
/**
|
||||
* The IMAP SSL protocol provider.
|
||||
*/
|
||||
@DefaultProvider // Remove this annotation if you copy this provider
|
||||
public class IMAPSSLProvider extends Provider {
|
||||
public IMAPSSLProvider() {
|
||||
super(Provider.Type.STORE, "imaps", IMAPSSLStore.class.getName(),
|
||||
"Oracle", null);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import javax.mail.*;
|
||||
|
||||
/**
|
||||
* This class provides access to an IMAP message store over SSL.
|
||||
*/
|
||||
|
||||
public class IMAPSSLStore extends IMAPStore {
|
||||
|
||||
/**
|
||||
* Constructor that takes a Session object and a URLName that
|
||||
* represents a specific IMAP server.
|
||||
*
|
||||
* @param session the Session
|
||||
* @param url the URLName of this store
|
||||
*/
|
||||
public IMAPSSLStore(Session session, URLName url) {
|
||||
super(session, url, "imaps", true); // call super constructor
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,491 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.Socket;
|
||||
import java.nio.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.mail.*;
|
||||
|
||||
import com.sun.mail.imap.protocol.IMAPProtocol;
|
||||
import com.sun.mail.util.MailLogger;
|
||||
|
||||
/**
|
||||
* IdleManager uses the optional IMAP IDLE command
|
||||
* (<A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>)
|
||||
* to watch multiple folders for new messages.
|
||||
* IdleManager uses an Executor to execute tasks in separate threads.
|
||||
* An Executor is typically provided by an ExecutorService.
|
||||
* For example, for a Java SE application:
|
||||
* <blockquote><pre>
|
||||
* ExecutorService es = Executors.newCachedThreadPool();
|
||||
* final IdleManager idleManager = new IdleManager(session, es);
|
||||
* </pre></blockquote>
|
||||
* For a Java EE 7 application:
|
||||
* <blockquote><pre>
|
||||
* {@literal @}Resource
|
||||
* ManagedExecutorService es;
|
||||
* final IdleManager idleManager = new IdleManager(session, es);
|
||||
* </pre></blockquote>
|
||||
* To watch for new messages in a folder, open the folder, register a listener,
|
||||
* and ask the IdleManager to watch the folder:
|
||||
* <blockquote><pre>
|
||||
* Folder folder = store.getFolder("INBOX");
|
||||
* folder.open(Folder.READ_WRITE);
|
||||
* folder.addMessageCountListener(new MessageCountAdapter() {
|
||||
* public void messagesAdded(MessageCountEvent ev) {
|
||||
* Folder folder = (Folder)ev.getSource();
|
||||
* Message[] msgs = ev.getMessages();
|
||||
* System.out.println("Folder: " + folder +
|
||||
* " got " + msgs.length + " new messages");
|
||||
* try {
|
||||
* // process new messages
|
||||
* idleManager.watch(folder); // keep watching for new messages
|
||||
* } catch (MessagingException mex) {
|
||||
* // handle exception related to the Folder
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* idleManager.watch(folder);
|
||||
* </pre></blockquote>
|
||||
* This delivers the events for each folder in a separate thread, <b>NOT</b>
|
||||
* using the Executor. To deliver all events in a single thread
|
||||
* using the Executor, set the following properties for the Session
|
||||
* (once), and then add listeners and watch the folder as above.
|
||||
* <blockquote><pre>
|
||||
* // the following should be done once...
|
||||
* Properties props = session.getProperties();
|
||||
* props.put("mail.event.scope", "session"); // or "application"
|
||||
* props.put("mail.event.executor", es);
|
||||
* </pre></blockquote>
|
||||
* Note that, after processing new messages in your listener, or doing any
|
||||
* other operations on the folder in any other thread, you need to tell
|
||||
* the IdleManager to watch for more new messages. Unless, of course, you
|
||||
* close the folder.
|
||||
* <p>
|
||||
* The IdleManager is created with a Session, which it uses only to control
|
||||
* debug output. A single IdleManager instance can watch multiple Folders
|
||||
* from multiple Stores and multiple Sessions.
|
||||
* <p>
|
||||
* Due to limitations in the Java SE nio support, a
|
||||
* {@link java.nio.channels.SocketChannel SocketChannel} must be used instead
|
||||
* of a {@link java.net.Socket Socket} to connect to the server. However,
|
||||
* SocketChannels don't support all the features of Sockets, such as connecting
|
||||
* through a SOCKS proxy server. SocketChannels also don't support
|
||||
* simultaneous read and write, which means that the
|
||||
* {@link com.sun.mail.imap.IMAPFolder#idle idle} method can't be used if
|
||||
* SocketChannels are being used; use this IdleManager instead.
|
||||
* To enable support for SocketChannels instead of Sockets, set the
|
||||
* <code>mail.imap.usesocketchannels</code> property in the Session used to
|
||||
* access the IMAP Folder. (Or <code>mail.imaps.usesocketchannels</code> if
|
||||
* you're using the "imaps" protocol.) This will effect all connections in
|
||||
* that Session, but you can create another Session without this property set
|
||||
* if you need to use the features that are incompatible with SocketChannels.
|
||||
* <p>
|
||||
* NOTE: The IdleManager, and all APIs and properties related to it, should
|
||||
* be considered <strong>EXPERIMENTAL</strong>. They may be changed in the
|
||||
* future in ways that are incompatible with applications using the
|
||||
* current APIs.
|
||||
*
|
||||
* @since JavaMail 1.5.2
|
||||
*/
|
||||
public class IdleManager {
|
||||
private Executor es;
|
||||
private Selector selector;
|
||||
private MailLogger logger;
|
||||
private volatile boolean die = false;
|
||||
private volatile boolean running;
|
||||
private Queue<IMAPFolder> toWatch = new ConcurrentLinkedQueue<>();
|
||||
private Queue<IMAPFolder> toAbort = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* Create an IdleManager. The Session is used only to configure
|
||||
* debugging output. The Executor is used to create the
|
||||
* "select" thread.
|
||||
*
|
||||
* @param session the Session containing configuration information
|
||||
* @param es the Executor used to create threads
|
||||
* @exception IOException for Selector failures
|
||||
*/
|
||||
public IdleManager(Session session, Executor es) throws IOException {
|
||||
this.es = es;
|
||||
logger = new MailLogger(this.getClass(), "DEBUG IMAP",
|
||||
session.getDebug(), session.getDebugOut());
|
||||
selector = Selector.open();
|
||||
es.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
logger.fine("IdleManager select starting");
|
||||
try {
|
||||
running = true;
|
||||
select();
|
||||
} finally {
|
||||
running = false;
|
||||
logger.fine("IdleManager select terminating");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the IdleManager currently running? The IdleManager starts
|
||||
* running when the Executor schedules its task. The IdleManager
|
||||
* stops running after its task detects the stop request from the
|
||||
* {@link #stop stop} method, or if it terminates abnormally due
|
||||
* to an unexpected error.
|
||||
*
|
||||
* @return true if the IdleMaanger is running
|
||||
* @since JavaMail 1.5.5
|
||||
*/
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch the Folder for new messages and other events using the IMAP IDLE
|
||||
* command.
|
||||
*
|
||||
* @param folder the folder to watch
|
||||
* @exception MessagingException for errors related to the folder
|
||||
*/
|
||||
public void watch(Folder folder)
|
||||
throws MessagingException {
|
||||
if (die) // XXX - should be IllegalStateException?
|
||||
throw new MessagingException("IdleManager is not running");
|
||||
if (!(folder instanceof IMAPFolder))
|
||||
throw new MessagingException("Can only watch IMAP folders");
|
||||
IMAPFolder ifolder = (IMAPFolder)folder;
|
||||
SocketChannel sc = ifolder.getChannel();
|
||||
if (sc == null) {
|
||||
if (folder.isOpen())
|
||||
throw new MessagingException(
|
||||
"Folder is not using SocketChannels");
|
||||
else
|
||||
throw new MessagingException("Folder is not open");
|
||||
}
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST, "IdleManager watching {0}",
|
||||
folderName(ifolder));
|
||||
// keep trying to start the IDLE command until we're successful.
|
||||
// may block if we're in the middle of aborting an IDLE command.
|
||||
int tries = 0;
|
||||
while (!ifolder.startIdle(this)) {
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager.watch startIdle failed for {0}",
|
||||
folderName(ifolder));
|
||||
tries++;
|
||||
}
|
||||
if (logger.isLoggable(Level.FINEST)) {
|
||||
if (tries > 0)
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager.watch startIdle succeeded for {0}" +
|
||||
" after " + tries + " tries",
|
||||
folderName(ifolder));
|
||||
else
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager.watch startIdle succeeded for {0}",
|
||||
folderName(ifolder));
|
||||
}
|
||||
synchronized (this) {
|
||||
toWatch.add(ifolder);
|
||||
selector.wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that the specified folder abort an IDLE command.
|
||||
* We can't do the abort directly because the DONE message needs
|
||||
* to be sent through the (potentially) SSL socket, which means
|
||||
* we need to be in blocking I/O mode. We can only switch to
|
||||
* blocking I/O mode when not selecting, so wake up the selector,
|
||||
* which will process this request when it wakes up.
|
||||
*/
|
||||
void requestAbort(IMAPFolder folder) {
|
||||
toAbort.add(folder);
|
||||
selector.wakeup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the {@link java.nio.channels.Selector#select select} loop
|
||||
* to poll each watched folder for events sent from the server.
|
||||
*/
|
||||
private void select() {
|
||||
die = false;
|
||||
try {
|
||||
while (!die) {
|
||||
watchAll();
|
||||
logger.finest("IdleManager waiting...");
|
||||
int ns = selector.select();
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager selected {0} channels", ns);
|
||||
if (die || Thread.currentThread().isInterrupted())
|
||||
break;
|
||||
|
||||
/*
|
||||
* Process any selected folders. We cancel the
|
||||
* selection key for any selected folder, so if we
|
||||
* need to continue watching that folder it's added
|
||||
* to the toWatch list again. We can't actually
|
||||
* register that folder again until the previous
|
||||
* selection key is cancelled, so we call selectNow()
|
||||
* just for the side effect of cancelling the selection
|
||||
* keys. But if selectNow() selects something, we
|
||||
* process it before adding folders from the toWatch
|
||||
* queue. And so on until there is nothing to do, at
|
||||
* which point it's safe to register folders from the
|
||||
* toWatch queue. This should be "fair" since each
|
||||
* selection key is used only once before being added
|
||||
* to the toWatch list.
|
||||
*/
|
||||
do {
|
||||
processKeys();
|
||||
} while (selector.selectNow() > 0 || !toAbort.isEmpty());
|
||||
}
|
||||
} catch (InterruptedIOException ex) {
|
||||
logger.log(Level.FINEST, "IdleManager interrupted", ex);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.FINEST, "IdleManager got I/O exception", ex);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.FINEST, "IdleManager got exception", ex);
|
||||
} finally {
|
||||
die = true; // prevent new watches in case of exception
|
||||
logger.finest("IdleManager unwatchAll");
|
||||
try {
|
||||
unwatchAll();
|
||||
selector.close();
|
||||
} catch (IOException ex2) {
|
||||
// nothing to do...
|
||||
logger.log(Level.FINEST, "IdleManager unwatch exception", ex2);
|
||||
}
|
||||
logger.fine("IdleManager exiting");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all of the folders in the queue with the selector,
|
||||
* switching them to nonblocking I/O mode first.
|
||||
*/
|
||||
private void watchAll() {
|
||||
/*
|
||||
* Pull each of the folders from the toWatch queue
|
||||
* and register it.
|
||||
*/
|
||||
IMAPFolder folder;
|
||||
while ((folder = toWatch.poll()) != null) {
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager adding {0} to selector", folderName(folder));
|
||||
try {
|
||||
SocketChannel sc = folder.getChannel();
|
||||
if (sc == null)
|
||||
continue;
|
||||
// has to be non-blocking to select
|
||||
sc.configureBlocking(false);
|
||||
sc.register(selector, SelectionKey.OP_READ, folder);
|
||||
} catch (IOException ex) {
|
||||
// oh well, nothing to do
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager can't register folder", ex);
|
||||
} catch (CancelledKeyException ex) {
|
||||
// this should never happen
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager can't register folder", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the selected keys.
|
||||
*/
|
||||
private void processKeys() throws IOException {
|
||||
IMAPFolder folder;
|
||||
|
||||
/*
|
||||
* First, process any channels with data to read.
|
||||
*/
|
||||
Set<SelectionKey> selectedKeys = selector.selectedKeys();
|
||||
/*
|
||||
* XXX - this is simpler, but it can fail with
|
||||
* ConcurrentModificationException
|
||||
*
|
||||
for (SelectionKey sk : selectedKeys) {
|
||||
selectedKeys.remove(sk); // only process each key once
|
||||
...
|
||||
}
|
||||
*/
|
||||
Iterator<SelectionKey> it = selectedKeys.iterator();
|
||||
while (it.hasNext()) {
|
||||
SelectionKey sk = it.next();
|
||||
it.remove(); // only process each key once
|
||||
// have to cancel so we can switch back to blocking I/O mode
|
||||
sk.cancel();
|
||||
folder = (IMAPFolder)sk.attachment();
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager selected folder: {0}", folderName(folder));
|
||||
SelectableChannel sc = sk.channel();
|
||||
// switch back to blocking to allow normal I/O
|
||||
sc.configureBlocking(true);
|
||||
try {
|
||||
if (folder.handleIdle(false)) {
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager continue watching folder {0}",
|
||||
folderName(folder));
|
||||
// more to do with this folder, select on it again
|
||||
toWatch.add(folder);
|
||||
} else {
|
||||
// done watching this folder,
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager done watching folder {0}",
|
||||
folderName(folder));
|
||||
}
|
||||
} catch (MessagingException ex) {
|
||||
// something went wrong, stop watching this folder
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager got exception for folder: " +
|
||||
folderName(folder), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now, process any folders that we need to abort.
|
||||
*/
|
||||
while ((folder = toAbort.poll()) != null) {
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager aborting IDLE for folder: {0}",
|
||||
folderName(folder));
|
||||
SocketChannel sc = folder.getChannel();
|
||||
if (sc == null)
|
||||
continue;
|
||||
SelectionKey sk = sc.keyFor(selector);
|
||||
// have to cancel so we can switch back to blocking I/O mode
|
||||
if (sk != null)
|
||||
sk.cancel();
|
||||
// switch back to blocking to allow normal I/O
|
||||
sc.configureBlocking(true);
|
||||
|
||||
// if there's a read timeout, have to do the abort in a new thread
|
||||
Socket sock = sc.socket();
|
||||
if (sock != null && sock.getSoTimeout() > 0) {
|
||||
logger.finest("IdleManager requesting DONE with timeout");
|
||||
toWatch.remove(folder);
|
||||
final IMAPFolder folder0 = folder;
|
||||
es.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// send the DONE and wait for the response
|
||||
folder0.idleAbortWait();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
folder.idleAbort(); // send the DONE message
|
||||
// watch for OK response to DONE
|
||||
// XXX - what if we also added it above? should be a nop
|
||||
toWatch.add(folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching all folders. Cancel any selection keys and,
|
||||
* most importantly, switch the channel back to blocking mode.
|
||||
* If there's any folders waiting to be watched, need to abort
|
||||
* them too.
|
||||
*/
|
||||
private void unwatchAll() {
|
||||
IMAPFolder folder;
|
||||
Set<SelectionKey> keys = selector.keys();
|
||||
for (SelectionKey sk : keys) {
|
||||
// have to cancel so we can switch back to blocking I/O mode
|
||||
sk.cancel();
|
||||
folder = (IMAPFolder)sk.attachment();
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager no longer watching folder: {0}",
|
||||
folderName(folder));
|
||||
SelectableChannel sc = sk.channel();
|
||||
// switch back to blocking to allow normal I/O
|
||||
try {
|
||||
sc.configureBlocking(true);
|
||||
folder.idleAbortWait(); // send the DONE message and wait
|
||||
} catch (IOException ex) {
|
||||
// ignore it, channel might be closed
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager exception while aborting idle for folder: " +
|
||||
folderName(folder), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally, process any folders waiting to be watched.
|
||||
*/
|
||||
while ((folder = toWatch.poll()) != null) {
|
||||
if (logger.isLoggable(Level.FINEST))
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager aborting IDLE for unwatched folder: {0}",
|
||||
folderName(folder));
|
||||
SocketChannel sc = folder.getChannel();
|
||||
if (sc == null)
|
||||
continue;
|
||||
try {
|
||||
// channel should still be in blocking mode, but make sure
|
||||
sc.configureBlocking(true);
|
||||
folder.idleAbortWait(); // send the DONE message and wait
|
||||
} catch (IOException ex) {
|
||||
// ignore it, channel might be closed
|
||||
logger.log(Level.FINEST,
|
||||
"IdleManager exception while aborting idle for folder: " +
|
||||
folderName(folder), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the IdleManager. The IdleManager can not be restarted.
|
||||
*/
|
||||
public synchronized void stop() {
|
||||
die = true;
|
||||
logger.fine("IdleManager stopping");
|
||||
selector.wakeup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully qualified name of the folder, for use in log messages.
|
||||
* Essentially just the getURLName method, but ignoring the
|
||||
* MessagingException that can never happen.
|
||||
*/
|
||||
private static String folderName(Folder folder) {
|
||||
try {
|
||||
return folder.getURLName().toString();
|
||||
} catch (MessagingException mex) {
|
||||
// can't happen
|
||||
return folder.getStore().toString() + "/" + folder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,443 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.mail.*;
|
||||
import com.sun.mail.util.PropUtil;
|
||||
import com.sun.mail.util.MailLogger;
|
||||
|
||||
/**
|
||||
* A cache of IMAPMessage objects along with the
|
||||
* mapping from message number to IMAP sequence number.
|
||||
*
|
||||
* All operations on this object are protected by the messageCacheLock
|
||||
* in IMAPFolder.
|
||||
*/
|
||||
public class MessageCache {
|
||||
/*
|
||||
* The array of IMAPMessage objects. Elements of the array might
|
||||
* be null if no one has asked for the message. The array expands
|
||||
* as needed and might be larger than the number of messages in the
|
||||
* folder. The "size" field indicates the number of entries that
|
||||
* are valid.
|
||||
*/
|
||||
private IMAPMessage[] messages;
|
||||
|
||||
/*
|
||||
* A parallel array of sequence numbers for each message. If the
|
||||
* array pointer is null, the sequence number of a message is just
|
||||
* its message number. This is the common case, until a message is
|
||||
* expunged.
|
||||
*/
|
||||
private int[] seqnums;
|
||||
|
||||
/*
|
||||
* The amount of the messages (and seqnum) array that is valid.
|
||||
* Might be less than the actual size of the array.
|
||||
*/
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* The folder these messages belong to.
|
||||
*/
|
||||
private IMAPFolder folder;
|
||||
|
||||
// debugging logger
|
||||
private MailLogger logger;
|
||||
|
||||
/**
|
||||
* Grow the array by at least this much, to avoid constantly
|
||||
* reallocating the array.
|
||||
*/
|
||||
private static final int SLOP = 64;
|
||||
|
||||
/**
|
||||
* Construct a new message cache of the indicated size.
|
||||
*/
|
||||
MessageCache(IMAPFolder folder, IMAPStore store, int size) {
|
||||
this.folder = folder;
|
||||
logger = folder.logger.getSubLogger("messagecache", "DEBUG IMAP MC",
|
||||
store.getMessageCacheDebug());
|
||||
if (logger.isLoggable(Level.CONFIG))
|
||||
logger.config("create cache of size " + size);
|
||||
ensureCapacity(size, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for debugging and testing.
|
||||
*/
|
||||
MessageCache(int size, boolean debug) {
|
||||
this.folder = null;
|
||||
logger = new MailLogger(
|
||||
this.getClass(), "messagecache",
|
||||
"DEBUG IMAP MC", debug, System.out);
|
||||
if (logger.isLoggable(Level.CONFIG))
|
||||
logger.config("create DEBUG cache of size " + size);
|
||||
ensureCapacity(size, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of cache.
|
||||
*
|
||||
* @return the size of the cache
|
||||
*/
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message object for the indicated message number.
|
||||
* If the message object hasn't been created, create it.
|
||||
*
|
||||
* @param msgnum the message number
|
||||
* @return the message
|
||||
*/
|
||||
public IMAPMessage getMessage(int msgnum) {
|
||||
// check range
|
||||
if (msgnum < 1 || msgnum > size)
|
||||
throw new ArrayIndexOutOfBoundsException(
|
||||
"message number (" + msgnum + ") out of bounds (" + size + ")");
|
||||
IMAPMessage msg = messages[msgnum-1];
|
||||
if (msg == null) {
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("create message number " + msgnum);
|
||||
msg = folder.newIMAPMessage(msgnum);
|
||||
messages[msgnum-1] = msg;
|
||||
// mark message expunged if no seqnum
|
||||
if (seqnumOf(msgnum) <= 0) {
|
||||
logger.fine("it's expunged!");
|
||||
msg.setExpunged(true);
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message object for the indicated sequence number.
|
||||
* If the message object hasn't been created, create it.
|
||||
* Return null if there's no message with that sequence number.
|
||||
*
|
||||
* @param seqnum the sequence number of the message
|
||||
* @return the message
|
||||
*/
|
||||
public IMAPMessage getMessageBySeqnum(int seqnum) {
|
||||
int msgnum = msgnumOf(seqnum);
|
||||
if (msgnum < 0) { // XXX - < 1 ?
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("no message seqnum " + seqnum);
|
||||
return null;
|
||||
} else
|
||||
return getMessage(msgnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expunge the message with the given sequence number.
|
||||
*
|
||||
* @param seqnum the sequence number of the message to expunge
|
||||
*/
|
||||
public void expungeMessage(int seqnum) {
|
||||
int msgnum = msgnumOf(seqnum);
|
||||
if (msgnum < 0) {
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("expunge no seqnum " + seqnum);
|
||||
return; // XXX - should never happen
|
||||
}
|
||||
IMAPMessage msg = messages[msgnum-1];
|
||||
if (msg != null) {
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("expunge existing " + msgnum);
|
||||
msg.setExpunged(true);
|
||||
}
|
||||
if (seqnums == null) { // time to fill it in
|
||||
logger.fine("create seqnums array");
|
||||
seqnums = new int[messages.length];
|
||||
for (int i = 1; i < msgnum; i++)
|
||||
seqnums[i-1] = i;
|
||||
seqnums[msgnum - 1] = 0;
|
||||
for (int i = msgnum + 1; i <= seqnums.length; i++)
|
||||
seqnums[i-1] = i - 1;
|
||||
} else {
|
||||
seqnums[msgnum - 1] = 0;
|
||||
for (int i = msgnum + 1; i <= seqnums.length; i++) {
|
||||
assert seqnums[i-1] != 1;
|
||||
if (seqnums[i-1] > 0)
|
||||
seqnums[i-1]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all the expunged messages from the array,
|
||||
* returning a list of removed message objects.
|
||||
*
|
||||
* @return the removed messages
|
||||
*/
|
||||
public IMAPMessage[] removeExpungedMessages() {
|
||||
logger.fine("remove expunged messages");
|
||||
// list of expunged messages
|
||||
List<IMAPMessage> mlist = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* Walk through the array compressing it by copying
|
||||
* higher numbered messages further down in the array,
|
||||
* effectively removing expunged messages from the array.
|
||||
* oldnum is the index we use to walk through the array.
|
||||
* newnum is the index where we copy the next valid message.
|
||||
* oldnum == newnum until we encounter an expunged message.
|
||||
*/
|
||||
int oldnum = 1;
|
||||
int newnum = 1;
|
||||
while (oldnum <= size) {
|
||||
// is message expunged?
|
||||
if (seqnumOf(oldnum) <= 0) {
|
||||
IMAPMessage m = getMessage(oldnum);
|
||||
mlist.add(m);
|
||||
} else {
|
||||
// keep this message
|
||||
if (newnum != oldnum) {
|
||||
// move message down in the array (compact array)
|
||||
messages[newnum-1] = messages[oldnum-1];
|
||||
if (messages[newnum-1] != null)
|
||||
messages[newnum-1].setMessageNumber(newnum);
|
||||
}
|
||||
newnum++;
|
||||
}
|
||||
oldnum++;
|
||||
}
|
||||
seqnums = null;
|
||||
shrink(newnum, oldnum);
|
||||
|
||||
IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("return " + rmsgs.length);
|
||||
mlist.toArray(rmsgs);
|
||||
return rmsgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove expunged messages in msgs from the array,
|
||||
* returning a list of removed message objects.
|
||||
* All messages in msgs must be IMAPMessage objects
|
||||
* from this folder.
|
||||
*
|
||||
* @param msgs the messages
|
||||
* @return the removed messages
|
||||
*/
|
||||
public IMAPMessage[] removeExpungedMessages(Message[] msgs) {
|
||||
logger.fine("remove expunged messages");
|
||||
// list of expunged messages
|
||||
List<IMAPMessage> mlist = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* Copy the message numbers of the expunged messages into
|
||||
* a separate array and sort the array to make it easier to
|
||||
* process later.
|
||||
*/
|
||||
int[] mnum = new int[msgs.length];
|
||||
for (int i = 0; i < msgs.length; i++)
|
||||
mnum[i] = msgs[i].getMessageNumber();
|
||||
Arrays.sort(mnum);
|
||||
|
||||
/*
|
||||
* Walk through the array compressing it by copying
|
||||
* higher numbered messages further down in the array,
|
||||
* effectively removing expunged messages from the array.
|
||||
* oldnum is the index we use to walk through the array.
|
||||
* newnum is the index where we copy the next valid message.
|
||||
* oldnum == newnum until we encounter an expunged message.
|
||||
*
|
||||
* Even though we know the message number of the first possibly
|
||||
* expunged message, we still start scanning at message number 1
|
||||
* so that we can check whether there's any message whose
|
||||
* sequence number is different than its message number. If there
|
||||
* is, we can't throw away the seqnums array when we're done.
|
||||
*/
|
||||
int oldnum = 1;
|
||||
int newnum = 1;
|
||||
int mnumi = 0; // index into mnum
|
||||
boolean keepSeqnums = false;
|
||||
while (oldnum <= size) {
|
||||
/*
|
||||
* Are there still expunged messsages in msgs to consider,
|
||||
* and is the message we're considering the next one in the
|
||||
* list, and is it expunged?
|
||||
*/
|
||||
if (mnumi < mnum.length &&
|
||||
oldnum == mnum[mnumi] &&
|
||||
seqnumOf(oldnum) <= 0) {
|
||||
IMAPMessage m = getMessage(oldnum);
|
||||
mlist.add(m);
|
||||
/*
|
||||
* Just in case there are duplicate entries in the msgs array,
|
||||
* we keep advancing mnumi past any duplicates, but of course
|
||||
* stop when we get to the end of the array.
|
||||
*/
|
||||
while (mnumi < mnum.length && mnum[mnumi] <= oldnum)
|
||||
mnumi++; // consider next message in array
|
||||
} else {
|
||||
// keep this message
|
||||
if (newnum != oldnum) {
|
||||
// move message down in the array (compact array)
|
||||
messages[newnum-1] = messages[oldnum-1];
|
||||
if (messages[newnum-1] != null)
|
||||
messages[newnum-1].setMessageNumber(newnum);
|
||||
if (seqnums != null)
|
||||
seqnums[newnum-1] = seqnums[oldnum-1];
|
||||
}
|
||||
if (seqnums != null && seqnums[newnum-1] != newnum)
|
||||
keepSeqnums = true;
|
||||
newnum++;
|
||||
}
|
||||
oldnum++;
|
||||
}
|
||||
|
||||
if (!keepSeqnums)
|
||||
seqnums = null;
|
||||
shrink(newnum, oldnum);
|
||||
|
||||
IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("return " + rmsgs.length);
|
||||
mlist.toArray(rmsgs);
|
||||
return rmsgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrink the messages and seqnums arrays. newend is one past last
|
||||
* valid element. oldend is one past the previous last valid element.
|
||||
*/
|
||||
private void shrink(int newend, int oldend) {
|
||||
size = newend - 1;
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("size now " + size);
|
||||
if (size == 0) { // no messages left
|
||||
messages = null;
|
||||
seqnums = null;
|
||||
} else if (size > SLOP && size < messages.length / 2) {
|
||||
// if array shrinks by too much, reallocate it
|
||||
logger.fine("reallocate array");
|
||||
IMAPMessage[] newm = new IMAPMessage[size + SLOP];
|
||||
System.arraycopy(messages, 0, newm, 0, size);
|
||||
messages = newm;
|
||||
if (seqnums != null) {
|
||||
int[] news = new int[size + SLOP];
|
||||
System.arraycopy(seqnums, 0, news, 0, size);
|
||||
seqnums = news;
|
||||
}
|
||||
} else {
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("clean " + newend + " to " + oldend);
|
||||
// clear out unused entries in array
|
||||
for (int msgnum = newend; msgnum < oldend; msgnum++) {
|
||||
messages[msgnum-1] = null;
|
||||
if (seqnums != null)
|
||||
seqnums[msgnum-1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add count messages to the cache.
|
||||
* newSeqNum is the sequence number of the first message added.
|
||||
*
|
||||
* @param count the number of messges
|
||||
* @param newSeqNum sequence number of first message
|
||||
*/
|
||||
public void addMessages(int count, int newSeqNum) {
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("add " + count + " messages");
|
||||
// don't have to do anything other than making sure there's space
|
||||
ensureCapacity(size + count, newSeqNum);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure the arrays are at least big enough to hold
|
||||
* "newsize" messages.
|
||||
*/
|
||||
private void ensureCapacity(int newsize, int newSeqNum) {
|
||||
if (messages == null)
|
||||
messages = new IMAPMessage[newsize + SLOP];
|
||||
else if (messages.length < newsize) {
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("expand capacity to " + newsize);
|
||||
IMAPMessage[] newm = new IMAPMessage[newsize + SLOP];
|
||||
System.arraycopy(messages, 0, newm, 0, messages.length);
|
||||
messages = newm;
|
||||
if (seqnums != null) {
|
||||
int[] news = new int[newsize + SLOP];
|
||||
System.arraycopy(seqnums, 0, news, 0, seqnums.length);
|
||||
for (int i = size; i < news.length; i++)
|
||||
news[i] = newSeqNum++;
|
||||
seqnums = news;
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("message " + newsize +
|
||||
" has sequence number " + seqnums[newsize-1]);
|
||||
}
|
||||
} else if (newsize < size) { // shrinking?
|
||||
// this should never happen
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("shrink capacity to " + newsize);
|
||||
for (int msgnum = newsize + 1; msgnum <= size; msgnum++) {
|
||||
messages[msgnum-1] = null;
|
||||
if (seqnums != null)
|
||||
seqnums[msgnum-1] = -1;
|
||||
}
|
||||
}
|
||||
size = newsize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sequence number for the given message number.
|
||||
*
|
||||
* @param msgnum the message number
|
||||
* @return the sequence number
|
||||
*/
|
||||
public int seqnumOf(int msgnum) {
|
||||
if (seqnums == null)
|
||||
return msgnum;
|
||||
else {
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("msgnum " + msgnum + " is seqnum " +
|
||||
seqnums[msgnum-1]);
|
||||
return seqnums[msgnum-1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message number for the given sequence number.
|
||||
*/
|
||||
private int msgnumOf(int seqnum) {
|
||||
if (seqnums == null)
|
||||
return seqnum;
|
||||
if (seqnum < 1) { // should never happen
|
||||
if (logger.isLoggable(Level.FINE))
|
||||
logger.fine("bad seqnum " + seqnum);
|
||||
return -1;
|
||||
}
|
||||
for (int msgnum = seqnum; msgnum <= size; msgnum++) {
|
||||
if (seqnums[msgnum-1] == seqnum)
|
||||
return msgnum;
|
||||
if (seqnums[msgnum-1] > seqnum)
|
||||
break; // message doesn't exist
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import javax.mail.Folder;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.event.MessageCountEvent;
|
||||
|
||||
/**
|
||||
* This class provides notification of messages that have been removed
|
||||
* since the folder was last synchronized.
|
||||
*
|
||||
* @since JavaMail 1.5.1
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class MessageVanishedEvent extends MessageCountEvent {
|
||||
|
||||
/**
|
||||
* The message UIDs.
|
||||
*/
|
||||
private long[] uids;
|
||||
|
||||
// a reusable empty array
|
||||
private static final Message[] noMessages = { };
|
||||
|
||||
private static final long serialVersionUID = 2142028010250024922L;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param folder the containing folder
|
||||
* @param uids the UIDs for the vanished messages
|
||||
*/
|
||||
public MessageVanishedEvent(Folder folder, long[] uids) {
|
||||
super(folder, REMOVED, true, noMessages);
|
||||
this.uids = uids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the UIDs for this event.
|
||||
*
|
||||
* @return the UIDs
|
||||
*/
|
||||
public long[] getUIDs() {
|
||||
return uids;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import javax.mail.Message;
|
||||
import javax.mail.search.SearchTerm;
|
||||
|
||||
/**
|
||||
* Find messages that have been modified since a given MODSEQ value.
|
||||
* Relies on the server implementing the CONDSTORE extension
|
||||
* (<A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>).
|
||||
*
|
||||
* @since JavaMail 1.5.1
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
public final class ModifiedSinceTerm extends SearchTerm {
|
||||
|
||||
private long modseq;
|
||||
|
||||
private static final long serialVersionUID = 5151457469634727992L;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param modseq modification sequence number
|
||||
*/
|
||||
public ModifiedSinceTerm(long modseq) {
|
||||
this.modseq = modseq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the modseq.
|
||||
*
|
||||
* @return the modseq
|
||||
*/
|
||||
public long getModSeq() {
|
||||
return modseq;
|
||||
}
|
||||
|
||||
/**
|
||||
* The match method.
|
||||
*
|
||||
* @param msg the date comparator is applied to this Message's
|
||||
* MODSEQ
|
||||
* @return true if the comparison succeeds, otherwise false
|
||||
*/
|
||||
@Override
|
||||
public boolean match(Message msg) {
|
||||
long m;
|
||||
|
||||
try {
|
||||
if (msg instanceof IMAPMessage)
|
||||
m = ((IMAPMessage)msg).getModSeq();
|
||||
else
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m >= modseq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof ModifiedSinceTerm))
|
||||
return false;
|
||||
return modseq == ((ModifiedSinceTerm)obj).modseq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hashCode for this object.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int)modseq;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.util.Date;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.search.SearchTerm;
|
||||
|
||||
/**
|
||||
* Find messages that are older than a given interval (in seconds).
|
||||
* Relies on the server implementing the WITHIN search extension
|
||||
* (<A HREF="http://www.ietf.org/rfc/rfc5032.txt">RFC 5032</A>).
|
||||
*
|
||||
* @since JavaMail 1.5.1
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
public final class OlderTerm extends SearchTerm {
|
||||
|
||||
private int interval;
|
||||
|
||||
private static final long serialVersionUID = 3951078948727995682L;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param interval number of seconds older
|
||||
*/
|
||||
public OlderTerm(int interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interval.
|
||||
*
|
||||
* @return the interval
|
||||
*/
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The match method.
|
||||
*
|
||||
* @param msg the date comparator is applied to this Message's
|
||||
* received date
|
||||
* @return true if the comparison succeeds, otherwise false
|
||||
*/
|
||||
@Override
|
||||
public boolean match(Message msg) {
|
||||
Date d;
|
||||
|
||||
try {
|
||||
d = msg.getReceivedDate();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d == null)
|
||||
return false;
|
||||
|
||||
return d.getTime() <=
|
||||
System.currentTimeMillis() - ((long)interval * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof OlderTerm))
|
||||
return false;
|
||||
return interval == ((OlderTerm)obj).interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hashCode for this object.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import javax.mail.AuthenticationFailedException;
|
||||
|
||||
/**
|
||||
* A special kind of AuthenticationFailedException that indicates that
|
||||
* the reason for the failure was an IMAP REFERRAL in the response code.
|
||||
* See <a href="http://www.ietf.org/rfc/rfc2221.txt">RFC 2221</a> for details.
|
||||
*
|
||||
* @since JavaMail 1.5.5
|
||||
*/
|
||||
|
||||
public class ReferralException extends AuthenticationFailedException {
|
||||
|
||||
private String url;
|
||||
private String text;
|
||||
|
||||
private static final long serialVersionUID = -3414063558596287683L;
|
||||
|
||||
/**
|
||||
* Constructs an ReferralException with the specified URL and text.
|
||||
*
|
||||
* @param text the detail message
|
||||
* @param url the URL
|
||||
*/
|
||||
public ReferralException(String url, String text) {
|
||||
super("[REFERRAL " + url + "] " + text);
|
||||
this.url = url;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IMAP URL in the referral.
|
||||
*
|
||||
* @return the IMAP URL
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the text sent by the server along with the referral.
|
||||
*
|
||||
* @return the text
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import com.sun.mail.imap.protocol.UIDSet;
|
||||
|
||||
/**
|
||||
* Resynchronization data as defined by the QRESYNC extension
|
||||
* (<A HREF="http://www.ietf.org/rfc/rfc5162.txt">RFC 5162</A>).
|
||||
* An instance of <CODE>ResyncData</CODE> is supplied to the
|
||||
* {@link com.sun.mail.imap.IMAPFolder#open(int,com.sun.mail.imap.ResyncData)
|
||||
* IMAPFolder open} method.
|
||||
* The CONDSTORE <CODE>ResyncData</CODE> instance is used to enable the
|
||||
* CONDSTORE extension
|
||||
* (<A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>).
|
||||
* A <CODE>ResyncData</CODE> instance with uidvalidity and modseq values
|
||||
* is used to enable the QRESYNC extension.
|
||||
*
|
||||
* @since JavaMail 1.5.1
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class ResyncData {
|
||||
private long uidvalidity = -1;
|
||||
private long modseq = -1;
|
||||
private UIDSet[] uids = null;
|
||||
|
||||
/**
|
||||
* Used to enable only the CONDSTORE extension.
|
||||
*/
|
||||
public static final ResyncData CONDSTORE = new ResyncData(-1, -1);
|
||||
|
||||
/**
|
||||
* Used to report on changes since the specified modseq.
|
||||
* If the UIDVALIDITY of the folder has changed, no message
|
||||
* changes will be reported. The application must check the
|
||||
* UIDVALIDITY of the folder after open to make sure it's
|
||||
* the expected folder.
|
||||
*
|
||||
* @param uidvalidity the UIDVALIDITY
|
||||
* @param modseq the MODSEQ
|
||||
*/
|
||||
public ResyncData(long uidvalidity, long modseq) {
|
||||
this.uidvalidity = uidvalidity;
|
||||
this.modseq = modseq;
|
||||
this.uids = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to limit the reported message changes to those with UIDs
|
||||
* in the specified range.
|
||||
*
|
||||
* @param uidvalidity the UIDVALIDITY
|
||||
* @param modseq the MODSEQ
|
||||
* @param uidFirst the first UID
|
||||
* @param uidLast the last UID
|
||||
*/
|
||||
public ResyncData(long uidvalidity, long modseq,
|
||||
long uidFirst, long uidLast) {
|
||||
this.uidvalidity = uidvalidity;
|
||||
this.modseq = modseq;
|
||||
this.uids = new UIDSet[] { new UIDSet(uidFirst, uidLast) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to limit the reported message changes to those with the
|
||||
* specified UIDs.
|
||||
*
|
||||
* @param uidvalidity the UIDVALIDITY
|
||||
* @param modseq the MODSEQ
|
||||
* @param uids the UID values
|
||||
*/
|
||||
public ResyncData(long uidvalidity, long modseq, long[] uids) {
|
||||
this.uidvalidity = uidvalidity;
|
||||
this.modseq = modseq;
|
||||
this.uids = UIDSet.createUIDSets(uids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UIDVALIDITY value specified when this instance was created.
|
||||
*
|
||||
* @return the UIDVALIDITY value
|
||||
*/
|
||||
public long getUIDValidity() {
|
||||
return uidvalidity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MODSEQ value specified when this instance was created.
|
||||
*
|
||||
* @return the MODSEQ value
|
||||
*/
|
||||
public long getModSeq() {
|
||||
return modseq;
|
||||
}
|
||||
|
||||
/*
|
||||
* Package private. IMAPProtocol gets this data indirectly
|
||||
* using Utility.getResyncUIDSet().
|
||||
*/
|
||||
UIDSet[] getUIDSet() {
|
||||
return uids;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,444 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The Rights class represents the set of rights for an authentication
|
||||
* identifier (for instance, a user or a group). <p>
|
||||
*
|
||||
* A right is represented by the <code>Rights.Right</code>
|
||||
* inner class. <p>
|
||||
*
|
||||
* A set of standard rights are predefined (see RFC 2086). Most folder
|
||||
* implementations are expected to support these rights. Some
|
||||
* implementations may also support site-defined rights. <p>
|
||||
*
|
||||
* The following code sample illustrates how to examine your
|
||||
* rights for a folder.
|
||||
* <pre>
|
||||
*
|
||||
* Rights rights = folder.myRights();
|
||||
*
|
||||
* // Check if I can write this folder
|
||||
* if (rights.contains(Rights.Right.WRITE))
|
||||
* System.out.println("Can write folder");
|
||||
*
|
||||
* // Now give Joe all my rights, except the ability to write the folder
|
||||
* rights.remove(Rights.Right.WRITE);
|
||||
* ACL acl = new ACL("joe", rights);
|
||||
* folder.setACL(acl);
|
||||
* </pre>
|
||||
* <p>
|
||||
*
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class Rights implements Cloneable {
|
||||
|
||||
private boolean[] rights = new boolean[128]; // XXX
|
||||
|
||||
/**
|
||||
* This inner class represents an individual right. A set
|
||||
* of standard rights objects are predefined here.
|
||||
*/
|
||||
public static final class Right {
|
||||
private static Right[] cache = new Right[128];
|
||||
|
||||
// XXX - initialization order?
|
||||
/**
|
||||
* Lookup - mailbox is visible to LIST/LSUB commands.
|
||||
*/
|
||||
public static final Right LOOKUP = getInstance('l');
|
||||
|
||||
/**
|
||||
* Read - SELECT the mailbox, perform CHECK, FETCH, PARTIAL,
|
||||
* SEARCH, COPY from mailbox
|
||||
*/
|
||||
public static final Right READ = getInstance('r');
|
||||
|
||||
/**
|
||||
* Keep seen/unseen information across sessions - STORE \SEEN flag.
|
||||
*/
|
||||
public static final Right KEEP_SEEN = getInstance('s');
|
||||
|
||||
/**
|
||||
* Write - STORE flags other than \SEEN and \DELETED.
|
||||
*/
|
||||
public static final Right WRITE = getInstance('w');
|
||||
|
||||
/**
|
||||
* Insert - perform APPEND, COPY into mailbox.
|
||||
*/
|
||||
public static final Right INSERT = getInstance('i');
|
||||
|
||||
/**
|
||||
* Post - send mail to submission address for mailbox,
|
||||
* not enforced by IMAP4 itself.
|
||||
*/
|
||||
public static final Right POST = getInstance('p');
|
||||
|
||||
/**
|
||||
* Create - CREATE new sub-mailboxes in any implementation-defined
|
||||
* hierarchy, RENAME or DELETE mailbox.
|
||||
*/
|
||||
public static final Right CREATE = getInstance('c');
|
||||
|
||||
/**
|
||||
* Delete - STORE \DELETED flag, perform EXPUNGE.
|
||||
*/
|
||||
public static final Right DELETE = getInstance('d');
|
||||
|
||||
/**
|
||||
* Administer - perform SETACL.
|
||||
*/
|
||||
public static final Right ADMINISTER = getInstance('a');
|
||||
|
||||
char right; // the right represented by this Right object
|
||||
|
||||
/**
|
||||
* Private constructor used only by getInstance.
|
||||
*/
|
||||
private Right(char right) {
|
||||
if ((int)right >= 128)
|
||||
throw new IllegalArgumentException("Right must be ASCII");
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Right object representing the specified character.
|
||||
* Characters are assigned per RFC 2086.
|
||||
*
|
||||
* @param right the character representing the right
|
||||
* @return the Right object
|
||||
*/
|
||||
public static synchronized Right getInstance(char right) {
|
||||
if ((int)right >= 128)
|
||||
throw new IllegalArgumentException("Right must be ASCII");
|
||||
if (cache[(int)right] == null)
|
||||
cache[(int)right] = new Right(right);
|
||||
return cache[(int)right];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(right);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct an empty Rights object.
|
||||
*/
|
||||
public Rights() { }
|
||||
|
||||
/**
|
||||
* Construct a Rights object initialized with the given rights.
|
||||
*
|
||||
* @param rights the rights for initialization
|
||||
*/
|
||||
public Rights(Rights rights) {
|
||||
System.arraycopy(rights.rights, 0, this.rights, 0, this.rights.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Rights object initialized with the given rights.
|
||||
*
|
||||
* @param rights the rights for initialization
|
||||
*/
|
||||
public Rights(String rights) {
|
||||
for (int i = 0; i < rights.length(); i++)
|
||||
add(Right.getInstance(rights.charAt(i)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Rights object initialized with the given right.
|
||||
*
|
||||
* @param right the right for initialization
|
||||
*/
|
||||
public Rights(Right right) {
|
||||
this.rights[(int)right.right] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified right to this Rights object.
|
||||
*
|
||||
* @param right the right to add
|
||||
*/
|
||||
public void add(Right right) {
|
||||
this.rights[(int)right.right] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the rights in the given Rights object to this
|
||||
* Rights object.
|
||||
*
|
||||
* @param rights Rights object
|
||||
*/
|
||||
public void add(Rights rights) {
|
||||
for (int i = 0; i < rights.rights.length; i++)
|
||||
if (rights.rights[i])
|
||||
this.rights[i] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified right from this Rights object.
|
||||
*
|
||||
* @param right the right to be removed
|
||||
*/
|
||||
public void remove(Right right) {
|
||||
this.rights[(int)right.right] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all rights in the given Rights object from this
|
||||
* Rights object.
|
||||
*
|
||||
* @param rights the rights to be removed
|
||||
*/
|
||||
public void remove(Rights rights) {
|
||||
for (int i = 0; i < rights.rights.length; i++)
|
||||
if (rights.rights[i])
|
||||
this.rights[i] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the specified right is present in this Rights object.
|
||||
*
|
||||
* @param right the Right to check
|
||||
* @return true of the given right is present, otherwise false.
|
||||
*/
|
||||
public boolean contains(Right right) {
|
||||
return this.rights[(int)right.right];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether all the rights in the specified Rights object are
|
||||
* present in this Rights object.
|
||||
*
|
||||
* @param rights the Rights to check
|
||||
* @return true if all rights in the given Rights object are present,
|
||||
* otherwise false.
|
||||
*/
|
||||
public boolean contains(Rights rights) {
|
||||
for (int i = 0; i < rights.rights.length; i++)
|
||||
if (rights.rights[i] && !this.rights[i])
|
||||
return false;
|
||||
|
||||
// If we've made it till here, return true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the two Rights objects are equal.
|
||||
*
|
||||
* @return true if they're equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Rights))
|
||||
return false;
|
||||
|
||||
Rights rights = (Rights)obj;
|
||||
|
||||
for (int i = 0; i < rights.rights.length; i++)
|
||||
if (rights.rights[i] != this.rights[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hash code for this Rights object.
|
||||
*
|
||||
* @return the hash code
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 0;
|
||||
for (int i = 0; i < this.rights.length; i++)
|
||||
if (this.rights[i])
|
||||
hash++;
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the rights in this Rights object. Returns
|
||||
* an array of size zero if no rights are set.
|
||||
*
|
||||
* @return array of Rights.Right objects representing rights
|
||||
*/
|
||||
public Right[] getRights() {
|
||||
List<Right> v = new ArrayList<>();
|
||||
for (int i = 0; i < this.rights.length; i++)
|
||||
if (this.rights[i])
|
||||
v.add(Right.getInstance((char)i));
|
||||
return v.toArray(new Right[v.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clone of this Rights object.
|
||||
*/
|
||||
@Override
|
||||
public Object clone() {
|
||||
Rights r = null;
|
||||
try {
|
||||
r = (Rights)super.clone();
|
||||
r.rights = new boolean[128];
|
||||
System.arraycopy(this.rights, 0, r.rights, 0, this.rights.length);
|
||||
} catch (CloneNotSupportedException cex) {
|
||||
// ignore, can't happen
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < this.rights.length; i++)
|
||||
if (this.rights[i])
|
||||
sb.append((char)i);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/*****
|
||||
public static void main(String argv[]) throws Exception {
|
||||
// a new rights object
|
||||
Rights f1 = new Rights();
|
||||
f1.add(Rights.Right.READ);
|
||||
f1.add(Rights.Right.WRITE);
|
||||
f1.add(Rights.Right.CREATE);
|
||||
f1.add(Rights.Right.DELETE);
|
||||
|
||||
// check copy constructor
|
||||
Rights fc = new Rights(f1);
|
||||
if (f1.equals(fc) && fc.equals(f1))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
// check clone
|
||||
fc = (Rights)f1.clone();
|
||||
if (f1.equals(fc) && fc.equals(f1))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
// add a right and make sure it still works right
|
||||
f1.add(Rights.Right.ADMINISTER);
|
||||
|
||||
// shouldn't be equal here
|
||||
if (!f1.equals(fc) && !fc.equals(f1))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
// check clone
|
||||
fc = (Rights)f1.clone();
|
||||
if (f1.equals(fc) && fc.equals(f1))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
fc.add(Rights.Right.INSERT);
|
||||
if (!f1.equals(fc) && !fc.equals(f1))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
// check copy constructor
|
||||
fc = new Rights(f1);
|
||||
if (f1.equals(fc) && fc.equals(f1))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
// another new rights object
|
||||
Rights f2 = new Rights(Rights.Right.READ);
|
||||
f2.add(Rights.Right.WRITE);
|
||||
|
||||
if (f1.contains(Rights.Right.READ))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
if (f1.contains(Rights.Right.WRITE))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
if (f1.contains(Rights.Right.CREATE))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
if (f1.contains(Rights.Right.DELETE))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
if (f2.contains(Rights.Right.WRITE))
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
|
||||
System.out.println("----------------");
|
||||
|
||||
Right[] r = f1.getRights();
|
||||
for (int i = 0; i < r.length; i++)
|
||||
System.out.println(r[i]);
|
||||
System.out.println("----------------");
|
||||
|
||||
if (f1.contains(f2)) // this should be true
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
if (!f2.contains(f1)) // this should be false
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
|
||||
Rights f3 = new Rights();
|
||||
f3.add(Rights.Right.READ);
|
||||
f3.add(Rights.Right.WRITE);
|
||||
f3.add(Rights.Right.CREATE);
|
||||
f3.add(Rights.Right.DELETE);
|
||||
f3.add(Rights.Right.ADMINISTER);
|
||||
f3.add(Rights.Right.LOOKUP);
|
||||
|
||||
f1.add(Rights.Right.LOOKUP);
|
||||
|
||||
if (f1.equals(f3))
|
||||
System.out.println("equals success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
if (f3.equals(f1))
|
||||
System.out.println("equals success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
System.out.println("f1 hash code " + f1.hashCode());
|
||||
System.out.println("f3 hash code " + f3.hashCode());
|
||||
if (f1.hashCode() == f3.hashCode())
|
||||
System.out.println("success");
|
||||
else
|
||||
System.out.println("fail");
|
||||
}
|
||||
****/
|
||||
}
|
||||
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
/**
|
||||
* A particular sort criteria, as defined by
|
||||
* <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
|
||||
* Sort criteria are used with the
|
||||
* {@link IMAPFolder#getSortedMessages getSortedMessages} method.
|
||||
* Multiple sort criteria are specified in an array with the order in
|
||||
* the array specifying the order in which the sort criteria are applied.
|
||||
*
|
||||
* @since JavaMail 1.4.4
|
||||
*/
|
||||
public final class SortTerm {
|
||||
/**
|
||||
* Sort by message arrival date and time.
|
||||
*/
|
||||
public static final SortTerm ARRIVAL = new SortTerm("ARRIVAL");
|
||||
|
||||
/**
|
||||
* Sort by email address of first Cc recipient.
|
||||
*/
|
||||
public static final SortTerm CC = new SortTerm("CC");
|
||||
|
||||
/**
|
||||
* Sort by sent date and time.
|
||||
*/
|
||||
public static final SortTerm DATE = new SortTerm("DATE");
|
||||
|
||||
/**
|
||||
* Sort by first From email address.
|
||||
*/
|
||||
public static final SortTerm FROM = new SortTerm("FROM");
|
||||
|
||||
/**
|
||||
* Reverse the sort order of the following item.
|
||||
*/
|
||||
public static final SortTerm REVERSE = new SortTerm("REVERSE");
|
||||
|
||||
/**
|
||||
* Sort by the message size.
|
||||
*/
|
||||
public static final SortTerm SIZE = new SortTerm("SIZE");
|
||||
|
||||
/**
|
||||
* Sort by the base subject text. Note that the "base subject"
|
||||
* is defined by RFC 5256 and doesn't include items such as "Re:"
|
||||
* in the subject header.
|
||||
*/
|
||||
public static final SortTerm SUBJECT = new SortTerm("SUBJECT");
|
||||
|
||||
/**
|
||||
* Sort by email address of first To recipient.
|
||||
*/
|
||||
public static final SortTerm TO = new SortTerm("TO");
|
||||
|
||||
private String term;
|
||||
private SortTerm(String term) {
|
||||
this.term = term;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return term;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
import javax.mail.*;
|
||||
|
||||
import com.sun.mail.imap.protocol.MessageSet;
|
||||
import com.sun.mail.imap.protocol.UIDSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holder for some static utility methods.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public final class Utility {
|
||||
|
||||
// Cannot be initialized
|
||||
private Utility() { }
|
||||
|
||||
/**
|
||||
* Run thru the given array of messages, apply the given
|
||||
* Condition on each message and generate sets of contiguous
|
||||
* sequence-numbers for the successful messages. If a message
|
||||
* in the given array is found to be expunged, it is ignored.
|
||||
*
|
||||
* ASSERT: Since this method uses and returns message sequence
|
||||
* numbers, you should use this method only when holding the
|
||||
* messageCacheLock.
|
||||
*
|
||||
* @param msgs the messages
|
||||
* @param cond the condition to check
|
||||
* @return the MessageSet array
|
||||
*/
|
||||
public static MessageSet[] toMessageSet(Message[] msgs, Condition cond) {
|
||||
List<MessageSet> v = new ArrayList<>(1);
|
||||
int current, next;
|
||||
|
||||
IMAPMessage msg;
|
||||
for (int i = 0; i < msgs.length; i++) {
|
||||
msg = (IMAPMessage)msgs[i];
|
||||
if (msg.isExpunged()) // expunged message, skip it
|
||||
continue;
|
||||
|
||||
current = msg.getSequenceNumber();
|
||||
// Apply the condition. If it fails, skip it.
|
||||
if ((cond != null) && !cond.test(msg))
|
||||
continue;
|
||||
|
||||
MessageSet set = new MessageSet();
|
||||
set.start = current;
|
||||
|
||||
// Look for contiguous sequence numbers
|
||||
for (++i; i < msgs.length; i++) {
|
||||
// get next message
|
||||
msg = (IMAPMessage)msgs[i];
|
||||
|
||||
if (msg.isExpunged()) // expunged message, skip it
|
||||
continue;
|
||||
next = msg.getSequenceNumber();
|
||||
|
||||
// Does this message match our condition ?
|
||||
if ((cond != null) && !cond.test(msg))
|
||||
continue;
|
||||
|
||||
if (next == current+1)
|
||||
current = next;
|
||||
else { // break in sequence
|
||||
// We need to reexamine this message at the top of
|
||||
// the outer loop, so decrement 'i' to cancel the
|
||||
// outer loop's autoincrement
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
set.end = current;
|
||||
v.add(set);
|
||||
}
|
||||
|
||||
if (v.isEmpty()) // No valid messages
|
||||
return null;
|
||||
else {
|
||||
return v.toArray(new MessageSet[v.size()]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sort (a copy of) the given array of messages and then
|
||||
* run thru the sorted array of messages, apply the given
|
||||
* Condition on each message and generate sets of contiguous
|
||||
* sequence-numbers for the successful messages. If a message
|
||||
* in the given array is found to be expunged, it is ignored.
|
||||
*
|
||||
* ASSERT: Since this method uses and returns message sequence
|
||||
* numbers, you should use this method only when holding the
|
||||
* messageCacheLock.
|
||||
*
|
||||
* @param msgs the messages
|
||||
* @param cond the condition to check
|
||||
* @return the MessageSet array
|
||||
* @since JavaMail 1.5.4
|
||||
*/
|
||||
public static MessageSet[] toMessageSetSorted(Message[] msgs,
|
||||
Condition cond) {
|
||||
/*
|
||||
* XXX - This is quick and dirty. A more efficient strategy would be
|
||||
* to generate an array of message numbers by applying the condition
|
||||
* (with zero indicating the message doesn't satisfy the condition),
|
||||
* sort it, and then convert it to a MessageSet skipping all the zeroes.
|
||||
*/
|
||||
msgs = msgs.clone();
|
||||
Arrays.sort(msgs,
|
||||
new Comparator<Message>() {
|
||||
@Override
|
||||
public int compare(Message msg1, Message msg2) {
|
||||
return msg1.getMessageNumber() - msg2.getMessageNumber();
|
||||
}
|
||||
});
|
||||
return toMessageSet(msgs, cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return UIDSets for the messages. Note that the UIDs
|
||||
* must have already been fetched for the messages.
|
||||
*
|
||||
* @param msgs the messages
|
||||
* @return the UIDSet array
|
||||
*/
|
||||
public static UIDSet[] toUIDSet(Message[] msgs) {
|
||||
List<UIDSet> v = new ArrayList<>(1);
|
||||
long current, next;
|
||||
|
||||
IMAPMessage msg;
|
||||
for (int i = 0; i < msgs.length; i++) {
|
||||
msg = (IMAPMessage)msgs[i];
|
||||
if (msg.isExpunged()) // expunged message, skip it
|
||||
continue;
|
||||
|
||||
current = msg.getUID();
|
||||
|
||||
UIDSet set = new UIDSet();
|
||||
set.start = current;
|
||||
|
||||
// Look for contiguous UIDs
|
||||
for (++i; i < msgs.length; i++) {
|
||||
// get next message
|
||||
msg = (IMAPMessage)msgs[i];
|
||||
|
||||
if (msg.isExpunged()) // expunged message, skip it
|
||||
continue;
|
||||
next = msg.getUID();
|
||||
|
||||
if (next == current+1)
|
||||
current = next;
|
||||
else { // break in sequence
|
||||
// We need to reexamine this message at the top of
|
||||
// the outer loop, so decrement 'i' to cancel the
|
||||
// outer loop's autoincrement
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
set.end = current;
|
||||
v.add(set);
|
||||
}
|
||||
|
||||
if (v.isEmpty()) // No valid messages
|
||||
return null;
|
||||
else {
|
||||
return v.toArray(new UIDSet[v.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the ResyncData UIDSet available to IMAPProtocol,
|
||||
* which is in a different package. Note that this class
|
||||
* is not included in the public javadocs, thus "hiding"
|
||||
* this method.
|
||||
*
|
||||
* @param rd the ResyncData
|
||||
* @return the UIDSet array
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
public static UIDSet[] getResyncUIDSet(ResyncData rd) {
|
||||
return rd.getUIDSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface defines the test to be executed in
|
||||
* <code>toMessageSet()</code>.
|
||||
*/
|
||||
public static interface Condition {
|
||||
public boolean test(IMAPMessage message);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap;
|
||||
|
||||
import java.util.Date;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.search.SearchTerm;
|
||||
|
||||
/**
|
||||
* Find messages that are younger than a given interval (in seconds).
|
||||
* Relies on the server implementing the WITHIN search extension
|
||||
* (<A HREF="http://www.ietf.org/rfc/rfc5032.txt">RFC 5032</A>).
|
||||
*
|
||||
* @since JavaMail 1.5.1
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
public final class YoungerTerm extends SearchTerm {
|
||||
|
||||
private int interval;
|
||||
|
||||
private static final long serialVersionUID = 1592714210688163496L;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param interval number of seconds younger
|
||||
*/
|
||||
public YoungerTerm(int interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interval.
|
||||
*
|
||||
* @return the interval
|
||||
*/
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* The match method.
|
||||
*
|
||||
* @param msg the date comparator is applied to this Message's
|
||||
* received date
|
||||
* @return true if the comparison succeeds, otherwise false
|
||||
*/
|
||||
@Override
|
||||
public boolean match(Message msg) {
|
||||
Date d;
|
||||
|
||||
try {
|
||||
d = msg.getReceivedDate();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (d == null)
|
||||
return false;
|
||||
|
||||
return d.getTime() >=
|
||||
System.currentTimeMillis() - ((long)interval * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality comparison.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof YoungerTerm))
|
||||
return false;
|
||||
return interval == ((YoungerTerm)obj).interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hashCode for this object.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.text.StringCharacterIterator;
|
||||
import java.text.CharacterIterator;
|
||||
|
||||
/**
|
||||
* See the BASE64MailboxEncoder for a description of the RFC2060 and how
|
||||
* mailbox names should be encoded. This class will do the correct decoding
|
||||
* for mailbox names.
|
||||
*
|
||||
* @author Christopher Cotton
|
||||
*/
|
||||
|
||||
public class BASE64MailboxDecoder {
|
||||
|
||||
public static String decode(String original) {
|
||||
if (original == null || original.length() == 0)
|
||||
return original;
|
||||
|
||||
boolean changedString = false;
|
||||
int copyTo = 0;
|
||||
// it will always be less than the original
|
||||
char[] chars = new char[original.length()];
|
||||
StringCharacterIterator iter = new StringCharacterIterator(original);
|
||||
|
||||
for(char c = iter.first(); c != CharacterIterator.DONE;
|
||||
c = iter.next()) {
|
||||
|
||||
if (c == '&') {
|
||||
changedString = true;
|
||||
copyTo = base64decode(chars, copyTo, iter);
|
||||
} else {
|
||||
chars[copyTo++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
// now create our string from the char array
|
||||
if (changedString) {
|
||||
return new String(chars, 0, copyTo);
|
||||
} else {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static int base64decode(char[] buffer, int offset,
|
||||
CharacterIterator iter) {
|
||||
boolean firsttime = true;
|
||||
int leftover = -1;
|
||||
|
||||
while(true) {
|
||||
// get the first byte
|
||||
byte orig_0 = (byte) iter.next();
|
||||
if (orig_0 == -1) break; // no more chars
|
||||
if (orig_0 == '-') {
|
||||
if (firsttime) {
|
||||
// means we got the string "&-" which is turned into a "&"
|
||||
buffer[offset++] = '&';
|
||||
}
|
||||
// we are done now
|
||||
break;
|
||||
}
|
||||
firsttime = false;
|
||||
|
||||
// next byte
|
||||
byte orig_1 = (byte) iter.next();
|
||||
if (orig_1 == -1 || orig_1 == '-')
|
||||
break; // no more chars, invalid base64
|
||||
|
||||
byte a, b, current;
|
||||
a = pem_convert_array[orig_0 & 0xff];
|
||||
b = pem_convert_array[orig_1 & 0xff];
|
||||
// The first decoded byte
|
||||
current = (byte)(((a << 2) & 0xfc) | ((b >>> 4) & 3));
|
||||
|
||||
// use the leftover to create a Unicode Character (2 bytes)
|
||||
if (leftover != -1) {
|
||||
buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
|
||||
leftover = -1;
|
||||
} else {
|
||||
leftover = current & 0xff;
|
||||
}
|
||||
|
||||
byte orig_2 = (byte) iter.next();
|
||||
if (orig_2 == '=') { // End of this BASE64 encoding
|
||||
continue;
|
||||
} else if (orig_2 == -1 || orig_2 == '-') {
|
||||
break; // no more chars
|
||||
}
|
||||
|
||||
// second decoded byte
|
||||
a = b;
|
||||
b = pem_convert_array[orig_2 & 0xff];
|
||||
current = (byte)(((a << 4) & 0xf0) | ((b >>> 2) & 0xf));
|
||||
|
||||
// use the leftover to create a Unicode Character (2 bytes)
|
||||
if (leftover != -1) {
|
||||
buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
|
||||
leftover = -1;
|
||||
} else {
|
||||
leftover = current & 0xff;
|
||||
}
|
||||
|
||||
byte orig_3 = (byte) iter.next();
|
||||
if (orig_3 == '=') { // End of this BASE64 encoding
|
||||
continue;
|
||||
} else if (orig_3 == -1 || orig_3 == '-') {
|
||||
break; // no more chars
|
||||
}
|
||||
|
||||
// The third decoded byte
|
||||
a = b;
|
||||
b = pem_convert_array[orig_3 & 0xff];
|
||||
current = (byte)(((a << 6) & 0xc0) | (b & 0x3f));
|
||||
|
||||
// use the leftover to create a Unicode Character (2 bytes)
|
||||
if (leftover != -1) {
|
||||
buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
|
||||
leftover = -1;
|
||||
} else {
|
||||
leftover = current & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This character array provides the character to value map
|
||||
* based on RFC1521, but with the modification from RFC2060
|
||||
* which changes the '/' to a ','.
|
||||
*/
|
||||
|
||||
// shared with BASE64MailboxEncoder
|
||||
static final char pem_array[] = {
|
||||
'A','B','C','D','E','F','G','H', // 0
|
||||
'I','J','K','L','M','N','O','P', // 1
|
||||
'Q','R','S','T','U','V','W','X', // 2
|
||||
'Y','Z','a','b','c','d','e','f', // 3
|
||||
'g','h','i','j','k','l','m','n', // 4
|
||||
'o','p','q','r','s','t','u','v', // 5
|
||||
'w','x','y','z','0','1','2','3', // 6
|
||||
'4','5','6','7','8','9','+',',' // 7
|
||||
};
|
||||
|
||||
private static final byte pem_convert_array[] = new byte[256];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 255; i++)
|
||||
pem_convert_array[i] = -1;
|
||||
for(int i = 0; i < pem_array.length; i++)
|
||||
pem_convert_array[pem_array[i]] = (byte) i;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/**
|
||||
* From RFC2060:
|
||||
*
|
||||
* <blockquote><pre>
|
||||
*
|
||||
* 5.1.3. Mailbox International Naming Convention
|
||||
*
|
||||
* By convention, international mailbox names are specified using a
|
||||
* modified version of the UTF-7 encoding described in [UTF-7]. The
|
||||
* purpose of these modifications is to correct the following problems
|
||||
* with UTF-7:
|
||||
*
|
||||
* 1) UTF-7 uses the "+" character for shifting; this conflicts with
|
||||
* the common use of "+" in mailbox names, in particular USENET
|
||||
* newsgroup names.
|
||||
*
|
||||
* 2) UTF-7's encoding is BASE64 which uses the "/" character; this
|
||||
* conflicts with the use of "/" as a popular hierarchy delimiter.
|
||||
*
|
||||
* 3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
|
||||
* the use of "\" as a popular hierarchy delimiter.
|
||||
*
|
||||
* 4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
|
||||
* the use of "~" in some servers as a home directory indicator.
|
||||
*
|
||||
* 5) UTF-7 permits multiple alternate forms to represent the same
|
||||
* string; in particular, printable US-ASCII chararacters can be
|
||||
* represented in encoded form.
|
||||
*
|
||||
* In modified UTF-7, printable US-ASCII characters except for "&"
|
||||
* represent themselves; that is, characters with octet values 0x20-0x25
|
||||
* and 0x27-0x7e. The character "&" (0x26) is represented by the two-
|
||||
* octet sequence "&-".
|
||||
*
|
||||
* All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
|
||||
* Unicode 16-bit octets) are represented in modified BASE64, with a
|
||||
* further modification from [UTF-7] that "," is used instead of "/".
|
||||
* Modified BASE64 MUST NOT be used to represent any printing US-ASCII
|
||||
* character which can represent itself.
|
||||
*
|
||||
* "&" is used to shift to modified BASE64 and "-" to shift back to US-
|
||||
* ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that
|
||||
* is, a name that ends with a Unicode 16-bit octet MUST end with a "-
|
||||
* ").
|
||||
*
|
||||
* For example, here is a mailbox name which mixes English, Japanese,
|
||||
* and Chinese text: ~peter/mail/&ZeVnLIqe-/&U,BTFw-
|
||||
*
|
||||
* </pre></blockquote>
|
||||
*
|
||||
* This class will do the correct Encoding for the IMAP mailboxes.
|
||||
*
|
||||
* @author Christopher Cotton
|
||||
*/
|
||||
|
||||
public class BASE64MailboxEncoder {
|
||||
protected byte[] buffer = new byte[4];
|
||||
protected int bufsize = 0;
|
||||
protected boolean started = false;
|
||||
protected Writer out = null;
|
||||
|
||||
|
||||
public static String encode(String original) {
|
||||
BASE64MailboxEncoder base64stream = null;
|
||||
char origchars[] = original.toCharArray();
|
||||
int length = origchars.length;
|
||||
boolean changedString = false;
|
||||
CharArrayWriter writer = new CharArrayWriter(length);
|
||||
|
||||
// loop over all the chars
|
||||
for(int index = 0; index < length; index++) {
|
||||
char current = origchars[index];
|
||||
|
||||
// octets in the range 0x20-0x25,0x27-0x7e are themselves
|
||||
// 0x26 "&" is represented as "&-"
|
||||
if (current >= 0x20 && current <= 0x7e) {
|
||||
if (base64stream != null) {
|
||||
base64stream.flush();
|
||||
}
|
||||
|
||||
if (current == '&') {
|
||||
changedString = true;
|
||||
writer.write('&');
|
||||
writer.write('-');
|
||||
} else {
|
||||
writer.write(current);
|
||||
}
|
||||
} else {
|
||||
|
||||
// use a B64MailboxEncoder to write out the other bytes
|
||||
// as a modified BASE64. The stream will write out
|
||||
// the beginning '&' and the ending '-' which is part
|
||||
// of every encoding.
|
||||
|
||||
if (base64stream == null) {
|
||||
base64stream = new BASE64MailboxEncoder(writer);
|
||||
changedString = true;
|
||||
}
|
||||
|
||||
base64stream.write(current);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (base64stream != null) {
|
||||
base64stream.flush();
|
||||
}
|
||||
|
||||
if (changedString) {
|
||||
return writer.toString();
|
||||
} else {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a BASE64 encoder
|
||||
*
|
||||
* @param what where to write the encoded name
|
||||
*/
|
||||
public BASE64MailboxEncoder(Writer what) {
|
||||
out = what;
|
||||
}
|
||||
|
||||
public void write(int c) {
|
||||
try {
|
||||
// write out the initial character if this is the first time
|
||||
if (!started) {
|
||||
started = true;
|
||||
out.write('&');
|
||||
}
|
||||
|
||||
// we write each character as a 2 byte unicode character
|
||||
buffer[bufsize++] = (byte) (c >> 8);
|
||||
buffer[bufsize++] = (byte) (c & 0xff);
|
||||
|
||||
if (bufsize >= 3) {
|
||||
encode();
|
||||
bufsize -= 3;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void flush() {
|
||||
try {
|
||||
// flush any bytes we have
|
||||
if (bufsize > 0) {
|
||||
encode();
|
||||
bufsize = 0;
|
||||
}
|
||||
|
||||
// write the terminating character of the encoding
|
||||
if (started) {
|
||||
out.write('-');
|
||||
started = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void encode() throws IOException {
|
||||
byte a, b, c;
|
||||
if (bufsize == 1) {
|
||||
a = buffer[0];
|
||||
b = 0;
|
||||
c = 0;
|
||||
out.write(pem_array[(a >>> 2) & 0x3F]);
|
||||
out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
// no padding characters are written
|
||||
} else if (bufsize == 2) {
|
||||
a = buffer[0];
|
||||
b = buffer[1];
|
||||
c = 0;
|
||||
out.write(pem_array[(a >>> 2) & 0x3F]);
|
||||
out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
out.write(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||
// no padding characters are written
|
||||
} else {
|
||||
a = buffer[0];
|
||||
b = buffer[1];
|
||||
c = buffer[2];
|
||||
out.write(pem_array[(a >>> 2) & 0x3F]);
|
||||
out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
out.write(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||
out.write(pem_array[c & 0x3F]);
|
||||
|
||||
// copy back the extra byte
|
||||
if (bufsize == 4)
|
||||
buffer[0] = buffer[3];
|
||||
}
|
||||
}
|
||||
|
||||
private final static char pem_array[] = {
|
||||
'A','B','C','D','E','F','G','H', // 0
|
||||
'I','J','K','L','M','N','O','P', // 1
|
||||
'Q','R','S','T','U','V','W','X', // 2
|
||||
'Y','Z','a','b','c','d','e','f', // 3
|
||||
'g','h','i','j','k','l','m','n', // 4
|
||||
'o','p','q','r','s','t','u','v', // 5
|
||||
'w','x','y','z','0','1','2','3', // 6
|
||||
'4','5','6','7','8','9','+',',' // 7
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import com.sun.mail.iap.*;
|
||||
import com.sun.mail.util.ASCIIUtility;
|
||||
|
||||
/**
|
||||
* The BODY fetch response item.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class BODY implements Item {
|
||||
|
||||
static final char[] name = {'B','O','D','Y'};
|
||||
|
||||
private final int msgno;
|
||||
private final ByteArray data;
|
||||
private final String section;
|
||||
private final int origin;
|
||||
private final boolean isHeader;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param r the FetchResponse
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public BODY(FetchResponse r) throws ParsingException {
|
||||
msgno = r.getNumber();
|
||||
|
||||
r.skipSpaces();
|
||||
|
||||
if (r.readByte() != '[')
|
||||
throw new ParsingException(
|
||||
"BODY parse error: missing ``['' at section start");
|
||||
section = r.readString(']');
|
||||
if (r.readByte() != ']')
|
||||
throw new ParsingException(
|
||||
"BODY parse error: missing ``]'' at section end");
|
||||
isHeader = section.regionMatches(true, 0, "HEADER", 0, 6);
|
||||
|
||||
if (r.readByte() == '<') { // origin
|
||||
origin = r.readNumber();
|
||||
r.skip(1); // skip '>';
|
||||
} else
|
||||
origin = -1;
|
||||
|
||||
data = r.readByteArray();
|
||||
}
|
||||
|
||||
public ByteArray getByteArray() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public ByteArrayInputStream getByteArrayInputStream() {
|
||||
if (data != null)
|
||||
return data.toByteArrayInputStream();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isHeader() {
|
||||
return isHeader;
|
||||
}
|
||||
|
||||
public String getSection() {
|
||||
return section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since Jakarta Mail 1.6.4
|
||||
*/
|
||||
public int getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,450 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import javax.mail.internet.ParameterList;
|
||||
import com.sun.mail.iap.*;
|
||||
import com.sun.mail.util.PropUtil;
|
||||
|
||||
/**
|
||||
* A BODYSTRUCTURE response.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class BODYSTRUCTURE implements Item {
|
||||
|
||||
static final char[] name =
|
||||
{'B','O','D','Y','S','T','R','U','C','T','U','R','E'};
|
||||
public int msgno;
|
||||
|
||||
public String type; // Type
|
||||
public String subtype; // Subtype
|
||||
public String encoding; // Encoding
|
||||
public int lines = -1; // Size in lines
|
||||
public int size = -1; // Size in bytes
|
||||
public String disposition; // Disposition
|
||||
public String id; // Content-ID
|
||||
public String description; // Content-Description
|
||||
public String md5; // MD-5 checksum
|
||||
public String attachment; // Attachment name
|
||||
public ParameterList cParams; // Body parameters
|
||||
public ParameterList dParams; // Disposition parameters
|
||||
public String[] language; // Language
|
||||
public BODYSTRUCTURE[] bodies; // array of BODYSTRUCTURE objects
|
||||
// for multipart & message/rfc822
|
||||
public ENVELOPE envelope; // for message/rfc822
|
||||
|
||||
private static int SINGLE = 1;
|
||||
private static int MULTI = 2;
|
||||
private static int NESTED = 3;
|
||||
private int processedType; // MULTI | SINGLE | NESTED
|
||||
|
||||
// special debugging output to debug parsing errors
|
||||
private static final boolean parseDebug =
|
||||
PropUtil.getBooleanSystemProperty("mail.imap.parse.debug", false);
|
||||
|
||||
|
||||
public BODYSTRUCTURE(FetchResponse r) throws ParsingException {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parsing BODYSTRUCTURE");
|
||||
msgno = r.getNumber();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: msgno " + msgno);
|
||||
|
||||
r.skipSpaces();
|
||||
|
||||
if (r.readByte() != '(')
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: missing ``('' at start");
|
||||
|
||||
if (r.peekByte() == '(') { // multipart
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parsing multipart");
|
||||
type = "multipart";
|
||||
processedType = MULTI;
|
||||
List<BODYSTRUCTURE> v = new ArrayList<>(1);
|
||||
int i = 1;
|
||||
do {
|
||||
v.add(new BODYSTRUCTURE(r));
|
||||
/*
|
||||
* Even though the IMAP spec says there can't be any spaces
|
||||
* between parts, some servers erroneously put a space in
|
||||
* here. In the spirit of "be liberal in what you accept",
|
||||
* we skip it.
|
||||
*/
|
||||
r.skipSpaces();
|
||||
} while (r.peekByte() == '(');
|
||||
|
||||
// setup bodies.
|
||||
bodies = v.toArray(new BODYSTRUCTURE[v.size()]);
|
||||
|
||||
subtype = r.readString(); // subtype
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: subtype " + subtype);
|
||||
|
||||
if (r.isNextNonSpace(')')) { // done
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parse DONE");
|
||||
return;
|
||||
}
|
||||
|
||||
// Else, we have extension data
|
||||
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parsing extension data");
|
||||
// Body parameters
|
||||
cParams = parseParameters(r);
|
||||
if (r.isNextNonSpace(')')) { // done
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: body parameters DONE");
|
||||
return;
|
||||
}
|
||||
|
||||
// Disposition
|
||||
byte b = r.peekByte();
|
||||
if (b == '(') {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parse disposition");
|
||||
r.readByte();
|
||||
disposition = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: disposition " +
|
||||
disposition);
|
||||
dParams = parseParameters(r);
|
||||
if (!r.isNextNonSpace(')')) // eat the end ')'
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: " +
|
||||
"missing ``)'' at end of disposition in multipart");
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: disposition DONE");
|
||||
} else if (b == 'N' || b == 'n') {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: disposition NIL");
|
||||
r.skip(3); // skip 'NIL'
|
||||
} else {
|
||||
/*
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: " +
|
||||
type + "/" + subtype + ": " +
|
||||
"bad multipart disposition, b " + b);
|
||||
*/
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: bad multipart disposition" +
|
||||
", applying Exchange bug workaround");
|
||||
description = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: multipart description " +
|
||||
description);
|
||||
// Throw away whatever comes after it, since we have no
|
||||
// idea what it's supposed to be
|
||||
while (r.readByte() == ' ')
|
||||
parseBodyExtension(r);
|
||||
return;
|
||||
}
|
||||
|
||||
// RFC3501 allows no body-fld-lang after body-fld-disp,
|
||||
// even though RFC2060 required it
|
||||
if (r.isNextNonSpace(')')) {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: no body-fld-lang");
|
||||
return; // done
|
||||
}
|
||||
|
||||
// Language
|
||||
if (r.peekByte() == '(') { // a list follows
|
||||
language = r.readStringList();
|
||||
if (parseDebug)
|
||||
System.out.println(
|
||||
"DEBUG IMAP: language len " + language.length);
|
||||
} else {
|
||||
String l = r.readString();
|
||||
if (l != null) {
|
||||
String[] la = { l };
|
||||
language = la;
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: language " + l);
|
||||
}
|
||||
}
|
||||
|
||||
// RFC3501 defines an optional "body location" next,
|
||||
// but for now we ignore it along with other extensions.
|
||||
|
||||
// Throw away any further extension data
|
||||
while (r.readByte() == ' ')
|
||||
parseBodyExtension(r);
|
||||
} else if (r.peekByte() == ')') { // (illegal) empty body
|
||||
/*
|
||||
* Domino will fail to return the body structure of nested messages.
|
||||
* Fake it by providing an empty message. Could probably do better
|
||||
* with more work...
|
||||
*/
|
||||
/*
|
||||
* XXX - this prevents the exception, but without the exception
|
||||
* the application has no way to know the data from the message
|
||||
* is missing.
|
||||
*
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: empty body, fake it");
|
||||
r.readByte();
|
||||
type = "text";
|
||||
subtype = "plain";
|
||||
lines = 0;
|
||||
size = 0;
|
||||
*/
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: missing body content");
|
||||
} else { // Single part
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: single part");
|
||||
type = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: type " + type);
|
||||
processedType = SINGLE;
|
||||
subtype = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: subtype " + subtype);
|
||||
|
||||
// SIMS 4.0 returns NIL for a Content-Type of "binary", fix it here
|
||||
if (type == null) {
|
||||
type = "application";
|
||||
subtype = "octet-stream";
|
||||
}
|
||||
cParams = parseParameters(r);
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: cParams " + cParams);
|
||||
id = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: id " + id);
|
||||
description = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: description " + description);
|
||||
/*
|
||||
* XXX - Work around bug in Exchange 2010 that
|
||||
* returns unquoted string.
|
||||
*/
|
||||
encoding = r.readAtomString();
|
||||
if (encoding != null && encoding.equalsIgnoreCase("NIL")) {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: NIL encoding" +
|
||||
", applying Exchange bug workaround");
|
||||
encoding = null;
|
||||
}
|
||||
/*
|
||||
* XXX - Work around bug in office365.com that returns
|
||||
* a string with a trailing space in some cases.
|
||||
*/
|
||||
if (encoding != null)
|
||||
encoding = encoding.trim();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: encoding " + encoding);
|
||||
size = r.readNumber();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: size " + size);
|
||||
if (size < 0)
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: bad ``size'' element");
|
||||
|
||||
// "text/*" & "message/rfc822" types have additional data ..
|
||||
if (type.equalsIgnoreCase("text")) {
|
||||
lines = r.readNumber();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: lines " + lines);
|
||||
if (lines < 0)
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: bad ``lines'' element");
|
||||
} else if (type.equalsIgnoreCase("message") &&
|
||||
subtype.equalsIgnoreCase("rfc822")) {
|
||||
// Nested message
|
||||
processedType = NESTED;
|
||||
// The envelope comes next, but sadly Gmail handles nested
|
||||
// messages just like simple body parts and fails to return
|
||||
// the envelope and body structure of the message (sort of
|
||||
// like IMAP4 before rev1).
|
||||
r.skipSpaces();
|
||||
if (r.peekByte() == '(') { // the envelope follows
|
||||
envelope = new ENVELOPE(r);
|
||||
if (parseDebug)
|
||||
System.out.println(
|
||||
"DEBUG IMAP: got envelope of nested message");
|
||||
BODYSTRUCTURE[] bs = { new BODYSTRUCTURE(r) };
|
||||
bodies = bs;
|
||||
lines = r.readNumber();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: lines " + lines);
|
||||
if (lines < 0)
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: bad ``lines'' element");
|
||||
} else {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: " +
|
||||
"missing envelope and body of nested message");
|
||||
}
|
||||
} else {
|
||||
// Detect common error of including lines element on other types
|
||||
r.skipSpaces();
|
||||
byte bn = r.peekByte();
|
||||
if (Character.isDigit((char)bn)) // number
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: server erroneously " +
|
||||
"included ``lines'' element with type " +
|
||||
type + "/" + subtype);
|
||||
}
|
||||
|
||||
if (r.isNextNonSpace(')')) {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parse DONE");
|
||||
return; // done
|
||||
}
|
||||
|
||||
// Optional extension data
|
||||
|
||||
// MD5
|
||||
md5 = r.readString();
|
||||
if (r.isNextNonSpace(')')) {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: no MD5 DONE");
|
||||
return; // done
|
||||
}
|
||||
|
||||
// Disposition
|
||||
byte b = r.readByte();
|
||||
if (b == '(') {
|
||||
disposition = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: disposition " +
|
||||
disposition);
|
||||
dParams = parseParameters(r);
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: dParams " + dParams);
|
||||
if (!r.isNextNonSpace(')')) // eat the end ')'
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: " +
|
||||
"missing ``)'' at end of disposition");
|
||||
} else if (b == 'N' || b == 'n') {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: disposition NIL");
|
||||
r.skip(2); // skip 'NIL'
|
||||
} else {
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: " +
|
||||
type + "/" + subtype + ": " +
|
||||
"bad single part disposition, b " + b);
|
||||
}
|
||||
|
||||
if (r.isNextNonSpace(')')) {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: disposition DONE");
|
||||
return; // done
|
||||
}
|
||||
|
||||
// Language
|
||||
if (r.peekByte() == '(') { // a list follows
|
||||
language = r.readStringList();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: language len " +
|
||||
language.length);
|
||||
} else { // protocol is unnessarily complex here
|
||||
String l = r.readString();
|
||||
if (l != null) {
|
||||
String[] la = { l };
|
||||
language = la;
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: language " + l);
|
||||
}
|
||||
}
|
||||
|
||||
// RFC3501 defines an optional "body location" next,
|
||||
// but for now we ignore it along with other extensions.
|
||||
|
||||
// Throw away any further extension data
|
||||
while (r.readByte() == ' ')
|
||||
parseBodyExtension(r);
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: all DONE");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMulti() {
|
||||
return processedType == MULTI;
|
||||
}
|
||||
|
||||
public boolean isSingle() {
|
||||
return processedType == SINGLE;
|
||||
}
|
||||
|
||||
public boolean isNested() {
|
||||
return processedType == NESTED;
|
||||
}
|
||||
|
||||
private ParameterList parseParameters(Response r)
|
||||
throws ParsingException {
|
||||
r.skipSpaces();
|
||||
|
||||
ParameterList list = null;
|
||||
byte b = r.readByte();
|
||||
if (b == '(') {
|
||||
list = new ParameterList();
|
||||
do {
|
||||
String name = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parameter name " + name);
|
||||
if (name == null)
|
||||
throw new ParsingException(
|
||||
"BODYSTRUCTURE parse error: " +
|
||||
type + "/" + subtype + ": " +
|
||||
"null name in parameter list");
|
||||
String value = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parameter value " + value);
|
||||
if (value == null) { // work around buggy servers
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: NIL parameter value" +
|
||||
", applying Exchange bug workaround");
|
||||
value = "";
|
||||
}
|
||||
list.set(name, value);
|
||||
} while (!r.isNextNonSpace(')'));
|
||||
list.combineSegments();
|
||||
} else if (b == 'N' || b == 'n') {
|
||||
if (parseDebug)
|
||||
System.out.println("DEBUG IMAP: parameter list NIL");
|
||||
r.skip(2);
|
||||
} else
|
||||
throw new ParsingException("Parameter list parse error");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private void parseBodyExtension(Response r) throws ParsingException {
|
||||
r.skipSpaces();
|
||||
|
||||
byte b = r.peekByte();
|
||||
if (b == '(') {
|
||||
r.skip(1); // skip '('
|
||||
do {
|
||||
parseBodyExtension(r);
|
||||
} while (!r.isNextNonSpace(')'));
|
||||
} else if (Character.isDigit((char)b)) // number
|
||||
r.readNumber();
|
||||
else // nstring
|
||||
r.readString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.text.ParseException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.MailDateFormat;
|
||||
import javax.mail.internet.MimeUtility;
|
||||
import com.sun.mail.iap.*;
|
||||
import com.sun.mail.util.PropUtil;
|
||||
|
||||
/**
|
||||
* The ENEVELOPE item of an IMAP FETCH response.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class ENVELOPE implements Item {
|
||||
|
||||
// IMAP item name
|
||||
static final char[] name = {'E','N','V','E','L','O','P','E'};
|
||||
public int msgno;
|
||||
|
||||
public Date date = null;
|
||||
public String subject;
|
||||
public InternetAddress[] from;
|
||||
public InternetAddress[] sender;
|
||||
public InternetAddress[] replyTo;
|
||||
public InternetAddress[] to;
|
||||
public InternetAddress[] cc;
|
||||
public InternetAddress[] bcc;
|
||||
public String inReplyTo;
|
||||
public String messageId;
|
||||
|
||||
// Used to parse dates
|
||||
private static final MailDateFormat mailDateFormat = new MailDateFormat();
|
||||
|
||||
// special debugging output to debug parsing errors
|
||||
private static final boolean parseDebug =
|
||||
PropUtil.getBooleanSystemProperty("mail.imap.parse.debug", false);
|
||||
|
||||
public ENVELOPE(FetchResponse r) throws ParsingException {
|
||||
if (parseDebug)
|
||||
System.out.println("parse ENVELOPE");
|
||||
msgno = r.getNumber();
|
||||
|
||||
r.skipSpaces();
|
||||
|
||||
if (r.readByte() != '(')
|
||||
throw new ParsingException("ENVELOPE parse error");
|
||||
|
||||
String s = r.readString();
|
||||
if (s != null) {
|
||||
try {
|
||||
synchronized (mailDateFormat) {
|
||||
date = mailDateFormat.parse(s);
|
||||
}
|
||||
} catch (ParseException pex) {
|
||||
}
|
||||
}
|
||||
if (parseDebug)
|
||||
System.out.println(" Date: " + date);
|
||||
|
||||
subject = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println(" Subject: " + subject);
|
||||
if (parseDebug)
|
||||
System.out.println(" From addresses:");
|
||||
from = parseAddressList(r);
|
||||
if (parseDebug)
|
||||
System.out.println(" Sender addresses:");
|
||||
sender = parseAddressList(r);
|
||||
if (parseDebug)
|
||||
System.out.println(" Reply-To addresses:");
|
||||
replyTo = parseAddressList(r);
|
||||
if (parseDebug)
|
||||
System.out.println(" To addresses:");
|
||||
to = parseAddressList(r);
|
||||
if (parseDebug)
|
||||
System.out.println(" Cc addresses:");
|
||||
cc = parseAddressList(r);
|
||||
if (parseDebug)
|
||||
System.out.println(" Bcc addresses:");
|
||||
bcc = parseAddressList(r);
|
||||
inReplyTo = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println(" In-Reply-To: " + inReplyTo);
|
||||
messageId = r.readString();
|
||||
if (parseDebug)
|
||||
System.out.println(" Message-ID: " + messageId);
|
||||
|
||||
if (!r.isNextNonSpace(')'))
|
||||
throw new ParsingException("ENVELOPE parse error");
|
||||
}
|
||||
|
||||
private InternetAddress[] parseAddressList(Response r)
|
||||
throws ParsingException {
|
||||
r.skipSpaces(); // skip leading spaces
|
||||
|
||||
byte b = r.readByte();
|
||||
if (b == '(') {
|
||||
/*
|
||||
* Some broken servers (e.g., Yahoo Mail) return an empty
|
||||
* list instead of NIL. Handle that here even though it
|
||||
* doesn't conform to the IMAP spec.
|
||||
*/
|
||||
if (r.isNextNonSpace(')'))
|
||||
return null;
|
||||
|
||||
List<InternetAddress> v = new ArrayList<>();
|
||||
|
||||
do {
|
||||
IMAPAddress a = new IMAPAddress(r);
|
||||
if (parseDebug)
|
||||
System.out.println(" Address: " + a);
|
||||
// if we see an end-of-group address at the top, ignore it
|
||||
if (!a.isEndOfGroup())
|
||||
v.add(a);
|
||||
} while (!r.isNextNonSpace(')'));
|
||||
|
||||
return v.toArray(new InternetAddress[v.size()]);
|
||||
} else if (b == 'N' || b == 'n') { // NIL
|
||||
r.skip(2); // skip 'NIL'
|
||||
return null;
|
||||
} else
|
||||
throw new ParsingException("ADDRESS parse error");
|
||||
}
|
||||
}
|
||||
|
||||
class IMAPAddress extends InternetAddress {
|
||||
private boolean group = false;
|
||||
private InternetAddress[] grouplist;
|
||||
private String groupname;
|
||||
|
||||
private static final long serialVersionUID = -3835822029483122232L;
|
||||
|
||||
IMAPAddress(Response r) throws ParsingException {
|
||||
r.skipSpaces(); // skip leading spaces
|
||||
|
||||
if (r.readByte() != '(')
|
||||
throw new ParsingException("ADDRESS parse error");
|
||||
|
||||
encodedPersonal = r.readString();
|
||||
|
||||
r.readString(); // throw away address_list
|
||||
String mb = r.readString();
|
||||
String host = r.readString();
|
||||
// skip bogus spaces inserted by Yahoo IMAP server if
|
||||
// "undisclosed-recipients" is a recipient
|
||||
r.skipSpaces();
|
||||
if (!r.isNextNonSpace(')')) // skip past terminating ')'
|
||||
throw new ParsingException("ADDRESS parse error");
|
||||
|
||||
if (host == null) {
|
||||
// it's a group list, start or end
|
||||
group = true;
|
||||
groupname = mb;
|
||||
if (groupname == null) // end of group list
|
||||
return;
|
||||
// Accumulate a group list. The members of the group
|
||||
// are accumulated in a List and the corresponding string
|
||||
// representation of the group is accumulated in a StringBuilder.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(groupname).append(':');
|
||||
List<InternetAddress> v = new ArrayList<>();
|
||||
while (r.peekByte() != ')') {
|
||||
IMAPAddress a = new IMAPAddress(r);
|
||||
if (a.isEndOfGroup()) // reached end of group
|
||||
break;
|
||||
if (v.size() != 0) // if not first element, need a comma
|
||||
sb.append(',');
|
||||
sb.append(a.toString());
|
||||
v.add(a);
|
||||
}
|
||||
sb.append(';');
|
||||
address = sb.toString();
|
||||
grouplist = v.toArray(new IMAPAddress[v.size()]);
|
||||
} else {
|
||||
if (mb == null || mb.length() == 0)
|
||||
address = host;
|
||||
else if (host.length() == 0)
|
||||
address = mb;
|
||||
else
|
||||
address = mb + "@" + host;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean isEndOfGroup() {
|
||||
return group && groupname == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternetAddress[] getGroup(boolean strict) throws AddressException {
|
||||
if (grouplist == null)
|
||||
return null;
|
||||
return grouplist.clone();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import javax.mail.Flags;
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* This class
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class FLAGS extends Flags implements Item {
|
||||
|
||||
// IMAP item name
|
||||
static final char[] name = {'F','L','A','G','S'};
|
||||
public int msgno;
|
||||
|
||||
private static final long serialVersionUID = 439049847053756670L;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param r the IMAPResponse
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public FLAGS(IMAPResponse r) throws ParsingException {
|
||||
msgno = r.getNumber();
|
||||
|
||||
r.skipSpaces();
|
||||
String[] flags = r.readSimpleList();
|
||||
if (flags != null) { // if not empty flaglist
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
String s = flags[i];
|
||||
if (s.length() >= 2 && s.charAt(0) == '\\') {
|
||||
switch (Character.toUpperCase(s.charAt(1))) {
|
||||
case 'S': // \Seen
|
||||
add(Flags.Flag.SEEN);
|
||||
break;
|
||||
case 'R': // \Recent
|
||||
add(Flags.Flag.RECENT);
|
||||
break;
|
||||
case 'D':
|
||||
if (s.length() >= 3) {
|
||||
char c = s.charAt(2);
|
||||
if (c == 'e' || c == 'E') // \Deleted
|
||||
add(Flags.Flag.DELETED);
|
||||
else if (c == 'r' || c == 'R') // \Draft
|
||||
add(Flags.Flag.DRAFT);
|
||||
} else
|
||||
add(s); // unknown, treat it as a user flag
|
||||
break;
|
||||
case 'A': // \Answered
|
||||
add(Flags.Flag.ANSWERED);
|
||||
break;
|
||||
case 'F': // \Flagged
|
||||
add(Flags.Flag.FLAGGED);
|
||||
break;
|
||||
case '*': // \*
|
||||
add(Flags.Flag.USER);
|
||||
break;
|
||||
default:
|
||||
add(s); // unknown, treat it as a user flag
|
||||
break;
|
||||
}
|
||||
} else
|
||||
add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
|
||||
import javax.mail.FetchProfile;
|
||||
import com.sun.mail.iap.ParsingException;
|
||||
|
||||
/**
|
||||
* Metadata describing a FETCH item.
|
||||
* Note that the "name" field MUST be in uppercase. <p>
|
||||
*
|
||||
* @author Bill Shannon
|
||||
* @since JavaMail 1.4.6
|
||||
*/
|
||||
|
||||
public abstract class FetchItem {
|
||||
private String name;
|
||||
private FetchProfile.Item fetchProfileItem;
|
||||
|
||||
public FetchItem(String name, FetchProfile.Item fetchProfileItem) {
|
||||
this.name = name;
|
||||
this.fetchProfileItem = fetchProfileItem;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public FetchProfile.Item getFetchProfileItem() {
|
||||
return fetchProfileItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the item into some kind of object appropriate for the item.
|
||||
* Note that the item name will have been parsed and skipped already.
|
||||
*
|
||||
* @param r the response
|
||||
* @return the fetch item
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public abstract Object parseItem(FetchResponse r) throws ParsingException;
|
||||
}
|
||||
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import com.sun.mail.util.ASCIIUtility;
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* This class represents a FETCH response obtained from the input stream
|
||||
* of an IMAP server.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class FetchResponse extends IMAPResponse {
|
||||
/*
|
||||
* Regular Items are saved in the items array.
|
||||
* Extension items (items handled by subclasses
|
||||
* that extend the IMAP provider) are saved in the
|
||||
* extensionItems map, indexed by the FETCH item name.
|
||||
* The map is only created when needed.
|
||||
*
|
||||
* XXX - Should consider unifying the handling of
|
||||
* regular items and extension items.
|
||||
*/
|
||||
private Item[] items;
|
||||
private Map<String, Object> extensionItems;
|
||||
private final FetchItem[] fitems;
|
||||
|
||||
public FetchResponse(Protocol p)
|
||||
throws IOException, ProtocolException {
|
||||
super(p);
|
||||
fitems = null;
|
||||
parse();
|
||||
}
|
||||
|
||||
public FetchResponse(IMAPResponse r)
|
||||
throws IOException, ProtocolException {
|
||||
this(r, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a FetchResponse that handles the additional FetchItems.
|
||||
*
|
||||
* @param r the IMAPResponse
|
||||
* @param fitems the fetch items
|
||||
* @exception IOException for I/O errors
|
||||
* @exception ProtocolException for protocol failures
|
||||
* @since JavaMail 1.4.6
|
||||
*/
|
||||
public FetchResponse(IMAPResponse r, FetchItem[] fitems)
|
||||
throws IOException, ProtocolException {
|
||||
super(r);
|
||||
this.fitems = fitems;
|
||||
parse();
|
||||
}
|
||||
|
||||
public int getItemCount() {
|
||||
return items.length;
|
||||
}
|
||||
|
||||
public Item getItem(int index) {
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public <T extends Item> T getItem(Class<T> c) {
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
if (c.isInstance(items[i]))
|
||||
return c.cast(items[i]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first fetch response item of the given class
|
||||
* for the given message number.
|
||||
*
|
||||
* @param r the responses
|
||||
* @param msgno the message number
|
||||
* @param c the class
|
||||
* @param <T> the type of fetch item
|
||||
* @return the fetch item
|
||||
*/
|
||||
public static <T extends Item> T getItem(Response[] r, int msgno,
|
||||
Class<T> c) {
|
||||
if (r == null)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < r.length; i++) {
|
||||
|
||||
if (r[i] == null ||
|
||||
!(r[i] instanceof FetchResponse) ||
|
||||
((FetchResponse)r[i]).getNumber() != msgno)
|
||||
continue;
|
||||
|
||||
FetchResponse f = (FetchResponse)r[i];
|
||||
for (int j = 0; j < f.items.length; j++) {
|
||||
if (c.isInstance(f.items[j]))
|
||||
return c.cast(f.items[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all fetch response items of the given class
|
||||
* for the given message number.
|
||||
*
|
||||
* @param r the responses
|
||||
* @param msgno the message number
|
||||
* @param c the class
|
||||
* @param <T> the type of fetch items
|
||||
* @return the list of fetch items
|
||||
* @since JavaMail 1.5.2
|
||||
*/
|
||||
public static <T extends Item> List<T> getItems(Response[] r, int msgno,
|
||||
Class<T> c) {
|
||||
List<T> items = new ArrayList<>();
|
||||
|
||||
if (r == null)
|
||||
return items;
|
||||
|
||||
for (int i = 0; i < r.length; i++) {
|
||||
|
||||
if (r[i] == null ||
|
||||
!(r[i] instanceof FetchResponse) ||
|
||||
((FetchResponse)r[i]).getNumber() != msgno)
|
||||
continue;
|
||||
|
||||
FetchResponse f = (FetchResponse)r[i];
|
||||
for (int j = 0; j < f.items.length; j++) {
|
||||
if (c.isInstance(f.items[j]))
|
||||
items.add(c.cast(f.items[j]));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of the extension items found in this fetch response.
|
||||
* The map is indexed by extension item name. Callers should not
|
||||
* modify the map.
|
||||
*
|
||||
* @return Map of extension items, or null if none
|
||||
* @since JavaMail 1.4.6
|
||||
*/
|
||||
public Map<String, Object> getExtensionItems() {
|
||||
return extensionItems;
|
||||
}
|
||||
|
||||
private final static char[] HEADER = {'.','H','E','A','D','E','R'};
|
||||
private final static char[] TEXT = {'.','T','E','X','T'};
|
||||
|
||||
private void parse() throws ParsingException {
|
||||
if (!isNextNonSpace('('))
|
||||
throw new ParsingException(
|
||||
"error in FETCH parsing, missing '(' at index " + index);
|
||||
|
||||
List<Item> v = new ArrayList<>();
|
||||
Item i = null;
|
||||
skipSpaces();
|
||||
do {
|
||||
|
||||
if (index >= size)
|
||||
throw new ParsingException(
|
||||
"error in FETCH parsing, ran off end of buffer, size " + size);
|
||||
|
||||
i = parseItem();
|
||||
if (i != null)
|
||||
v.add(i);
|
||||
else if (!parseExtensionItem())
|
||||
throw new ParsingException(
|
||||
"error in FETCH parsing, unrecognized item at index " +
|
||||
index + ", starts with \"" + next20() + "\"");
|
||||
} while (!isNextNonSpace(')'));
|
||||
|
||||
items = v.toArray(new Item[v.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next 20 characters in the buffer, for exception messages.
|
||||
*/
|
||||
private String next20() {
|
||||
if (index + 20 > size)
|
||||
return ASCIIUtility.toString(buffer, index, size);
|
||||
else
|
||||
return ASCIIUtility.toString(buffer, index, index + 20) + "...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the item at the current position in the buffer,
|
||||
* skipping over the item if successful. Otherwise, return null
|
||||
* and leave the buffer position unmodified.
|
||||
*/
|
||||
@SuppressWarnings("empty")
|
||||
private Item parseItem() throws ParsingException {
|
||||
switch (buffer[index]) {
|
||||
case 'E': case 'e':
|
||||
if (match(ENVELOPE.name))
|
||||
return new ENVELOPE(this);
|
||||
break;
|
||||
case 'F': case 'f':
|
||||
if (match(FLAGS.name))
|
||||
return new FLAGS((IMAPResponse)this);
|
||||
break;
|
||||
case 'I': case 'i':
|
||||
if (match(INTERNALDATE.name))
|
||||
return new INTERNALDATE(this);
|
||||
break;
|
||||
case 'B': case 'b':
|
||||
if (match(BODYSTRUCTURE.name))
|
||||
return new BODYSTRUCTURE(this);
|
||||
else if (match(BODY.name)) {
|
||||
if (buffer[index] == '[')
|
||||
return new BODY(this);
|
||||
else
|
||||
return new BODYSTRUCTURE(this);
|
||||
}
|
||||
break;
|
||||
case 'R': case 'r':
|
||||
if (match(RFC822SIZE.name))
|
||||
return new RFC822SIZE(this);
|
||||
else if (match(RFC822DATA.name)) {
|
||||
boolean isHeader = false;
|
||||
if (match(HEADER))
|
||||
isHeader = true; // skip ".HEADER"
|
||||
else if (match(TEXT))
|
||||
isHeader = false; // skip ".TEXT"
|
||||
return new RFC822DATA(this, isHeader);
|
||||
}
|
||||
break;
|
||||
case 'U': case 'u':
|
||||
if (match(UID.name))
|
||||
return new UID(this);
|
||||
break;
|
||||
case 'M': case 'm':
|
||||
if (match(MODSEQ.name))
|
||||
return new MODSEQ(this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this item is a known extension item, parse it.
|
||||
*/
|
||||
private boolean parseExtensionItem() throws ParsingException {
|
||||
if (fitems == null)
|
||||
return false;
|
||||
for (int i = 0; i < fitems.length; i++) {
|
||||
if (match(fitems[i].getName())) {
|
||||
if (extensionItems == null)
|
||||
extensionItems = new HashMap<>();
|
||||
extensionItems.put(fitems[i].getName(),
|
||||
fitems[i].parseItem(this));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the current buffer match the given item name?
|
||||
* itemName is the name of the IMAP item to compare against.
|
||||
* NOTE that itemName *must* be all uppercase.
|
||||
* If the match is successful, the buffer pointer (index)
|
||||
* is incremented past the matched item.
|
||||
*/
|
||||
private boolean match(char[] itemName) {
|
||||
int len = itemName.length;
|
||||
for (int i = 0, j = index; i < len;)
|
||||
// IMAP tokens are case-insensitive. We store itemNames in
|
||||
// uppercase, so convert operand to uppercase before comparing.
|
||||
if (Character.toUpperCase((char)buffer[j++]) != itemName[i++])
|
||||
return false;
|
||||
index += len;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the current buffer match the given item name?
|
||||
* itemName is the name of the IMAP item to compare against.
|
||||
* NOTE that itemName *must* be all uppercase.
|
||||
* If the match is successful, the buffer pointer (index)
|
||||
* is incremented past the matched item.
|
||||
*/
|
||||
private boolean match(String itemName) {
|
||||
int len = itemName.length();
|
||||
for (int i = 0, j = index; i < len;)
|
||||
// IMAP tokens are case-insensitive. We store itemNames in
|
||||
// uppercase, so convert operand to uppercase before comparing.
|
||||
if (Character.toUpperCase((char)buffer[j++]) !=
|
||||
itemName.charAt(i++))
|
||||
return false;
|
||||
index += len;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.*;
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* This class represents the response to the ID command. <p>
|
||||
*
|
||||
* See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
|
||||
*
|
||||
* @since JavaMail 1.5.1
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class ID {
|
||||
|
||||
private Map<String, String> serverParams = null;
|
||||
|
||||
/**
|
||||
* Parse the server parameter list out of the response.
|
||||
*
|
||||
* @param r the response
|
||||
* @exception ProtocolException for protocol failures
|
||||
*/
|
||||
public ID(Response r) throws ProtocolException {
|
||||
// id_response ::= "ID" SPACE id_params_list
|
||||
// id_params_list ::= "(" #(string SPACE nstring) ")" / nil
|
||||
// ;; list of field value pairs
|
||||
|
||||
r.skipSpaces();
|
||||
int c = r.peekByte();
|
||||
if (c == 'N' || c == 'n') // assume NIL
|
||||
return;
|
||||
|
||||
if (c != '(')
|
||||
throw new ProtocolException("Missing '(' at start of ID");
|
||||
|
||||
serverParams = new HashMap<>();
|
||||
|
||||
String[] v = r.readStringList();
|
||||
if (v != null) {
|
||||
for (int i = 0; i < v.length; i += 2) {
|
||||
String name = v[i];
|
||||
if (name == null)
|
||||
throw new ProtocolException("ID field name null");
|
||||
if (i + 1 >= v.length)
|
||||
throw new ProtocolException("ID field without value: " +
|
||||
name);
|
||||
String value = v[i + 1];
|
||||
serverParams.put(name, value);
|
||||
}
|
||||
}
|
||||
serverParams = Collections.unmodifiableMap(serverParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parsed server params.
|
||||
*/
|
||||
Map<String, String> getServerParams() {
|
||||
return serverParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the client parameters into an argument list for the ID command.
|
||||
*/
|
||||
static Argument getArgumentList(Map<String,String> clientParams) {
|
||||
Argument arg = new Argument();
|
||||
if (clientParams == null) {
|
||||
arg.writeAtom("NIL");
|
||||
return arg;
|
||||
}
|
||||
Argument list = new Argument();
|
||||
// add params to list
|
||||
for (Map.Entry<String, String> e : clientParams.entrySet()) {
|
||||
list.writeNString(e.getKey()); // assume these are ASCII only
|
||||
list.writeNString(e.getValue());
|
||||
}
|
||||
arg.writeArgument(list);
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import com.sun.mail.iap.ProtocolException;
|
||||
|
||||
/**
|
||||
* A ProtocolException that includes IMAP login referral information.
|
||||
*
|
||||
* @since JavaMail 1.5.5
|
||||
*/
|
||||
|
||||
public class IMAPReferralException extends ProtocolException {
|
||||
|
||||
private String url;
|
||||
|
||||
private static final long serialVersionUID = 2578770669364251968L;
|
||||
|
||||
/**
|
||||
* Constructs an IMAPReferralException with the specified detail message.
|
||||
* and URL.
|
||||
*
|
||||
* @param s the detail message
|
||||
* @param url the URL
|
||||
*/
|
||||
public IMAPReferralException(String s, String url) {
|
||||
super(s);
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IMAP URL in the referral.
|
||||
*
|
||||
* @return the IMAP URL
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import com.sun.mail.util.ASCIIUtility;
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* This class represents a response obtained from the input stream
|
||||
* of an IMAP server.
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class IMAPResponse extends Response {
|
||||
private String key;
|
||||
private int number;
|
||||
|
||||
public IMAPResponse(Protocol c) throws IOException, ProtocolException {
|
||||
super(c);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() throws IOException, ProtocolException {
|
||||
// continue parsing if this is an untagged response
|
||||
if (isUnTagged() && !isOK() && !isNO() && !isBAD() && !isBYE()) {
|
||||
key = readAtom();
|
||||
|
||||
// Is this response of the form "* <number> <command>"
|
||||
try {
|
||||
number = Integer.parseInt(key);
|
||||
key = readAtom();
|
||||
} catch (NumberFormatException ne) { }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
*
|
||||
* @param r the IMAPResponse to copy
|
||||
*/
|
||||
public IMAPResponse(IMAPResponse r) {
|
||||
super((Response)r);
|
||||
key = r.key;
|
||||
number = r.number;
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing.
|
||||
*
|
||||
* @param r the response string
|
||||
* @exception IOException for I/O errors
|
||||
* @exception ProtocolException for protocol failures
|
||||
*/
|
||||
public IMAPResponse(String r) throws IOException, ProtocolException {
|
||||
this(r, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing.
|
||||
*
|
||||
* @param r the response string
|
||||
* @param utf8 UTF-8 allowed?
|
||||
* @exception IOException for I/O errors
|
||||
* @exception ProtocolException for protocol failures
|
||||
* @since JavaMail 1.6.0
|
||||
*/
|
||||
public IMAPResponse(String r, boolean utf8)
|
||||
throws IOException, ProtocolException {
|
||||
super(r, utf8);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a list of space-separated "flag-extension" sequences and
|
||||
* return the list as a array of Strings. An empty list is returned
|
||||
* as null. Each item is expected to be an atom, possibly preceeded
|
||||
* by a backslash, but we aren't that strict; we just look for strings
|
||||
* separated by spaces and terminated by a right paren. We assume items
|
||||
* are always ASCII.
|
||||
*
|
||||
* @return the list items as a String array
|
||||
*/
|
||||
public String[] readSimpleList() {
|
||||
skipSpaces();
|
||||
|
||||
if (buffer[index] != '(') // not what we expected
|
||||
return null;
|
||||
index++; // skip '('
|
||||
|
||||
List<String> v = new ArrayList<>();
|
||||
int start;
|
||||
for (start = index; buffer[index] != ')'; index++) {
|
||||
if (buffer[index] == ' ') { // got one item
|
||||
v.add(ASCIIUtility.toString(buffer, start, index));
|
||||
start = index+1; // index gets incremented at the top
|
||||
}
|
||||
}
|
||||
if (index > start) // get the last item
|
||||
v.add(ASCIIUtility.toString(buffer, start, index));
|
||||
index++; // skip ')'
|
||||
|
||||
int size = v.size();
|
||||
if (size > 0)
|
||||
return v.toArray(new String[size]);
|
||||
else // empty list
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public boolean keyEquals(String k) {
|
||||
if (key != null && key.equalsIgnoreCase(k))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getNumber() {
|
||||
return number;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import java.util.Locale;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.text.FieldPosition;
|
||||
|
||||
import javax.mail.internet.MailDateFormat;
|
||||
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
|
||||
/**
|
||||
* An INTERNALDATE FETCH item.
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class INTERNALDATE implements Item {
|
||||
|
||||
static final char[] name =
|
||||
{'I','N','T','E','R','N','A','L','D','A','T','E'};
|
||||
public int msgno;
|
||||
protected Date date;
|
||||
|
||||
/*
|
||||
* Used to parse dates only. The parse method is thread safe
|
||||
* so we only need to create a single object for use by all
|
||||
* instances. We depend on the fact that the MailDateFormat
|
||||
* class will parse dates in INTERNALDATE format as well as
|
||||
* dates in RFC 822 format.
|
||||
*/
|
||||
private static final MailDateFormat mailDateFormat = new MailDateFormat();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param r the FetchResponse
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public INTERNALDATE(FetchResponse r) throws ParsingException {
|
||||
msgno = r.getNumber();
|
||||
r.skipSpaces();
|
||||
String s = r.readString();
|
||||
if (s == null)
|
||||
throw new ParsingException("INTERNALDATE is NIL");
|
||||
try {
|
||||
synchronized (mailDateFormat) {
|
||||
date = mailDateFormat.parse(s);
|
||||
}
|
||||
} catch (ParseException pex) {
|
||||
throw new ParsingException("INTERNALDATE parse error");
|
||||
}
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
// INTERNALDATE formatter
|
||||
|
||||
private static SimpleDateFormat df =
|
||||
// Need Locale.US, the "MMM" field can produce unexpected values
|
||||
// in non US locales !
|
||||
new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss ", Locale.US);
|
||||
|
||||
/**
|
||||
* Format given Date object into INTERNALDATE string
|
||||
*
|
||||
* @param d the Date
|
||||
* @return INTERNALDATE string
|
||||
*/
|
||||
public static String format(Date d) {
|
||||
/*
|
||||
* SimpleDateFormat objects aren't thread safe, so rather
|
||||
* than create a separate such object for each request,
|
||||
* we create one object and synchronize its use here
|
||||
* so that only one thread is using it at a time. This
|
||||
* trades off some potential concurrency for speed in the
|
||||
* common case.
|
||||
*
|
||||
* This method is only used when formatting the date in a
|
||||
* message that's being appended to a folder.
|
||||
*/
|
||||
StringBuffer sb = new StringBuffer();
|
||||
synchronized (df) {
|
||||
df.format(d, sb, new FieldPosition(0));
|
||||
}
|
||||
|
||||
// compute timezone offset string
|
||||
TimeZone tz = TimeZone.getDefault();
|
||||
int offset = tz.getOffset(d.getTime()); // get offset from GMT
|
||||
int rawOffsetInMins = offset / 60 / 1000; // offset from GMT in mins
|
||||
if (rawOffsetInMins < 0) {
|
||||
sb.append('-');
|
||||
rawOffsetInMins = (-rawOffsetInMins);
|
||||
} else
|
||||
sb.append('+');
|
||||
|
||||
int offsetInHrs = rawOffsetInMins / 60;
|
||||
int offsetInMins = rawOffsetInMins % 60;
|
||||
|
||||
sb.append(Character.forDigit((offsetInHrs/10), 10));
|
||||
sb.append(Character.forDigit((offsetInHrs%10), 10));
|
||||
sb.append(Character.forDigit((offsetInMins/10), 10));
|
||||
sb.append(Character.forDigit((offsetInMins%10), 10));
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
/**
|
||||
* A tagging interface for all IMAP data items.
|
||||
* Note that the "name" field of all IMAP items MUST be in uppercase. <p>
|
||||
*
|
||||
* See the BODY, BODYSTRUCTURE, ENVELOPE, FLAGS, INTERNALDATE, RFC822DATA,
|
||||
* RFC822SIZE, and UID classes.
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public interface Item {
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* A LIST response.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class ListInfo {
|
||||
public String name = null;
|
||||
public char separator = '/';
|
||||
public boolean hasInferiors = true;
|
||||
public boolean canOpen = true;
|
||||
public int changeState = INDETERMINATE;
|
||||
public String[] attrs;
|
||||
|
||||
public static final int CHANGED = 1;
|
||||
public static final int UNCHANGED = 2;
|
||||
public static final int INDETERMINATE = 3;
|
||||
|
||||
public ListInfo(IMAPResponse r) throws ParsingException {
|
||||
String[] s = r.readSimpleList();
|
||||
|
||||
List<String> v = new ArrayList<>(); // accumulate attributes
|
||||
if (s != null) {
|
||||
// non-empty attribute list
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
if (s[i].equalsIgnoreCase("\\Marked"))
|
||||
changeState = CHANGED;
|
||||
else if (s[i].equalsIgnoreCase("\\Unmarked"))
|
||||
changeState = UNCHANGED;
|
||||
else if (s[i].equalsIgnoreCase("\\Noselect"))
|
||||
canOpen = false;
|
||||
else if (s[i].equalsIgnoreCase("\\Noinferiors"))
|
||||
hasInferiors = false;
|
||||
v.add(s[i]);
|
||||
}
|
||||
}
|
||||
attrs = v.toArray(new String[v.size()]);
|
||||
|
||||
r.skipSpaces();
|
||||
if (r.readByte() == '"') {
|
||||
if ((separator = (char)r.readByte()) == '\\')
|
||||
// escaped separator character
|
||||
separator = (char)r.readByte();
|
||||
r.skip(1); // skip <">
|
||||
} else // NIL
|
||||
r.skip(2);
|
||||
|
||||
r.skipSpaces();
|
||||
name = r.readAtomString();
|
||||
|
||||
if (!r.supportsUtf8())
|
||||
// decode the name (using RFC2060's modified UTF7)
|
||||
name = BASE64MailboxDecoder.decode(name);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* This class represents the MODSEQ data item.
|
||||
*
|
||||
* @since JavaMail 1.5.1
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class MODSEQ implements Item {
|
||||
|
||||
static final char[] name = {'M','O','D','S','E','Q'};
|
||||
public int seqnum;
|
||||
|
||||
public long modseq;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param r the FetchResponse
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public MODSEQ(FetchResponse r) throws ParsingException {
|
||||
seqnum = r.getNumber();
|
||||
r.skipSpaces();
|
||||
|
||||
if (r.readByte() != '(')
|
||||
throw new ParsingException("MODSEQ parse error");
|
||||
|
||||
modseq = r.readLong();
|
||||
|
||||
if (!r.isNextNonSpace(')'))
|
||||
throw new ParsingException("MODSEQ parse error");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.mail.Flags;
|
||||
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* Information collected when opening a mailbox.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class MailboxInfo {
|
||||
/** The available flags. */
|
||||
public Flags availableFlags = null;
|
||||
/** The permanent flags. */
|
||||
public Flags permanentFlags = null;
|
||||
/** The total number of messages. */
|
||||
public int total = -1;
|
||||
/** The number of recent messages. */
|
||||
public int recent = -1;
|
||||
/** The first unseen message. */
|
||||
public int first = -1;
|
||||
/** The UIDVALIDITY. */
|
||||
public long uidvalidity = -1;
|
||||
/** The next UID value to be assigned. */
|
||||
public long uidnext = -1;
|
||||
/** UIDs are not sticky. */
|
||||
public boolean uidNotSticky = false; // RFC 4315
|
||||
/** The highest MODSEQ value. */
|
||||
public long highestmodseq = -1; // RFC 4551 - CONDSTORE
|
||||
/** Folder.READ_WRITE or Folder.READ_ONLY, set by IMAPProtocol. */
|
||||
public int mode;
|
||||
/** VANISHED or FETCH responses received while opening the mailbox. */
|
||||
public List<IMAPResponse> responses;
|
||||
|
||||
/**
|
||||
* Collect the information about this mailbox from the
|
||||
* responses to a SELECT or EXAMINE.
|
||||
*
|
||||
* @param r the responses
|
||||
* @exception ParsingException for errors parsing the responses
|
||||
*/
|
||||
public MailboxInfo(Response[] r) throws ParsingException {
|
||||
for (int i = 0; i < r.length; i++) {
|
||||
if (r[i] == null || !(r[i] instanceof IMAPResponse))
|
||||
continue;
|
||||
|
||||
IMAPResponse ir = (IMAPResponse)r[i];
|
||||
|
||||
if (ir.keyEquals("EXISTS")) {
|
||||
total = ir.getNumber();
|
||||
r[i] = null; // remove this response
|
||||
} else if (ir.keyEquals("RECENT")) {
|
||||
recent = ir.getNumber();
|
||||
r[i] = null; // remove this response
|
||||
} else if (ir.keyEquals("FLAGS")) {
|
||||
availableFlags = new FLAGS(ir);
|
||||
r[i] = null; // remove this response
|
||||
} else if (ir.keyEquals("VANISHED")) {
|
||||
if (responses == null)
|
||||
responses = new ArrayList<>();
|
||||
responses.add(ir);
|
||||
r[i] = null; // remove this response
|
||||
} else if (ir.keyEquals("FETCH")) {
|
||||
if (responses == null)
|
||||
responses = new ArrayList<>();
|
||||
responses.add(ir);
|
||||
r[i] = null; // remove this response
|
||||
} else if (ir.isUnTagged() && ir.isOK()) {
|
||||
/*
|
||||
* should be one of:
|
||||
* * OK [UNSEEN 12]
|
||||
* * OK [UIDVALIDITY 3857529045]
|
||||
* * OK [PERMANENTFLAGS (\Deleted)]
|
||||
* * OK [UIDNEXT 44]
|
||||
* * OK [HIGHESTMODSEQ 103]
|
||||
*/
|
||||
ir.skipSpaces();
|
||||
|
||||
if (ir.readByte() != '[') { // huh ???
|
||||
ir.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
String s = ir.readAtom();
|
||||
if (s.equalsIgnoreCase("UNSEEN"))
|
||||
first = ir.readNumber();
|
||||
else if (s.equalsIgnoreCase("UIDVALIDITY"))
|
||||
uidvalidity = ir.readLong();
|
||||
else if (s.equalsIgnoreCase("PERMANENTFLAGS"))
|
||||
permanentFlags = new FLAGS(ir);
|
||||
else if (s.equalsIgnoreCase("UIDNEXT"))
|
||||
uidnext = ir.readLong();
|
||||
else if (s.equalsIgnoreCase("HIGHESTMODSEQ"))
|
||||
highestmodseq = ir.readLong();
|
||||
else
|
||||
handled = false; // possibly an ALERT
|
||||
|
||||
if (handled)
|
||||
r[i] = null; // remove this response
|
||||
else
|
||||
ir.reset(); // so ALERT can be read
|
||||
} else if (ir.isUnTagged() && ir.isNO()) {
|
||||
/*
|
||||
* should be one of:
|
||||
* * NO [UIDNOTSTICKY]
|
||||
*/
|
||||
ir.skipSpaces();
|
||||
|
||||
if (ir.readByte() != '[') { // huh ???
|
||||
ir.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean handled = true;
|
||||
String s = ir.readAtom();
|
||||
if (s.equalsIgnoreCase("UIDNOTSTICKY"))
|
||||
uidNotSticky = true;
|
||||
else
|
||||
handled = false; // possibly an ALERT
|
||||
|
||||
if (handled)
|
||||
r[i] = null; // remove this response
|
||||
else
|
||||
ir.reset(); // so ALERT can be read
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The PERMANENTFLAGS response code is optional, and if
|
||||
* not present implies that all flags in the required FLAGS
|
||||
* response can be changed permanently.
|
||||
*/
|
||||
if (permanentFlags == null) {
|
||||
if (availableFlags != null)
|
||||
permanentFlags = new Flags(availableFlags);
|
||||
else
|
||||
permanentFlags = new Flags();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This class holds the 'start' and 'end' for a range of messages.
|
||||
*/
|
||||
public class MessageSet {
|
||||
|
||||
public int start;
|
||||
public int end;
|
||||
|
||||
public MessageSet() { }
|
||||
|
||||
public MessageSet(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the total number of elements in a MessageSet
|
||||
*
|
||||
* @return how many messages in this MessageSet
|
||||
*/
|
||||
public int size() {
|
||||
return end - start + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of integers into an array of MessageSets
|
||||
*
|
||||
* @param msgs the messages
|
||||
* @return array of MessageSet objects
|
||||
*/
|
||||
public static MessageSet[] createMessageSets(int[] msgs) {
|
||||
List<MessageSet> v = new ArrayList<>();
|
||||
int i,j;
|
||||
|
||||
for (i=0; i < msgs.length; i++) {
|
||||
MessageSet ms = new MessageSet();
|
||||
ms.start = msgs[i];
|
||||
|
||||
// Look for contiguous elements
|
||||
for (j=i+1; j < msgs.length; j++) {
|
||||
if (msgs[j] != msgs[j-1] +1)
|
||||
break;
|
||||
}
|
||||
ms.end = msgs[j-1];
|
||||
v.add(ms);
|
||||
i = j-1; // i gets incremented @ top of the loop
|
||||
}
|
||||
return v.toArray(new MessageSet[v.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of MessageSets into an IMAP sequence range
|
||||
*
|
||||
* @param msgsets the MessageSets
|
||||
* @return IMAP sequence string
|
||||
*/
|
||||
public static String toString(MessageSet[] msgsets) {
|
||||
if (msgsets == null || msgsets.length == 0) // Empty msgset
|
||||
return null;
|
||||
|
||||
int i = 0; // msgset index
|
||||
StringBuilder s = new StringBuilder();
|
||||
int size = msgsets.length;
|
||||
int start, end;
|
||||
|
||||
for (;;) {
|
||||
start = msgsets[i].start;
|
||||
end = msgsets[i].end;
|
||||
|
||||
if (end > start)
|
||||
s.append(start).append(':').append(end);
|
||||
else // end == start means only one element
|
||||
s.append(start);
|
||||
|
||||
i++; // Next MessageSet
|
||||
if (i >= size) // No more MessageSets
|
||||
break;
|
||||
else
|
||||
s.append(',');
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Count the total number of elements in an array of MessageSets
|
||||
*/
|
||||
public static int size(MessageSet[] msgsets) {
|
||||
int count = 0;
|
||||
|
||||
if (msgsets == null) // Null msgset
|
||||
return 0;
|
||||
|
||||
for (int i=0; i < msgsets.length; i++)
|
||||
count += msgsets[i].size();
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.*;
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* This class and its inner class represent the response to the
|
||||
* NAMESPACE command. <p>
|
||||
*
|
||||
* See <A HREF="http://www.ietf.org/rfc/rfc2342.txt">RFC 2342</A>.
|
||||
*
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class Namespaces {
|
||||
|
||||
/**
|
||||
* A single namespace entry.
|
||||
*/
|
||||
public static class Namespace {
|
||||
/**
|
||||
* Prefix string for the namespace.
|
||||
*/
|
||||
public String prefix;
|
||||
|
||||
/**
|
||||
* Delimiter between names in this namespace.
|
||||
*/
|
||||
public char delimiter;
|
||||
|
||||
/**
|
||||
* Parse a namespace element out of the response.
|
||||
*
|
||||
* @param r the Response to parse
|
||||
* @exception ProtocolException for any protocol errors
|
||||
*/
|
||||
public Namespace(Response r) throws ProtocolException {
|
||||
// Namespace_Element = "(" string SP (<"> QUOTED_CHAR <"> / nil)
|
||||
// *(Namespace_Response_Extension) ")"
|
||||
if (!r.isNextNonSpace('('))
|
||||
throw new ProtocolException(
|
||||
"Missing '(' at start of Namespace");
|
||||
// first, the prefix
|
||||
prefix = r.readString();
|
||||
if (!r.supportsUtf8())
|
||||
prefix = BASE64MailboxDecoder.decode(prefix);
|
||||
r.skipSpaces();
|
||||
// delimiter is a quoted character or NIL
|
||||
if (r.peekByte() == '"') {
|
||||
r.readByte();
|
||||
delimiter = (char)r.readByte();
|
||||
if (delimiter == '\\')
|
||||
delimiter = (char)r.readByte();
|
||||
if (r.readByte() != '"')
|
||||
throw new ProtocolException(
|
||||
"Missing '\"' at end of QUOTED_CHAR");
|
||||
} else {
|
||||
String s = r.readAtom();
|
||||
if (s == null)
|
||||
throw new ProtocolException("Expected NIL, got null");
|
||||
if (!s.equalsIgnoreCase("NIL"))
|
||||
throw new ProtocolException("Expected NIL, got " + s);
|
||||
delimiter = 0;
|
||||
}
|
||||
// at end of Namespace data?
|
||||
if (r.isNextNonSpace(')'))
|
||||
return;
|
||||
|
||||
// otherwise, must be a Namespace_Response_Extension
|
||||
// Namespace_Response_Extension = SP string SP
|
||||
// "(" string *(SP string) ")"
|
||||
r.readString();
|
||||
r.skipSpaces();
|
||||
r.readStringList();
|
||||
if (!r.isNextNonSpace(')'))
|
||||
throw new ProtocolException("Missing ')' at end of Namespace");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The personal namespaces.
|
||||
* May be null.
|
||||
*/
|
||||
public Namespace[] personal;
|
||||
|
||||
/**
|
||||
* The namespaces for other users.
|
||||
* May be null.
|
||||
*/
|
||||
public Namespace[] otherUsers;
|
||||
|
||||
/**
|
||||
* The shared namespace.
|
||||
* May be null.
|
||||
*/
|
||||
public Namespace[] shared;
|
||||
|
||||
/**
|
||||
* Parse out all the namespaces.
|
||||
*
|
||||
* @param r the Response to parse
|
||||
* @throws ProtocolException for any protocol errors
|
||||
*/
|
||||
public Namespaces(Response r) throws ProtocolException {
|
||||
personal = getNamespaces(r);
|
||||
otherUsers = getNamespaces(r);
|
||||
shared = getNamespaces(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out one of the three sets of namespaces.
|
||||
*/
|
||||
private Namespace[] getNamespaces(Response r) throws ProtocolException {
|
||||
// Namespace = nil / "(" 1*( Namespace_Element) ")"
|
||||
if (r.isNextNonSpace('(')) {
|
||||
List<Namespace> v = new ArrayList<>();
|
||||
do {
|
||||
Namespace ns = new Namespace(r);
|
||||
v.add(ns);
|
||||
} while (!r.isNextNonSpace(')'));
|
||||
return v.toArray(new Namespace[v.size()]);
|
||||
} else {
|
||||
String s = r.readAtom();
|
||||
if (s == null)
|
||||
throw new ProtocolException("Expected NIL, got null");
|
||||
if (!s.equalsIgnoreCase("NIL"))
|
||||
throw new ProtocolException("Expected NIL, got " + s);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import com.sun.mail.iap.*;
|
||||
import com.sun.mail.util.ASCIIUtility;
|
||||
|
||||
/**
|
||||
* The RFC822 response data item.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class RFC822DATA implements Item {
|
||||
|
||||
static final char[] name = {'R','F','C','8','2','2'};
|
||||
private final int msgno;
|
||||
private final ByteArray data;
|
||||
private final boolean isHeader;
|
||||
|
||||
/**
|
||||
* Constructor, header flag is false.
|
||||
*
|
||||
* @param r the FetchResponse
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public RFC822DATA(FetchResponse r) throws ParsingException {
|
||||
this(r, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor, specifying header flag.
|
||||
*
|
||||
* @param r the FetchResponse
|
||||
* @param isHeader just header information?
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public RFC822DATA(FetchResponse r, boolean isHeader)
|
||||
throws ParsingException {
|
||||
this.isHeader = isHeader;
|
||||
msgno = r.getNumber();
|
||||
r.skipSpaces();
|
||||
data = r.readByteArray();
|
||||
}
|
||||
|
||||
public ByteArray getByteArray() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public ByteArrayInputStream getByteArrayInputStream() {
|
||||
if (data != null)
|
||||
return data.toByteArrayInputStream();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isHeader() {
|
||||
return isHeader;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* An RFC822SIZE FETCH item.
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class RFC822SIZE implements Item {
|
||||
|
||||
static final char[] name = {'R','F','C','8','2','2','.','S','I','Z','E'};
|
||||
public int msgno;
|
||||
|
||||
public long size;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param r the FetchResponse
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public RFC822SIZE(FetchResponse r) throws ParsingException {
|
||||
msgno = r.getNumber();
|
||||
r.skipSpaces();
|
||||
size = r.readLong();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import com.sun.mail.iap.ProtocolException;
|
||||
|
||||
/**
|
||||
* Interface to make it easier to call IMAPSaslAuthenticator.
|
||||
*/
|
||||
|
||||
public interface SaslAuthenticator {
|
||||
public boolean authenticate(String[] mechs, String realm, String authzid,
|
||||
String u, String p) throws ProtocolException;
|
||||
|
||||
}
|
||||
@ -0,0 +1,527 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.mail.*;
|
||||
import javax.mail.search.*;
|
||||
import com.sun.mail.iap.*;
|
||||
import com.sun.mail.imap.OlderTerm;
|
||||
import com.sun.mail.imap.YoungerTerm;
|
||||
import com.sun.mail.imap.ModifiedSinceTerm;
|
||||
|
||||
/**
|
||||
* This class traverses a search-tree and generates the
|
||||
* corresponding IMAP search sequence.
|
||||
*
|
||||
* Each IMAPProtocol instance contains an instance of this class,
|
||||
* which might be subclassed by subclasses of IMAPProtocol to add
|
||||
* support for additional product-specific search terms.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
public class SearchSequence {
|
||||
|
||||
private IMAPProtocol protocol; // for hasCapability checks; may be null
|
||||
|
||||
/**
|
||||
* Create a SearchSequence for this IMAPProtocol.
|
||||
*
|
||||
* @param p the IMAPProtocol object for the server
|
||||
* @since JavaMail 1.6.0
|
||||
*/
|
||||
public SearchSequence(IMAPProtocol p) {
|
||||
protocol = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a SearchSequence.
|
||||
*/
|
||||
@Deprecated
|
||||
public SearchSequence() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the IMAP search sequence for the given search expression.
|
||||
*
|
||||
* @param term the search term
|
||||
* @param charset charset for the search
|
||||
* @return the SEARCH Argument
|
||||
* @exception SearchException for failures
|
||||
* @exception IOException for I/O errors
|
||||
*/
|
||||
public Argument generateSequence(SearchTerm term, String charset)
|
||||
throws SearchException, IOException {
|
||||
/*
|
||||
* Call the appropriate handler depending on the type of
|
||||
* the search-term ...
|
||||
*/
|
||||
if (term instanceof AndTerm) // AND
|
||||
return and((AndTerm)term, charset);
|
||||
else if (term instanceof OrTerm) // OR
|
||||
return or((OrTerm)term, charset);
|
||||
else if (term instanceof NotTerm) // NOT
|
||||
return not((NotTerm)term, charset);
|
||||
else if (term instanceof HeaderTerm) // HEADER
|
||||
return header((HeaderTerm)term, charset);
|
||||
else if (term instanceof FlagTerm) // FLAG
|
||||
return flag((FlagTerm)term);
|
||||
else if (term instanceof FromTerm) { // FROM
|
||||
FromTerm fterm = (FromTerm)term;
|
||||
return from(fterm.getAddress().toString(), charset);
|
||||
}
|
||||
else if (term instanceof FromStringTerm) { // FROM
|
||||
FromStringTerm fterm = (FromStringTerm)term;
|
||||
return from(fterm.getPattern(), charset);
|
||||
}
|
||||
else if (term instanceof RecipientTerm) { // RECIPIENT
|
||||
RecipientTerm rterm = (RecipientTerm)term;
|
||||
return recipient(rterm.getRecipientType(),
|
||||
rterm.getAddress().toString(),
|
||||
charset);
|
||||
}
|
||||
else if (term instanceof RecipientStringTerm) { // RECIPIENT
|
||||
RecipientStringTerm rterm = (RecipientStringTerm)term;
|
||||
return recipient(rterm.getRecipientType(),
|
||||
rterm.getPattern(),
|
||||
charset);
|
||||
}
|
||||
else if (term instanceof SubjectTerm) // SUBJECT
|
||||
return subject((SubjectTerm)term, charset);
|
||||
else if (term instanceof BodyTerm) // BODY
|
||||
return body((BodyTerm)term, charset);
|
||||
else if (term instanceof SizeTerm) // SIZE
|
||||
return size((SizeTerm)term);
|
||||
else if (term instanceof SentDateTerm) // SENTDATE
|
||||
return sentdate((SentDateTerm)term);
|
||||
else if (term instanceof ReceivedDateTerm) // INTERNALDATE
|
||||
return receiveddate((ReceivedDateTerm)term);
|
||||
else if (term instanceof OlderTerm) // RFC 5032 OLDER
|
||||
return older((OlderTerm)term);
|
||||
else if (term instanceof YoungerTerm) // RFC 5032 YOUNGER
|
||||
return younger((YoungerTerm)term);
|
||||
else if (term instanceof MessageIDTerm) // MessageID
|
||||
return messageid((MessageIDTerm)term, charset);
|
||||
else if (term instanceof ModifiedSinceTerm) // RFC 4551 MODSEQ
|
||||
return modifiedSince((ModifiedSinceTerm)term);
|
||||
else
|
||||
throw new SearchException("Search too complex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the "text" terms in the given SearchTerm contain
|
||||
* non US-ASCII characters.
|
||||
*
|
||||
* @param term the search term
|
||||
* @return true if only ASCII
|
||||
*/
|
||||
public static boolean isAscii(SearchTerm term) {
|
||||
if (term instanceof AndTerm)
|
||||
return isAscii(((AndTerm)term).getTerms());
|
||||
else if (term instanceof OrTerm)
|
||||
return isAscii(((OrTerm)term).getTerms());
|
||||
else if (term instanceof NotTerm)
|
||||
return isAscii(((NotTerm)term).getTerm());
|
||||
else if (term instanceof StringTerm)
|
||||
return isAscii(((StringTerm)term).getPattern());
|
||||
else if (term instanceof AddressTerm)
|
||||
return isAscii(((AddressTerm)term).getAddress().toString());
|
||||
|
||||
// Any other term returns true.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of the "text" terms in the given SearchTerms contain
|
||||
* non US-ASCII characters.
|
||||
*
|
||||
* @param terms the search terms
|
||||
* @return true if only ASCII
|
||||
*/
|
||||
public static boolean isAscii(SearchTerm[] terms) {
|
||||
for (int i = 0; i < terms.length; i++)
|
||||
if (!isAscii(terms[i])) // outta here !
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this string contain only ASCII characters?
|
||||
*
|
||||
* @param s the string
|
||||
* @return true if only ASCII
|
||||
*/
|
||||
public static boolean isAscii(String s) {
|
||||
int l = s.length();
|
||||
|
||||
for (int i=0; i < l; i++) {
|
||||
if ((int)s.charAt(i) > 0177) // non-ascii
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Argument and(AndTerm term, String charset)
|
||||
throws SearchException, IOException {
|
||||
// Combine the sequences for both terms
|
||||
SearchTerm[] terms = term.getTerms();
|
||||
// Generate the search sequence for the first term
|
||||
Argument result = generateSequence(terms[0], charset);
|
||||
// Append other terms
|
||||
for (int i = 1; i < terms.length; i++)
|
||||
result.append(generateSequence(terms[i], charset));
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument or(OrTerm term, String charset)
|
||||
throws SearchException, IOException {
|
||||
SearchTerm[] terms = term.getTerms();
|
||||
|
||||
/* The IMAP OR operator takes only two operands. So if
|
||||
* we have more than 2 operands, group them into 2-operand
|
||||
* OR Terms.
|
||||
*/
|
||||
if (terms.length > 2) {
|
||||
SearchTerm t = terms[0];
|
||||
|
||||
// Include rest of the terms
|
||||
for (int i = 1; i < terms.length; i++)
|
||||
t = new OrTerm(t, terms[i]);
|
||||
|
||||
term = (OrTerm)t; // set 'term' to the new jumbo OrTerm we
|
||||
// just created
|
||||
terms = term.getTerms();
|
||||
}
|
||||
|
||||
// 'term' now has only two operands
|
||||
Argument result = new Argument();
|
||||
|
||||
// Add the OR search-key, if more than one term
|
||||
if (terms.length > 1)
|
||||
result.writeAtom("OR");
|
||||
|
||||
/* If this term is an AND expression, we need to enclose it
|
||||
* within paranthesis.
|
||||
*
|
||||
* AND expressions are either AndTerms or FlagTerms
|
||||
*/
|
||||
if (terms[0] instanceof AndTerm || terms[0] instanceof FlagTerm)
|
||||
result.writeArgument(generateSequence(terms[0], charset));
|
||||
else
|
||||
result.append(generateSequence(terms[0], charset));
|
||||
|
||||
// Repeat the above for the second term, if there is one
|
||||
if (terms.length > 1) {
|
||||
if (terms[1] instanceof AndTerm || terms[1] instanceof FlagTerm)
|
||||
result.writeArgument(generateSequence(terms[1], charset));
|
||||
else
|
||||
result.append(generateSequence(terms[1], charset));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument not(NotTerm term, String charset)
|
||||
throws SearchException, IOException {
|
||||
Argument result = new Argument();
|
||||
|
||||
// Add the NOT search-key
|
||||
result.writeAtom("NOT");
|
||||
|
||||
/* If this term is an AND expression, we need to enclose it
|
||||
* within paranthesis.
|
||||
*
|
||||
* AND expressions are either AndTerms or FlagTerms
|
||||
*/
|
||||
SearchTerm nterm = term.getTerm();
|
||||
if (nterm instanceof AndTerm || nterm instanceof FlagTerm)
|
||||
result.writeArgument(generateSequence(nterm, charset));
|
||||
else
|
||||
result.append(generateSequence(nterm, charset));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument header(HeaderTerm term, String charset)
|
||||
throws SearchException, IOException {
|
||||
Argument result = new Argument();
|
||||
result.writeAtom("HEADER");
|
||||
result.writeString(term.getHeaderName());
|
||||
result.writeString(term.getPattern(), charset);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument messageid(MessageIDTerm term, String charset)
|
||||
throws SearchException, IOException {
|
||||
Argument result = new Argument();
|
||||
result.writeAtom("HEADER");
|
||||
result.writeString("Message-ID");
|
||||
// XXX confirm that charset conversion ought to be done
|
||||
result.writeString(term.getPattern(), charset);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument flag(FlagTerm term) throws SearchException {
|
||||
boolean set = term.getTestSet();
|
||||
|
||||
Argument result = new Argument();
|
||||
|
||||
Flags flags = term.getFlags();
|
||||
Flags.Flag[] sf = flags.getSystemFlags();
|
||||
String[] uf = flags.getUserFlags();
|
||||
if (sf.length == 0 && uf.length == 0)
|
||||
throw new SearchException("Invalid FlagTerm");
|
||||
|
||||
for (int i = 0; i < sf.length; i++) {
|
||||
if (sf[i] == Flags.Flag.DELETED)
|
||||
result.writeAtom(set ? "DELETED": "UNDELETED");
|
||||
else if (sf[i] == Flags.Flag.ANSWERED)
|
||||
result.writeAtom(set ? "ANSWERED": "UNANSWERED");
|
||||
else if (sf[i] == Flags.Flag.DRAFT)
|
||||
result.writeAtom(set ? "DRAFT": "UNDRAFT");
|
||||
else if (sf[i] == Flags.Flag.FLAGGED)
|
||||
result.writeAtom(set ? "FLAGGED": "UNFLAGGED");
|
||||
else if (sf[i] == Flags.Flag.RECENT)
|
||||
result.writeAtom(set ? "RECENT": "OLD");
|
||||
else if (sf[i] == Flags.Flag.SEEN)
|
||||
result.writeAtom(set ? "SEEN": "UNSEEN");
|
||||
}
|
||||
|
||||
for (int i = 0; i < uf.length; i++) {
|
||||
result.writeAtom(set ? "KEYWORD" : "UNKEYWORD");
|
||||
result.writeAtom(uf[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument from(String address, String charset)
|
||||
throws SearchException, IOException {
|
||||
Argument result = new Argument();
|
||||
result.writeAtom("FROM");
|
||||
result.writeString(address, charset);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument recipient(Message.RecipientType type,
|
||||
String address, String charset)
|
||||
throws SearchException, IOException {
|
||||
Argument result = new Argument();
|
||||
|
||||
if (type == Message.RecipientType.TO)
|
||||
result.writeAtom("TO");
|
||||
else if (type == Message.RecipientType.CC)
|
||||
result.writeAtom("CC");
|
||||
else if (type == Message.RecipientType.BCC)
|
||||
result.writeAtom("BCC");
|
||||
else
|
||||
throw new SearchException("Illegal Recipient type");
|
||||
|
||||
result.writeString(address, charset);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument subject(SubjectTerm term, String charset)
|
||||
throws SearchException, IOException {
|
||||
Argument result = new Argument();
|
||||
|
||||
result.writeAtom("SUBJECT");
|
||||
result.writeString(term.getPattern(), charset);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument body(BodyTerm term, String charset)
|
||||
throws SearchException, IOException {
|
||||
Argument result = new Argument();
|
||||
|
||||
result.writeAtom("BODY");
|
||||
result.writeString(term.getPattern(), charset);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument size(SizeTerm term)
|
||||
throws SearchException {
|
||||
Argument result = new Argument();
|
||||
|
||||
switch (term.getComparison()) {
|
||||
case ComparisonTerm.GT:
|
||||
result.writeAtom("LARGER");
|
||||
break;
|
||||
case ComparisonTerm.LT:
|
||||
result.writeAtom("SMALLER");
|
||||
break;
|
||||
default:
|
||||
// GT and LT is all we get from IMAP for size
|
||||
throw new SearchException("Cannot handle Comparison");
|
||||
}
|
||||
|
||||
result.writeNumber(term.getNumber());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Date SEARCH stuff ...
|
||||
|
||||
// NOTE: The built-in IMAP date comparisons are equivalent to
|
||||
// "<" (BEFORE), "=" (ON), and ">=" (SINCE)!!!
|
||||
// There is no built-in greater-than comparison!
|
||||
|
||||
/**
|
||||
* Print an IMAP Date string, that is suitable for the Date
|
||||
* SEARCH commands.
|
||||
*
|
||||
* The IMAP Date string is :
|
||||
* date ::= date_day "-" date_month "-" date_year
|
||||
*
|
||||
* Note that this format does not contain the TimeZone
|
||||
*/
|
||||
private static String monthTable[] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||
};
|
||||
|
||||
// A GregorianCalendar object in the current timezone
|
||||
protected Calendar cal = new GregorianCalendar();
|
||||
|
||||
protected String toIMAPDate(Date date) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
|
||||
cal.setTime(date);
|
||||
|
||||
s.append(cal.get(Calendar.DATE)).append("-");
|
||||
s.append(monthTable[cal.get(Calendar.MONTH)]).append('-');
|
||||
s.append(cal.get(Calendar.YEAR));
|
||||
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
protected Argument sentdate(DateTerm term)
|
||||
throws SearchException {
|
||||
Argument result = new Argument();
|
||||
String date = toIMAPDate(term.getDate());
|
||||
|
||||
switch (term.getComparison()) {
|
||||
case ComparisonTerm.GT:
|
||||
result.writeAtom("NOT SENTON " + date + " SENTSINCE " + date);
|
||||
break;
|
||||
case ComparisonTerm.EQ:
|
||||
result.writeAtom("SENTON " + date);
|
||||
break;
|
||||
case ComparisonTerm.LT:
|
||||
result.writeAtom("SENTBEFORE " + date);
|
||||
break;
|
||||
case ComparisonTerm.GE:
|
||||
result.writeAtom("SENTSINCE " + date);
|
||||
break;
|
||||
case ComparisonTerm.LE:
|
||||
result.writeAtom("OR SENTBEFORE " + date + " SENTON " + date);
|
||||
break;
|
||||
case ComparisonTerm.NE:
|
||||
result.writeAtom("NOT SENTON " + date);
|
||||
break;
|
||||
default:
|
||||
throw new SearchException("Cannot handle Date Comparison");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Argument receiveddate(DateTerm term)
|
||||
throws SearchException {
|
||||
Argument result = new Argument();
|
||||
String date = toIMAPDate(term.getDate());
|
||||
|
||||
switch (term.getComparison()) {
|
||||
case ComparisonTerm.GT:
|
||||
result.writeAtom("NOT ON " + date + " SINCE " + date);
|
||||
break;
|
||||
case ComparisonTerm.EQ:
|
||||
result.writeAtom("ON " + date);
|
||||
break;
|
||||
case ComparisonTerm.LT:
|
||||
result.writeAtom("BEFORE " + date);
|
||||
break;
|
||||
case ComparisonTerm.GE:
|
||||
result.writeAtom("SINCE " + date);
|
||||
break;
|
||||
case ComparisonTerm.LE:
|
||||
result.writeAtom("OR BEFORE " + date + " ON " + date);
|
||||
break;
|
||||
case ComparisonTerm.NE:
|
||||
result.writeAtom("NOT ON " + date);
|
||||
break;
|
||||
default:
|
||||
throw new SearchException("Cannot handle Date Comparison");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate argument for OlderTerm.
|
||||
*
|
||||
* @param term the search term
|
||||
* @return the SEARCH Argument
|
||||
* @exception SearchException for failures
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
protected Argument older(OlderTerm term) throws SearchException {
|
||||
if (protocol != null && !protocol.hasCapability("WITHIN"))
|
||||
throw new SearchException("Server doesn't support OLDER searches");
|
||||
Argument result = new Argument();
|
||||
result.writeAtom("OLDER");
|
||||
result.writeNumber(term.getInterval());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate argument for YoungerTerm.
|
||||
*
|
||||
* @param term the search term
|
||||
* @return the SEARCH Argument
|
||||
* @exception SearchException for failures
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
protected Argument younger(YoungerTerm term) throws SearchException {
|
||||
if (protocol != null && !protocol.hasCapability("WITHIN"))
|
||||
throw new SearchException("Server doesn't support YOUNGER searches");
|
||||
Argument result = new Argument();
|
||||
result.writeAtom("YOUNGER");
|
||||
result.writeNumber(term.getInterval());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate argument for ModifiedSinceTerm.
|
||||
*
|
||||
* @param term the search term
|
||||
* @return the SEARCH Argument
|
||||
* @exception SearchException for failures
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
protected Argument modifiedSince(ModifiedSinceTerm term)
|
||||
throws SearchException {
|
||||
if (protocol != null && !protocol.hasCapability("CONDSTORE"))
|
||||
throw new SearchException("Server doesn't support MODSEQ searches");
|
||||
Argument result = new Argument();
|
||||
result.writeAtom("MODSEQ");
|
||||
result.writeNumber(term.getModSeq());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* STATUS response.
|
||||
*
|
||||
* @author John Mani
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
|
||||
public class Status {
|
||||
public String mbox = null;
|
||||
public int total = -1;
|
||||
public int recent = -1;
|
||||
public long uidnext = -1;
|
||||
public long uidvalidity = -1;
|
||||
public int unseen = -1;
|
||||
public long highestmodseq = -1;
|
||||
public Map<String,Long> items; // any unknown items
|
||||
|
||||
static final String[] standardItems =
|
||||
{ "MESSAGES", "RECENT", "UNSEEN", "UIDNEXT", "UIDVALIDITY" };
|
||||
|
||||
public Status(Response r) throws ParsingException {
|
||||
// mailbox := astring
|
||||
mbox = r.readAtomString();
|
||||
if (!r.supportsUtf8())
|
||||
mbox = BASE64MailboxDecoder.decode(mbox);
|
||||
|
||||
// Workaround buggy IMAP servers that don't quote folder names
|
||||
// with spaces.
|
||||
final StringBuilder buffer = new StringBuilder();
|
||||
boolean onlySpaces = true;
|
||||
|
||||
while (r.peekByte() != '(' && r.peekByte() != 0) {
|
||||
final char next = (char)r.readByte();
|
||||
|
||||
buffer.append(next);
|
||||
|
||||
if (next != ' ') {
|
||||
onlySpaces = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlySpaces) {
|
||||
mbox = (mbox + buffer).trim();
|
||||
}
|
||||
|
||||
if (r.readByte() != '(')
|
||||
throw new ParsingException("parse error in STATUS");
|
||||
|
||||
do {
|
||||
String attr = r.readAtom();
|
||||
if (attr == null)
|
||||
throw new ParsingException("parse error in STATUS");
|
||||
if (attr.equalsIgnoreCase("MESSAGES"))
|
||||
total = r.readNumber();
|
||||
else if (attr.equalsIgnoreCase("RECENT"))
|
||||
recent = r.readNumber();
|
||||
else if (attr.equalsIgnoreCase("UIDNEXT"))
|
||||
uidnext = r.readLong();
|
||||
else if (attr.equalsIgnoreCase("UIDVALIDITY"))
|
||||
uidvalidity = r.readLong();
|
||||
else if (attr.equalsIgnoreCase("UNSEEN"))
|
||||
unseen = r.readNumber();
|
||||
else if (attr.equalsIgnoreCase("HIGHESTMODSEQ"))
|
||||
highestmodseq = r.readLong();
|
||||
else {
|
||||
if (items == null)
|
||||
items = new HashMap<>();
|
||||
items.put(attr.toUpperCase(Locale.ENGLISH),
|
||||
Long.valueOf(r.readLong()));
|
||||
}
|
||||
} while (!r.isNextNonSpace(')'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value for the STATUS item.
|
||||
*
|
||||
* @param item the STATUS item
|
||||
* @return the value
|
||||
* @since JavaMail 1.5.2
|
||||
*/
|
||||
public long getItem(String item) {
|
||||
item = item.toUpperCase(Locale.ENGLISH);
|
||||
Long v;
|
||||
long ret = -1;
|
||||
if (items != null && (v = items.get(item)) != null)
|
||||
ret = v.longValue();
|
||||
else if (item.equals("MESSAGES"))
|
||||
ret = total;
|
||||
else if (item.equals("RECENT"))
|
||||
ret = recent;
|
||||
else if (item.equals("UIDNEXT"))
|
||||
ret = uidnext;
|
||||
else if (item.equals("UIDVALIDITY"))
|
||||
ret = uidvalidity;
|
||||
else if (item.equals("UNSEEN"))
|
||||
ret = unseen;
|
||||
else if (item.equals("HIGHESTMODSEQ"))
|
||||
ret = highestmodseq;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void add(Status s1, Status s2) {
|
||||
if (s2.total != -1)
|
||||
s1.total = s2.total;
|
||||
if (s2.recent != -1)
|
||||
s1.recent = s2.recent;
|
||||
if (s2.uidnext != -1)
|
||||
s1.uidnext = s2.uidnext;
|
||||
if (s2.uidvalidity != -1)
|
||||
s1.uidvalidity = s2.uidvalidity;
|
||||
if (s2.unseen != -1)
|
||||
s1.unseen = s2.unseen;
|
||||
if (s2.highestmodseq != -1)
|
||||
s1.highestmodseq = s2.highestmodseq;
|
||||
if (s1.items == null)
|
||||
s1.items = s2.items;
|
||||
else if (s2.items != null)
|
||||
s1.items.putAll(s2.items);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import com.sun.mail.iap.*;
|
||||
|
||||
/**
|
||||
* This class represents the UID data item.
|
||||
*
|
||||
* @author John Mani
|
||||
*/
|
||||
|
||||
public class UID implements Item {
|
||||
|
||||
static final char[] name = {'U','I','D'};
|
||||
public int seqnum;
|
||||
|
||||
public long uid;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param r the FetchResponse
|
||||
* @exception ParsingException for parsing failures
|
||||
*/
|
||||
public UID(FetchResponse r) throws ParsingException {
|
||||
seqnum = r.getNumber();
|
||||
r.skipSpaces();
|
||||
uid = r.readLong();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.imap.protocol;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* This class holds the 'start' and 'end' for a range of UIDs.
|
||||
* Just like MessageSet except using long instead of int.
|
||||
*/
|
||||
public class UIDSet {
|
||||
|
||||
public long start;
|
||||
public long end;
|
||||
|
||||
public UIDSet() { }
|
||||
|
||||
public UIDSet(long start, long end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the total number of elements in a UIDSet
|
||||
*
|
||||
* @return the number of elements
|
||||
*/
|
||||
public long size() {
|
||||
return end - start + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of longs into an array of UIDSets
|
||||
*
|
||||
* @param uids the UIDs
|
||||
* @return array of UIDSet objects
|
||||
*/
|
||||
public static UIDSet[] createUIDSets(long[] uids) {
|
||||
if (uids == null)
|
||||
return null;
|
||||
List<UIDSet> v = new ArrayList<>();
|
||||
int i,j;
|
||||
|
||||
for (i=0; i < uids.length; i++) {
|
||||
UIDSet ms = new UIDSet();
|
||||
ms.start = uids[i];
|
||||
|
||||
// Look for contiguous elements
|
||||
for (j=i+1; j < uids.length; j++) {
|
||||
if (uids[j] != uids[j-1] +1)
|
||||
break;
|
||||
}
|
||||
ms.end = uids[j-1];
|
||||
v.add(ms);
|
||||
i = j-1; // i gets incremented @ top of the loop
|
||||
}
|
||||
UIDSet[] uidset = new UIDSet[v.size()];
|
||||
return v.toArray(uidset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string in IMAP UID range format.
|
||||
*
|
||||
* @param uids UID string
|
||||
* @return array of UIDSet objects
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
public static UIDSet[] parseUIDSets(String uids) {
|
||||
if (uids == null)
|
||||
return null;
|
||||
List<UIDSet> v = new ArrayList<>();
|
||||
StringTokenizer st = new StringTokenizer(uids, ",:", true);
|
||||
long start = -1;
|
||||
UIDSet cur = null;
|
||||
try {
|
||||
while(st.hasMoreTokens()) {
|
||||
String s = st.nextToken();
|
||||
if (s.equals(",")) {
|
||||
if (cur != null)
|
||||
v.add(cur);
|
||||
cur = null;
|
||||
} else if (s.equals(":")) {
|
||||
// nothing to do, wait for next number
|
||||
} else { // better be a number
|
||||
long n = Long.parseLong(s);
|
||||
if (cur != null)
|
||||
cur.end = n;
|
||||
else
|
||||
cur = new UIDSet(n, n);
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException nex) {
|
||||
// give up and return what we have so far
|
||||
}
|
||||
if (cur != null)
|
||||
v.add(cur);
|
||||
UIDSet[] uidset = new UIDSet[v.size()];
|
||||
return v.toArray(uidset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of UIDSets into an IMAP sequence range.
|
||||
*
|
||||
* @param uidset the UIDSets
|
||||
* @return the IMAP sequence string
|
||||
*/
|
||||
public static String toString(UIDSet[] uidset) {
|
||||
if (uidset == null)
|
||||
return null;
|
||||
if (uidset.length == 0) // Empty uidset
|
||||
return "";
|
||||
|
||||
int i = 0; // uidset index
|
||||
StringBuilder s = new StringBuilder();
|
||||
int size = uidset.length;
|
||||
long start, end;
|
||||
|
||||
for (;;) {
|
||||
start = uidset[i].start;
|
||||
end = uidset[i].end;
|
||||
|
||||
if (end > start)
|
||||
s.append(start).append(':').append(end);
|
||||
else // end == start means only one element
|
||||
s.append(start);
|
||||
|
||||
i++; // Next UIDSet
|
||||
if (i >= size) // No more UIDSets
|
||||
break;
|
||||
else
|
||||
s.append(',');
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of UIDSets into a array of long UIDs.
|
||||
*
|
||||
* @param uidset the UIDSets
|
||||
* @return arrray of UIDs
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
public static long[] toArray(UIDSet[] uidset) {
|
||||
//return toArray(uidset, -1);
|
||||
if (uidset == null)
|
||||
return null;
|
||||
long[] uids = new long[(int)UIDSet.size(uidset)];
|
||||
int i = 0;
|
||||
for (UIDSet u : uidset) {
|
||||
for (long n = u.start; n <= u.end; n++)
|
||||
uids[i++] = n;
|
||||
}
|
||||
return uids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of UIDSets into a array of long UIDs.
|
||||
* Don't include any UIDs larger than uidmax.
|
||||
*
|
||||
* @param uidset the UIDSets
|
||||
* @param uidmax maximum UID
|
||||
* @return arrray of UIDs
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
public static long[] toArray(UIDSet[] uidset, long uidmax) {
|
||||
if (uidset == null)
|
||||
return null;
|
||||
long[] uids = new long[(int)UIDSet.size(uidset, uidmax)];
|
||||
int i = 0;
|
||||
for (UIDSet u : uidset) {
|
||||
for (long n = u.start; n <= u.end; n++) {
|
||||
if (uidmax >= 0 && n > uidmax)
|
||||
break;
|
||||
uids[i++] = n;
|
||||
}
|
||||
}
|
||||
return uids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the total number of elements in an array of UIDSets.
|
||||
*
|
||||
* @param uidset the UIDSets
|
||||
* @return the number of elements
|
||||
*/
|
||||
public static long size(UIDSet[] uidset) {
|
||||
long count = 0;
|
||||
|
||||
if (uidset != null)
|
||||
for (UIDSet u : uidset)
|
||||
count += u.size();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the total number of elements in an array of UIDSets.
|
||||
* Don't count UIDs greater then uidmax.
|
||||
*
|
||||
* @since JavaMail 1.5.1
|
||||
*/
|
||||
private static long size(UIDSet[] uidset, long uidmax) {
|
||||
long count = 0;
|
||||
|
||||
if (uidset != null)
|
||||
for (UIDSet u : uidset) {
|
||||
if (uidmax < 0)
|
||||
count += u.size();
|
||||
else if (u.start <= uidmax) {
|
||||
if (u.end < uidmax)
|
||||
count += u.end - u.start + 1;
|
||||
else
|
||||
count += uidmax - u.start + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<!--
|
||||
|
||||
Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
This program and the accompanying materials are made available under the
|
||||
terms of the Eclipse Public License v. 2.0, which is available at
|
||||
http://www.eclipse.org/legal/epl-2.0.
|
||||
|
||||
This Source Code may also be made available under the following Secondary
|
||||
Licenses when the conditions for such availability set forth in the
|
||||
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
version 2 with the GNU Classpath Exception, which is available at
|
||||
https://www.gnu.org/software/classpath/license.html.
|
||||
|
||||
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
|
||||
-->
|
||||
|
||||
<TITLE>com.sun.mail.imap.protocol package</TITLE>
|
||||
</HEAD>
|
||||
<BODY BGCOLOR="white">
|
||||
|
||||
<P>
|
||||
This package includes internal IMAP support classes and
|
||||
<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
|
||||
</P>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.pop3;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* A stream for writing to the temp file, and when done can return a stream for
|
||||
* reading the data just written. NOTE: We assume that only one thread is
|
||||
* writing to the file at a time.
|
||||
*/
|
||||
class AppendStream extends OutputStream {
|
||||
|
||||
private final WritableSharedFile tf;
|
||||
private RandomAccessFile raf;
|
||||
private final long start;
|
||||
private long end;
|
||||
|
||||
public AppendStream(WritableSharedFile tf) throws IOException {
|
||||
this.tf = tf;
|
||||
raf = tf.getWritableFile();
|
||||
start = raf.length();
|
||||
raf.seek(start);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
raf.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
raf.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
raf.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
end = tf.updateLength();
|
||||
raf = null; // no more writing allowed
|
||||
}
|
||||
|
||||
public synchronized InputStream getInputStream() throws IOException {
|
||||
return tf.newStream(start, end);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.pop3;
|
||||
|
||||
import javax.mail.*;
|
||||
|
||||
/**
|
||||
* The POP3 DefaultFolder. Only contains the "INBOX" folder.
|
||||
*
|
||||
* @author Christopher Cotton
|
||||
*/
|
||||
public class DefaultFolder extends Folder {
|
||||
|
||||
DefaultFolder(POP3Store store) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Folder getParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Folder[] list(String pattern) throws MessagingException {
|
||||
Folder[] f = { getInbox() };
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getSeparator() {
|
||||
return '/';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType() {
|
||||
return HOLDS_FOLDERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean create(int type) throws MessagingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNewMessages() throws MessagingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Folder getFolder(String name) throws MessagingException {
|
||||
if (!name.equalsIgnoreCase("INBOX")) {
|
||||
throw new MessagingException("only INBOX supported");
|
||||
} else {
|
||||
return getInbox();
|
||||
}
|
||||
}
|
||||
|
||||
protected Folder getInbox() throws MessagingException {
|
||||
return getStore().getFolder("INBOX");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean delete(boolean recurse) throws MessagingException {
|
||||
throw new MethodNotSupportedException("delete");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameTo(Folder f) throws MessagingException {
|
||||
throw new MethodNotSupportedException("renameTo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(int mode) throws MessagingException {
|
||||
throw new MethodNotSupportedException("open");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(boolean expunge) throws MessagingException {
|
||||
throw new MethodNotSupportedException("close");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flags getPermanentFlags() {
|
||||
return new Flags(); // empty flags object
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMessageCount() throws MessagingException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message getMessage(int msgno) throws MessagingException {
|
||||
throw new MethodNotSupportedException("getMessage");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendMessages(Message[] msgs) throws MessagingException {
|
||||
throw new MethodNotSupportedException("Append not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message[] expunge() throws MessagingException {
|
||||
throw new MethodNotSupportedException("expunge");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,611 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.pop3;
|
||||
|
||||
import javax.mail.*;
|
||||
import javax.mail.event.*;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.EOFException;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.logging.Level;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import com.sun.mail.util.LineInputStream;
|
||||
import com.sun.mail.util.MailLogger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A POP3 Folder (can only be "INBOX").
|
||||
*
|
||||
* See the <a href="package-summary.html">com.sun.mail.pop3</a> package
|
||||
* documentation for further information on the POP3 protocol provider. <p>
|
||||
*
|
||||
* @author Bill Shannon
|
||||
* @author John Mani (ported to the javax.mail APIs)
|
||||
*/
|
||||
public class POP3Folder extends Folder {
|
||||
|
||||
private String name;
|
||||
private POP3Store store;
|
||||
private volatile Protocol port;
|
||||
private int total;
|
||||
private int size;
|
||||
private boolean exists = false;
|
||||
private volatile boolean opened = false;
|
||||
private POP3Message[] message_cache;
|
||||
private boolean doneUidl = false;
|
||||
private volatile TempFile fileCache = null;
|
||||
private boolean forceClose;
|
||||
|
||||
MailLogger logger; // package private, for POP3Message
|
||||
|
||||
protected POP3Folder(POP3Store store, String name) {
|
||||
super(store);
|
||||
this.name = name;
|
||||
this.store = store;
|
||||
if (name.equalsIgnoreCase("INBOX"))
|
||||
exists = true;
|
||||
logger = new MailLogger(this.getClass(), "DEBUG POP3",
|
||||
store.getSession().getDebug(), store.getSession().getDebugOut());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Folder getParent() {
|
||||
return new DefaultFolder(store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always true for the folder "INBOX", always false for
|
||||
* any other name.
|
||||
*
|
||||
* @return true for INBOX, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always throws <code>MessagingException</code> because no POP3 folders
|
||||
* can contain subfolders.
|
||||
*
|
||||
* @exception MessagingException always
|
||||
*/
|
||||
@Override
|
||||
public Folder[] list(String pattern) throws MessagingException {
|
||||
throw new MessagingException("not a directory");
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns a NUL character because POP3 doesn't support a hierarchy.
|
||||
*
|
||||
* @return NUL
|
||||
*/
|
||||
@Override
|
||||
public char getSeparator() {
|
||||
return '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns Folder.HOLDS_MESSAGES.
|
||||
*
|
||||
* @return Folder.HOLDS_MESSAGES
|
||||
*/
|
||||
@Override
|
||||
public int getType() {
|
||||
return HOLDS_MESSAGES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns <code>false</code>; the POP3 protocol doesn't
|
||||
* support creating folders.
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
@Override
|
||||
public boolean create(int type) throws MessagingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns <code>false</code>; the POP3 protocol provides
|
||||
* no way to determine when a new message arrives.
|
||||
*
|
||||
* @return false
|
||||
*/
|
||||
@Override
|
||||
public boolean hasNewMessages() throws MessagingException {
|
||||
return false; // no way to know
|
||||
}
|
||||
|
||||
/**
|
||||
* Always throws <code>MessagingException</code> because no POP3 folders
|
||||
* can contain subfolders.
|
||||
*
|
||||
* @exception MessagingException always
|
||||
*/
|
||||
@Override
|
||||
public Folder getFolder(String name) throws MessagingException {
|
||||
throw new MessagingException("not a directory");
|
||||
}
|
||||
|
||||
/**
|
||||
* Always throws <code>MethodNotSupportedException</code>
|
||||
* because the POP3 protocol doesn't allow the INBOX to
|
||||
* be deleted.
|
||||
*
|
||||
* @exception MethodNotSupportedException always
|
||||
*/
|
||||
@Override
|
||||
public boolean delete(boolean recurse) throws MessagingException {
|
||||
throw new MethodNotSupportedException("delete");
|
||||
}
|
||||
|
||||
/**
|
||||
* Always throws <code>MethodNotSupportedException</code>
|
||||
* because the POP3 protocol doesn't support multiple folders.
|
||||
*
|
||||
* @exception MethodNotSupportedException always
|
||||
*/
|
||||
@Override
|
||||
public boolean renameTo(Folder f) throws MessagingException {
|
||||
throw new MethodNotSupportedException("renameTo");
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws <code>FolderNotFoundException</code> unless this
|
||||
* folder is named "INBOX".
|
||||
*
|
||||
* @exception FolderNotFoundException if not INBOX
|
||||
* @exception AuthenticationFailedException authentication failures
|
||||
* @exception MessagingException other open failures
|
||||
*/
|
||||
@Override
|
||||
public synchronized void open(int mode) throws MessagingException {
|
||||
checkClosed();
|
||||
if (!exists)
|
||||
throw new FolderNotFoundException(this, "folder is not INBOX");
|
||||
|
||||
try {
|
||||
port = store.getPort(this);
|
||||
Status s = port.stat();
|
||||
total = s.total;
|
||||
size = s.size;
|
||||
this.mode = mode;
|
||||
if (store.useFileCache) {
|
||||
try {
|
||||
fileCache = new TempFile(store.fileCacheDir);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.FINE, "failed to create file cache", ex);
|
||||
throw ex; // caught below
|
||||
}
|
||||
}
|
||||
opened = true;
|
||||
} catch (IOException ioex) {
|
||||
try {
|
||||
if (port != null)
|
||||
port.quit();
|
||||
} catch (IOException ioex2) {
|
||||
// ignore
|
||||
} finally {
|
||||
port = null;
|
||||
store.closePort(this);
|
||||
}
|
||||
throw new MessagingException("Open failed", ioex);
|
||||
}
|
||||
|
||||
// Create the message cache array of appropriate size
|
||||
message_cache = new POP3Message[total];
|
||||
doneUidl = false;
|
||||
|
||||
notifyConnectionListeners(ConnectionEvent.OPENED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close(boolean expunge) throws MessagingException {
|
||||
checkOpen();
|
||||
|
||||
try {
|
||||
/*
|
||||
* Some POP3 servers will mark messages for deletion when
|
||||
* they're read. To prevent such messages from being
|
||||
* deleted before the client deletes them, you can set
|
||||
* the mail.pop3.rsetbeforequit property to true. This
|
||||
* causes us to issue a POP3 RSET command to clear all
|
||||
* the "marked for deletion" flags. We can then explicitly
|
||||
* delete messages as desired.
|
||||
*/
|
||||
if (store.rsetBeforeQuit && !forceClose)
|
||||
port.rset();
|
||||
POP3Message m;
|
||||
if (expunge && mode == READ_WRITE && !forceClose) {
|
||||
// find all messages marked deleted and issue DELE commands
|
||||
for (int i = 0; i < message_cache.length; i++) {
|
||||
if ((m = message_cache[i]) != null) {
|
||||
if (m.isSet(Flags.Flag.DELETED))
|
||||
try {
|
||||
port.dele(i + 1);
|
||||
} catch (IOException ioex) {
|
||||
throw new MessagingException(
|
||||
"Exception deleting messages during close",
|
||||
ioex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Flush and free all cached data for the messages.
|
||||
*/
|
||||
for (int i = 0; i < message_cache.length; i++) {
|
||||
if ((m = message_cache[i]) != null)
|
||||
m.invalidate(true);
|
||||
}
|
||||
|
||||
if (forceClose)
|
||||
port.close();
|
||||
else
|
||||
port.quit();
|
||||
} catch (IOException ex) {
|
||||
// do nothing
|
||||
} finally {
|
||||
port = null;
|
||||
store.closePort(this);
|
||||
message_cache = null;
|
||||
opened = false;
|
||||
notifyConnectionListeners(ConnectionEvent.CLOSED);
|
||||
if (fileCache != null) {
|
||||
fileCache.close();
|
||||
fileCache = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isOpen() {
|
||||
if (!opened)
|
||||
return false;
|
||||
try {
|
||||
if (!port.noop())
|
||||
throw new IOException("NOOP failed");
|
||||
} catch (IOException ioex) {
|
||||
try {
|
||||
close(false);
|
||||
} catch (MessagingException mex) {
|
||||
// ignore it
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns an empty <code>Flags</code> object because
|
||||
* the POP3 protocol doesn't support any permanent flags.
|
||||
*
|
||||
* @return empty Flags object
|
||||
*/
|
||||
@Override
|
||||
public Flags getPermanentFlags() {
|
||||
return new Flags(); // empty flags object
|
||||
}
|
||||
|
||||
/**
|
||||
* Will not change while the folder is open because the POP3
|
||||
* protocol doesn't support notification of new messages
|
||||
* arriving in open folders.
|
||||
*/
|
||||
@Override
|
||||
public synchronized int getMessageCount() throws MessagingException {
|
||||
if (!opened)
|
||||
return -1;
|
||||
checkReadable();
|
||||
return total;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Message getMessage(int msgno)
|
||||
throws MessagingException {
|
||||
checkOpen();
|
||||
|
||||
POP3Message m;
|
||||
|
||||
// Assuming that msgno is <= total
|
||||
if ((m = message_cache[msgno-1]) == null) {
|
||||
m = createMessage(this, msgno);
|
||||
message_cache[msgno-1] = m;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
protected POP3Message createMessage(Folder f, int msgno)
|
||||
throws MessagingException {
|
||||
POP3Message m = null;
|
||||
Constructor<?> cons = store.messageConstructor;
|
||||
if (cons != null) {
|
||||
try {
|
||||
Object[] o = { this, Integer.valueOf(msgno) };
|
||||
m = (POP3Message)cons.newInstance(o);
|
||||
} catch (Exception ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (m == null)
|
||||
m = new POP3Message(this, msgno);
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always throws <code>MethodNotSupportedException</code>
|
||||
* because the POP3 protocol doesn't support appending messages.
|
||||
*
|
||||
* @exception MethodNotSupportedException always
|
||||
*/
|
||||
@Override
|
||||
public void appendMessages(Message[] msgs) throws MessagingException {
|
||||
throw new MethodNotSupportedException("Append not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Always throws <code>MethodNotSupportedException</code>
|
||||
* because the POP3 protocol doesn't support expunging messages
|
||||
* without closing the folder; call the {@link #close close} method
|
||||
* with the <code>expunge</code> argument set to <code>true</code>
|
||||
* instead.
|
||||
*
|
||||
* @exception MethodNotSupportedException always
|
||||
*/
|
||||
@Override
|
||||
public Message[] expunge() throws MessagingException {
|
||||
throw new MethodNotSupportedException("Expunge not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch information about POP3 messages.
|
||||
* If the FetchProfile contains <code>UIDFolder.FetchProfileItem.UID</code>,
|
||||
* POP3 UIDs for all messages in the folder are fetched using the POP3
|
||||
* UIDL command.
|
||||
* If the FetchProfile contains <code>FetchProfile.Item.ENVELOPE</code>,
|
||||
* the headers and size of all messages are fetched using the POP3 TOP
|
||||
* and LIST commands.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void fetch(Message[] msgs, FetchProfile fp)
|
||||
throws MessagingException {
|
||||
checkReadable();
|
||||
if (!doneUidl && store.supportsUidl &&
|
||||
fp.contains(UIDFolder.FetchProfileItem.UID)) {
|
||||
/*
|
||||
* Since the POP3 protocol only lets us fetch the UID
|
||||
* for a single message or for all messages, we go ahead
|
||||
* and fetch UIDs for all messages here, ignoring the msgs
|
||||
* parameter. We could be more intelligent and base this
|
||||
* decision on the number of messages fetched, or the
|
||||
* percentage of the total number of messages fetched.
|
||||
*/
|
||||
String[] uids = new String[message_cache.length];
|
||||
try {
|
||||
if (!port.uidl(uids))
|
||||
return;
|
||||
} catch (EOFException eex) {
|
||||
close(false);
|
||||
throw new FolderClosedException(this, eex.toString());
|
||||
} catch (IOException ex) {
|
||||
throw new MessagingException("error getting UIDL", ex);
|
||||
}
|
||||
for (int i = 0; i < uids.length; i++) {
|
||||
if (uids[i] == null)
|
||||
continue;
|
||||
POP3Message m = (POP3Message)getMessage(i + 1);
|
||||
m.uid = uids[i];
|
||||
}
|
||||
doneUidl = true; // only do this once
|
||||
}
|
||||
if (fp.contains(FetchProfile.Item.ENVELOPE)) {
|
||||
for (int i = 0; i < msgs.length; i++) {
|
||||
try {
|
||||
POP3Message msg = (POP3Message)msgs[i];
|
||||
// fetch headers
|
||||
msg.getHeader("");
|
||||
// fetch message size
|
||||
msg.getSize();
|
||||
} catch (MessageRemovedException mex) {
|
||||
// should never happen, but ignore it if it does
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the unique ID string for this message, or null if
|
||||
* not available. Uses the POP3 UIDL command.
|
||||
*
|
||||
* @param msg the message
|
||||
* @return unique ID string
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
public synchronized String getUID(Message msg) throws MessagingException {
|
||||
checkOpen();
|
||||
if (!(msg instanceof POP3Message))
|
||||
throw new MessagingException("message is not a POP3Message");
|
||||
POP3Message m = (POP3Message)msg;
|
||||
try {
|
||||
if (!store.supportsUidl)
|
||||
return null;
|
||||
if (m.uid == POP3Message.UNKNOWN)
|
||||
m.uid = port.uidl(m.getMessageNumber());
|
||||
return m.uid;
|
||||
} catch (EOFException eex) {
|
||||
close(false);
|
||||
throw new FolderClosedException(this, eex.toString());
|
||||
} catch (IOException ex) {
|
||||
throw new MessagingException("error getting UIDL", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of this folder, as was returned by the POP3 STAT
|
||||
* command when this folder was opened.
|
||||
*
|
||||
* @return folder size
|
||||
* @exception IllegalStateException if the folder isn't open
|
||||
* @exception MessagingException for other failures
|
||||
*/
|
||||
public synchronized int getSize() throws MessagingException {
|
||||
checkOpen();
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sizes of all messages in this folder, as returned
|
||||
* by the POP3 LIST command. Each entry in the array corresponds
|
||||
* to a message; entry <i>i</i> corresponds to message number <i>i+1</i>.
|
||||
*
|
||||
* @return array of message sizes
|
||||
* @exception IllegalStateException if the folder isn't open
|
||||
* @exception MessagingException for other failures
|
||||
* @since JavaMail 1.3.3
|
||||
*/
|
||||
public synchronized int[] getSizes() throws MessagingException {
|
||||
checkOpen();
|
||||
int sizes[] = new int[total];
|
||||
InputStream is = null;
|
||||
LineInputStream lis = null;
|
||||
try {
|
||||
is = port.list();
|
||||
lis = new LineInputStream(is);
|
||||
String line;
|
||||
while ((line = lis.readLine()) != null) {
|
||||
try {
|
||||
StringTokenizer st = new StringTokenizer(line);
|
||||
int msgnum = Integer.parseInt(st.nextToken());
|
||||
int size = Integer.parseInt(st.nextToken());
|
||||
if (msgnum > 0 && msgnum <= total)
|
||||
sizes[msgnum - 1] = size;
|
||||
} catch (RuntimeException e) {
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// ignore it?
|
||||
} finally {
|
||||
try {
|
||||
if (lis != null)
|
||||
lis.close();
|
||||
} catch (IOException cex) { }
|
||||
try {
|
||||
if (is != null)
|
||||
is.close();
|
||||
} catch (IOException cex) { }
|
||||
}
|
||||
return sizes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw results of the POP3 LIST command with no arguments.
|
||||
*
|
||||
* @return InputStream containing results
|
||||
* @exception IllegalStateException if the folder isn't open
|
||||
* @exception IOException for I/O errors talking to the server
|
||||
* @exception MessagingException for other errors
|
||||
* @since JavaMail 1.3.3
|
||||
*/
|
||||
public synchronized InputStream listCommand()
|
||||
throws MessagingException, IOException {
|
||||
checkOpen();
|
||||
return port.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the folder when we're finalized.
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
forceClose = !store.finalizeCleanClose;
|
||||
try {
|
||||
if (opened)
|
||||
close(false);
|
||||
} finally {
|
||||
super.finalize();
|
||||
forceClose = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure the folder is open */
|
||||
private void checkOpen() throws IllegalStateException {
|
||||
if (!opened)
|
||||
throw new IllegalStateException("Folder is not Open");
|
||||
}
|
||||
|
||||
/* Ensure the folder is not open */
|
||||
private void checkClosed() throws IllegalStateException {
|
||||
if (opened)
|
||||
throw new IllegalStateException("Folder is Open");
|
||||
}
|
||||
|
||||
/* Ensure the folder is open & readable */
|
||||
private void checkReadable() throws IllegalStateException {
|
||||
if (!opened || (mode != READ_ONLY && mode != READ_WRITE))
|
||||
throw new IllegalStateException("Folder is not Readable");
|
||||
}
|
||||
|
||||
/* Ensure the folder is open & writable */
|
||||
/*
|
||||
private void checkWritable() throws IllegalStateException {
|
||||
if (!opened || mode != READ_WRITE)
|
||||
throw new IllegalStateException("Folder is not Writable");
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Centralize access to the Protocol object by POP3Message
|
||||
* objects so that they will fail appropriately when the folder
|
||||
* is closed.
|
||||
*/
|
||||
Protocol getProtocol() throws MessagingException {
|
||||
Protocol p = port; // read it before close() can set it to null
|
||||
checkOpen();
|
||||
// close() might happen here
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only here to make accessible to POP3Message.
|
||||
*/
|
||||
@Override
|
||||
protected void notifyMessageChangedListeners(int type, Message m) {
|
||||
super.notifyMessageChangedListeners(type, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by POP3Message.
|
||||
*/
|
||||
TempFile getFileCache() {
|
||||
return fileCache;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,644 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.pop3;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Enumeration;
|
||||
import java.util.logging.Level;
|
||||
import java.lang.ref.SoftReference;
|
||||
import javax.mail.*;
|
||||
import javax.mail.internet.*;
|
||||
import javax.mail.event.*;
|
||||
import com.sun.mail.util.ReadableMime;
|
||||
|
||||
/**
|
||||
* A POP3 Message. Just like a MimeMessage except that
|
||||
* some things are not supported.
|
||||
*
|
||||
* @author Bill Shannon
|
||||
*/
|
||||
public class POP3Message extends MimeMessage implements ReadableMime {
|
||||
|
||||
/*
|
||||
* Our locking strategy is to always lock the POP3Folder before the
|
||||
* POP3Message so we have to be careful to drop our lock before calling
|
||||
* back to the folder to close it and notify of connection lost events.
|
||||
*/
|
||||
|
||||
// flag to indicate we haven't tried to fetch the UID yet
|
||||
static final String UNKNOWN = "UNKNOWN";
|
||||
|
||||
private POP3Folder folder; // overrides folder in MimeMessage
|
||||
private int hdrSize = -1;
|
||||
private int msgSize = -1;
|
||||
String uid = UNKNOWN; // controlled by folder lock
|
||||
|
||||
// rawData itself is never null
|
||||
private SoftReference<InputStream> rawData
|
||||
= new SoftReference<>(null);
|
||||
|
||||
public POP3Message(Folder folder, int msgno)
|
||||
throws MessagingException {
|
||||
super(folder, msgno);
|
||||
assert folder instanceof POP3Folder;
|
||||
this.folder = (POP3Folder)folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specified flags on this message to the specified value.
|
||||
*
|
||||
* @param newFlags the flags to be set
|
||||
* @param set the value to be set
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setFlags(Flags newFlags, boolean set)
|
||||
throws MessagingException {
|
||||
Flags oldFlags = (Flags)flags.clone();
|
||||
super.setFlags(newFlags, set);
|
||||
if (!flags.equals(oldFlags))
|
||||
folder.notifyMessageChangedListeners(
|
||||
MessageChangedEvent.FLAGS_CHANGED, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size of the content of this message in bytes.
|
||||
* Returns -1 if the size cannot be determined. <p>
|
||||
*
|
||||
* Note that this number may not be an exact measure of the
|
||||
* content size and may or may not account for any transfer
|
||||
* encoding of the content. <p>
|
||||
*
|
||||
* @return size of content in bytes
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
@Override
|
||||
public int getSize() throws MessagingException {
|
||||
try {
|
||||
synchronized (this) {
|
||||
// if we already have the size, return it
|
||||
if (msgSize > 0)
|
||||
return msgSize;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use LIST to determine the entire message
|
||||
* size and subtract out the header size
|
||||
* (which may involve loading the headers,
|
||||
* which may load the content as a side effect).
|
||||
* If the content is loaded as a side effect of
|
||||
* loading the headers, it will set the size.
|
||||
*
|
||||
* Make sure to call loadHeaders() outside of the
|
||||
* synchronization block. There's a potential race
|
||||
* condition here but synchronization will occur in
|
||||
* loadHeaders() to make sure the headers are only
|
||||
* loaded once, and again in the following block to
|
||||
* only compute msgSize once.
|
||||
*/
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
|
||||
synchronized (this) {
|
||||
if (msgSize < 0)
|
||||
msgSize = folder.getProtocol().list(msgnum) - hdrSize;
|
||||
return msgSize;
|
||||
}
|
||||
} catch (EOFException eex) {
|
||||
folder.close(false);
|
||||
throw new FolderClosedException(folder, eex.toString());
|
||||
} catch (IOException ex) {
|
||||
throw new MessagingException("error getting size", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the raw bytes of the message. The data is fetched using
|
||||
* the POP3 RETR command. If skipHeader is true, just the content
|
||||
* is returned.
|
||||
*/
|
||||
private InputStream getRawStream(boolean skipHeader)
|
||||
throws MessagingException {
|
||||
InputStream rawcontent = null;
|
||||
try {
|
||||
synchronized(this) {
|
||||
rawcontent = rawData.get();
|
||||
if (rawcontent == null) {
|
||||
TempFile cache = folder.getFileCache();
|
||||
if (cache != null) {
|
||||
if (folder.logger.isLoggable(Level.FINE))
|
||||
folder.logger.fine("caching message #" + msgnum +
|
||||
" in temp file");
|
||||
AppendStream os = cache.getAppendStream();
|
||||
BufferedOutputStream bos = new BufferedOutputStream(os);
|
||||
try {
|
||||
folder.getProtocol().retr(msgnum, bos);
|
||||
} finally {
|
||||
bos.close();
|
||||
}
|
||||
rawcontent = os.getInputStream();
|
||||
} else {
|
||||
rawcontent = folder.getProtocol().retr(msgnum,
|
||||
msgSize > 0 ? msgSize + hdrSize : 0);
|
||||
}
|
||||
if (rawcontent == null) {
|
||||
expunged = true;
|
||||
throw new MessageRemovedException(
|
||||
"can't retrieve message #" + msgnum +
|
||||
" in POP3Message.getContentStream"); // XXX - what else?
|
||||
}
|
||||
|
||||
if (headers == null ||
|
||||
((POP3Store)(folder.getStore())).forgetTopHeaders) {
|
||||
headers = new InternetHeaders(rawcontent);
|
||||
hdrSize =
|
||||
(int)((SharedInputStream)rawcontent).getPosition();
|
||||
} else {
|
||||
/*
|
||||
* Already have the headers, have to skip the headers
|
||||
* in the content array and return the body.
|
||||
*
|
||||
* XXX - It seems that some mail servers return slightly
|
||||
* different headers in the RETR results than were returned
|
||||
* in the TOP results, so we can't depend on remembering
|
||||
* the size of the headers from the TOP command and just
|
||||
* skipping that many bytes. Instead, we have to process
|
||||
* the content, skipping over the header until we come to
|
||||
* the empty line that separates the header from the body.
|
||||
*/
|
||||
int offset = 0;
|
||||
for (;;) {
|
||||
int len = 0; // number of bytes in this line
|
||||
int c1;
|
||||
while ((c1 = rawcontent.read()) >= 0) {
|
||||
if (c1 == '\n') // end of line
|
||||
break;
|
||||
else if (c1 == '\r') {
|
||||
// got CR, is the next char LF?
|
||||
if (rawcontent.available() > 0) {
|
||||
rawcontent.mark(1);
|
||||
if (rawcontent.read() != '\n')
|
||||
rawcontent.reset();
|
||||
}
|
||||
break; // in any case, end of line
|
||||
}
|
||||
|
||||
// not CR, NL, or CRLF, count the byte
|
||||
len++;
|
||||
}
|
||||
// here when end of line or out of data
|
||||
|
||||
// if out of data, we're done
|
||||
if (rawcontent.available() == 0)
|
||||
break;
|
||||
|
||||
// if it was an empty line, we're done
|
||||
if (len == 0)
|
||||
break;
|
||||
}
|
||||
hdrSize =
|
||||
(int)((SharedInputStream)rawcontent).getPosition();
|
||||
}
|
||||
|
||||
// skipped the header, the message is what's left
|
||||
msgSize = rawcontent.available();
|
||||
|
||||
rawData = new SoftReference<>(rawcontent);
|
||||
}
|
||||
}
|
||||
} catch (EOFException eex) {
|
||||
folder.close(false);
|
||||
throw new FolderClosedException(folder, eex.toString());
|
||||
} catch (IOException ex) {
|
||||
throw new MessagingException("error fetching POP3 content", ex);
|
||||
}
|
||||
|
||||
/*
|
||||
* We have a cached stream, but we need to return
|
||||
* a fresh stream to read from the beginning and
|
||||
* that can be safely closed.
|
||||
*/
|
||||
rawcontent = ((SharedInputStream)rawcontent).newStream(
|
||||
skipHeader ? hdrSize : 0, -1);
|
||||
return rawcontent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the raw bytes of the content. The data is fetched using
|
||||
* the POP3 RETR command.
|
||||
*
|
||||
* @see #contentStream
|
||||
*/
|
||||
@Override
|
||||
protected synchronized InputStream getContentStream()
|
||||
throws MessagingException {
|
||||
if (contentStream != null)
|
||||
return ((SharedInputStream)contentStream).newStream(0, -1);
|
||||
|
||||
InputStream cstream = getRawStream(true);
|
||||
|
||||
/*
|
||||
* Keep a hard reference to the data if we're using a file
|
||||
* cache or if the "mail.pop3.keepmessagecontent" prop is set.
|
||||
*/
|
||||
TempFile cache = folder.getFileCache();
|
||||
if (cache != null ||
|
||||
((POP3Store)(folder.getStore())).keepMessageContent)
|
||||
contentStream = ((SharedInputStream)cstream).newStream(0, -1);
|
||||
return cstream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MIME format stream corresponding to this message part.
|
||||
*
|
||||
* @return the MIME format stream
|
||||
* @since JavaMail 1.4.5
|
||||
*/
|
||||
@Override
|
||||
public InputStream getMimeStream() throws MessagingException {
|
||||
return getRawStream(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the cache of content for this message object, causing
|
||||
* it to be fetched again from the server the next time it is needed.
|
||||
* If <code>invalidateHeaders</code> is true, invalidate the headers
|
||||
* as well.
|
||||
*
|
||||
* @param invalidateHeaders invalidate the headers as well?
|
||||
*/
|
||||
public synchronized void invalidate(boolean invalidateHeaders) {
|
||||
content = null;
|
||||
InputStream rstream = rawData.get();
|
||||
if (rstream != null) {
|
||||
// note that if the content is in the file cache, it will be lost
|
||||
// and fetched from the server if it's needed again
|
||||
try {
|
||||
rstream.close();
|
||||
} catch (IOException ex) {
|
||||
// ignore it
|
||||
}
|
||||
rawData = new SoftReference<>(null);
|
||||
}
|
||||
if (contentStream != null) {
|
||||
try {
|
||||
contentStream.close();
|
||||
} catch (IOException ex) {
|
||||
// ignore it
|
||||
}
|
||||
contentStream = null;
|
||||
}
|
||||
msgSize = -1;
|
||||
if (invalidateHeaders) {
|
||||
headers = null;
|
||||
hdrSize = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the header of the message and the first <code>n</code> lines
|
||||
* of the raw content of the message. The headers and data are
|
||||
* available in the returned InputStream.
|
||||
*
|
||||
* @param n number of lines of content to fetch
|
||||
* @return InputStream containing the message headers and n content lines
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
public InputStream top(int n) throws MessagingException {
|
||||
try {
|
||||
synchronized (this) {
|
||||
return folder.getProtocol().top(msgnum, n);
|
||||
}
|
||||
} catch (EOFException eex) {
|
||||
folder.close(false);
|
||||
throw new FolderClosedException(folder, eex.toString());
|
||||
} catch (IOException ex) {
|
||||
throw new MessagingException("error getting size", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the headers for this header_name. Note that certain
|
||||
* headers may be encoded as per RFC 2047 if they contain
|
||||
* non US-ASCII characters and these should be decoded. <p>
|
||||
*
|
||||
* @param name name of header
|
||||
* @return array of headers
|
||||
* @exception MessagingException for failures
|
||||
* @see javax.mail.internet.MimeUtility
|
||||
*/
|
||||
@Override
|
||||
public String[] getHeader(String name)
|
||||
throws MessagingException {
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
return headers.getHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the headers for this header name, returned as a single
|
||||
* String, with headers separated by the delimiter. If the
|
||||
* delimiter is <code>null</code>, only the first header is
|
||||
* returned.
|
||||
*
|
||||
* @param name the name of this header
|
||||
* @param delimiter delimiter between returned headers
|
||||
* @return the value fields for all headers with
|
||||
* this name
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
@Override
|
||||
public String getHeader(String name, String delimiter)
|
||||
throws MessagingException {
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
return headers.getHeader(name, delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for this header_name. Throws IllegalWriteException
|
||||
* because POP3 messages are read-only.
|
||||
*
|
||||
* @param name header name
|
||||
* @param value header value
|
||||
* @see javax.mail.internet.MimeUtility
|
||||
* @exception IllegalWriteException because the underlying
|
||||
* implementation does not support modification
|
||||
* @exception IllegalStateException if this message is
|
||||
* obtained from a READ_ONLY folder.
|
||||
* @exception MessagingException for other failures
|
||||
*/
|
||||
@Override
|
||||
public void setHeader(String name, String value)
|
||||
throws MessagingException {
|
||||
// XXX - should check for read-only folder?
|
||||
throw new IllegalWriteException("POP3 messages are read-only");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this value to the existing values for this header_name.
|
||||
* Throws IllegalWriteException because POP3 messages are read-only.
|
||||
*
|
||||
* @param name header name
|
||||
* @param value header value
|
||||
* @see javax.mail.internet.MimeUtility
|
||||
* @exception IllegalWriteException because the underlying
|
||||
* implementation does not support modification
|
||||
* @exception IllegalStateException if this message is
|
||||
* obtained from a READ_ONLY folder.
|
||||
*/
|
||||
@Override
|
||||
public void addHeader(String name, String value)
|
||||
throws MessagingException {
|
||||
// XXX - should check for read-only folder?
|
||||
throw new IllegalWriteException("POP3 messages are read-only");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all headers with this name.
|
||||
* Throws IllegalWriteException because POP3 messages are read-only.
|
||||
*
|
||||
* @exception IllegalWriteException because the underlying
|
||||
* implementation does not support modification
|
||||
* @exception IllegalStateException if this message is
|
||||
* obtained from a READ_ONLY folder.
|
||||
*/
|
||||
@Override
|
||||
public void removeHeader(String name)
|
||||
throws MessagingException {
|
||||
// XXX - should check for read-only folder?
|
||||
throw new IllegalWriteException("POP3 messages are read-only");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the headers from this Message as an enumeration
|
||||
* of Header objects. <p>
|
||||
*
|
||||
* Note that certain headers may be encoded as per RFC 2047
|
||||
* if they contain non US-ASCII characters and these should
|
||||
* be decoded. <p>
|
||||
*
|
||||
* @return array of header objects
|
||||
* @exception MessagingException for failures
|
||||
* @see javax.mail.internet.MimeUtility
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<Header> getAllHeaders() throws MessagingException {
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
return headers.getAllHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return matching headers from this Message as an Enumeration of
|
||||
* Header objects.
|
||||
*
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<Header> getMatchingHeaders(String[] names)
|
||||
throws MessagingException {
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
return headers.getMatchingHeaders(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return non-matching headers from this Message as an
|
||||
* Enumeration of Header objects.
|
||||
*
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<Header> getNonMatchingHeaders(String[] names)
|
||||
throws MessagingException {
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
return headers.getNonMatchingHeaders(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a raw RFC822 header-line.
|
||||
* Throws IllegalWriteException because POP3 messages are read-only.
|
||||
*
|
||||
* @exception IllegalWriteException because the underlying
|
||||
* implementation does not support modification
|
||||
* @exception IllegalStateException if this message is
|
||||
* obtained from a READ_ONLY folder.
|
||||
*/
|
||||
@Override
|
||||
public void addHeaderLine(String line) throws MessagingException {
|
||||
// XXX - should check for read-only folder?
|
||||
throw new IllegalWriteException("POP3 messages are read-only");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all header lines as an Enumeration of Strings. A Header
|
||||
* line is a raw RFC822 header-line, containing both the "name"
|
||||
* and "value" field.
|
||||
*
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<String> getAllHeaderLines() throws MessagingException {
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
return headers.getAllHeaderLines();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get matching header lines as an Enumeration of Strings.
|
||||
* A Header line is a raw RFC822 header-line, containing both
|
||||
* the "name" and "value" field.
|
||||
*
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<String> getMatchingHeaderLines(String[] names)
|
||||
throws MessagingException {
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
return headers.getMatchingHeaderLines(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get non-matching header lines as an Enumeration of Strings.
|
||||
* A Header line is a raw RFC822 header-line, containing both
|
||||
* the "name" and "value" field.
|
||||
*
|
||||
* @exception MessagingException for failures
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<String> getNonMatchingHeaderLines(String[] names)
|
||||
throws MessagingException {
|
||||
if (headers == null)
|
||||
loadHeaders();
|
||||
return headers.getNonMatchingHeaderLines(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* POP3 message can't be changed. This method throws
|
||||
* IllegalWriteException.
|
||||
*
|
||||
* @exception IllegalWriteException because the underlying
|
||||
* implementation does not support modification
|
||||
*/
|
||||
@Override
|
||||
public void saveChanges() throws MessagingException {
|
||||
// POP3 Messages are read-only
|
||||
throw new IllegalWriteException("POP3 messages are read-only");
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the message as an RFC 822 format stream, without
|
||||
* specified headers. If the property "mail.pop3.cachewriteto"
|
||||
* is set to "true", and ignoreList is null, and the message hasn't
|
||||
* already been cached as a side effect of other operations, the message
|
||||
* content is cached before being written. Otherwise, the message is
|
||||
* streamed directly to the output stream without being cached.
|
||||
*
|
||||
* @exception IOException if an error occurs writing to the stream
|
||||
* or if an error is generated by the
|
||||
* javax.activation layer.
|
||||
* @exception MessagingException for other failures
|
||||
* @see javax.activation.DataHandler#writeTo
|
||||
*/
|
||||
@Override
|
||||
public synchronized void writeTo(OutputStream os, String[] ignoreList)
|
||||
throws IOException, MessagingException {
|
||||
InputStream rawcontent = rawData.get();
|
||||
if (rawcontent == null && ignoreList == null &&
|
||||
!((POP3Store)(folder.getStore())).cacheWriteTo) {
|
||||
if (folder.logger.isLoggable(Level.FINE))
|
||||
folder.logger.fine("streaming msg " + msgnum);
|
||||
if (!folder.getProtocol().retr(msgnum, os)) {
|
||||
expunged = true;
|
||||
throw new MessageRemovedException("can't retrieve message #" +
|
||||
msgnum + " in POP3Message.writeTo"); // XXX - what else?
|
||||
}
|
||||
} else if (rawcontent != null && ignoreList == null) {
|
||||
// can just copy the cached data
|
||||
InputStream in = ((SharedInputStream)rawcontent).newStream(0, -1);
|
||||
try {
|
||||
byte[] buf = new byte[16*1024];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0)
|
||||
os.write(buf, 0, len);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null)
|
||||
in.close();
|
||||
} catch (IOException ex) { }
|
||||
}
|
||||
} else
|
||||
super.writeTo(os, ignoreList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the headers for this message into the InternetHeaders object.
|
||||
* The headers are fetched using the POP3 TOP command.
|
||||
*/
|
||||
private void loadHeaders() throws MessagingException {
|
||||
assert !Thread.holdsLock(this);
|
||||
try {
|
||||
boolean fetchContent = false;
|
||||
synchronized (this) {
|
||||
if (headers != null) // check again under lock
|
||||
return;
|
||||
InputStream hdrs = null;
|
||||
if (((POP3Store)(folder.getStore())).disableTop ||
|
||||
(hdrs = folder.getProtocol().top(msgnum, 0)) == null) {
|
||||
// possibly because the TOP command isn't supported,
|
||||
// load headers as a side effect of loading the entire
|
||||
// content.
|
||||
fetchContent = true;
|
||||
} else {
|
||||
try {
|
||||
hdrSize = hdrs.available();
|
||||
headers = new InternetHeaders(hdrs);
|
||||
} finally {
|
||||
hdrs.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Outside the synchronization block...
|
||||
*
|
||||
* Do we need to fetch the entire mesage content in order to
|
||||
* load the headers as a side effect? Yes, there's a race
|
||||
* condition here - multiple threads could decide that the
|
||||
* content needs to be fetched. Fortunately, they'll all
|
||||
* synchronize in the getContentStream method and the content
|
||||
* will only be loaded once.
|
||||
*/
|
||||
if (fetchContent) {
|
||||
InputStream cs = null;
|
||||
try {
|
||||
cs = getContentStream();
|
||||
} finally {
|
||||
if (cs != null)
|
||||
cs.close();
|
||||
}
|
||||
}
|
||||
} catch (EOFException eex) {
|
||||
folder.close(false);
|
||||
throw new FolderClosedException(folder, eex.toString());
|
||||
} catch (IOException ex) {
|
||||
throw new MessagingException("error loading POP3 headers", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0, which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the
|
||||
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
|
||||
* version 2 with the GNU Classpath Exception, which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
package com.sun.mail.pop3;
|
||||
|
||||
import javax.mail.Provider;
|
||||
|
||||
import com.sun.mail.util.DefaultProvider;
|
||||
|
||||
/**
|
||||
* The POP3 protocol provider.
|
||||
*/
|
||||
@DefaultProvider // Remove this annotation if you copy this provider
|
||||
public class POP3Provider extends Provider {
|
||||
public POP3Provider() {
|
||||
super(Provider.Type.STORE, "pop3", POP3Store.class.getName(),
|
||||
"Oracle", null);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue