undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
|
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package doccheckutils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Reads an HTML file, and calls a series of{@link HtmlChecker HTML checkers}
|
||||
* for the HTML constructs found therein.
|
||||
*/
|
||||
public class HtmlFileChecker implements FileChecker {
|
||||
private final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
|
||||
.onMalformedInput(CodingErrorAction.IGNORE)
|
||||
.onUnmappableCharacter(CodingErrorAction.IGNORE);
|
||||
|
||||
private final Log log;
|
||||
private final HtmlChecker htmlChecker;
|
||||
private Path path;
|
||||
private BufferedReader in;
|
||||
private int ch;
|
||||
private int lineNumber;
|
||||
private boolean inScript;
|
||||
private boolean xml;
|
||||
|
||||
public HtmlFileChecker(HtmlChecker htmlChecker, Path BaseDir) {
|
||||
this.log = new Log();
|
||||
log.setBaseDirectory(BaseDir);
|
||||
this.htmlChecker = htmlChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkFiles(List<Path> files) {
|
||||
for (Path file : files) {
|
||||
read(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report() {
|
||||
System.err.println(log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// report();
|
||||
htmlChecker.close();
|
||||
}
|
||||
|
||||
private void read(Path path) {
|
||||
try (BufferedReader r = new BufferedReader(
|
||||
new InputStreamReader(Files.newInputStream(path), decoder))) {
|
||||
this.path = path;
|
||||
this.in = r;
|
||||
StringBuilder content = new StringBuilder();
|
||||
|
||||
startFile(path);
|
||||
try {
|
||||
lineNumber = 1;
|
||||
xml = false;
|
||||
nextChar();
|
||||
|
||||
while (ch != -1) {
|
||||
if (ch == '<') {
|
||||
content(content.toString());
|
||||
content.setLength(0);
|
||||
html();
|
||||
} else {
|
||||
content.append((char) ch);
|
||||
if (ch == '\n') {
|
||||
content(content.toString());
|
||||
content.setLength(0);
|
||||
}
|
||||
nextChar();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
endFile();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.log(path, lineNumber, e);
|
||||
} catch (Throwable t) {
|
||||
log.log(path, lineNumber, t);
|
||||
log.log(String.valueOf(t));
|
||||
}
|
||||
}
|
||||
|
||||
private void startFile(Path path) {
|
||||
htmlChecker.startFile(path);
|
||||
}
|
||||
|
||||
private void endFile() {
|
||||
htmlChecker.endFile();
|
||||
}
|
||||
|
||||
private void docType(String s) {
|
||||
htmlChecker.docType(lineNumber, s);
|
||||
}
|
||||
|
||||
private void startElement(String name, Map<String, String> attrs, boolean selfClosing) {
|
||||
htmlChecker.startElement(lineNumber, name, attrs, selfClosing);
|
||||
}
|
||||
|
||||
private void endElement(String name) {
|
||||
htmlChecker.endElement(lineNumber, name);
|
||||
}
|
||||
|
||||
private void content(String s) {
|
||||
htmlChecker.content(lineNumber, s);
|
||||
}
|
||||
|
||||
private void nextChar() throws IOException {
|
||||
ch = in.read();
|
||||
if (ch == '\n')
|
||||
lineNumber++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the start or end of an HTML tag, or an HTML comment
|
||||
* {@literal <identifier attrs> } or {@literal </identifier> }
|
||||
*
|
||||
* @throws IOException if there is a problem reading the file
|
||||
*/
|
||||
protected void html() throws IOException {
|
||||
nextChar();
|
||||
if (isIdentifierStart((char) ch)) {
|
||||
String name = readIdentifier().toLowerCase(Locale.US);
|
||||
Map<String, String> attrs = htmlAttrs();
|
||||
if (attrs != null) {
|
||||
boolean selfClosing = false;
|
||||
if (ch == '/') {
|
||||
nextChar();
|
||||
selfClosing = true;
|
||||
}
|
||||
if (ch == '>') {
|
||||
nextChar();
|
||||
startElement(name, attrs, selfClosing);
|
||||
if (name.equals("script")) {
|
||||
inScript = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (ch == '/') {
|
||||
nextChar();
|
||||
if (isIdentifierStart((char) ch)) {
|
||||
String name = readIdentifier().toLowerCase(Locale.US);
|
||||
skipWhitespace();
|
||||
if (ch == '>') {
|
||||
nextChar();
|
||||
endElement(name);
|
||||
if (name.equals("script")) {
|
||||
inScript = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (ch == '!') {
|
||||
nextChar();
|
||||
if (ch == '-') {
|
||||
nextChar();
|
||||
if (ch == '-') {
|
||||
nextChar();
|
||||
while (ch != -1) {
|
||||
int dash = 0;
|
||||
while (ch == '-') {
|
||||
dash++;
|
||||
nextChar();
|
||||
}
|
||||
// Strictly speaking, a comment should not contain "--"
|
||||
// so dash > 2 is an error, dash == 2 implies ch == '>'
|
||||
// See http://www.w3.org/TR/html-markup/syntax.html#syntax-comments
|
||||
// for more details.
|
||||
if (dash >= 2 && ch == '>') {
|
||||
nextChar();
|
||||
return;
|
||||
}
|
||||
|
||||
nextChar();
|
||||
}
|
||||
}
|
||||
} else if (ch == '[') {
|
||||
nextChar();
|
||||
if (ch == 'C') {
|
||||
nextChar();
|
||||
if (ch == 'D') {
|
||||
nextChar();
|
||||
if (ch == 'A') {
|
||||
nextChar();
|
||||
if (ch == 'T') {
|
||||
nextChar();
|
||||
if (ch == 'A') {
|
||||
nextChar();
|
||||
if (ch == '[') {
|
||||
while (true) {
|
||||
nextChar();
|
||||
if (ch == ']') {
|
||||
nextChar();
|
||||
if (ch == ']') {
|
||||
nextChar();
|
||||
if (ch == '>') {
|
||||
nextChar();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (ch != -1 && ch != '>') {
|
||||
sb.append((char) ch);
|
||||
nextChar();
|
||||
}
|
||||
Pattern p = Pattern.compile("(?is)doctype\\s+html\\s?.*");
|
||||
String s = sb.toString();
|
||||
if (p.matcher(s).matches()) {
|
||||
xml = s.contains("XHTML");
|
||||
docType(s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (ch == '?') {
|
||||
nextChar();
|
||||
if (ch == 'x') {
|
||||
nextChar();
|
||||
if (ch == 'm') {
|
||||
nextChar();
|
||||
if (ch == 'l') {
|
||||
nextChar();
|
||||
if (ch == '?') {
|
||||
nextChar();
|
||||
if (ch == '>') {
|
||||
nextChar();
|
||||
xml = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!inScript) {
|
||||
log.log(path, lineNumber, "bad html");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a series of HTML attributes, terminated by {@literal > }.
|
||||
* Each attribute is of the form {@literal identifier[=value] }.
|
||||
* "value" may be unquoted, single-quoted, or double-quoted.
|
||||
*/
|
||||
protected Map<String, String> htmlAttrs() throws IOException {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
skipWhitespace();
|
||||
|
||||
while (isIdentifierStart((char) ch)) {
|
||||
String name = readAttributeName().toLowerCase(Locale.US);
|
||||
skipWhitespace();
|
||||
String value = null;
|
||||
if (ch == '=') {
|
||||
nextChar();
|
||||
skipWhitespace();
|
||||
if (ch == '\'' || ch == '"') {
|
||||
char quote = (char) ch;
|
||||
nextChar();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (ch != -1 && ch != quote) {
|
||||
// if (ch == '\n') {
|
||||
// error(path, lineNumber, "unterminated string");
|
||||
// // No point trying to read more.
|
||||
// // In fact, all attrs get discarded by the caller
|
||||
// // and superseded by a malformed.html node because
|
||||
// // the html tag itself is not terminated correctly.
|
||||
// break loop;
|
||||
// }
|
||||
sb.append((char) ch);
|
||||
nextChar();
|
||||
}
|
||||
value = sb.toString() // hack to replace common entities
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("&", "&");
|
||||
nextChar();
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (ch != -1 && !isUnquotedAttrValueTerminator((char) ch)) {
|
||||
sb.append((char) ch);
|
||||
nextChar();
|
||||
}
|
||||
value = sb.toString();
|
||||
}
|
||||
skipWhitespace();
|
||||
}
|
||||
map.put(name, value);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
protected boolean isIdentifierStart(char ch) {
|
||||
return Character.isUnicodeIdentifierStart(ch);
|
||||
}
|
||||
|
||||
protected String readIdentifier() throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append((char) ch);
|
||||
nextChar();
|
||||
while (ch != -1 && Character.isUnicodeIdentifierPart(ch)) {
|
||||
sb.append((char) ch);
|
||||
nextChar();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected String readAttributeName() throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append((char) ch);
|
||||
nextChar();
|
||||
while ((ch != -1 && Character.isUnicodeIdentifierPart(ch))
|
||||
|| ch == '-'
|
||||
|| (xml && ch == ':')) {
|
||||
sb.append((char) ch);
|
||||
nextChar();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected boolean isWhitespace(char ch) {
|
||||
return Character.isWhitespace(ch);
|
||||
}
|
||||
|
||||
protected void skipWhitespace() throws IOException {
|
||||
while (isWhitespace((char) ch)) {
|
||||
nextChar();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isUnquotedAttrValueTerminator(char ch) {
|
||||
return switch (ch) {
|
||||
case '\f', '\n', '\r', '\t', ' ', '"', '\'', '`', '=', '<', '>' -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOK() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue