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 ) {
2020-07-10 14:26:19 +02:00
media_name := mux . Vars ( r ) [ "name" ]
2020-02-09 14:21:53 +01:00
2020-07-10 14:26:19 +02:00
row := db . QueryRow ( "SELECT media_url.purpose, media_url.content_type, media_url.media_id FROM media_url, media WHERE media_url.media_name = ? AND media_url.media_id = media.media_id" , media_name )
2020-02-09 14:21:53 +01:00
2020-07-10 12:58:11 +02:00
var purpose models . MediaPurpose
2020-02-09 14:21:53 +01:00
var content_type string
2020-07-10 14:26:19 +02:00
var media_id int
2020-02-09 14:21:53 +01:00
2020-07-10 14:26:19 +02:00
if err := row . Scan ( & purpose , & content_type , & media_id ) ; err != nil {
2020-02-09 14:21:53 +01:00
w . WriteHeader ( http . StatusNotFound )
w . Write ( [ ] byte ( "404" ) )
return
}
2020-07-10 14:26:19 +02:00
row = db . QueryRow ( "SELECT * FROM media WHERE media_id = ?" , media_id )
media , err := models . NewMediaFromRow ( row )
2020-02-23 18:00:08 +01:00
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-07-10 14:26:19 +02:00
row := db . QueryRow ( "SELECT owner_id FROM album WHERE album.album_id = ?" , media . 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-07-10 14:26:19 +02:00
if shareToken . AlbumID != nil && media . 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-07-10 14:26:19 +02:00
` , * shareToken . AlbumID , media . 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
}
}
2020-07-10 14:26:19 +02:00
if shareToken . MediaID != nil && media_id != * shareToken . MediaID {
2020-02-21 12:05:28 +01:00
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-07-11 14:21:10 +02:00
if purpose == models . PhotoThumbnail || purpose == models . PhotoHighRes || purpose == models . VideoThumbnail {
2020-07-10 14:26:19 +02:00
cachedPath = path . Join ( scanner . PhotoCache ( ) , strconv . Itoa ( media . AlbumId ) , strconv . Itoa ( media_id ) , media_name )
2020-07-11 15:57:58 +02:00
} else if purpose == models . MediaOriginal {
2020-07-10 14:26:19 +02:00
cachedPath = media . Path
2020-07-11 15:57:58 +02:00
} else {
log . Printf ( "ERROR: Can not handle media_purpose for photo: %s\n" , purpose )
w . WriteHeader ( http . StatusInternalServerError )
w . Write ( [ ] byte ( "internal server error" ) )
return
2020-02-23 18:00:08 +01:00
}
file , err = os . Open ( cachedPath )
2020-07-11 15:57:58 +02:00
defer file . Close ( )
2020-02-23 18:00:08 +01:00
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-07-10 14:26:19 +02:00
_ , err = scanner . ProcessMedia ( tx , media )
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 )
} )
}