This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Queue;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* A thread safe wrapper around {@link SimpleDateFormat} that does not make use
* of ThreadLocal and - broadly - only creates enough SimpleDateFormat objects
* to satisfy the concurrency requirements.
*/
public class ConcurrentDateFormat {
private final String format;
private final Locale locale;
private final TimeZone timezone;
private final Queue<SimpleDateFormat> queue = new ConcurrentLinkedQueue<>();
public ConcurrentDateFormat(String format, Locale locale, TimeZone timezone) {
this.format = format;
this.locale = locale;
this.timezone = timezone;
SimpleDateFormat initial = createInstance();
queue.add(initial);
}
public String format(Date date) {
SimpleDateFormat sdf = queue.poll();
if (sdf == null) {
sdf = createInstance();
}
String result = sdf.format(date);
queue.add(sdf);
return result;
}
public Date parse(String source) throws ParseException {
SimpleDateFormat sdf = queue.poll();
if (sdf == null) {
sdf = createInstance();
}
Date result = sdf.parse(source);
queue.add(sdf);
return result;
}
private SimpleDateFormat createInstance() {
SimpleDateFormat sdf = new SimpleDateFormat(format, locale);
sdf.setTimeZone(timezone);
return sdf;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.nio.charset.Charset;
import javax.servlet.http.Cookie;
public interface CookieProcessor {
/**
* Parse the provided headers into server cookie objects.
*
* @param headers The HTTP headers to parse
* @param serverCookies The server cookies object to populate with the
* results of the parsing
*/
void parseCookieHeader(MimeHeaders headers, ServerCookies serverCookies);
/**
* Generate the {@code Set-Cookie} HTTP header value for the given Cookie.
*
* @param cookie The cookie for which the header will be generated
*
* @return The header value in a form that can be added directly to the
* response
*/
String generateHeader(Cookie cookie);
/**
* Obtain the character set that will be used when converting between bytes
* and characters when parsing and/or generating HTTP headers for cookies.
*
* @return The character set used for byte&lt;-&gt;character conversions
*/
Charset getCharset();
}

View File

@@ -0,0 +1,55 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
public abstract class CookieProcessorBase implements CookieProcessor {
private static final String COOKIE_DATE_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss z";
protected static final ThreadLocal<DateFormat> COOKIE_DATE_FORMAT =
new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
DateFormat df =
new SimpleDateFormat(COOKIE_DATE_PATTERN, Locale.US);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df;
}
};
protected static final String ANCIENT_DATE;
static {
ANCIENT_DATE = COOKIE_DATE_FORMAT.get().format(new Date(10000));
}
private SameSiteCookies sameSiteCookies = SameSiteCookies.UNSET;
public SameSiteCookies getSameSiteCookies() {
return sameSiteCookies;
}
public void setSameSiteCookies(String sameSiteCookies) {
this.sameSiteCookies = SameSiteCookies.fromString(sameSiteCookies);
}
}

View File

