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

io.github.mike10004.vhs.harbridge.ContentDisposition Maven / Gradle / Ivy

There is a newer version: 0.32
Show newest version
/*
 * FROM SPRING FRAMEWORK org.springframework.http.ContentDisposition
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.github.mike10004.vhs.harbridge;

import com.google.common.base.Strings;

import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;

/**
 * Represent the Content-Disposition type and parameters as defined in RFC 2183.
 *
 * @author Sebastien Deleuze
 * @author Juergen Hoeller
 * @since 5.0
 * @see RFC 2183
 */
@SuppressWarnings("unused")
public class ContentDisposition {

    @Nullable
    private final String type;

    @Nullable
    private final String name;

    @Nullable
    private final String filename;

    @Nullable
    private final Charset charset;

    @Nullable
    private final Long size;

    @Nullable
    private final ZonedDateTime creationDate;

    @Nullable
    private final ZonedDateTime modificationDate;

    @Nullable
    private final ZonedDateTime readDate;


    /**
     * Private constructor. See static factory methods in this class.
     */
    private ContentDisposition(@Nullable String type, @Nullable String name, @Nullable String filename,
                               @Nullable Charset charset, @Nullable Long size, @Nullable ZonedDateTime creationDate,
                               @Nullable ZonedDateTime modificationDate, @Nullable ZonedDateTime readDate) {

        this.type = type;
        this.name = name;
        this.filename = filename;
        this.charset = charset;
        this.size = size;
        this.creationDate = creationDate;
        this.modificationDate = modificationDate;
        this.readDate = readDate;
    }


    /**
     * Return the disposition type, like for example {@literal inline}, {@literal attachment},
     * {@literal form-data}, or {@code null} if not defined.
     * @return type
     */
    @Nullable
    public String getType() {
        return this.type;
    }

    /**
     * Return the value of the {@literal name} parameter, or {@code null} if not defined.
     * @return name
     */
    @Nullable
    public String getName() {
        return this.name;
    }

    /**
     * Return the value of the {@literal filename} parameter (or the value of the
     * {@literal filename*} one decoded as defined in the RFC 5987), or {@code null} if not defined.
     * @return filename
     */
    @Nullable
    public String getFilename() {
        return this.filename;
    }

    /**
     * Return the charset defined in {@literal filename*} parameter, or {@code null} if not defined.
     * @return charset
     */
    @Nullable
    public Charset getCharset() {
        return this.charset;
    }

    /**
     * Return the value of the {@literal size} parameter, or {@code null} if not defined.
     * @return size
     */
    @Nullable
    public Long getSize() {
        return this.size;
    }

    /**
     * Return the value of the {@literal creation-date} parameter, or {@code null} if not defined.
     * @return datetime
     */
    @Nullable
    public ZonedDateTime getCreationDate() {
        return this.creationDate;
    }

    /**
     * Return the value of the {@literal modification-date} parameter, or {@code null} if not defined.
     * @return datetime
     */
    @Nullable
    public ZonedDateTime getModificationDate() {
        return this.modificationDate;
    }

