package net.rustnotmc.auth; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; /** * Reads and writes the account file. * * Saving goes to a temporary file and is then moved into place. Writing the * real file directly means a crash halfway through leaves a server with half * its accounts and no way to know which half, and the damage is only discovered * the next time somebody tries to log in. */ public final class AccountFile { /** Format marker, so a future format can be told apart from this one. */ private static final String HEADER = "# rsx-accounts v1"; private AccountFile() { } public static void load(Path path, AccountStore store) throws IOException { if (!Files.exists(path)) { Files.createDirectories(path.toAbsolutePath().getParent()); Files.createFile(path); return; } BufferedReader reader = new BufferedReader( new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8)); try { String line; int lineNumber = 0; while ((line = reader.readLine()) != null) { lineNumber++; if (line.isEmpty() || line.startsWith("#")) { continue; } String[] parts = line.split("\t"); if (parts.length < 4) { throw new IOException("malformed account file at line " + lineNumber); } long created; long last; try { created = Long.parseLong(parts[2]); last = Long.parseLong(parts[3]); } catch (NumberFormatException e) { throw new IOException("bad timestamp at line " + lineNumber); } store.restore(new AccountStore.Account(parts[0], parts[1], created, last)); } } finally { reader.close(); } } public static void save(Path path, AccountStore store) throws IOException { Path directory = path.toAbsolutePath().getParent(); Files.createDirectories(directory); Path temporary = Files.createTempFile(directory, "accounts", ".tmp"); Writer writer = new OutputStreamWriter( Files.newOutputStream(temporary), StandardCharsets.UTF_8); try { writer.write(HEADER); writer.write('\n'); for (AccountStore.Account account : store.all()) { writer.write(account.encode()); writer.write('\n'); } } finally { writer.close(); } try { Files.move(temporary, path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } catch (AtomicMoveNotSupportedException e) { Files.move(temporary, path, StandardCopyOption.REPLACE_EXISTING); } } }