383 lines
9.7 KiB
Go
383 lines
9.7 KiB
Go
|
|
/*
|
||
|
|
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
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|