Java: getting info from a specific line in files - java

I have made this nice login program with all usernames in one .txt file and all passwords in the other. so when the user enters a username and it corresponds to one of the names on for example line 5 of the first file, I want the program to read the password from the 5th line in the second file and see if it matches with the password given by the user. I just don't know how to read from a specific file or how to see on what line it is,
here is the code I have now.
package databarry_;
import java.util.*;
import java.io.*;
import java.lang.*;
import javax.swing.JOptionPane;
public class interfacelogin {
public static void main (String[] args) {
boolean check1=false, check2=false, check3=false;
int trys = 3;
while (check3 == false){
int id1 = 0;
int id2 = 0;
String username = null;
String password = null;
Scanner fileout = null;
Scanner fileout2 = null;
try{
fileout = new Scanner(new File("username.txt"));
}
catch(Exception e){
JOptionPane.showMessageDialog(null, "Fatal Error, please Reboot or reinstal program", "Boot", JOptionPane.PLAIN_MESSAGE);
}
String Username = JOptionPane.showInputDialog("Please enter your username");
while(fileout.hasNext()){
username = fileout.next();
if(Username.equals(username))
check1=true;
}
try{
fileout2 = new Scanner(new File("password.txt"));
}
catch(Exception e){
JOptionPane.showMessageDialog(null, "Fatal Error, please Reboot or reinstal program", "Boot", JOptionPane.PLAIN_MESSAGE);
}
String Password = JOptionPane.showInputDialog("Please enter your username");
while(fileout2.hasNext()){
password = fileout2.next();
if(Password.equals(password) && id1 == id2)
check2=true;
}
if (check1 == true && check2 == true){
JOptionPane.showMessageDialog(null, "succeded", "login", JOptionPane.PLAIN_MESSAGE);
check3 = true;
} else {
if (trys > 1){
trys--;
JOptionPane.showMessageDialog(null, "bad login, you have " + trys + " try's remaining", "login", JOptionPane.PLAIN_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, "To many bad logins, program is closing", "login", JOptionPane.PLAIN_MESSAGE);
check3 = true;
}
}
}
}
}
as you can see the only big flow is that if you enter a password and username that ar not on the same line (so not linked to each other) in the files (like both on line 5) the user gets trough.

