View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.transport.impl;
17  
18  import org.fourthline.cling.model.UnsupportedDataException;
19  import org.fourthline.cling.model.XMLUtil;
20  import org.fourthline.cling.model.message.gena.IncomingEventRequestMessage;
21  import org.fourthline.cling.transport.spi.GENAEventProcessor;
22  import org.seamless.xml.XmlPullParserUtils;
23  
24  import javax.enterprise.inject.Alternative;
25  import java.util.logging.Logger;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  
30  /**
31   * Implementation based on the <em>Xml Pull Parser</em> XML processing API.
32   * <p>
33   * This processor extends {@link PullGENAEventProcessorImpl}, it will always
34   * first try to read messages regularly with the superclass' methods before
35   * trying to recover from a failure.
36   * </p>
37   * <p>
38   * When the superclass can't read the message, this processor will try to
39   * recover from broken XML by for example, detecting wrongly encoded XML entities,
40   * and working around other vendor-specific bugs caused by incompatible UPnP
41   * stacks in the wild.
42   * </p>
43   * <p>
44   * This processor will also return partial results, if at least a single
45   * state variable value could be recovered from the (broken) event XML.
46   * </p>
47   *
48   * @author Michael Pujos
49   */
50  @Alternative
51  public class RecoveringGENAEventProcessorImpl extends PullGENAEventProcessorImpl {
52  
53      private static Logger log = Logger.getLogger(GENAEventProcessor.class.getName());
54  
55      public void readBody(IncomingEventRequestMessage requestMessage) throws UnsupportedDataException {
56          try {
57              super.readBody(requestMessage);
58          } catch (UnsupportedDataException ex) {
59  
60              // Can't recover from this
61              if (!requestMessage.isBodyNonEmptyString())
62                  throw ex;
63  
64              log.warning("Trying to recover from invalid GENA XML event: " + ex);
65  
66              // Some properties may have been read at this point, so reset the list
67              requestMessage.getStateVariableValues().clear();
68  
69              String body = getMessageBody(requestMessage);
70  
71              String fixedBody = fixXMLEncodedLastChange(
72                  XmlPullParserUtils.fixXMLEntities(body)
73              );
74  
75              try {
76                  // Try again, if this fails, we are done...
77                  requestMessage.setBody(fixedBody);
78                  super.readBody(requestMessage);
79              } catch (UnsupportedDataException ex2) {
80                  // Check if some properties were read
81                  if (requestMessage.getStateVariableValues().isEmpty()) {
82                      // Throw the initial exception containing unmodified XML
83                      throw ex;
84                  }
85                  log.warning("Partial read of GENA event properties (probably due to truncated XML)");
86              }
87          }
88      }
89  
90      protected String fixXMLEncodedLastChange(String xml) {
91          Pattern pattern = Pattern.compile("<LastChange>(.*)</LastChange>", Pattern.DOTALL);
92          Matcher matcher = pattern.matcher(xml);
93  
94          if (matcher.find() && matcher.groupCount() == 1) {
95  
96              String lastChange = matcher.group(1);
97  
98              if (XmlPullParserUtils.isNullOrEmpty(lastChange))
99                  return xml;
100 
101             lastChange = lastChange.trim();
102 
103             String fixedLastChange = lastChange;
104 
105             if (lastChange.charAt(0) == '<') {
106             // TODO: UPNP VIOLATION: Orange Liveradio does not encode LastChange XML properly
107                 fixedLastChange = XMLUtil.encodeText(fixedLastChange);
108             } else {
109                 /* Doesn't work for Philips NP2900, there is complete garbage after the HTML
110                 // TODO: UPNP VIOLATION: Philips NP2900 inserts garbage HTML, try to fix it
111                 fixedLastChange = fixedLastChange.replaceAll("<", "");
112                 fixedLastChange = fixedLastChange.replaceAll(">", "");
113                 */
114             }
115 
116             if (fixedLastChange.equals(lastChange)) {
117                 return xml;
118             }
119 
120             return "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
121                 "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">" +
122                 "<e:property>" +
123                 "<LastChange>" +
124                 fixedLastChange +
125                 "</LastChange>" +
126                 "</e:property>" +
127                 "</e:propertyset>";
128         }
129         return xml;
130     }
131 }