Message.java

  1package eu.siacs.conversations.entities;
  2
  3import android.content.ContentValues;
  4import android.database.Cursor;
  5
  6import java.net.MalformedURLException;
  7import java.net.URL;
  8import java.util.Arrays;
  9
 10import eu.siacs.conversations.Config;
 11import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
 12import eu.siacs.conversations.utils.GeoHelper;
 13import eu.siacs.conversations.utils.MimeUtils;
 14import eu.siacs.conversations.utils.UIHelper;
 15import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 16import eu.siacs.conversations.xmpp.jid.Jid;
 17
 18public class Message extends AbstractEntity {
 19
 20	public static final String TABLENAME = "messages";
 21
 22	public static final String MERGE_SEPARATOR = " \u200B\n\n";
 23
 24	public static final int STATUS_RECEIVED = 0;
 25	public static final int STATUS_UNSEND = 1;
 26	public static final int STATUS_SEND = 2;
 27	public static final int STATUS_SEND_FAILED = 3;
 28	public static final int STATUS_WAITING = 5;
 29	public static final int STATUS_OFFERED = 6;
 30	public static final int STATUS_SEND_RECEIVED = 7;
 31	public static final int STATUS_SEND_DISPLAYED = 8;
 32
 33	public static final int ENCRYPTION_NONE = 0;
 34	public static final int ENCRYPTION_PGP = 1;
 35	public static final int ENCRYPTION_OTR = 2;
 36	public static final int ENCRYPTION_DECRYPTED = 3;
 37	public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
 38	public static final int ENCRYPTION_AXOLOTL = 5;
 39
 40	public static final int TYPE_TEXT = 0;
 41	public static final int TYPE_IMAGE = 1;
 42	public static final int TYPE_FILE = 2;
 43	public static final int TYPE_STATUS = 3;
 44	public static final int TYPE_PRIVATE = 4;
 45
 46	public static final String CONVERSATION = "conversationUuid";
 47	public static final String COUNTERPART = "counterpart";
 48	public static final String TRUE_COUNTERPART = "trueCounterpart";
 49	public static final String BODY = "body";
 50	public static final String TIME_SENT = "timeSent";
 51	public static final String ENCRYPTION = "encryption";
 52	public static final String STATUS = "status";
 53	public static final String TYPE = "type";
 54	public static final String CARBON = "carbon";
 55	public static final String REMOTE_MSG_ID = "remoteMsgId";
 56	public static final String SERVER_MSG_ID = "serverMsgId";
 57	public static final String RELATIVE_FILE_PATH = "relativeFilePath";
 58	public static final String FINGERPRINT = "axolotl_fingerprint";
 59	public static final String READ = "read";
 60	public static final String ME_COMMAND = "/me ";
 61
 62
 63	public boolean markable = false;
 64	protected String conversationUuid;
 65	protected Jid counterpart;
 66	protected Jid trueCounterpart;
 67	protected String body;
 68	protected String encryptedBody;
 69	protected long timeSent;
 70	protected int encryption;
 71	protected int status;
 72	protected int type;
 73	protected boolean carbon = false;
 74	protected String relativeFilePath;
 75	protected boolean read = true;
 76	protected String remoteMsgId = null;
 77	protected String serverMsgId = null;
 78	protected Conversation conversation = null;
 79	protected Transferable transferable = null;
 80	private Message mNextMessage = null;
 81	private Message mPreviousMessage = null;
 82	private String axolotlFingerprint = null;
 83
 84	private Message() {
 85
 86	}
 87
 88	public Message(Conversation conversation, String body, int encryption) {
 89		this(conversation, body, encryption, STATUS_UNSEND);
 90	}
 91
 92	public Message(Conversation conversation, String body, int encryption, int status) {
 93		this(java.util.UUID.randomUUID().toString(),
 94				conversation.getUuid(),
 95				conversation.getJid() == null ? null : conversation.getJid().toBareJid(),
 96				null,
 97				body,
 98				System.currentTimeMillis(),
 99				encryption,
100				status,
101				TYPE_TEXT,
102				false,
103				null,
104				null,
105				null,
106				null,
107				true);
108		this.conversation = conversation;
109	}
110
111	private Message(final String uuid, final String conversationUUid, final Jid counterpart,
112					final Jid trueCounterpart, final String body, final long timeSent,
113					final int encryption, final int status, final int type, final boolean carbon,
114					final String remoteMsgId, final String relativeFilePath,
115					final String serverMsgId, final String fingerprint, final boolean read) {
116		this.uuid = uuid;
117		this.conversationUuid = conversationUUid;
118		this.counterpart = counterpart;
119		this.trueCounterpart = trueCounterpart;
120		this.body = body;
121		this.timeSent = timeSent;
122		this.encryption = encryption;
123		this.status = status;
124		this.type = type;
125		this.carbon = carbon;
126		this.remoteMsgId = remoteMsgId;
127		this.relativeFilePath = relativeFilePath;
128		this.serverMsgId = serverMsgId;
129		this.axolotlFingerprint = fingerprint;
130		this.read = read;
131	}
132
133	public static Message fromCursor(Cursor cursor) {
134		Jid jid;
135		try {
136			String value = cursor.getString(cursor.getColumnIndex(COUNTERPART));
137			if (value != null) {
138				jid = Jid.fromString(value, true);
139			} else {
140				jid = null;
141			}
142		} catch (InvalidJidException e) {
143			jid = null;
144		}
145		Jid trueCounterpart;
146		try {
147			String value = cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART));
148			if (value != null) {
149				trueCounterpart = Jid.fromString(value, true);
150			} else {
151				trueCounterpart = null;
152			}
153		} catch (InvalidJidException e) {
154			trueCounterpart = null;
155		}
156		return new Message(cursor.getString(cursor.getColumnIndex(UUID)),
157				cursor.getString(cursor.getColumnIndex(CONVERSATION)),
158				jid,
159				trueCounterpart,
160				cursor.getString(cursor.getColumnIndex(BODY)),
161				cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
162				cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
163				cursor.getInt(cursor.getColumnIndex(STATUS)),
164				cursor.getInt(cursor.getColumnIndex(TYPE)),
165				cursor.getInt(cursor.getColumnIndex(CARBON))>0,
166				cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
167				cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
168				cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
169				cursor.getString(cursor.getColumnIndex(FINGERPRINT)),
170				cursor.getInt(cursor.getColumnIndex(READ)) > 0);
171	}
172
173	public static Message createStatusMessage(Conversation conversation, String body) {
174		final Message message = new Message();
175		message.setType(Message.TYPE_STATUS);
176		message.setConversation(conversation);
177		message.setBody(body);
178		return message;
179	}
180
181	public static Message createLoadMoreMessage(Conversation conversation) {
182		final Message message = new Message();
183		message.setType(Message.TYPE_STATUS);
184		message.setConversation(conversation);
185		message.setBody("LOAD_MORE");
186		return message;
187	}
188
189	@Override
190	public ContentValues getContentValues() {
191		ContentValues values = new ContentValues();
192		values.put(UUID, uuid);
193		values.put(CONVERSATION, conversationUuid);
194		if (counterpart == null) {
195			values.putNull(COUNTERPART);
196		} else {
197			values.put(COUNTERPART, counterpart.toString());
198		}
199		if (trueCounterpart == null) {
200			values.putNull(TRUE_COUNTERPART);
201		} else {
202			values.put(TRUE_COUNTERPART, trueCounterpart.toString());
203		}
204		values.put(BODY, body);
205		values.put(TIME_SENT, timeSent);
206		values.put(ENCRYPTION, encryption);
207		values.put(STATUS, status);
208		values.put(TYPE, type);
209		values.put(CARBON, carbon ? 1 : 0);
210		values.put(REMOTE_MSG_ID, remoteMsgId);
211		values.put(RELATIVE_FILE_PATH, relativeFilePath);
212		values.put(SERVER_MSG_ID, serverMsgId);
213		values.put(FINGERPRINT, axolotlFingerprint);
214		values.put(READ,read);
215		return values;
216	}
217
218	public String getConversationUuid() {
219		return conversationUuid;
220	}
221
222	public Conversation getConversation() {
223		return this.conversation;
224	}
225
226	public void setConversation(Conversation conv) {
227		this.conversation = conv;
228	}
229
230	public Jid getCounterpart() {
231		return counterpart;
232	}
233
234	public void setCounterpart(final Jid counterpart) {
235		this.counterpart = counterpart;
236	}
237
238	public Contact getContact() {
239		if (this.conversation.getMode() == Conversation.MODE_SINGLE) {
240			return this.conversation.getContact();
241		} else {
242			if (this.trueCounterpart == null) {
243				return null;
244			} else {
245				return this.conversation.getAccount().getRoster()
246						.getContactFromRoster(this.trueCounterpart);
247			}
248		}
249	}
250
251	public String getBody() {
252		return body;
253	}
254
255	public void setBody(String body) {
256		this.body = body;
257	}
258
259	public long getTimeSent() {
260		return timeSent;
261	}
262
263	public int getEncryption() {
264		return encryption;
265	}
266
267	public void setEncryption(int encryption) {
268		this.encryption = encryption;
269	}
270
271	public int getStatus() {
272		return status;
273	}
274
275	public void setStatus(int status) {
276		this.status = status;
277	}
278
279	public String getRelativeFilePath() {
280		return this.relativeFilePath;
281	}
282
283	public void setRelativeFilePath(String path) {
284		this.relativeFilePath = path;
285	}
286
287	public String getRemoteMsgId() {
288		return this.remoteMsgId;
289	}
290
291	public void setRemoteMsgId(String id) {
292		this.remoteMsgId = id;
293	}
294
295	public String getServerMsgId() {
296		return this.serverMsgId;
297	}
298
299	public void setServerMsgId(String id) {
300		this.serverMsgId = id;
301	}
302
303	public boolean isRead() {
304		return this.read;
305	}
306
307	public void markRead() {
308		this.read = true;
309	}
310
311	public void markUnread() {
312		this.read = false;
313	}
314
315	public void setTime(long time) {
316		this.timeSent = time;
317	}
318
319	public String getEncryptedBody() {
320		return this.encryptedBody;
321	}
322
323	public void setEncryptedBody(String body) {
324		this.encryptedBody = body;
325	}
326
327	public int getType() {
328		return this.type;
329	}
330
331	public void setType(int type) {
332		this.type = type;
333	}
334
335	public boolean isCarbon() {
336		return carbon;
337	}
338
339	public void setCarbon(boolean carbon) {
340		this.carbon = carbon;
341	}
342
343	public void setTrueCounterpart(Jid trueCounterpart) {
344		this.trueCounterpart = trueCounterpart;
345	}
346
347	public Transferable getTransferable() {
348		return this.transferable;
349	}
350
351	public void setTransferable(Transferable transferable) {
352		this.transferable = transferable;
353	}
354
355	public boolean equals(Message message) {
356		if (this.serverMsgId != null && message.getServerMsgId() != null) {
357			return this.serverMsgId.equals(message.getServerMsgId());
358		} else if (this.body == null || this.counterpart == null) {
359			return false;
360		} else {
361			String body, otherBody;
362			if (this.hasFileOnRemoteHost()) {
363				body = getFileParams().url.toString();
364				otherBody = message.body == null ? null : message.body.trim();
365			} else {
366				body = this.body;
367				otherBody = message.body;
368			}
369			if (message.getRemoteMsgId() != null) {
370				return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
371						&& this.counterpart.equals(message.getCounterpart())
372						&& (body.equals(otherBody)
373						||(message.getEncryption() == Message.ENCRYPTION_PGP
374						&&  message.getRemoteMsgId().matches("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"))) ;
375			} else {
376				return this.remoteMsgId == null
377						&& this.counterpart.equals(message.getCounterpart())
378						&& body.equals(otherBody)
379						&& Math.abs(this.getTimeSent() - message.getTimeSent()) < Config.MESSAGE_MERGE_WINDOW * 1000;
380			}
381		}
382	}
383
384	public Message next() {
385		synchronized (this.conversation.messages) {
386			if (this.mNextMessage == null) {
387				int index = this.conversation.messages.indexOf(this);
388				if (index < 0 || index >= this.conversation.messages.size() - 1) {
389					this.mNextMessage = null;
390				} else {
391					this.mNextMessage = this.conversation.messages.get(index + 1);
392				}
393			}
394			return this.mNextMessage;
395		}
396	}
397
398	public Message prev() {
399		synchronized (this.conversation.messages) {
400			if (this.mPreviousMessage == null) {
401				int index = this.conversation.messages.indexOf(this);
402				if (index <= 0 || index > this.conversation.messages.size()) {
403					this.mPreviousMessage = null;
404				} else {
405					this.mPreviousMessage = this.conversation.messages.get(index - 1);
406				}
407			}
408			return this.mPreviousMessage;
409		}
410	}
411
412	public boolean mergeable(final Message message) {
413		return message != null &&
414				(message.getType() == Message.TYPE_TEXT &&
415						this.getTransferable() == null &&
416						message.getTransferable() == null &&
417						message.getEncryption() != Message.ENCRYPTION_PGP &&
418						this.getType() == message.getType() &&
419						//this.getStatus() == message.getStatus() &&
420						isStatusMergeable(this.getStatus(), message.getStatus()) &&
421						this.getEncryption() == message.getEncryption() &&
422						this.getCounterpart() != null &&
423						this.getCounterpart().equals(message.getCounterpart()) &&
424						(message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
425						!GeoHelper.isGeoUri(message.getBody()) &&
426						!GeoHelper.isGeoUri(this.body) &&
427						message.treatAsDownloadable() == Decision.NEVER &&
428						this.treatAsDownloadable() == Decision.NEVER &&
429						!message.getBody().startsWith(ME_COMMAND) &&
430						!this.getBody().startsWith(ME_COMMAND) &&
431						!this.bodyIsHeart() &&
432						!message.bodyIsHeart() &&
433						this.isTrusted() == message.isTrusted()
434				);
435	}
436
437	private static boolean isStatusMergeable(int a, int b) {
438		return a == b || (
439				(a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_UNSEND)
440						|| (a == Message.STATUS_SEND_RECEIVED && b == Message.STATUS_SEND)
441						|| (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND)
442						|| (a == Message.STATUS_UNSEND && b == Message.STATUS_SEND_RECEIVED)
443						|| (a == Message.STATUS_SEND && b == Message.STATUS_UNSEND)
444						|| (a == Message.STATUS_SEND && b == Message.STATUS_SEND_RECEIVED)
445		);
446	}
447
448	public String getMergedBody() {
449		StringBuilder body = new StringBuilder(this.body.trim());
450		Message current = this;
451		while(current.mergeable(current.next())) {
452			current = current.next();
453			body.append(MERGE_SEPARATOR);
454			body.append(current.getBody().trim());
455		}
456		return body.toString();
457	}
458
459	public boolean hasMeCommand() {
460		return getMergedBody().startsWith(ME_COMMAND);
461	}
462
463	public int getMergedStatus() {
464		int status = this.status;
465		Message current = this;
466		while(current.mergeable(current.next())) {
467			current = current.next();
468			status = current.status;
469		}
470		return status;
471	}
472
473	public long getMergedTimeSent() {
474		long time = this.timeSent;
475		Message current = this;
476		while(current.mergeable(current.next())) {
477			current = current.next();
478			time = current.timeSent;
479		}
480		return time;
481	}
482
483	public boolean wasMergedIntoPrevious() {
484		Message prev = this.prev();
485		return prev != null && prev.mergeable(this);
486	}
487
488	public boolean trusted() {
489		Contact contact = this.getContact();
490		return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
491	}
492
493	public boolean fixCounterpart() {
494		Presences presences = conversation.getContact().getPresences();
495		if (counterpart != null && presences.has(counterpart.getResourcepart())) {
496			return true;
497		} else if (presences.size() >= 1) {
498			try {
499				counterpart = Jid.fromParts(conversation.getJid().getLocalpart(),
500						conversation.getJid().getDomainpart(),
501						presences.asStringArray()[0]);
502				return true;
503			} catch (InvalidJidException e) {
504				counterpart = null;
505				return false;
506			}
507		} else {
508			counterpart = null;
509			return false;
510		}
511	}
512
513	public enum Decision {
514		MUST,
515		SHOULD,
516		NEVER,
517	}
518
519	private static String extractRelevantExtension(URL url) {
520		String path = url.getPath();
521		return extractRelevantExtension(path);
522	}
523
524	private static String extractRelevantExtension(String path) {
525		if (path == null || path.isEmpty()) {
526			return null;
527		}
528		
529		String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase();
530		int dotPosition = filename.lastIndexOf(".");
531
532		if (dotPosition != -1) {
533			String extension = filename.substring(dotPosition + 1);
534			// we want the real file extension, not the crypto one
535			if (Arrays.asList(Transferable.VALID_CRYPTO_EXTENSIONS).contains(extension)) {
536				return extractRelevantExtension(filename.substring(0,dotPosition));
537			} else {
538				return extension;
539			}
540		}
541		return null;
542	}
543
544	public String getMimeType() {
545		if (relativeFilePath != null) {
546			int start = relativeFilePath.lastIndexOf('.') + 1;
547			if (start < relativeFilePath.length()) {
548				return MimeUtils.guessMimeTypeFromExtension(relativeFilePath.substring(start));
549			} else {
550				return null;
551			}
552		} else {
553			try {
554				return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim())));
555			} catch (MalformedURLException e) {
556				return null;
557			}
558		}
559	}
560
561	public Decision treatAsDownloadable() {
562		if (body.trim().contains(" ")) {
563			return Decision.NEVER;
564		}
565		try {
566			URL url = new URL(body);
567			if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
568				return Decision.NEVER;
569			}
570			String extension = extractRelevantExtension(url);
571			if (extension == null) {
572				return Decision.NEVER;
573			}
574			String ref = url.getRef();
575			boolean encrypted = ref != null && ref.matches("([A-Fa-f0-9]{2}){48}");
576
577			if (encrypted) {
578				if (MimeUtils.guessMimeTypeFromExtension(extension) != null) {
579					return Decision.MUST;
580				} else {
581					return Decision.NEVER;
582				}
583			} else if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)
584					|| Arrays.asList(Transferable.WELL_KNOWN_EXTENSIONS).contains(extension)) {
585				return Decision.SHOULD;
586			} else {
587				return Decision.NEVER;
588			}
589
590		} catch (MalformedURLException e) {
591			return Decision.NEVER;
592		}
593	}
594
595	public boolean bodyIsHeart() {
596		return body != null && UIHelper.HEARTS.contains(body.trim());
597	}
598
599	public FileParams getFileParams() {
600		FileParams params = getLegacyFileParams();
601		if (params != null) {
602			return params;
603		}
604		params = new FileParams();
605		if (this.transferable != null) {
606			params.size = this.transferable.getFileSize();
607		}
608		if (body == null) {
609			return params;
610		}
611		String parts[] = body.split("\\|");
612		switch (parts.length) {
613			case 1:
614				try {
615					params.size = Long.parseLong(parts[0]);
616				} catch (NumberFormatException e) {
617					try {
618						params.url = new URL(parts[0]);
619					} catch (MalformedURLException e1) {
620						params.url = null;
621					}
622				}
623				break;
624			case 2:
625			case 4:
626				try {
627					params.url = new URL(parts[0]);
628				} catch (MalformedURLException e1) {
629					params.url = null;
630				}
631				try {
632					params.size = Long.parseLong(parts[1]);
633				} catch (NumberFormatException e) {
634					params.size = 0;
635				}
636				try {
637					params.width = Integer.parseInt(parts[2]);
638				} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
639					params.width = 0;
640				}
641				try {
642					params.height = Integer.parseInt(parts[3]);
643				} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
644					params.height = 0;
645				}
646				break;
647			case 3:
648				try {
649					params.size = Long.parseLong(parts[0]);
650				} catch (NumberFormatException e) {
651					params.size = 0;
652				}
653				try {
654					params.width = Integer.parseInt(parts[1]);
655				} catch (NumberFormatException e) {
656					params.width = 0;
657				}
658				try {
659					params.height = Integer.parseInt(parts[2]);
660				} catch (NumberFormatException e) {
661					params.height = 0;
662				}
663				break;
664		}
665		return params;
666	}
667
668	public FileParams getLegacyFileParams() {
669		FileParams params = new FileParams();
670		if (body == null) {
671			return params;
672		}
673		String parts[] = body.split(",");
674		if (parts.length == 3) {
675			try {
676				params.size = Long.parseLong(parts[0]);
677			} catch (NumberFormatException e) {
678				return null;
679			}
680			try {
681				params.width = Integer.parseInt(parts[1]);
682			} catch (NumberFormatException e) {
683				return null;
684			}
685			try {
686				params.height = Integer.parseInt(parts[2]);
687			} catch (NumberFormatException e) {
688				return null;
689			}
690			return params;
691		} else {
692			return null;
693		}
694	}
695
696	public void untie() {
697		this.mNextMessage = null;
698		this.mPreviousMessage = null;
699	}
700
701	public boolean isFileOrImage() {
702		return type == TYPE_FILE || type == TYPE_IMAGE;
703	}
704
705	public boolean hasFileOnRemoteHost() {
706		return isFileOrImage() && getFileParams().url != null;
707	}
708
709	public boolean needsUploading() {
710		return isFileOrImage() && getFileParams().url == null;
711	}
712
713	public class FileParams {
714		public URL url;
715		public long size = 0;
716		public int width = 0;
717		public int height = 0;
718	}
719
720	public void setAxolotlFingerprint(String fingerprint) {
721		this.axolotlFingerprint = fingerprint;
722	}
723
724	public String getAxolotlFingerprint() {
725		return axolotlFingerprint;
726	}
727
728	public boolean isTrusted() {
729		XmppAxolotlSession.Trust t = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint);
730		return t != null && t.trusted();
731	}
732
733	private  int getPreviousEncryption() {
734		for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){
735			if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
736				continue;
737			}
738			return iterator.getEncryption();
739		}
740		return ENCRYPTION_NONE;
741	}
742
743	private int getNextEncryption() {
744		for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){
745			if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
746				continue;
747			}
748			return iterator.getEncryption();
749		}
750		return conversation.getNextEncryption();
751	}
752
753	public boolean isValidInSession() {
754		int pastEncryption = getCleanedEncryption(this.getPreviousEncryption());
755		int futureEncryption = getCleanedEncryption(this.getNextEncryption());
756
757		boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
758				|| futureEncryption == ENCRYPTION_NONE
759				|| pastEncryption != futureEncryption;
760
761		return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption;
762	}
763
764	private static int getCleanedEncryption(int encryption) {
765		if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) {
766			return ENCRYPTION_PGP;
767		}
768		return encryption;
769	}
770}