Skip to content

Commit 7fdfcdb

Browse files
committed
JavaScript snippet injection
1 parent c797892 commit 7fdfcdb

File tree

29 files changed

+1239
-3
lines changed

29 files changed

+1239
-3
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
}
4+
5+
dependencies {
6+
testImplementation("javax.servlet:javax.servlet-api:3.0.1")
7+
testImplementation(project(":instrumentation:servlet:servlet-common:bootstrap"))
8+
testImplementation(project(":instrumentation:servlet:servlet-3.0:javaagent"))
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile;
9+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.when;
12+
13+
import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder;
14+
import java.io.IOException;
15+
import java.io.StringWriter;
16+
import java.nio.charset.StandardCharsets;
17+
import javax.servlet.ServletOutputStream;
18+
import org.junit.jupiter.api.Disabled;
19+
import org.junit.jupiter.api.Test;
20+
21+
class InjectionTest {
22+
23+
@Test
24+
void testInjectionForStringContainHeadTag() throws IOException {
25+
String testSnippet = "\n <script type=\"text/javascript\"> Test </script>";
26+
ExperimentalSnippetHolder.setSnippet(testSnippet);
27+
// read the originalFile
28+
String original = readFile("staticHtmlOrigin.html");
29+
// read the correct answer
30+
String correct = readFile("staticHtmlAfter.html");
31+
byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8);
32+
SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class);
33+
when(response.isCommitted()).thenReturn(false);
34+
when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name());
35+
InjectionState obj = new InjectionState(response);
36+
37+
StringWriter writer = new StringWriter();
38+
39+
ServletOutputStream sp =
40+
new ServletOutputStream() {
41+
@Override
42+
public void write(int b) throws IOException {
43+
writer.write(b);
44+
}
45+
};
46+
boolean injected =
47+
ServletOutputStreamInjectionHelper.handleWrite(
48+
originalBytes, 0, originalBytes.length, obj, sp);
49+
assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1);
50+
assertThat(injected).isEqualTo(true);
51+
writer.flush();
52+
53+
String result = writer.toString();
54+
writer.close();
55+
assertThat(result).isEqualTo(correct);
56+
}
57+
58+
@Test
59+
@Disabled
60+
void testInjectionForChinese() throws IOException {
61+
String testSnippet = "\n <script type=\"text/javascript\"> Test </script>";
62+
ExperimentalSnippetHolder.setSnippet(testSnippet);
63+
// read the originalFile
64+
String original = readFile("staticHtmlChineseOrigin.html");
65+
// read the correct answer
66+
String correct = readFile("staticHtmlChineseAfter.html");
67+
byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8);
68+
SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class);
69+
when(response.isCommitted()).thenReturn(false);
70+
when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name());
71+
InjectionState obj = new InjectionState(response);
72+
73+
StringWriter writer = new StringWriter();
74+
75+
ServletOutputStream sp =
76+
new ServletOutputStream() {
77+
@Override
78+
public void write(int b) throws IOException {
79+
writer.write(b);
80+
}
81+
};
82+
boolean injected =
83+
ServletOutputStreamInjectionHelper.handleWrite(
84+
originalBytes, 0, originalBytes.length, obj, sp);
85+
assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1);
86+
assertThat(injected).isEqualTo(true);
87+
writer.flush();
88+
89+
String result = writer.toString();
90+
writer.close();
91+
assertThat(result).isEqualTo(correct);
92+
}
93+
94+
@Test
95+
void testInjectionForStringWithoutHeadTag() throws IOException {
96+
String testSnippet = "\n <script type=\"text/javascript\"> Test </script>";
97+
ExperimentalSnippetHolder.setSnippet(testSnippet);
98+
// read the originalFile
99+
String original = readFile("htmlWithoutHeadTag.html");
100+
101+
byte[] originalBytes = original.getBytes(StandardCharsets.UTF_8);
102+
SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class);
103+
when(response.isCommitted()).thenReturn(false);
104+
when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name());
105+
InjectionState obj = new InjectionState(response);
106+
StringWriter writer = new StringWriter();
107+
108+
ServletOutputStream sp =
109+
new ServletOutputStream() {
110+
@Override
111+
public void write(int b) throws IOException {
112+
writer.write(b);
113+
}
114+
};
115+
boolean injected =
116+
ServletOutputStreamInjectionHelper.handleWrite(
117+
originalBytes, 0, originalBytes.length, obj, sp);
118+
assertThat(obj.getHeadTagBytesSeen()).isEqualTo(0);
119+
assertThat(injected).isEqualTo(false);
120+
writer.flush();
121+
String result = writer.toString();
122+
writer.close();
123+
assertThat(result).isEqualTo("");
124+
}
125+
126+
@Test
127+
void testHalfHeadTag() throws IOException {
128+
String testSnippet = "\n <script type=\"text/javascript\"> Test </script>";
129+
ExperimentalSnippetHolder.setSnippet(testSnippet);
130+
// read the original string
131+
String originalFirstPart = "<!DOCTYPE html>\n" + "<html lang=\"en\">\n" + "<he";
132+
byte[] originalFirstPartBytes = originalFirstPart.getBytes(StandardCharsets.UTF_8);
133+
SnippetInjectingResponseWrapper response = mock(SnippetInjectingResponseWrapper.class);
134+
when(response.isCommitted()).thenReturn(false);
135+
when(response.getCharacterEncoding()).thenReturn(StandardCharsets.UTF_8.name());
136+
InjectionState obj = new InjectionState(response);
137+
StringWriter writer = new StringWriter();
138+
139+
ServletOutputStream sp =
140+
new ServletOutputStream() {
141+
@Override
142+
public void write(int b) throws IOException {
143+
writer.write(b);
144+
}
145+
};
146+
boolean injected =
147+
ServletOutputStreamInjectionHelper.handleWrite(
148+
originalFirstPartBytes, 0, originalFirstPartBytes.length, obj, sp);
149+
150+
writer.flush();
151+
String result = writer.toString();
152+
assertThat(obj.getHeadTagBytesSeen()).isEqualTo(3);
153+
assertThat(result).isEqualTo("");
154+
assertThat(injected).isEqualTo(false);
155+
String originalSecondPart =
156+
"ad>\n"
157+
+ " <meta charset=\"UTF-8\">\n"
158+
+ " <title>Title</title>\n"
159+
+ "</head>\n"
160+
+ "<body>\n"
161+
+ "\n"
162+
+ "</body>\n"
163+
+ "</html>";
164+
byte[] originalSecondPartBytes = originalSecondPart.getBytes(StandardCharsets.UTF_8);
165+
injected =
166+
ServletOutputStreamInjectionHelper.handleWrite(
167+
originalSecondPartBytes, 0, originalSecondPartBytes.length, obj, sp);
168+
assertThat(obj.getHeadTagBytesSeen()).isEqualTo(-1);
169+
assertThat(injected).isEqualTo(true);
170+
String correctSecondPart =
171+
"ad>\n"
172+
+ " <script type=\"text/javascript\"> Test </script>\n"
173+
+ " <meta charset=\"UTF-8\">\n"
174+
+ " <title>Title</title>\n"
175+
+ "</head>\n"
176+
+ "<body>\n"
177+
+ "\n"
178+
+ "</body>\n"
179+
+ "</html>";
180+
writer.flush();
181+
result = writer.toString();
182+
assertThat(result).isEqualTo(correctSecondPart);
183+
}
184+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet;
7+
8+
import static io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.TestUtil.readFile;
9+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.when;
12+
13+
import io.opentelemetry.javaagent.bootstrap.servlet.ExperimentalSnippetHolder;
14+
import java.io.IOException;
15+
import java.io.PrintWriter;
16+
import java.io.StringWriter;
17+
import java.nio.charset.Charset;
18+
import javax.servlet.http.HttpServletResponse;
19+
import org.junit.jupiter.api.Disabled;
20+
import org.junit.jupiter.api.Test;
21+
22+
class SnippetInjectingResponseWrapperTest {
23+
24+
@Test
25+
void testInjectToTextHtml() throws IOException {
26+
27+
// read the originalFile
28+
String original = readFile("staticHtmlOrigin.html");
29+
String correct = readFile("staticHtmlAfter.html");
30+
HttpServletResponse response = mock(HttpServletResponse.class);
31+
when(response.getContentType()).thenReturn("text/html");
32+
when(response.getStatus()).thenReturn(200);
33+
when(response.containsHeader("content-type")).thenReturn(true);
34+
StringWriter writer = new StringWriter();
35+
when(response.getWriter()).thenReturn(new PrintWriter(writer));
36+
ExperimentalSnippetHolder.setSnippet("\n <script type=\"text/javascript\"> Test </script>");
37+
SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response);
38+
responseWrapper.getWriter().write(original);
39+
responseWrapper.getWriter().flush();
40+
responseWrapper.getWriter().close();
41+
42+
// read file get result
43+
String result = writer.toString();
44+
writer.close();
45+
// check whether new response == correct answer
46+
assertThat(result).isEqualTo(correct);
47+
}
48+
49+
@Test
50+
@Disabled
51+
void testInjectToChineseTextHtml() throws IOException {
52+
53+
// read the originalFile
54+
String original = readFile("staticHtmlChineseOrigin.html");
55+
String correct = readFile("staticHtmlChineseAfter.html");
56+
HttpServletResponse response = mock(HttpServletResponse.class);
57+
when(response.getContentType()).thenReturn("text/html");
58+
59+
StringWriter writer = new StringWriter();
60+
when(response.getWriter()).thenReturn(new PrintWriter(writer));
61+
ExperimentalSnippetHolder.setSnippet("\n <script type=\"text/javascript\"> Test </script>");
62+
SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response);
63+
responseWrapper.getWriter().write(original);
64+
responseWrapper.getWriter().flush();
65+
responseWrapper.getWriter().close();
66+
67+
// read file get result
68+
String result = writer.toString();
69+
writer.close();
70+
// check whether new response == correct answer
71+
assertThat(result).isEqualTo(correct);
72+
}
73+
74+
@Test
75+
void shouldNotInjectToTextHtml() throws IOException {
76+
77+
// read the originalFile
78+
String original = readFile("staticHtmlOrigin.html");
79+
80+
StringWriter writer = new StringWriter();
81+
HttpServletResponse response = mock(HttpServletResponse.class);
82+
when(response.getContentType()).thenReturn("not/text");
83+
when(response.getStatus()).thenReturn(200);
84+
when(response.containsHeader("content-type")).thenReturn(true);
85+
86+
when(response.getWriter()).thenReturn(new PrintWriter(writer, true));
87+
ExperimentalSnippetHolder.setSnippet("\n <script type=\"text/javascript\"> Test </script>");
88+
89+
SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response);
90+
responseWrapper.getWriter().write(original);
91+
responseWrapper.getWriter().flush();
92+
responseWrapper.getWriter().close();
93+
94+
// read file get result
95+
String result = writer.toString();
96+
writer.close();
97+
// check whether new response == correct answer
98+
assertThat(result).isEqualTo(original);
99+
}
100+
101+
@Test
102+
void testWriteInt() throws IOException {
103+
104+
// read the originalFile
105+
String original = readFile("staticHtmlOrigin.html");
106+
String correct = readFile("staticHtmlAfter.html");
107+
HttpServletResponse response = mock(HttpServletResponse.class);
108+
when(response.getContentType()).thenReturn("text/html");
109+
110+
StringWriter writer = new StringWriter();
111+
// StringWriter correctWriter = new StringWriter();
112+
when(response.getWriter()).thenReturn(new PrintWriter(writer));
113+
ExperimentalSnippetHolder.setSnippet("\n <script type=\"text/javascript\"> Test </script>");
114+
SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response);
115+
byte[] originalBytes = original.getBytes(Charset.defaultCharset().name());
116+
// byte[] correctBytes = correct.getBytes(UTF_8);
117+
// PrintWriter correctPw = new PrintWriter(correctWriter);
118+
for (int i = 0; i < originalBytes.length; i++) {
119+
responseWrapper.getWriter().write(originalBytes[i]);
120+
}
121+
122+
responseWrapper.getWriter().flush();
123+
responseWrapper.getWriter().close();
124+
125+
// read file get result
126+
String result = writer.toString();
127+
writer.close();
128+
// check whether new response == correct answer
129+
assertThat(result).isEqualTo(correct);
130+
}
131+
132+
@Test
133+
void testWriteCharArray() throws IOException {
134+
135+
// read the originalFile
136+
String original = readFile("staticHtmlChineseOrigin.html");
137+
String correct = readFile("staticHtmlChineseAfter.html");
138+
HttpServletResponse response = mock(HttpServletResponse.class);
139+
when(response.getContentType()).thenReturn("text/html");
140+
when(response.getStatus()).thenReturn(200);
141+
when(response.containsHeader("content-type")).thenReturn(true);
142+
143+
StringWriter writer = new StringWriter();
144+
when(response.getWriter()).thenReturn(new PrintWriter(writer));
145+
ExperimentalSnippetHolder.setSnippet("\n <script type=\"text/javascript\"> Test </script>");
146+
SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response);
147+
char[] originalChars = original.toCharArray();
148+
responseWrapper.getWriter().write(originalChars, 0, originalChars.length);
149+
responseWrapper.getWriter().flush();
150+
responseWrapper.getWriter().close();
151+
152+
// read file get result
153+
String result = writer.toString();
154+
writer.close();
155+
// check whether new response == correct answer
156+
assertThat(result).isEqualTo(correct);
157+
}
158+
159+
@Test
160+
void testWriteWithOffset() throws IOException {
161+
162+
// read the originalFile
163+
String original = readFile("staticHtmlChineseOrigin.html");
164+
String correct = readFile("staticHtmlChineseAfter.html");
165+
String extraBuffer = "this buffer should not be print out";
166+
original = extraBuffer + original;
167+
HttpServletResponse response = mock(HttpServletResponse.class);
168+
when(response.getContentType()).thenReturn("text/html");
169+
when(response.getStatus()).thenReturn(200);
170+
when(response.containsHeader("content-type")).thenReturn(true);
171+
172+
StringWriter writer = new StringWriter();
173+
when(response.getWriter()).thenReturn(new PrintWriter(writer));
174+
ExperimentalSnippetHolder.setSnippet("\n <script type=\"text/javascript\"> Test </script>");
175+
SnippetInjectingResponseWrapper responseWrapper = new SnippetInjectingResponseWrapper(response);
176+
177+
responseWrapper
178+
.getWriter()
179+
.write(original, extraBuffer.length(), original.length() - extraBuffer.length());
180+
responseWrapper.getWriter().flush();
181+
responseWrapper.getWriter().close();
182+
183+
// read file get result
184+
String result = writer.toString();
185+
writer.close();
186+
// check whether new response == correct answer
187+
assertThat(result).isEqualTo(correct);
188+
}
189+
}

0 commit comments

Comments
 (0)