001/** 002 * 003 * Copyright 2018 Paul Schaub. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.ox.store.filebased; 018 019import java.io.BufferedReader; 020import java.io.BufferedWriter; 021import java.io.File; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.InputStreamReader; 025import java.io.OutputStream; 026import java.io.OutputStreamWriter; 027import java.text.ParseException; 028import java.util.Date; 029import java.util.HashMap; 030import java.util.Map; 031import java.util.logging.Level; 032import java.util.logging.Logger; 033 034import org.jivesoftware.smack.util.CloseableUtil; 035import org.jivesoftware.smack.util.FileUtils; 036 037import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpMetadataStore; 038import org.jivesoftware.smackx.ox.store.definition.OpenPgpMetadataStore; 039import org.jivesoftware.smackx.ox.util.Util; 040 041import org.jxmpp.jid.BareJid; 042import org.jxmpp.util.XmppDateTime; 043import org.pgpainless.key.OpenPgpV4Fingerprint; 044 045/** 046 * Implementation of the {@link OpenPgpMetadataStore}, which stores metadata information in a file structure. 047 * The information is stored in the following directory structure: 048 * 049 * <pre> 050 * {@code 051 * <basePath>/ 052 * <userjid@server.tld>/ 053 * announced.list // list of the users announced key fingerprints and modification dates 054 * } 055 * </pre> 056 */ 057public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore { 058 059 public static final String ANNOUNCED = "announced.list"; 060 061 private static final Logger LOGGER = Logger.getLogger(FileBasedOpenPgpMetadataStore.class.getName()); 062 063 private final File basePath; 064 065 public FileBasedOpenPgpMetadataStore(File basePath) { 066 this.basePath = basePath; 067 } 068 069 @Override 070 public Map<OpenPgpV4Fingerprint, Date> readAnnouncedFingerprintsOf(BareJid contact) throws IOException { 071 return readFingerprintsAndDates(getAnnouncedFingerprintsPath(contact)); 072 } 073 074 @Override 075 public void writeAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> metadata) 076 throws IOException { 077 File destination = getAnnouncedFingerprintsPath(contact); 078 writeFingerprintsAndDates(metadata, destination); 079 } 080 081 static Map<OpenPgpV4Fingerprint, Date> readFingerprintsAndDates(File source) throws IOException { 082 // TODO: Why do we not throw a FileNotFoundException here? 083 if (!source.exists() || source.isDirectory()) { 084 return new HashMap<>(); 085 } 086 087 BufferedReader reader = null; 088 try { 089 InputStream inputStream = FileUtils.prepareFileInputStream(source); 090 InputStreamReader isr = new InputStreamReader(inputStream, Util.UTF8); 091 reader = new BufferedReader(isr); 092 Map<OpenPgpV4Fingerprint, Date> fingerprintDateMap = new HashMap<>(); 093 094 String line; int lineNr = 0; 095 while ((line = reader.readLine()) != null) { 096 lineNr++; 097 098 line = line.trim(); 099 String[] split = line.split(" "); 100 if (split.length != 2) { 101 LOGGER.log(Level.FINE, "Skipping invalid line " + lineNr + " in file " + source.getAbsolutePath()); 102 continue; 103 } 104 105 try { 106 OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(split[0]); 107 Date date = XmppDateTime.parseXEP0082Date(split[1]); 108 fingerprintDateMap.put(fingerprint, date); 109 } catch (IllegalArgumentException | ParseException e) { 110 LOGGER.log(Level.WARNING, "Error parsing fingerprint/date touple in line " + lineNr + 111 " of file " + source.getAbsolutePath(), e); 112 } 113 } 114 115 return fingerprintDateMap; 116 } finally { 117 CloseableUtil.maybeClose(reader, LOGGER); 118 } 119 } 120 121 @SuppressWarnings("JavaUtilDate") 122 static void writeFingerprintsAndDates(Map<OpenPgpV4Fingerprint, Date> data, File destination) 123 throws IOException { 124 if (data == null || data.isEmpty()) { 125 FileUtils.maybeDeleteFileOrThrow(destination); 126 return; 127 } 128 129 FileUtils.maybeCreateFileWithParentDirectories(destination); 130 131 BufferedWriter writer = null; 132 try { 133 OutputStream outputStream = FileUtils.prepareFileOutputStream(destination); 134 OutputStreamWriter osw = new OutputStreamWriter(outputStream, Util.UTF8); 135 writer = new BufferedWriter(osw); 136 for (OpenPgpV4Fingerprint fingerprint : data.keySet()) { 137 Date date = data.get(fingerprint); 138 String line = fingerprint.toString() + " " + 139 (date != null ? XmppDateTime.formatXEP0082Date(date) : XmppDateTime.formatXEP0082Date(new Date())); 140 writer.write(line); 141 writer.newLine(); 142 } 143 } finally { 144 CloseableUtil.maybeClose(writer, LOGGER); 145 } 146 } 147 148 private File getAnnouncedFingerprintsPath(BareJid contact) { 149 return new File(FileBasedOpenPgpStore.getContactsPath(basePath, contact), ANNOUNCED); 150 } 151}