summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorPeter Qiu <zqiu@google.com>2016-10-31 15:50:42 -0700
committerPeter Qiu <zqiu@google.com>2016-11-15 10:30:36 -0800
commit19831fa56c886da84e0006377e11c4c0b66ab9c3 (patch)
tree4e5169873c8193ba5e2f08dd20cece7b2bfafc06 /service
parent4bfade8d51ddbf6d6bee854883935d3674a97678 (diff)
configparse: remove support for ConfigBuilder
ConfigBuilder was used by the Hotspot 2.0 Release 1 app (WifiInstaller) for parsing the installation file data to a WifiConfiguration. WifiInstaller will be updated to use the new framework API (android.net.wifi.hotspot2.ConfigBuilder) instead, so the support in WifiService is no longer needed. Bug: 32509661 Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: I452d5d686c8fcd926d32a432dd042836abc0f2cf
Diffstat (limited to 'service')
-rw-r--r--service/java/com/android/server/wifi/WifiServiceImpl.java23
-rw-r--r--service/java/com/android/server/wifi/configparse/ConfigBuilder.java414
-rw-r--r--service/java/com/android/server/wifi/configparse/MIMEContainer.java345
3 files changed, 0 insertions, 782 deletions
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 22053f2ec..420f92366 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -85,11 +85,8 @@ import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
-import com.android.server.wifi.configparse.ConfigBuilder;
import com.android.server.wifi.util.WifiPermissionsUtil;
-import org.xml.sax.SAXException;
-
import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -676,26 +673,6 @@ public class WifiServiceImpl extends IWifiManager.Stub {
}
/**
- * see {@link WifiManager#buildWifiConfig()}
- * @return a WifiConfiguration.
- */
- @Override
- public WifiConfiguration buildWifiConfig(String uriString, String mimeType, byte[] data) {
- if (mimeType.equals(ConfigBuilder.WifiConfigType)) {
- try {
- return ConfigBuilder.buildConfig(uriString, data, mContext);
- }
- catch (IOException | GeneralSecurityException | SAXException e) {
- Log.e(TAG, "Failed to parse wi-fi configuration: " + e);
- }
- }
- else {
- Log.i(TAG, "Unknown wi-fi config type: " + mimeType);
- }
- return null;
- }
-
- /**
* see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
* @param wifiConfig WifiConfiguration details for soft access point
*/
diff --git a/service/java/com/android/server/wifi/configparse/ConfigBuilder.java b/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
deleted file mode 100644
index b9826dab7..000000000
--- a/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
+++ /dev/null
@@ -1,414 +0,0 @@
-package com.android.server.wifi.configparse;
-
-import android.content.Context;
-import android.net.Uri;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiEnterpriseConfig;
-import android.provider.DocumentsContract;
-import android.util.Base64;
-import android.util.Log;
-
-import com.android.server.wifi.IMSIParameter;
-import com.android.server.wifi.anqp.eap.AuthParam;
-import com.android.server.wifi.anqp.eap.EAP;
-import com.android.server.wifi.anqp.eap.EAPMethod;
-import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
-import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
-import com.android.server.wifi.hotspot2.pps.Credential;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
-
-import org.xml.sax.SAXException;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.LineNumberReader;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.MessageDigest;
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
-
-public class ConfigBuilder {
- public static final String WifiConfigType = "application/x-wifi-config";
- private static final String ProfileTag = "application/x-passpoint-profile";
- private static final String KeyTag = "application/x-pkcs12";
- private static final String CATag = "application/x-x509-ca-cert";
-
- private static final String X509 = "X.509";
-
- private static final String TAG = "WCFG";
-
- public static WifiConfiguration buildConfig(String uriString, byte[] data, Context context)
- throws IOException, GeneralSecurityException, SAXException {
- Log.d(TAG, "Content: " + (data != null ? data.length : -1));
-
- byte[] b64 = Base64.decode(new String(data, StandardCharsets.ISO_8859_1), Base64.DEFAULT);
- Log.d(TAG, "Decoded: " + b64.length + " bytes.");
-
- dropFile(Uri.parse(uriString), context);
-
- MIMEContainer mimeContainer = new
- MIMEContainer(new LineNumberReader(
- new InputStreamReader(new ByteArrayInputStream(b64), StandardCharsets.ISO_8859_1)),
- null);
- if (!mimeContainer.isBase64()) {
- throw new IOException("Encoding for " +
- mimeContainer.getContentType() + " is not base64");
- }
- MIMEContainer inner;
- if (mimeContainer.getContentType().equals(WifiConfigType)) {
- byte[] wrappedContent = Base64.decode(mimeContainer.getText(), Base64.DEFAULT);
- Log.d(TAG, "Building container from '" +
- new String(wrappedContent, StandardCharsets.ISO_8859_1) + "'");
- inner = new MIMEContainer(new LineNumberReader(
- new InputStreamReader(new ByteArrayInputStream(wrappedContent),
- StandardCharsets.ISO_8859_1)), null);
- }
- else {
- inner = mimeContainer;
- }
- return parse(inner);
- }
-
- private static void dropFile(Uri uri, Context context) {
- if (DocumentsContract.isDocumentUri(context, uri)) {
- DocumentsContract.deleteDocument(context.getContentResolver(), uri);
- } else {
- context.getContentResolver().delete(uri, null, null);
- }
- }
-
- private static WifiConfiguration parse(MIMEContainer root)
- throws IOException, GeneralSecurityException, SAXException {
-
- if (root.getMimeContainers() == null) {
- throw new IOException("Malformed MIME content: not multipart");
- }
-
- String moText = null;
- X509Certificate caCert = null;
- PrivateKey clientKey = null;
- List<X509Certificate> clientChain = null;
-
- for (MIMEContainer subContainer : root.getMimeContainers()) {
- Log.d(TAG, " + Content Type: " + subContainer.getContentType());
- switch (subContainer.getContentType()) {
- case ProfileTag:
- if (subContainer.isBase64()) {
- byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
- moText = new String(octets, StandardCharsets.UTF_8);
- } else {
- moText = subContainer.getText();
- }
- Log.d(TAG, "OMA: " + moText);
- break;
- case CATag: {
- if (!subContainer.isBase64()) {
- throw new IOException("Can't read non base64 encoded cert");
- }
-
- byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
- CertificateFactory factory = CertificateFactory.getInstance(X509);
- caCert = (X509Certificate) factory.generateCertificate(
- new ByteArrayInputStream(octets));
- Log.d(TAG, "Cert subject " + caCert.getSubjectX500Principal());
- Log.d(TAG, "Full Cert: " + caCert);
- break;
- }
- case KeyTag: {
- if (!subContainer.isBase64()) {
- throw new IOException("Can't read non base64 encoded key");
- }
-
- byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
-
- KeyStore ks = KeyStore.getInstance("PKCS12");
- ByteArrayInputStream in = new ByteArrayInputStream(octets);
- ks.load(in, new char[0]);
- in.close();
- Log.d(TAG, "---- Start PKCS12 info " + octets.length + ", size " + ks.size());
- Enumeration<String> aliases = ks.aliases();
- while (aliases.hasMoreElements()) {
- String alias = aliases.nextElement();
- clientKey = (PrivateKey) ks.getKey(alias, null);
- Log.d(TAG, "Key: " + clientKey.getFormat());
- Certificate[] chain = ks.getCertificateChain(alias);
- if (chain != null) {
- clientChain = new ArrayList<>();
- for (Certificate certificate : chain) {
- if (!(certificate instanceof X509Certificate)) {
- Log.w(TAG, "Element in cert chain is not an X509Certificate: " +
- certificate.getClass());
- }
- clientChain.add((X509Certificate) certificate);
- }
- Log.d(TAG, "Chain: " + clientChain.size());
- }
- }
- Log.d(TAG, "---- End PKCS12 info.");
- break;
- }
- }
- }
-
- if (moText == null) {
- throw new IOException("Missing profile");
- }
-
- HomeSP homeSP = PasspointManagementObjectManager.buildSP(moText);
-
- return buildConfig(homeSP, caCert, clientChain, clientKey);
- }
-
- private static WifiConfiguration buildConfig(HomeSP homeSP, X509Certificate caCert,
- List<X509Certificate> clientChain, PrivateKey key)
- throws IOException, GeneralSecurityException {
-
- WifiConfiguration config;
-
- EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID();
- switch (eapMethodID) {
- case EAP_TTLS:
- if (key != null || clientChain != null) {
- Log.w(TAG, "Client cert and/or key unnecessarily included with EAP-TTLS "+
- "profile");
- }
- config = buildTTLSConfig(homeSP, caCert);
- break;
- case EAP_TLS:
- config = buildTLSConfig(homeSP, clientChain, key, caCert);
- break;
- case EAP_AKA:
- case EAP_AKAPrim:
- case EAP_SIM:
- if (key != null || clientChain != null || caCert != null) {
- Log.i(TAG, "Client/CA cert and/or key unnecessarily included with " +
- eapMethodID + " profile");
- }
- config = buildSIMConfig(homeSP);
- break;
- default:
- throw new IOException("Unsupported EAP Method: " + eapMethodID);
- }
-
- return config;
- }
-
- // Retain for debugging purposes
- /*
- private static void xIterateCerts(KeyStore ks, X509Certificate caCert)
- throws GeneralSecurityException {
- Enumeration<String> aliases = ks.aliases();
- while (aliases.hasMoreElements()) {
- String alias = aliases.nextElement();
- Certificate cert = ks.getCertificate(alias);
- Log.d("HS2J", "Checking " + alias);
- if (cert instanceof X509Certificate) {
- X509Certificate x509Certificate = (X509Certificate) cert;
- boolean sm = x509Certificate.getSubjectX500Principal().equals(
- caCert.getSubjectX500Principal());
- boolean eq = false;
- if (sm) {
- eq = Arrays.equals(x509Certificate.getEncoded(), caCert.getEncoded());
- }
- Log.d("HS2J", "Subject: " + x509Certificate.getSubjectX500Principal() +
- ": " + sm + "/" + eq);
- }
- }
- }
- */
-
- private static void setAnonymousIdentityToNaiRealm(
- WifiConfiguration config, Credential credential) {
- /**
- * Set WPA supplicant's anonymous identity field to a string containing the NAI realm, so
- * that this value will be sent to the EAP server as part of the EAP-Response/ Identity
- * packet. WPA supplicant will reset this field after using it for the EAP-Response/Identity
- * packet, and revert to using the (real) identity field for subsequent transactions that
- * request an identity (e.g. in EAP-TTLS).
- *
- * This NAI realm value (the portion of the identity after the '@') is used to tell the
- * AAA server which AAA/H to forward packets to. The hardcoded username, "anonymous", is a
- * placeholder that is not used--it is set to this value by convention. See Section 5.1 of
- * RFC3748 for more details.
- *
- * NOTE: we do not set this value for EAP-SIM/AKA/AKA', since the EAP server expects the
- * EAP-Response/Identity packet to contain an actual, IMSI-based identity, in order to
- * identify the device.
- */
- config.enterpriseConfig.setAnonymousIdentity("anonymous@" + credential.getRealm());
- }
-
- private static WifiConfiguration buildTTLSConfig(HomeSP homeSP, X509Certificate caCert)
- throws IOException {
- Credential credential = homeSP.getCredential();
-
- if (credential.getUserName() == null || credential.getPassword() == null) {
- throw new IOException("EAP-TTLS provisioned without user name or password");
- }
-
- EAPMethod eapMethod = credential.getEAPMethod();
-
- AuthParam authParam = eapMethod.getAuthParam();
- if (authParam == null ||
- authParam.getAuthInfoID() != EAP.AuthInfoID.NonEAPInnerAuthType) {
- throw new IOException("Bad auth parameter for EAP-TTLS: " + authParam);
- }
-
- WifiConfiguration config = buildBaseConfiguration(homeSP);
- NonEAPInnerAuth ttlsParam = (NonEAPInnerAuth) authParam;
- WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
- enterpriseConfig.setPhase2Method(remapInnerMethod(ttlsParam.getType()));
- enterpriseConfig.setIdentity(credential.getUserName());
- enterpriseConfig.setPassword(credential.getPassword());
- enterpriseConfig.setCaCertificate(caCert);
-
- setAnonymousIdentityToNaiRealm(config, credential);
-
- return config;
- }
-
- private static WifiConfiguration buildTLSConfig(HomeSP homeSP,
- List<X509Certificate> clientChain,
- PrivateKey clientKey,
- X509Certificate caCert)
- throws IOException, GeneralSecurityException {
-
- Credential credential = homeSP.getCredential();
-
- X509Certificate clientCertificate = null;
-
- if (clientKey == null || clientChain == null) {
- throw new IOException("No key and/or cert passed for EAP-TLS");
- }
- if (credential.getCertType() != Credential.CertType.x509v3) {
- throw new IOException("Invalid certificate type for TLS: " +
- credential.getCertType());
- }
-
- byte[] reference = credential.getFingerPrint();
- MessageDigest digester = MessageDigest.getInstance("SHA-256");
- for (X509Certificate certificate : clientChain) {
- digester.reset();
- byte[] fingerprint = digester.digest(certificate.getEncoded());
- if (Arrays.equals(reference, fingerprint)) {
- clientCertificate = certificate;
- break;
- }
- }
- if (clientCertificate == null) {
- throw new IOException("No certificate in chain matches supplied fingerprint");
- }
-
- String alias = Base64.encodeToString(reference, Base64.DEFAULT);
-
- WifiConfiguration config = buildBaseConfiguration(homeSP);
- WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
- enterpriseConfig.setClientCertificateAlias(alias);
- enterpriseConfig.setClientKeyEntry(clientKey, clientCertificate);
- enterpriseConfig.setCaCertificate(caCert);
-
- setAnonymousIdentityToNaiRealm(config, credential);
-
- return config;
- }
-
- private static WifiConfiguration buildSIMConfig(HomeSP homeSP)
- throws IOException {
-
- Credential credential = homeSP.getCredential();
- IMSIParameter credImsi = credential.getImsi();
-
- /*
- * Uncomment to enforce strict IMSI matching with currently installed SIM cards.
- *
- TelephonyManager tm = TelephonyManager.from(context);
- SubscriptionManager sub = SubscriptionManager.from(context);
- boolean match = false;
-
- for (int subId : sub.getActiveSubscriptionIdList()) {
- String imsi = tm.getSubscriberId(subId);
- if (credImsi.matches(imsi)) {
- match = true;
- break;
- }
- }
- if (!match) {
- throw new IOException("Supplied IMSI does not match any SIM card");
- }
- */
-
- WifiConfiguration config = buildBaseConfiguration(homeSP);
- config.enterpriseConfig.setPlmn(credImsi != null ? credImsi.toString() : "");
- return config;
- }
-
- private static WifiConfiguration buildBaseConfiguration(HomeSP homeSP) throws IOException {
- EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID();
-
- WifiConfiguration config = new WifiConfiguration();
-
- config.FQDN = homeSP.getFQDN();
-
- HashSet<Long> roamingConsortiumIds = homeSP.getRoamingConsortiums();
- config.roamingConsortiumIds = new long[roamingConsortiumIds.size()];
- int i = 0;
- for (long id : roamingConsortiumIds) {
- config.roamingConsortiumIds[i] = id;
- i++;
- }
- config.providerFriendlyName = homeSP.getFriendlyName();
-
- config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
- config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
-
- WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
- enterpriseConfig.setEapMethod(remapEAPMethod(eapMethodID));
- enterpriseConfig.setRealm(homeSP.getCredential().getRealm());
- config.enterpriseConfig = enterpriseConfig;
- // The framework based config builder only ever builds r1 configs:
- config.updateIdentifier = null;
-
- return config;
- }
-
- private static int remapEAPMethod(EAP.EAPMethodID eapMethodID) throws IOException {
- switch (eapMethodID) {
- case EAP_TTLS:
- return WifiEnterpriseConfig.Eap.TTLS;
- case EAP_TLS:
- return WifiEnterpriseConfig.Eap.TLS;
- case EAP_SIM:
- return WifiEnterpriseConfig.Eap.SIM;
- case EAP_AKA:
- return WifiEnterpriseConfig.Eap.AKA;
- case EAP_AKAPrim:
- return WifiEnterpriseConfig.Eap.AKA_PRIME;
- default:
- throw new IOException("Bad EAP method: " + eapMethodID);
- }
- }
-
- private static int remapInnerMethod(NonEAPInnerAuth.NonEAPType type) throws IOException {
- switch (type) {
- case PAP:
- return WifiEnterpriseConfig.Phase2.PAP;
- case MSCHAP:
- return WifiEnterpriseConfig.Phase2.MSCHAP;
- case MSCHAPv2:
- return WifiEnterpriseConfig.Phase2.MSCHAPV2;
- case CHAP:
- default:
- throw new IOException("Inner method " + type + " not supported");
- }
- }
-}
diff --git a/service/java/com/android/server/wifi/configparse/MIMEContainer.java b/service/java/com/android/server/wifi/configparse/MIMEContainer.java
deleted file mode 100644
index 10ad45660..000000000
--- a/service/java/com/android/server/wifi/configparse/MIMEContainer.java
+++ /dev/null
@@ -1,345 +0,0 @@
-package com.android.server.wifi.configparse;
-
-import android.util.Log;
-
-import com.android.server.wifi.hotspot2.Utils;
-
-import java.io.IOException;
-import java.io.LineNumberReader;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class MIMEContainer {
- private static final String Type = "Content-Type";
- private static final String Encoding = "Content-Transfer-Encoding";
-
- private static final String Boundary = "boundary=";
- private static final String CharsetTag = "charset=";
-
- private final boolean mLast;
- private final List<MIMEContainer> mMimeContainers;
- private final String mText;
-
- private final boolean mMixed;
- private final boolean mBase64;
- private final Charset mCharset;
- private final String mContentType;
-
- /**
- * Parse nested MIME content
- * @param in A reader to read MIME data from; Note that the charset should be ISO-8859-1 to
- * ensure transparent octet to character mapping. This is because the content will
- * be re-encoded using the correct charset once it is discovered.
- * @param boundary A boundary string for the MIME section that this container is in.
- * Pass null for the top level object.
- * @throws java.io.IOException
- */
- public MIMEContainer(LineNumberReader in, String boundary) throws IOException {
- Map<String,List<String>> headers = parseHeader(in);
-
- List<String> type = headers.get(Type);
- if (type == null || type.isEmpty()) {
- throw new IOException("Missing " + Type + " @ " + in.getLineNumber());
- }
-
- boolean multiPart = false;
- boolean mixed = false;
- String subBoundary = null;
- Charset charset = StandardCharsets.ISO_8859_1;
-
- mContentType = type.get(0);
-
- if (mContentType.startsWith("multipart/")) {
- multiPart = true;
-
- for (String attribute : type) {
- if (attribute.startsWith(Boundary)) {
- subBoundary = Utils.unquote(attribute.substring(Boundary.length()));
- }
- }
-
- if (mContentType.endsWith("/mixed")) {
- mixed = true;
- }
- }
- else if (mContentType.startsWith("text/")) {
- for (String attribute : type) {
- if (attribute.startsWith(CharsetTag)) {
- charset = Charset.forName(attribute.substring(CharsetTag.length()));
- }
- }
- }
-
- mMixed = mixed;
- mCharset = charset;
-
- if (multiPart && subBoundary != null) {
- for (;;) {
- String line = in.readLine();
- if (line == null) {
- throw new IOException("Unexpected EOF before first boundary @ " +
- in.getLineNumber());
- }
- if (line.startsWith("--") && line.length() == subBoundary.length() + 2 &&
- line.regionMatches(2, subBoundary, 0, subBoundary.length())) {
- break;
- }
- }
-
- mMimeContainers = new ArrayList<>();
- for (;;) {
- MIMEContainer container = new MIMEContainer(in, subBoundary);
- mMimeContainers.add(container);
- if (container.isLast()) {
- break;
- }
- }
- }
- else {
- mMimeContainers = null;
- }
-
- List<String> encoding = headers.get(Encoding);
- boolean quoted = false;
- boolean base64 = false;
- if (encoding != null) {
- for (String text : encoding) {
- if (text.equalsIgnoreCase("quoted-printable")) {
- quoted = true;
- break;
- }
- else if (text.equalsIgnoreCase("base64")) {
- base64 = true;
- break;
- }
- }
- }
- mBase64 = base64;
-
- Log.d(Utils.hs2LogTag(getClass()),
- String.format("%s MIME container, boundary '%s', type '%s', encoding %s",
- multiPart ? "multipart" : "plain", boundary, mContentType, encoding));
-
- AtomicBoolean eof = new AtomicBoolean();
- mText = recode(getBody(in, boundary, quoted, eof), charset);
- mLast = eof.get();
- }
-
- public List<MIMEContainer> getMimeContainers() {
- return mMimeContainers;
- }
-
- public String getText() {
- return mText;
- }
-
- public boolean isMixed() {
- return mMixed;
- }
-
- public boolean isBase64() {
- return mBase64;
- }
-
- public String getContentType() {
- return mContentType;
- }
-
- private boolean isLast() {
- return mLast;
- }
-
- private void toString(StringBuilder sb, int nesting) {
- char[] indent = new char[nesting*4];
- Arrays.fill(indent, ' ');
- if (mBase64) {
- sb.append("base64, type ").append(mContentType).append('\n');
- }
- else if (mMimeContainers != null) {
- sb.append(indent).append("multipart/").append((mMixed ? "mixed" : "other" )).append('\n');
- }
- else {
- sb.append(indent).append(
- String.format("%s, type %s",
- mCharset,
- mContentType)
- ).append('\n');
- }
-
- if (mMimeContainers != null) {
- for (MIMEContainer mimeContainer : mMimeContainers) {
- mimeContainer.toString(sb, nesting + 1);
- }
- }
- sb.append(indent).append("Text: ");
- if (mText.length() < 100000) {
- sb.append("'").append(mText).append("'\n");
- }
- else {
- sb.append(mText.length()).append(" chars\n");
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- toString(sb, 0);
- return sb.toString();
- }
-
- private static Map<String,List<String>> parseHeader(LineNumberReader in) throws IOException {
-
- StringBuilder value = null;
- String header = null;
-
- Map<String,List<String>> headers = new HashMap<>();
-
- for (;;) {
- String line = in.readLine();
- if ( line == null ) {
- throw new IOException("Missing body @ " + in.getLineNumber());
- }
- else if (line.length() == 0) {
- break;
- }
-
- if (line.charAt(0) <= ' ') {
- if (value == null) {
- throw new IOException("Illegal blank prefix in header line '" + line + "' @ " + in.getLineNumber());
- }
- value.append(' ').append(line.trim());
- continue;
- }
-
- int nameEnd = line.indexOf(':');
- if (nameEnd < 0) {
- throw new IOException("Bad header line: '" + line + "' @ " + in.getLineNumber());
- }
-
- if (header != null) {
- String[] values = value.toString().split(";");
- List<String> valueList = new ArrayList<>(values.length);
- for (String segment : values) {
- valueList.add(segment.trim());
- }
- headers.put(header, valueList);
- //System.out.println("Header '" + header + "' = " + valueList);
- }
-
- header = line.substring(0, nameEnd);
- value = new StringBuilder();
- value.append(line.substring(nameEnd+1).trim());
- }
-
- if (header != null) {
- String[] values = value.toString().split(";");
- List<String> valueList = new ArrayList<>(values.length);
- for (String segment : values) {
- valueList.add(segment.trim());
- }
- headers.put(header, valueList);
- //System.out.println("Header '" + header + "' = " + valueList);
- }
-
- return headers;
- }
-
- private static String getBody(LineNumberReader in, String boundary, boolean quoted, AtomicBoolean eof)
- throws IOException {
-
- StringBuilder text = new StringBuilder();
- for (;;) {
- String line = in.readLine();
- if (line == null) {
- if (boundary != null) {
- throw new IOException("Unexpected EOF file in body @ " + in.getLineNumber());
- }
- else {
- return text.toString();
- }
- }
- Boolean end = boundaryCheck(line, boundary);
- if (end != null) {
- eof.set(end);
- //System.out.println("Boundary " + boundary + ": " + end);
- return text.toString();
- }
-
- if (quoted) {
- if (line.endsWith("=")) {
- text.append(unescape(line.substring(line.length() - 1), in.getLineNumber()));
- }
- else {
- text.append(unescape(line, in.getLineNumber()));
- }
- }
- else {
- text.append(line);
- }
- }
- }
-
- private static String recode(String s, Charset charset) {
- if (charset.equals(StandardCharsets.ISO_8859_1) || charset.equals(StandardCharsets.US_ASCII)) {
- return s;
- }
-
- byte[] octets = s.getBytes(StandardCharsets.ISO_8859_1);
- return new String(octets, charset);
- }
-
- private static Boolean boundaryCheck(String line, String boundary) {
- if (line.startsWith("--") && line.regionMatches(2, boundary, 0, boundary.length())) {
- if (line.length() == boundary.length() + 2) {
- return Boolean.FALSE;
- }
- else if (line.length() == boundary.length() + 4 && line.endsWith("--") ) {
- return Boolean.TRUE;
- }
- }
- return null;
- }
-
- private static String unescape(String text, int line) throws IOException {
- StringBuilder sb = new StringBuilder();
- for (int n = 0; n < text.length(); n++) {
- char ch = text.charAt(n);
- if (ch > 127) {
- throw new IOException("Bad codepoint " + (int)ch + " in quoted printable @ " + line);
- }
- if (ch == '=' && n < text.length() - 2) {
- int h1 = fromStrictHex(text.charAt(n+1));
- int h2 = fromStrictHex(text.charAt(n+2));
- if (h1 >= 0 && h2 >= 0) {
- sb.append((char)((h1 << 4) | h2));
- n += 2;
- }
- else {
- sb.append(ch);
- }
- }
- else {
- sb.append(ch);
- }
- }
- return sb.toString();
- }
-
- private static int fromStrictHex(char ch) {
- if (ch >= '0' && ch <= '9') {
- return ch - '0';
- }
- else if (ch >= 'A' && ch <= 'F') {
- return ch - 'A' + 10;
- }
- else {
- return -1;
- }
- }
-}