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 @@ -548,7 +548,10 @@ public static RuntimeScalar parseNumber(RuntimeScalar runtimeScalar) {

// parseNumber(RuntimeScalar, String) method - with optional operation context for warnings
public static RuntimeScalar parseNumber(RuntimeScalar runtimeScalar, String operation) {
String str = (String) runtimeScalar.value;
String str = runtimeScalar.toString();
if (str == null) {
str = "";
}

RuntimeScalar result = numificationCache.get(str);
if (result != null) {
Expand Down
95 changes: 60 additions & 35 deletions src/main/java/org/perlonjava/runtime/perlmodule/XMLParserExpat.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
public class XMLParserExpat extends PerlModuleBase {

public static final String XS_VERSION = "2.56";
private static final Pattern SYSTEM_ID_PATTERN =
Pattern.compile("\\bSYSTEM\\s+(['\"])(.*?)\\1", Pattern.DOTALL);

// Namespace separator character (same as expat's NSDELIM = 0xFC)
private static final char NS_SEP = '\u00FC';
Expand Down Expand Up @@ -208,6 +210,9 @@ static class ParserState {
// Base URI from InputSource for un-resolving SAX systemIds
String parseBaseUri;

// Raw DTD system IDs keyed by SAX-resolved equivalents.
Map<String, String> rawSystemIds;

// Protocol encoding (e.g. "ISO-8859-1") from ParserCreate
String protocolEncoding;

Expand Down Expand Up @@ -715,9 +720,7 @@ public static RuntimeList ParseString(RuntimeArray args, int ctx) {
? xmlString.getBytes(StandardCharsets.ISO_8859_1)
: xmlString.getBytes(StandardCharsets.UTF_8);
xmlBytes = convertEncoding(xmlBytes);
state.bytesProcessed = 0;
state.inputBytes = xmlBytes;
state.inputScanPos = 0;
prepareInputBytes(state, xmlBytes);
doParse(state, new ByteArrayInputStream(xmlBytes));
return scalarTrue.getList();
} catch (PerlDieException e) {
Expand Down Expand Up @@ -784,9 +787,7 @@ public static RuntimeList ParseStream(RuntimeArray args, int ctx) {

byte[] xmlBytes = baos.toByteArray();
xmlBytes = convertEncoding(xmlBytes);
state.bytesProcessed = 0;
state.inputBytes = xmlBytes;
state.inputScanPos = 0;
prepareInputBytes(state, xmlBytes);
doParse(state, new ByteArrayInputStream(xmlBytes));
return scalarTrue.getList();
} catch (PerlDieException e) {
Expand Down Expand Up @@ -840,9 +841,7 @@ public static RuntimeList ParseDone(RuntimeArray args, int ctx) {
: xml.getBytes(StandardCharsets.UTF_8);
xmlBytes = convertEncoding(xmlBytes);
state.partialIsByteString = false;
state.bytesProcessed = 0;
state.inputBytes = xmlBytes;
state.inputScanPos = 0;
prepareInputBytes(state, xmlBytes);
doParse(state, new ByteArrayInputStream(xmlBytes));
return scalarTrue.getList();
} catch (PerlDieException e) {
Expand Down Expand Up @@ -998,6 +997,7 @@ private static void doParse(ParserState state, InputStream input) throws Excepti
sb.insert(insertPos, doctypeDecl);
byte[] newBytes = sb.toString().getBytes(StandardCharsets.UTF_8);
state.inputBytes = newBytes;
rememberRawSystemIdsFromInput(state);
input = new ByteArrayInputStream(newBytes);
}
}
Expand Down Expand Up @@ -1552,12 +1552,13 @@ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXExce
@Override
public void unparsedEntityDecl(String name, String publicId, String systemId,
String notationName) throws SAXException {
String rawSysId = unresolveSysId(systemId, state);
rememberRawSystemId(rawSysId, state);
if (state.unparsedHandler != null) {
RuntimeArray callArgs = new RuntimeArray();
RuntimeArray.push(callArgs, state.selfRef);
RuntimeArray.push(callArgs, new RuntimeScalar(name));
RuntimeArray.push(callArgs, state.base != null ? new RuntimeScalar(state.base) : scalarUndef);
String rawSysId = unresolveSysId(systemId, state);
RuntimeArray.push(callArgs, new RuntimeScalar(rawSysId != null ? rawSysId : ""));
RuntimeArray.push(callArgs, publicId != null ? new RuntimeScalar(publicId) : scalarUndef);
RuntimeArray.push(callArgs, new RuntimeScalar(notationName));
Expand All @@ -1574,8 +1575,7 @@ public void unparsedEntityDecl(String name, String publicId, String systemId,
RuntimeArray.push(callArgs, state.selfRef);
RuntimeArray.push(callArgs, new RuntimeScalar(name));
RuntimeArray.push(callArgs, scalarUndef); // val (undef for external entities)
String rawSysId2 = unresolveSysId(systemId, state);
RuntimeArray.push(callArgs, rawSysId2 != null ? new RuntimeScalar(rawSysId2) : scalarUndef);
RuntimeArray.push(callArgs, rawSysId != null ? new RuntimeScalar(rawSysId) : scalarUndef);
RuntimeArray.push(callArgs, publicId != null ? new RuntimeScalar(publicId) : scalarUndef);
RuntimeArray.push(callArgs, new RuntimeScalar(notationName)); // ndata
RuntimeArray.push(callArgs, scalarZero); // is_param
Expand All @@ -1590,12 +1590,13 @@ public void unparsedEntityDecl(String name, String publicId, String systemId,
@Override
public void notationDecl(String name, String publicId, String systemId)
throws SAXException {
String rawNotSysId = unresolveSysId(systemId, state);
rememberRawSystemId(rawNotSysId, state);
if (state.notationHandler != null) {
RuntimeArray callArgs = new RuntimeArray();
RuntimeArray.push(callArgs, state.selfRef);
RuntimeArray.push(callArgs, new RuntimeScalar(name));
RuntimeArray.push(callArgs, state.base != null ? new RuntimeScalar(state.base) : scalarUndef);
String rawNotSysId = unresolveSysId(systemId, state);
RuntimeArray.push(callArgs, rawNotSysId != null ? new RuntimeScalar(rawNotSysId) : scalarUndef);
RuntimeArray.push(callArgs, publicId != null ? new RuntimeScalar(publicId) : scalarUndef);
try {
Expand Down Expand Up @@ -1665,6 +1666,7 @@ public void endCDATA() throws SAXException {

@Override
public void startDTD(String name, String publicId, String systemId) throws SAXException {
rememberRawSystemId(systemId, state);
if (state.doctypeHandler != null) {
RuntimeArray callArgs = new RuntimeArray();
RuntimeArray.push(callArgs, state.selfRef);
Expand Down Expand Up @@ -1732,12 +1734,13 @@ public void internalEntityDecl(String name, String value) throws SAXException {
@Override
public void externalEntityDecl(String name, String publicId, String systemId)
throws SAXException {
String rawExtSysId = unresolveSysId(systemId, state);
rememberRawSystemId(rawExtSysId, state);
if (state.entityDeclHandler != null) {
RuntimeArray callArgs = new RuntimeArray();
RuntimeArray.push(callArgs, state.selfRef);
RuntimeArray.push(callArgs, new RuntimeScalar(name));
RuntimeArray.push(callArgs, scalarUndef); // value (external entities have no inline value)
String rawExtSysId = unresolveSysId(systemId, state);
RuntimeArray.push(callArgs, rawExtSysId != null ? new RuntimeScalar(rawExtSysId) : scalarUndef);
RuntimeArray.push(callArgs, publicId != null ? new RuntimeScalar(publicId) : scalarUndef);
RuntimeArray.push(callArgs, scalarUndef); // notation
Expand Down Expand Up @@ -2135,6 +2138,10 @@ private static String escapeXmlAttr(String value) {
*/
private static String unresolveSysId(String systemId, ParserState state) {
if (systemId == null) return null;
if (state.rawSystemIds != null) {
String raw = state.rawSystemIds.get(systemId);
if (raw != null) return raw;
}
// Try to strip the parse base URI that we set on the InputSource
if (state.parseBaseUri != null && systemId.startsWith(state.parseBaseUri)) {
return systemId.substring(state.parseBaseUri.length());
Expand All @@ -2153,32 +2160,50 @@ private static String unresolveSysId(String systemId, ParserState state) {
return systemId.substring(base.length());
}
}
// Try to strip file:// + CWD prefix to recover relative or absolute file paths
// Preserve explicit file: URIs. Expat passes the lexical SYSTEM id through
// to Perl callbacks, so `file:///tmp/x` must not become `/tmp/x`.
if (systemId.startsWith("file:")) {
try {
String cwd = System.getProperty("user.dir");
String filePath;
if (systemId.startsWith("file:///")) {
filePath = systemId.substring(7); // file:///path -> /path
} else if (systemId.startsWith("file://")) {
filePath = systemId.substring(7); // file://path -> path
} else if (systemId.startsWith("file:/")) {
filePath = systemId.substring(5); // file:/path -> /path
} else {
filePath = systemId.substring(5); // file:path -> path
}
if (cwd != null) {
String cwdWithSlash = cwd.endsWith("/") ? cwd : cwd + "/";
if (filePath.startsWith(cwdWithSlash)) {
return filePath.substring(cwdWithSlash.length());
}
}
return filePath;
} catch (Exception ignored) {}
return systemId;
}
return systemId;
}

private static void rememberRawSystemId(String rawSystemId, ParserState state) {
if (rawSystemId == null) return;
if (state.rawSystemIds == null) {
state.rawSystemIds = new HashMap<>();
}
state.rawSystemIds.put(rawSystemId, rawSystemId);
try {
java.net.URI rawUri = new java.net.URI(rawSystemId);
if (rawUri.isAbsolute()) {
state.rawSystemIds.put(rawUri.toString(), rawSystemId);
} else if (rawSystemId.startsWith("/")) {
state.rawSystemIds.put(new File(rawSystemId).toURI().toString(), rawSystemId);
} else if (state.parseBaseUri != null) {
state.rawSystemIds.put(new java.net.URI(state.parseBaseUri).resolve(rawUri).toString(), rawSystemId);
}
} catch (Exception ignored) {
}
}

private static void prepareInputBytes(ParserState state, byte[] xmlBytes) {
state.bytesProcessed = 0;
state.inputBytes = xmlBytes;
state.inputScanPos = 0;
state.rawSystemIds = null;
rememberRawSystemIdsFromInput(state);
}

private static void rememberRawSystemIdsFromInput(ParserState state) {
if (state.inputBytes == null) return;
String input = new String(state.inputBytes, StandardCharsets.ISO_8859_1);
Matcher matcher = SYSTEM_ID_PATTERN.matcher(input);
while (matcher.find()) {
rememberRawSystemId(matcher.group(2), state);
}
}

/**
* Format an error with line/column info, matching expat error format.
* SAX error messages are wrapped with "not well-formed (invalid token)"
Expand Down
9 changes: 9 additions & 0 deletions src/main/perl/lib/CPAN/Config.pm
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ sub _bootstrap_prefs {
'IO-HTML.yml' => 'PerlOnJava/CpanDistroprefs/IO-HTML.yml',
'Image-BMP.yml' => 'PerlOnJava/CpanDistroprefs/Image-BMP.yml',
'Javascript-Menu-Full.yml' => 'PerlOnJava/CpanDistroprefs/Javascript-Menu-Full.yml',
'CGI-Widget-Tabs.yml' => 'PerlOnJava/CpanDistroprefs/CGI-Widget-Tabs.yml',
'Devel-Symdump.yml' => 'PerlOnJava/CpanDistroprefs/Devel-Symdump.yml',
'Pod-Parser.yml' => 'PerlOnJava/CpanDistroprefs/Pod-Parser.yml',
'Test-Class.yml' => 'PerlOnJava/CpanDistroprefs/Test-Class.yml',
'ExtUtils-CBuilder.yml' => 'PerlOnJava/CpanDistroprefs/ExtUtils-CBuilder.yml',
'ExtUtils-ParseXS.yml' => 'PerlOnJava/CpanDistroprefs/ExtUtils-ParseXS.yml',
'Module-Build.yml' => 'PerlOnJava/CpanDistroprefs/Module-Build.yml',
Expand Down Expand Up @@ -82,6 +86,7 @@ sub _bootstrap_prefs {
'HTTP-Daemon.yml' => 'PerlOnJava/CpanDistroprefs/HTTP-Daemon.yml',
'WWW-RobotRules.yml' => 'PerlOnJava/CpanDistroprefs/WWW-RobotRules.yml',
'libwww-perl.yml' => 'PerlOnJava/CpanDistroprefs/libwww-perl.yml',
'LWP-Protocol-https.yml' => 'PerlOnJava/CpanDistroprefs/LWP-Protocol-https.yml',
'PerlIO-via-Timeout.yml' => 'PerlOnJava/CpanDistroprefs/PerlIO-via-Timeout.yml',
);
$pref_install{'OpenAI-API.yml'} = $ENV{PERLONJAVA_OPENAI_LIVE_TESTING}
Expand Down Expand Up @@ -177,6 +182,8 @@ sub _bootstrap_patches {
'PerlOnJava/CpanPatches/Image-BMP-1.26/BMP.pm.patch' ],
[ 'Javascript-Menu-Full-2.02/NoCGIDependency.patch',
'PerlOnJava/CpanPatches/Javascript-Menu-Full-2.02/NoCGIDependency.patch' ],
[ 'CGI-Widget-Tabs-1.14/OptionalAuthorAndCGITests.patch',
'PerlOnJava/CpanPatches/CGI-Widget-Tabs-1.14/OptionalAuthorAndCGITests.patch' ],
[ 'Data-Dmp-0.242/PerlOnJava.patch',
'PerlOnJava/CpanPatches/Data-Dmp-0.242/PerlOnJava.patch' ],
[ 'Capture-Tiny-0.50/NoForkTeeCatchErrors.patch',
Expand All @@ -187,6 +194,8 @@ sub _bootstrap_patches {
'PerlOnJava/CpanPatches/Error-Pure-0.34/PlainLexicalConstants.patch' ],
[ 'String-ShellQuote-1.04/SkipForkScriptTests.patch',
'PerlOnJava/CpanPatches/String-ShellQuote-1.04/SkipForkScriptTests.patch' ],
[ 'LWP-Protocol-https-6.15/SkipForkProxyTest.patch',
'PerlOnJava/CpanPatches/LWP-Protocol-https-6.15/SkipForkProxyTest.patch' ],
[ 'Type-Tiny-2.010001/SkipRegexCallbackTests.patch',
'PerlOnJava/CpanPatches/Type-Tiny-2.010001/SkipRegexCallbackTests.patch' ],
[ 'PerlIO-via-Timeout-0.32/SkipViaRuntimeTest.patch',
Expand Down
16 changes: 16 additions & 0 deletions src/main/perl/lib/PerlOnJava/CpanDistroprefs/CGI-Widget-Tabs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
comment: |
PerlOnJava distroprefs for CGI::Widget::Tabs.

CGI::Widget::Tabs 1.14 declares Test::Distribution as a build requirement
even though t/01_distribution.t already treats it as optional author-test
coverage. Pulling it in adds Pod::Coverage dependencies that are unrelated
to installing CGI::Widget::Tabs.

Its main test also tries to skip itself when neither CGI nor CGI::Minimal is
installed, but it emits a fixed 11-test plan first. Patch the distribution
so these optional test dependencies are genuinely optional.
match:
distribution: "^SRSHAH/CGI-Widget-Tabs-"
patches:
- "CGI-Widget-Tabs-1.14/OptionalAuthorAndCGITests.patch"
12 changes: 12 additions & 0 deletions src/main/perl/lib/PerlOnJava/CpanDistroprefs/Devel-Symdump.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
comment: |
PerlOnJava distroprefs for Devel::Symdump.

Devel::Symdump is a pure-Perl prerequisite of Pod::Coverage. Its upstream
tests assert exact Perl symbol table details, including entries that differ
under PerlOnJava's runtime, but the built module is still useful to downstream
POD coverage tooling.
match:
distribution: "^ANDK/Devel-Symdump-"
test:
commandline: "PERLONJAVA_TEST_IGNORE_FAILURES"
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ comment: |
Allow installation so downstream pure-Perl modules such as Class::Utils can
test against the built module.
match:
distribution: "^SKIM/Error-Pure-"
distribution: "^SKIM/Error-Pure-[0-9]"
patches:
- "Error-Pure-0.34/PlainLexicalConstants.patch"
test:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
comment: |
PerlOnJava distroprefs for LWP::Protocol::https.

t/https_proxy.t forks a child process to run local proxy/server sockets.
PerlOnJava does not implement POSIX fork(), so skip that one test on
no-fork platforms while leaving the rest of the upstream suite intact.
match:
distribution: "^OALDERS/LWP-Protocol-https-"
patches:
- "LWP-Protocol-https-6.15/SkipForkProxyTest.patch"
12 changes: 12 additions & 0 deletions src/main/perl/lib/PerlOnJava/CpanDistroprefs/Pod-Parser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
comment: |
PerlOnJava distroprefs for Pod::Parser.

Pod::Parser supplies Pod::Find and Pod::Parser for Pod::Coverage. The 1.67
test suite expects File::Find to resolve to a filesystem core-library path,
while PerlOnJava loads bundled core modules from jar:PERL5LIB. Ignore that
packaging-path assertion so downstream CPAN distributions can install.
match:
distribution: "^MAREKR/Pod-Parser-"
test:
commandline: "PERLONJAVA_TEST_IGNORE_FAILURES"
13 changes: 13 additions & 0 deletions src/main/perl/lib/PerlOnJava/CpanDistroprefs/Test-Class.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
comment: |
PerlOnJava distroprefs for Test::Class.

Test::Class is a pure-Perl test helper used by downstream CPAN test-only
dependencies such as Test::MockTime::HiRes. Its upstream suite exercises
Test::Builder diagnostic formatting and several attribute/CHECK edge cases
that differ under PerlOnJava, while the built module remains usable for the
downstream tests that require it.
match:
distribution: "^SZABGAB/Test-Class-"
test:
commandline: "PERLONJAVA_TEST_IGNORE_FAILURES"
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
--- Build.PL.orig
+++ Build.PL
@@ -5,7 +5,6 @@ Module::Build->new(
license => 'perl',
module_name => 'CGI::Widget::Tabs',
build_requires => {
- 'Test::Distribution' => 1.14,
'Test::More' => 0,
},
requires => {
--- META.yml.orig
+++ META.yml
@@ -9,7 +9,6 @@ requires:
HTML::Entities: 0
URI::Escape: 0
build_requires:
- Test::Distribution: 1.14
Test::More: 0
provides:
CGI::Widget::Tabs:
--- t/02_main.t.orig
+++ t/02_main.t
@@ -3,11 +3,15 @@
use Test;
use CGI::Widget::Tabs;

-BEGIN { plan tests => 11 };
-
-my $cgi;
-if ( $cgi = cgi_available() ) {
- ok(1); # If we made it this far, we're ok.
+my $cgi = cgi_available();
+unless ($cgi) {
+ print "1..0 # Skip CGI or CGI::Minimal not installed\n";
+ exit 0;
+}
+
+plan tests => 11;
+
+ok(1); # If we made it this far, we're ok.

# --- First the simple headings

@@ -83,9 +87,7 @@
query => "t3",
cgi => $cgi } ),
"t3" );

-}
-

############################################################

Loading
Loading