Best way to do so is to read both files and store your login/password pairs in a
HashMap<String, String>
Then you can easily check, if entered login-password pair is a match.
Map<String, String> map = new HashMap<>();
while(fileout.hasNext() && fileout2.hasNext()){
username = fileout.next();
password = fileout2.next();
map.put(username, password);
}
...
if (password.equals(map.get(username)) {
...
}

If you don't have problems with memory, or if the amount of users is not so high, I suggest you to read together both files only once when the program starts, and store the data in a HashMap using the user as key and the password as value:
BufferedReader users = new BufferedReader(new FileReader("username.txt"));
BufferedReader passwords = new BufferedReader(new FileReader("password.txt"));
HashMap logins = new HashMap<String, String>();
String user,pass;
while ((user=users.readLine())!=null) {
pass=passwords.readLine();
logins.put(user, pass);
}
and then to check a username/password:
String username = null; //the username specified by the user
String password = null; //the password specified by the user
if (logins.get(user).compareTo(password)==0) {
//correct pass
} else {
//wrong pass
}
This is only a sketch to give you and idea (it does not consider special cases, as the user insert a username that does not exists, and so on...)

Related

How can I compare User and password input to a text file?

Heyoo! I'm creating a simple login form using the swing package in java and I am having trouble in checking if the username and password is correct or not.
this here is the code currently:
public void actionPerformed(ActionEvent e) {
try{
File user = new File("Usernames.txt");
File pass = new File("Passwords.txt");
FileReader frUsername = new FileReader(user);
FileReader frPassword = new FileReader(pass);
BufferedReader brUsername = new BufferedReader(frUsername);
BufferedReader brPassword = new BufferedReader(frPassword);
String username = brUsername.readLine();
String password = brPassword.readLine();
if (e.getSource() == btnLogin){
while(username != null && password != null){
if ((txtUsername.getText()).equals(username) && (new String (jpfPassword.getPassword()).equals(password))){
JOptionPane.showMessageDialog(null, "Welcome: " + username, "Login Successful",JOptionPane.INFORMATION_MESSAGE); //this is for testing purposes only
}
else{
JOptionPane.showMessageDialog(null, "Invalid Username or Password", "Unable to Login",0); //this is for testing purposes only
}
break;
}
}
brUsername.close();
brPassword.close();
}
catch(IOException err){
System.err.println("File not found.");
}
}
}
The idea is to have multiple accounts stored in the password and usernames files. for example the file content is:
Username.txt:
SampleUsername1
SampleUsername2
Password.txt:
SamplePassword1
SamplePassword2
If line 1 from the username file is "sampleUsername1" then the password for it is also from line 1 "samplePassword1" of the password file. if the user and password isn't the same line or not in the file, it should give an "invalid" error. I know it is not secure to put passwords in a txt file but this is only for practice purposes as I am still learning how to code. Any kind of help and tips is really appreciated. Thanks!
This works fine for me:
public static void main(String[] args) {
String txtUsername = "username2";
String jpfPassword = "password2";
try {
File user = new File("Usernames.txt");
File pass = new File("Passwords.txt");
FileReader frUsername = new FileReader(user);
FileReader frPassword = new FileReader(pass);
BufferedReader brUsername = new BufferedReader(frUsername);
BufferedReader brPassword = new BufferedReader(frPassword);
String username = brUsername.readLine();
String password = brPassword.readLine();
boolean loginSuccess = false;
while (username != null && password != null) {
if ((txtUsername).equals(username) && (jpfPassword.equals(password))) {
JOptionPane.showMessageDialog(null, "Welcome: " + username, "Login Successful", JOptionPane.INFORMATION_MESSAGE); //this is for testing purposes only
loginSuccess = true;
break;
}
username = brUsername.readLine();
password = brPassword.readLine();
}
if (!loginSuccess) {
JOptionPane.showMessageDialog(null, "Invalid Username or Password", "Unable to Login", 0); //this is for testing purposes only
}
brUsername.close();
brPassword.close();
} catch (IOException e) {
e.printStackTrace();
}
}
My Usernames.txt and Passwords.txt look like this
username
username1
username2
respectively
password
password1
password2
The main problem was that you were only checking the first line. Once you changed the break; to the readline() methods, the and you can't give the failed message everytime you check a name. That's why you have to loop through everything first, and then check if you failed or not.

Comparing user id with user id from textfile

I want to add user information and reject if user id already exists. Currently, my code displays "ID already Exists!" but still adds the user information.
this is my code:
String id = EditManagerID.getText();
String password = EditManagerPassword.getText();
String name = EditManagerName.getText();
String contact = EditManagerContact.getText();
String address = EditManagerAddress.getText();
String role = EditManagerRole.getText();
try
{
String reader;
BufferedReader br = new BufferedReader(new FileReader("ManagerDetails.txt"));
while ((reader = br.readLine())!=null)
{
String[] split = reader.split(",");
if (id.equals(split[0]))
{
JOptionPane.showMessageDialog(null, "ID already exists!", "Warning", JOptionPane.WARNING_MESSAGE);
}
}
}
catch(Exception e)
{
}
if(id.isEmpty() || password.isEmpty() || name.isEmpty() || contact.isEmpty() || address.isEmpty() || role.isEmpty())
{
JOptionPane.showMessageDialog(null, "Please Fill In Everything!", "Error", JOptionPane.ERROR_MESSAGE);
}
else
{
ClassUser u = new ClassUser (id, password, name, address, contact, role);
File f = new File("ManagerDetails.txt");
try(PrintWriter pw = new PrintWriter(new FileWriter(f, true))){
pw.println(u.toString()); //print into txtfile
JOptionPane.showMessageDialog(this, "Added Successfully!");
} catch(Exception e){
JOptionPane.showMessageDialog(null, "Invalid Input!", "Error", JOptionPane.ERROR_MESSAGE);
}
I tried to add an 'if-else' but not sure what to put as a statement.
In your code you 're saying if id already exists show message "ID already exists!" and then you continue adding users info anyway.
In your if id already exists statement set a boolean variable found to true and then make one more if statement and check if found is true or false.
boolean found = false;
// ...
if (id.equals(split[0]))
{
JOptionPane.showMessageDialog(null, "ID already exists!", "Warning",
JOptionPane.WARNING_MESSAGE);
found = true;
//stop the while
break;
}
// ...
//check if id already exists or not. if not then add users info
if(!found){
//put your adding users info code here
}

Java help me Login with txt File

I do not understand why I always get the result is false !
I tried sysout both input and split []. They are all the same
,Also I can not use this.dispose ()
public void actionPerformed(ActionEvent e) {
String record = null;
FileReader in = null;
try {
in = new FileReader("login.txt");
BufferedReader br = new BufferedReader(in);
String username = txtUser.getText();
String pass = txtPass.getText();
while ((record = br.readLine()) !=null) {
String[] split = record.split(",");
if (username.equals(split[0]) && pass.equals(split[1])) {
JOptionPane.showMessageDialog(null, "YOU IS LOG IN",
"OK", JOptionPane.WARNING_MESSAGE);
}
else {
JOptionPane.showMessageDialog(null, "ACCOUNT OR PASSWORD IS NOT ACCURATE",
"False", JOptionPane.WARNING_MESSAGE);
}
// Delete else branch
}
} catch (IOException e) {
e.getCause();
}
}
});
it is login.txt
user,pass
Taka,123
and txtpass is a textfiel normal
Just going out on a limb here as I cannot comment yet, but it might have to do with your inclusion of user,pass in the login.txt file. Try to remove that line so the file starts with the actual user and password.
Also, I'm not sure about the use of this code, but storing passwords in a plaintext file is never a good idea.

