2020-02-09 14:21:53 +01:00
package routes
import (
"database/sql"
"fmt"
"io"
2020-02-21 12:05:28 +01:00
"log"
2020-02-09 14:21:53 +01:00
"net/http"
"os"
2020-02-23 12:43:45 +01:00
"path"
"strconv"
2020-02-09 14:21:53 +01:00
2020-02-21 16:50:50 +01:00
"github.com/gorilla/mux"
2020-06-14 17:58:50 +02:00
"golang.org/x/crypto/bcrypt"
2020-02-21 16:50:50 +01:00
2020-02-20 17:31:41 +01:00
"github.com/viktorstrate/photoview/api/graphql/auth"
2020-02-09 14:21:53 +01:00
"github.com/viktorstrate/photoview/api/graphql/models"
2020-02-23 12:43:45 +01:00
"github.com/viktorstrate/photoview/api/scanner"
2020-02-09 14:21:53 +01:00
)
2020-02-21 16:50:50 +01:00
func RegisterPhotoRoutes ( db * sql . DB , router * mux . Router ) {
router . HandleFunc ( "/{name}" , func ( w http . ResponseWriter , r * http . Request ) {
image_name := mux . Vars ( r ) [ "name" ]
2020-02-09 14:21:53 +01:00
2020-02-23 18:00:08 +01:00
row := db . QueryRow ( "SELECT photo_url.purpose, photo_url.content_type, photo_url.photo_id FROM photo_url, photo WHERE photo_url.photo_name = ? AND photo_url.photo_id = photo.photo_id" , image_name )
2020-02-09 14:21:53 +01:00
var purpose models . PhotoPurpose
var content_type string
2020-02-11 23:35:35 +01:00
var photo_id int
2020-02-09 14:21:53 +01:00
2020-02-23 18:00:08 +01:00
if err := row . Scan ( & purpose , & content_type , & photo_id ) ; err != nil {
2020-02-09 14:21:53 +01:00
w . WriteHeader ( http . StatusNotFound )
w . Write ( [ ] byte ( "404" ) )
return
}
2020-02-23 18:00:08 +01:00
row = db . QueryRow ( "SELECT * FROM photo WHERE photo_id = ?" , photo_id )
photo , err := models . NewPhotoFromRow ( row )
if err != nil {
log . Printf ( "WARN: %s" , err )
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
}
2020-02-20 17:31:41 +01:00
user := auth . UserFromContext ( r . Context ( ) )
if user != nil {
2020-02-23 18:00:08 +01:00
row := db . QueryRow ( "SELECT owner_id FROM album WHERE album.album_id = ?" , photo . AlbumId )
2020-02-20 17:31:41 +01:00
var owner_id int
if err := row . Scan ( & owner_id ) ; err != nil {
2020-02-21 12:05:28 +01:00
log . Printf ( "WARN: %s" , err )
2020-02-20 17:31:41 +01:00
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
return
}
if owner_id != user . UserID {
w . WriteHeader ( http . StatusForbidden )
w . Write ( [ ] byte ( "invalid credentials" ) )
return
}
} else {
2020-06-14 17:58:50 +02:00
// Check if photo is authorized with a share token
2020-02-21 12:05:28 +01:00
token := r . URL . Query ( ) . Get ( "token" )
if token == "" {
w . WriteHeader ( http . StatusForbidden )
w . Write ( [ ] byte ( "unauthorized" ) )
return
}
row := db . QueryRow ( "SELECT * FROM share_token WHERE value = ?" , token )
shareToken , err := models . NewShareTokenFromRow ( row )
if err != nil {
log . Printf ( "WARN: %s" , err )
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
return
}
2020-06-14 17:58:50 +02:00
// Validate share token password, if set
if shareToken . Password != nil {
tokenPassword := r . Header . Get ( "TokenPassword" )
if err := bcrypt . CompareHashAndPassword ( [ ] byte ( * shareToken . Password ) , [ ] byte ( tokenPassword ) ) ; err != nil {
if err == bcrypt . ErrMismatchedHashAndPassword {
w . WriteHeader ( http . StatusForbidden )
w . Write ( [ ] byte ( "unauthorized" ) )
return
} else {
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
return
}
}
}
2020-02-23 18:00:08 +01:00
if shareToken . AlbumID != nil && photo . AlbumId != * shareToken . AlbumID {
2020-02-21 12:05:28 +01:00
// Check child albums
row := db . QueryRow ( `
WITH recursive child_albums AS (
SELECT * FROM album WHERE parent_album = ?
UNION ALL
SELECT child . * FROM album child JOIN child_albums parent ON parent . album_id = child . parent_album
)
SELECT * FROM child_albums WHERE album_id = ?
2020-02-23 18:00:08 +01:00
` , * shareToken . AlbumID , photo . AlbumId )
2020-02-21 12:05:28 +01:00
_ , err := models . NewAlbumFromRow ( row )
if err != nil {
if err == sql . ErrNoRows {
w . WriteHeader ( http . StatusForbidden )
w . Write ( [ ] byte ( "unauthorized" ) )
return
}
log . Printf ( "WARN: %s" , err )
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
return
}
}
if shareToken . PhotoID != nil && photo_id != * shareToken . PhotoID {
w . WriteHeader ( http . StatusForbidden )
w . Write ( [ ] byte ( "unauthorized" ) )
return
}
2020-02-20 17:31:41 +01:00
}
2020-02-23 18:00:08 +01:00
var cachedPath string
var file * os . File = nil
2020-02-09 14:21:53 +01:00
2020-02-14 13:31:44 +01:00
if purpose == models . PhotoThumbnail || purpose == models . PhotoHighRes {
2020-02-23 18:00:08 +01:00
cachedPath = path . Join ( scanner . PhotoCache ( ) , strconv . Itoa ( photo . AlbumId ) , strconv . Itoa ( photo_id ) , image_name )
2020-02-09 14:21:53 +01:00
}
2020-02-14 13:31:44 +01:00
if purpose == models . PhotoOriginal {
2020-02-23 18:00:08 +01:00
cachedPath = photo . Path
}
file , err = os . Open ( cachedPath )
if err != nil {
if os . IsNotExist ( err ) {
tx , err := db . Begin ( )
if err != nil {
log . Printf ( "ERROR: %s\n" , err )
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
return
}
2020-02-26 21:23:13 +01:00
err = scanner . ProcessPhoto ( tx , photo )
2020-02-23 18:00:08 +01:00
if err != nil {
log . Printf ( "ERROR: processing image not found in cache: %s\n" , err )
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
tx . Rollback ( )
return
}
file , err = os . Open ( cachedPath )
if err != nil {
log . Printf ( "ERROR: after reprocessing image not found in cache: %s\n" , err )
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
tx . Rollback ( )
return
}
tx . Commit ( )
2020-02-09 14:21:53 +01:00
}
}
2020-02-23 12:43:45 +01:00
w . Header ( ) . Set ( "Content-Type" , content_type )
2020-02-09 14:21:53 +01:00
if stats , err := file . Stat ( ) ; err == nil {
w . Header ( ) . Set ( "Content-Length" , fmt . Sprintf ( "%d" , stats . Size ( ) ) )
}
io . Copy ( w , file )
} )
}