mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 12:42:16 +01:00
refactoring: now detecting file type using filename from form data. Also, only one accepted content type
This commit is contained in:
BIN
.build/gotenberg
BIN
.build/gotenberg
Binary file not shown.
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.ci/coverage.txt
|
.ci/coverage.txt
|
||||||
|
.vscode
|
||||||
@@ -7,46 +7,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/converter"
|
"github.com/thecodingmachine/gotenberg/app/converter"
|
||||||
ghttp "github.com/thecodingmachine/gotenberg/app/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type key uint32
|
type key uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
contentTypeKey key = iota
|
converterKey key = iota
|
||||||
converterKey
|
|
||||||
resultFilePathKey
|
resultFilePathKey
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithContentType populates a request's context with the given content type
|
|
||||||
// and returns the updated request.
|
|
||||||
func WithContentType(r *http.Request, contentType ghttp.ContentType) *http.Request {
|
|
||||||
ctx := r.Context()
|
|
||||||
ctx = context.WithValue(ctx, contentTypeKey, contentType)
|
|
||||||
r = r.WithContext(ctx)
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type contentTypeNotFoundError struct{}
|
|
||||||
|
|
||||||
const contentTypeNotFoundErrorMessage = "The 'Content-Type' was not found in request context"
|
|
||||||
|
|
||||||
func (e *contentTypeNotFoundError) Error() string {
|
|
||||||
return contentTypeNotFoundErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContentType returns the content type if found in
|
|
||||||
// the request's context. Otherwise throws an error.
|
|
||||||
func GetContentType(r *http.Request) (ghttp.ContentType, error) {
|
|
||||||
ct, ok := r.Context().Value(contentTypeKey).(ghttp.ContentType)
|
|
||||||
if !ok {
|
|
||||||
return "", &contentTypeNotFoundError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ct, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithConverter populates a request's context with the given converter
|
// WithConverter populates a request's context with the given converter
|
||||||
// and returns the updated request.
|
// and returns the updated request.
|
||||||
func WithConverter(r *http.Request, converter *converter.Converter) *http.Request {
|
func WithConverter(r *http.Request, converter *converter.Converter) *http.Request {
|
||||||
|
|||||||
@@ -6,31 +6,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/converter"
|
"github.com/thecodingmachine/gotenberg/app/converter"
|
||||||
ghttp "github.com/thecodingmachine/gotenberg/app/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWithContentType(t *testing.T) {
|
|
||||||
req := WithContentType(httptest.NewRequest(http.MethodPost, "/", nil), ghttp.MultipartFormDataContentType)
|
|
||||||
if ct, _ := req.Context().Value(contentTypeKey).(ghttp.ContentType); ct != ghttp.MultipartFormDataContentType {
|
|
||||||
t.Errorf("Context returned a wrong content type: got %v want %v", ct, ghttp.MultipartFormDataContentType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetContentType(t *testing.T) {
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
||||||
|
|
||||||
// case 1: uses a request without a content type entry in its context.
|
|
||||||
if _, err := GetContentType(req); err == nil {
|
|
||||||
t.Error("Context should not have a content type entry!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a request with a content type entry in its context.
|
|
||||||
req = WithContentType(req, ghttp.MultipartFormDataContentType)
|
|
||||||
if _, err := GetContentType(req); err != nil {
|
|
||||||
t.Error("Context should have a content type entry!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithConverter(t *testing.T) {
|
func TestWithConverter(t *testing.T) {
|
||||||
req := WithConverter(httptest.NewRequest(http.MethodPost, "/", nil), &converter.Converter{})
|
req := WithConverter(httptest.NewRequest(http.MethodPost, "/", nil), &converter.Converter{})
|
||||||
if c, _ := req.Context().Value(converterKey).(*converter.Converter); c == nil {
|
if c, _ := req.Context().Value(converterKey).(*converter.Converter); c == nil {
|
||||||
@@ -53,14 +30,6 @@ func TestGetConverter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWithResultFilePath(t *testing.T) {
|
|
||||||
filePath := "file.pdf"
|
|
||||||
req := WithResultFilePath(httptest.NewRequest(http.MethodPost, "/", nil), filePath)
|
|
||||||
if path, _ := req.Context().Value(resultFilePathKey).(string); path != filePath {
|
|
||||||
t.Errorf("Context returned a wrong converter: got %s want %s", path, filePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetResultFilePath(t *testing.T) {
|
func TestGetResultFilePath(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
|
||||||
@@ -76,10 +45,11 @@ func TestGetResultFilePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContentTypeNotFoundError(t *testing.T) {
|
func TestWithResultFilePath(t *testing.T) {
|
||||||
err := &contentTypeNotFoundError{}
|
filePath := "file.pdf"
|
||||||
if err.Error() != contentTypeNotFoundErrorMessage {
|
req := WithResultFilePath(httptest.NewRequest(http.MethodPost, "/", nil), filePath)
|
||||||
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), contentTypeNotFoundErrorMessage)
|
if path, _ := req.Context().Value(resultFilePathKey).(string); path != filePath {
|
||||||
|
t.Errorf("Context returned a wrong converter: got %s want %s", path, filePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
gfile "github.com/thecodingmachine/gotenberg/app/converter/file"
|
gfile "github.com/thecodingmachine/gotenberg/app/converter/file"
|
||||||
"github.com/thecodingmachine/gotenberg/app/converter/process"
|
"github.com/thecodingmachine/gotenberg/app/converter/process"
|
||||||
ghttp "github.com/thecodingmachine/gotenberg/app/http"
|
|
||||||
|
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
)
|
)
|
||||||
@@ -21,7 +20,7 @@ type Converter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewConverter instantiates a converter by parsing a request.
|
// 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{
|
c := &Converter{
|
||||||
workingDir: fmt.Sprintf("./%s/", uuid.NewV4().String()),
|
workingDir: fmt.Sprintf("./%s/", uuid.NewV4().String()),
|
||||||
}
|
}
|
||||||
@@ -30,33 +29,23 @@ func NewConverter(r *http.Request, contentType ghttp.ContentType) (*Converter, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch contentType {
|
reader, err := r.MultipartReader()
|
||||||
case ghttp.MultipartFormDataContentType:
|
if err != nil {
|
||||||
reader, err := r.MultipartReader()
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
|
for {
|
||||||
|
part, err := reader.NextPart()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
fileName := part.FileName()
|
||||||
part, err := reader.NextPart()
|
if fileName == "" {
|
||||||
if err == io.EOF {
|
continue
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if part.FileName() == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := gfile.NewFile(c.workingDir, part)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.files = append(c.files, f)
|
|
||||||
}
|
}
|
||||||
break
|
|
||||||
default:
|
f, err := gfile.NewFile(c.workingDir, part, fileName)
|
||||||
f, err := gfile.NewFile(c.workingDir, r.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package converter
|
package converter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -12,23 +11,9 @@ import (
|
|||||||
|
|
||||||
"github.com/thecodingmachine/gotenberg/app/config"
|
"github.com/thecodingmachine/gotenberg/app/config"
|
||||||
"github.com/thecodingmachine/gotenberg/app/converter/process"
|
"github.com/thecodingmachine/gotenberg/app/converter/process"
|
||||||
ghttp "github.com/thecodingmachine/gotenberg/app/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeRequest(filesPaths ...string) *http.Request {
|
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()
|
r, w := io.Pipe()
|
||||||
mpw := multipart.NewWriter(w)
|
mpw := multipart.NewWriter(w)
|
||||||
|
|
||||||
@@ -56,27 +41,27 @@ func makeRequest(filesPaths ...string) *http.Request {
|
|||||||
func TestNewConverter(t *testing.T) {
|
func TestNewConverter(t *testing.T) {
|
||||||
// case 1: uses a request with a single file.
|
// case 1: uses a request with a single file.
|
||||||
path, _ := filepath.Abs("../../_tests/file.docx")
|
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!")
|
t.Error("Converter should have been instantiated!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 2: uses a request with wrong file type.
|
// case 2: uses a request with wrong file type.
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/gotenberg.yml")
|
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!")
|
t.Error("Converter should not have been instantiated!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 3: uses a request with two files.
|
// case 3: uses a request with two files.
|
||||||
oPath, _ := filepath.Abs("../../_tests/file.docx")
|
oPath, _ := filepath.Abs("../../_tests/file.docx")
|
||||||
path, _ = filepath.Abs("../../_tests/file.pdf")
|
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!")
|
t.Error("Converter should have been instantiated!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 4: uses a request with one Office file and one wrong file type.
|
// case 4: uses a request with one Office file and one wrong file type.
|
||||||
oPath, _ = filepath.Abs("../../_tests/file.docx")
|
oPath, _ = filepath.Abs("../../_tests/file.docx")
|
||||||
path, _ = filepath.Abs("../../_tests/configurations/gotenberg.yml")
|
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!")
|
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.
|
// case 1: uses a request with a single file.
|
||||||
path, _ = filepath.Abs("../../_tests/file.docx")
|
path, _ = filepath.Abs("../../_tests/file.docx")
|
||||||
c, _ := NewConverter(makeRequest(path), ghttp.OctetStreamContentType)
|
c, _ := NewConverter(makeRequest(path))
|
||||||
if _, err := c.Convert(); err != nil {
|
if _, err := c.Convert(); err != nil {
|
||||||
t.Error("Converter should have been able to convert an Office document to PDF!")
|
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.
|
// case 2: uses a request with two files.
|
||||||
oPath, _ := filepath.Abs("../../_tests/file.docx")
|
oPath, _ := filepath.Abs("../../_tests/file.docx")
|
||||||
path, _ = filepath.Abs("../../_tests/file.pdf")
|
path, _ = filepath.Abs("../../_tests/file.pdf")
|
||||||
c, _ = NewConverter(makeRequest(oPath, path), ghttp.MultipartFormDataContentType)
|
c, _ = NewConverter(makeRequest(oPath, path))
|
||||||
if _, err := c.Convert(); err != nil {
|
if _, err := c.Convert(); err != nil {
|
||||||
t.Error("Converter should have been able to convert two files to PDF and merge them!")
|
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)
|
appConfig, _ = config.NewAppConfig(path)
|
||||||
process.Load(appConfig.CommandsConfig)
|
process.Load(appConfig.CommandsConfig)
|
||||||
path, _ = filepath.Abs("../../_tests/file.docx")
|
path, _ = filepath.Abs("../../_tests/file.docx")
|
||||||
c, _ = NewConverter(makeRequest(path), ghttp.OctetStreamContentType)
|
c, _ = NewConverter(makeRequest(path))
|
||||||
if _, err := c.Convert(); err == nil {
|
if _, err := c.Convert(); err == nil {
|
||||||
t.Error("Converter should not have been able to convert an Office document to PDF!")
|
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)
|
process.Load(appConfig.CommandsConfig)
|
||||||
oPath, _ = filepath.Abs("../../_tests/file.docx")
|
oPath, _ = filepath.Abs("../../_tests/file.docx")
|
||||||
path, _ = filepath.Abs("../../_tests/file.pdf")
|
path, _ = filepath.Abs("../../_tests/file.pdf")
|
||||||
c, _ = NewConverter(makeRequest(oPath, path), ghttp.MultipartFormDataContentType)
|
c, _ = NewConverter(makeRequest(oPath, path))
|
||||||
if _, err := c.Convert(); err == nil {
|
if _, err := c.Convert(); err == nil {
|
||||||
t.Error("Converter should not have been able to merge PDF!")
|
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) {
|
func TestClear(t *testing.T) {
|
||||||
path, _ := filepath.Abs("../../_tests/file.docx")
|
path, _ := filepath.Abs("../../_tests/file.docx")
|
||||||
c, _ := NewConverter(makeRequest(path), ghttp.OctetStreamContentType)
|
c, _ := NewConverter(makeRequest(path))
|
||||||
if err := c.Clear(); err != nil {
|
if err := c.Clear(); err != nil {
|
||||||
t.Error("Converter should have been able to clear itself!")
|
t.Error("Converter should have been able to clear itself!")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
ghttp "github.com/thecodingmachine/gotenberg/app/http"
|
|
||||||
|
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
)
|
)
|
||||||
@@ -32,11 +31,42 @@ const (
|
|||||||
OfficeType
|
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.
|
// NewFile creates a file in the considered directory.
|
||||||
// Returns a *File instance or an error if something bad happened.
|
// 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{
|
f := &File{
|
||||||
Path: MakeFilePath(workingDir),
|
Path: MakeFilePath(workingDir, ext),
|
||||||
|
Type: t,
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Create(f.Path)
|
file, err := os.Create(f.Path)
|
||||||
@@ -54,103 +84,11 @@ func NewFile(workingDir string, r io.Reader) (*File, error) {
|
|||||||
// resets the read pointer.
|
// resets the read pointer.
|
||||||
file.Seek(0, 0)
|
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
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeFilePath is a simple helper which generates a random file name
|
// MakeFilePath is a simple helper which generates a random file name
|
||||||
// and associates it with the considered directory to make a path.
|
// and associates it with the considered directory to make a path.
|
||||||
func MakeFilePath(workingDir string) string {
|
func MakeFilePath(workingDir string, ext string) string {
|
||||||
return fmt.Sprintf("%s%s", workingDir, uuid.NewV4().String())
|
return fmt.Sprintf("%s%s%s", workingDir, uuid.NewV4().String(), ext)
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package file
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,56 +12,27 @@ func TestNewFile(t *testing.T) {
|
|||||||
workingDir := "test"
|
workingDir := "test"
|
||||||
os.Mkdir(workingDir, 0666)
|
os.Mkdir(workingDir, 0666)
|
||||||
|
|
||||||
// case 1: uses an empty reader.
|
// case 2: uses a wrong file name.
|
||||||
if _, err := NewFile(workingDir, new(bytes.Buffer)); err == nil {
|
if _, err := NewFile(workingDir, new(bytes.Buffer), "file.yml"); 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 {
|
|
||||||
t.Error("File should not have been instantiated!")
|
t.Error("File should not have been instantiated!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 3: uses a reader from a correct file type.
|
// case 3: uses a reader from a correct file type.
|
||||||
path, _ = filepath.Abs("../../../_tests/file.pdf")
|
filePath, _ := filepath.Abs("../../../_tests/file.pdf")
|
||||||
r, _ = os.Open(path)
|
r, _ := os.Open(filePath)
|
||||||
defer r.Close()
|
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!")
|
t.Error("File should have been instantiated!")
|
||||||
}
|
}
|
||||||
|
|
||||||
os.RemoveAll(workingDir)
|
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) {
|
func TestFileTypeNotFoundError(t *testing.T) {
|
||||||
err := &fileTypeNotFoundError{}
|
fileName := "file.wp"
|
||||||
if err.Error() != fileTypeNotFoundErrorMessage {
|
err := &fileTypeNotFoundError{fileName: fileName}
|
||||||
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), fileTypeNotFoundErrorMessage)
|
expected := fmt.Sprintf("File type was not found for '%s'", fileName)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileExtNotFoundError(t *testing.T) {
|
if err.Error() != expected {
|
||||||
err := &fileExtNotFoundError{}
|
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), expected)
|
||||||
if err.Error() != fileExtNotFoundErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), fileExtNotFoundErrorMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package process
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
@@ -37,7 +36,7 @@ func (e *impossibleConversionError) Error() string {
|
|||||||
func Unconv(workingDir string, file *gfile.File) (string, error) {
|
func Unconv(workingDir string, file *gfile.File) (string, error) {
|
||||||
cmdData := &conversionData{
|
cmdData := &conversionData{
|
||||||
FilePath: file.Path,
|
FilePath: file.Path,
|
||||||
ResultFilePath: fmt.Sprintf("%s%s", gfile.MakeFilePath(workingDir), gfile.PDFExt),
|
ResultFilePath: gfile.MakeFilePath(workingDir, ".pdf"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -81,7 +80,7 @@ type mergeData struct {
|
|||||||
func Merge(workingDir string, filesPaths []string) (string, error) {
|
func Merge(workingDir string, filesPaths []string) (string, error) {
|
||||||
cmdData := &mergeData{
|
cmdData := &mergeData{
|
||||||
FilesPaths: filesPaths,
|
FilesPaths: filesPaths,
|
||||||
ResultFilePath: fmt.Sprintf("%s%s", gfile.MakeFilePath(workingDir), gfile.PDFExt),
|
ResultFilePath: gfile.MakeFilePath(workingDir, ".pdf"),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdTemplate := commandsConfig.Merge.Template
|
cmdTemplate := commandsConfig.Merge.Template
|
||||||
@@ -127,7 +126,6 @@ func run(command string, timeout int) error {
|
|||||||
if err := cmd.Process.Kill(); err != nil {
|
if err := cmd.Process.Kill(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &commandTimeoutError{}
|
return &commandTimeoutError{}
|
||||||
case err := <-done:
|
case err := <-done:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package process
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -9,6 +10,18 @@ import (
|
|||||||
gfile "github.com/thecodingmachine/gotenberg/app/converter/file"
|
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) {
|
func TestLoad(t *testing.T) {
|
||||||
path, _ := filepath.Abs("../../../_tests/configurations/gotenberg.yml")
|
path, _ := filepath.Abs("../../../_tests/configurations/gotenberg.yml")
|
||||||
c, _ := config.NewAppConfig(path)
|
c, _ := config.NewAppConfig(path)
|
||||||
@@ -28,29 +41,17 @@ func TestUnconv(t *testing.T) {
|
|||||||
os.Mkdir(workingDir, 0666)
|
os.Mkdir(workingDir, 0666)
|
||||||
|
|
||||||
// case 1: uses an HTML file type.
|
// case 1: uses an HTML file type.
|
||||||
path, _ = filepath.Abs("../../../_tests/file.html")
|
if _, err := Unconv(workingDir, makeFile(workingDir, "file.html")); err != nil {
|
||||||
r, _ := os.Open(path)
|
|
||||||
defer r.Close()
|
|
||||||
f, _ := gfile.NewFile(workingDir, r)
|
|
||||||
if _, err := Unconv(workingDir, f); err != nil {
|
|
||||||
t.Error("HTML conversion to PDF should have worked!")
|
t.Error("HTML conversion to PDF should have worked!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 2: uses an Office file type.
|
// case 2: uses an Office file type.
|
||||||
path, _ = filepath.Abs("../../../_tests/file.docx")
|
if _, err := Unconv(workingDir, makeFile(workingDir, "file.docx")); err != nil {
|
||||||
r, _ = os.Open(path)
|
|
||||||
defer r.Close()
|
|
||||||
f, _ = gfile.NewFile(workingDir, r)
|
|
||||||
if _, err := Unconv(workingDir, f); err != nil {
|
|
||||||
t.Error("Office conversion to PDF should have worked!")
|
t.Error("Office conversion to PDF should have worked!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 3: uses a PDF file type.
|
// case 3: uses a PDF file type.
|
||||||
path, _ = filepath.Abs("../../../_tests/file.pdf")
|
if _, err := Unconv(workingDir, makeFile(workingDir, "file.pdf")); err == nil {
|
||||||
r, _ = os.Open(path)
|
|
||||||
defer r.Close()
|
|
||||||
f, _ = gfile.NewFile(workingDir, r)
|
|
||||||
if _, err := Unconv(workingDir, f); err == nil {
|
|
||||||
t.Error("PDF conversion to PDF should not have worked!")
|
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")
|
path, _ = filepath.Abs("../../../_tests/configurations/timeout-gotenberg.yml")
|
||||||
c, _ = config.NewAppConfig(path)
|
c, _ = config.NewAppConfig(path)
|
||||||
Load(c.CommandsConfig)
|
Load(c.CommandsConfig)
|
||||||
path, _ = filepath.Abs("../../../_tests/file.docx")
|
if _, err := Unconv(workingDir, makeFile(workingDir, "file.docx")); err == nil {
|
||||||
r, _ = os.Open(path)
|
|
||||||
defer r.Close()
|
|
||||||
f, _ = gfile.NewFile(workingDir, r)
|
|
||||||
if _, err := Unconv(workingDir, f); err == nil {
|
|
||||||
t.Error("Office conversion to PDF should have reached timeout!")
|
t.Error("Office conversion to PDF should have reached timeout!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,18 +42,15 @@ func enforceContentLengthHandler(next http.Handler) http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// enforceContentTypeHandler checks if the "Content-Type" entry
|
// enforceContentTypeHandler checks if the "Content-Type" entry
|
||||||
// from the request's header matches one of the allowed content types.
|
// from the request's header matches the allowed content type.
|
||||||
func enforceContentTypeHandler(next http.Handler) http.Handler {
|
func enforceContentTypeHandler(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ct, err := ghttp.FindAuthorizedContentType(r.Header)
|
if err := ghttp.CheckAuthorizedContentType(r.Header); err != nil {
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
|
http.Error(w, err.Error(), http.StatusUnsupportedMediaType)
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r = context.WithContentType(r, ct)
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -61,14 +58,7 @@ func enforceContentTypeHandler(next http.Handler) http.Handler {
|
|||||||
// convertHandler is in charge of converting the file(s) from the request to PDF.
|
// convertHandler is in charge of converting the file(s) from the request to PDF.
|
||||||
func convertHandler(next http.Handler) http.Handler {
|
func convertHandler(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ct, err := context.GetContentType(r)
|
c, err := converter.NewConverter(r)
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
logger.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := converter.NewConverter(r, ct)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -24,19 +23,6 @@ func fakeSuccessHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeRequest(filesPaths ...string) *http.Request {
|
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()
|
r, w := io.Pipe()
|
||||||
mpw := multipart.NewWriter(w)
|
mpw := multipart.NewWriter(w)
|
||||||
|
|
||||||
@@ -86,7 +72,7 @@ func TestEnforceContentTypeHandler(t *testing.T) {
|
|||||||
|
|
||||||
// case 1: sends a wrong content type.
|
// case 1: sends a wrong content type.
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
req.Header.Set("Content-Type", string(ghttp.PDFContentType))
|
req.Header.Set("Content-Type", "application/pdf")
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
h.ServeHTTP(rr, req)
|
h.ServeHTTP(rr, req)
|
||||||
if status := rr.Code; status != http.StatusUnsupportedMediaType {
|
if status := rr.Code; status != http.StatusUnsupportedMediaType {
|
||||||
@@ -95,7 +81,7 @@ func TestEnforceContentTypeHandler(t *testing.T) {
|
|||||||
|
|
||||||
// case 2: sends a good content type.
|
// case 2: sends a good content type.
|
||||||
req = httptest.NewRequest(http.MethodPost, "/", nil)
|
req = httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
req.Header.Set("Content-Type", string(ghttp.OctetStreamContentType))
|
req.Header.Set("Content-Type", string(ghttp.MultipartFormDataContentType))
|
||||||
rr = httptest.NewRecorder()
|
rr = httptest.NewRecorder()
|
||||||
h.ServeHTTP(rr, req)
|
h.ServeHTTP(rr, req)
|
||||||
if status := rr.Code; status != http.StatusOK {
|
if status := rr.Code; status != http.StatusOK {
|
||||||
@@ -106,7 +92,7 @@ func TestEnforceContentTypeHandler(t *testing.T) {
|
|||||||
func TestConvertHandler(t *testing.T) {
|
func TestConvertHandler(t *testing.T) {
|
||||||
h := alice.New(convertHandler).ThenFunc(fakeSuccessHandler)
|
h := alice.New(convertHandler).ThenFunc(fakeSuccessHandler)
|
||||||
|
|
||||||
// case 1: sends a request without a content type entry in its context.
|
// case 1: sends a request without body.
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
h.ServeHTTP(rr, req)
|
h.ServeHTTP(rr, req)
|
||||||
@@ -114,22 +100,14 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
t.Errorf("Handler returned a wrong status code: got %v want %v", status, http.StatusInternalServerError)
|
t.Errorf("Handler returned a wrong status code: got %v want %v", status, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 2: sends a request without body.
|
// case 2: sends a request with two files and using an unsuitable timeout for merge commande.
|
||||||
req = context.WithContentType(httptest.NewRequest(http.MethodPost, "/", nil), ghttp.OctetStreamContentType)
|
|
||||||
rr = httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rr, req)
|
|
||||||
if status := rr.Code; status != http.StatusInternalServerError {
|
|
||||||
t.Errorf("Handler returned a wrong status code: got %v want %v", status, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 3: sends a request with two files and using an unsuitable timeout for merge commande.
|
|
||||||
path, _ := filepath.Abs("../_tests/configurations/merge-timeout-gotenberg.yml")
|
path, _ := filepath.Abs("../_tests/configurations/merge-timeout-gotenberg.yml")
|
||||||
appConfig, _ := config.NewAppConfig(path)
|
appConfig, _ := config.NewAppConfig(path)
|
||||||
process.Load(appConfig.CommandsConfig)
|
process.Load(appConfig.CommandsConfig)
|
||||||
|
|
||||||
oPath, _ := filepath.Abs("../_tests/file.docx")
|
oPath, _ := filepath.Abs("../_tests/file.docx")
|
||||||
path, _ = filepath.Abs("../_tests/configurations/gotenberg.yml")
|
path, _ = filepath.Abs("../_tests/configurations/gotenberg.yml")
|
||||||
req = context.WithContentType(makeRequest(oPath, path), ghttp.MultipartFormDataContentType)
|
req = makeRequest(oPath, path)
|
||||||
rr = httptest.NewRecorder()
|
rr = httptest.NewRecorder()
|
||||||
h.ServeHTTP(rr, req)
|
h.ServeHTTP(rr, req)
|
||||||
if status := rr.Code; status != http.StatusInternalServerError {
|
if status := rr.Code; status != http.StatusInternalServerError {
|
||||||
@@ -143,7 +121,7 @@ func TestConvertHandler(t *testing.T) {
|
|||||||
|
|
||||||
oPath, _ = filepath.Abs("../_tests/file.docx")
|
oPath, _ = filepath.Abs("../_tests/file.docx")
|
||||||
path, _ = filepath.Abs("../_tests/file.pdf")
|
path, _ = filepath.Abs("../_tests/file.pdf")
|
||||||
req = context.WithContentType(makeRequest(oPath, path), ghttp.MultipartFormDataContentType)
|
req = makeRequest(oPath, path)
|
||||||
rr = httptest.NewRecorder()
|
rr = httptest.NewRecorder()
|
||||||
h.ServeHTTP(rr, req)
|
h.ServeHTTP(rr, req)
|
||||||
if status := rr.Code; status != http.StatusOK {
|
if status := rr.Code; status != http.StatusOK {
|
||||||
|
|||||||
@@ -4,71 +4,30 @@ package http
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContentType is a string which represents a content type.
|
// ContentType is a string which represents a content type.
|
||||||
type ContentType string
|
type ContentType string
|
||||||
|
|
||||||
const (
|
// MultipartFormDataContentType represents... the multipart form data content type.
|
||||||
// PDFContentType represents... the PDF content type.
|
const MultipartFormDataContentType ContentType = "multipart/form-data"
|
||||||
PDFContentType ContentType = "application/pdf"
|
|
||||||
// HTMLContentType represents... the HTML content type.
|
|
||||||
HTMLContentType ContentType = "text/html"
|
|
||||||
// OctetStreamContentType represents... the octet stream content type.
|
|
||||||
OctetStreamContentType ContentType = "application/octet-stream"
|
|
||||||
// ZipContentType represents... the zip content type.
|
|
||||||
ZipContentType ContentType = "application/zip"
|
|
||||||
// MultipartFormDataContentType represents... the multipart form data content type.
|
|
||||||
MultipartFormDataContentType ContentType = "multipart/form-data"
|
|
||||||
)
|
|
||||||
|
|
||||||
type notAuthorizedContentTypeError struct{}
|
type notAuthorizedContentTypeError struct{}
|
||||||
|
|
||||||
func (e *notAuthorizedContentTypeError) Error() string {
|
func (e *notAuthorizedContentTypeError) Error() string {
|
||||||
return fmt.Sprintf("Accepted values for 'Content-Type': %s, %s", OctetStreamContentType, MultipartFormDataContentType)
|
return fmt.Sprintf("Accepted value for 'Content-Type': %s", MultipartFormDataContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindAuthorizedContentType tries to return a content type according to a request header.
|
// CheckAuthorizedContentType check if the request header header has an authorized content type.
|
||||||
// If no authorized content type found, throws an error.
|
// If no authorized content type found, throws an error.
|
||||||
func FindAuthorizedContentType(h http.Header) (ContentType, error) {
|
func CheckAuthorizedContentType(h http.Header) error {
|
||||||
ct := findContentType(h.Get("Content-Type"), OctetStreamContentType, MultipartFormDataContentType)
|
ct := findContentType(h.Get("Content-Type"), MultipartFormDataContentType)
|
||||||
if ct == "" {
|
if ct == "" {
|
||||||
return "", ¬AuthorizedContentTypeError{}
|
return ¬AuthorizedContentTypeError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ct, nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
type notAuthorizedFileContentTypeError struct{}
|
|
||||||
|
|
||||||
const notAuthorizedFileContentTypeErrorMessage = "Unable to detect an authorized file content type"
|
|
||||||
|
|
||||||
func (e *notAuthorizedFileContentTypeError) Error() string {
|
|
||||||
return notAuthorizedFileContentTypeErrorMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
// SniffContentType tries to detect the content type of a file.
|
|
||||||
// If no authorized content type found, throws an error.
|
|
||||||
func SniffContentType(f *os.File) (ContentType, error) {
|
|
||||||
// only the first 512 bytes are used to sniff the content type.
|
|
||||||
buffer := make([]byte, 512)
|
|
||||||
n, err := f.Read(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// resets the read pointer.
|
|
||||||
f.Seek(0, 0)
|
|
||||||
|
|
||||||
// using n if size of buffer < 512 bytes.
|
|
||||||
ct := findContentType(http.DetectContentType(buffer[:n]), PDFContentType, HTMLContentType, OctetStreamContentType, ZipContentType)
|
|
||||||
if ct == "" {
|
|
||||||
return "", ¬AuthorizedFileContentTypeError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ct, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// findContentType parses a string representing a content type and tries to find
|
// findContentType parses a string representing a content type and tries to find
|
||||||
|
|||||||
@@ -4,55 +4,28 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindAuthorizedContentType(t *testing.T) {
|
func TestCheckAuthorizedContentType(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||||
|
|
||||||
// case 1: uses a request without a content type entry in its header.
|
// case 1: uses a request without a content type entry in its header.
|
||||||
if _, err := FindAuthorizedContentType(req.Header); err == nil {
|
if err := CheckAuthorizedContentType(req.Header); err == nil {
|
||||||
t.Error("It should not have been able to retrieve an authorized content type from header!")
|
t.Error("It should not have been able to retrieve an authorized content type from header!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 2: uses a request with a content type entry in its header.
|
// case 2: uses a request with a content type entry in its header.
|
||||||
req.Header.Set("Content-Type", string(MultipartFormDataContentType))
|
req.Header.Set("Content-Type", string(MultipartFormDataContentType))
|
||||||
if _, err := FindAuthorizedContentType(req.Header); err != nil {
|
if err := CheckAuthorizedContentType(req.Header); err != nil {
|
||||||
t.Error("It should have been able to retrieve an authorized content type from header!")
|
t.Error("It should have been able to retrieve an authorized content type from header!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSniffContentType(t *testing.T) {
|
|
||||||
// case 1: uses a file with a wrong content type.
|
|
||||||
path, _ := filepath.Abs("../../_tests/configurations/gotenberg.yml")
|
|
||||||
f, _ := os.Open(path)
|
|
||||||
defer f.Close()
|
|
||||||
if _, err := SniffContentType(f); err == nil {
|
|
||||||
t.Error("It should not have been able to retrieve an authorized content type from an YAML file!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: uses a file with a correct content type.
|
|
||||||
path, _ = filepath.Abs("../../_tests/file.pdf")
|
|
||||||
f, _ = os.Open(path)
|
|
||||||
defer f.Close()
|
|
||||||
if _, err := SniffContentType(f); err != nil {
|
|
||||||
t.Error("It should have been able to retrieve an authorized content type from a PDF file!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotAuthorizedContentTypeError(t *testing.T) {
|
func TestNotAuthorizedContentTypeError(t *testing.T) {
|
||||||
err := ¬AuthorizedContentTypeError{}
|
err := ¬AuthorizedContentTypeError{}
|
||||||
message := fmt.Sprintf("Accepted values for 'Content-Type': %s, %s", OctetStreamContentType, MultipartFormDataContentType)
|
message := fmt.Sprintf("Accepted value for 'Content-Type': %s", MultipartFormDataContentType)
|
||||||
if err.Error() != message {
|
if err.Error() != message {
|
||||||
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), message)
|
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotAuthorizedFileContentTypeError(t *testing.T) {
|
|
||||||
err := ¬AuthorizedFileContentTypeError{}
|
|
||||||
if err.Error() != notAuthorizedFileContentTypeErrorMessage {
|
|
||||||
t.Errorf("Error returned a wrong message: got %s want %s", err.Error(), notAuthorizedFileContentTypeErrorMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user