@@ -0,0 +1,218 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
/**
* Utility class to generate HTTP dates.
*
* @author Remy Maucherat
*/
public final class FastHttpDateFormat {
// -------------------------------------------------------------- Variables
private static final int CACHE_SIZE =
Integer.parseInt(System.getProperty("org.apache.tomcat.util.http.FastHttpDateFormat.CACHE_SIZE", "1000"));
/**
* The only date format permitted when generating HTTP headers.
*
* @deprecated Unused. This will be removed in Tomcat 10.
*/
@Deprecated
public static final String RFC1123_DATE = "EEE, dd MMM yyyy HH:mm:ss zzz";
// HTTP date formats
private static final String DATE_RFC5322 = "EEE, dd MMM yyyy HH:mm:ss z";
private static final String DATE_OBSOLETE_RFC850 = "EEEEEE, dd-MMM-yy HH:mm:ss zzz";
private static final String DATE_OBSOLETE_ASCTIME = "EEE MMMM d HH:mm:ss yyyy";
private static final ConcurrentDateFormat FORMAT_RFC5322;
private static final ConcurrentDateFormat FORMAT_OBSOLETE_RFC850;
private static final ConcurrentDateFormat FORMAT_OBSOLETE_ASCTIME;
private static final ConcurrentDateFormat[] httpParseFormats;
static {
// All the formats that use a timezone use GMT
TimeZone tz = TimeZone.getTimeZone("GMT");
FORMAT_RFC5322 = new ConcurrentDateFormat(DATE_RFC5322, Locale.US, tz);
FORMAT_OBSOLETE_RFC850 = new ConcurrentDateFormat(DATE_OBSOLETE_RFC850, Locale.US, tz);
FORMAT_OBSOLETE_ASCTIME = new ConcurrentDateFormat(DATE_OBSOLETE_ASCTIME, Locale.US, tz);
httpParseFormats = new ConcurrentDateFormat[] {
FORMAT_RFC5322, FORMAT_OBSOLETE_RFC850, FORMAT_OBSOLETE_ASCTIME };
}
/**
* Instant on which the currentDate object was generated.
*/
private static volatile long currentDateGenerated = 0L;
/**
* Current formatted date.
*/
private static String currentDate = null;
/**
* Formatter cache.
*/
private static final Map<Long, String> formatCache = new ConcurrentHashMap<>(CACHE_SIZE);
/**
* Parser cache.
*/
private static final Map<String, Long> parseCache = new ConcurrentHashMap<>(CACHE_SIZE);
// --------------------------------------------------------- Public Methods
/**
* Get the current date in HTTP format.
* @return the HTTP date
*/
public static final String getCurrentDate() {
long now = System.currentTimeMillis();
if ((now - currentDateGenerated) > 1000) {
currentDate = FORMAT_RFC5322.format(new Date(now));
currentDateGenerated = now;
}
return currentDate;
}
/**
* Get the HTTP format of the specified date.
* @param value The date
* @param threadLocalformat Ignored. The local ConcurrentDateFormat will
* always be used.
* @return the HTTP date
*
* @deprecated Unused. This will be removed in Tomcat 10
*/
@Deprecated
public static final String formatDate(long value, DateFormat threadLocalformat) {
return formatDate(value);
}
/**
* Get the HTTP format of the specified date.
* @param value The date
* @return the HTTP date
*/
public static final String formatDate(long value) {
Long longValue = Long.valueOf(value);
String cachedDate = formatCache.get(longValue);
if (cachedDate != null) {
return cachedDate;
}
String newDate = FORMAT_RFC5322.format(new Date(value));
updateFormatCache(longValue, newDate);
return newDate;
}
/**
* Try to parse the given date as an HTTP date.
* @param value The HTTP date
* @param threadLocalformats Ignored. The local array of
* ConcurrentDateFormat will always be used.
* @return the date as a long
*
* @deprecated Unused. This will be removed in Tomcat 10
* Use {@link #parseDate(String)}
*/
@Deprecated
public static final long parseDate(String value, DateFormat[] threadLocalformats) {
return parseDate(value);
}
/**
* Try to parse the given date as an HTTP date.
* @param value The HTTP date
* @return the date as a long or <code>-1</code> if the value cannot be
* parsed
*/
public static final long parseDate(String value) {
Long cachedDate = parseCache.get(value);
if (cachedDate != null) {
return cachedDate.longValue();
}
long date = -1;
for (int i = 0; (date == -1) && (i < httpParseFormats.length); i++) {
try {
date = httpParseFormats[i].parse(value).getTime();
updateParseCache(value, Long.valueOf(date));
} catch (ParseException e) {
// Ignore
}
}
return date;
}
/**
* Update cache.
*/
private static void updateFormatCache(Long key, String value) {
if (value == null) {
return;
}
if (formatCache.size() > CACHE_SIZE) {
formatCache.clear();
}
formatCache.put(key, value);
}
/**
* Update cache.
*/
private static void updateParseCache(String key, Long value) {
if (value == null) {
return;
}
if (parseCache.size() > CACHE_SIZE) {
parseCache.clear();
}
parseCache.put(key, value);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
public class HeaderUtil {
/**
* Converts an HTTP header line in byte form to a printable String.
* Bytes corresponding to visible ASCII characters will converted to those
* characters. All other bytes (0x00 to 0x1F, 0x7F to OxFF) will be
* represented in 0xNN form.
*
* @param bytes Contains an HTTP header line
* @param offset The start position of the header line in the array
* @param len The length of the HTTP header line
*
* @return A String with non-printing characters replaced by the 0xNN
* equivalent
*/
public static String toPrintableString(byte[] bytes, int offset, int len) {
StringBuilder result = new StringBuilder();
for (int i = offset; i < offset + len; i++) {
char c = (char) (bytes[i] & 0xFF);
if (c < 0x20 || c > 0x7E) {
result.append("0x");
result.append(Character.forDigit((c >> 4) & 0xF, 16));
result.append(Character.forDigit((c) & 0xF, 16));
} else {
result.append(c);
}
}
return result.toString();
}
private HeaderUtil() {
// Utility class. Hide default constructor.
}
}

View File

@@ -0,0 +1,150 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.tomcat.util.res.StringManager;
/**
* Handle (internationalized) HTTP messages.
*
* @author James Duncan Davidson [duncan@eng.sun.com]
* @author James Todd [gonzo@eng.sun.com]
* @author Jason Hunter [jch@eng.sun.com]
* @author Harish Prabandham
* @author costin@eng.sun.com
*/
public class HttpMessages {
private static final Map<Locale,HttpMessages> instances =
new ConcurrentHashMap<>();
private static final HttpMessages DEFAULT = new HttpMessages(
StringManager.getManager("org.apache.tomcat.util.http.res",
Locale.getDefault()));
// XXX move message resources in this package
private final StringManager sm;
private String st_200 = null;
private String st_302 = null;
private String st_400 = null;
private String st_404 = null;
private String st_500 = null;
private HttpMessages(StringManager sm) {
this.sm = sm;
}
/**
* Get the status string associated with a status code. Common messages are
* cached.
*
* @param status The HTTP status code to retrieve the message for
*
* @return The HTTP status string that conforms to the requirements of the
* HTTP specification
*/
public String getMessage(int status) {
// method from Response.
// Does HTTP requires/allow international messages or
// are pre-defined? The user doesn't see them most of the time
switch( status ) {
case 200:
if(st_200 == null ) {
st_200 = sm.getString("sc.200");
}
return st_200;
case 302:
if(st_302 == null ) {
st_302 = sm.getString("sc.302");
}
return st_302;
case 400:
if(st_400 == null ) {
st_400 = sm.getString("sc.400");
}
return st_400;
case 404:
if(st_404 == null ) {
st_404 = sm.getString("sc.404");
}
return st_404;
case 500:
if (st_500 == null) {
st_500 = sm.getString("sc.500");
}
return st_500;
}
return sm.getString("sc."+ status);
}
public static HttpMessages getInstance(Locale locale) {
HttpMessages result = instances.get(locale);
if (result == null) {
StringManager sm = StringManager.getManager(
"org.apache.tomcat.util.http.res", locale);
if (Locale.getDefault().equals(sm.getLocale())) {
result = DEFAULT;
} else {
result = new HttpMessages(sm);
}
instances.put(locale, result);
}
return result;
}
/**
* Is the provided message safe to use in an HTTP header. Safe messages must
* meet the requirements of RFC2616 - i.e. must consist only of TEXT.
*
* @param msg The message to test
* @return <code>true</code> if the message is safe to use in an HTTP
* header else <code>false</code>
*/
public static boolean isSafeInHttpHeader(String msg) {
// Nulls are fine. It is up to the calling code to address any NPE
// concerns
if (msg == null) {
return true;
}
// Reason-Phrase is defined as *<TEXT, excluding CR, LF>
// TEXT is defined as any OCTET except CTLs, but including LWS
// OCTET is defined as an 8-bit sequence of data
// CTL is defined as octets 0-31 and 127
// LWS, if we exclude CR LF pairs, is defined as SP or HT (32, 9)
final int len = msg.length();
for (int i = 0; i < len; i++) {
char c = msg.charAt(i);
if (32 <= c && c <= 126 || 128 <= c && c <= 255 || c == 9) {
continue;
}
return false;
}
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
cookies.fallToDebug=\n\
\ Note: further occurrences of Cookie errors will be logged at DEBUG level.
cookies.invalidCookieToken=Cookies: Invalid cookie. Value not a token or quoted value
cookies.invalidSameSiteCookies=Unknown setting [{0}], must be one of: unset, none, lax, strict. Default value is unset.
cookies.invalidSpecial=Cookies: Unknown Special Cookie
cookies.maxCountFail=More than the maximum allowed number of cookies, [{0}], were detected.
headers.maxCountFail=More than the maximum allowed number of headers, [{0}], were detected.
parameters.bytes=Start processing with input [{0}]
parameters.copyFail=Failed to create copy of original parameter values for debug logging purposes
parameters.decodeFail.debug=Character decoding failed. Parameter [{0}] with value [{1}] has been ignored.
parameters.decodeFail.info=Character decoding failed. Parameter [{0}] with value [{1}] has been ignored. Note that the name and value quoted here may be corrupted due to the failed decoding. Use debug level logging to see the original, non-corrupted values.
parameters.emptyChunk=Empty parameter chunk ignored
parameters.fallToDebug=\n\
\ Note: further occurrences of Parameter errors will be logged at DEBUG level.
parameters.invalidChunk=Invalid chunk starting at byte [{0}] and ending at byte [{1}] with a value of [{2}] ignored
parameters.maxCountFail=More than the maximum number of request parameters (GET plus POST) for a single request ([{0}]) were detected. Any parameters beyond this limit have been ignored. To change this limit, set the maxParameterCount attribute on the Connector.
parameters.maxCountFail.fallToDebug=\n\
\ Note: further occurrences of this error will be logged at DEBUG level.
parameters.multipleDecodingFail=Character decoding failed. A total of [{0}] failures were detected but only the first was logged. Enable debug level logging for this logger to log all failures.
parameters.noequal=Parameter starting at position [{0}] and ending at position [{1}] with a value of [{2}] was not followed by an ''='' character
rfc6265CookieProcessor.invalidCharInValue=An invalid character [{0}] was present in the Cookie value
rfc6265CookieProcessor.invalidDomain=An invalid domain [{0}] was specified for this cookie
rfc6265CookieProcessor.invalidPath=An invalid path [{0}] was specified for this cookie

View File

@@ -0,0 +1,24 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
cookies.invalidCookieToken=Cookies: Ungültiges Cookie. Wert ist kein Token oder Quoted Token
cookies.invalidSameSiteCookies=Unbekannte Einstellung [{0}], Sollte einer der Werte: ''none'', ''lax'', ''strict'' entsprechen. Standardwert ist ''none''
parameters.bytes=Starte Verarbeitung mit Eingabe [{0}]
parameters.copyFail=Konnte keine Kopie der Originalwerte der Parameter für Debug-Ausgaben erzeugen
parameters.fallToDebug=Beachte: weitere Vorkommen von Parameter Fehlern werden im DEBUG Level geloggt.
parameters.maxCountFail.fallToDebug=Hinweis: weitere Vorkommen dieses Fehlers werden im DEBUG-Level protokolliert.
rfc6265CookieProcessor.invalidPath=Ein ungültiger Pfad [{0}] ist für das Cookie spezifiziert

View File

@@ -0,0 +1,25 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
cookies.invalidCookieToken=Cookies: cookie no válida. El valor no es un token o un valor acotado
parameters.copyFail=Fallo al crear copia de los valores orignales del parámetro para propósitos de debug
parameters.decodeFail.debug=Fallo al decodificar el caracter. Parámetro [{0}] con valor [{1}] ha sido ignorado.\n
parameters.fallToDebug=Nota: Futuras ocurrencias de error del Parámetro serán loggueadas a nivel DEBUG.\n
parameters.maxCountFail=Se detectaron más del máximo número de los parámetros solicitados (GET plus POST) para una solicitud simple ([{0}]). Cualquier parámetro por encima de este límite ha sido ignorado. Para cambiar este límite, fije el atributo maxParameterCount attribute en el Conector.\n
parameters.maxCountFail.fallToDebug=Nota: futuras ocurrencias de este tipo de error serán logueadas a nivel DEBUG
parameters.noequal=El parámetro que inicia en la posición [{0}] y termina en la posición [{1}] con valor de [{2}] no tiene un caracter ''='' a continuación
rfc6265CookieProcessor.invalidPath=Se ha especificado un camino no válido [{0}] para esta cookie

View File

@@ -0,0 +1,39 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
cookies.fallToDebug=\n\
Note: toutes les occurrences suivantes d'erreurs de cookies seront enregistrées au niveau DEBUG
cookies.invalidCookieToken=Cookie non valide. Sa valeur n'est ni un "token" ni une valeur entre guillemets
cookies.invalidSameSiteCookies=Valeur inconnue [{0}], elle doit être parmi: none, lax, strict, la valeur par défaut est none
cookies.invalidSpecial=Cookie spécial inconnu
cookies.maxCountFail=Le nombre maximum de cookies [{0}] est dépassé
headers.maxCountFail=Le nombre d''en-têtes [{0}] dépasse le maximum autorisé
parameters.bytes=Début du traitement avec les données [{0}]
parameters.copyFail=Echec de la copie des valeurs de paramètres originaux pour raisons de journalisation du déboguage
parameters.decodeFail.debug=Echec de décodage de caractère, le paramètre [{0}] de valeur [{1}] a été ignoré
parameters.decodeFail.info=Echec de décodage de caractère, le paramètre [{0}] avec la valeur [{1}] a été ignoré; le nom et la valeur mentionnés ici peuvent avoir été corrompus à cause de l''erreur de décodage, utilisez le niveau debug pour voir les originaux
parameters.emptyChunk=Le bloc de paramètres vide a été ignoré
parameters.fallToDebug=\ Note : les occurrences suivantes d'erreur de traitement de paramètres seront enregistrées au niveau DEBUG
parameters.invalidChunk=Morceau (chunk) invalide démarrant à l''octet [{0}] et se terminant à l''octet [{1}] avec une valeur de [{2}] ignoré
parameters.maxCountFail=Le nombre maximum de paramètres pour une seule requête (GET plus POST) [{0}] a été détecté, les paramètres supplémentaires ont été ignorés; l''attribut maxParameterCount du Connector permet de changer cette limite
parameters.maxCountFail.fallToDebug=\ Note : les occurrences suivantes de cette erreur seront enregistrées au niveau DEBUG
parameters.multipleDecodingFail=Echec de décodage de caractère, [{0}] erreurs ont été détectées au total mais seule la première a été logguée, activez le niveau debug pour avoir toutes les erreurs
parameters.noequal=Le paramètre qui démarre à la position [{0}] et qui se termine à la position [{1}] avec comme valeur [{2}] n''est pas suivi par un caractère ''=''
rfc6265CookieProcessor.invalidCharInValue=Un caractère invalide [{0}] était présent dans la valeur du cookie
rfc6265CookieProcessor.invalidDomain=Un domaine [{0}] invalide a été spécifié pour ce cookie
rfc6265CookieProcessor.invalidPath=Un chemin (path) invalide [{0}] a été spécifié pour ce biscuit (cookie)

View File

@@ -0,0 +1,38 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
cookies.fallToDebug=さらなるCookieエラーの発生はDEBUGレベルで記録されます。
cookies.invalidCookieToken=Cookie: 不正な cookie です。値がトークンではないか、クォートされていません。
cookies.invalidSpecial=Cookies: 不明な特別な Cookie
cookies.maxCountFail=最大数[{0}]以上のクッキーが検出されました。
headers.maxCountFail=検出したヘッダー数 [{0}] は上限値を越えています。
parameters.bytes=入力[{0}]で処理を開始します。
parameters.copyFail=デバッグログの目的で元のパラメータ値のコピーを作成できませんでした
parameters.decodeFail.debug=文字列のデコードに失敗しました。パラメーター [{0}] (値 [{1}]) は無視しました。
parameters.decodeFail.info=文字のデコードに失敗しました。 値[{1}]のパラメータ[{0}]は無視されました。 ここで引用された名前と値は、デコードに失敗したために破損している可能性があることに注意してください。 デバッグレベルのログを使用して、破損していない元の値を確認してください。
parameters.emptyChunk=空のパラメータチャンクが無視されます。
parameters.fallToDebug=\n\
メモ: 以降のパラメータエラーは DEBUG レベルでロギングします。
parameters.invalidChunk=バイト値 [{0}] で始まりバイト値 [{1}] で終了する不正なチャンクです。値 [{2}] を無視します。
parameters.maxCountFail=単独のリクエスト ([{0}]) のリクエストパラメーター (GET および POST) の数が上限値を超えています。上限値を超えるすべてのパラメーターは無視します。上限値を変更するには Connector 要素の maxParameterCount 属性を設定してください。
parameters.maxCountFail.fallToDebug=Note: 今後同様のエラーが発生した場合ログを DEBUG レベルで出力します。
parameters.multipleDecodingFail=文字のデコードに失敗しました。 合計[{0}]個の障害が検出されましたが、最初のものだけが記録されました。 このロガーがすべての失敗を記録するためにはデバッグレベルのロギングを有効にします。
parameters.noequal=[{2}]の値で[{0}]の位置から[{1}]の位置で終了するパラメータには、 ''=''文字が続いていませんでした。
rfc6265CookieProcessor.invalidCharInValue=無効な文字[{0}]がCookie値に存在します。
rfc6265CookieProcessor.invalidDomain=cookie に不正なドメイン [{0}] が指定されました。
rfc6265CookieProcessor.invalidPath=Cookie の path 属性に不正な値 [{0}] が指定されました。

View File

@@ -0,0 +1,41 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
cookies.fallToDebug=\n\
\ 비고: 쿠키 오류들이 더 발생하는 경우 DEBUG 레벨 로그로 기록될 것입니다.
cookies.invalidCookieToken=쿠키들: 유효하지 않은 쿠키입니다. 유효한 토큰 또는 인용부호로 처리된 값이 아닙니다.
cookies.invalidSameSiteCookies=알 수 없는 설정 값: [{0}]. 반드시 다음 중 하나여야 합니다: none, lax, strict. 기본 값은 none입니다.
cookies.invalidSpecial=쿠키들: 알 수 없는 특별한 쿠키
cookies.maxCountFail=허용된 최대 쿠키 개수 [{0}]을(를) 초과한 쿠키들이 탐지되었습니다.
headers.maxCountFail=최대 허용 헤더 개수 [{0}]보다 더 많은 헤더들이 탐지되었습니다.
parameters.bytes=입력 [{0}]을(를) 사용하여 처리를 시작합니다.
parameters.copyFail=디버그 로그를 위한 원래의 파라미터 값들을 복사하지 못했습니다.
parameters.decodeFail.debug=문자 디코딩 실패. 값 [{1}](으)로 설정된 파라미터 [{0}]은(는) 무시됩니다.
parameters.decodeFail.info=문자 디코딩이 실패했습니다. 값 [{1}]을(를) 가진 파라미터 [{0}]은(는) 무시되었습니다. 주의: 여기서 인용된 이름과 값은 디코딩 실패로 인해 데이터가 손상되었을 수 있습니다. 손상되지 않은 원본 데이터를 보시려면, 로그 레벨을 디버그 레벨로 하십시오.
parameters.emptyChunk=빈 파라미터 chunk는 무시됩니다.
parameters.fallToDebug=\n\
비고: 파라미터 오류들이 더 발생하는 경우 DEBUG 레벨 로그로 기록될 것입니다.
parameters.invalidChunk=[{0}] 바이트에서 시작하고 [{1}] 바이트에서 끝나며 값이 [{2}]인, 유효하지 않은 chunk는 무시됩니다.
parameters.maxCountFail=단일 요청 ([{0}])에 허용되는 최대 요청 파라미터들의 개수 보다 더 많은 파라미터들이 탐지되었습니다. 이 한계값을 초과하는 파라미터들은 무시되었습니다. 이 한계값을 변경하기 위해서는 Connector의 maxParameterCount 속성을 설정하십시오.
parameters.maxCountFail.fallToDebug=\n\
비고: 이 오류가 더 발생하는 경우 DEBUG 레벨 로그로 기록될 것입니다.
parameters.multipleDecodingFail=문자 디코딩이 실패했습니다. 전체 [{0}]개의 실패가 탐지되었지만, 오직 첫번째 실패만 로그에 기록되었습니다. 모든 실패들을 로그에 남기려면 로그 레벨을 디버그 레벨로 설정하십시오.
parameters.noequal=위치 [{0}]에서 시작하고 위치 [{1}]에서 끝나며 값이 [{2}]인 파라미터 다음에, ''='' 문자가 뒤따르지 않습니다.
rfc6265CookieProcessor.invalidCharInValue=쿠키 값에 유효하지 않은 문자 [{0}]이(가) 있습니다.
rfc6265CookieProcessor.invalidDomain=이 쿠키를 위해, 유효하지 않은 도메인 [{0}]이(가) 지정되었습니다.
rfc6265CookieProcessor.invalidPath=이 쿠키를 위해 유효하지 않은 경로가 설정되었습니다: [{0}]

View File

@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
parameters.copyFail=Невозможно создать копию оригинальных значений параметров для логгирования

View File

@@ -0,0 +1,31 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
cookies.invalidCookieToken=Cookiecookie无效。值不是令牌或引用值
cookies.invalidSpecial=Cookies未知特殊的Cookie
cookies.maxCountFail=检测到超过Cookie最大允许的数量[{0}]
headers.maxCountFail=检测到超过了允许设置的最大header 数[{0}]
parameters.bytes=开始处理输入[{0}]
parameters.copyFail=无法创建以调试日志记录为目的的原始参数值的副本
parameters.decodeFail.debug=字符解码失败.参数 [{0}]和值 [{1}]被忽略
parameters.emptyChunk=忽略空参数块
parameters.fallToDebug=更多的参数错误将以DEBUG级别日志进行记录。
parameters.maxCountFail=检测到单个请求([{0}]的最大请求参数数GET加POST。 超出此限制的任何参数都被忽略。 要更改此限制请在Connector上设置maxParameterCount属性。
parameters.maxCountFail.fallToDebug=注意:更多的错误信息只在debug级别日志中记录
parameters.noequal=):参数从位置[{0}]开始,到位置[{1}]结束,值为[{2}],后面没有“=”字符
rfc6265CookieProcessor.invalidPath=这个cookie被指定了一个无效的路径 [{0}]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
public class RequestUtil {
private RequestUtil() {
// Hide default constructor as this is a utility class
}
/**
* Normalize a relative URI path that may have relative values ("/./",
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
* useful only for normalizing application-generated paths. It does not
* try to perform security checks for malicious input.
*
* @param path Relative path to be normalized
*
* @return The normalized path or <code>null</code> if the path cannot be
* normalized
*/
public static String normalize(String path) {
return normalize(path, true);
}
/**
* Normalize a relative URI path that may have relative values ("/./",
* "/../", and so on ) it it. <strong>WARNING</strong> - This method is
* useful only for normalizing application-generated paths. It does not
* try to perform security checks for malicious input.
*
* @param path Relative path to be normalized
* @param replaceBackSlash Should '\\' be replaced with '/'
*
* @return The normalized path or <code>null</code> if the path cannot be
* normalized
*/
public static String normalize(String path, boolean replaceBackSlash) {
if (path == null) {
return null;
}
// Create a place for the normalized path
String normalized = path;
if (replaceBackSlash && normalized.indexOf('\\') >= 0)
normalized = normalized.replace('\\', '/');
// Add a leading "/" if necessary
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
boolean addedTrailingSlash = false;
if (normalized.endsWith("/.") || normalized.endsWith("/..")) {
normalized = normalized + "/";
addedTrailingSlash = true;
}
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0) {
break;
}
normalized = normalized.substring(0, index) + normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0) {
break;
}
normalized = normalized.substring(0, index) + normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0) {
break;
}
if (index == 0) {
return null; // Trying to go outside our context
}
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
}
if (normalized.length() > 1 && addedTrailingSlash) {
// Remove the trailing '/' we added to that input and output are
// consistent w.r.t. to the presence of the trailing '/'.
normalized = normalized.substring(0, normalized.length() - 1);
}
// Return the normalized path that we have completed
return normalized;
}
public static boolean isSameOrigin(HttpServletRequest request, String origin) {
// Build scheme://host:port from request
StringBuilder target = new StringBuilder();
String scheme = request.getScheme();
if (scheme == null) {
return false;
} else {
scheme = scheme.toLowerCase(Locale.ENGLISH);
}
target.append(scheme);
target.append("://");
String host = request.getServerName();
if (host == null) {
return false;
}
target.append(host);
int port = request.getServerPort();
// Origin may or may not include the (default) port.
// At this point target doesn't include a port.
if (target.length() == origin.length()) {
// origin and target can only be equal if both are using default
// ports. Therefore only append the port to the target if a
// non-default port is used.
if (("http".equals(scheme) || "ws".equals(scheme)) && port != 80 ||
("https".equals(scheme) || "wss".equals(scheme)) && port != 443) {
target.append(':');
target.append(port);
}
} else {
// origin and target can only be equal if:
// a) origin includes an explicit default port
// b) origin is using a non-default port
// Either way, add the port to the target so it can be compared
target.append(':');
target.append(port);
}
// Both scheme and host are case-insensitive but the CORS spec states
// this check should be case-sensitive
return origin.equals(target.toString());
}
/**
* Checks if a given origin is valid or not. Criteria:
* <ul>
* <li>If an encoded character is present in origin, it's not valid.</li>
* <li>If origin is "null", it's valid.</li>
* <li>Origin should be a valid {@link URI}</li>
* </ul>
*
* @param origin The origin URI
* @return <code>true</code> if the origin was valid
* @see <a href="http://tools.ietf.org/html/rfc952">RFC952</a>
*/
public static boolean isValidOrigin(String origin) {
// Checks for encoded characters. Helps prevent CRLF injection.
if (origin.contains("%")) {
return false;
}
// "null" is a valid origin
if ("null".equals(origin)) {
return true;
}
// RFC6454, section 4. "If uri-scheme is file, the implementation MAY
// return an implementation-defined value.". No limits are placed on
// that value so treat all file URIs as valid origins.
if (origin.startsWith("file://")) {
return true;
}
URI originURI;
try {
originURI = new URI(origin);
} catch (URISyntaxException e) {
return false;
}
// If scheme for URI is null, return false. Return true otherwise.
return originURI.getScheme() != null;
}
}

View File

@@ -0,0 +1,170 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.parser.TokenList;
public class ResponseUtil {
private static final String VARY_HEADER = "vary";
private static final String VARY_ALL = "*";
private ResponseUtil() {
// Utility class. Hide default constructor.
}
public static void addVaryFieldName(MimeHeaders headers, String name) {
addVaryFieldName(new HeaderAdapter(headers), name);
}
public static void addVaryFieldName(HttpServletResponse response, String name) {
addVaryFieldName(new ResponseAdapter(response), name);
}
private static void addVaryFieldName(Adapter adapter, String name) {
Collection<String> varyHeaders = adapter.getHeaders(VARY_HEADER);
// Short-cut if only * has been set
if (varyHeaders.size() == 1 && varyHeaders.iterator().next().trim().equals(VARY_ALL)) {
// No need to add an additional field
return;
}
// Short-cut if no headers have been set
if (varyHeaders.size() == 0) {
adapter.addHeader(VARY_HEADER, name);
return;
}
// Short-cut if "*" is added
if (VARY_ALL.equals(name.trim())) {
adapter.setHeader(VARY_HEADER, VARY_ALL);
return;
}
// May be dealing with an application set header, or multiple headers.
// Header names overlap so can't use String.contains(). Have to parse
// the existing values, check if the new value is already present and
// then add it if not. The good news is field names are tokens which
// makes parsing simpler.
Set<String> fieldNames = new HashSet<>();
for (String varyHeader : varyHeaders) {
StringReader input = new StringReader(varyHeader);
try {
TokenList.parseTokenList(input, fieldNames);
} catch (IOException ioe) {
// Should never happen
}
}
if (fieldNames.contains(VARY_ALL)) {
// '*' has been added without removing other values. Optimise.
adapter.setHeader(VARY_HEADER, VARY_ALL);
return;
}
// Build single header to replace current multiple headers
// Replace existing header(s) to ensure any invalid values are removed
fieldNames.add(name);
StringBuilder varyHeader = new StringBuilder();
varyHeader.append(name);
for (String fieldName : fieldNames) {
varyHeader.append(',');
varyHeader.append(fieldName);
}
adapter.setHeader(VARY_HEADER, varyHeader.toString());
}
private static interface Adapter {
Collection<String> getHeaders(String name);
void setHeader(String name, String value);
void addHeader(String name, String value);
}
private static final class HeaderAdapter implements Adapter {
private final MimeHeaders headers;
public HeaderAdapter(MimeHeaders headers) {
this.headers = headers;
}
@Override
public Collection<String> getHeaders(String name) {
Enumeration<String> values = headers.values(name);
List<String> result = new ArrayList<>();
while (values.hasMoreElements()) {
result.add(values.nextElement());
}
return result;
}
@Override
public void setHeader(String name, String value) {
headers.setValue(name).setString(value);
}
@Override
public void addHeader(String name, String value) {
headers.addValue(name).setString(value);
}
}
private static final class ResponseAdapter implements Adapter {
private final HttpServletResponse response;
public ResponseAdapter(HttpServletResponse response) {
this.response = response;
}
@Override
public Collection<String> getHeaders(String name) {
return response.getHeaders(name);
}
@Override
public void setHeader(String name, String value) {
response.setHeader(name, value);
}
@Override
public void addHeader(String name, String value) {
response.addHeader(name, value);
}
}
}

View File

