From aecb3399b756d2ba04223bc6f553999fce73f9fb Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Sat, 26 Mar 2016 20:23:01 +0100 Subject: [PATCH] VCard format: correctly co/decode escaped values --- src/be/nikiroo/jvcard/BaseClass.java | 33 +++++ src/be/nikiroo/jvcard/Data.java | 132 ++++++++++++++++-- src/be/nikiroo/jvcard/TypeInfo.java | 13 +- .../nikiroo/jvcard/parsers/Vcard21Parser.java | 4 +- .../jvcard/tui/panes/ContactDetails.java | 28 ++-- .../jvcard/tui/panes/ContactDetailsRaw.java | 9 +- 6 files changed, 184 insertions(+), 35 deletions(-) diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java index 1dd7644..026c5b4 100644 --- a/src/be/nikiroo/jvcard/BaseClass.java +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -433,6 +433,39 @@ public abstract class BaseClass> implements List { } } + /** + * Escape the given value to VCF standard. + * + * @param value + * the value to escape + * + * @return the escaped value + */ + protected String escape(String value) { + if (value == null) + return null; + + return value.replaceAll(",", "\\\\,").replaceAll(";", "\\\\;") + .replaceAll("\n", "\\\\n"); + } + + /** + * Escape the given value to VCF standard. + * + * @param value + * the value to escape + * + * @return the escaped value + */ + protected String unescape(String value) { + if (value == null) + return null; + + return value.replaceAll("\\\\,", ",").replaceAll("\\\\;", ";") + + .replaceAll("\\\\n", "\n"); + } + /** * Each element that leaves the parent will pass trough here. * diff --git a/src/be/nikiroo/jvcard/Data.java b/src/be/nikiroo/jvcard/Data.java index 6336233..d6ba628 100644 --- a/src/be/nikiroo/jvcard/Data.java +++ b/src/be/nikiroo/jvcard/Data.java @@ -1,6 +1,7 @@ package be.nikiroo.jvcard; import java.security.InvalidParameterException; +import java.util.LinkedList; import java.util.List; /** @@ -68,6 +69,15 @@ public class Data extends BaseClass { * @return the value */ public String getValue() { + return unescape(value); + } + + /** + * Return the RAW value of this {@link Data} + * + * @return the RAW value + */ + public String getRawValue() { return value; } @@ -78,6 +88,8 @@ public class Data extends BaseClass { * the new value */ public void setValue(String value) { + value = escape(value); + if ((value == null && this.value != null) || (value != null && !value.equals(this.value))) { this.value = value; @@ -85,6 +97,45 @@ public class Data extends BaseClass { } } + /** + * Return the {@link List} of comma-listed values from this {@link Data}. + * + * @return the {@link List} of values + */ + public List getValues() { + return getList(','); + } + + /** + * Set the {@link List} of comma-listed values from this {@link Data}. + * + * @param values + * the {@link List} of values + */ + public void setValues(List values) { + setList(values, ','); + } + + /** + * Return the {@link List} of semi-column-listed fields from this + * {@link Data}. + * + * @return the {@link List} of values + */ + public List getFields() { + return getList(';'); + } + + /** + * Set the {@link List} of comma-listed values from this {@link Data}. + * + * @param values + * the {@link List} of values + */ + public void setFields(List values) { + setList(values, ';'); + } + /** * Return the group of this {@link Data} * @@ -117,6 +168,30 @@ public class Data extends BaseClass { return b64; } + /** + * Check if this {@link Data} is binary + * + * @return TRUE if it is + */ + public boolean isBinary() { + return b64 >= 0; + } + + /** + * Check if this {@link Data} has the "preferred" flag. + * + * @return TRUE if it has + */ + public boolean isPreferred() { + for (TypeInfo type : this) { + if (type.getName().equals("TYPE") && type.getValue().equals("pref")) { + return true; + } + } + + return false; + } + /** * Change the bkey of this {@link Data} * @@ -138,27 +213,60 @@ public class Data extends BaseClass { } /** - * Check if this {@link Data} is binary + * Return the {@link List} of sep-listed values from this {@link String} + * data. * - * @return TRUE if it is + * @param value + * the data + * + * @param the + * separator + * + * @return the {@link List} of values */ - public boolean isBinary() { - return b64 >= 0; + private List getList(char sep) { + List rep = new LinkedList(); + + if (value != null && value.length() > 0) { + int last = 0; + for (int i = 0; i < value.length(); i++) { + if (value.charAt(i) == sep + && (i == 0 || value.charAt(i - 1) != '\\')) { + rep.add(value.substring(last, i - last)); + } + } + + rep.add(value.substring(last)); + } + + return rep; } /** - * Check if this {@link Data} has the "preferred" flag. + * Create the {@link String}-encoded {@link List} of sep-listed values from + * the given values. * - * @return TRUE if it has + * @param values + * the {@link List} of values + * + * @param sep + * the separator + * + * @return the {@link String} */ - public boolean isPreferred() { - for (TypeInfo type : this) { - if (type.getName().equals("TYPE") && type.getValue().equals("pref")) { - return true; - } + private void setList(List values, char sep) { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (String value : values) { + if (!first) + builder.append(sep); + + builder.append(escape(value)); + + first = false; } - return false; + value = builder.toString(); } @Override diff --git a/src/be/nikiroo/jvcard/TypeInfo.java b/src/be/nikiroo/jvcard/TypeInfo.java index 847cc25..c172996 100644 --- a/src/be/nikiroo/jvcard/TypeInfo.java +++ b/src/be/nikiroo/jvcard/TypeInfo.java @@ -6,7 +6,6 @@ package be.nikiroo.jvcard; * @author niki * */ -@SuppressWarnings("rawtypes") public class TypeInfo extends BaseClass { private String name; private String value; @@ -19,12 +18,11 @@ public class TypeInfo extends BaseClass { * @param value * its value (MUST NOT be NULL) */ - @SuppressWarnings("unchecked") public TypeInfo(String name, String value) { super(null); this.name = name.toUpperCase(); - this.value = value.toString(); // crash NOW if null + this.value = escape(value.toString()); // crash NOW if null } /** @@ -42,6 +40,15 @@ public class TypeInfo extends BaseClass { * @return the value */ public String getValue() { + return unescape(value); + } + + /** + * Return the RAW value + * + * @return the RAW value + */ + public String getRawValue() { return value; } diff --git a/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java b/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java index 61f9016..5388783 100644 --- a/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java +++ b/src/be/nikiroo/jvcard/parsers/Vcard21Parser.java @@ -201,13 +201,13 @@ public class Vcard21Parser { dataBuilder.append(type.getName()); if (type.getValue() != null && !type.getValue().trim().equals("")) { dataBuilder.append('='); - dataBuilder.append(type.getValue()); + dataBuilder.append(type.getRawValue()); } } dataBuilder.append(':'); // TODO: bkey! - dataBuilder.append(data.getValue()); + dataBuilder.append(data.getRawValue()); // RFC says: Content lines SHOULD be folded to a maximum width of 75 // octets -> since it is SHOULD, we will just cut it as 74/75 chars diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java index 4cbbdf7..1d6beb9 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java @@ -151,23 +151,25 @@ public class ContactDetails extends MainContent { if (all) { for (Data data : contact.getData(field)) { if (data.isPreferred()) { - infoPanel - .addComponent(el.createLabel(StringUtils - .padString(label, labelSize) - + contact - .getPreferredDataValue(field))); + infoPanel.addComponent(el + .createLabel(StringUtils.padString( + label, labelSize) + + data.toString())); } else { infoPanel - .addComponent(UiColors.Element.VIEW_CONTACT_NORMAL.createLabel(StringUtils - .padString(label, labelSize) - + contact - .getPreferredDataValue(field))); + .addComponent(UiColors.Element.VIEW_CONTACT_NORMAL + .createLabel(StringUtils + .padString(label, + labelSize) + + data.toString())); } } } else { + String val = contact.getPreferredDataValue(field); + if (val == null) + val = ""; infoPanel.addComponent(el.createLabel(StringUtils - .padString(label, labelSize) - + contact.getPreferredDataValue(field))); + .padString(label, labelSize) + val)); } } else { String label = info; @@ -184,7 +186,7 @@ public class ContactDetails extends MainContent { String notes = contact.getPreferredDataValue("NOTE"); if (notes == null) notes = ""; - note.setText(notes.replaceAll("\\\\n", "\n")); + note.setText(notes); Data photo = contact.getPreferredData("PHOTO"); if (photo != null) { @@ -319,7 +321,7 @@ public class ContactDetails extends MainContent { } else { // TODO: configure size? int w = getSize().getColumns() - 40; - int h = getSize().getRows() - 5; + int h = getSize().getRows() - 9; if (w <= 0 || h <= 0) return null; diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java index d0cc8a8..97fede0 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java @@ -61,7 +61,7 @@ public class ContactDetailsRaw extends MainContentList { public String getDefaultAnswer() { Data data = getData(); if (data != null) { - return data.getValue(); + return data.getValue().replaceAll("\n", "\\\\n"); } return null; @@ -71,7 +71,7 @@ public class ContactDetailsRaw extends MainContentList { public String callback(String answer) { Data data = getData(); if (data != null) { - data.setValue(answer); + data.setValue(answer.replaceAll("\\\\n", "\n")); return null; } @@ -297,7 +297,7 @@ public class ContactDetailsRaw extends MainContentList { StringBuilder valueBuilder = new StringBuilder(" "); if (!extMode) { - valueBuilder.append(data.getValue()); + valueBuilder.append(data.getValue().replaceAll("\n", "\\\\n")); if (data.getGroup() != null && data.getGroup().length() > 0) { valueBuilder.append("("); valueBuilder.append(data.getGroup()); @@ -362,8 +362,7 @@ public class ContactDetailsRaw extends MainContentList { if (builder == null) builder = new StringBuilder(); - for (int indexType = 0; indexType < data.size(); indexType++) { - TypeInfo type = data.get(indexType); + for (TypeInfo type : data) { if (builder.length() > 1) builder.append(", "); builder.append(type.getName().replaceAll(",", "\\,")); -- 2.27.0