refactoring: now detecting file type using filename from form data. Also, only one accepted content type

This commit is contained in:
Julien Neuhart
2018-04-08 21:49:02 +02:00
parent cdeef33ff7
commit bca02f6198
14 changed files with 119 additions and 400 deletions

View File

@@ -9,7 +9,6 @@ import (
gfile "github.com/thecodingmachine/gotenberg/app/converter/file"
"github.com/thecodingmachine/gotenberg/app/converter/process"
ghttp "github.com/thecodingmachine/gotenberg/app/http"
"github.com/satori/go.uuid"
)
@@ -21,7 +20,7 @@ type Converter struct {
}
// NewConverter instantiates a converter by parsing a request.
func NewConverter(r *http.Request, contentType ghttp.ContentType) (*Converter, error) {
func NewConverter(r *http.Request) (*Converter, error) {
c := &Converter{
workingDir: fmt.Sprintf("./%s/", uuid.NewV4().String()),
}
@@ -30,33 +29,23 @@ func NewConverter(r *http.Request, contentType ghttp.ContentType) (*Converter, e
return nil, err
}
switch contentType {
case ghttp.MultipartFormDataContentType:
reader, err := r.MultipartReader()
if err != nil {
return nil, err
reader, err := r.MultipartReader()
if err != nil {
return nil, err
}
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
if part.FileName() == "" {
continue
}
f, err := gfile.NewFile(c.workingDir, part)
if err != nil {
return nil, err
}
c.files = append(c.files, f)
fileName := part.FileName()
if fileName == "" {
continue
}
break
default:
f, err := gfile.NewFile(c.workingDir, r.Body)
f, err := gfile.NewFile(c.workingDir, part, fileName)
if err != nil {
return nil, err
}

View File

@@ -1,7 +1,6 @@
package converter
import (
"bytes"
"io"
"mime/multipart"
"net/http"
@@ -12,23 +11,9 @@ import (
"github.com/thecodingmachine/gotenberg/app/config"
"github.com/thecodingmachine/gotenberg/app/converter/process"
ghttp "github.com/thecodingmachine/gotenberg/app/http"
)
func makeRequest(filesPaths ...string) *http.Request {
if len(filesPaths) == 0 {
req := httptest.NewRequest(http.MethodPost, "/", new(bytes.Buffer))
req.Header.Set("Content-Type", string(ghttp.OctetStreamContentType))
return req
}
if len(filesPaths) == 1 {
file, _ := os.Open(filesPaths[0])
req := httptest.NewRequest(http.MethodPost, "/", file)
req.Header.Set("Content-Type", string(ghttp.OctetStreamContentType))
return req
}
r, w := io.Pipe()
mpw := multipart.NewWriter(w)
@@ -56,27 +41,27 @@ func makeRequest(filesPaths ...string) *http.Request {
func TestNewConverter(t *testing.T) {
// case 1: uses a request with a single file.
path, _ := filepath.Abs("../../_tests/file.docx")
if _, err := NewConverter(makeRequest(path), ghttp.OctetStreamContentType); err != nil {
if _, err := NewConverter(makeRequest(path)); err != nil {
t.Error("Converter should have been instantiated!")
}
// case 2: uses a request with wrong file type.
path, _ = filepath.Abs("../../_tests/configurations/gotenberg.yml")
if _, err := NewConverter(makeRequest(path), ghttp.OctetStreamContentType); err == nil {
if _, err := NewConverter(makeRequest(path)); err == nil {
t.Error("Converter should not have been instantiated!")
}
// case 3: uses a request with two files.
oPath, _ := filepath.Abs("../../_tests/file.docx")
path, _ = filepath.Abs("../../_tests/file.pdf")
if _, err := NewConverter(makeRequest(oPath, path), ghttp.MultipartFormDataContentType); err != nil {
if _, err := NewConverter(makeRequest(oPath, path)); err != nil {
t.Error("Converter should have been instantiated!")
}
// case 4: uses a request with one Office file and one wrong file type.
oPath, _ = filepath.Abs("../../_tests/file.docx")
path, _ = filepath.Abs("../../_tests/configurations/gotenberg.yml")
if _, err := NewConverter(makeRequest(oPath, path), ghttp.MultipartFormDataContentType); err == nil {
if _, err := NewConverter(makeRequest(oPath, path)); err == nil {
t.Error("Converter should not have been instantiated!")
}
}
@@ -88,7 +73,7 @@ func TestConvert(t *testing.T) {
// case 1: uses a request with a single file.
path, _ = filepath.Abs("../../_tests/file.docx")
c, _ := NewConverter(makeRequest(path), ghttp.OctetStreamContentType)
c, _ := NewConverter(makeRequest(path))
if _, err := c.Convert(); err != nil {
t.Error("Converter should have been able to convert an Office document to PDF!")
}
@@ -96,7 +81,7 @@ func TestConvert(t *testing.T) {
// case 2: uses a request with two files.
oPath, _ := filepath.Abs("../../_tests/file.docx")
path, _ = filepath.Abs("../../_tests/file.pdf")
c, _ = NewConverter(makeRequest(oPath, path), ghttp.MultipartFormDataContentType)
c, _ = NewConverter(makeRequest(oPath, path))
if _, err := c.Convert(); err != nil {
t.Error("Converter should have been able to convert two files to PDF and merge them!")
}
@@ -106,7 +91,7 @@ func TestConvert(t *testing.T) {
appConfig, _ = config.NewAppConfig(path)
process.Load(appConfig.CommandsConfig)
path, _ = filepath.Abs("../../_tests/file.docx")
c, _ = NewConverter(makeRequest(path), ghttp.OctetStreamContentType)
c, _ = NewConverter(makeRequest(path))
if _, err := c.Convert(); err == nil {
t.Error("Converter should not have been able to convert an Office document to PDF!")
}
@@ -117,7 +102,7 @@ func TestConvert(t *testing.T) {
process.Load(appConfig.CommandsConfig)
oPath, _ = filepath.Abs("../../_tests/file.docx")
path, _ = filepath.Abs("../../_tests/file.pdf")
c, _ = NewConverter(makeRequest(oPath, path), ghttp.MultipartFormDataContentType)
c, _ = NewConverter(makeRequest(oPath, path))
if _, err := c.Convert(); err == nil {
t.Error("Converter should not have been able to merge PDF!")
}
@@ -125,7 +110,7 @@ func TestConvert(t *testing.T) {
func TestClear(t *testing.T) {
path, _ := filepath.Abs("../../_tests/file.docx")
c, _ := NewConverter(makeRequest(path), ghttp.OctetStreamContentType)
c, _ := NewConverter(makeRequest(path))
if err := c.Clear(); err != nil {
t.Error("Converter should have been able to clear itself!")
}

View File

@@ -5,8 +5,7 @@ import (
"fmt"
"io"
"os"
ghttp "github.com/thecodingmachine/gotenberg/app/http"
"path/filepath"
"github.com/satori/go.uuid"
)
@@ -32,11 +31,42 @@ const (
OfficeType
)
// filesTypes associates a file extension with its file kind counterpart.
var filesTypes = map[string]Type{
".pdf": PDFType,
".html": HTMLType,
".doc": OfficeType,
".docx": OfficeType,
".odt": OfficeType,
".xls": OfficeType,
".xlsx": OfficeType,
".ods": OfficeType,
".ppt": OfficeType,
".pptx": OfficeType,
".odp": OfficeType,
}
type fileTypeNotFoundError struct {
fileName string
}
func (e *fileTypeNotFoundError) Error() string {
return fmt.Sprintf("File type was not found for '%s'", e.fileName)
}
// NewFile creates a file in the considered directory.
// Returns a *File instance or an error if something bad happened.
func NewFile(workingDir string, r io.Reader) (*File, error) {
func NewFile(workingDir string, r io.Reader, fileName string) (*File, error) {
ext := filepath.Ext(fileName)
t, ok := filesTypes[ext]
if !ok {
return nil, &fileTypeNotFoundError{fileName: fileName}
}
f := &File{
Path: MakeFilePath(workingDir),
Path: MakeFilePath(workingDir, ext),
Type: t,
}
file, err := os.Create(f.Path)
@@ -54,103 +84,11 @@ func NewFile(workingDir string, r io.Reader) (*File, error) {
// resets the read pointer.
file.Seek(0, 0)
t, err := findFileType(file)
if err != nil {
return nil, err
}
f.Type = t
f, err = reworkFilePath(workingDir, f)
if err != nil {
return nil, err
}
return f, nil
}
// MakeFilePath is a simple helper which generates a random file name
// and associates it with the considered directory to make a path.
func MakeFilePath(workingDir string) string {
return fmt.Sprintf("%s%s", workingDir, uuid.NewV4().String())
}
// filesTypes associates a content type with its file kind counterpart.
var filesTypes = map[ghttp.ContentType]Type{
ghttp.PDFContentType: PDFType,
ghttp.HTMLContentType: HTMLType,
ghttp.OctetStreamContentType: OfficeType,
ghttp.ZipContentType: OfficeType,
}
type fileTypeNotFoundError struct{}
const fileTypeNotFoundErrorMessage = "The file type was not found for the given 'Content-Type'"
func (e *fileTypeNotFoundError) Error() string {
return fileTypeNotFoundErrorMessage
}
// findFileType tries to detect what kind of file is the given file.
func findFileType(f *os.File) (Type, error) {
ct, err := ghttp.SniffContentType(f)
if err != nil {
return 999, err
}
t, ok := filesTypes[ct]
if !ok {
return 999, &fileTypeNotFoundError{}
}
return t, nil
}
// Ext represents a file extension.
type Ext string
const (
// PDFExt represents a... PDF extension.
PDFExt Ext = ".pdf"
// HTMLExt represents an... HTML extension.
HTMLExt Ext = ".html"
// OfficeExt is a empty string, as Office documents
// have a lot of different extensions (.docx, .doc and so on).
OfficeExt Ext = ""
)
// filesExtensions associates a kind of file with its extension.
var filesExtensions = map[Type]Ext{
PDFType: PDFExt,
HTMLType: HTMLExt,
OfficeType: OfficeExt,
}
type fileExtNotFoundError struct{}
const fileExtNotFoundErrorMessage = "The file extension was not found for the given file type"
func (e *fileExtNotFoundError) Error() string {
return fileExtNotFoundErrorMessage
}
// reworkFilePath renames a file in the considered directory and adds its extension.
func reworkFilePath(workingDir string, f *File) (*File, error) {
ext, ok := filesExtensions[f.Type]
if !ok {
return nil, &fileExtNotFoundError{}
}
if ext != OfficeExt {
newPath := fmt.Sprintf("%s%s", MakeFilePath(workingDir), ext)
err := os.Rename(f.Path, newPath)
if err != nil {
return nil, err
}
f.Path = newPath
}
return f, nil
func MakeFilePath(workingDir string, ext string) string {
return fmt.Sprintf("%s%s%s", workingDir, uuid.NewV4().String(), ext)
}

View File

@@ -2,6 +2,7 @@ package file
import (
"bytes"
"fmt"
"os"
"path/filepath"
"testing"
@@ -11,56 +12,27 @@ func TestNewFile(t *testing.T) {
workingDir := "test"
os.Mkdir(workingDir, 0666)
// case 1: uses an empty reader.
if _, err := NewFile(workingDir, new(bytes.Buffer)); err == nil {
t.Error("File should not have been instantiated!")
}
// case 2: uses a reader from a wrong file type.
path, _ := filepath.Abs("../../../_tests/configurations/gotenberg.yml")
r, _ := os.Open(path)
defer r.Close()
if _, err := NewFile(workingDir, r); err == nil {
// case 2: uses a wrong file name.
if _, err := NewFile(workingDir, new(bytes.Buffer), "file.yml"); err == nil {
t.Error("File should not have been instantiated!")
}
// case 3: uses a reader from a correct file type.
path, _ = filepath.Abs("../../../_tests/file.pdf")
r, _ = os.Open(path)
filePath, _ := filepath.Abs("../../../_tests/file.pdf")
r, _ := os.Open(filePath)
defer r.Close()
if _, err := NewFile(workingDir, r); err != nil {
if _, err := NewFile(workingDir, r, "file.pdf"); err != nil {
t.Error("File should have been instantiated!")
}
os.RemoveAll(workingDir)
}
func TestReworkFilePath(t *testing.T) {
workingDir := "test"
os.Mkdir(workingDir, 0666)
f := &File{
Path: MakeFilePath(workingDir),
Type: 999,
}
if _, err := reworkFilePath(workingDir, f); err == nil {
t.Error("It should not have been able to found the file extension!")
}
os.RemoveAll(workingDir)
}
func TestFileTypeNotFoundError(t *testing.T) {
err := &fileTypeNotFoundError{}
if err.Error() != fileTypeNotFoundErrorMessage {
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), fileTypeNotFoundErrorMessage)
}
}
fileName := "file.wp"
err := &fileTypeNotFoundError{fileName: fileName}
expected := fmt.Sprintf("File type was not found for '%s'", fileName)
func TestFileExtNotFoundError(t *testing.T) {
err := &fileExtNotFoundError{}
if err.Error() != fileExtNotFoundErrorMessage {
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), fileExtNotFoundErrorMessage)
if err.Error() != expected {
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), expected)
}
}

View File

@@ -3,7 +3,6 @@ package process
import (
"bytes"
"fmt"
"os/exec"
"text/template"
"time"
@@ -37,7 +36,7 @@ func (e *impossibleConversionError) Error() string {
func Unconv(workingDir string, file *gfile.File) (string, error) {
cmdData := &conversionData{
FilePath: file.Path,
ResultFilePath: fmt.Sprintf("%s%s", gfile.MakeFilePath(workingDir), gfile.PDFExt),
ResultFilePath: gfile.MakeFilePath(workingDir, ".pdf"),
}
var (
@@ -81,7 +80,7 @@ type mergeData struct {
func Merge(workingDir string, filesPaths []string) (string, error) {
cmdData := &mergeData{
FilesPaths: filesPaths,
ResultFilePath: fmt.Sprintf("%s%s", gfile.MakeFilePath(workingDir), gfile.PDFExt),
ResultFilePath: gfile.MakeFilePath(workingDir, ".pdf"),
}
cmdTemplate := commandsConfig.Merge.Template
@@ -127,7 +126,6 @@ func run(command string, timeout int) error {
if err := cmd.Process.Kill(); err != nil {
return err
}
return &commandTimeoutError{}
case err := <-done:
if err != nil {

View File

@@ -1,6 +1,7 @@
package process
import (
"fmt"
"os"
"path/filepath"
"testing"
@@ -9,6 +10,18 @@ import (
gfile "github.com/thecodingmachine/gotenberg/app/converter/file"
)
func makeFile(workingDir string, fileName string) *gfile.File {
filePath := fmt.Sprintf("%s%s", "../../../_tests/", fileName)
absPath, _ := filepath.Abs(filePath)
r, _ := os.Open(absPath)
defer r.Close()
f, _ := gfile.NewFile(workingDir, r, fileName)
return f
}
func TestLoad(t *testing.T) {
path, _ := filepath.Abs("../../../_tests/configurations/gotenberg.yml")
c, _ := config.NewAppConfig(path)
@@ -28,29 +41,17 @@ func TestUnconv(t *testing.T) {
os.Mkdir(workingDir, 0666)
// case 1: uses an HTML file type.
path, _ = filepath.Abs("../../../_tests/file.html")
r, _ := os.Open(path)
defer r.Close()
f, _ := gfile.NewFile(workingDir, r)
if _, err := Unconv(workingDir, f); err != nil {
if _, err := Unconv(workingDir, makeFile(workingDir, "file.html")); err != nil {
t.Error("HTML conversion to PDF should have worked!")
}
// case 2: uses an Office file type.
path, _ = filepath.Abs("../../../_tests/file.docx")
r, _ = os.Open(path)
defer r.Close()
f, _ = gfile.NewFile(workingDir, r)
if _, err := Unconv(workingDir, f); err != nil {
if _, err := Unconv(workingDir, makeFile(workingDir, "file.docx")); err != nil {
t.Error("Office conversion to PDF should have worked!")
}
// case 3: uses a PDF file type.
path, _ = filepath.Abs("../../../_tests/file.pdf")
r, _ = os.Open(path)
defer r.Close()
f, _ = gfile.NewFile(workingDir, r)
if _, err := Unconv(workingDir, f); err == nil {
if _, err := Unconv(workingDir, makeFile(workingDir, "file.pdf")); err == nil {
t.Error("PDF conversion to PDF should not have worked!")
}
@@ -58,11 +59,7 @@ func TestUnconv(t *testing.T) {
path, _ = filepath.Abs("../../../_tests/configurations/timeout-gotenberg.yml")
c, _ = config.NewAppConfig(path)
Load(c.CommandsConfig)
path, _ = filepath.Abs("../../../_tests/file.docx")
r, _ = os.Open(path)
defer r.Close()
f, _ = gfile.NewFile(workingDir, r)
if _, err := Unconv(workingDir, f); err == nil {
if _, err := Unconv(workingDir, makeFile(workingDir, "file.docx")); err == nil {
t.Error("Office conversion to PDF should have reached timeout!")
}