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.smackx.ox.store.abstr.AbstractOpenPgpMetadataStore; 035import org.jivesoftware.smackx.ox.store.definition.OpenPgpMetadataStore; 036import org.jivesoftware.smackx.ox.util.FileUtils; 037import org.jivesoftware.smackx.ox.util.Util; 038 039import org.jxmpp.jid.BareJid; 040import org.jxmpp.util.XmppDateTime; 041import org.pgpainless.key.OpenPgpV4Fingerprint; 042 043/** 044 * Implementation of the {@link OpenPgpMetadataStore}, which stores metadata information in a file structure. 045 * The information is stored in the following directory structure: 046 * 047 * <pre> 048 * {@code 049 * <basePath>/ 050 * <userjid@server.tld>/ 051 * announced.list // list of the users announced key fingerprints and modification dates 052 * } 053 * </pre> 054 */ 055public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore { 056 057 public static final String ANNOUNCED = "announced.list"; 058 public static final String RETRIEVED = "retrieved.list"; 059 060 private static final Logger LOGGER = Logger.getLogger(FileBasedOpenPgpMetadataStore.class.getName()); 061 062 private final File basePath; 063 064 public FileBasedOpenPgpMetadataStore(File basePath) { 065 this.basePath = basePath; 066 } 067 068 @Override 069 public Map<OpenPgpV4Fingerprint, Date> readAnnouncedFingerprintsOf(BareJid contact) throws IOException { 070 return readFingerprintsAndDates(getAnnouncedFingerprintsPath(contact)); 071 } 072 073 @Override 074 public void writeAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> metadata) 075 throws IOException { 076 File destination = getAnnouncedFingerprintsPath(contact); 077 writeFingerprintsAndDates(metadata, destination); 078 } 079 080 static Map<OpenPgpV4Fingerprint, Date> readFingerprintsAndDates(File source) throws IOException { 081 if (!source.exists() || source.isDirectory()) { 082 return new HashMap<>(); 083 } 084 085 BufferedReader reader = null; 086 try { 087 InputStream inputStream = FileUtils.prepareFileInputStream(source); 088 InputStreamReader isr = new InputStreamReader(inputStream, Util.UTF8); 089 reader = new BufferedReader(isr); 090 Map<OpenPgpV4Fingerprint, Date> fingerprintDateMap = new HashMap<>(); 091 092 String line; int lineNr = 0; 093 while ((line = reader.readLine()) != null) { 094 lineNr++; 095 096 line = line.trim(); 097 String[] split = line.split(" "); 098 if (split.length != 2) { 099 LOGGER.log(Level.FINE, "Skipping invalid line " + lineNr + " in file " + source.getAbsolutePath()); 100 continue; 101 } 102 103 try { 104 OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(split[0]); 105 Date date = XmppDateTime.parseXEP0082Date(split[1]); 106 fingerprintDateMap.put(fingerprint, date); 107 } catch (IllegalArgumentException | ParseException e) { 108 LOGGER.log(Level.WARNING, "Error parsing fingerprint/date touple in line " + lineNr + 109 " of file " + source.getAbsolutePath(), e); 110 } 111 } 112 113 reader.close(); 114 return fingerprintDateMap; 115 116 } catch (IOException e) { 117 if (reader != null) { 118 try { 119 reader.close(); 120 } catch (IOException ignored) { 121 // Don't care 122 } 123 } 124 throw e; 125 } 126 } 127 128 static void writeFingerprintsAndDates(Map<OpenPgpV4Fingerprint, Date> data, File destination) 129 throws IOException { 130 131 if (data == null || data.isEmpty()) { 132 if (!destination.exists()) { 133 return; 134 } else { 135 if (!destination.delete()) { 136 throw new IOException("Cannot delete file " + destination.getAbsolutePath()); 137 } 138 } 139 return; 140 } 141 142 if (!destination.exists()) { 143 File parent = destination.getParentFile(); 144 if (!parent.exists() && !parent.mkdirs()) { 145 throw new IOException("Cannot create directory " + parent.getAbsolutePath()); 146 } 147 148 if (!destination.createNewFile()) { 149 throw new IOException("Cannot create file " + destination.getAbsolutePath()); 150 } 151 } 152 153 if (destination.isDirectory()) { 154 throw new IOException("File " + destination.getAbsolutePath() + " is a directory."); 155 } 156 157 BufferedWriter writer = null; 158 try { 159 OutputStream outputStream = FileUtils.prepareFileOutputStream(destination); 160 OutputStreamWriter osw = new OutputStreamWriter(outputStream, Util.UTF8); 161 writer = new BufferedWriter(osw); 162 for (OpenPgpV4Fingerprint fingerprint : data.keySet()) { 163 Date date = data.get(fingerprint); 164 String line = fingerprint.toString() + " " + 165 (date != null ? XmppDateTime.formatXEP0082Date(date) : XmppDateTime.formatXEP0082Date(new Date())); 166 writer.write(line); 167 writer.newLine(); 168 } 169 writer.flush(); 170 writer.close(); 171 } catch (IOException e) { 172 if (writer != null) { 173 try { 174 writer.close(); 175 } catch (IOException ignored) { 176 // Don't care 177 } 178 } 179 throw e; 180 } 181 } 182 183 private File getAnnouncedFingerprintsPath(BareJid contact) { 184 return new File(FileBasedOpenPgpStore.getContactsPath(basePath, contact), ANNOUNCED); 185 } 186 187 private File getRetrievedFingerprintsPath(BareJid contact) { 188 return new File(FileBasedOpenPgpStore.getContactsPath(basePath, contact), RETRIEVED); 189 } 190}