Initial commit
This commit is contained in:
commit
a12d13aa00
85
api.go
Normal file
85
api.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DispatchFunction func(*User, []string, io.ReadCloser) (interface{}, error)
|
||||||
|
|
||||||
|
var apiRoutes = map[string]*(map[string]struct{AuthFunction;DispatchFunction}){
|
||||||
|
"version": &ApiVersionRouting,
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiHandler struct {
|
||||||
|
Authenticate func(*http.Request) (*User)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiHandler(Authenticate func(*http.Request) (*User)) (apiHandler) {
|
||||||
|
return apiHandler{Authenticate}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user := a.Authenticate(r)
|
||||||
|
|
||||||
|
log.Printf("Handling %s API request from %s: %s %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, user, r.UserAgent())
|
||||||
|
|
||||||
|
// Extract URL arguments
|
||||||
|
var sURL = strings.Split(r.URL.Path[1:], "/")
|
||||||
|
if sURL[len(sURL)-1] == "" && len(sURL) > 0 {
|
||||||
|
// Remove trailing /
|
||||||
|
sURL = sURL[:len(sURL)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
var ret interface{}
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
// Refuse too large requests
|
||||||
|
if r.ContentLength < 0 || r.ContentLength > 10485760 || ((r.Method == "DELETE" || r.Method == "GET" || r.Method == "HEAD") && r.ContentLength > 0) {
|
||||||
|
http.Error(w, "{errmsg:\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route request
|
||||||
|
if len(sURL) == 0 {
|
||||||
|
err = errors.New(fmt.Sprintf("No action provided"))
|
||||||
|
} else if h, ok := apiRoutes[sURL[0]]; ok {
|
||||||
|
if f, ok := (*h)[r.Method]; ok {
|
||||||
|
if f.AuthFunction(user, sURL[1:]) {
|
||||||
|
ret, err = f.DispatchFunction(user, sURL[1:], r.Body)
|
||||||
|
} else {
|
||||||
|
w.Header().Set("WWW-Authenticate", "Basic realm=\"YouP0m API\"")
|
||||||
|
http.Error(w, "{errmsg:\"You are not allowed to do this request.\"}", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = errors.New(fmt.Sprintf("Invalid action (%s) provided for %s.", r.Method, sURL[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format response
|
||||||
|
resStatus := http.StatusOK
|
||||||
|
if err != nil {
|
||||||
|
ret = map[string]string{"errmsg": err.Error()}
|
||||||
|
resStatus = http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret == nil {
|
||||||
|
ret = map[string]string{"errmsg": "Page not found"}
|
||||||
|
resStatus = http.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if j, err := json.Marshal(ret); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("{errmsg:\"%q\"}", err), http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(resStatus)
|
||||||
|
w.Write(j)
|
||||||
|
}
|
||||||
|
}
|
78
auth.go
Normal file
78
auth.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nyarla/go-crypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Htpasswd struct {
|
||||||
|
entries map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHtpasswd(path string) (*Htpasswd, error) {
|
||||||
|
if fd, err := os.Open(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
htpasswd := Htpasswd{
|
||||||
|
map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(fd)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.SplitN(strings.TrimSpace(scanner.Text()), ":", 2)
|
||||||
|
if len(line) == 2 && len(line[1]) > 2 {
|
||||||
|
htpasswd.entries[line[0]] = line[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &htpasswd, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Htpasswd) Authenticate(username, password string) *User {
|
||||||
|
if hash, ok := h.entries[username]; !ok {
|
||||||
|
return nil
|
||||||
|
} else if crypt.Crypt(password, hash[:2]) != hash {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
var u = User{username}
|
||||||
|
return &u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Request authentication
|
||||||
|
|
||||||
|
func Authenticate(htpasswd Htpasswd, r *http.Request) (*User) {
|
||||||
|
// Authenticate the user if any
|
||||||
|
if username, password, ok := r.BasicAuth(); ok {
|
||||||
|
return htpasswd.Authenticate(username, password)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Page rules
|
||||||
|
|
||||||
|
type AuthFunction func(*User, []string) bool
|
||||||
|
|
||||||
|
func PublicPage(u *User, args []string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivatePage(u *User, args []string) bool {
|
||||||
|
return u != nil
|
||||||
|
}
|
54
main.go
Normal file
54
main.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var PublishedImgDir string
|
||||||
|
var NextImgDir string
|
||||||
|
var ThumbsDir string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bind := flag.String("bind", "0.0.0.0:8080", "Bind port/socket")
|
||||||
|
htpasswd_file := flag.String("htpasswd", "", "Admin passwords file, Apache htpasswd format")
|
||||||
|
flag.StringVar(&PublishedImgDir, "publishedimgdir", "./images/published/", "Directory where save published pictures")
|
||||||
|
flag.StringVar(&NextImgDir, "nextimgdir", "./images/next/", "Directory where save pictures to review")
|
||||||
|
flag.StringVar(&ThumbsDir, "thumbsdir", "./images/thumbs/", "Directory where generate thumbs")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
htpasswd := &Htpasswd{}
|
||||||
|
|
||||||
|
if htpasswd_file != nil && *htpasswd_file != "" {
|
||||||
|
log.Println("Reading htpasswd file...")
|
||||||
|
var err error
|
||||||
|
if htpasswd, err = NewHtpasswd(*htpasswd_file); htpasswd == nil {
|
||||||
|
log.Fatal("Unable to parse htpasswd:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Checking paths...")
|
||||||
|
if _, err := os.Stat(PublishedImgDir); os.IsNotExist(err) {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(NextImgDir); os.IsNotExist(err) {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(ThumbsDir); os.IsNotExist(err) {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Registering handlers...")
|
||||||
|
authFunc := func (r *http.Request) (*User){ return Authenticate(*htpasswd, r) }
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/api/", http.StripPrefix("/api", ApiHandler(authFunc)))
|
||||||
|
mux.Handle("/", http.FileServer(http.Dir("./static/")))
|
||||||
|
|
||||||
|
log.Println("Ready, listening on", *bind)
|
||||||
|
if err := http.ListenAndServe(*bind, mux); err != nil {
|
||||||
|
log.Fatal("Unable to listen and serve: ", err)
|
||||||
|
}
|
||||||
|
}
|
1
static/index.html
Normal file
1
static/index.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<h1>Welcome on YouP0m!</h1>
|
19
version.go
Normal file
19
version.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ApiVersionRouting = map[string]struct{AuthFunction; DispatchFunction}{
|
||||||
|
"GET": {PublicPage, showVersion},
|
||||||
|
}
|
||||||
|
|
||||||
|
func showVersion(u *User, args []string, body io.ReadCloser) (interface{}, error) {
|
||||||
|
m := map[string]interface{}{"version": 0.1}
|
||||||
|
|
||||||
|
if u != nil {
|
||||||
|
m["youare"] = *u
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user