Trying to finish off a java code program

I am working on a java code for school and I have spent days on it and I just don't think that I'm heading in the right direction. Here is the info on the project:
For security-minded professionals, it is important that only the appropriate people gain access to data in a computer system. This is called authentication. Once users gain entry, it is also important that they only see data related to their role in a computer system. This is called authorization. For the zoo, you will develop an authentication system that manages both authentication and authorization. You have been given a credentials file that contains credential information for authorized users. You have also been given three files, one for each role: zookeeper, veterinarian, and admin. Each role file describes the data the particular role should be authorized to access. Create an authentication system that does all of the following:
 Asks the user for a username
 Asks the user for a password
 Converts the password using a message digest five (MD5) hash
It is not required that you write the MD5 from scratch. Use the code located in this document and follow the comments in it to perform this operation.

Checks the credentials against the valid credentials provided in the credentials file
Use the hashed passwords in the second column; the third column contains the actual passwords for testing and the fourth row contains the
role of each user.

Limits failed attempts to three before notifying the user and exiting the program

Gives authenticated users access to the correct role file after successful authentication
The system information stored in the role file should be displayed. For example, if a zookeeper’s credentials is successfully authenticated, then the contents from the zookeeper file will be displayed. If an admin’s credentials is successfully authenticated, then the contents from the admin file will be displayed.
 Allows a user to log out
 Stays on the credential screen until either a successful attempt has been made, three unsuccessful attempts have been made, or a user chooses to exit
Here are the five text files I was given:
admin.txt
Hello, System Admin!
As administrator, you have access to the zoo's main computer system.
This allows you to monitor users in the system and their roles.
credentials.txt
griffin.keyes 108de81c31bf9c622f76876b74e9285f "alphabet soup" zookeeper
rosario.dawson 3e34baa4ee2ff767af8c120a496742b5 "animal doctor" admin
bernie.gorilla a584efafa8f9ea7fe5cf18442f32b07b "secret password" veterinarian
donald.monkey 17b1b7d8a706696ed220bc414f729ad3 "M0nk3y business" zookeeper
jerome.grizzlybear 3adea92111e6307f8f2aae4721e77900 "grizzly1234" veterinarian
bruce.grizzlybear 0d107d09f5bbe40cade3de5c71e9e9b7 "letmein" admin
validateCredentials.txt
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package authentication;
import java.security.MessageDigest;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner;
/**
*
* #author JoeP
*/
public class ValidateCredentials {
private boolean isValid;
private String filePath;
private String credentialsFileName;
public ValidateCredentials() {
isValid = false;
//filePath = "C:\\Users\\joep\\Documents\\NetBeansProjects\\ Authentication\\";
filePath = "";
credentialsFileName = "credentials";
}
public boolean isCredentialsValid(String userName, String passWord) throws Exception {
String original = passWord;
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(original.getBytes());
byte[] digest = md.digest();
StringBuffer sb = new StringBuffer();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
System.out.println("");
System.out.println("original:" + original);
System.out.println("digested:" + sb.toString()); //sb.toString() is what you'll need to compare password strings
isValid = readDataFiles(userName, sb.toString());
return isValid;
}
public boolean readDataFiles(String userName, String passWord) throws IOException {
FileInputStream fileByteStream1 = null; // File input stream
FileInputStream fileByteStream2 = null; // File input stream
Scanner inFS1 = null; // Scanner object
Scanner inFS2 = null; // Scanner object
String textLine = null;
boolean foundCredentials = false;
// Try to open file
System.out.println("");
System.out.println("Opening file " + credentialsFileName + ".txt");
fileByteStream1 = new FileInputStream(filePath + "credentials.txt");
inFS1 = new Scanner(fileByteStream1);
System.out.println("");
System.out.println("Reading lines of text.");
while (inFS1.hasNextLine()) {
textLine = inFS1.nextLine();
System.out.println(textLine);
if (textLine.contains(userName) && textLine.contains(passWord)) {
foundCredentials = true;
break;
}
}
// Done with file, so try to close it
System.out.println("");
System.out.println("Closing file " + credentialsFileName + ".txt");
if (textLine != null) {
fileByteStream1.close(); // close() may throw IOException if fails
}
if (foundCredentials == true) {
// Try to open file
System.out.println("");
System.out.println("Opening file " + userName + ".txt");
fileByteStream2 = new FileInputStream(filePath + userName + ".txt");
inFS2 = new Scanner(fileByteStream2);
System.out.println("");
while (inFS2.hasNextLine()) {
textLine = inFS2.nextLine();
System.out.println(textLine);
}
// Done with file, so try to close it
System.out.println("");
System.out.println("Closing file " + userName + ".txt");
if (textLine != null) {
fileByteStream2.close(); // close() may throw IOException if fails
}
}
return foundCredentials;
}
}
veterinarian.txt
Hello, Veterinarian!
As veterinarian, you have access to all of the animals' health records. This allows you to view each animal's medical history, current treatments/illnesses (if any), and maintain a vaccination log.
zookeeper.txt
Hello, Zookeeper!
As zookeeper, you have access to all of the animals information and their daily monitoring logs. This allows you to track their feeding habits, habitat conditions, and general welfare.
Finally, this is the code that I have so far, but when I try to run it in Netbeans it just won't work.
import java.io.File;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.List;
import java.util.Scanner;
public class Authentication {
public static void main(String args[]) {
try {
Scanner scan = null;
scan = new Scanner(new File("credentials.txt"));
String credentials[][] = new String[100][4];
int count = 0;
while (scan.hasNextLine()) {
//read name and hased pass
credentials[count][0] = scan.next();
credentials[count][1] = scan.next();
//get original pass from file
String l[] = scan.nextLine().split("\"[ ]+");
l[0] = l[0].trim();
l[0] = l[0].replace("\"", "");
credentials[count][2] = l[0];
credentials[count][3] = l[1].trim();
count++;
}
//ask for user input
Scanner scanio = new Scanner(System.in);
boolean RUN = true;
int tries = 0;
while (RUN) {
System.out.println("**WELCOME**");
System.out.println("1) Login");
System.out.println("2) Exit");
int ch = Integer.parseInt(scanio.nextLine().trim());
if (ch == 1) {
//increment number of attempts
tries++;
//ask for user and pass
System.out.print("Input username:");
String username = scanio.nextLine();
System.out.print("Input password:");
String password = scanio.nextLine();
//generate hash
MessageDigest md;
md = MessageDigest.getInstance("MD5");
md.update(password.getBytes());
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
String hPassword = sb.toString();
boolean badUser = true;
for (int i = 0; i < count; i++) {
if (username.contentEquals(credentials[i][0])) {
if (hPassword.contentEquals(credentials[i][1])) {
//everything looks good. login
List<String> data = null;
//check type of user and print
switch (credentials[i][3]) {
case "zookeeper":
data = Files.readAllLines(Paths.get("zookeeper.txt"), Charset.defaultCharset());
break;
case "admin":
data = Files.readAllLines(Paths.get("admin.txt"), Charset.defaultCharset());
break;
case "veterinarian":
data = Files.readAllLines(Paths.get("veterinarian.txt"), Charset.defaultCharset());
break;
default:
break;
}
for (String s : data) {
System.out.println(s);
}
//reset tries
tries = 0;
//now what to do?
System.out.println("\n1) Logout.");
System.out.println("2) Exit.");
ch = Integer.parseInt(scanio.nextLine().trim());
if (ch == 2) {
RUN = false;
}
badUser = false;
break;
}
}
}
if (badUser) {
System.out.println("Invalid Username or password.");
}
} else {
RUN = false;
break;
}
//thief alert!!
if (tries == 3) {
RUN = false;
System.out.println("Too many invlaid attempts.");
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
PLEASE HELP ME!!!
THANK YOU!!!
Youa re getting a FileNotFoundException. That is, Netbeans is not finding your credentials.txt file (which possibly is located in a different directory). Make sure your credentials.txt file is located in your Netbeans project directory, as follows:
1) Find your project directory by going to the "Netbeans "Projects" tab, right-clicking in your project folder and selecting "Properties". The "Project folder" will be shown on at the top of the new window displayed (i.e. "C:\Users\admin\Documents\NetBeansProjects\YourProjectName").
2) Place your credentials.txt file in that project directory.
3) Re-run your code.

Java does not append to the .DAT file

I am writing a basic program that has 3 menu options: Create User, Sign In, and Exit. The user can then choose one of these menu options.
If they choose Create User, they will be prompted to enter a User ID and password (which must follow patterns), and will then check against a .DAT file to ensure the User ID has not already been taken. After successful completion, the program will write the new User ID and password to the end of the .DAT file.
If they choose Sign In, they will be prompted to enter their User ID, followed by their Password, and the program will then read the .DAT file to validate they are on record.
Choosing exit will display a message, "You have signed out."
I am fairly new at java programming just as a forewarning.
Issues I am encountering with my code:
Choosing new user does not append to the .DAT file
Choosing Sign In - program does not seem to correctly check .DAT file because even-
though I am entering an existing account information it still gives my error "Invalid User ID."
import java.io.*;
import java.util.*;
/**
* This program will utilize a menu structure and validate input if the user doesn't choose
* a correct option. Writes a new ID and password to .dat file when user chooses to
* create new user from menu.
*
* #author CMS
* #Date 7/28/2014
*/
public class Passwordv2 {
static boolean answer = true;
static final String MENUANSW = "[1-3]{1}", USERID = "[A-Z,a-z]{6}-[0-9]{2}"; //, PASSWORD = "";
static String iMenuOption="", iID, recPassword, recUserID, password;
static Scanner scanner,scannerDat;
static PrintWriter pw;
public static void main(String[] args) {
init();
while (answer == true) {
menu();
if (iMenuOption.equals("1")) createUser();
else
if (iMenuOption.equals("2")) signIn();
else {answer = false;}
}
closing();
} // end of main
public static void init(){
//User input scanner
scanner = new Scanner(System.in);
//PrintWriter
try {
pw = new PrintWriter(new FileOutputStream (new File ("account.dat"),true) );
}
catch(FileNotFoundException ex) {
}
} // end of INIT
public static String menu(){
do {System.out.println("Please select from the following:");
System.out.println("1. Create a New User");
System.out.println("2. Sign in");
System.out.println("3. Exit");
iMenuOption = scanner.next();
answer = isValMenuOption(iMenuOption);
if (answer == false) { System.out.print("Incorrect Choice. ");}
} while (!answer);
return iMenuOption;
}
public static boolean isValMenuOption(String iMenuOption) {
return(iMenuOption.matches(MENUANSW));
}
public static void createUser() {
boolean validID = true, newID = true;
do {if (!validID) {System.out.println("User ID did not meet requirements.");}
if (!newID) {System.out.println("This User ID has been taken.");}
System.out.println("Please select a User Id (6 letters, followed by a dash (-), followed by 2 numbers).");
iID = scanner.next();
validID = isValidID(iID);
newID = isNewID(iID);}
while (validID==false || newID == false);
boolean valLength = true, valNum = true, valUpper = true, valLower = true;
do{ System.out.println("Please select a Password:");
System.out.println("(6-12 characters, one number, one upper case, one lower case, no white space or symbols).");
password = scanner.next();
valLength = isValLength(password);
valNum = valNum(password);
valUpper = valUpper(password);
valLower = valLower(password);}
while (!valLength || !valNum || !valUpper || !valLower);
pw.println(iID);
pw.println(password);
//menu();
}
public static boolean isValidID(String iID){
return(iID.matches(USERID));
}
public static boolean isNewID(String iID){
answer = true;
// Dat file scanner
try {
scannerDat = new Scanner(new File("account.dat"));
scannerDat.useDelimiter("\r\n");
}
catch (Exception e) {
e.printStackTrace();
System.out.println("File error");
System.exit(1);
}
while (scannerDat.hasNext()) {
recUserID = scannerDat.next();
recPassword = scannerDat.next();
if (recUserID.equals(iID)) {
answer = false;
break;
}
}
return answer;
}
public static boolean isValLength(String password) {
if (password.length() <6 || password.length() > 12) System.out.println("Password did not meet length requirements. ");
return(password.length() >= 6 && password.length() <= 12);
}
public static boolean valNum(String password) {
if (password.matches(".*[0-9].*") == false) System.out.println("Password must contain at least one number. ");
return(password.matches(".*[0-9].*"));
}
public static boolean valUpper(String password){
if (password.matches(".*[A-Z].*") == false) System.out.println("Password must contain one upper case letter.");
return (password.matches(".*[A-Z].*"));
}
public static boolean valLower(String password){
if (password.matches(".*[a-z].*") == false) System.out.println("Password must contain one lower case letter.");
return (password.matches(".*[a-z].*"));
}
public static void signIn() {
boolean newID;
System.out.println("Enter User ID.");
iID = scanner.next();
System.out.println("Enter Password.");
password = scanner.next();
newID = isNewID(iID);
if (newID == false) {
if (password.equals(recPassword)) {System.out.println("Authenticated. You have signed in.");}
else {System.out.println("Invalid Password.");}
}
else {System.out.println("Invalid User ID.");}
}
public static void closing(){
System.out.println("You signed out.");
pw.close();
}
} // end of program
My .DAT file simply has
aabbcc-11
Onetwo3
aaabbb-22
Onetwo34
Change this line:
scannerDat.useDelimiter("\r\n");
to
scannerDat.useDelimiter("\n");
Works for me!
The first bad thing you are doing is that you have a PrintWriter (pw) and a Scanner (scannerDat) both accessing the same file and neither of them closing the access to the file, except right at the end, the pw is closed.
isNewId is the main culprit. Inside here you are better off using a FileReader instead of a Scanner. Declare the FileReader locally within the method and ensure that the file access is closed before exiting, this procedure.
Also within isNewId - don't call System.exit(); In a program this size it's OK, but anything bigger than this it is a cardinal sin and you should never just exit a program as ungracefully as this.
You need to flush your PrintWriter in order for it to do append to the file immediately. Otherwise it would just store it in the buffer to write it into the file eventually
Also, please check what user #simo.3792095 said about your code. You should not have several file streams opened at the same time. Either open/close your streams every time you do something with them, or read the whole data file on program start, then work with in-memory data, then save on exit. It is much easier to work with in-memory data, but if your program crashes all of the in-memory changes will be lost.
Thanks for all who replied. In the end, the problem ended up being that I had a "rough draft" java class, which I then copied and pasted the code of into a new java class under the same java project, which seemed to be giving me issues. Once I created a new java project and used the valid java class it worked fine. I also added the pw.flush(); method to my code so I was able to append to the file immediately instead of upon closing. Also removed the system.exit.

Categories

Resources