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

io.helidon.http.ContentDisposition Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
 *
 * 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.helidon.http;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import io.helidon.common.GenericType;
import io.helidon.common.mapper.MapperException;
import io.helidon.common.mapper.MapperManager;
import io.helidon.common.mapper.Value;

/**
 * A generic representation of the {@code Content-Disposition} header.
 * 

* Parameter encoding is not supported, other than * URI percent * encoding in the filename parameter. See {@link java.net.URLDecoder}. *

* See also: * */ public class ContentDisposition implements Header { private static final String NAME_PARAMETER = "name"; private static final String FILENAME_PARAMETER = "filename"; private static final String CREATION_DATE_PARAMETER = "creation-date"; private static final String MODIFICATION_DATE_PARAMETER = "modification-date"; private static final String READ_DATE_PARAMETER = "read-date"; private static final String SIZE_PARAMETER = "size"; private static final ContentDisposition EMPTY = ContentDisposition.builder() .type("") .build(); private static final Pattern DISPOSITION_PART_PATTERN = Pattern.compile("^(.+?)=\"?(.+?)\"?$"); private final String type; private final Map parameters; private String value; private ContentDisposition(Builder builder) { this.type = builder.type; this.parameters = new LinkedHashMap<>(builder.parameters); } /** * A new builder to set up content disposition. * * @return builder */ public static Builder builder() { return new Builder(); } /** * Parse a received header value. * * @param headerValue content disposition header value * @return a parsed content disposition */ public static ContentDisposition parse(String headerValue) { Builder builder = ContentDisposition.builder(); // first split by semicolon String[] parts = headerValue.split("(?<=[^\\\\]);"); if (parts.length > 0) { String type = parts[0]; if (type.indexOf('=') > -1) { throw new IllegalArgumentException("No type defined"); } else { builder.type(type.trim()); } for (int i = 1; i < parts.length; i++) { String part = parts[i]; Matcher matcher = DISPOSITION_PART_PATTERN.matcher(part.trim()); if (matcher.matches()) { String name = matcher.group(1); String value = matcher.group(2); value = value.replace("\\\\", "\\"); value = value.replace("\\\"", "\""); value = value.replace("\\;", ";"); builder.parameter(name, value); } } } return builder.build(); } /** * An empty content disposition. * * @return empty disposition with empty type */ public static ContentDisposition empty() { return EMPTY; } @Override public String name() { return HeaderNames.CONTENT_DISPOSITION.defaultCase(); } @Override public HeaderName headerName() { return HeaderNames.CONTENT_DISPOSITION; } @Override public String get() { if (value == null) { StringBuilder sb = new StringBuilder(); sb.append(type); for (Map.Entry param : parameters.entrySet()) { sb.append(";"); sb.append(param.getKey()); sb.append("="); if (SIZE_PARAMETER.equals(param.getKey())) { sb.append(param.getValue()); } else { sb.append("\""); sb.append(param.getValue()); sb.append("\""); } } value = sb.toString(); } return value; } @Override public Value as(Class type) throws MapperException { return asString().as(type); } @Override public Value as(GenericType type) throws MapperException { return asString().as(type); } @Override public Value as(Function mapper) { return asString().as(mapper); } @Override public Optional asOptional() throws MapperException { return asString().asOptional(); } @Override public Value asBoolean() { return asString().asBoolean(); } @Override public Value asString() { return Value.create(MapperManager.global(), name(), get(), GenericType.STRING, "http", "header"); } @Override public Value asInt() { return asString().asInt(); } @Override public Value asLong() { return asString().asLong(); } @Override public Value asDouble() { return asString().asDouble(); } @Override public List allValues() { return List.of(get()); } @Override public int valueCount() { return 1; } @Override public boolean sensitive() { return false; } @Override public boolean changing() { return true; } @Override public String toString() { return get(); } /** * Get the value of the {@code name} parameter. In the case of a * {@code form-data} disposition type the value is the original field name * from the form. * * @return {@code Optional}, never {@code null} */ public Optional contentName() { return Optional.ofNullable(parameters.get(NAME_PARAMETER)); } /** * Get the value of the {@code filename} parameter that can be used to * suggest a filename to be used if the entity is detached and stored in a * separate file. * * @return {@code Optional}, never {@code null} */ public Optional filename() { String filename = null; String value = parameters.get(FILENAME_PARAMETER); if (value != null) { filename = URLDecoder.decode(value, StandardCharsets.UTF_8); } return Optional.ofNullable(filename); } /** * Get the value of the {@code creation-date} parameter that can be used * to indicate the date at which the file was created. * * @return {@code Optional}, never {@code null} */ public Optional creationDate() { return Optional.ofNullable(parameters.get(CREATION_DATE_PARAMETER)).map(DateTime::parse); } /** * Get the value of the {@code modification-date} parameter that can be * used to indicate the date at which the file was last modified. * * @return {@code Optional}, never {@code null} */ public Optional modificationDate() { return Optional.ofNullable(parameters.get(MODIFICATION_DATE_PARAMETER)).map(DateTime::parse); } /** * Get the value of the {@code modification-date} parameter that can be * used to indicate the date at which the file was last read. * * @return {@code Optional}, never {@code null} */ public Optional readDate() { return Optional.ofNullable(parameters.get(READ_DATE_PARAMETER)).map(DateTime::parse); } /** * Get the value of the {@code size} parameter that can be * used to indicate an approximate size of the file in octets. * * @return {@code OptionalLong}, never {@code null} */ public OptionalLong size() { String size = parameters.get(SIZE_PARAMETER); if (size != null) { return OptionalLong.of(Long.parseLong(size)); } return OptionalLong.empty(); } /** * Get the parameters map. * * @return map, never {@code null} */ public Map parameters() { return Map.copyOf(parameters); } /** * Content disposition type. * * @return type of this content disposition */ public String type() { return type; } /** * Fluent API builder for {@link ContentDisposition}. */ public static final class Builder implements io.helidon.common.Builder { /** * The form-data content disposition used by {@link io.helidon.common.media.type.MediaTypes#MULTIPART_FORM_DATA}. */ public static final String TYPE_FORM_DATA = "form-data"; private final Map parameters = new LinkedHashMap<>(); private String type = TYPE_FORM_DATA; private Builder() { } @Override public ContentDisposition build() { return new ContentDisposition(this); } /** * Set the content disposition type. * Defaults to {@value #TYPE_FORM_DATA}. * * @param type content disposition type * @return updated builder */ public Builder type(String type) { this.type = type.toLowerCase(); return this; } /** * Set the content disposition {@code name} parameter. * * @param name control name * @return this builder */ public Builder name(String name) { parameters.put(NAME_PARAMETER, name); return this; } /** * Set the content disposition {@code filename} parameter. * * @param filename filename parameter * @return this builder */ public Builder filename(String filename) { parameters.put(FILENAME_PARAMETER, URLEncoder.encode(filename, StandardCharsets.UTF_8)); return this; } /** * Set the content disposition {@code creation-date} parameter. * * @param date date value * @return this builder */ public Builder creationDate(ZonedDateTime date) { parameters.put(CREATION_DATE_PARAMETER, date.format(DateTime.RFC_1123_DATE_TIME)); return this; } /** * Set the content disposition {@code modification-date} parameter. * * @param date date value * @return this builder */ public Builder modificationDate(ZonedDateTime date) { parameters.put(MODIFICATION_DATE_PARAMETER, date.format(DateTime.RFC_1123_DATE_TIME)); return this; } /** * Set the content disposition {@code read-date} parameter. * * @param date date value * @return this builder */ public Builder readDate(ZonedDateTime date) { parameters.put(READ_DATE_PARAMETER, date.format(DateTime.RFC_1123_DATE_TIME)); return this; } /** * Set the content disposition {@code size} parameter. * * @param size size value * @return this builder */ public Builder size(long size) { parameters.put(SIZE_PARAMETER, Long.toString(size)); return this; } /** * Add a new content disposition header parameter. * * @param name parameter name * @param value parameter value * @return this builder */ public Builder parameter(String name, String value) { parameters.put(name, value); return this; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy