1package eu.siacs.conversations.parser;
2
3
4import java.text.ParseException;
5import java.text.SimpleDateFormat;
6import java.util.ArrayList;
7import java.util.Date;
8import java.util.List;
9import java.util.Locale;
10
11import eu.siacs.conversations.entities.Account;
12import eu.siacs.conversations.entities.Contact;
13import eu.siacs.conversations.entities.Conversation;
14import eu.siacs.conversations.entities.MucOptions;
15import eu.siacs.conversations.services.XmppConnectionService;
16import eu.siacs.conversations.xml.Element;
17import eu.siacs.conversations.xmpp.InvalidJid;
18import eu.siacs.conversations.xmpp.Jid;
19import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
20
21public abstract class AbstractParser {
22
23 protected XmppConnectionService mXmppConnectionService;
24
25 protected AbstractParser(XmppConnectionService service) {
26 this.mXmppConnectionService = service;
27 }
28
29 public static Long parseTimestamp(Element element, Long d) {
30 return parseTimestamp(element,d,false);
31 }
32
33 public static Long parseTimestamp(Element element, Long d, boolean ignoreCsiAndSm) {
34 long min = Long.MAX_VALUE;
35 boolean returnDefault = true;
36 final Jid to;
37 if (ignoreCsiAndSm && element instanceof AbstractStanza) {
38 to = ((AbstractStanza) element).getTo();
39 } else {
40 to = null;
41 }
42 for(Element child : element.getChildren()) {
43 if ("delay".equals(child.getName()) && "urn:xmpp:delay".equals(child.getNamespace())) {
44 final Jid f = to == null ? null : InvalidJid.getNullForInvalid(child.getAttributeAsJid("from"));
45 if (f != null && (to.asBareJid().equals(f) || to.getDomain().equals(f))) {
46 continue;
47 }
48 final String stamp = child.getAttribute("stamp");
49 if (stamp != null) {
50 try {
51 min = Math.min(min,AbstractParser.parseTimestamp(stamp));
52 returnDefault = false;
53 } catch (Throwable t) {
54 //ignore
55 }
56 }
57 }
58 }
59 if (returnDefault) {
60 return d;
61 } else {
62 return min;
63 }
64 }
65
66 public static long parseTimestamp(Element element) {
67 return parseTimestamp(element, System.currentTimeMillis());
68 }
69
70 public static long parseTimestamp(String timestamp) throws ParseException {
71 timestamp = timestamp.replace("Z", "+0000");
72 SimpleDateFormat dateFormat;
73 long ms;
74 if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') {
75 String millis = timestamp.substring(19,timestamp.length() - 5);
76 try {
77 double fractions = Double.parseDouble("0" + millis);
78 ms = Math.round(1000 * fractions);
79 } catch (NumberFormatException e) {
80 ms = 0;
81 }
82 } else {
83 ms = 0;
84 }
85 timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5);
86 dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US);
87 return Math.min(dateFormat.parse(timestamp).getTime()+ms, System.currentTimeMillis());
88 }
89
90 public static long getTimestamp(final String input) throws ParseException {
91 if (input == null) {
92 throw new IllegalArgumentException("timestamp should not be null");
93 }
94 final String timestamp = input.replace("Z", "+0000");
95 final SimpleDateFormat simpleDateFormat =
96 new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
97 final long milliseconds = getMilliseconds(timestamp);
98 final String formatted =
99 timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5);
100 final Date date = simpleDateFormat.parse(formatted);
101 if (date == null) {
102 throw new IllegalArgumentException("Date was null");
103 }
104 return date.getTime() + milliseconds;
105 }
106
107 private static long getMilliseconds(final String timestamp) {
108 if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') {
109 final String millis = timestamp.substring(19, timestamp.length() - 5);
110 try {
111 double fractions = Double.parseDouble("0" + millis);
112 return Math.round(1000 * fractions);
113 } catch (NumberFormatException e) {
114 return 0;
115 }
116 } else {
117 return 0;
118 }
119 }
120
121 protected void updateLastseen(final Account account, final Jid from) {
122 final Contact contact = account.getRoster().getContact(from);
123 contact.setLastResource(from.isBareJid() ? "" : from.getResource());
124 }
125
126 protected String avatarData(Element items) {
127 Element item = items.findChild("item");
128 if (item == null) {
129 return null;
130 }
131 return item.findChildContent("data", "urn:xmpp:avatar:data");
132 }
133
134 public static MucOptions.User parseItem(Conversation conference, Element item) {
135 return parseItem(conference,item, null);
136 }
137
138 public static MucOptions.User parseItem(Conversation conference, Element item, Jid fullJid) {
139 final String local = conference.getJid().getLocal();
140 final String domain = conference.getJid().getDomain().toEscapedString();
141 String affiliation = item.getAttribute("affiliation");
142 String role = item.getAttribute("role");
143 String nick = item.getAttribute("nick");
144 if (nick != null && fullJid == null) {
145 try {
146 fullJid = Jid.of(local, domain, nick);
147 } catch (IllegalArgumentException e) {
148 fullJid = null;
149 }
150 }
151 Jid realJid = item.getAttributeAsJid("jid");
152 MucOptions.User user = new MucOptions.User(conference.getMucOptions(), fullJid);
153 if (InvalidJid.isValid(realJid)) {
154 user.setRealJid(realJid);
155 }
156 user.setAffiliation(affiliation);
157 user.setRole(role);
158 return user;
159 }
160
161 public static String extractErrorMessage(final Element packet) {
162 final Element error = packet.findChild("error");
163 if (error != null && error.getChildren().size() > 0) {
164 final List<String> errorNames = orderedElementNames(error.getChildren());
165 final String text = error.findChildContent("text");
166 if (text != null && !text.trim().isEmpty()) {
167 return prefixError(errorNames)+text;
168 } else if (errorNames.size() > 0){
169 return prefixError(errorNames)+errorNames.get(0).replace("-"," ");
170 }
171 }
172 return null;
173 }
174
175 public static String errorMessage(Element packet) {
176 final Element error = packet.findChild("error");
177 if (error != null && error.getChildren().size() > 0) {
178 final List<String> errorNames = orderedElementNames(error.getChildren());
179 final String text = error.findChildContent("text");
180 if (text != null && !text.trim().isEmpty()) {
181 return text;
182 } else if (errorNames.size() > 0){
183 return errorNames.get(0).replace("-"," ");
184 }
185 }
186 return null;
187 }
188
189 private static String prefixError(List<String> errorNames) {
190 if (errorNames.size() > 0) {
191 return errorNames.get(0)+'\u001f';
192 }
193 return "";
194 }
195
196 private static List<String> orderedElementNames(List<Element> children) {
197 List<String> names = new ArrayList<>();
198 for(Element child : children) {
199 final String name = child.getName();
200 if (name != null && !name.equals("text")) {
201 if ("urn:ietf:params:xml:ns:xmpp-stanzas".equals(child.getNamespace())) {
202 names.add(name);
203 } else {
204 names.add(0, name);
205 }
206 }
207 }
208 return names;
209 }
210}