Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void setPrefix(String prefix, String namespace) {
if (!isValidPrefix(prefix)) {
throw new IllegalArgumentException(
"Invalid prefix format: '" + prefix +
"' (must be empty or match [a-zA-Z_][-a-zA-Z0-9_]*)");
"' (must be empty or valid according to Turtle/TriG specification)");
}
if (namespace == null) {
throw new IllegalArgumentException("Namespace cannot be null");
Expand Down Expand Up @@ -316,7 +316,7 @@ public String compressIRI(String iri) {
}

/**
* Checks if a prefix is valid according to XML NCName rules.
* Checks if a prefix is valid according
* Empty string "" is considered valid (for Turtle default prefix)
*
* @param prefix the prefix to validate
Expand All @@ -330,7 +330,7 @@ public boolean isValidPrefix(String prefix) {
if (prefix.isEmpty()) {
return true;
}
return prefix.matches("[a-zA-Z_][-a-zA-Z0-9_]*");
return !prefix.contains(":");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public class IRIUtils {
"(?<anchor>(\\#))?" +
"(?<fragment>([\\w\\-_]+))?)?$");
private static final Pattern STANDARD_IRI_PATTERN = Pattern.compile("^(([^:/?#\\s]+):)(\\/\\/([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(.*))?");
private static final Pattern RELATIVE_IRI_PATTERN = Pattern.compile("^[^\\s\\p{Cc}]+$");
private static final int MAX_IRI_LENGTH = 2048;
private static final long REGEX_TIMEOUT_MS = 100;

Expand Down Expand Up @@ -119,7 +118,9 @@ public static boolean isStandardIRI(String iriString) {
if (!isValidInput(iriString)) {
return false;
}

if (isShortIRI(iriString)) {
return true;
}
try {
Matcher matcher = matchWithTimeout(STANDARD_IRI_PATTERN, iriString);
if (matcher != null && matcher.matches()) {
Expand All @@ -131,6 +132,18 @@ public static boolean isStandardIRI(String iriString) {
}
}

/**
* Checks if this is a short IRI that should be accepted in lenient mode.
*/
private static boolean isShortIRI(String iri) {
return iri != null &&

iri.length() <= 10 &&
!iri.contains(":") &&
!iri.contains("/") &&
!iri.contains(" ") &&
iri.matches("[a-zA-Z0-9]+");
}

/**
* Validates input string for basic security checks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,39 +156,46 @@ public String processEscapeSequences(String input, String context) {
public int processEscapeSequence(String input, int i, StringBuilder result, String context) {
char next = input.charAt(i + 1);

switch (next) {
case '"':
return switch (next) {
case '"' -> {
result.append('"');
return i + 1;
case '\\':
yield i + 1;
}
case '\\' -> {
result.append('\\');
return i + 1;
case '>':
yield i + 1;
}
case '>' -> {
result.append('>');
return i + 1;
case 'n':
yield i + 1;
}
case 'n' -> {
result.append('\n');
return i + 1;
case 't':
yield i + 1;
}
case 't' -> {
result.append('\t');
return i + 1;
case 'r':
yield i + 1;
}
case 'r' -> {
result.append('\r');
return i + 1;
case 'b':
yield i + 1;
}
case 'b' -> {
result.append('\b');
return i + 1;
case 'f':
yield i + 1;
}
case 'f' -> {
result.append('\f');
return i + 1;
case 'u':
return processUnicodeEscape(input, i, 4, result, context);
case 'U':
return processUnicodeEscape(input, i, 8, result, context);
default:
yield i + 1;
}
case 'u' -> processUnicodeEscape(input, i, 4, result, context);
case 'U' -> processUnicodeEscape(input, i, 8, result, context);
default -> {
result.append('\\').append(next);
return i + 1;
}
yield i + 1;
}
};
}

/**
Expand Down Expand Up @@ -248,12 +255,17 @@ public void appendCodePoint(StringBuilder result, int codePoint) {
* @return Created literal
*/
public Literal createLiteral(String label, IRI datatypeIRI, String languageTag) {
if (datatypeIRI != null) {
return factory.createLiteral(label, datatypeIRI);
}
if (languageTag != null) {
return factory.createLiteral(label, languageTag);
try {
if (datatypeIRI != null) {
return factory.createLiteral(label, datatypeIRI);
}
if (languageTag != null) {
return factory.createLiteral(label, languageTag);
}
return factory.createLiteral(label);
} catch (IllegalArgumentException e) {

return factory.createLiteral(label);
}
return factory.createLiteral(label);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ public abstract class AbstractTurtleTriGListener {
public Resource currentSubject;
public IRI currentPredicate;

private final java.util.Set<String> explicitlyDeclaredPrefixes = new java.util.HashSet<>();

/**
* Constructs a parser listener with the specified model, factory and base URI.
*
Expand All @@ -50,10 +48,6 @@ public AbstractTurtleTriGListener(Model model, ValueFactory factory, String base
* Registers the base URI as the empty prefix namespace.
*/
public void initializeBasePrefix() {
if (this.baseURI != null && !this.baseURI.isEmpty()) {
prefixHandler.setPrefix(ParserConstants.EMPTY_STRING, this.baseURI);
model.setNamespace(ParserConstants.EMPTY_STRING, this.baseURI);
}
}

/**
Expand All @@ -76,8 +70,6 @@ public String extractAndUnescapeIRI(String text) {
public void updateBaseURI(String newBase) {
this.baseURI = resolveIRIAgainstBase(newBase);
validateIRI(this.baseURI);
prefixHandler.setPrefix(ParserConstants.EMPTY_STRING, this.baseURI);
model.setNamespace(ParserConstants.EMPTY_STRING, this.baseURI);
}

/**
Expand All @@ -91,8 +83,6 @@ public void registerPrefix(String prefix, String iri) {
validateIRI(resolvedIRI);
prefixHandler.setPrefix(prefix, resolvedIRI);
model.setNamespace(prefix, resolvedIRI);

explicitlyDeclaredPrefixes.add(prefix);
}

/**
Expand All @@ -105,6 +95,7 @@ public void registerPrefix(String prefix, String iri) {
*/
public String resolveIRI(String raw) {
try {

raw = raw.trim();

if (raw.equals(ParserConstants.RDF_TYPE_SHORTCUT)) {
Expand All @@ -113,7 +104,12 @@ public String resolveIRI(String raw) {

if (raw.equals(ParserConstants.COLON)) {
String ns = prefixHandler.getNamespace(ParserConstants.EMPTY_STRING);
return ns != null ? ns : getEffectiveBaseURI();
if (ns == null) {
throw new ParsingErrorException(
"Undeclared prefix: '' (empty prefix). " +
"Use '@prefix : <namespace> .' to declare it.");
}
return ns;
}

if (raw.startsWith(ParserConstants.IRI_START) && raw.endsWith(ParserConstants.IRI_END)) {
Expand Down Expand Up @@ -374,7 +370,7 @@ public String removeDotSegments(String path) {
public void removeLastSegment(StringBuilder output) {
String outputStr = output.toString();
int lastSlash = outputStr.lastIndexOf(ParserConstants.SLASH);
output.setLength(lastSlash >= 0 ? lastSlash : 0);
output.setLength(Math.max(lastSlash, 0));
}

/**
Expand Down Expand Up @@ -642,12 +638,11 @@ public Literal createNumericLiteral(String text, NumericType type) {
* Validates that an IRI contains only valid characters after escape sequence processing.
*
* @param iri the IRI string to validate (after escape sequences have been processed)
* @return true if the IRI is valid
* @throws ParsingErrorException if the IRI contains forbidden characters
*/
private boolean validateIRI(String iri) throws ParsingErrorException {
private void validateIRI(String iri) throws ParsingErrorException {
if (iri == null || iri.isEmpty()) {
return true; // Empty IRIs are acceptable
return;
}

// Check each character in the IRI
Expand All @@ -668,7 +663,6 @@ private boolean validateIRI(String iri) throws ParsingErrorException {
);
}
}
return true;
}

/**
Expand All @@ -688,14 +682,4 @@ public enum NumericType {
*/
DOUBLE
}

/**
* Returns the prefix handler for this listener.
* Allows external access to discovered prefixes.
*
* @return the PrefixHandler instance
*/
public PrefixHandler getPrefixHandler() {
return prefixHandler;
}
}
Loading