1 package org.owasp.dependencycheck.xml;
2
3 import java.io.FilterInputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import javax.annotation.concurrent.NotThreadSafe;
7
8 import org.jetbrains.annotations.NotNull;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 @NotThreadSafe
28 public class XmlInputStream extends FilterInputStream {
29
30
31
32
33 private static final Logger LOGGER = LoggerFactory.getLogger(XmlInputStream.class);
34
35
36
37 private static final int MIN_LENGTH = 2;
38
39
40
41 private final StringBuilder red = new StringBuilder();
42
43
44
45 private final StringBuilder pushBack = new StringBuilder();
46
47
48
49 private int given = 0;
50
51
52
53 private int pulled = 0;
54
55
56
57
58
59
60 public XmlInputStream(InputStream in) {
61 super(in);
62 }
63
64
65
66
67
68
69
70 public int length() {
71 try {
72 final StringBuilder s = read(MIN_LENGTH);
73 pushBack.append(s);
74 return s.length();
75 } catch (IOException ex) {
76 LOGGER.warn("Oops ", ex);
77 }
78 return 0;
79 }
80
81
82
83
84
85
86
87
88 private StringBuilder read(int n) throws IOException {
89
90 boolean eof = false;
91
92 final StringBuilder s = new StringBuilder(n);
93 while (s.length() < n && !eof) {
94
95 if (pushBack.length() == 0) {
96
97 eof = readIntoPushBack();
98 }
99
100
101 if (pushBack.length() > 0) {
102
103 s.append(pushBack.charAt(0));
104
105 pushBack.deleteCharAt(0);
106 }
107
108 }
109 return s;
110 }
111
112
113
114
115
116
117
118
119 private boolean readIntoPushBack() throws IOException {
120
121 boolean eof = false;
122
123 final int ch = in.read();
124 if (ch >= 0) {
125
126 if (!(pulled == 0 && isWhiteSpace(ch))) {
127
128 pulled += 1;
129
130 if (ch == '&') {
131
132 readAmpersand();
133 } else {
134
135 pushBack.append((char) ch);
136 }
137 }
138 } else {
139
140 eof = true;
141 }
142 return eof;
143 }
144
145
146
147
148
149
150 private void readAmpersand() throws IOException {
151
152 final StringBuilder reference = new StringBuilder();
153 int ch;
154
155 for (ch = in.read(); isAlphaNumeric(ch); ch = in.read()) {
156 reference.append((char) ch);
157 }
158
159 if (ch == ';') {
160
161 final String code = XmlEntity.fromNamedReference(reference);
162 if (code != null) {
163
164 pushBack.append(code);
165 } else {
166
167 pushBack.append("&").append(reference).append((char) ch);
168 }
169 } else {
170
171
172
173 pushBack.append("&").append(reference).append((char) ch);
174 }
175 }
176
177
178
179
180
181
182
183
184 private void given(CharSequence s, int wanted, int got) {
185 red.append(s);
186 given += got;
187 LOGGER.trace("Given: [" + wanted + "," + got + "]-" + s);
188 }
189
190
191
192
193
194
195
196 @Override
197 public int read() throws IOException {
198 final StringBuilder s = read(1);
199 given(s, 1, 1);
200 return s.length() > 0 ? s.charAt(0) : -1;
201 }
202
203
204
205
206
207
208
209
210
211
212
213
214 @Override
215 public int read(@NotNull byte[] data, int offset, int length) throws IOException {
216 final StringBuilder s = read(length);
217 int n = 0;
218 for (int i = 0; i < Math.min(length, s.length()); i++) {
219 data[offset + i] = (byte) s.charAt(i);
220 n += 1;
221 }
222 given(s, length, n);
223 return n > 0 ? n : -1;
224 }
225
226
227
228
229
230
231
232 @Override
233 public String toString() {
234 final String s = red.toString();
235 final StringBuilder h = new StringBuilder();
236
237 if (s.length() < 8) {
238 for (int i = 0; i < s.length(); i++) {
239 h.append(" ").append(Integer.toHexString(s.charAt(i)));
240 }
241 }
242 return "[" + given + "]-\"" + s + "\"" + (h.length() > 0 ? " (" + h + ")" : "");
243 }
244
245
246
247
248
249
250
251 private boolean isWhiteSpace(int ch) {
252 switch (ch) {
253 case ' ':
254 case '\r':
255 case '\n':
256 case '\t':
257 return true;
258 default:
259 return false;
260 }
261 }
262
263
264
265
266
267
268
269 private boolean isAlphaNumeric(int ch) {
270 return ('a' <= ch && ch <= 'z')
271 || ('A' <= ch && ch <= 'Z')
272 || ('0' <= ch && ch <= '9');
273 }
274 }