/*
* Copyright 2008-2017 by Emeric Vernat
*
* This file is part of Java Melody.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bull.javamelody;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
/**
* Implémentation de ServletOutputStream qui fonctionne avec le HtmlInjectorServletResponseWrapper.
* @author Emeric Vernat
*/
class HtmlInjectorResponseStream extends FilterServletOutputStream {
private final HttpServletResponse response;
private final HtmlToInject htmlToInject;
private final byte[] beforeTag;
private boolean injectionCanceled;
interface HtmlToInject {
/**
* @return Html content to inject.
*/
String getContent();
/**
* @return Portion of html to inject content before.
*/
String getBeforeTag();
}
/**
* Construit un servlet output stream associé avec la réponse spécifiée.
* @param response HttpServletResponse
* @param htmlToInject HtmlToInject
* @throws IOException Erreur d'entrée/sortie
*/
HtmlInjectorResponseStream(HttpServletResponse response, HtmlToInject htmlToInject)
throws IOException {
super(response.getOutputStream());
this.response = response;
this.htmlToInject = htmlToInject;
// HttpServletResponse.getCharacterEncoding() shouldn't return null according the spec.
// And response.getCharacterEncoding() may not be explicit yet,
// but we suppose that it does not make any difference on the beforeTag.
this.beforeTag = htmlToInject.getBeforeTag().getBytes(response.getCharacterEncoding());
}
void cancelInjection() {
injectionCanceled = true;
}
// not worth it
// /** {@inheritDoc} */
// @Override
// public void write(int i) throws IOException {
// super.write(i);
// }
/** {@inheritDoc} */
@Override
public void write(byte[] bytes) throws IOException {
write(bytes, 0, bytes.length);
}
/** {@inheritDoc} */
@Override
public void write(byte[] bytes, int off, int len) throws IOException {
// if httpResponse.setContentType(x) has been called with !x.contains("text/html"),
// then no need to continue scanning for the beforeTag
if (injectionCanceled) {
super.write(bytes, off, len);
} else {
final int index = indexOf(bytes, beforeTag, off, len);
if (index == -1) {
// beforeTag not found yet
super.write(bytes, off, len);
} else {
// beforeTag found: inject content.
super.write(bytes, off, index);
final String content = htmlToInject.getContent();
// HttpServletResponse.getCharacterEncoding() shouldn't return null according the spec
super.write(content.getBytes(response.getCharacterEncoding()));
super.write(bytes, off + index, len - index);
}
}
}
private static int indexOf(byte[] sourceBytes, byte[] targetBytes, int sourceOffset,
int sourceLength) {
final byte first = targetBytes[0];
final int max = sourceOffset + sourceLength - targetBytes.length;
for (int i = sourceOffset; i <= max; i++) {
// Look for first byte
while (i <= max && sourceBytes[i] != first) {
i++;
}
if (i <= max) {
// Found first byte, now look at the rest of sourceBytes
int j = i + 1;
final int end = j + targetBytes.length - 1;
for (int k = 1; j < end && sourceBytes[j] == targetBytes[k]; k++) {
j++;
}
if (j == end) {
// Found whole bytes
return i - sourceOffset;
}
}
}
return -1;
}
}