1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xml.internal.utils;
  22 
  23 import java.util.ArrayList;
  24 import java.util.List;
  25 import java.util.StringTokenizer;
  26 import javax.xml.transform.Source;
  27 import javax.xml.transform.TransformerException;
  28 import javax.xml.transform.URIResolver;
  29 import javax.xml.transform.sax.SAXSource;
  30 import org.xml.sax.Attributes;
  31 import org.xml.sax.InputSource;
  32 import org.xml.sax.helpers.DefaultHandler;
  33 
  34 /**
  35  * Search for the xml-stylesheet processing instructions in an XML document.
  36  * @see <a href="http://www.w3.org/TR/xml-stylesheet/">
  37  * Associating Style Sheets with XML documents, Version 1.0</a>
  38  *
  39  * @LastModified: Oct 2017
  40  */
  41 public class StylesheetPIHandler extends DefaultHandler
  42 {
  43   /** The baseID of the document being processed.  */
  44   String m_baseID;
  45 
  46   /** The desired media criteria. */
  47   String m_media;
  48 
  49   /** The desired title criteria.  */
  50   String m_title;
  51 
  52   /** The desired character set criteria.   */
  53   String m_charset;
  54 
  55   /** A list of SAXSource objects that match the criteria.  */
  56   List<Source> m_stylesheets = new ArrayList<>();
  57 
  58   // Add code to use a URIResolver. Patch from Dmitri Ilyin.
  59 
  60   /**
  61    * The object that implements the URIResolver interface,
  62    * or null.
  63    */
  64   URIResolver m_uriResolver;
  65 
  66   /**
  67    * Get the object that will be used to resolve URIs in href
  68    * in xml-stylesheet processing instruction.
  69    *
  70    * @param resolver An object that implements the URIResolver interface,
  71    * or null.
  72    */
  73   public void setURIResolver(URIResolver resolver)
  74   {
  75     m_uriResolver = resolver;
  76   }
  77 
  78   /**
  79    * Get the object that will be used to resolve URIs in href
  80    * in xml-stylesheet processing instruction.
  81    *
  82    * @return The URIResolver that was set with setURIResolver.
  83    */
  84   public URIResolver getURIResolver()
  85   {
  86     return m_uriResolver;
  87   }
  88 
  89   /**
  90    * Construct a StylesheetPIHandler instance that will search
  91    * for xml-stylesheet PIs based on the given criteria.
  92    *
  93    * @param baseID The base ID of the XML document, needed to resolve
  94    *               relative IDs.
  95    * @param media The desired media criteria.
  96    * @param title The desired title criteria.
  97    * @param charset The desired character set criteria.
  98    */
  99   public StylesheetPIHandler(String baseID, String media, String title,
 100                              String charset)
 101   {
 102 
 103     m_baseID = baseID;
 104     m_media = media;
 105     m_title = title;
 106     m_charset = charset;
 107   }
 108 
 109   /**
 110    * Return the last stylesheet found that match the constraints.
 111    *
 112    * @return Source object that references the last stylesheet reference
 113    *         that matches the constraints.
 114    */
 115   public Source getAssociatedStylesheet()
 116   {
 117 
 118     int sz = m_stylesheets.size();
 119 
 120     if (sz > 0)
 121     {
 122       Source source = m_stylesheets.get(sz-1);
 123       return source;
 124     }
 125     else
 126       return null;
 127   }
 128 
 129   /**
 130    * Handle the xml-stylesheet processing instruction.
 131    *
 132    * @param target The processing instruction target.
 133    * @param data The processing instruction data, or null if
 134    *             none is supplied.
 135    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 136    *            wrapping another exception.
 137    * @see org.xml.sax.ContentHandler#processingInstruction
 138    * @see <a href="http://www.w3.org/TR/xml-stylesheet/">
 139    * Associating Style Sheets with XML documents, Version 1.0</a>
 140    */
 141   public void processingInstruction(String target, String data)
 142           throws org.xml.sax.SAXException
 143   {
 144 
 145     if (target.equals("xml-stylesheet"))
 146     {
 147       String href = null;  // CDATA #REQUIRED
 148       String type = null;  // CDATA #REQUIRED
 149       String title = null;  // CDATA #IMPLIED
 150       String media = null;  // CDATA #IMPLIED
 151       String charset = null;  // CDATA #IMPLIED
 152       boolean alternate = false;  // (yes|no) "no"
 153       StringTokenizer tokenizer = new StringTokenizer(data, " \t=\n", true);
 154       boolean lookedAhead = false;
 155       Source source = null;
 156 
 157       String token = "";
 158       while (tokenizer.hasMoreTokens())
 159       {
 160         if (!lookedAhead)
 161           token = tokenizer.nextToken();
 162         else
 163           lookedAhead = false;
 164         if (tokenizer.hasMoreTokens() &&
 165                (token.equals(" ") || token.equals("\t") || token.equals("=")))
 166           continue;
 167 
 168         String name = token;
 169         if (name.equals("type"))
 170         {
 171           token = tokenizer.nextToken();
 172           while (tokenizer.hasMoreTokens() &&
 173                (token.equals(" " ) || token.equals("\t") || token.equals("=")))
 174             token = tokenizer.nextToken();
 175           type = token.substring(1, token.length() - 1);
 176 
 177         }
 178         else if (name.equals("href"))
 179         {
 180           token = tokenizer.nextToken();
 181           while (tokenizer.hasMoreTokens() &&
 182                (token.equals(" " ) || token.equals("\t") || token.equals("=")))
 183             token = tokenizer.nextToken();
 184           href = token;
 185           if (tokenizer.hasMoreTokens())
 186           {
 187             token = tokenizer.nextToken();
 188             // If the href value has parameters to be passed to a
 189             // servlet(something like "foobar?id=12..."),
 190             // we want to make sure we get them added to
 191             // the href value. Without this check, we would move on
 192             // to try to process another attribute and that would be
 193             // wrong.
 194             // We need to set lookedAhead here to flag that we
 195             // already have the next token.
 196             while ( token.equals("=") && tokenizer.hasMoreTokens())
 197             {
 198               href = href + token + tokenizer.nextToken();
 199               if (tokenizer.hasMoreTokens())
 200               {
 201                 token = tokenizer.nextToken();
 202                 lookedAhead = true;
 203               }
 204               else
 205               {
 206                 break;
 207               }
 208             }
 209           }
 210           href = href.substring(1, href.length() - 1);
 211           try
 212           {
 213             // Add code to use a URIResolver. Patch from Dmitri Ilyin.
 214             if (m_uriResolver != null)
 215             {
 216               source = m_uriResolver.resolve(href, m_baseID);
 217             }
 218            else
 219             {
 220               href = SystemIDResolver.getAbsoluteURI(href, m_baseID);
 221               source = new SAXSource(new InputSource(href));
 222             }
 223           }
 224           catch(TransformerException te)
 225           {
 226             throw new org.xml.sax.SAXException(te);
 227           }
 228         }
 229         else if (name.equals("title"))
 230         {
 231           token = tokenizer.nextToken();
 232           while (tokenizer.hasMoreTokens() &&
 233                (token.equals(" " ) || token.equals("\t") || token.equals("=")))
 234             token = tokenizer.nextToken();
 235           title = token.substring(1, token.length() - 1);
 236         }
 237         else if (name.equals("media"))
 238         {
 239           token = tokenizer.nextToken();
 240           while (tokenizer.hasMoreTokens() &&
 241                (token.equals(" " ) || token.equals("\t") || token.equals("=")))
 242             token = tokenizer.nextToken();
 243           media = token.substring(1, token.length() - 1);
 244         }
 245         else if (name.equals("charset"))
 246         {
 247           token = tokenizer.nextToken();
 248           while (tokenizer.hasMoreTokens() &&
 249               (token.equals(" " ) || token.equals("\t") || token.equals("=")))
 250             token = tokenizer.nextToken();
 251           charset = token.substring(1, token.length() - 1);
 252         }
 253         else if (name.equals("alternate"))
 254         {
 255           token = tokenizer.nextToken();
 256           while (tokenizer.hasMoreTokens() &&
 257                (token.equals(" " ) || token.equals("\t") || token.equals("=")))
 258             token = tokenizer.nextToken();
 259           alternate = token.substring(1, token.length()
 260                                              - 1).equals("yes");
 261         }
 262 
 263       }
 264 
 265       if ((null != type)
 266           && (type.equals("text/xsl") || type.equals("text/xml") || type.equals("application/xml+xslt"))
 267           && (null != href))
 268       {
 269         if (null != m_media)
 270         {
 271           if (null != media)
 272           {
 273             if (!media.equals(m_media))
 274               return;
 275           }
 276           else
 277             return;
 278         }
 279 
 280         if (null != m_charset)
 281         {
 282           if (null != charset)
 283           {
 284             if (!charset.equals(m_charset))
 285               return;
 286           }
 287           else
 288             return;
 289         }
 290 
 291         if (null != m_title)
 292         {
 293           if (null != title)
 294           {
 295             if (!title.equals(m_title))
 296               return;
 297           }
 298           else
 299             return;
 300         }
 301 
 302         m_stylesheets.add(source);
 303       }
 304     }
 305   }
 306 
 307 
 308   /**
 309    * The spec notes that "The xml-stylesheet processing instruction is allowed only in the prolog of an XML document.",
 310    * so, at least for right now, I'm going to go ahead an throw a TransformerException
 311    * in order to stop the parse.
 312    *
 313    * @param namespaceURI The Namespace URI, or an empty string.
 314    * @param localName The local name (without prefix), or empty string if not namespace processing.
 315    * @param qName The qualified name (with prefix).
 316    * @param atts  The specified or defaulted attributes.
 317    *
 318    * @throws StopParseException since there can be no valid xml-stylesheet processing
 319    *                            instructions past the first element.
 320    */
 321   public void startElement(
 322           String namespaceURI, String localName, String qName, Attributes atts)
 323             throws org.xml.sax.SAXException
 324   {
 325     throw new StopParseException();
 326   }
 327 
 328   /**
 329     * Added additional getter and setter methods for the Base Id
 330     * to fix bugzilla bug 24187
 331     *
 332     */
 333    public void setBaseId(String baseId) {
 334        m_baseID = baseId;
 335 
 336    }
 337    public String  getBaseId() {
 338        return m_baseID ;
 339    }
 340 
 341 }