001 package ezvcard.types;
002
003 import java.io.File;
004 import java.io.FileInputStream;
005 import java.io.IOException;
006 import java.io.InputStream;
007 import java.util.List;
008
009 import org.apache.commons.codec.binary.Base64;
010
011 import ezvcard.VCard;
012 import ezvcard.VCardSubTypes;
013 import ezvcard.VCardVersion;
014 import ezvcard.io.CompatibilityMode;
015 import ezvcard.io.SkipMeException;
016 import ezvcard.parameters.EncodingParameter;
017 import ezvcard.parameters.MediaTypeParameter;
018 import ezvcard.parameters.ValueParameter;
019 import ezvcard.util.DataUri;
020 import ezvcard.util.HCardElement;
021 import ezvcard.util.IOUtils;
022 import ezvcard.util.XCardElement;
023
024 /*
025 Copyright (c) 2012, Michael Angstadt
026 All rights reserved.
027
028 Redistribution and use in source and binary forms, with or without
029 modification, are permitted provided that the following conditions are met:
030
031 1. Redistributions of source code must retain the above copyright notice, this
032 list of conditions and the following disclaimer.
033 2. Redistributions in binary form must reproduce the above copyright notice,
034 this list of conditions and the following disclaimer in the documentation
035 and/or other materials provided with the distribution.
036
037 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
038 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
039 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
041 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
042 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
043 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
045 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
046 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
047
048 The views and conclusions contained in the software and documentation are those
049 of the authors and should not be interpreted as representing official policies,
050 either expressed or implied, of the FreeBSD Project.
051 */
052
053 /**
054 * Represents a Type that contains binary data (for example, the "PHOTO" type).
055 * @author Michael Angstadt
056 *
057 * @param <T> the class used for representing the content type of the resource
058 */
059 public abstract class BinaryType<T extends MediaTypeParameter> extends VCardType {
060 /**
061 * The decoded data.
062 */
063 protected byte[] data;
064
065 /**
066 * The URL to the resource.
067 */
068 protected String url;
069
070 /**
071 * The content type of the resource (e.g. "JPEG image").
072 */
073 protected T contentType;
074
075 /**
076 * @param name the type name (e.g. "PHOTO")
077 */
078 public BinaryType(String name) {
079 super(name);
080 }
081
082 /**
083 * @param name the type name (e.g. "PHOTO")
084 * @param url the URL to the resource
085 * @param type the content type
086 */
087 public BinaryType(String name, String url, T type) {
088 this(name);
089 setUrl(url, type);
090 }
091
092 /**
093 * @param name the type name (e.g. "PHOTO")
094 * @param data the binary data
095 * @param type the content type
096 */
097 public BinaryType(String name, byte[] data, T type) {
098 this(name);
099 setData(data, type);
100 }
101
102 /**
103 * @param name the type name (e.g. "PHOTO")
104 * @param in an input stream to the binary data (will be closed)
105 * @param type the content type
106 * @throws IOException
107 */
108 public BinaryType(String name, InputStream in, T type) throws IOException {
109 this(name, IOUtils.toByteArray(in, true), type);
110 }
111
112 /**
113 * @param name the type name (e.g. "PHOTO")
114 * @param file the file containing the binary data
115 * @param type the content type
116 * @throws IOException
117 */
118 public BinaryType(String name, File file, T type) throws IOException {
119 this(name, new FileInputStream(file), type);
120 }
121
122 /**
123 * Gets the binary data of the resource.
124 * @return the binary data or null if there is none
125 */
126 public byte[] getData() {
127 return data;
128 }
129
130 /**
131 * Sets the binary data of the resource.
132 * @param data the binary data
133 * @param type the content type (e.g. "JPEG image")
134 */
135 public void setData(byte[] data, T type) {
136 this.url = null;
137 this.data = data;
138 setContentType(type);
139 }
140
141 /**
142 * Gets the URL to the resource
143 * @return the URL or null if there is none
144 */
145 public String getUrl() {
146 return url;
147 }
148
149 /**
150 * Sets the URL to the resource.
151 * @param url the URL
152 * @param type the content type (e.g. "JPEG image")
153 */
154 public void setUrl(String url, T type) {
155 this.url = url;
156 this.data = null;
157 setContentType(type);
158 }
159
160 /**
161 * Gets the content type of the resource.
162 * @return the content type (e.g. "JPEG image")
163 */
164 public T getContentType() {
165 return contentType;
166 }
167
168 /**
169 * Sets the content type of the resource.
170 * @param contentType the content type (e.g. "JPEG image")
171 */
172 public void setContentType(T contentType) {
173 this.contentType = contentType;
174 }
175
176 /**
177 * Gets the vCard 4.0 TYPE parameter. This should NOT be used to get the
178 * TYPE parameter for 2.1/3.0 vCards. Use {@link #getContentType} instead.
179 * <p>
180 * vCard versions: 4.0
181 * </p>
182 * @return the TYPE value (typically, this will be either "work" or "home")
183 * or null if it doesn't exist
184 */
185 public String getType() {
186 return subTypes.getType();
187 }
188
189 /**
190 * Sets the vCard 4.0 TYPE parameter. This should NOT be used to set the
191 * TYPE parameter for 2.1/3.0 vCards. Use {@link #setContentType} instead.
192 * <p>
193 * vCard versions: 4.0
194 * </p>
195 * @param type the TYPE value (should be either "work" or "home") or null to
196 * remove
197 */
198 public void setType(String type) {
199 subTypes.setType(type);
200 }
201
202 /**
203 * Gets all PID parameter values.
204 * <p>
205 * vCard versions: 4.0
206 * </p>
207 * @return the PID values or empty set if there are none
208 * @see VCardSubTypes#getPids
209 */
210 public List<Integer[]> getPids() {
211 return subTypes.getPids();
212 }
213
214 /**
215 * Adds a PID value.
216 * <p>
217 * vCard versions: 4.0
218 * </p>
219 * @param localId the local ID
220 * @param clientPidMapRef the ID used to reference the property's globally
221 * unique identifier in the CLIENTPIDMAP property.
222 * @see VCardSubTypes#addPid(int, int)
223 */
224 public void addPid(int localId, int clientPidMapRef) {
225 subTypes.addPid(localId, clientPidMapRef);
226 }
227
228 /**
229 * Removes all PID values.
230 * <p>
231 * vCard versions: 4.0
232 * </p>
233 * @see VCardSubTypes#removePids
234 */
235 public void removePids() {
236 subTypes.removePids();
237 }
238
239 /**
240 * Gets the preference value.
241 * <p>
242 * vCard versions: 4.0
243 * </p>
244 * @return the preference value or null if it doesn't exist
245 * @see VCardSubTypes#getPref
246 */
247 public Integer getPref() {
248 return subTypes.getPref();
249 }
250
251 /**
252 * Sets the preference value.
253 * <p>
254 * vCard versions: 4.0
255 * </p>
256 * @param pref the preference value or null to remove
257 * @see VCardSubTypes#setPref
258 */
259 public void setPref(Integer pref) {
260 subTypes.setPref(pref);
261 }
262
263 /**
264 * Gets the ALTID.
265 * <p>
266 * vCard versions: 4.0
267 * </p>
268 * @return the ALTID or null if it doesn't exist
269 * @see VCardSubTypes#getAltId
270 */
271 public String getAltId() {
272 return subTypes.getAltId();
273 }
274
275 /**
276 * Sets the ALTID.
277 * <p>
278 * vCard versions: 4.0
279 * </p>
280 * @param altId the ALTID or null to remove
281 * @see VCardSubTypes#setAltId
282 */
283 public void setAltId(String altId) {
284 subTypes.setAltId(altId);
285 }
286
287 @Override
288 protected void doMarshalSubTypes(VCardSubTypes copy, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode, VCard vcard) {
289 MediaTypeParameter contentType = this.contentType;
290 if (contentType == null) {
291 contentType = new MediaTypeParameter(null, null, null);
292 }
293
294 if (url != null) {
295 ValueParameter vp = null;
296 switch (version) {
297 case V2_1:
298 vp = ValueParameter.URL;
299 break;
300 case V3_0:
301 case V4_0:
302 vp = ValueParameter.URI;
303 break;
304 }
305 copy.setValue(vp);
306
307 copy.setEncoding(null);
308
309 if (version == VCardVersion.V4_0) {
310 //don't null out TYPE, it could be set to "home" or "work"
311 copy.setMediaType(contentType.getMediaType());
312 } else {
313 copy.setType(contentType.getValue());
314 copy.setMediaType(null);
315 }
316 }
317 if (data != null) {
318 copy.setMediaType(null);
319 if (version == VCardVersion.V2_1) {
320 copy.setEncoding(EncodingParameter.BASE64);
321 copy.setValue(null);
322 copy.setType(contentType.getValue());
323 } else if (version == VCardVersion.V3_0) {
324 copy.setEncoding(EncodingParameter.B);
325 copy.setValue(null);
326 copy.setType(contentType.getValue());
327 } else {
328 copy.setEncoding(null);
329 copy.setValue(ValueParameter.URI);
330 //don't null out TYPE, it could be set to "home" or "work"
331 }
332 }
333 }
334
335 @Override
336 protected void doMarshalText(StringBuilder sb, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) {
337 if (url != null) {
338 sb.append(url);
339 } else if (data != null) {
340 String base64 = new String(Base64.encodeBase64(data));
341 if (version == VCardVersion.V4_0) {
342 String mediaType = (contentType == null || contentType.getMediaType() == null) ? "application/octet-stream" : contentType.getMediaType();
343 DataUri uri = new DataUri(mediaType, data);
344 sb.append(uri.toString());
345 } else {
346 sb.append(base64);
347 }
348 } else {
349 throw new SkipMeException("Property has neither a URL nor binary data attached to it.");
350 }
351 }
352
353 @Override
354 protected void doUnmarshalText(String value, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) {
355 //check for a 4.0 data URI
356 try {
357 DataUri uri = new DataUri(value);
358 T contentType = buildMediaTypeObj(uri.getContentType());
359 setData(uri.getData(), contentType);
360 return;
361 } catch (IllegalArgumentException e) {
362 //not a data URI
363 }
364
365 //get content type from 4.0 MEDIATYPE parameter
366 T contentType = null;
367 String mediaType = subTypes.getMediaType();
368 if (mediaType != null) {
369 contentType = buildMediaTypeObj(mediaType);
370 } else {
371 //get content type from 2.1/3.0 TYPE parameter
372 String type = subTypes.getType();
373 if (type != null && (version == VCardVersion.V2_1 || version == VCardVersion.V3_0)) {
374 contentType = buildTypeObj(getType());
375 }
376 }
377
378 //check for a URL
379 ValueParameter valueSubType = subTypes.getValue();
380 if (valueSubType == ValueParameter.URL || valueSubType == ValueParameter.URI) {
381 setUrl(value, contentType);
382 return;
383 }
384
385 //check for 2.1/3.0 base64 data
386 EncodingParameter encodingSubType = subTypes.getEncoding();
387 if (encodingSubType != null) {
388 if (encodingSubType != EncodingParameter.B && encodingSubType != EncodingParameter.BASE64) {
389 warnings.add("Unrecognized " + EncodingParameter.NAME + " parameter value \"" + encodingSubType + "\" in " + getTypeName() + " property. Attempting to decode as base64.");
390 }
391 setData(Base64.decodeBase64(value), contentType);
392 return;
393 }
394
395 //see if the value is a URL incase they didn't set the VALUE parameter
396 if (value.matches("(?i)http.*")) {
397 setUrl(value, contentType);
398 return;
399 }
400
401 cannotUnmarshalValue(value, version, warnings, compatibilityMode, contentType);
402 }
403
404 @Override
405 protected void doMarshalXml(XCardElement parent, List<String> warnings, CompatibilityMode compatibilityMode) {
406 StringBuilder sb = new StringBuilder();
407 doMarshalText(sb, parent.version(), warnings, compatibilityMode);
408 parent.uri(sb.toString());
409 }
410
411 @Override
412 protected void doUnmarshalXml(XCardElement element, List<String> warnings, CompatibilityMode compatibilityMode) {
413 String value = element.uri();
414 if (value != null) {
415 doUnmarshalText(value, element.version(), warnings, compatibilityMode);
416 }
417 }
418
419 @Override
420 protected void doUnmarshalHtml(HCardElement element, List<String> warnings) {
421 String elementName = element.tagName();
422 String error = null;
423 if ("object".equals(elementName)) {
424 T mediaType = null;
425 String type = element.attr("type");
426 if (type.length() > 0) {
427 mediaType = buildMediaTypeObj(type);
428 }
429
430 String data = element.absUrl("data");
431 if (data.length() > 0) {
432 try {
433 DataUri uri = new DataUri(data);
434 mediaType = buildMediaTypeObj(uri.getContentType());
435 setData(uri.getData(), mediaType);
436 } catch (IllegalArgumentException e) {
437 //TODO create buildTypeObjFromExtension() method
438 setUrl(data, null);
439 }
440 } else {
441 error = "<object> tag does not have a \"data\" attribute.";
442 }
443 } else {
444 error = "Cannot parse HTML tag \"" + elementName + "\".";
445 }
446
447 if (error != null) {
448 throw new SkipMeException(error);
449 }
450 }
451
452 /**
453 * Called if the unmarshalling code cannot determine how to unmarshal the
454 * value.
455 * @param value the value
456 * @param version the version of the vCard
457 * @param warnings the warnings
458 * @param compatibilityMode the compatibility mode
459 * @param contentType the content type of the resource of null if unknown
460 */
461 protected void cannotUnmarshalValue(String value, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode, T contentType) {
462 warnings.add("No " + ValueParameter.NAME + " or " + EncodingParameter.NAME + " parameter given. Attempting to decode as base64.");
463 setData(Base64.decodeBase64(value), contentType);
464 }
465
466 /**
467 * Builds a {@link MediaTypeParameter} object based on the information in
468 * the MEDIATYPE parameter or data URI of 4.0 vCards.
469 * @param mediaType the media type string (e.g. "image/jpeg")
470 * @return the parameter object
471 */
472 protected abstract T buildMediaTypeObj(String mediaType);
473
474 /**
475 * Builds a {@link MediaTypeParameter} object based on the value of the TYPE
476 * parameter in 2.1/3.0 vCards.
477 * @param type the TYPE value
478 * @return the parameter object
479 */
480 protected abstract T buildTypeObj(String type);
481 }