1515 */
1616package io .goobi .viewer .connector .oai .model .formats ;
1717
18- import java .io .FileInputStream ;
18+ import java .io .File ;
1919import java .io .FileNotFoundException ;
2020import java .io .IOException ;
21+ import java .io .InputStream ;
22+ import java .net .MalformedURLException ;
23+ import java .net .URI ;
24+ import java .net .URISyntaxException ;
25+ import java .net .URL ;
26+ import java .nio .file .Path ;
27+ import java .nio .file .Paths ;
2128import java .util .Arrays ;
2229import java .util .List ;
2330
31+ import javax .xml .XMLConstants ;
32+ import javax .xml .transform .Transformer ;
33+ import javax .xml .transform .TransformerException ;
34+ import javax .xml .transform .TransformerFactory ;
35+ import javax .xml .transform .URIResolver ;
36+ import javax .xml .transform .stream .StreamSource ;
37+
2438import org .apache .logging .log4j .LogManager ;
2539import org .apache .logging .log4j .Logger ;
2640import org .apache .solr .client .solrj .SolrServerException ;
41+ import org .jdom2 .Document ;
2742import org .jdom2 .Element ;
2843import org .jdom2 .Namespace ;
29- import org .jdom2 .transform .XSLTransformException ;
30- import org .jdom2 .transform .XSLTransformer ;
44+ import org .jdom2 .transform .JDOMResult ;
45+ import org .jdom2 .transform .JDOMSource ;
3146
3247import io .goobi .viewer .connector .DataManager ;
3348import io .goobi .viewer .connector .oai .RequestHandler ;
@@ -47,6 +62,8 @@ public class MARCXMLFormat extends METSFormat {
4762 static final Namespace NAMESPACE_MARC = Namespace .getNamespace ("marc" , "http://www.loc.gov/MARC21/slim" );
4863 static final Namespace NAMESPACE_MODS = Namespace .getNamespace ("mods" , "http://www.loc.gov/mods/v3" );
4964
65+ private static final String FILENAME_XSLT = "MODS2MARC21slim.xsl" ;
66+
5067 /** {@inheritDoc} */
5168 @ Override
5269 public Element createListRecords (RequestHandler handler , int firstVirtualRow , int firstRawRow , int numRows , String versionDiscriminatorField ,
@@ -178,10 +195,42 @@ static Element convertModsToMarc(Element mods, Element header) {
178195 org .jdom2 .Document marcDoc = new org .jdom2 .Document ();
179196 marcDoc .setRootElement (newmods );
180197
181- String filename = DataManager .getInstance ().getConfiguration ().getMods2MarcXsl ();
182- try (FileInputStream fis = new FileInputStream (filename )) {
183- XSLTransformer transformer = new XSLTransformer (fis );
184- org .jdom2 .Document docTrans = transformer .transform (marcDoc );
198+ ClassLoader cl = MARCXMLFormat .class .getClassLoader ();
199+ URL xsltUrl = null ;
200+ File localXsltFile = new File (DataManager .getInstance ().getConfiguration ().getMods2MarcXsl ());
201+ if (localXsltFile .exists ()) {
202+ // Check for local XSLT file
203+ try {
204+ xsltUrl = localXsltFile .toURI ().toURL ();
205+ logger .debug ("Using local XSLT: {}" , localXsltFile .getAbsolutePath ());
206+ } catch (MalformedURLException e ) {
207+ throw new IllegalStateException ("Invalid path for local override XSLT" , e );
208+ }
209+ }
210+ if (xsltUrl == null ) {
211+ // In-JAR XSLT fallback
212+ xsltUrl = cl .getResource (FILENAME_XSLT );
213+ if (xsltUrl == null ) {
214+ throw new IllegalStateException ("Default XSLT not found: " + FILENAME_XSLT );
215+ }
216+ }
217+
218+ try (InputStream input = xsltUrl .openStream ()) {
219+ StreamSource xsltSource = new StreamSource (input );
220+ xsltSource .setSystemId (xsltUrl .toExternalForm ());
221+
222+ TransformerFactory factory = TransformerFactory .newInstance ();
223+ factory .setFeature (XMLConstants .FEATURE_SECURE_PROCESSING , true );
224+ factory .setAttribute (XMLConstants .ACCESS_EXTERNAL_DTD , "" );
225+ factory .setAttribute (XMLConstants .ACCESS_EXTERNAL_STYLESHEET , "" );
226+ factory .setURIResolver (createXsltUriResolver ());
227+
228+ Transformer transformer = factory .newTransformer (xsltSource );
229+
230+ JDOMSource source = new JDOMSource (marcDoc );
231+ JDOMResult result = new JDOMResult ();
232+ transformer .transform (source , result );
233+ Document docTrans = result .getDocument ();
185234 Element root = docTrans .getRootElement ();
186235
187236 Element eleRecord = new Element (XmlConstants .ELE_NAME_RECORD , NAMESPACE_XML );
@@ -201,9 +250,66 @@ static Element convertModsToMarc(Element mods, Element header) {
201250 } catch (FileNotFoundException e ) {
202251 logger .error (e .getMessage ());
203252 return new ErrorCode ().getCannotDisseminateFormat ();
204- } catch (IOException | XSLTransformException e ) {
253+ } catch (IOException | TransformerException e ) {
205254 logger .error (e .getMessage (), e );
206255 return new ErrorCode ().getCannotDisseminateFormat ();
207256 }
208257 }
258+
259+ private static URIResolver createXsltUriResolver () {
260+ return (href , base ) -> {
261+ // If base is null, just try classpath
262+ if (base == null ) {
263+ InputStream is = MARCXMLFormat .class .getClassLoader ().getResourceAsStream (href );
264+ if (is == null ) {
265+ throw new TransformerException ("Classpath include not found: " + href );
266+ }
267+ StreamSource src = new StreamSource (is );
268+ src .setSystemId (MARCXMLFormat .class .getClassLoader ()
269+ .getResource (href )
270+ .toExternalForm ());
271+ return src ;
272+ }
273+
274+ try {
275+ URI baseUri = new URI (base );
276+ if ("file" .equals (baseUri .getScheme ())) {
277+ // filesystem include
278+ Path basePath = Paths .get (baseUri ).getParent ();
279+ Path resolved = basePath .resolve (href ).normalize ();
280+
281+ Path allowedRoot = Paths .get (DataManager .getInstance ()
282+ .getConfiguration ()
283+ .getOaiFolder ())
284+ .toAbsolutePath ()
285+ .normalize ();
286+ if (!resolved .startsWith (allowedRoot )) {
287+ throw new TransformerException ("Access denied: " + href );
288+ }
289+ return new StreamSource (resolved .toFile ());
290+
291+ }
292+ // classpath include: resolve relative to base systemId
293+ String basePathStr = baseUri .getPath ();
294+ if (basePathStr == null )
295+ basePathStr = "" ;
296+ int lastSlash = basePathStr .lastIndexOf ('/' );
297+ String parentDir = (lastSlash >= 0 ) ? basePathStr .substring (0 , lastSlash + 1 ) : "" ;
298+ String includedPath = parentDir + href ;
299+
300+ InputStream is = MARCXMLFormat .class .getClassLoader ().getResourceAsStream (includedPath );
301+ if (is == null ) {
302+ throw new TransformerException ("Classpath include not found: " + includedPath );
303+ }
304+ StreamSource src = new StreamSource (is );
305+ src .setSystemId (MARCXMLFormat .class .getClassLoader ()
306+ .getResource (includedPath )
307+ .toExternalForm ());
308+ return src ;
309+
310+ } catch (URISyntaxException e ) {
311+ throw new TransformerException ("Invalid base URI: " + base , e );
312+ }
313+ };
314+ }
209315}
0 commit comments