afr0dita/afr0dita.go

383 lines
9.7 KiB
Go
Raw Normal View History

2022-12-27 10:00:40 +01:00
/*
Description: Tool to save images.
Auth0r: sml@lacashita.com
Use it only as educational purpose.
*/
package main
import (
"database/sql"
"fmt"
"html/template"
"log"
"net/http"
"io"
"errors"
"io/ioutil"
"os"
"os/exec"
"strings"
"time"
_ "github.com/mattn/go-sqlite3"
"crypto/md5"
"encoding/hex"
)
// Start Web PART
type urlinfo struct {
Date string
Url string
Tags string
Screenshot string
Separated []string
}
type Message struct {
Msj string
}
func main() {
var filedb string
var homepath = os.Getenv("HOME")
if os.Getenv("afr0dita") == "" {
filedb = homepath+"/afr0dita.db"
} else {
filedb = os.Getenv("afr0dita")
}
// Create ~/.afr0ditafiles folder to save screenshots
if _, err := os.Stat(homepath+"/.afr0ditafiles"); os.IsNotExist(err) {
err = os.Mkdir(homepath+"/.afr0ditafiles", 0755)
if err != nil {
log.Fatal(err)
}
}
// Link ~/.afr0ditafiles to screenshots/ folder.
if _, err := os.Stat("screenshots"); os.IsNotExist(err) {
cmd := exec.Command("/usr/bin/ln", "-s", homepath+"/.afr0ditafiles", "screenshots")
_, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
}
checkDB(filedb)
sqliteDatabase, _ := sql.Open("sqlite3", filedb)
defer sqliteDatabase.Close()
startServer(sqliteDatabase)
}
// Func to check if DB exists, if not create it.
func checkDB(filedb string) {
_, err := os.Stat(filedb)
//If file afr0dita.db doesnt exist.
if err != nil {
fmt.Println("[+]Creating Database\n")
file, err := os.Create(filedb)
if err != nil {
log.Fatal(err.Error())
}
file.Close()
sqlDB, _ := sql.Open("sqlite3",filedb)
defer sqlDB.Close()
createTable(sqlDB)
}
}
// Func to create DB.
func createTable(db *sql.DB) {
createafr0ditatable := `CREATE TABLE afr0dita (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"date" TEXT,
"url" TEXT,
"tags" TEXT,
"screenshot" TEXT
);`
statement, err := db.Prepare(createafr0ditatable)
if err != nil {
log.Fatal(err.Error())
}
statement.Exec()
log.Println("[+] afr0dita table created")
}
func startServer(db *sql.DB) {
// All requests to /screenshots/ will serve the static content of screenshots/
fs := http.FileServer(http.Dir("./screenshots"))
http.Handle("/screenshots/", http.StripPrefix("/screenshots/", fs))
http.HandleFunc("/", afr0ditaHandler(db))
// All requests to /styles/css will serve static/css/bootstrap.min.css.
http.HandleFunc("/styles/css", func(response http.ResponseWriter, request *http.Request) {
http.ServeFile(response, request, "static/css/bootstrap.min.css")
})
http.HandleFunc("/add", func(response http.ResponseWriter, request *http.Request) {
http.ServeFile(response, request, "static/html/add.html")
})
http.ListenAndServe("127.0.0.1:8001", nil)
}
func afr0ditaHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var repetido int
tagtose := r.URL.Query()["tags"]
tagtosearch := strings.Join(tagtose ," ")
urltoa := r.URL.Query()["url"]
urltoadd := strings.Join(urltoa, " ")
repetido = dupURL(db,urltoadd)
urltodel := r.URL.Query()["delete"]
urltodelete := strings.Join(urltodel ," ")
var homepath = os.Getenv("HOME")
filename := getFilename(urltoadd)
screenshotname := filename+".jpg"
screenshotfullpath := homepath+"/.afr0ditafiles/"+filename+".jpg"
if urltodelete != "" {
imageURLname := getImageName(db,urltodelete)
filetodelete := "screenshots/"+imageURLname
os.Remove(filetodelete)
deleteUrl(db,urltodelete)
}
if urltoadd == "" {
displayafr0dita(db, w,tagtosearch)
} else {
if repetido != 0 {
m := Message{"URL is duplicated."}
printMessage(w,m)
} else {
err := archiveURL(urltoadd,screenshotfullpath,w)
if err != nil {
m := Message{"Error with the image."}
printMessage(w,m)
} else {
insertUrl(db,urltoadd,tagtosearch,screenshotname)
m := Message{"URL added successfully."}
printMessage(w,m)
}
}
}
}
}
func displayafr0dita(db *sql.DB, w http.ResponseWriter,tagtosearch string) {
var Arrayurlinfo []urlinfo
var Repetidos []string
tmpl := template.Must(template.ParseFiles("static/html/main.html"))
p := urlinfo{}
if tagtosearch == "" {
row, err := db.Query("SELECT date,url,tags,screenshot FROM afr0dita")
if err != nil {
log.Fatal(err)
}
defer row.Close()
for row.Next() {
var date string
var url string
var tags string
var screenshot string
row.Scan( &date, &url, &tags, &screenshot)
// s is []string where tags are separated by ,
s := strings.Split(tags, ",")
//All s elementes are appended to Repetidos []string
for _,element := range s {
Repetidos = append(Repetidos,element)
}
p = urlinfo{date,url,tags,screenshot,s}
Arrayurlinfo = append(Arrayurlinfo, p)
}
}
if tagtosearch != "" {
row, err := db.Query("SELECT date,url,tags,screenshot FROM afr0dita where tags like ?","%"+tagtosearch+"%")
if err != nil {
log.Fatal(err)
}
defer row.Close()
for row.Next() {
var date string
var url string
var tags string
var screenshot string
row.Scan( &date, &url, &tags, &screenshot)
var s []string
s = strings.Split(tags, ",")
for _,element := range s {
Repetidos = append(Repetidos,element)
}
p = urlinfo{date,url,tags,screenshot,s}
Arrayurlinfo = append(Arrayurlinfo, p)
}
}
//Repetidos store now []string without tags duplicated.
Repetidos = removeDuplicates(Repetidos)
// Creates the last urlinfo struct with Repetidos.
lastp := urlinfo{"","","","",nil}
Arrayurlinfo = append(Arrayurlinfo,lastp)
tmpl.Execute(w,Arrayurlinfo)
}
// Func to Print Messages through Web.
func printMessage(w http.ResponseWriter, msg Message) {
tmpl := template.Must(template.ParseFiles("static/html/message.html"))
tmpl.Execute(w, msg)
}
// Inserting URL into SQL.
func insertUrl(db *sql.DB, url string, tagstoinsert string, screenshotname string) {
currentTime := time.Now()
date := currentTime.Format("01-02-2006")
insertURLSQL := `INSERT INTO afr0dita(date,url,tags,screenshot) VALUES (?,?,?,?)`
statement, err := db.Prepare(insertURLSQL)
if err != nil {
log.Fatalln(err.Error())
}
_, err = statement.Exec(date, url, tagstoinsert,screenshotname)
if err != nil {
log.Fatalln(err.Error())
}
}
// Delete URL.
func deleteUrl(db *sql.DB, url string) {
deleteURLSQL := `DELETE FROM afr0dita WHERE url = ?`
statement, err := db.Prepare(deleteURLSQL)
if err != nil {
log.Fatalln(err.Error())
}
_, err = statement.Exec(url)
if err != nil {
log.Fatalln(err.Error())
}
}
// Func to update an URL to add the filename of the screenshot.
func updateUrl(db *sql.DB, filename string, url string) {
updateURLSQL := `UPDATE afr0dita set screenshot = ? WHERE url = ?`
statement, err := db.Prepare(updateURLSQL)
if err != nil {
log.Fatalln(err.Error())
}
_, err = statement.Exec(filename,url)
if err != nil {
log.Fatalln(err.Error())
}
}
// Func to check if URL is duplicated, it returns an int with the number of times that URL is repeated.
func dupURL(db *sql.DB, url string) int{
var total int
row, err := db.Query("SELECT count(*) as total from afr0dita where url = ?",url)
if err != nil {
log.Fatal(err)
}
defer row.Close()
for row.Next() {
var count int
row.Scan( &count)
total = count
}
return total
}
// Func to get the "id" that will be used as imagename from an given URL, it returns the id.
func getImageName(db *sql.DB, url string) string{
var imagename string
row, err := db.Query("SELECT screenshot from afr0dita where url = ?",url)
if err != nil {
log.Fatal(err)
}
defer row.Close()
for row.Next() {
var screenshot string
row.Scan( &screenshot)
imagename = screenshot
}
return imagename
}
// Func to remove duplicated strings from an slice.
func removeDuplicates(strSlice []string) []string {
allKeys := make(map[string]bool)
list := []string{}
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
// Func to create a filename using the md5 hash of the url name.
func getFilename(text string) string {
hash := md5.Sum([]byte(text))
return hex.EncodeToString(hash[:])
}
// Func to take one screenshot from the given URL and save it to a file. It returns error.
func archiveURL(URL string, fileName string,w http.ResponseWriter) error {
// Set http client to make the request with custom User-Agent.
client := &http.Client{}
req, _ := http.NewRequest("GET", URL, nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0")
// Makes request.
response, err := client.Do(req)
if err != nil {
return err
}
defer response.Body.Close()
// Check the URL and if the mime is an image or not. If not return an error.
bytes, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
mimeType := http.DetectContentType(bytes)
if mimeType != "image/jpg" && mimeType != "image/jpg" && mimeType != "image/png" && mimeType != "image/bmp" && mimeType != "image/jpeg" {
m := Message{"URL is not an image."}
printMessage(w,m)
err := errors.New("URL is not an image")
return err
}
// If mime is an image makes a request to get the image.
response, err = client.Do(req)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != 200 {
m := Message{"Returning not 200 Code"}
printMessage(w,m)
}
//Create a empty file
file, err := os.Create(fileName)
if err != nil {
m := Message{"Error creating the image."}
printMessage(w,m)
return err
}
defer file.Close()
//Write the bytes to the file
_, err = io.Copy(file, response.Body)
if err != nil {
m := Message{"Error copying the image."}
printMessage(w,m)
return err
}
return nil
}