    /**
     * Return the value of the {@literal read-date} parameter, or {@code null} if not defined.
     * @return datetime
     */
    @Nullable
    public ZonedDateTime getReadDate() {
        return this.readDate;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof ContentDisposition)) {
            return false;
        }
        ContentDisposition otherCd = (ContentDisposition) other;
        return (java.util.Objects.equals(this.type, otherCd.type) &&
                java.util.Objects.equals(this.name, otherCd.name) &&
                java.util.Objects.equals(this.filename, otherCd.filename) &&
                java.util.Objects.equals(this.charset, otherCd.charset) &&
                java.util.Objects.equals(this.size, otherCd.size) &&
                java.util.Objects.equals(this.creationDate, otherCd.creationDate)&&
                java.util.Objects.equals(this.modificationDate, otherCd.modificationDate)&&
                java.util.Objects.equals(this.readDate, otherCd.readDate));
    }

    @Override
    public int hashCode() {
        int result = java.util.Objects.hashCode(this.type);
        result = 31 * result + java.util.Objects.hashCode(this.name);
        result = 31 * result + java.util.Objects.hashCode(this.filename);
        result = 31 * result + java.util.Objects.hashCode(this.charset);
        result = 31 * result + java.util.Objects.hashCode(this.size);
        result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0);
        result = 31 * result + (modificationDate != null ? modificationDate.hashCode() : 0);
        result = 31 * result + (readDate != null ? readDate.hashCode() : 0);
        return result;
    }

    /**
     * Return the header value for this content disposition as defined in RFC 2183.
     * @see #parse(String)
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        if (this.type != null) {
            sb.append(this.type);
        }
        if (this.name != null) {
            sb.append("; name=\"");
            sb.append(this.name).append('\"');
        }
        if (this.filename != null) {
            if(this.charset == null || StandardCharsets.US_ASCII.equals(this.charset)) {
                sb.append("; filename=\"");
                sb.append(this.filename).append('\"');
            }
            else {
                sb.append("; filename*=");
                sb.append(encodeHeaderFieldParam(this.filename, this.charset));
            }
        }
        if (this.size != null) {
            sb.append("; size=");
            sb.append(this.size);
        }
        if (this.creationDate != null) {
            sb.append("; creation-date=\"");
            sb.append(RFC_1123_DATE_TIME.format(this.creationDate));
            sb.append('\"');
        }
        if (this.modificationDate != null) {
            sb.append("; modification-date=\"");
            sb.append(RFC_1123_DATE_TIME.format(this.modificationDate));
            sb.append('\"');
        }
        if (this.readDate != null) {
            sb.append("; read-date=\"");
            sb.append(RFC_1123_DATE_TIME.format(this.readDate));
            sb.append('\"');
        }
        return sb.toString();
    }


    /**
     * Return a builder for a {@code ContentDisposition}.
     * @param type the disposition type like for example {@literal inline},
     * {@literal attachment}, or {@literal form-data}
     * @return the builder
     */
    public static Builder builder(String type) {
        return new BuilderImpl(type);
    }

    /**
     * Return an empty content disposition.
     * @return a new empty instance
     */
    public static ContentDisposition empty() {
        return new ContentDisposition("", null, null, null, null, null, null, null);
    }

    /**
     * Parse a {@literal Content-Disposition} header value as defined in RFC 2183.
     * @param contentDisposition the {@literal Content-Disposition} header value
     * @return the parsed content disposition
     * @see #toString()
     */
    public static ContentDisposition parse(String contentDisposition) {
        List parts = tokenize(contentDisposition);
        String type = parts.get(0);
        String name = null;
        String filename = null;
        Charset charset = null;
        Long size = null;
        ZonedDateTime creationDate = null;
        ZonedDateTime modificationDate = null;
        ZonedDateTime readDate = null;
        for (int i = 1; i < parts.size(); i++) {
            String part = parts.get(i);
            int eqIndex = part.indexOf('=');
            if (eqIndex != -1) {
                String attribute = part.substring(0, eqIndex);
                String value = (part.startsWith("\"", eqIndex + 1) && part.endsWith("\"") ?
                        part.substring(eqIndex + 2, part.length() - 1) :
                        part.substring(eqIndex + 1, part.length()));
                if (attribute.equals("name") ) {
                    name = value;
                }
                else if (attribute.equals("filename*") ) {
                    filename = decodeHeaderFieldParam(value);
                    charset = Charset.forName(value.substring(0, value.indexOf('\'')));
                    checkArgument(UTF_8.equals(charset) || ISO_8859_1.equals(charset),
                            "Charset should be UTF-8 or ISO-8859-1");
                }
                else if (attribute.equals("filename") && (filename == null)) {
                    filename = value;
                }
                else if (attribute.equals("size") ) {
                    size = Long.parseLong(value);
                }
                else if (attribute.equals("creation-date")) {
                    try {
                        creationDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME);
                    }
                    catch (DateTimeParseException ex) {
                        // ignore
                    }
                }
                else if (attribute.equals("modification-date")) {
                    try {
                        modificationDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME);
                    }
                    catch (DateTimeParseException ex) {
                        // ignore
                    }
                }
                else if (attribute.equals("read-date")) {
                    try {
                        readDate = ZonedDateTime.parse(value, RFC_1123_DATE_TIME);
                    }
                    catch (DateTimeParseException ex) {
                        // ignore
                    }
                }
            }
            else {
                throw new IllegalArgumentException("Invalid content disposition format");
            }
        }
        return new ContentDisposition(type, name, filename, charset, size, creationDate, modificationDate, readDate);
    }

    private static List tokenize(String headerValue) {
        int index = headerValue.indexOf(';');
        String type = (index >= 0 ? headerValue.substring(0, index) : headerValue).trim();
        if (type.isEmpty()) {
            throw new IllegalArgumentException("Content-Disposition header must not be empty");
        }
        List parts = new ArrayList<>();
        parts.add(type);
        if (index >= 0) {
            do {
                int nextIndex = index + 1;
                boolean quoted = false;
                while (nextIndex < headerValue.length()) {
                    char ch = headerValue.charAt(nextIndex);
                    if (ch == ';') {
                        if (!quoted) {
                            break;
                        }
                    }
                    else if (ch == '"') {
                        quoted = !quoted;
                    }
                    nextIndex++;
                }
                String part = headerValue.substring(index + 1, nextIndex).trim();
                if (!part.isEmpty()) {
                    parts.add(part);
                }
                index = nextIndex;
            }
            while (index < headerValue.length());
        }
        return parts;
    }

    /**
     * Decode the given header field param as describe in RFC 5987.
     * 

Only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported. * @param input the header field param * @return the encoded header field param * @see RFC 5987 */ private static String decodeHeaderFieldParam(String input) { Objects.requireNonNull(input, "Input String should not be null"); int firstQuoteIndex = input.indexOf('\''); int secondQuoteIndex = input.indexOf('\'', firstQuoteIndex + 1); // US_ASCII if (firstQuoteIndex == -1 || secondQuoteIndex == -1) { return input; } Charset charset = Charset.forName(input.substring(0, firstQuoteIndex)); checkArgument(UTF_8.equals(charset) || ISO_8859_1.equals(charset), "Charset should be UTF-8 or ISO-8859-1"); byte[] value = input.substring(secondQuoteIndex + 1, input.length()).getBytes(charset); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int index = 0; while (index < value.length) { byte b = value[index]; if (isRFC5987AttrChar(b)) { bos.write((char) b); index++; } else if (b == '%') { char[] array = { (char)value[index + 1], (char)value[index + 2]}; bos.write(Integer.parseInt(String.valueOf(array), 16)); index+=3; } else { throw new IllegalArgumentException("Invalid header field parameter format (as defined in RFC 5987)"); } } return new String(bos.toByteArray(), charset); } private static boolean isRFC5987AttrChar(byte c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '!' || c == '#' || c == '$' || c == '&' || c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; } /** * Encode the given header field param as describe in RFC 5987. * @param input the header field param * @param charset the charset of the header field param string, * only the US-ASCII, UTF-8 and ISO-8859-1 charsets are supported * @return the encoded header field param * @see RFC 5987 */ private static String encodeHeaderFieldParam(String input, Charset charset) { java.util.Objects.requireNonNull(input, "Input String should not be null"); java.util.Objects.requireNonNull(charset, "Charset should not be null"); if (StandardCharsets.US_ASCII.equals(charset)) { return input; } checkArgument(UTF_8.equals(charset) || ISO_8859_1.equals(charset), "Charset should be UTF-8 or ISO-8859-1"); byte[] source = input.getBytes(charset); int len = source.length; StringBuilder sb = new StringBuilder(len << 1); sb.append(charset.name()); sb.append("''"); for (byte b : source) { if (isRFC5987AttrChar(b)) { sb.append((char) b); } else { sb.append('%'); char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16)); char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16)); sb.append(hex1); sb.append(hex2); } } return sb.toString(); } /** * A mutable builder for {@code ContentDisposition}. */ public interface Builder { /** * Set the value of the {@literal name} parameter * @param name name * @return this instance */ Builder name(String name); /** * Set the value of the {@literal filename} parameter * @param filename filename * @return this instance */ Builder filename(String filename); /** * Set the value of the {@literal filename*} that will be encoded as * defined in the RFC 5987. Only the US-ASCII, UTF-8 and ISO-8859-1 * charsets are supported. *

Note: Do not use this for a * {@code "multipart/form-data"} requests as per * RFC 7578, Section 4.2 * and also RFC 5987 itself mentions it does not apply to multipart * requests. * @param filename filename * @param charset charset * @return this instance */ Builder filename(String filename, Charset charset); /** * Set the value of the {@literal size} parameter * @param size the size * @return this instance */ Builder size(Long size); /** * Set the value of the {@literal creation-date} parameter. * @return this instance */ Builder creationDate(ZonedDateTime creationDate); /** * Set the value of the {@literal modification-date} parameter. * @return this instance */ Builder modificationDate(ZonedDateTime modificationDate); /** * Set the value of the {@literal read-date} parameter. * @return this instance */ Builder readDate(ZonedDateTime readDate); /** * Build the content disposition * @return a new content disposition instance */ ContentDisposition build(); } private static class BuilderImpl implements Builder { private String type; @Nullable private String name; @Nullable private String filename; @Nullable private Charset charset; @Nullable private Long size; @Nullable private ZonedDateTime creationDate; @Nullable private ZonedDateTime modificationDate; @Nullable private ZonedDateTime readDate; public BuilderImpl(String type) { checkArgument(!Strings.isNullOrEmpty(type), "'type' must not be not empty"); this.type = type; } @Override public Builder name(String name) { this.name = name; return this; } @Override public Builder filename(String filename) { this.filename = filename; return this; } @Override public Builder filename(String filename, Charset charset) { this.filename = filename; this.charset = charset; return this; } @Override public Builder size(Long size) { this.size = size; return this; } @Override public Builder creationDate(ZonedDateTime creationDate) { this.creationDate = creationDate; return this; } @Override public Builder modificationDate(ZonedDateTime modificationDate) { this.modificationDate = modificationDate; return this; } @Override public Builder readDate(ZonedDateTime readDate) { this.readDate = readDate; return this; } @Override public ContentDisposition build() { return new ContentDisposition(this.type, this.name, this.filename, this.charset, this.size, this.creationDate, this.modificationDate, this.readDate); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy