1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.fourthline.cling.binding.xml;
17
18 import org.fourthline.cling.model.ValidationException;
19 import org.fourthline.cling.model.meta.Device;
20 import org.seamless.util.Exceptions;
21 import org.seamless.xml.ParserException;
22 import org.seamless.xml.XmlPullParserUtils;
23 import org.xml.sax.SAXParseException;
24
25 import java.util.Locale;
26 import java.util.logging.Logger;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30
31
32
33 public class RecoveringUDA10DeviceDescriptorBinderImpl extends UDA10DeviceDescriptorBinderImpl {
34
35 private static Logger log = Logger.getLogger(RecoveringUDA10DeviceDescriptorBinderImpl.class.getName());
36
37 @Override
38 public <D extends Device> D describe(D undescribedDevice, String descriptorXml) throws DescriptorBindingException, ValidationException {
39
40 D device = null;
41 DescriptorBindingException originalException;
42 try {
43
44 try {
45 if (descriptorXml != null)
46 descriptorXml = descriptorXml.trim();
47 device = super.describe(undescribedDevice, descriptorXml);
48 return device;
49 } catch (DescriptorBindingException ex) {
50 log.warning("Regular parsing failed: " + Exceptions.unwrap(ex).getMessage());
51 originalException = ex;
52 }
53
54 String fixedXml;
55
56
57 fixedXml = fixGarbageLeadingChars(descriptorXml);
58 if (fixedXml != null) {
59 try {
60 device = super.describe(undescribedDevice, fixedXml);
61 return device;
62 } catch (DescriptorBindingException ex) {
63 log.warning("Removing leading garbage didn't work: " + Exceptions.unwrap(ex).getMessage());
64 }
65 }
66
67 fixedXml = fixGarbageTrailingChars(descriptorXml, originalException);
68 if (fixedXml != null) {
69 try {
70 device = super.describe(undescribedDevice, fixedXml);
71 return device;
72 } catch (DescriptorBindingException ex) {
73 log.warning("Removing trailing garbage didn't work: " + Exceptions.unwrap(ex).getMessage());
74 }
75 }
76
77
78 DescriptorBindingException lastException = originalException;
79 fixedXml = descriptorXml;
80 for (int retryCount = 0; retryCount < 5; retryCount++) {
81 fixedXml = fixMissingNamespaces(fixedXml, lastException);
82 if (fixedXml != null) {
83 try {
84 device = super.describe(undescribedDevice, fixedXml);
85 return device;
86 } catch (DescriptorBindingException ex) {
87 log.warning("Fixing namespace prefix didn't work: " + Exceptions.unwrap(ex).getMessage());
88 lastException = ex;
89 }
90 } else {
91 break;
92 }
93 }
94
95 fixedXml = XmlPullParserUtils.fixXMLEntities(descriptorXml);
96 if(!fixedXml.equals(descriptorXml)) {
97 try {
98 device = super.describe(undescribedDevice, fixedXml);
99 return device;
100 } catch (DescriptorBindingException ex) {
101 log.warning("Fixing XML entities didn't work: " + Exceptions.unwrap(ex).getMessage());
102 }
103 }
104
105 handleInvalidDescriptor(descriptorXml, originalException);
106
107 } catch (ValidationException ex) {
108 device = handleInvalidDevice(descriptorXml, device, ex);
109 if (device != null)
110 return device;
111 }
112 throw new IllegalStateException("No device produced, did you swallow exceptions in your subclass?");
113 }
114
115 private String fixGarbageLeadingChars(String descriptorXml) {
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133 int index = descriptorXml.indexOf("<?xml");
134 if(index == -1) return descriptorXml;
135 return descriptorXml.substring(index);
136 }
137
138 protected String fixGarbageTrailingChars(String descriptorXml, DescriptorBindingException ex) {
139 int index = descriptorXml.indexOf("</root>");
140 if (index == -1) {
141 log.warning("No closing </root> element in descriptor");
142 return null;
143 }
144 if (descriptorXml.length() != index + "</root>".length()) {
145 log.warning("Detected garbage characters after <root> node, removing");
146 return descriptorXml.substring(0, index) + "</root>";
147 }
148 return null;
149 }
150
151 protected String fixMissingNamespaces(String descriptorXml, DescriptorBindingException ex) {
152
153
154
155
156 Throwable cause = ex.getCause();
157 if (!((cause instanceof SAXParseException) || (cause instanceof ParserException)))
158 return null;
159 String message = cause.getMessage();
160 if (message == null)
161 return null;
162
163 Pattern pattern = Pattern.compile("The prefix \"(.*)\" for element");
164 Matcher matcher = pattern.matcher(message);
165 if (!matcher.find() || matcher.groupCount() != 1) {
166 pattern = Pattern.compile("undefined prefix: ([^ ]*)");
167 matcher = pattern.matcher(message);
168 if (!matcher.find() || matcher.groupCount() != 1)
169 return null;
170 }
171
172 String missingNS = matcher.group(1);
173 log.warning("Fixing missing namespace declaration for: " + missingNS);
174
175
176 pattern = Pattern.compile("<root([^>]*)");
177 matcher = pattern.matcher(descriptorXml);
178 if (!matcher.find() || matcher.groupCount() != 1) {
179 log.fine("Could not find <root> element attributes");
180 return null;
181 }
182
183 String rootAttributes = matcher.group(1);
184 log.fine("Preserving existing <root> element attributes/namespace declarations: " + matcher.group(0));
185
186
187 pattern = Pattern.compile("<root[^>]*>(.*)</root>", Pattern.DOTALL);
188 matcher = pattern.matcher(descriptorXml);
189 if (!matcher.find() || matcher.groupCount() != 1) {
190 log.fine("Could not extract body of <root> element");
191 return null;
192 }
193
194 String rootBody = matcher.group(1);
195
196
197 return "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
198 + "<root "
199 + String.format(Locale.ROOT, "xmlns:%s=\"urn:schemas-dlna-org:device-1-0\"", missingNS) + rootAttributes + ">"
200 + rootBody
201 + "</root>";
202
203
204
205 }
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220 protected void handleInvalidDescriptor(String xml, DescriptorBindingException exception)
221 throws DescriptorBindingException {
222 throw exception;
223 }
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245 protected <D extends Device> D handleInvalidDevice(String xml, D device, ValidationException exception)
246 throws ValidationException {
247 throw exception;
248 }
249 }