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; 036import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpMetadataStore; 037import org.jivesoftware.smackx.ox.store.definition.OpenPgpMetadataStore; 038import org.jivesoftware.smackx.ox.util.Util; 039 040import org.jxmpp.jid.BareJid; 041import org.jxmpp.util.XmppDateTime; 042import org.pgpainless.key.OpenPgpV4Fingerprint; 043 044/** 045 * Implementation of the {@link OpenPgpMetadataStore}, which stores metadata information in a file structure. 046 * The information is stored in the following directory structure: 047 * 048 * <pre> 049 * {@code 050 * <basePath>/ 051 * <userjid@server.tld>/ 052 * announced.list // list of the users announced key fingerprints and modification dates 053 * } 054 * </pre> 055 */ 056public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore { 057 058 public static final String ANNOUNCED = "announced.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 // TODO: Why do we not throw a FileNotFoundException here? 082 if (!source.exists() || source.isDirectory()) { 083 return new HashMap<>(); 084 } 085 086 BufferedReader reader = null; 087 try { 088 InputStream inputStream = FileUtils.prepareFileInputStream(source); 089 InputStreamReader isr = new InputStreamReader(inputStream, Util.UTF8); 090 reader = new BufferedReader(isr); 091 Map<OpenPgpV4Fingerprint, Date> fingerprintDateMap = new HashMap<>(); 092 093 String line; int lineNr = 0; 094 while ((line = reader.readLine()) != null) { 095 lineNr++; 096 097 line = line.trim(); 098 String[] split = line.split(" "); 099 if (split.length != 2) { 100 LOGGER.log(Level.FINE, "Skipping invalid line " + lineNr + " in file " + source.getAbsolutePath()); 101 continue; 102 } 103 104 try { 105 OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(split[0]); 106 Date date = XmppDateTime.parseXEP0082Date(split[1]); 107 fingerprintDateMap.put(fingerprint, date); 108 } catch (IllegalArgumentException | ParseException e) { 109 LOGGER.log(Level.WARNING, "Error parsing fingerprint/date touple in line " + lineNr + 110 " of file " + source.getAbsolutePath(), e); 111 } 112 } 113 114 return fingerprintDateMap; 115 } finally { 116 CloseableUtil.maybeClose(reader, LOGGER); 117 } 118 } 119 120 static void writeFingerprintsAndDates(Map<OpenPgpV4Fingerprint, Date> data, File destination) 121 throws IOException { 122 if (data == null || data.isEmpty()) { 123 FileUtils.maybeDeleteFileOrThrow(destination); 124 return; 125 } 126 127 FileUtils.maybeCreateFileWithParentDirectories(destination); 128 129 BufferedWriter writer = null; 130 try { 131 OutputStream outputStream = FileUtils.prepareFileOutputStream(destination); 132 OutputStreamWriter osw = new OutputStreamWriter(outputStream, Util.UTF8); 133 writer = new BufferedWriter(osw); 134 for (OpenPgpV4Fingerprint fingerprint : data.keySet()) { 135 Date date = data.get(fingerprint); 136 String line = fingerprint.toString() + " " + 137 (date != null ? XmppDateTime.formatXEP0082Date(date) : XmppDateTime.formatXEP0082Date(new Date())); 138 writer.write(line); 139 writer.newLine(); 140 } 141 } finally { 142 CloseableUtil.maybeClose(writer, LOGGER); 143 } 144 } 145 146 private File getAnnouncedFingerprintsPath(BareJid contact) { 147 return new File(FileBasedOpenPgpStore.getContactsPath(basePath, contact), ANNOUNCED); 148 } 149}