All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.upokecenter.mail.ContentDisposition Maven / Gradle / Ivy

package com.upokecenter.mail;
/*
Written by Peter O. in 2014.
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
If you like this, you should donate to Peter O.
at: http://upokecenter.dreamhosters.com/articles/donate-now-2/
 */

import java.util.*;

import com.upokecenter.util.*;
import com.upokecenter.text.*;
import com.upokecenter.text.encoders.*;

    /**
     * Specifies how a message body should be displayed or handled by a mail user
     * agent. This type is immutable; its contents can't be changed after
     * it's created. To create a changeable disposition object, use the
     * DispositionBuilder class.
     */
  public class ContentDisposition {
    private String dispositionType;

    /**
     * Gets a string containing this object's disposition type, such as "inline" or
     * "attachment".
     * @return A string containing this object's disposition type, such as "inline"
     * or "attachment".
     */
    public final String getDispositionType() {
        return this.dispositionType;
      }

    /**
     * Determines whether this object and another object are equal.
     * @param obj An arbitrary object.
     * @return True if the objects are equal; otherwise, false.
     */
    @Override public boolean equals(Object obj) {
      ContentDisposition other = ((obj instanceof ContentDisposition) ? (ContentDisposition)obj : null);
      if (other == null) {
        return false;
      }
      return this.dispositionType.equals(other.dispositionType) &&
        CollectionUtilities.MapEquals(this.parameters, other.parameters);
    }

    /**
     * Returns the hash code for this instance.
     * @return A 32-bit hash code.
     */
    @Override public int hashCode() {
      int valueHashCode = 632580499;
      {
        if (this.dispositionType != null) {
          valueHashCode += 632580503 * this.dispositionType.hashCode();
        }
        if (this.parameters != null) {
          valueHashCode += 632580587 * this.parameters.size();
        }
      }
      return valueHashCode;
    }

    /**
     * Gets a value indicating whether the disposition type is inline.
     * @return True if the disposition type is inline; otherwise, false.
     */
    public final boolean isInline() {
        return this.dispositionType.equals("inline");
      }

    /**
     * Gets a value indicating whether the disposition type is attachment.
     * @return True if the disposition type is attachment; otherwise, false.
     */
    public final boolean isAttachment() {
        return this.dispositionType.equals("attachment");
      }

    ContentDisposition(
String type,
 Map parameters) {
      this.dispositionType = type;
      this.parameters = new TreeMap(parameters);
    }

    private TreeMap parameters;

    /**
     * Gets a list of parameter names associated with this object and their values.
     * @return A read-only list of parameter names associated with this object and
     * their values. The names will be sorted.
     */
    public final Map getParameters() {
        return java.util.Collections.unmodifiableMap(this.parameters);
      }

    /**
     * Converts this object to a text string.
     * @return A string representation of this object.
     */
    @Override public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append(this.dispositionType);
      MediaType.AppendParameters(this.parameters, sb);
      return sb.toString();
    }

    private static String RemoveEncodedWordEnds(String str) {
      StringBuilder sb = new StringBuilder();
      int index = 0;
      boolean inEncodedWord = false;
      while (index < str.length()) {
        if (!inEncodedWord && index + 1 < str.length() && str.charAt(index) == '=' &&
          str.charAt(index + 1) == '?') {
          // Remove start of encoded word
          inEncodedWord = true;
          index += 2;
          int qmarks = 0;
          // skip charset and encoding
          while (index < str.length()) {
            if (str.charAt(index) == '?') {
              ++qmarks;
              ++index;
              if (qmarks == 2) {
                break;
              }
            } else {
              ++index;
            }
          }
          inEncodedWord = true;
        } else if (inEncodedWord && index + 1 < str.length() && str.charAt(index) ==
          '?' && str.charAt(index + 1) == '=') {
          // End of encoded word
          index += 2;
          inEncodedWord = false;
        } else {
          int c = DataUtilities.CodePointAt(str, index);
          if (c == 0xfffd) {
            sb.append((char)0xfffd);
            ++index;
          } else {
            sb.append(str.charAt(index++));
            if (c >= 0x10000) {
              sb.append(str.charAt(index++));
            }
          }
        }
      }
      return sb.toString();
    }

    /**
     * Converts a filename from the Content-Disposition header to a suitable name
     * for saving data to a file. 

Examples:

*

"=?utf-8?q?hello=2Etxt?=" -> "hello.txt" (RFC 2047 * encoding)

"=?utf-8?q?long_filename?=" -> "long filename" * (RFC 2047 encoding)

"utf-8'en'hello%2Etxt" -> "hello.txt" * (RFC 2231 encoding)

"nul.txt" -> "_nul.txt" (Reserved * name)

"dir1/dir2/file" -> "dir1_dir2_file" (Directory * separators)

* @param str A string representing a file name. Can be null. * @return A string with the converted version of the file name. Among other * things, encoded words under RFC 2047 are decoded (since they occur so * frequently in Content-Disposition filenames); the value is decoded * under RFC 2231 if possible; characters unsuitable for use in a * filename (including the directory separators slash and backslash) are * replaced with underscores; and the filename is truncated if it would * otherwise be too long. Returns an empty string if {@code str} is * null. */ public static String MakeFilename(String str) { if (str == null) { return ""; } str = ParserUtility.TrimSpaceAndTab(str); if (str.indexOf("=?") >= 0) { // May contain encoded words, which are very frequent // in Content-Disposition filenames (they would appear quoted // in the Content-Disposition "filename" parameter); these changes // appear justified in sec. 2.3 of RFC 2183, which says that // the parameter's value "should be used as a // basis for the actual filename, where possible." str = Rfc2047.DecodeEncodedWordsLenient( str, 0, str.length()); if (str.indexOf("=?") >= 0) { // Remove ends of encoded words that remain str = RemoveEncodedWordEnds(str); } } else if (str.indexOf('\'') > 0) { // Check for RFC 2231 encoding, as long as the value before the // apostrophe is a recognized charset. It appears to be common, // too, to use quotes around a filename parameter AND use // RFC 2231 encoding, even though all the examples in that RFC // show unquoted use of this encoding. String charset = Encodings.ResolveAliasForEmail( str.substring( 0, ( 0)+(str.indexOf('\'')))); if (!((charset) == null || (charset).length() == 0)) { String newstr = MediaType.DecodeRfc2231Extension(str); if (!((newstr) == null || (newstr).length() == 0)) { // Value was decoded under RFC 2231 str = newstr; } } } str = ParserUtility.TrimSpaceAndTab(str); if (str.length() == 0) { return "_"; } StringBuilder builder = new StringBuilder(); // Replace unsuitable characters for filenames // and make sure the filename's // length doesn't exceed 250. (A few additional characters // may be added later on.) // NOTE: Even if there are directory separators (backslash // and forward slash), the filename is not treated as a // file system path (in accordance with sec. 2.3 of RFC // 2183); as a result, the directory separators // will be treated as unsuitable characters for filenames // and are handled below. for (int i = 0; i < str.length() && builder.length() < 250; ++i) { int c = DataUtilities.CodePointAt(str, i); if (c >= 0x10000) { ++i; } if (c == (int)'\t') { // Replace tab with space builder.append(' '); } else if (c < 0x20 || c == '\\' || c == '/' || c == '*' || c == '?' || c == '|' || c == ':' || c == '<' || c == '>' || c == '"' || (c >= 0x7f && c <= 0x9f)) { // Unsuitable character for a filename (one of the characters // reserved by Windows, // backslash, forward slash, ASCII controls, and C1 controls). builder.append('_'); } else if (c == '%') { // Treat percent ((character instanceof unsuitable) ? (unsuitable)character : null), even though it can occur // in a Windows filename, since it's used in MS-DOS and Windows // in environment variable placeholders builder.append('_'); } else { if (builder.length() < 249 || c < 0x10000) { if (c <= 0xffff) { builder.append((char)c); } else if (c <= 0x10ffff) { builder.append((char)((((c - 0x10000) >> 10) & 0x3ff) + 0xd800)); builder.append((char)(((c - 0x10000) & 0x3ff) + 0xdc00)); } } } } str = builder.toString(); str = ParserUtility.TrimSpaceAndTab(str); if (str.length() == 0) { return "_"; } if (str.charAt(str.length() - 1) == '.') { // Ends in a dot str += "_"; } String strLower = DataUtilities.ToLowerCaseAscii(str); if ( strLower.equals( "nul") || strLower.indexOf( "nul.") == 0 || strLower.equals( "prn") || strLower.indexOf( "prn.") == 0 || strLower.equals( "aux") || strLower.indexOf( "aux.") == 0 || strLower.equals( "con") || strLower.indexOf( "con.") == 0 || ( strLower.length() >= 4 && strLower.indexOf( "lpt") == 0 && strLower.charAt(3) >= '0' && strLower.charAt(3) <= '9') || (strLower.length() >= 4 && strLower.indexOf( "com") == 0 && strLower.charAt(3) >= '0' && strLower.charAt(3) <= '9')) { // Reserved filenames on Windows str = "_" + str; } if (str.charAt(0) == '~' || str.charAt(0) == '-') { // Home folder convention (tilde). // Filenames starting with hyphens can also be // problematic especially in Unix-based systems str = "_" + str; } if (str.charAt(0) == '.') { // Starts with period; may be hidden in some configurations str = "_" + str; } return NormalizingCharacterInput.Normalize(str, Normalization.NFC); } /** * Gets a parameter from this disposition object. * @param name The name of the parameter to get. The name will be matched * case-insensitively. Can't be null. * @return The value of the parameter, or null if the parameter does not exist. * @throws NullPointerException The parameter {@code name} is null. * @throws IllegalArgumentException The parameter {@code name} is empty. */ public String GetParameter(String name) { if (name == null) { throw new NullPointerException("name"); } if (name.length() == 0) { throw new IllegalArgumentException("name is empty."); } name = DataUtilities.ToLowerCaseAscii(name); return this.parameters.containsKey(name) ? this.parameters.get(name) : null; } private boolean ParseDisposition(String str) { boolean HttpRules = false; int index = 0; if (str == null) { throw new NullPointerException("str"); } int endIndex = str.length(); index = HeaderParser.ParseCFWS(str, index, endIndex, null); int i = MediaType.SkipMimeToken(str, index, endIndex, null, HttpRules); if (i == index) { return false; } this.dispositionType = DataUtilities.ToLowerCaseAscii(str.substring(index, (index)+(i - index))); if (i < endIndex) { // if not at end int i3 = HeaderParser.ParseCFWS(str, i, endIndex, null); if (i3 == endIndex) { // at end return true; } if (i3 < endIndex && str.charAt(i3) != ';') { // not followed by ";", so not a content disposition return false; } } index = i; return MediaType.ParseParameters( str, index, endIndex, HttpRules, this.parameters); } private static ContentDisposition Build(String name) { ContentDisposition dispo = new ContentDisposition(); dispo.parameters = new TreeMap(); dispo.dispositionType = name; return dispo; } /** * The content disposition value "attachment". */ public static final ContentDisposition Attachment = Build("attachment"); /** * The content disposition value "inline". */ public static final ContentDisposition Inline = Build("inline"); private ContentDisposition() { } /** * Parses a content disposition string and returns a content disposition * object. * @param dispoValue A string object. * @return A content disposition object, or "Attachment" if {@code dispoValue} * is empty or syntactically invalid. * @throws NullPointerException The parameter {@code dispoValue} is null. */ public static ContentDisposition Parse(String dispoValue) { if ((dispoValue) == null) { throw new NullPointerException("dispoValue"); } return Parse(dispoValue, Attachment); } /** * Creates a new content disposition object from the value of a * Content-Disposition header field. * @param dispositionValue A string object that should be the value of a * Content-Disposition header field. * @param defaultValue The value to return in case the disposition value is * syntactically invalid. Can be null. * @return A ContentDisposition object. * @throws NullPointerException The parameter {@code dispositionValue} is * null. */ public static ContentDisposition Parse( String dispositionValue, ContentDisposition defaultValue) { if (dispositionValue == null) { throw new NullPointerException("dispositionValue"); } ContentDisposition dispo = new ContentDisposition(); dispo.parameters = new TreeMap(); return (!dispo.ParseDisposition(dispositionValue)) ? defaultValue : dispo; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy