/* * 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.catalina.connector;
/** * Recyclable buffer to hold the redirect URL.
*/ protectedfinal CharChunk redirectURLCC = new CharChunk();
/* * Not strictly required but it makes generating HTTP/2 push requests a lot easier if these are retained until the * response is recycled.
*/ privatefinal List<Cookie> cookies = new ArrayList<>();
/** * @return the number of bytes the application has actually written to the output stream. This excludes chunking, * compression, etc. as well as headers.
*/ publiclong getContentWritten() { return outputBuffer.getContentWritten();
}
/** * @return the number of bytes the actually written to the socket. This includes chunking, compression, etc. but * excludes headers. * * @param flush if <code>true</code> will perform a buffer flush first
*/ publiclong getBytesWritten(boolean flush) { if (flush) { try {
outputBuffer.flush();
} catch (IOException ioe) { // Ignore - the client has probably closed the connection
}
} return getCoyoteResponse().getBytesWritten(flush);
}
/** * Set the application commit flag. * * @param appCommitted The new application committed flag value
*/ publicvoid setAppCommitted(boolean appCommitted) { this.appCommitted = appCommitted;
}
/** * Application commit flag accessor. * * @return <code>true</code> if the application has committed the response
*/ publicboolean isAppCommitted() { returnthis.appCommitted || isCommitted() || isSuspended() ||
((getContentLength() > 0) && (getContentWritten() >= getContentLength()));
}
/** * The request with which this response is associated.
*/ protected Request request = null;
/** * @return the Request with which this Response is associated.
*/ public Request getRequest() { returnthis.request;
}
/** * Set the Request with which this Response is associated. * * @param request The new associated request
*/ publicvoid setRequest(Request request) { this.request = request;
}
/** * The facade associated with this response.
*/ protected ResponseFacade facade = null;
/** * @return the <code>ServletResponse</code> for which this object is the facade.
*/ public HttpServletResponse getResponse() { if (facade == null) {
facade = new ResponseFacade(this);
} if (applicationResponse == null) {
applicationResponse = facade;
} return applicationResponse;
}
/** * Set a wrapped HttpServletResponse to pass to the application. Components wishing to wrap the response should * obtain the response via {@link #getResponse()}, wrap it and then call this method with the wrapped response. * * @param applicationResponse The wrapped response to pass to the application
*/ publicvoid setResponse(HttpServletResponse applicationResponse) { // Check the wrapper wraps this request
ServletResponse r = applicationResponse; while (r instanceof HttpServletResponseWrapper) {
r = ((HttpServletResponseWrapper) r).getResponse();
} if (r != facade) { thrownew IllegalArgumentException(sm.getString("response.illegalWrap"));
} this.applicationResponse = applicationResponse;
}
/** * Set the suspended flag. * * @param suspended The new suspended flag value
*/ publicvoid setSuspended(boolean suspended) {
outputBuffer.setSuspended(suspended);
}
/** * Suspended flag accessor. * * @return <code>true</code> if the response is suspended
*/ publicboolean isSuspended() { return outputBuffer.isSuspended();
}
/** * Closed flag accessor. * * @return <code>true</code> if the response has been closed
*/ publicboolean isClosed() { return outputBuffer.isClosed();
}
/** * Set the error flag. * * @return <code>false</code> if the error flag was already set * * @deprecated This method will be changed to return void in Tomcat 11 onwards
*/
@Deprecated publicboolean setError() { return getCoyoteResponse().setError();
}
/** * Error flag accessor. * * @return <code>true</code> if the response has encountered an error
*/ publicboolean isError() { return getCoyoteResponse().isError();
}
/** * Perform whatever actions are required to flush and close the output stream or writer, in a single operation. * * @exception IOException if an input/output error occurs
*/ publicvoid finishResponse() throws IOException { // Writing leftover bytes
outputBuffer.close();
}
/** * @return the content length that was set or calculated for this Response.
*/ publicint getContentLength() { return getCoyoteResponse().getContentLength();
}
/** * @return the content type that was set or calculated for this response, or <code>null</code> if no content type * was set.
*/
@Override public String getContentType() { return getCoyoteResponse().getContentType();
}
/** * Return a PrintWriter that can be used to render error messages, regardless of whether a stream or writer has * already been acquired. * * @return Writer which can be used for error reports. If the response is not an error report returned using * sendError or triggered by an unexpected exception thrown during the servlet processing (and only in * that case), null will be returned if the response stream has already been used. * * @exception IOException if an input/output error occurs
*/ public PrintWriter getReporter() throws IOException { if (outputBuffer.isNew()) {
outputBuffer.checkConverter(); if (writer == null) {
writer = new CoyoteWriter(outputBuffer);
} return writer;
} else { returnnull;
}
}
/** * Flush the buffer and commit this response. * * @exception IOException if an input/output error occurs
*/
@Override publicvoid flushBuffer() throws IOException {
outputBuffer.flush();
}
/** * @return the actual buffer size used for this Response.
*/
@Override publicint getBufferSize() { return outputBuffer.getBufferSize();
}
/** * @return the character encoding used for this Response.
*/
@Override public String getCharacterEncoding() {
String charset = getCoyoteResponse().getCharacterEncoding(); if (charset != null) { return charset;
}
Context context = getContext();
String result = null; if (context != null) {
result = context.getResponseCharacterEncoding();
}
if (result == null) {
result = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET.name();
}
return result;
}
/** * @return the servlet output stream associated with this Response. * * @exception IllegalStateException if <code>getWriter</code> has already been called for this response * @exception IOException if an input/output error occurs
*/
@Override public ServletOutputStream getOutputStream() throws IOException {
if (usingWriter) { thrownew IllegalStateException(sm.getString("coyoteResponse.getOutputStream.ise"));
}
usingOutputStream = true; if (outputStream == null) {
outputStream = new CoyoteOutputStream(outputBuffer);
} return outputStream;
}
/** * @return the Locale assigned to this response.
*/
@Override public Locale getLocale() { return getCoyoteResponse().getLocale();
}
/** * @return the writer associated with this Response. * * @exception IllegalStateException if <code>getOutputStream</code> has already been called for this response * @exception IOException if an input/output error occurs
*/
@Override public PrintWriter getWriter() throws IOException {
if (usingOutputStream) { thrownew IllegalStateException(sm.getString("coyoteResponse.getWriter.ise"));
}
if (request.getConnector().getEnforceEncodingInGetWriter()) { /* * If the response's character encoding has not been specified as described in * <code>getCharacterEncoding</code> (i.e., the method just returns the default value * <code>ISO-8859-1</code>), <code>getWriter</code> updates it to <code>ISO-8859-1</code> (with the effect * that a subsequent call to getContentType() will include a charset=ISO-8859-1 component which will also be * reflected in the Content-Type response header, thereby satisfying the Servlet spec requirement that * containers must communicate the character encoding used for the servlet response's writer to the client).
*/
setCharacterEncoding(getCharacterEncoding());
}
usingWriter = true;
outputBuffer.checkConverter(); if (writer == null) {
writer = new CoyoteWriter(outputBuffer);
} return writer;
}
/** * Has the output of this response already been committed? * * @return <code>true</code> if the response has been committed
*/
@Override publicboolean isCommitted() { return getCoyoteResponse().isCommitted();
}
/** * Clear any content written to the buffer. * * @exception IllegalStateException if this response has already been committed
*/
@Override publicvoid reset() { // Ignore any call from an included servlet if (included) { return;
}
/** * Reset the data buffer but not any status or header information. * * @exception IllegalStateException if the response has already been committed
*/
@Override publicvoid resetBuffer() {
resetBuffer(false);
}
/** * Reset the data buffer and the using Writer/Stream flags but not any status or header information. * * @param resetWriterStreamFlags <code>true</code> if the internal <code>usingWriter</code>, * <code>usingOutputStream</code>, <code>isCharacterEncodingSet</code> flags * should also be reset * * @exception IllegalStateException if the response has already been committed
*/ publicvoid resetBuffer(boolean resetWriterStreamFlags) {
if (isCommitted()) { thrownew IllegalStateException(sm.getString("coyoteResponse.resetBuffer.ise"));
}
/** * Set the buffer size to be used for this Response. * * @param size The new buffer size * * @exception IllegalStateException if this method is called after output has been committed for this response
*/
@Override publicvoid setBufferSize(int size) {
if (isCommitted() || !outputBuffer.isNew()) { thrownew IllegalStateException(sm.getString("coyoteResponse.setBufferSize.ise"));
}
outputBuffer.setBufferSize(size);
}
/** * Set the content length (in bytes) for this Response. * * @param length The new content length
*/
@Override publicvoid setContentLength(int length) {
setContentLengthLong(length);
}
@Override publicvoid setContentLengthLong(long length) { if (isCommitted()) { return;
}
// Ignore any call from an included servlet if (included) { return;
}
getCoyoteResponse().setContentLength(length);
}
/** * Set the content type for this Response. * * @param type The new content type
*/
@Override publicvoid setContentType(String type) {
if (isCommitted()) { return;
}
// Ignore any call from an included servlet if (included) { return;
}
if (type == null) {
getCoyoteResponse().setContentType(null); try {
getCoyoteResponse().setCharacterEncoding(null);
} catch (UnsupportedEncodingException e) { // Can never happen when calling with null
}
isCharacterEncodingSet = false; return;
}
String[] m = MEDIA_TYPE_CACHE.parse(type); if (m == null) { // Invalid - Assume no charset and just pass through whatever // the user provided.
getCoyoteResponse().setContentTypeNoCharset(type); return;
}
if (m[1] == null) { // No charset and we know value is valid as cache lookup was // successful // Pass-through user provided value in case user-agent is buggy and // requires specific format
getCoyoteResponse().setContentTypeNoCharset(type);
} else { // There is a charset so have to rebuild content-type without it
getCoyoteResponse().setContentTypeNoCharset(m[0]);
// Ignore charset if getWriter() has already been called if (!usingWriter) { try {
getCoyoteResponse().setCharacterEncoding(m[1]);
} catch (UnsupportedEncodingException e) {
log.warn(sm.getString("coyoteResponse.encoding.invalid", m[1]), e);
}
isCharacterEncodingSet = true;
}
}
}
/** * Overrides the name of the character encoding used in the body of the request. This method must be called prior to * reading request parameters or reading input using getReader(). * * @param encoding String containing the name of the character encoding.
*/
@Override publicvoid setCharacterEncoding(String encoding) {
if (isCommitted()) { return;
}
// Ignore any call from an included servlet if (included) { return;
}
// Ignore any call made after the getWriter has been invoked // The default should be used if (usingWriter) { return;
}
/** * Set the Locale that is appropriate for this response, including setting the appropriate character encoding. * * @param locale The new locale
*/
@Override publicvoid setLocale(Locale locale) {
if (isCommitted()) { return;
}
// Ignore any call from an included servlet if (included) { return;
}
getCoyoteResponse().setLocale(locale);
// Ignore any call made after the getWriter has been invoked. // The default should be used if (usingWriter) { return;
}
if (isCharacterEncodingSet) { return;
}
if (locale == null) { try {
getCoyoteResponse().setCharacterEncoding(null);
} catch (UnsupportedEncodingException e) { // Impossible when calling with null
}
} else { // In some error handling scenarios, the context is unknown // (e.g. a 404 when a ROOT context is not present)
Context context = getContext(); if (context != null) {
String charset = context.getCharset(locale); if (charset != null) { try {
getCoyoteResponse().setCharacterEncoding(charset);
} catch (UnsupportedEncodingException e) {
log.warn(sm.getString("coyoteResponse.encoding.invalid", charset), e);
}
}
}
}
}
@Override public String getHeader(String name) { return getCoyoteResponse().getMimeHeaders().getHeader(name);
}
@Override public Collection<String> getHeaderNames() {
MimeHeaders headers = getCoyoteResponse().getMimeHeaders(); int n = headers.size();
List<String> result = new ArrayList<>(n); for (int i = 0; i < n; i++) {
result.add(headers.getName(i).toString());
} return result;
}
@Override public Collection<String> getHeaders(String name) {
Enumeration<String> enumeration = getCoyoteResponse().getMimeHeaders().values(name);
Set<String> result = new LinkedHashSet<>(); while (enumeration.hasMoreElements()) {
result.add(enumeration.nextElement());
} return result;
}
/** * @return the error message that was set with <code>sendError()</code> for this Response.
*/ public String getMessage() { return getCoyoteResponse().getMessage();
}
/** * Add the specified Cookie to those that will be included with this Response. * * @param cookie Cookie to be added
*/
@Override publicvoid addCookie(final Cookie cookie) {
// Ignore any call from an included servlet if (included || isCommitted()) { return;
}
cookies.add(cookie);
String header = generateCookieString(cookie); // if we reached here, no exception, cookie is valid
addHeader("Set-Cookie", header, getContext().getCookieProcessor().getCharset());
}
/** * Special method for adding a session cookie as we should be overriding any previous. * * @param cookie The new session cookie to add the response
*/ publicvoid addSessionCookieInternal(final Cookie cookie) { if (isCommitted()) { return;
}
String name = cookie.getName(); final String headername = "Set-Cookie"; final String startsWith = name + "=";
String header = generateCookieString(cookie); boolean set = false;
MimeHeaders headers = getCoyoteResponse().getMimeHeaders(); int n = headers.size(); for (int i = 0; i < n; i++) { if (headers.getName(i).toString().equals(headername)) { if (headers.getValue(i).toString().startsWith(startsWith)) {
headers.getValue(i).setString(header);
set = true;
}
}
} if (!set) {
addHeader(headername, header);
}
}
public String generateCookieString(final Cookie cookie) { // Web application code can receive a IllegalArgumentException // from the generateHeader() invocation if (SecurityUtil.isPackageProtectionEnabled()) { return AccessController
.doPrivileged(new PrivilegedGenerateCookieString(getContext(), cookie, request.getRequest()));
} else { return getContext().getCookieProcessor().generateHeader(cookie, request.getRequest());
}
}
/** * Add the specified date header to the specified value. * * @param name Name of the header to set * @param value Date value to be set
*/
@Override publicvoid addDateHeader(String name, long value) {
/** * Add the specified header to the specified value. * * @param name Name of the header to set * @param value Value to be set
*/
@Override publicvoid addHeader(String name, String value) {
addHeader(name, value, null);
}
/** * An extended version of this exists in {@link org.apache.coyote.Response}. This check is required here to ensure * that the usingWriter check in {@link #setContentType(String)} is applied since usingWriter is not visible to * {@link org.apache.coyote.Response} Called from set/addHeader. * * @return <code>true</code> if the header is special, no need to set the header.
*/ privateboolean checkSpecialHeader(String name, String value) { if (name.equalsIgnoreCase("Content-Type")) {
setContentType(value); returntrue;
} returnfalse;
}
/** * Add the specified integer header to the specified value. * * @param name Name of the header to set * @param value Integer value to be set
*/
@Override publicvoid addIntHeader(String name, int value) {
// Ignore any call from an included servlet if (included) { return;
}
addHeader(name, "" + value);
}
/** * Has the specified header been set already in this response? * * @param name Name of the header to check * * @return <code>true</code> if the header has been set
*/
@Override publicboolean containsHeader(String name) { // Need special handling for Content-Type and Content-Length due to // special handling of these in coyoteResponse char cc = name.charAt(0); if (cc == 'C' || cc == 'c') { if (name.equalsIgnoreCase("Content-Type")) { // Will return null if this has not been set return (getCoyoteResponse().getContentType() != null);
} if (name.equalsIgnoreCase("Content-Length")) { // -1 means not known and is not sent to client return (getCoyoteResponse().getContentLengthLong() != -1);
}
}
@Override public Supplier<Map<String,String>> getTrailerFields() { return getCoyoteResponse().getTrailerFields();
}
/** * Encode the session identifier associated with this response into the specified redirect URL, if necessary. * * @param url URL to be encoded * * @return <code>true</code> if the URL was encoded
*/
@Override public String encodeRedirectURL(String url) { if (isEncodeable(toAbsolute(url))) { return toEncoded(url, request.getSessionInternal().getIdInternal());
} else { return url;
}
}
/** * Encode the session identifier associated with this response into the specified URL, if necessary. * * @param url URL to be encoded * * @return <code>true</code> if the URL was encoded
*/
@Override public String encodeURL(String url) {
/** * Send an acknowledgement of a request. * * @param continueResponseTiming Indicates when the request for the ACK originated so it can be compared with the * configured timing for ACK responses. * * @exception IOException if an input/output error occurs
*/ publicvoid sendAcknowledgement(ContinueResponseTiming continueResponseTiming) throws IOException {
if (isCommitted()) { return;
}
// Ignore any call from an included servlet if (included) { return;
}
/** * Send an error response with the specified status and a default message. * * @param status HTTP status code to send * * @exception IllegalStateException if this response has already been committed * @exception IOException if an input/output error occurs
*/
@Override publicvoid sendError(int status) throws IOException {
sendError(status, null);
}
/** * Send an error response with the specified status and message. * * @param status HTTP status code to send * @param message Corresponding message to send * * @exception IllegalStateException if this response has already been committed * @exception IOException if an input/output error occurs
*/
@Override publicvoid sendError(int status, String message) throws IOException {
if (isCommitted()) { thrownew IllegalStateException(sm.getString("coyoteResponse.sendError.ise"));
}
// Ignore any call from an included servlet if (included) { return;
}
/** * Internal method that allows a redirect to be sent with a status other than {@link HttpServletResponse#SC_FOUND} * (302). No attempt is made to validate the status code. * * @param location Location URL to redirect to * @param status HTTP status code that will be sent * * @throws IOException an IO exception occurred
*/ publicvoid sendRedirect(String location, int status) throws IOException { if (isCommitted()) { thrownew IllegalStateException(sm.getString("coyoteResponse.sendRedirect.ise"));
}
// Ignore any call from an included servlet if (included) { return;
}
// Clear any data content that has been buffered
resetBuffer(true);
// Generate a temporary redirect to the specified location try {
Context context = getContext(); // If no ROOT context is defined, the context can be null. // In this case, the default Tomcat values are assumed, but without // reference to org.apache.catalina.STRICT_SERVLET_COMPLIANCE.
String locationUri; // Relative redirects require HTTP/1.1 or later if (getRequest().getCoyoteRequest().getSupportsRelativeRedirects() &&
(context == null || context.getUseRelativeRedirects())) {
locationUri = location;
} else {
locationUri = toAbsolute(location);
}
setStatus(status);
setHeader("Location", locationUri); if (context != null && context.getSendRedirectBody()) {
PrintWriter writer = getWriter();
writer.print(sm.getString("coyoteResponse.sendRedirect.note", Escape.htmlElementContent(locationUri)));
flushBuffer();
}
} catch (IllegalArgumentException e) {
log.warn(sm.getString("response.sendRedirectFail", location), e);
setStatus(SC_NOT_FOUND);
}
// Cause the response to be finished (from the application perspective)
setSuspended(true);
}
/** * Set the specified date header to the specified value. * * @param name Name of the header to set * @param value Date value to be set
*/
@Override publicvoid setDateHeader(String name, long value) {
/** * Set the specified header to the specified value. * * @param name Name of the header to set * @param value Value to be set
*/
@Override publicvoid setHeader(String name, String value) {
if (name == null || name.length() == 0 || value == null) { return;
}
if (isCommitted()) { return;
}
// Ignore any call from an included servlet if (included) { return;
}
char cc = name.charAt(0); if (cc == 'C' || cc == 'c') { if (checkSpecialHeader(name, value)) { return;
}
}
getCoyoteResponse().setHeader(name, value);
}
/** * Set the specified integer header to the specified value. * * @param name Name of the header to set * @param value Integer value to be set
*/
@Override publicvoid setIntHeader(String name, int value) {
/** * Return <code>true</code> if the specified URL should be encoded with a session identifier. This will be true if * all of the following conditions are met: * <ul> * <li>The request we are responding to asked for a valid session * <li>The requested session ID was not received via a cookie * <li>The specified URL points back to somewhere within the web application that is responding to this request * </ul> * * @param location Absolute URL to be validated * * @return <code>true</code> if the URL should be encoded
*/ protectedboolean isEncodeable(final String location) {
if (location == null) { returnfalse;
}
// Is this an intra-document reference? if (location.startsWith("#")) { returnfalse;
}
// Are we in a valid session that is not using cookies? final Request hreq = request; final Session session = hreq.getSessionInternal(false); if (session == null) { returnfalse;
} if (hreq.isRequestedSessionIdFromCookie()) { returnfalse;
}
// Is URL encoding permitted if (!hreq.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) { returnfalse;
}
privatestaticboolean doIsEncodeable(Context context, Request hreq, Session session, String location) { // Is this a valid absolute URL?
URL url = null; try {
URI uri = new URI(location);
url = uri.toURL();
} catch (MalformedURLException | URISyntaxException | IllegalArgumentException e) { returnfalse;
}
// Does this URL match down to (and including) the context path? if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) { returnfalse;
} if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) { returnfalse;
} int serverPort = hreq.getServerPort(); if (serverPort == -1) { if ("https".equals(hreq.getScheme())) {
serverPort = 443;
} else {
serverPort = 80;
}
} int urlPort = url.getPort(); if (urlPort == -1) { if ("https".equals(url.getProtocol())) {
urlPort = 443;
} else {
urlPort = 80;
}
} if (serverPort != urlPort) { returnfalse;
}
String contextPath = context.getPath(); if (contextPath != null) {
String file = url.getFile(); if (!file.startsWith(contextPath)) { returnfalse;
}
String tok = ";" + SessionConfig.getSessionUriParamName(context) + "=" + session.getIdInternal(); if (file.indexOf(tok, contextPath.length()) >= 0) { returnfalse;
}
}
// This URL belongs to our web application, so it is encodeable returntrue;
}
/** * Convert (if necessary) and return the absolute URL that represents the resource referenced by this possibly * relative URL. If this URL is already absolute, return it unchanged. * * @param location URL to be (possibly) converted and then returned * * @return the encoded URL * * @exception IllegalArgumentException if a MalformedURLException is thrown when converting the relative URL to an * absolute one
*/ protected String toAbsolute(String location) {
/** * Removes /./ and /../ sequences from absolute URLs. Code borrowed heavily from CoyoteAdapter.normalize() * * @param cc the char chunk containing the chars to normalize
*/ privatevoid normalize(CharChunk cc) { // Strip query string and/or fragment first as doing it this way makes // the normalization logic a lot simpler int truncate = cc.indexOf('?'); if (truncate == -1) {
truncate = cc.indexOf('#');
} char[] truncateCC = null; if (truncate > -1) {
truncateCC = Arrays.copyOfRange(cc.getBuffer(), cc.getStart() + truncate, cc.getEnd());
cc.setEnd(cc.getStart() + truncate);
}
char[] c = cc.getChars(); int start = cc.getStart(); int end = cc.getEnd(); int index = 0; int startIndex = 0;
// Advance past the first three / characters (should place index just // scheme://host[:port]
for (int i = 0; i < 3; i++) {
startIndex = cc.indexOf('/', startIndex + 1);
}
// Remove /./
index = startIndex; while (true) {
index = cc.indexOf("/./", 0, 3, index); if (index < 0) { break;
}
copyChars(c, start + index, start + index + 2, end - start - index - 2);
end = end - 2;
cc.setEnd(end);
}
// Remove /../
index = startIndex; int pos; while (true) {
index = cc.indexOf("/../", 0, 4, index); if (index < 0) { break;
} // Can't go above the server root if (index == startIndex) { thrownew IllegalArgumentException();
} int index2 = -1; for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos--) { if (c[pos] == (byte) '/') {
index2 = pos;
}
}
copyChars(c, start + index2, start + index + 3, end - start - index - 3);
end = end + index2 - index - 3;
cc.setEnd(end);
index = index2;
}
// Add the query string and/or fragment (if present) back in if (truncateCC != null) { try {
cc.append(truncateCC, 0, truncateCC.length);
} catch (IOException ioe) { thrownew IllegalArgumentException(ioe);
}
}
}
privatevoid copyChars(char[] c, int dest, int src, int len) {
System.arraycopy(c, src, c, dest, len);
}
/** * Determine if an absolute URL has a path component. * * @param uri the URL that will be checked * * @return <code>true</code> if the URL has a path
*/ privateboolean hasPath(String uri) { int pos = uri.indexOf("://"); if (pos < 0) { returnfalse;
}
pos = uri.indexOf('/', pos + 3); if (pos < 0) { returnfalse;
} returntrue;
}
/** * Return the specified URL with the specified session identifier suitably encoded. * * @param url URL to be encoded with the session id * @param sessionId Session id to be included in the encoded URL * * @return the encoded URL
*/ protected String toEncoded(String url, String sessionId) { if (url == null || sessionId == null) { return url;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.