@@ -0,0 +1,239 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.FieldPosition;
import java.util.BitSet;
import java.util.Date;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.http.parser.Cookie;
import org.apache.tomcat.util.res.StringManager;
public class Rfc6265CookieProcessor extends CookieProcessorBase {
private static final Log log = LogFactory.getLog(Rfc6265CookieProcessor.class);
private static final StringManager sm =
StringManager.getManager(Rfc6265CookieProcessor.class.getPackage().getName());
private static final BitSet domainValid = new BitSet(128);
static {
for (char c = '0'; c <= '9'; c++) {
domainValid.set(c);
}
for (char c = 'a'; c <= 'z'; c++) {
domainValid.set(c);
}
for (char c = 'A'; c <= 'Z'; c++) {
domainValid.set(c);
}
domainValid.set('.');
domainValid.set('-');
}
@Override
public Charset getCharset() {
return StandardCharsets.UTF_8;
}
@Override
public void parseCookieHeader(MimeHeaders headers,
ServerCookies serverCookies) {
if (headers == null) {
// nothing to process
return;
}
// process each "cookie" header
int pos = headers.findHeader("Cookie", 0);
while (pos >= 0) {
MessageBytes cookieValue = headers.getValue(pos);
if (cookieValue != null && !cookieValue.isNull() ) {
if (cookieValue.getType() != MessageBytes.T_BYTES ) {
if (log.isDebugEnabled()) {
Exception e = new Exception();
// TODO: Review this in light of HTTP/2
log.debug("Cookies: Parsing cookie as String. Expected bytes.", e);
}
cookieValue.toBytes();
}
if (log.isDebugEnabled()) {
log.debug("Cookies: Parsing b[]: " + cookieValue.toString());
}
ByteChunk bc = cookieValue.getByteChunk();
Cookie.parseCookie(bc.getBytes(), bc.getOffset(), bc.getLength(),
serverCookies);
}
// search from the next position
pos = headers.findHeader("Cookie", ++pos);
}
}
@Override
public String generateHeader(javax.servlet.http.Cookie cookie) {
// Can't use StringBuilder due to DateFormat
StringBuffer header = new StringBuffer();
// TODO: Name validation takes place in Cookie and cannot be configured
// per Context. Moving it to here would allow per Context config
// but delay validation until the header is generated. However,
// the spec requires an IllegalArgumentException on Cookie
// generation.
header.append(cookie.getName());
header.append('=');
String value = cookie.getValue();
if (value != null && value.length() > 0) {
validateCookieValue(value);
header.append(value);
}
// RFC 6265 prefers Max-Age to Expires but... (see below)
int maxAge = cookie.getMaxAge();
if (maxAge > -1) {
// Negative Max-Age is equivalent to no Max-Age
header.append("; Max-Age=");
header.append(maxAge);
// Microsoft IE and Microsoft Edge don't understand Max-Age so send
// expires as well. Without this, persistent cookies fail with those
// browsers. See http://tomcat.markmail.org/thread/g6sipbofsjossacn
// Wdy, DD-Mon-YY HH:MM:SS GMT ( Expires Netscape format )
header.append ("; Expires=");
// To expire immediately we need to set the time in past
if (maxAge == 0) {
header.append(ANCIENT_DATE);
} else {
COOKIE_DATE_FORMAT.get().format(
new Date(System.currentTimeMillis() + maxAge * 1000L),
header,
new FieldPosition(0));
}
}
String domain = cookie.getDomain();
if (domain != null && domain.length() > 0) {
validateDomain(domain);
header.append("; Domain=");
header.append(domain);
}
String path = cookie.getPath();
if (path != null && path.length() > 0) {
validatePath(path);
header.append("; Path=");
header.append(path);
}
if (cookie.getSecure()) {
header.append("; Secure");
}
if (cookie.isHttpOnly()) {
header.append("; HttpOnly");
}
SameSiteCookies sameSiteCookiesValue = getSameSiteCookies();
if (!sameSiteCookiesValue.equals(SameSiteCookies.UNSET)) {
header.append("; SameSite=");
header.append(sameSiteCookiesValue.getValue());
}
return header.toString();
}
private void validateCookieValue(String value) {
int start = 0;
int end = value.length();
if (end > 1 && value.charAt(0) == '"' && value.charAt(end - 1) == '"') {
start = 1;
end--;
}
char[] chars = value.toCharArray();
for (int i = start; i < end; i++) {
char c = chars[i];
if (c < 0x21 || c == 0x22 || c == 0x2c || c == 0x3b || c == 0x5c || c == 0x7f) {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidCharInValue", Integer.toString(c)));
}
}
}
private void validateDomain(String domain) {
int i = 0;
int prev = -1;
int cur = -1;
char[] chars = domain.toCharArray();
while (i < chars.length) {
prev = cur;
cur = chars[i];
if (!domainValid.get(cur)) {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
// labels must start with a letter or number
if ((prev == '.' || prev == -1) && (cur == '.' || cur == '-')) {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
// labels must end with a letter or number
if (prev == '-' && cur == '.') {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
i++;
}
// domain must end with a label
if (cur == '.' || cur == '-') {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
}
private void validatePath(String path) {
char[] chars = path.toCharArray();
for (int i = 0; i < chars.length; i++) {
char ch = chars[i];
if (ch < 0x20 || ch > 0x7E || ch == ';') {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidPath", path));
}
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import org.apache.tomcat.util.res.StringManager;
public enum SameSiteCookies {
/**
* Don't set the SameSite cookie attribute.
*/
UNSET("Unset"),
/**
* Cookie is always sent in cross-site requests.
*/
NONE("None"),
/**
* Cookie is only sent on same-site requests and cross-site top level navigation GET requests
*/
LAX("Lax"),
/**
* Prevents the cookie from being sent by the browser in all cross-site requests
*/
STRICT("Strict");
private static final StringManager sm = StringManager.getManager(SameSiteCookies.class);
private final String value;
SameSiteCookies(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static SameSiteCookies fromString(String value) {
for (SameSiteCookies sameSiteCookies : SameSiteCookies.values()) {
if (sameSiteCookies.getValue().equalsIgnoreCase(value)) {
return sameSiteCookies;
}
}
throw new IllegalStateException(sm.getString("cookies.invalidSameSiteCookies", value));
}
}

View File

@@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import java.io.Serializable;
import org.apache.tomcat.util.buf.MessageBytes;
/**
* Server-side cookie representation.
* Allows recycling and uses MessageBytes as low-level
* representation ( and thus the byte -&gt; char conversion can be delayed
* until we know the charset ).
*
* Tomcat.core uses this recyclable object to represent cookies,
* and the facade will convert it to the external representation.
*/
public class ServerCookie implements Serializable {
private static final long serialVersionUID = 1L;
// Version 0 (Netscape) attributes
private final MessageBytes name=MessageBytes.newInstance();
private final MessageBytes value=MessageBytes.newInstance();
// Expires - Not stored explicitly. Generated from Max-Age (see V1)
private final MessageBytes path=MessageBytes.newInstance();
private final MessageBytes domain=MessageBytes.newInstance();
// Version 1 (RFC2109) attributes
private final MessageBytes comment=MessageBytes.newInstance();
private int version = 0;
// Note: Servlet Spec =< 3.0 only refers to Netscape and RFC2109, not RFC2965
// Version 2 (RFC2965) attributes that would need to be added to support v2 cookies
// CommentURL
// Discard - implied by maxAge <0
// Port
public ServerCookie() {
// NOOP
}
public void recycle() {
name.recycle();
value.recycle();
comment.recycle();
path.recycle();
domain.recycle();
version=0;
}
public MessageBytes getComment() {
return comment;
}
public MessageBytes getDomain() {
return domain;
}
public MessageBytes getPath() {
return path;
}
public MessageBytes getName() {
return name;
}
public MessageBytes getValue() {
return value;
}
public int getVersion() {
return version;
}
public void setVersion(int v) {
version = v;
}
// -------------------- utils --------------------
@Override
public String toString() {
return "Cookie " + getName() + "=" + getValue() + " ; "
+ getVersion() + " " + getPath() + " " + getDomain();
}
}

View File

@@ -0,0 +1,95 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http;
import org.apache.tomcat.util.res.StringManager;
/**
* This class is not thread-safe.
*/
public class ServerCookies {
private static final StringManager sm = StringManager.getManager(ServerCookies.class);
private ServerCookie[] serverCookies;
private int cookieCount = 0;
private int limit = 200;
public ServerCookies(int initialSize) {
serverCookies = new ServerCookie[initialSize];
}
/**
* Register a new, initialized cookie. Cookies are recycled, and most of the
* time an existing ServerCookie object is returned. The caller can set the
* name/value and attributes for the cookie.
* @return the new cookie
*/
public ServerCookie addCookie() {
if (limit > -1 && cookieCount >= limit) {
throw new IllegalArgumentException(
sm.getString("cookies.maxCountFail", Integer.valueOf(limit)));
}
if (cookieCount >= serverCookies.length) {
int newSize = limit > -1 ? Math.min(2*cookieCount, limit) : 2*cookieCount;
ServerCookie scookiesTmp[] = new ServerCookie[newSize];
System.arraycopy(serverCookies, 0, scookiesTmp, 0, cookieCount);
serverCookies = scookiesTmp;
}
ServerCookie c = serverCookies[cookieCount];
if (c == null) {
c = new ServerCookie();
serverCookies[cookieCount] = c;
}
cookieCount++;
return c;
}
public ServerCookie getCookie(int idx) {
return serverCookies[idx];
}
public int getCookieCount() {
return cookieCount;
}
public void setLimit(int limit) {
this.limit = limit;
if (limit > -1 && serverCookies.length > limit && cookieCount <= limit) {
// shrink cookie list array
ServerCookie scookiesTmp[] = new ServerCookie[limit];
System.arraycopy(serverCookies, 0, scookiesTmp, 0, cookieCount);
serverCookies = scookiesTmp;
}
}
public void recycle() {
for (int i = 0; i < cookieCount; i++) {
serverCookies[i].recycle();
}
cookieCount = 0;
}
}

View File

@@ -0,0 +1,256 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* This class implements an output stream in which the data is
* written into a byte array. The buffer automatically grows as data
* is written to it.
* <p>
* The data can be retrieved using <code>toByteArray()</code> and
* <code>toString()</code>.
* <p>
* Closing a {@code ByteArrayOutputStream} has no effect. The methods in
* this class can be called after the stream has been closed without
* generating an {@code IOException}.
* <p>
* This is an alternative implementation of the {@link java.io.ByteArrayOutputStream}
* class. The original implementation only allocates 32 bytes at the beginning.
* As this class is designed for heavy duty it starts at 1024 bytes. In contrast
* to the original it doesn't reallocate the whole memory block but allocates
* additional buffers. This way no buffers need to be garbage collected and
* the contents don't have to be copied to the new buffer. This class is
* designed to behave exactly like the original. The only exception is the
* deprecated toString(int) method that has been ignored.
*/
public class ByteArrayOutputStream extends OutputStream {
static final int DEFAULT_SIZE = 1024;
/** A singleton empty byte array. */
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/** The list of buffers, which grows and never reduces. */
private final List<byte[]> buffers = new ArrayList<>();
/** The index of the current buffer. */
private int currentBufferIndex;
/** The total count of bytes in all the filled buffers. */
private int filledBufferSum;
/** The current buffer. */
private byte[] currentBuffer;
/** The total count of bytes written. */
private int count;
/**
* Creates a new byte array output stream. The buffer capacity is
* initially 1024 bytes, though its size increases if necessary.
*/
public ByteArrayOutputStream() {
this(DEFAULT_SIZE);
}
/**
* Creates a new byte array output stream, with a buffer capacity of
* the specified size, in bytes.
*
* @param size the initial size
* @throws IllegalArgumentException if size is negative
*/
public ByteArrayOutputStream(final int size) {
if (size < 0) {
throw new IllegalArgumentException(
"Negative initial size: " + size);
}
synchronized (this) {
needNewBuffer(size);
}
}
/**
* Makes a new buffer available either by allocating
* a new one or re-cycling an existing one.
*
* @param newcount the size of the buffer if one is created
*/
private void needNewBuffer(final int newcount) {
if (currentBufferIndex < buffers.size() - 1) {
//Recycling old buffer
filledBufferSum += currentBuffer.length;
currentBufferIndex++;
currentBuffer = buffers.get(currentBufferIndex);
} else {
//Creating new buffer
int newBufferSize;
if (currentBuffer == null) {
newBufferSize = newcount;
filledBufferSum = 0;
} else {
newBufferSize = Math.max(
currentBuffer.length << 1,
newcount - filledBufferSum);
filledBufferSum += currentBuffer.length;
}
currentBufferIndex++;
currentBuffer = new byte[newBufferSize];
buffers.add(currentBuffer);
}
}
/**
* Write the bytes to byte array.
* @param b the bytes to write
* @param off The start offset
* @param len The number of bytes to write
*/
@Override
public void write(final byte[] b, final int off, final int len) {
if ((off < 0)
|| (off > b.length)
|| (len < 0)
|| ((off + len) > b.length)
|| ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
synchronized (this) {
final int newcount = count + len;
int remaining = len;
int inBufferPos = count - filledBufferSum;
while (remaining > 0) {
final int part = Math.min(remaining, currentBuffer.length - inBufferPos);
System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
remaining -= part;
if (remaining > 0) {
needNewBuffer(newcount);
inBufferPos = 0;
}
}
count = newcount;
}
}
/**
* Write a byte to byte array.
* @param b the byte to write
*/
@Override
public synchronized void write(final int b) {
int inBufferPos = count - filledBufferSum;
if (inBufferPos == currentBuffer.length) {
needNewBuffer(count + 1);
inBufferPos = 0;
}
currentBuffer[inBufferPos] = (byte) b;
count++;
}
/**
* Writes the entire contents of the specified input stream to this
* byte stream. Bytes from the input stream are read directly into the
* internal buffers of this streams.
*
* @param in the input stream to read from
* @return total number of bytes read from the input stream
* (and written to this stream)
* @throws IOException if an I/O error occurs while reading the input stream
* @since 1.4
*/
public synchronized int write(final InputStream in) throws IOException {
int readCount = 0;
int inBufferPos = count - filledBufferSum;
int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
while (n != -1) {
readCount += n;
inBufferPos += n;
count += n;
if (inBufferPos == currentBuffer.length) {
needNewBuffer(currentBuffer.length);
inBufferPos = 0;
}
n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
}
return readCount;
}
/**
* Closing a {@code ByteArrayOutputStream} has no effect. The methods in
* this class can be called after the stream has been closed without
* generating an {@code IOException}.
*
* @throws IOException never (this method should not declare this exception
* but it has to now due to backwards compatibility)
*/
@Override
public void close() throws IOException {
//nop
}
/**
* Writes the entire contents of this byte stream to the
* specified output stream.
*
* @param out the output stream to write to
* @throws IOException if an I/O error occurs, such as if the stream is closed
* @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
*/
public synchronized void writeTo(final OutputStream out) throws IOException {
int remaining = count;
for (final byte[] buf : buffers) {
final int c = Math.min(buf.length, remaining);
out.write(buf, 0, c);
remaining -= c;
if (remaining == 0) {
break;
}
}
}
/**
* Gets the current contents of this byte stream as a byte array.
* The result is independent of this stream.
*
* @return the current contents of this output stream, as a byte array
* @see java.io.ByteArrayOutputStream#toByteArray()
*/
public synchronized byte[] toByteArray() {
int remaining = count;
if (remaining == 0) {
return EMPTY_BYTE_ARRAY;
}
final byte newbuf[] = new byte[remaining];
int pos = 0;
for (final byte[] buf : buffers) {
final int c = Math.min(buf.length, remaining);
System.arraycopy(buf, 0, newbuf, pos, c);
pos += c;
remaining -= c;
if (remaining == 0) {
break;
}
}
return newbuf;
}
}

View File

@@ -0,0 +1,227 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* An output stream which will retain data in memory until a specified
* threshold is reached, and only then commit it to disk. If the stream is
* closed before the threshold is reached, the data will not be written to
* disk at all.
* <p>
* This class originated in FileUpload processing. In this use case, you do
* not know in advance the size of the file being uploaded. If the file is small
* you want to store it in memory (for speed), but if the file is large you want
* to store it to file (to avoid memory issues).
*/
public class DeferredFileOutputStream
extends ThresholdingOutputStream
{
// ----------------------------------------------------------- Data members
/**
* The output stream to which data will be written prior to the threshold
* being reached.
*/
private ByteArrayOutputStream memoryOutputStream;
/**
* The output stream to which data will be written at any given time. This
* will always be one of <code>memoryOutputStream</code> or
* <code>diskOutputStream</code>.
*/
private OutputStream currentOutputStream;
/**
* The file to which output will be directed if the threshold is exceeded.
*/
private File outputFile;
/**
* The temporary file prefix.
*/
private final String prefix;
/**
* The temporary file suffix.
*/
private final String suffix;
/**
* The directory to use for temporary files.
*/
private final File directory;
// ----------------------------------------------------------- Constructors
/**
* Constructs an instance of this class which will trigger an event at the
* specified threshold, and save data to a file beyond that point.
* The initial buffer size will default to 1024 bytes which is ByteArrayOutputStream's default buffer size.
*
* @param threshold The number of bytes at which to trigger an event.
* @param outputFile The file to which data is saved beyond the threshold.
*/
public DeferredFileOutputStream(final int threshold, final File outputFile)
{
this(threshold, outputFile, null, null, null, ByteArrayOutputStream.DEFAULT_SIZE);
}
/**
* Constructs an instance of this class which will trigger an event at the
* specified threshold, and save data either to a file beyond that point.
*
* @param threshold The number of bytes at which to trigger an event.
* @param outputFile The file to which data is saved beyond the threshold.
* @param prefix Prefix to use for the temporary file.
* @param suffix Suffix to use for the temporary file.
* @param directory Temporary file directory.
* @param initialBufferSize The initial size of the in memory buffer.
*/
private DeferredFileOutputStream(final int threshold, final File outputFile, final String prefix,
final String suffix, final File directory, final int initialBufferSize) {
super(threshold);
this.outputFile = outputFile;
this.prefix = prefix;
this.suffix = suffix;
this.directory = directory;
memoryOutputStream = new ByteArrayOutputStream(initialBufferSize);
currentOutputStream = memoryOutputStream;
}
// --------------------------------------- ThresholdingOutputStream methods
/**
* Returns the current output stream. This may be memory based or disk
* based, depending on the current state with respect to the threshold.
*
* @return The underlying output stream.
*
* @throws IOException if an error occurs.
*/
@Override
protected OutputStream getStream() throws IOException
{
return currentOutputStream;
}
/**
* Switches the underlying output stream from a memory based stream to one
* that is backed by disk. This is the point at which we realise that too
* much data is being written to keep in memory, so we elect to switch to
* disk-based storage.
*
* @throws IOException if an error occurs.
*/
@Override
protected void thresholdReached() throws IOException
{
if (prefix != null) {
outputFile = File.createTempFile(prefix, suffix, directory);
}
FileUtils.forceMkdirParent(outputFile);
final FileOutputStream fos = new FileOutputStream(outputFile);
try {
memoryOutputStream.writeTo(fos);
} catch (final IOException e){
fos.close();
throw e;
}
currentOutputStream = fos;
memoryOutputStream = null;
}
// --------------------------------------------------------- Public methods
/**
* Determines whether or not the data for this output stream has been
* retained in memory.
*
* @return {@code true} if the data is available in memory;
* {@code false} otherwise.
*/
public boolean isInMemory()
{
return !isThresholdExceeded();
}
/**
* Returns the data for this output stream as an array of bytes, assuming
* that the data has been retained in memory. If the data was written to
* disk, this method returns {@code null}.
*
* @return The data for this output stream, or {@code null} if no such
* data is available.
*/
public byte[] getData()
{
if (memoryOutputStream != null)
{
return memoryOutputStream.toByteArray();
}
return null;
}
/**
* Returns either the output file specified in the constructor or
* the temporary file created or null.
* <p>
* If the constructor specifying the file is used then it returns that
* same output file, even when threshold has not been reached.
* <p>
* If constructor specifying a temporary file prefix/suffix is used
* then the temporary file created once the threshold is reached is returned
* If the threshold was not reached then {@code null} is returned.
*
* @return The file for this output stream, or {@code null} if no such
* file exists.
*/
public File getFile()
{
return outputFile;
}
/**
* Closes underlying output stream, and mark this as closed
*
* @throws IOException if an error occurs.
*/
@Override
public void close() throws IOException
{
super.close();
}
}

View File

@@ -0,0 +1,204 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
/**
* <p> This class represents a file or form item that was received within a
* <code>multipart/form-data</code> POST request.
*
* <p> After retrieving an instance of this class from a {@link
* org.apache.tomcat.util.http.fileupload.FileUpload FileUpload} instance (see
* {@link org.apache.tomcat.util.http.fileupload.FileUpload
* #parseRequest(RequestContext)}), you may
* either request all contents of the file at once using {@link #get()} or
* request an {@link java.io.InputStream InputStream} with
* {@link #getInputStream()} and process the file without attempting to load
* it into memory, which may come handy with large files.
*
* <p> While this interface does not extend
* <code>javax.activation.DataSource</code> per se (to avoid a seldom used
* dependency), several of the defined methods are specifically defined with
* the same signatures as methods in that interface. This allows an
* implementation of this interface to also implement
* <code>javax.activation.DataSource</code> with minimal additional work.
*
* @since 1.3 additionally implements FileItemHeadersSupport
*/
public interface FileItem extends FileItemHeadersSupport {
// ------------------------------- Methods from javax.activation.DataSource
/**
* Returns an {@link java.io.InputStream InputStream} that can be
* used to retrieve the contents of the file.
*
* @return An {@link java.io.InputStream InputStream} that can be
* used to retrieve the contents of the file.
*
* @throws IOException if an error occurs.
*/
InputStream getInputStream() throws IOException;
/**
* Returns the content type passed by the browser or <code>null</code> if
* not defined.
*
* @return The content type passed by the browser or <code>null</code> if
* not defined.
*/
String getContentType();
/**
* Returns the original file name in the client's file system, as provided by
* the browser (or other client software). In most cases, this will be the
* base file name, without path information. However, some clients, such as
* the Opera browser, do include path information.
*
* @return The original file name in the client's file system.
* @throws InvalidFileNameException The file name contains a NUL character,
* which might be an indicator of a security attack. If you intend to
* use the file name anyways, catch the exception and use
* InvalidFileNameException#getName().
*/
String getName();
// ------------------------------------------------------- FileItem methods
/**
* Provides a hint as to whether or not the file contents will be read
* from memory.
*
* @return <code>true</code> if the file contents will be read from memory;
* <code>false</code> otherwise.
*/
boolean isInMemory();
/**
* Returns the size of the file item.
*
* @return The size of the file item, in bytes.
*/
long getSize();
/**
* Returns the contents of the file item as an array of bytes.
*
* @return The contents of the file item as an array of bytes.
*/
byte[] get();
/**
* Returns the contents of the file item as a String, using the specified
* encoding. This method uses {@link #get()} to retrieve the
* contents of the item.
*
* @param encoding The character encoding to use.
*
* @return The contents of the item, as a string.
*
* @throws UnsupportedEncodingException if the requested character
* encoding is not available.
*/
String getString(String encoding) throws UnsupportedEncodingException;
/**
* Returns the contents of the file item as a String, using the default
* character encoding. This method uses {@link #get()} to retrieve the
* contents of the item.
*
* @return The contents of the item, as a string.
*/
String getString();
/**
* A convenience method to write an uploaded item to disk. The client code
* is not concerned with whether or not the item is stored in memory, or on
* disk in a temporary location. They just want to write the uploaded item
* to a file.
* <p>
* This method is not guaranteed to succeed if called more than once for
* the same item. This allows a particular implementation to use, for
* example, file renaming, where possible, rather than copying all of the
* underlying data, thus gaining a significant performance benefit.
*
* @param file The <code>File</code> into which the uploaded item should
* be stored.
*
* @throws Exception if an error occurs.
*/
void write(File file) throws Exception;
/**
* Deletes the underlying storage for a file item, including deleting any
* associated temporary disk file. Although this storage will be deleted
* automatically when the <code>FileItem</code> instance is garbage
* collected, this method can be used to ensure that this is done at an
* earlier time, thus preserving system resources.
*/
void delete();
/**
* Returns the name of the field in the multipart form corresponding to
* this file item.
*
* @return The name of the form field.
*/
String getFieldName();
/**
* Sets the field name used to reference this file item.
*
* @param name The name of the form field.
*/
void setFieldName(String name);
/**
* Determines whether or not a <code>FileItem</code> instance represents
* a simple form field.
*
* @return <code>true</code> if the instance represents a simple form
* field; <code>false</code> if it represents an uploaded file.
*/
boolean isFormField();
/**
* Specifies whether or not a <code>FileItem</code> instance represents
* a simple form field.
*
* @param state <code>true</code> if the instance represents a simple form
* field; <code>false</code> if it represents an uploaded file.
*/
void setFormField(boolean state);
/**
* Returns an {@link java.io.OutputStream OutputStream} that can
* be used for storing the contents of the file.
*
* @return An {@link java.io.OutputStream OutputStream} that can be used
* for storing the contensts of the file.
*
* @throws IOException if an error occurs.
*/
OutputStream getOutputStream() throws IOException;
}

View File

@@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
/**
* <p>A factory interface for creating {@link FileItem} instances. Factories
* can provide their own custom configuration, over and above that provided
* by the default file upload implementation.</p>
*/
public interface FileItemFactory {
/**
* Create a new {@link FileItem} instance from the supplied parameters and
* any local factory configuration.
*
* @param fieldName The name of the form field.
* @param contentType The content type of the form field.
* @param isFormField <code>true</code> if this is a plain form field;
* <code>false</code> otherwise.
* @param fileName The name of the uploaded file, if any, as supplied
* by the browser or other client.
*
* @return The newly created file item.
*/
FileItem createItem(
String fieldName,
String contentType,
boolean isFormField,
String fileName
);
}

View File

@@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.util.Iterator;
/**
* <p> This class provides support for accessing the headers for a file or form
* item that was received within a <code>multipart/form-data</code> POST
* request.</p>
*
* @since 1.2.1
*/
public interface FileItemHeaders {
/**
* Returns the value of the specified part header as a <code>String</code>.
*
* If the part did not include a header of the specified name, this method
* return <code>null</code>. If there are multiple headers with the same
* name, this method returns the first header in the item. The header
* name is case insensitive.
*
* @param name a <code>String</code> specifying the header name
* @return a <code>String</code> containing the value of the requested
* header, or <code>null</code> if the item does not have a header
* of that name
*/
String getHeader(String name);
/**
* <p>
* Returns all the values of the specified item header as an
* <code>Iterator</code> of <code>String</code> objects.
* </p>
* <p>
* If the item did not include any headers of the specified name, this
* method returns an empty <code>Iterator</code>. The header name is
* case insensitive.
* </p>
*
* @param name a <code>String</code> specifying the header name
* @return an <code>Iterator</code> containing the values of the
* requested header. If the item does not have any headers of
* that name, return an empty <code>Iterator</code>
*/
Iterator<String> getHeaders(String name);
/**
* <p>
* Returns an <code>Iterator</code> of all the header names.
* </p>
*
* @return an <code>Iterator</code> containing all of the names of
* headers provided with this file item. If the item does not have
* any headers return an empty <code>Iterator</code>
*/
Iterator<String> getHeaderNames();
}

View File

@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
/**
* Interface that will indicate that {@link FileItem} or {@link FileItemStream}
* implementations will accept the headers read for the item.
*
* @since 1.2.1
*
* @see FileItem
* @see FileItemStream
*/
public interface FileItemHeadersSupport {
/**
* Returns the collection of headers defined locally within this item.
*
* @return the {@link FileItemHeaders} present for this item.
*/
FileItemHeaders getHeaders();
/**
* Sets the headers read from within an item. Implementations of
* {@link FileItem} or {@link FileItemStream} should implement this
* interface to be able to get the raw headers found within the item
* header block.
*
* @param headers the instance that holds onto the headers
* for this instance.
*/
void setHeaders(FileItemHeaders headers);
}

View File

@@ -0,0 +1,97 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.IOException;
import java.util.List;
import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
/**
* An iterator, as returned by
* {@link FileUploadBase#getItemIterator(RequestContext)}.
*/
public interface FileItemIterator {
/** Returns the maximum size of a single file. An {@link FileSizeLimitExceededException}
* will be thrown, if there is an uploaded file, which is exceeding this value.
* By default, this value will be copied from the {@link FileUploadBase#getFileSizeMax()
* FileUploadBase} object, however, the user may replace the default value with a
* request specific value by invoking {@link #setFileSizeMax(long)} on this object.
* @return The maximum size of a single, uploaded file. The value -1 indicates "unlimited".
*/
public long getFileSizeMax();
/** Sets the maximum size of a single file. An {@link FileSizeLimitExceededException}
* will be thrown, if there is an uploaded file, which is exceeding this value.
* By default, this value will be copied from the {@link FileUploadBase#getFileSizeMax()
* FileUploadBase} object, however, the user may replace the default value with a
* request specific value by invoking {@link #setFileSizeMax(long)} on this object, so
* there is no need to configure it here.
* <em>Note:</em>Changing this value doesn't affect files, that have already been uploaded.
* @param pFileSizeMax The maximum size of a single, uploaded file. The value -1 indicates "unlimited".
*/
public void setFileSizeMax(long pFileSizeMax);
/** Returns the maximum size of the complete HTTP request. A {@link SizeLimitExceededException}
* will be thrown, if the HTTP request will exceed this value.
* By default, this value will be copied from the {@link FileUploadBase#getSizeMax()
* FileUploadBase} object, however, the user may replace the default value with a
* request specific value by invoking {@link #setSizeMax(long)} on this object.
* @return The maximum size of the complete HTTP requqest. The value -1 indicates "unlimited".
*/
public long getSizeMax();
/** Returns the maximum size of the complete HTTP request. A {@link SizeLimitExceededException}
* will be thrown, if the HTTP request will exceed this value.
* By default, this value will be copied from the {@link FileUploadBase#getSizeMax()
* FileUploadBase} object, however, the user may replace the default value with a
* request specific value by invoking {@link #setSizeMax(long)} on this object.
* <em>Note:</em> Setting the maximum size on this object will work only, if the iterator is not
* yet initialized. In other words: If the methods {@link #hasNext()}, {@link #next()} have not
* yet been invoked.
* @param pSizeMax The maximum size of the complete HTTP request. The value -1 indicates "unlimited".
*/
public void setSizeMax(long pSizeMax);
/**
* Returns, whether another instance of {@link FileItemStream}
* is available.
*
* @throws FileUploadException Parsing or processing the
* file item failed.
* @throws IOException Reading the file item failed.
* @return True, if one or more additional file items
* are available, otherwise false.
*/
public boolean hasNext() throws FileUploadException, IOException;
/**
* Returns the next available {@link FileItemStream}.
*
* @throws java.util.NoSuchElementException No more items are available. Use
* {@link #hasNext()} to prevent this exception.
* @throws FileUploadException Parsing or processing the
* file item failed.
* @throws IOException Reading the file item failed.
* @return FileItemStream instance, which provides
* access to the next file item.
*/
public FileItemStream next() throws FileUploadException, IOException;
public List<FileItem> getFileItems() throws FileUploadException, IOException;
}

View File

@@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.IOException;
import java.io.InputStream;
/**
* <p> This interface provides access to a file or form item that was
* received within a <code>multipart/form-data</code> POST request.
* The items contents are retrieved by calling {@link #openStream()}.</p>
* <p>Instances of this class are created by accessing the
* iterator, returned by
* {@link FileUploadBase#getItemIterator(RequestContext)}.</p>
* <p><em>Note</em>: There is an interaction between the iterator and
* its associated instances of {@link FileItemStream}: By invoking
* {@link java.util.Iterator#hasNext()} on the iterator, you discard all data,
* which hasn't been read so far from the previous data.</p>
*/
public interface FileItemStream extends FileItemHeadersSupport {
/**
* This exception is thrown, if an attempt is made to read
* data from the {@link InputStream}, which has been returned
* by {@link FileItemStream#openStream()}, after
* {@link java.util.Iterator#hasNext()} has been invoked on the
* iterator, which created the {@link FileItemStream}.
*/
public static class ItemSkippedException extends IOException {
/**
* The exceptions serial version UID, which is being used
* when serializing an exception instance.
*/
private static final long serialVersionUID = -7280778431581963740L;
}
/**
* Creates an {@link InputStream}, which allows to read the
* items contents.
*
* @return The input stream, from which the items data may
* be read.
* @throws IllegalStateException The method was already invoked on
* this item. It is not possible to recreate the data stream.
* @throws IOException An I/O error occurred.
* @see ItemSkippedException
*/
InputStream openStream() throws IOException;
/**
* Returns the content type passed by the browser or <code>null</code> if
* not defined.
*
* @return The content type passed by the browser or <code>null</code> if
* not defined.
*/
String getContentType();
/**
* Returns the original file name in the client's file system, as provided by
* the browser (or other client software). In most cases, this will be the
* base file name, without path information. However, some clients, such as
* the Opera browser, do include path information.
*
* @return The original file name in the client's file system.
*/
String getName();
/**
* Returns the name of the field in the multipart form corresponding to
* this file item.
*
* @return The name of the form field.
*/
String getFieldName();
/**
* Determines whether or not a <code>FileItem</code> instance represents
* a simple form field.
*
* @return <code>true</code> if the instance represents a simple form
* field; <code>false</code> if it represents an uploaded file.
*/
boolean isFormField();
}

View File

@@ -0,0 +1,92 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
/**
* <p>High level API for processing file uploads.</p>
*
* <p>This class handles multiple files per single HTML widget, sent using
* <code>multipart/mixed</code> encoding type, as specified by
* <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
* #parseRequest(RequestContext)} to acquire a list
* of {@link org.apache.tomcat.util.http.fileupload.FileItem FileItems} associated
* with a given HTML widget.</p>
*
* <p>How the data for individual parts is stored is determined by the factory
* used to create them; a given part may be in memory, on disk, or somewhere
* else.</p>
*/
public class FileUpload
extends FileUploadBase {
// ----------------------------------------------------------- Data members
/**
* The factory to use to create new form items.
*/
private FileItemFactory fileItemFactory;
// ----------------------------------------------------------- Constructors
/**
* Constructs an uninitialised instance of this class.
*
* A factory must be
* configured, using <code>setFileItemFactory()</code>, before attempting
* to parse requests.
*
* @see #FileUpload(FileItemFactory)
*/
public FileUpload() {
super();
}
/**
* Constructs an instance of this class which uses the supplied factory to
* create <code>FileItem</code> instances.
*
* @see #FileUpload()
* @param fileItemFactory The factory to use for creating file items.
*/
public FileUpload(FileItemFactory fileItemFactory) {
super();
this.fileItemFactory = fileItemFactory;
}
// ----------------------------------------------------- Property accessors
/**
* Returns the factory class used when creating file items.
*
* @return The factory class for new file items.
*/
@Override
public FileItemFactory getFileItemFactory() {
return fileItemFactory;
}
/**
* Sets the factory class to use when creating file items.
*
* @param factory The factory class for new file items.
*/
@Override
public void setFileItemFactory(FileItemFactory factory) {
this.fileItemFactory = factory;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
/**
* Exception for errors encountered while processing the request.
*/
public class FileUploadException extends Exception {
private static final long serialVersionUID = -4222909057964038517L;
public FileUploadException() {
super();
}
public FileUploadException(String message, Throwable cause) {
super(message, cause);
}
public FileUploadException(String message) {
super(message);
}
public FileUploadException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,309 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* General file manipulation utilities.
* <p>
* Facilities are provided in the following areas:
* <ul>
* <li>writing to a file
* <li>reading from a file
* <li>make a directory including parent directories
* <li>copying files and directories
* <li>deleting files and directories
* <li>converting to and from a URL
* <li>listing files and directories by filter and extension
* <li>comparing file content
* <li>file last changed date
* <li>calculating a checksum
* </ul>
* <p>
* Note that a specific charset should be specified whenever possible.
* Relying on the platform default means that the code is Locale-dependent.
* Only use the default if the files are known to always use the platform default.
* <p>
* Origin of code: Excalibur, Alexandria, Commons-Utils
*/
public class FileUtils {
/**
* Instances should NOT be constructed in standard programming.
*/
public FileUtils() {
super();
}
//-----------------------------------------------------------------------
/**
* Deletes a directory recursively.
*
* @param directory directory to delete
* @throws IOException in case deletion is unsuccessful
* @throws IllegalArgumentException if {@code directory} does not exist or is not a directory
*/
public static void deleteDirectory(final File directory) throws IOException {
if (!directory.exists()) {
return;
}
if (!isSymlink(directory)) {
cleanDirectory(directory);
}
if (!directory.delete()) {
final String message =
"Unable to delete directory " + directory + ".";
throw new IOException(message);
}
}
/**
* Cleans a directory without deleting it.
*
* @param directory directory to clean
* @throws IOException in case cleaning is unsuccessful
* @throws IllegalArgumentException if {@code directory} does not exist or is not a directory
*/
public static void cleanDirectory(final File directory) throws IOException {
if (!directory.exists()) {
final String message = directory + " does not exist";
throw new IllegalArgumentException(message);
}
if (!directory.isDirectory()) {
final String message = directory + " is not a directory";
throw new IllegalArgumentException(message);
}
final File[] files = directory.listFiles();
if (files == null) { // null if security restricted
throw new IOException("Failed to list contents of " + directory);
}
IOException exception = null;
for (File file : files) {
try {
forceDelete(file);
} catch (IOException ioe) {
exception = ioe;
}
}
if (null != exception) {
throw exception;
}
}
//-----------------------------------------------------------------------
/**
* Deletes a file. If file is a directory, delete it and all sub-directories.
* <p>
* The difference between File.delete() and this method are:
* <ul>
* <li>A directory to be deleted does not have to be empty.</li>
* <li>You get exceptions when a file or directory cannot be deleted.
* (java.io.File methods returns a boolean)</li>
* </ul>
*
* @param file file or directory to delete, must not be {@code null}
* @throws NullPointerException if the directory is {@code null}
* @throws FileNotFoundException if the file was not found
* @throws IOException in case deletion is unsuccessful
*/
public static void forceDelete(final File file) throws IOException {
if (file.isDirectory()) {
deleteDirectory(file);
} else {
final boolean filePresent = file.exists();
if (!file.delete()) {
if (!filePresent) {
throw new FileNotFoundException("File does not exist: " + file);
}
final String message =
"Unable to delete file: " + file;
throw new IOException(message);
}
}
}
/**
* Schedules a file to be deleted when JVM exits.
* If file is directory delete it and all sub-directories.
*
* @param file file or directory to delete, must not be {@code null}
* @throws NullPointerException if the file is {@code null}
* @throws IOException in case deletion is unsuccessful
*/
public static void forceDeleteOnExit(final File file) throws IOException {
if (file.isDirectory()) {
deleteDirectoryOnExit(file);
} else {
file.deleteOnExit();
}
}
/**
* Schedules a directory recursively for deletion on JVM exit.
*
* @param directory directory to delete, must not be {@code null}
* @throws NullPointerException if the directory is {@code null}
* @throws IOException in case deletion is unsuccessful
*/
private static void deleteDirectoryOnExit(final File directory) throws IOException {
if (!directory.exists()) {
return;
}
directory.deleteOnExit();
if (!isSymlink(directory)) {
cleanDirectoryOnExit(directory);
}
}
/**
* Cleans a directory without deleting it.
*
* @param directory directory to clean, must not be {@code null}
* @throws NullPointerException if the directory is {@code null}
* @throws IOException in case cleaning is unsuccessful
*/
private static void cleanDirectoryOnExit(final File directory) throws IOException {
if (!directory.exists()) {
String message = directory + " does not exist";
throw new IllegalArgumentException(message);
}
if (!directory.isDirectory()) {
String message = directory + " is not a directory";
throw new IllegalArgumentException(message);
}
File[] files = directory.listFiles();
if (files == null) { // null if security restricted
throw new IOException("Failed to list contents of " + directory);
}
IOException exception = null;
for (File file : files) {
try {
forceDeleteOnExit(file);
} catch (IOException ioe) {
exception = ioe;
}
}
if (null != exception) {
throw exception;
}
}
/**
* Makes a directory, including any necessary but nonexistent parent
* directories. If a file already exists with specified name but it is
* not a directory then an IOException is thrown.
* If the directory cannot be created (or does not already exist)
* then an IOException is thrown.
*
* @param directory directory to create, must not be {@code null}
* @throws NullPointerException if the directory is {@code null}
* @throws IOException if the directory cannot be created or the file already exists but is not a directory
*/
public static void forceMkdir(final File directory) throws IOException {
if (directory.exists()) {
if (!directory.isDirectory()) {
final String message =
"File "
+ directory
+ " exists and is "
+ "not a directory. Unable to create directory.";
throw new IOException(message);
}
} else {
if (!directory.mkdirs()) {
// Double-check that some other thread or process hasn't made
// the directory in the background
if (!directory.isDirectory()) {
final String message =
"Unable to create directory " + directory;
throw new IOException(message);
}
}
}
}
/**
* Makes any necessary but nonexistent parent directories for a given File. If the parent directory cannot be
* created then an IOException is thrown.
*
* @param file file with parent to create, must not be {@code null}
* @throws NullPointerException if the file is {@code null}
* @throws IOException if the parent directory cannot be created
* @since 2.5
*/
public static void forceMkdirParent(final File file) throws IOException {
final File parent = file.getParentFile();
if (parent == null) {
return;
}
forceMkdir(parent);
}
/**
* Determines whether the specified file is a Symbolic Link rather than an actual file.
* <p>
* Will not return true if there is a Symbolic Link anywhere in the path,
* only if the specific file is.
* <p>
* <b>Note:</b> the current implementation always returns {@code false} if
* the system is detected as Windows using
* {@link File#separatorChar} == '\\'
*
* @param file the file to check
* @return true if the file is a Symbolic Link
* @throws IOException if an IO error occurs while checking the file
* @since 2.0
*/
public static boolean isSymlink(File file) throws IOException {
if (file == null) {
throw new NullPointerException("File must not be null");
}
//FilenameUtils.isSystemWindows()
if (File.separatorChar == '\\') {
return false;
}
File fileInCanonicalDir = null;
if (file.getParent() == null) {
fileInCanonicalDir = file;
} else {
File canonicalDir = file.getParentFile().getCanonicalFile();
fileInCanonicalDir = new File(canonicalDir, file.getName());
}
if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) {
return false;
} else {
return true;
}
}
}

View File

@@ -0,0 +1,249 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* General IO stream manipulation utilities.
* <p>
* This class provides static utility methods for input/output operations.
* <ul>
* <li>closeQuietly - these methods close a stream ignoring nulls and exceptions
* <li>toXxx/read - these methods read data from a stream
* <li>write - these methods write data to a stream
* <li>copy - these methods copy all the data from one stream to another
* <li>contentEquals - these methods compare the content of two streams
* </ul>
* <p>
* The byte-to-char methods and char-to-byte methods involve a conversion step.
* Two methods are provided in each case, one that uses the platform default
* encoding and the other which allows you to specify an encoding. You are
* encouraged to always specify an encoding because relying on the platform
* default can lead to unexpected results, for example when moving from
* development to production.
* <p>
* All the methods in this class that read a stream are buffered internally.
* This means that there is no cause to use a <code>BufferedInputStream</code>
* or <code>BufferedReader</code>. The default buffer size of 4K has been shown
* to be efficient in tests.
* <p>
* Wherever possible, the methods in this class do <em>not</em> flush or close
* the stream. This is to avoid making non-portable assumptions about the
* streams' origin and further use. Thus the caller is still responsible for
* closing streams after use.
* <p>
* Origin of code: Excalibur.
*/
public class IOUtils {
// NOTE: This class is focused on InputStream, OutputStream, Reader and
// Writer. Each method should take at least one of these as a parameter,
// or return one of them.
/**
* Represents the end-of-file (or stream).
* @since 2.5 (made public)
*/
public static final int EOF = -1;
/**
* The default buffer size ({@value}) to use for
* {@link #copyLarge(InputStream, OutputStream)}.
*/
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
/**
* Closes a <code>Closeable</code> unconditionally.
* <p>
* Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is typically used in
* finally blocks.
* <p>
* Example code:
* </p>
* <pre>
* Closeable closeable = null;
* try {
* closeable = new FileReader(&quot;foo.txt&quot;);
* // process closeable
* closeable.close();
* } catch (Exception e) {
* // error handling
* } finally {
* IOUtils.closeQuietly(closeable);
* }
* </pre>
* <p>
* Closing all streams:
* </p>
* <pre>
* try {
* return IOUtils.copy(inputStream, outputStream);
* } finally {
* IOUtils.closeQuietly(inputStream);
* IOUtils.closeQuietly(outputStream);
* }
* </pre>
*
* @param closeable the objects to close, may be null or already closed
* @since 2.0
*/
public static void closeQuietly(final Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (final IOException ioe) {
// ignore
}
}
// copy from InputStream
//-----------------------------------------------------------------------
/**
* Copies bytes from an <code>InputStream</code> to an
* <code>OutputStream</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* Large streams (over 2GB) will return a bytes copied value of
* <code>-1</code> after the copy has completed since the correct
* number of bytes cannot be returned as an int. For large streams
* use the <code>copyLarge(InputStream, OutputStream)</code> method.
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> to write to
* @return the number of bytes copied, or -1 if &gt; Integer.MAX_VALUE
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 1.1
*/
public static int copy(final InputStream input, final OutputStream output) throws IOException {
final long count = copyLarge(input, output);
if (count > Integer.MAX_VALUE) {
return -1;
}
return (int) count;
}
/**
* Copies bytes from a large (over 2GB) <code>InputStream</code> to an
* <code>OutputStream</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> to write to
* @return the number of bytes copied
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since 1.3
*/
public static long copyLarge(final InputStream input, final OutputStream output)
throws IOException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
long count = 0;
int n = 0;
while (EOF != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
return count;
}
/**
* Reads bytes from an input stream.
* This implementation guarantees that it will read as many bytes
* as possible before giving up; this may not always be the case for
* subclasses of {@link InputStream}.
*
* @param input where to read input from
* @param buffer destination
* @param offset initial offset into buffer
* @param length length to read, must be &gt;= 0
* @return actual length read; may be less than requested if EOF was reached
* @throws IOException if a read error occurs
* @since 2.2
*/
public static int read(final InputStream input, final byte[] buffer, final int offset, final int length)
throws IOException {
if (length < 0) {
throw new IllegalArgumentException("Length must not be negative: " + length);
}
int remaining = length;
while (remaining > 0) {
final int location = length - remaining;
final int count = input.read(buffer, offset + location, remaining);
if (EOF == count) { // EOF
break;
}
remaining -= count;
}
return length - remaining;
}
/**
* Reads the requested number of bytes or fail if there are not enough left.
* <p>
* This allows for the possibility that {@link InputStream#read(byte[], int, int)} may
* not read as many bytes as requested (most likely because of reaching EOF).
*
* @param input where to read input from
* @param buffer destination
* @param offset initial offset into buffer
* @param length length to read, must be &gt;= 0
*
* @throws IOException if there is a problem reading the file
* @throws IllegalArgumentException if length is negative
* @throws EOFException if the number of bytes read was incorrect
* @since 2.2
*/
public static void readFully(final InputStream input, final byte[] buffer, final int offset, final int length)
throws IOException {
final int actual = read(input, buffer, offset, length);
if (actual != length) {
throw new EOFException("Length to read: " + length + " actual: " + actual);
}
}
/**
* Reads the requested number of bytes or fail if there are not enough left.
* <p>
* This allows for the possibility that {@link InputStream#read(byte[], int, int)} may
* not read as many bytes as requested (most likely because of reaching EOF).
*
* @param input where to read input from
* @param buffer destination
*
* @throws IOException if there is a problem reading the file
* @throws IllegalArgumentException if length is negative
* @throws EOFException if the number of bytes read was incorrect
* @since 2.2
*/
public static void readFully(final InputStream input, final byte[] buffer) throws IOException {
readFully(input, buffer, 0, buffer.length);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
/**
* This exception is thrown in case of an invalid file name.
* A file name is invalid, if it contains a NUL character.
* Attackers might use this to circumvent security checks:
* For example, a malicious user might upload a file with the name
* "foo.exe\0.png". This file name might pass security checks (i.e.
* checks for the extension ".png"), while, depending on the underlying
* C library, it might create a file named "foo.exe", as the NUL
* character is the string terminator in C.
*/
public class InvalidFileNameException extends RuntimeException {
/**
* Serial version UID, being used, if the exception
* is serialized.
*/
private static final long serialVersionUID = 7922042602454350470L;
/**
* The file name causing the exception.
*/
private final String name;
/**
* Creates a new instance.
*
* @param pName The file name causing the exception.
* @param pMessage A human readable error message.
*/
public InvalidFileNameException(String pName, String pMessage) {
super(pMessage);
name = pName;
}
/**
* Returns the invalid file name.
*
* @return the invalid file name.
*/
public String getName() {
return name;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,339 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.tomcat.util.http.fileupload.util.mime.MimeUtility;
/**
* A simple parser intended to parse sequences of name/value pairs.
*
* Parameter values are expected to be enclosed in quotes if they
* contain unsafe characters, such as '=' characters or separators.
* Parameter values are optional and can be omitted.
*
* <p>
* <code>param1 = value; param2 = "anything goes; really"; param3</code>
* </p>
*/
public class ParameterParser {
/**
* String to be parsed.
*/
private char[] chars = null;
/**
* Current position in the string.
*/
private int pos = 0;
/**
* Maximum position in the string.
*/
private int len = 0;
/**
* Start of a token.
*/
private int i1 = 0;
/**
* End of a token.
*/
private int i2 = 0;
/**
* Whether names stored in the map should be converted to lower case.
*/
private boolean lowerCaseNames = false;
/**
* Default ParameterParser constructor.
*/
public ParameterParser() {
super();
}
/**
* Are there any characters left to parse?
*
* @return {@code true} if there are unparsed characters,
* {@code false} otherwise.
*/
private boolean hasChar() {
return this.pos < this.len;
}
/**
* A helper method to process the parsed token. This method removes
* leading and trailing blanks as well as enclosing quotation marks,
* when necessary.
*
* @param quoted {@code true} if quotation marks are expected,
* {@code false} otherwise.
* @return the token
*/
private String getToken(boolean quoted) {
// Trim leading white spaces
while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
i1++;
}
// Trim trailing white spaces
while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
i2--;
}
// Strip away quotation marks if necessary
if (quoted
&& ((i2 - i1) >= 2)
&& (chars[i1] == '"')
&& (chars[i2 - 1] == '"')) {
i1++;
i2--;
}
String result = null;
if (i2 > i1) {
result = new String(chars, i1, i2 - i1);
}
return result;
}
/**
* Tests if the given character is present in the array of characters.
*
* @param ch the character to test for presence in the array of characters
* @param charray the array of characters to test against
*
* @return {@code true} if the character is present in the array of
* characters, {@code false} otherwise.
*/
private boolean isOneOf(char ch, final char[] charray) {
boolean result = false;
for (char element : charray) {
if (ch == element) {
result = true;
break;
}
}
return result;
}
/**
* Parses out a token until any of the given terminators
* is encountered.
*
* @param terminators the array of terminating characters. Any of these
* characters when encountered signify the end of the token
*
* @return the token
*/
private String parseToken(final char[] terminators) {
char ch;
i1 = pos;
i2 = pos;
while (hasChar()) {
ch = chars[pos];
if (isOneOf(ch, terminators)) {
break;
}
i2++;
pos++;
}
return getToken(false);
}
/**
* Parses out a token until any of the given terminators
* is encountered outside the quotation marks.
*
* @param terminators the array of terminating characters. Any of these
* characters when encountered outside the quotation marks signify the end
* of the token
*
* @return the token
*/
private String parseQuotedToken(final char[] terminators) {
char ch;
i1 = pos;
i2 = pos;
boolean quoted = false;
boolean charEscaped = false;
while (hasChar()) {
ch = chars[pos];
if (!quoted && isOneOf(ch, terminators)) {
break;
}
if (!charEscaped && ch == '"') {
quoted = !quoted;
}
charEscaped = (!charEscaped && ch == '\\');
i2++;
pos++;
}
return getToken(true);
}
/**
* Returns {@code true} if parameter names are to be converted to lower
* case when name/value pairs are parsed.
*
* @return {@code true} if parameter names are to be
* converted to lower case when name/value pairs are parsed.
* Otherwise returns {@code false}
*/
public boolean isLowerCaseNames() {
return this.lowerCaseNames;
}
/**
* Sets the flag if parameter names are to be converted to lower case when
* name/value pairs are parsed.
*
* @param b {@code true} if parameter names are to be
* converted to lower case when name/value pairs are parsed.
* {@code false} otherwise.
*/
public void setLowerCaseNames(boolean b) {
this.lowerCaseNames = b;
}
/**
* Extracts a map of name/value pairs from the given string. Names are
* expected to be unique. Multiple separators may be specified and
* the earliest found in the input string is used.
*
* @param str the string that contains a sequence of name/value pairs
* @param separators the name/value pairs separators
*
* @return a map of name/value pairs
*/
public Map<String, String> parse(final String str, char[] separators) {
if (separators == null || separators.length == 0) {
return new HashMap<>();
}
char separator = separators[0];
if (str != null) {
int idx = str.length();
for (char separator2 : separators) {
int tmp = str.indexOf(separator2);
if (tmp != -1 && tmp < idx) {
idx = tmp;
separator = separator2;
}
}
}
return parse(str, separator);
}
/**
* Extracts a map of name/value pairs from the given string. Names are
* expected to be unique.
*
* @param str the string that contains a sequence of name/value pairs
* @param separator the name/value pairs separator
*
* @return a map of name/value pairs
*/
public Map<String, String> parse(final String str, char separator) {
if (str == null) {
return new HashMap<>();
}
return parse(str.toCharArray(), separator);
}
/**
* Extracts a map of name/value pairs from the given array of
* characters. Names are expected to be unique.
*
* @param charArray the array of characters that contains a sequence of
* name/value pairs
* @param separator the name/value pairs separator
*
* @return a map of name/value pairs
*/
public Map<String, String> parse(final char[] charArray, char separator) {
if (charArray == null) {
return new HashMap<>();
}
return parse(charArray, 0, charArray.length, separator);
}
/**
* Extracts a map of name/value pairs from the given array of
* characters. Names are expected to be unique.
*
* @param charArray the array of characters that contains a sequence of
* name/value pairs
* @param offset - the initial offset.
* @param length - the length.
* @param separator the name/value pairs separator
*
* @return a map of name/value pairs
*/
public Map<String, String> parse(
final char[] charArray,
int offset,
int length,
char separator) {
if (charArray == null) {
return new HashMap<>();
}
HashMap<String, String> params = new HashMap<>();
this.chars = charArray;
this.pos = offset;
this.len = length;
String paramName = null;
String paramValue = null;
while (hasChar()) {
paramName = parseToken(new char[] {
'=', separator });
paramValue = null;
if (hasChar() && (charArray[pos] == '=')) {
pos++; // skip '='
paramValue = parseQuotedToken(new char[] {
separator });
if (paramValue != null) {
try {
paramValue = MimeUtility.decodeText(paramValue);
} catch (UnsupportedEncodingException e) {
// let's keep the original value in this case
}
}
}
if (hasChar() && (charArray[pos] == separator)) {
pos++; // skip separator
}
if ((paramName != null) && (paramName.length() > 0)) {
if (this.lowerCaseNames) {
paramName = paramName.toLowerCase(Locale.ENGLISH);
}
params.put(paramName, paramValue);
}
}
return params;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
/**
* The {@link ProgressListener} may be used to display a progress bar
* or do stuff like that.
*/
public interface ProgressListener {
/**
* Updates the listeners status information.
*
* @param pBytesRead The total number of bytes, which have been read
* so far.
* @param pContentLength The total number of bytes, which are being
* read. May be -1, if this number is unknown.
* @param pItems The number of the field, which is currently being
* read. (0 = no item so far, 1 = first item is being read, ...)
*/
void update(long pBytesRead, long pContentLength, int pItems);
}

View File

@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.IOException;
import java.io.InputStream;
/**
* <p>Abstracts access to the request information needed for file uploads. This
* interface should be implemented for each type of request that may be
* handled by FileUpload, such as servlets and portlets.</p>
*
* @since FileUpload 1.1
*/
public interface RequestContext {
/**
* Retrieve the character encoding for the request.
*
* @return The character encoding for the request.
*/
String getCharacterEncoding();
/**
* Retrieve the content type of the request.
*
* @return The content type of the request.
*/
String getContentType();
/**
* Retrieve the input stream for the request.
*
* @return The input stream for the request.
*
* @throws IOException if a problem occurs.
*/
InputStream getInputStream() throws IOException;
}

View File

@@ -0,0 +1,226 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
import java.io.IOException;
import java.io.OutputStream;
/**
* An output stream which triggers an event when a specified number of bytes of
* data have been written to it. The event can be used, for example, to throw
* an exception if a maximum has been reached, or to switch the underlying
* stream type when the threshold is exceeded.
* <p>
* This class overrides all <code>OutputStream</code> methods. However, these
* overrides ultimately call the corresponding methods in the underlying output
* stream implementation.
* <p>
* NOTE: This implementation may trigger the event <em>before</em> the threshold
* is actually reached, since it triggers when a pending write operation would
* cause the threshold to be exceeded.
*/
public abstract class ThresholdingOutputStream
extends OutputStream
{
// ----------------------------------------------------------- Data members
/**
* The threshold at which the event will be triggered.
*/
private final int threshold;
/**
* The number of bytes written to the output stream.
*/
private long written;
/**
* Whether or not the configured threshold has been exceeded.
*/
private boolean thresholdExceeded;
// ----------------------------------------------------------- Constructors
/**
* Constructs an instance of this class which will trigger an event at the
* specified threshold.
*
* @param threshold The number of bytes at which to trigger an event.
*/
public ThresholdingOutputStream(final int threshold)
{
this.threshold = threshold;
}
// --------------------------------------------------- OutputStream methods
/**
* Writes the specified byte to this output stream.
*
* @param b The byte to be written.
*
* @throws IOException if an error occurs.
*/
@Override
public void write(final int b) throws IOException
{
checkThreshold(1);
getStream().write(b);
written++;
}
/**
* Writes <code>b.length</code> bytes from the specified byte array to this
* output stream.
*
* @param b The array of bytes to be written.
*
* @throws IOException if an error occurs.
*/
@Override
public void write(final byte b[]) throws IOException
{
checkThreshold(b.length);
getStream().write(b);
written += b.length;
}
/**
* Writes <code>len</code> bytes from the specified byte array starting at
* offset <code>off</code> to this output stream.
*
* @param b The byte array from which the data will be written.
* @param off The start offset in the byte array.
* @param len The number of bytes to write.
*
* @throws IOException if an error occurs.
*/
@Override
public void write(final byte b[], final int off, final int len) throws IOException
{
checkThreshold(len);
getStream().write(b, off, len);
written += len;
}
/**
* Flushes this output stream and forces any buffered output bytes to be
* written out.
*
* @throws IOException if an error occurs.
*/
@Override
public void flush() throws IOException
{
getStream().flush();
}
/**
* Closes this output stream and releases any system resources associated
* with this stream.
*
* @throws IOException if an error occurs.
*/
@Override
public void close() throws IOException
{
try
{
flush();
}
catch (final IOException ignored)
{
// ignore
}
getStream().close();
}
// --------------------------------------------------------- Public methods
/**
* Determines whether or not the configured threshold has been exceeded for
* this output stream.
*
* @return {@code true} if the threshold has been reached;
* {@code false} otherwise.
*/
public boolean isThresholdExceeded()
{
return written > threshold;
}
// ------------------------------------------------------ Protected methods
/**
* Checks to see if writing the specified number of bytes would cause the
* configured threshold to be exceeded. If so, triggers an event to allow
* a concrete implementation to take action on this.
*
* @param count The number of bytes about to be written to the underlying
* output stream.
*
* @throws IOException if an error occurs.
*/
protected void checkThreshold(final int count) throws IOException
{
if (!thresholdExceeded && written + count > threshold)
{
thresholdExceeded = true;
thresholdReached();
}
}
// ------------------------------------------------------- Abstract methods
/**
* Returns the underlying output stream, to which the corresponding
* <code>OutputStream</code> methods in this class will ultimately delegate.
*
* @return The underlying output stream.
*
* @throws IOException if an error occurs.
*/
protected abstract OutputStream getStream() throws IOException;
/**
* Indicates that the configured threshold has been reached, and that a
* subclass should take whatever action necessary on this event. This may
* include changing the underlying output stream.
*
* @throws IOException if an error occurs.
*/
protected abstract void thresholdReached() throws IOException;
}

View File

@@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload;
/**
* Enhanced access to the request information needed for file uploads,
* which fixes the Content Length data access in {@link RequestContext}.
*
* The reason of introducing this new interface is just for backward compatibility
* and it might vanish for a refactored 2.x version moving the new method into
* RequestContext again.
*
* @since 1.3
*/
public interface UploadContext extends RequestContext {
/**
* Retrieve the content length of the request.
*
* @return The content length of the request.
* @since 1.3
*/
long contentLength();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,205 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload.disk;
import java.io.File;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.FileItemFactory;
/**
* <p>The default {@link org.apache.tomcat.util.http.fileupload.FileItemFactory}
* implementation. This implementation creates
* {@link org.apache.tomcat.util.http.fileupload.FileItem} instances which keep
* their
* content either in memory, for smaller items, or in a temporary file on disk,
* for larger items. The size threshold, above which content will be stored on
* disk, is configurable, as is the directory in which temporary files will be
* created.</p>
*
* <p>If not otherwise configured, the default configuration values are as
* follows:</p>
* <ul>
* <li>Size threshold is 10KB.</li>
* <li>Repository is the system default temp directory, as returned by
* <code>System.getProperty("java.io.tmpdir")</code>.</li>
* </ul>
* <p>
* <b>NOTE</b>: Files are created in the system default temp directory with
* predictable names. This means that a local attacker with write access to that
* directory can perform a TOUTOC attack to replace any uploaded file with a
* file of the attackers choice. The implications of this will depend on how the
* uploaded file is used but could be significant. When using this
* implementation in an environment with local, untrusted users,
* {@link #setRepository(File)} MUST be used to configure a repository location
* that is not publicly writable. In a Servlet container the location identified
* by the ServletContext attribute <code>javax.servlet.context.tempdir</code>
* may be used.
* </p>
*
* <p>Temporary files, which are created for file items, should be
* deleted later on.</p>
*
* @since FileUpload 1.1
*/
public class DiskFileItemFactory implements FileItemFactory {
// ----------------------------------------------------- Manifest constants
/**
* The default threshold above which uploads will be stored on disk.
*/
public static final int DEFAULT_SIZE_THRESHOLD = 10240;
// ----------------------------------------------------- Instance Variables
/**
* The directory in which uploaded files will be stored, if stored on disk.
*/
private File repository;
/**
* The threshold above which uploads will be stored on disk.
*/
private int sizeThreshold = DEFAULT_SIZE_THRESHOLD;
/**
* Default content charset to be used when no explicit charset
* parameter is provided by the sender.
*/
private String defaultCharset = DiskFileItem.DEFAULT_CHARSET;
// ----------------------------------------------------------- Constructors
/**
* Constructs an unconfigured instance of this class. The resulting factory
* may be configured by calling the appropriate setter methods.
*/
public DiskFileItemFactory() {
this(DEFAULT_SIZE_THRESHOLD, null);
}
/**
* Constructs a preconfigured instance of this class.
*
* @param sizeThreshold The threshold, in bytes, below which items will be
* retained in memory and above which they will be
* stored as a file.
* @param repository The data repository, which is the directory in
* which files will be created, should the item size
* exceed the threshold.
*/
public DiskFileItemFactory(int sizeThreshold, File repository) {
this.sizeThreshold = sizeThreshold;
this.repository = repository;
}
// ------------------------------------------------------------- Properties
/**
* Returns the directory used to temporarily store files that are larger
* than the configured size threshold.
*
* @return The directory in which temporary files will be located.
*
* @see #setRepository(java.io.File)
*
*/
public File getRepository() {
return repository;
}
/**
* Sets the directory used to temporarily store files that are larger
* than the configured size threshold.
*
* @param repository The directory in which temporary files will be located.
*
* @see #getRepository()
*
*/
public void setRepository(File repository) {
this.repository = repository;
}
/**
* Returns the size threshold beyond which files are written directly to
* disk. The default value is 10240 bytes.
*
* @return The size threshold, in bytes.
*
* @see #setSizeThreshold(int)
*/
public int getSizeThreshold() {
return sizeThreshold;
}
/**
* Sets the size threshold beyond which files are written directly to disk.
*
* @param sizeThreshold The size threshold, in bytes.
*
* @see #getSizeThreshold()
*
*/
public void setSizeThreshold(int sizeThreshold) {
this.sizeThreshold = sizeThreshold;
}
// --------------------------------------------------------- Public Methods
/**
* Create a new {@link DiskFileItem}
* instance from the supplied parameters and the local factory
* configuration.
*
* @param fieldName The name of the form field.
* @param contentType The content type of the form field.
* @param isFormField <code>true</code> if this is a plain form field;
* <code>false</code> otherwise.
* @param fileName The name of the uploaded file, if any, as supplied
* by the browser or other client.
*
* @return The newly created file item.
*/
@Override
public FileItem createItem(String fieldName, String contentType,
boolean isFormField, String fileName) {
DiskFileItem result = new DiskFileItem(fieldName, contentType,
isFormField, fileName, sizeThreshold, repository);
result.setDefaultCharset(defaultCharset);
return result;
}
/**
* Returns the default charset for use when no explicit charset
* parameter is provided by the sender.
* @return the default charset
*/
public String getDefaultCharset() {
return defaultCharset;
}
/**
* Sets the default charset for use when no explicit charset
* parameter is provided by the sender.
* @param pCharset the default charset
*/
public void setDefaultCharset(String pCharset) {
defaultCharset = pCharset;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/**
* <p>
* A disk-based implementation of the
* {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem}
* interface. This implementation retains smaller items in memory, while
* writing larger ones to disk. The threshold between these two is
* configurable, as is the location of files that are written to disk.
* </p>
* <p>
* In typical usage, an instance of
* {@link org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory DiskFileItemFactory}
* would be created, configured, and then passed to a
* {@link org.apache.tomcat.util.http.fileupload.FileUpload FileUpload}
* implementation such as
* {@link org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload ServletFileUpload}.
* </p>
* <p>
* The following code fragment demonstrates this usage.
* </p>
* <pre>
* DiskFileItemFactory factory = new DiskFileItemFactory();
* // maximum size that will be stored in memory
* factory.setSizeThreshold(4096);
* // the location for saving data that is larger than getSizeThreshold()
* factory.setRepository(new File("/tmp"));
*
* ServletFileUpload upload = new ServletFileUpload(factory);
* </pre>
* <p>
* Please see the FileUpload
* <a href="https://commons.apache.org/fileupload/using.html" target="_top">User Guide</a>
* for further details and examples of how to use this package.
* </p>
*/
package org.apache.tomcat.util.http.fileupload.disk;

View File

@@ -0,0 +1,339 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.FileItemHeaders;
import org.apache.tomcat.util.http.fileupload.FileItemIterator;
import org.apache.tomcat.util.http.fileupload.FileItemStream;
import org.apache.tomcat.util.http.fileupload.FileUploadBase;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.apache.tomcat.util.http.fileupload.MultipartStream;
import org.apache.tomcat.util.http.fileupload.ProgressListener;
import org.apache.tomcat.util.http.fileupload.RequestContext;
import org.apache.tomcat.util.http.fileupload.UploadContext;
import org.apache.tomcat.util.http.fileupload.util.LimitedInputStream;
/**
* The iterator, which is returned by
* {@link FileUploadBase#getItemIterator(RequestContext)}.
*/
public class FileItemIteratorImpl implements FileItemIterator {
private final FileUploadBase fileUploadBase;
private final RequestContext ctx;
private long sizeMax, fileSizeMax;
@Override
public long getSizeMax() {
return sizeMax;
}
@Override
public void setSizeMax(long sizeMax) {
this.sizeMax = sizeMax;
}
@Override
public long getFileSizeMax() {
return fileSizeMax;
}
@Override
public void setFileSizeMax(long fileSizeMax) {
this.fileSizeMax = fileSizeMax;
}
/**
* The multi part stream to process.
*/
private MultipartStream multiPartStream;
/**
* The notifier, which used for triggering the
* {@link ProgressListener}.
*/
private MultipartStream.ProgressNotifier progressNotifier;
/**
* The boundary, which separates the various parts.
*/
private byte[] multiPartBoundary;
/**
* The item, which we currently process.
*/
private FileItemStreamImpl currentItem;
/**
* The current items field name.
*/
private String currentFieldName;
/**
* Whether we are currently skipping the preamble.
*/
private boolean skipPreamble;
/**
* Whether the current item may still be read.
*/
private boolean itemValid;
/**
* Whether we have seen the end of the file.
*/
private boolean eof;
/**
* Creates a new instance.
*
* @param pFileUploadBase Upload instance
* @param pRequestContext The request context.
* @throws FileUploadException An error occurred while
* parsing the request.
* @throws IOException An I/O error occurred.
*/
public FileItemIteratorImpl(FileUploadBase pFileUploadBase, RequestContext pRequestContext)
throws FileUploadException, IOException {
fileUploadBase = pFileUploadBase;
sizeMax = fileUploadBase.getSizeMax();
fileSizeMax = fileUploadBase.getFileSizeMax();
ctx = pRequestContext;
if (ctx == null) {
throw new NullPointerException("ctx parameter");
}
skipPreamble = true;
findNextItem();
}
protected void init(FileUploadBase fileUploadBase, @SuppressWarnings("unused") RequestContext pRequestContext)
throws FileUploadException, IOException {
String contentType = ctx.getContentType();
if ((null == contentType)
|| (!contentType.toLowerCase(Locale.ENGLISH).startsWith(FileUploadBase.MULTIPART))) {
throw new InvalidContentTypeException(
String.format("the request doesn't contain a %s or %s stream, content type header is %s",
FileUploadBase.MULTIPART_FORM_DATA, FileUploadBase.MULTIPART_MIXED, contentType));
}
final long requestSize = ((UploadContext) ctx).contentLength();
InputStream input; // N.B. this is eventually closed in MultipartStream processing
if (sizeMax >= 0) {
if (requestSize != -1 && requestSize > sizeMax) {
throw new SizeLimitExceededException(
String.format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
Long.valueOf(requestSize), Long.valueOf(sizeMax)),
requestSize, sizeMax);
}
// N.B. this is eventually closed in MultipartStream processing
input = new LimitedInputStream(ctx.getInputStream(), sizeMax) {
@Override
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
FileUploadException ex = new SizeLimitExceededException(
String.format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
Long.valueOf(pCount), Long.valueOf(pSizeMax)),
pCount, pSizeMax);
throw new FileUploadIOException(ex);
}
};
} else {
input = ctx.getInputStream();
}
String charEncoding = fileUploadBase.getHeaderEncoding();
if (charEncoding == null) {
charEncoding = ctx.getCharacterEncoding();
}
multiPartBoundary = fileUploadBase.getBoundary(contentType);
if (multiPartBoundary == null) {
IOUtils.closeQuietly(input); // avoid possible resource leak
throw new FileUploadException("the request was rejected because no multipart boundary was found");
}
progressNotifier = new MultipartStream.ProgressNotifier(fileUploadBase.getProgressListener(), requestSize);
try {
multiPartStream = new MultipartStream(input, multiPartBoundary, progressNotifier);
} catch (IllegalArgumentException iae) {
IOUtils.closeQuietly(input); // avoid possible resource leak
throw new InvalidContentTypeException(
String.format("The boundary specified in the %s header is too long", FileUploadBase.CONTENT_TYPE), iae);
}
multiPartStream.setHeaderEncoding(charEncoding);
}
public MultipartStream getMultiPartStream() throws FileUploadException, IOException {
if (multiPartStream == null) {
init(fileUploadBase, ctx);
}
return multiPartStream;
}
/**
* Called for finding the next item, if any.
*
* @return True, if an next item was found, otherwise false.
* @throws IOException An I/O error occurred.
*/
private boolean findNextItem() throws FileUploadException, IOException {
if (eof) {
return false;
}
if (currentItem != null) {
currentItem.close();
currentItem = null;
}
final MultipartStream multi = getMultiPartStream();
for (;;) {
boolean nextPart;
if (skipPreamble) {
nextPart = multi.skipPreamble();
} else {
nextPart = multi.readBoundary();
}
if (!nextPart) {
if (currentFieldName == null) {
// Outer multipart terminated -> No more data
eof = true;
return false;
}
// Inner multipart terminated -> Return to parsing the outer
multi.setBoundary(multiPartBoundary);
currentFieldName = null;
continue;
}
FileItemHeaders headers = fileUploadBase.getParsedHeaders(multi.readHeaders());
if (currentFieldName == null) {
// We're parsing the outer multipart
String fieldName = fileUploadBase.getFieldName(headers);
if (fieldName != null) {
String subContentType = headers.getHeader(FileUploadBase.CONTENT_TYPE);
if (subContentType != null
&& subContentType.toLowerCase(Locale.ENGLISH)
.startsWith(FileUploadBase.MULTIPART_MIXED)) {
currentFieldName = fieldName;
// Multiple files associated with this field name
byte[] subBoundary = fileUploadBase.getBoundary(subContentType);
multi.setBoundary(subBoundary);
skipPreamble = true;
continue;
}
String fileName = fileUploadBase.getFileName(headers);
currentItem = new FileItemStreamImpl(this, fileName,
fieldName, headers.getHeader(FileUploadBase.CONTENT_TYPE),
fileName == null, getContentLength(headers));
currentItem.setHeaders(headers);
progressNotifier.noteItem();
itemValid = true;
return true;
}
} else {
String fileName = fileUploadBase.getFileName(headers);
if (fileName != null) {
currentItem = new FileItemStreamImpl(this, fileName,
currentFieldName,
headers.getHeader(FileUploadBase.CONTENT_TYPE),
false, getContentLength(headers));
currentItem.setHeaders(headers);
progressNotifier.noteItem();
itemValid = true;
return true;
}
}
multi.discardBodyData();
}
}
private long getContentLength(FileItemHeaders pHeaders) {
try {
return Long.parseLong(pHeaders.getHeader(FileUploadBase.CONTENT_LENGTH));
} catch (Exception e) {
return -1;
}
}
/**
* Returns, whether another instance of {@link FileItemStream}
* is available.
*
* @throws FileUploadException Parsing or processing the
* file item failed.
* @throws IOException Reading the file item failed.
* @return True, if one or more additional file items
* are available, otherwise false.
*/
@Override
public boolean hasNext() throws FileUploadException, IOException {
if (eof) {
return false;
}
if (itemValid) {
return true;
}
try {
return findNextItem();
} catch (FileUploadIOException e) {
// unwrap encapsulated SizeException
throw (FileUploadException) e.getCause();
}
}
/**
* Returns the next available {@link FileItemStream}.
*
* @throws java.util.NoSuchElementException No more items are
* available. Use {@link #hasNext()} to prevent this exception.
* @throws FileUploadException Parsing or processing the
* file item failed.
* @throws IOException Reading the file item failed.
* @return FileItemStream instance, which provides
* access to the next file item.
*/
@Override
public FileItemStream next() throws FileUploadException, IOException {
if (eof || (!itemValid && !hasNext())) {
throw new NoSuchElementException();
}
itemValid = false;
return currentItem;
}
@Override
public List<FileItem> getFileItems() throws FileUploadException, IOException {
final List<FileItem> items = new ArrayList<>();
while (hasNext()) {
final FileItemStream fis = next();
final FileItem fi = fileUploadBase.getFileItemFactory().createItem(fis.getFieldName(), fis.getContentType(), fis.isFormField(), fis.getName());
items.add(fi);
}
return items;
}
}

View File

@@ -0,0 +1,213 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload.impl;
import java.io.IOException;
import java.io.InputStream;
import org.apache.tomcat.util.http.fileupload.FileItemHeaders;
import org.apache.tomcat.util.http.fileupload.FileItemStream;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.apache.tomcat.util.http.fileupload.InvalidFileNameException;
import org.apache.tomcat.util.http.fileupload.MultipartStream.ItemInputStream;
import org.apache.tomcat.util.http.fileupload.util.Closeable;
import org.apache.tomcat.util.http.fileupload.util.LimitedInputStream;
import org.apache.tomcat.util.http.fileupload.util.Streams;
/**
* Default implementation of {@link FileItemStream}.
*/
public class FileItemStreamImpl implements FileItemStream {
private final FileItemIteratorImpl fileItemIteratorImpl;
/**
* The file items content type.
*/
private final String contentType;
/**
* The file items field name.
*/
private final String fieldName;
/**
* The file items file name.
*/
final String name;
/**
* Whether the file item is a form field.
*/
private final boolean formField;
/**
* The file items input stream.
*/
private final InputStream stream;
/**
* The headers, if any.
*/
private FileItemHeaders headers;
/**
* Creates a new instance.
* @param pFileItemIterator Iterator for all files in this upload
* @param pName The items file name, or null.
* @param pFieldName The items field name.
* @param pContentType The items content type, or null.
* @param pFormField Whether the item is a form field.
* @param pContentLength The items content length, if known, or -1
* @throws FileUploadException If an error is encountered processing the request
* @throws IOException Creating the file item failed.
*/
public FileItemStreamImpl(FileItemIteratorImpl pFileItemIterator, String pName, String pFieldName,
String pContentType, boolean pFormField,
long pContentLength) throws FileUploadException, IOException {
fileItemIteratorImpl = pFileItemIterator;
name = pName;
fieldName = pFieldName;
contentType = pContentType;
formField = pFormField;
final long fileSizeMax = fileItemIteratorImpl.getFileSizeMax();
if (fileSizeMax != -1) { // Check if limit is already exceeded
if (pContentLength != -1
&& pContentLength > fileSizeMax) {
FileSizeLimitExceededException e =
new FileSizeLimitExceededException(
String.format("The field %s exceeds its maximum permitted size of %s bytes.",
fieldName, Long.valueOf(fileSizeMax)),
pContentLength, fileSizeMax);
e.setFileName(pName);
e.setFieldName(pFieldName);
throw new FileUploadIOException(e);
}
}
// OK to construct stream now
final ItemInputStream itemStream = fileItemIteratorImpl.getMultiPartStream().newInputStream();
InputStream istream = itemStream;
if (fileSizeMax != -1) {
istream = new LimitedInputStream(istream, fileSizeMax) {
@Override
protected void raiseError(long pSizeMax, long pCount)
throws IOException {
itemStream.close(true);
FileSizeLimitExceededException e =
new FileSizeLimitExceededException(
String.format("The field %s exceeds its maximum permitted size of %s bytes.",
fieldName, Long.valueOf(pSizeMax)),
pCount, pSizeMax);
e.setFieldName(fieldName);
e.setFileName(name);
throw new FileUploadIOException(e);
}
};
}
stream = istream;
}
/**
* Returns the items content type, or null.
*
* @return Content type, if known, or null.
*/
@Override
public String getContentType() {
return contentType;
}
/**
* Returns the items field name.
*
* @return Field name.
*/
@Override
public String getFieldName() {
return fieldName;
}
/**
* Returns the items file name.
*
* @return File name, if known, or null.
* @throws InvalidFileNameException The file name contains a NUL character,
* which might be an indicator of a security attack. If you intend to
* use the file name anyways, catch the exception and use
* InvalidFileNameException#getName().
*/
@Override
public String getName() {
return Streams.checkFileName(name);
}
/**
* Returns, whether this is a form field.
*
* @return True, if the item is a form field,
* otherwise false.
*/
@Override
public boolean isFormField() {
return formField;
}
/**
* Returns an input stream, which may be used to
* read the items contents.
*
* @return Opened input stream.
* @throws IOException An I/O error occurred.
*/
@Override
public InputStream openStream() throws IOException {
if (((Closeable) stream).isClosed()) {
throw new FileItemStream.ItemSkippedException();
}
return stream;
}
/**
* Closes the file item.
*
* @throws IOException An I/O error occurred.
*/
public void close() throws IOException {
stream.close();
}
/**
* Returns the file item headers.
*
* @return The items header object
*/
@Override
public FileItemHeaders getHeaders() {
return headers;
}
/**
* Sets the file item headers.
*
* @param pHeaders The items header object
*/
@Override
public void setHeaders(FileItemHeaders pHeaders) {
headers = pHeaders;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload.impl;
/**
* Thrown to indicate that A files size exceeds the configured maximum.
*/
public class FileSizeLimitExceededException
extends SizeException {
/**
* The exceptions UID, for serializing an instance.
*/
private static final long serialVersionUID = 8150776562029630058L;
/**
* File name of the item, which caused the exception.
*/
private String fileName;
/**
* Field name of the item, which caused the exception.
*/
private String fieldName;
/**
* Constructs a <code>SizeExceededException</code> with
* the specified detail message, and actual and permitted sizes.
*
* @param message The detail message.
* @param actual The actual request size.
* @param permitted The maximum permitted request size.
*/
public FileSizeLimitExceededException(String message, long actual,
long permitted) {
super(message, actual, permitted);
}
/**
* Returns the file name of the item, which caused the
* exception.
*
* @return File name, if known, or null.
*/
public String getFileName() {
return fileName;
}
/**
* Sets the file name of the item, which caused the
* exception.
*
* @param pFileName the file name of the item, which caused the exception.
*/
public void setFileName(String pFileName) {
fileName = pFileName;
}
/**
* Returns the field name of the item, which caused the
* exception.
*
* @return Field name, if known, or null.
*/
public String getFieldName() {
return fieldName;
}
/**
* Sets the field name of the item, which caused the
* exception.
*
* @param pFieldName the field name of the item,
* which caused the exception.
*/
public void setFieldName(String pFieldName) {
fieldName = pFieldName;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tomcat.util.http.fileupload.impl;
import java.io.IOException;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
/**
* This exception is thrown for hiding an inner
* {@link FileUploadException} in an {@link IOException}.
*/
public class FileUploadIOException extends IOException {
/**
* The exceptions UID, for serializing an instance.
*/
private static final long serialVersionUID = -7047616958165584154L;
/**
* The exceptions cause; we overwrite the parent
* classes field, which is available since Java
* 1.4 only.
*/
private final FileUploadException cause;
/**
* Creates a <code>FileUploadIOException</code> with the
* given cause.
*
* @param pCause The exceptions cause, if any, or null.
*/
public FileUploadIOException(FileUploadException pCause) {
// We're not doing super(pCause) cause of 1.3 compatibility.
cause = pCause;
}
/**
* Returns the exceptions cause.
*
* @return The exceptions cause, if any, or null.
*/
@SuppressWarnings("sync-override") // Field is final
@Override
public Throwable getCause() {
return cause;
}
}

Some files were not shown because too many files have changed in this diff Show More