Autopsy  4.21.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
XRYCallsFileParser.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019-2021 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.datasourceprocessors.xry;
20 
21 import java.time.format.DateTimeParseException;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.logging.Level;
38 
42 final class XRYCallsFileParser extends AbstractSingleEntityParser {
43 
44  private static final Logger logger = Logger.getLogger(XRYCallsFileParser.class.getName());
45 
50  private enum XryKey {
52  TIME("time", null),
53  DIRECTION("direction", null),
54  CALL_TYPE("call type", null),
55  NUMBER("number", null),
56  TEL("tel", null),
57  TO("to", null),
58  FROM("from", null),
60  DURATION("duration", null),
61  STORAGE("storage", null),
62  INDEX("index", null),
63  TYPE("type", null),
65 
66  private final String name;
68 
70  this.name = name;
71  this.type = type;
72  }
73 
75  return type;
76  }
77 
81  public static boolean contains(String key) {
82  try {
84  return true;
85  } catch (IllegalArgumentException ex) {
86  return false;
87  }
88  }
89 
97  public static XryKey fromDisplayName(String key) {
98  String normalizedKey = key.trim().toLowerCase();
99  for (XryKey keyChoice : XryKey.values()) {
100  if (normalizedKey.equals(keyChoice.name)) {
101  return keyChoice;
102  }
103  }
104 
105  throw new IllegalArgumentException(String.format("Key [%s] was not found."
106  + " All keys should be tested with contains.", key));
107  }
108  }
109 
113  private enum XryNamespace {
114  TO("to"),
115  FROM("from"),
116  NONE(null);
117 
118  private final String name;
119 
120  XryNamespace(String name) {
121  this.name = name;
122  }
123 
128  public static boolean contains(String xryNamespace) {
129  try {
130  XryNamespace.fromDisplayName(xryNamespace);
131  return true;
132  } catch (IllegalArgumentException ex) {
133  return false;
134  }
135  }
136 
145  public static XryNamespace fromDisplayName(String xryNamespace) {
146  String normalizedNamespace = xryNamespace.trim().toLowerCase();
147  for (XryNamespace keyChoice : XryNamespace.values()) {
148  if (normalizedNamespace.equals(keyChoice.name)) {
149  return keyChoice;
150  }
151  }
152 
153  throw new IllegalArgumentException(String.format("Key [%s] was not found."
154  + " All keys should be tested with contains.", xryNamespace));
155  }
156  }
157 
158  @Override
159  boolean canProcess(XRYKeyValuePair pair) {
160  return XryKey.contains(pair.getKey());
161  }
162 
163  @Override
164  boolean isNamespace(String nameSpace) {
165  return XryNamespace.contains(nameSpace);
166  }
167 
168  @Override
169  void makeArtifact(List<XRYKeyValuePair> keyValuePairs, Content parent, SleuthkitCase currentCase) throws TskCoreException, BlackboardException {
170  // Transform all the data from XRY land into the appropriate CommHelper
171  // data types.
172  String callerId = null;
173  final Collection<String> calleeList = new ArrayList<>();
174  CommunicationDirection direction = CommunicationDirection.UNKNOWN;
175  long startTime = 0L;
176  final long endTime = 0L;
177  final CallMediaType callType = CallMediaType.UNKNOWN;
178  final Collection<BlackboardAttribute> otherAttributes = new ArrayList<>();
179 
180  for (XRYKeyValuePair pair : keyValuePairs) {
181  XryKey xryKey = XryKey.fromDisplayName(pair.getKey());
182  XryNamespace xryNamespace = XryNamespace.NONE;
183  if (XryNamespace.contains(pair.getNamespace())) {
184  xryNamespace = XryNamespace.fromDisplayName(pair.getNamespace());
185  }
186 
187  switch (xryKey) {
188  case TEL:
189  case NUMBER:
190  if (!XRYUtils.isPhoneValid(pair.getValue())) {
191  continue;
192  }
193 
194  // Apply namespace or direction
195  if (xryNamespace == XryNamespace.FROM || direction == CommunicationDirection.INCOMING) {
196  callerId = pair.getValue();
197  } else if (xryNamespace == XryNamespace.TO || direction == CommunicationDirection.OUTGOING) {
198  calleeList.add(pair.getValue());
199  } else {
200  otherAttributes.add(new BlackboardAttribute(
201  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
202  PARSER_NAME, pair.getValue()));
203  }
204  break;
205  // Although confusing, as these are also 'name spaces', it appears
206  // later versions of XRY just made these standardized lines.
207  case TO:
208  if (!XRYUtils.isPhoneValid(pair.getValue())) {
209  continue;
210  }
211 
212  calleeList.add(pair.getValue());
213  break;
214  case FROM:
215  if (!XRYUtils.isPhoneValid(pair.getValue())) {
216  continue;
217  }
218 
219  callerId = pair.getValue();
220  break;
221  case TIME:
222  try {
223  //Tranform value to seconds since epoch
224  long dateTimeSinceEpoch = XRYUtils.calculateSecondsSinceEpoch(pair.getValue());
225  startTime = dateTimeSinceEpoch;
226  } catch (DateTimeParseException ex) {
227  logger.log(Level.WARNING, String.format("[XRY DSP] Assumption"
228  + " about the date time formatting of call logs is "
229  + "not right. Here is the value [ %s ]", pair.getValue()), ex);
230  }
231  break;
232  case DIRECTION:
233  String directionString = pair.getValue().toLowerCase();
234  if (directionString.equals("incoming")) {
235  direction = CommunicationDirection.INCOMING;
236  } else {
237  direction = CommunicationDirection.OUTGOING;
238  }
239  break;
240  case TYPE:
241  String typeString = pair.getValue();
242  if (typeString.equalsIgnoreCase("received")) {
243  direction = CommunicationDirection.INCOMING;
244  } else if (typeString.equalsIgnoreCase("dialed")) {
245  direction = CommunicationDirection.OUTGOING;
246  }
247  break;
248  default:
249  //Otherwise, the XryKey enum contains the correct BlackboardAttribute
250  //type.
251  if (xryKey.getType() != null) {
252  otherAttributes.add(new BlackboardAttribute(xryKey.getType(),
253  PARSER_NAME, pair.getValue()));
254  }
255 
256  logger.log(Level.INFO, String.format("[XRY DSP] Key value pair "
257  + "(in brackets) [ %s ] was recognized but "
258  + "more data or time is needed to finish implementation. Discarding... ",
259  pair));
260  }
261  }
262 
263  // Make sure we have the required fields, otherwise the CommHelper will
264  // complain about illegal arguments.
265  // These are all the invalid combinations.
266  if (callerId == null && calleeList.isEmpty()
267  || direction == CommunicationDirection.INCOMING && callerId == null
268  || direction == CommunicationDirection.OUTGOING && calleeList.isEmpty()) {
269 
270  // If the combo is invalid, just make an artifact with what we've got.
271  if (direction != CommunicationDirection.UNKNOWN) {
272  otherAttributes.add(new BlackboardAttribute(
273  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION,
274  PARSER_NAME, direction.getDisplayName()));
275  }
276 
277  if (startTime > 0L) {
278  otherAttributes.add(new BlackboardAttribute(
279  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START,
280  PARSER_NAME, startTime));
281  }
282 
283  // If the DIRECTION check failed, just manually create accounts
284  // for these phones. Note, there is no need to create relationships.
285  // If both callerId and calleeList were non-null/non-empty, then
286  // it would have been a valid combination.
287  if (callerId != null) {
288  try {
289  currentCase.getCommunicationsManager().createAccountFileInstance(
290  Account.Type.PHONE, callerId, PARSER_NAME, parent, null, null);
291  } catch (InvalidAccountIDException ex) {
292  logger.log(Level.WARNING, String.format("Invalid account identifier %s", callerId), ex);
293  }
294 
295  otherAttributes.add(new BlackboardAttribute(
296  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
297  PARSER_NAME, callerId));
298  }
299 
300  for (String phone : calleeList) {
301  try {
302  currentCase.getCommunicationsManager().createAccountFileInstance(
303  Account.Type.PHONE, phone, PARSER_NAME, parent, null, null);
304  } catch (InvalidAccountIDException ex) {
305  logger.log(Level.WARNING, String.format("Invalid account identifier %s", phone), ex);
306  }
307 
308  otherAttributes.add(new BlackboardAttribute(
309  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
310  PARSER_NAME, phone));
311  }
312 
313  if (!otherAttributes.isEmpty()) {
314  BlackboardArtifact artifact = parent.newDataArtifact(new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG), otherAttributes);
315 
316  currentCase.getBlackboard().postArtifact(artifact, PARSER_NAME, null);
317  }
318  } else {
319 
320  // Otherwise we can safely use the helper.
321  CommunicationArtifactsHelper helper = new CommunicationArtifactsHelper(
322  currentCase, PARSER_NAME, parent, Account.Type.PHONE, null);
323 
324  helper.addCalllog(direction, callerId, calleeList, startTime,
325  endTime, callType, otherAttributes);
326  }
327  }
328 }
XryKey(String name, BlackboardAttribute.ATTRIBUTE_TYPE type)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on: Mon Mar 17 2025
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.