diff --git a/gemini/error.go b/gemini/error.go index 3c01815..5693efb 100644 --- a/gemini/error.go +++ b/gemini/error.go @@ -45,7 +45,7 @@ var ( errNotFoundRsp = buildResponseHeader(statusNotFound, "Not Found") errTemporaryFailureRsp = buildResponseHeader(statusTemporaryFailure, "Temporary Failure") errPermanentFailureRsp = buildResponseHeader(statusPermanentFailure, "Permanent Failure") - errInvalidRequestRsp = buildResponseHeader(statusBadRequest, "Invalid Request") + errBadRequestRsp = buildResponseHeader(statusBadRequest, "Bad Request") ) // generateErrorResponse takes an error code and generates an error response byte slice @@ -74,13 +74,13 @@ func generateErrorResponse(err error) ([]byte, bool) { case errors.Is(err, core.ErrRestrictedPath): return errRestrictedRsp, true case errors.Is(err, core.ErrInvalidRequest): - return errInvalidRequestRsp, true + return errBadRequestRsp, true case errors.Is(err, core.ErrParsingScheme): - return errInvalidRequestRsp, true + return errBadRequestRsp, true case errors.Is(err, core.ErrParsingHost): - return errInvalidRequestRsp, true + return errBadRequestRsp, true case errors.Is(err, core.ErrParsingURI): - return errInvalidRequestRsp, true + return errBadRequestRsp, true case errors.Is(err, core.ErrCGIStart): return errPermanentFailureRsp, true case errors.Is(err, core.ErrCGIExitCode): diff --git a/gemini/server.go b/gemini/server.go index 06c16bc..20ee129 100644 --- a/gemini/server.go +++ b/gemini/server.go @@ -24,7 +24,7 @@ func serve(client *core.Client) { // Ensure is a valid URL string if core.HasAsciiControlBytes(raw) { client.LogError("Invalid request: %s", raw) - handleError(client, core.ErrInvalidRequest.Extend("has ascii control bytes")) + handleError(client, core.ErrInvalidRequest.Extendf("%s has ascii control bytes", raw)) return } diff --git a/go.mod b/go.mod index a3a6bd5..5165ae6 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.15 require ( github.com/grufwub/go-bufpools v0.1.1 github.com/grufwub/go-config v0.1.0 - github.com/grufwub/go-errors v0.3.1 + github.com/grufwub/go-errors v0.3.2 github.com/grufwub/go-filecache v0.1.0 github.com/grufwub/go-logger v0.1.1 ) diff --git a/go.sum b/go.sum index 070490b..f982082 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/grufwub/go-bufpools v0.1.1 h1:TOUKNY+UaQ784EtvP+wKoXssYQQRX5zewtrZ7u4 github.com/grufwub/go-bufpools v0.1.1/go.mod h1:ITqLRtG+W1bZHGdkWewV7inb+GcWfq2Jcjqx4AZ7aBY= github.com/grufwub/go-config v0.1.0 h1:/UDEmprs4h4qEkgmQqthmtGZeJs8eB44qMVSqa+5sxU= github.com/grufwub/go-config v0.1.0/go.mod h1:0U5Y0EkNeL09YkY70fNZv4Kelfayp/VroEs2UzmUG04= -github.com/grufwub/go-errors v0.3.1 h1:Q0N5njkkFgAquX26MmNctonYD86htRly8yEZyWIhF+E= -github.com/grufwub/go-errors v0.3.1/go.mod h1:AXGtU2fWv8ejaUUT0+9wTOlWqcxYDo8wuYnhrYtoBKM= +github.com/grufwub/go-errors v0.3.2 h1:IB17KWLB+NNXCb+YUPMxPpvlNXGpxt0Xpad7wPxxRoo= +github.com/grufwub/go-errors v0.3.2/go.mod h1:AXGtU2fWv8ejaUUT0+9wTOlWqcxYDo8wuYnhrYtoBKM= github.com/grufwub/go-filecache v0.1.0 h1:OugzIHzLco8LLRnAlD7m6zSFQTILjltNO8Hhr/8vcCo= github.com/grufwub/go-filecache v0.1.0/go.mod h1:iAfqEfsC5YsyGD+f8JducuWeRqCDBVPi1+VmCaPL07Q= github.com/grufwub/go-logger v0.1.1 h1:KnD6NNyeq3cz6dZKW/Gr+Fz9dNvkLf8KvYZXKGM5cN0= diff --git a/gopher/error.go b/gopher/error.go index 837843f..7a62332 100644 --- a/gopher/error.go +++ b/gopher/error.go @@ -6,11 +6,37 @@ import ( "github.com/grufwub/go-errors" ) -// Gopher specific error codes +// Gopher specific errors var ( - errInvalidGophermap = errors.Error(invalidGophermapErrStr) - errSubgophermapIsDir = errors.Error(subgophermapIsDirErrStr) - errSubgophermapSize = errors.Error(subgophermapSizeErrStr) + errInvalidGophermap = errors.BaseError("invalid gophermap") + errSubgophermapIsDir = errors.BaseError("subgophermap path is dir") + errSubgophermapSize = errors.BaseError("subgophermap size too large") +) + +// Gopher response error text +const ( + statusBadRequest = "400 Bad Request" + statusUnauthorized = "401 Unauthorised" + statusForbidden = "403 Forbidden" + statusNotFound = "404 Not Found" + statusRequestTimeout = "408 Request Time-out" + statusGone = "410 Gone" + statusInternalServerError = "500 Internal Server Error" + statusNotImplemented = "501 Not Implemented" + statusServiceUnavailable = "503 Service Unavailable" +) + +// Gopher error responses +var ( + errBadRequestRsp = buildErrorLine(statusBadRequest) + errUnauthorizedRsp = buildErrorLine(statusUnauthorized) + errForbiddenRsp = buildErrorLine(statusForbidden) + errNotFoundRsp = buildErrorLine(statusNotFound) + errRequestTimeoutRsp = buildErrorLine(statusRequestTimeout) + errGoneRsp = buildErrorLine(statusGone) + errInternalServerErrorRsp = buildErrorLine(statusInternalServerError) + errNotImplementedRsp = buildErrorLine(statusNotImplemented) + errServiceUnavailableRsp = buildErrorLine(statusServiceUnavailable) ) // generateErrorResponse takes an error code and generates an error response byte slice @@ -19,42 +45,39 @@ func generateErrorResponse(err error) ([]byte, bool) { case errors.Is(err, core.ErrConnWrite): return nil, false // no point responding if we couldn't write case errors.Is(err, core.ErrConnRead): - return buildErrorLine(errorResponse503), true + return errServiceUnavailableRsp, true case errors.Is(err, core.ErrConnClose): return nil, false // no point responding if we couldn't close case errors.Is(err, core.ErrMutexUpgrade): - return buildErrorLine(errorResponse500), true + return errServiceUnavailableRsp, true case errors.Is(err, core.ErrMutexDowngrade): - return buildErrorLine(errorResponse500), true + return errServiceUnavailableRsp, true case errors.Is(err, core.ErrFileOpen): - return buildErrorLine(errorResponse404), true + return errNotFoundRsp, true case errors.Is(err, core.ErrFileStat): - return buildErrorLine(errorResponse500), true + return errNotFoundRsp, true case errors.Is(err, core.ErrFileRead): - return buildErrorLine(errorResponse500), true + return errNotFoundRsp, true case errors.Is(err, core.ErrFileType): - return buildErrorLine(errorResponse404), true + return errNotFoundRsp, true case errors.Is(err, core.ErrDirectoryRead): - return buildErrorLine(errorResponse500), true + return errNotFoundRsp, true case errors.Is(err, core.ErrRestrictedPath): - return buildErrorLine(errorResponse403), true - - // All forms of invalid request + return errForbiddenRsp, true case errors.Is(err, core.ErrInvalidRequest): - return buildErrorLine(errorResponse400), true + return errBadRequestRsp, true case errors.Is(err, core.ErrParsingScheme): - return buildErrorLine(errorResponse400), true - + return errBadRequestRsp, true case errors.Is(err, core.ErrCGIStart): - return buildErrorLine(errorResponse500), true + return errInternalServerErrorRsp, true case errors.Is(err, core.ErrCGIExitCode): - return buildErrorLine(errorResponse500), true + return errServiceUnavailableRsp, true case errors.Is(err, errInvalidGophermap): - return buildErrorLine(errorResponse500), true + return errInternalServerErrorRsp, true case errors.Is(err, errSubgophermapIsDir): - return buildErrorLine(errorResponse500), true + return errInternalServerErrorRsp, true case errors.Is(err, errSubgophermapSize): - return buildErrorLine(errorResponse500), true + return errInternalServerErrorRsp, true default: return nil, false } diff --git a/gopher/format.go b/gopher/format.go index c39e667..46b7ead 100644 --- a/gopher/format.go +++ b/gopher/format.go @@ -57,8 +57,8 @@ func buildInfoLine(line string) []byte { } // buildErrorLine builds a gopher error line string -func buildErrorLine(selector string) []byte { - return []byte(string(typeError) + selector + "\r\n" + ".\r\n") +func buildErrorLine(text string) []byte { + return []byte(string(typeError) + text + "\r\n" + ".\r\n") } // appendFileListing formats and appends a new file entry as part of a directory listing diff --git a/gopher/gophermap.go b/gopher/gophermap.go index 8846914..22679cc 100644 --- a/gopher/gophermap.go +++ b/gopher/gophermap.go @@ -3,7 +3,6 @@ package gopher import ( "gophi/core" "os" - "strconv" "github.com/grufwub/go-errors" ) @@ -46,7 +45,7 @@ func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, error) { titleAlready = true return true } - returnErr = errInvalidGophermap + returnErr = errInvalidGophermap.Extendf("%s multiple title declarations", p.Absolute()) return false case typeComment: @@ -69,7 +68,7 @@ func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, error) { // equal to current gophermap (recurse!!!) we return error request := core.NewRequest(core.BuildPath(path), query) if request.Path().Relative() == "" || request.Path().Relative() == p.Relative() { - returnErr = errInvalidGophermap + returnErr = errInvalidGophermap.Extendf("%s invalid subgophermap '%s'", p.Absolute(), request.Path().Absolute()) return false } @@ -83,10 +82,10 @@ func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, error) { // Get stat stat, err := subFile.Stat() if err != nil { - returnErr = errors.WrapError(core.ErrFileStat, err) + returnErr = errors.With(err).WrapWithin(core.ErrFileStat) return false } else if stat.IsDir() { - returnErr = errors.Wrap(request.Path().Absolute(), errSubgophermapIsDir) + returnErr = errSubgophermapIsDir.Extend(request.Path().Absolute()) return false } @@ -98,7 +97,7 @@ func readGophermap(file *os.File, p *core.Path) ([]gophermapSection, error) { // Error out if file too big if stat.Size() > subgophermapSizeMax { - returnErr = errors.Wrap(strconv.FormatInt(stat.Size(), 10), errSubgophermapSize) + returnErr = errSubgophermapSize.Extendf("%s %.2fMB", request.Path().Absolute(), stat.Size()/1000.0) return false } diff --git a/gopher/server.go b/gopher/server.go index b62a4b3..799cbc1 100644 --- a/gopher/server.go +++ b/gopher/server.go @@ -18,7 +18,7 @@ func serve(client *core.Client) { // Receive line from client received, err := client.Conn().ReadLine() if err != nil { - client.LogError(clientReadFailStr) + client.LogError("Conn read fail") handleError(client, err) return } @@ -30,15 +30,15 @@ func serve(client *core.Client) { // Ensure we've received a valid URL string if core.HasAsciiControlBytes(raw) { - client.LogError(invalidRequestStr, raw) - handleError(client, core.ErrInvalidRequest) + client.LogError("Invalid request: %s", raw) + handleError(client, core.ErrInvalidRequest.Extendf("%s has ascii control bytes", raw)) return } // Parse the encoded URI into path and query components path, query, err := core.ParseEncodedURI(raw) if err != nil { - client.LogError(invalidRequestStr, raw) + client.LogError("Invalid request: %s", raw) handleError(client, err) return } @@ -47,7 +47,7 @@ func serve(client *core.Client) { if strings.HasPrefix(path, "/URL:") { raw = raw[5:] client.Conn().Write(generateHTMLRedirect(raw)) - client.LogInfo(gopherRedirectStr, raw) + client.LogInfo("Redirect to: %s", raw) return } @@ -61,9 +61,9 @@ func serve(client *core.Client) { // Final error handling if err != nil { handleError(client, err) - client.LogError(clientServeFailStr, request.String()) + client.LogError("Failed to serve: %s", request.String()) } else { - client.LogInfo(clientServedStr, request.String()) + client.LogInfo("Served: %s", request.String()) } } @@ -88,13 +88,10 @@ func handleDirectory(client *core.Client, file *os.File, p *core.Path) error { // Slice to write dirContents := make([]byte, 0) - // Escape the previous dir - dirSel := core.EscapePath(p.SelectorDir()) - // Add directory heading, empty line and a back line dirContents = append(dirContents, buildLine(typeInfo, "[ "+core.Hostname+p.Selector()+" ]", "TITLE", nullHost, nullPort)...) dirContents = append(dirContents, buildInfoLine("")...) - dirContents = append(dirContents, buildLine(typeDirectory, "..", dirSel, core.Hostname, core.Port)...) + dirContents = append(dirContents, buildLine(typeDirectory, "..", p.Selector(), core.Hostname, core.Port)...) // Scan directory and build lines err = core.ScanDirectory( diff --git a/gopher/string_constants.go b/gopher/string_constants.go deleted file mode 100644 index e998ee9..0000000 --- a/gopher/string_constants.go +++ /dev/null @@ -1,30 +0,0 @@ -package gopher - -// Client error response strings -const ( - errorResponse400 = "400 Bad Request" - errorResponse401 = "401 Unauthorised" - errorResponse403 = "403 Forbidden" - errorResponse404 = "404 Not Found" - errorResponse408 = "408 Request Time-out" - errorResponse410 = "410 Gone" - errorResponse500 = "500 Internal Server Error" - errorResponse501 = "501 Not Implemented" - errorResponse503 = "503 Service Unavailable" -) - -// Gopher specific error string constants -const ( - invalidGophermapErrStr = "Invalid gophermap" - subgophermapIsDirErrStr = "Subgophermap path is dir" - subgophermapSizeErrStr = "Subgophermap size too large" -) - -// Log string constants -const ( - clientReadFailStr = "Failed to read" - gopherRedirectStr = "Redirecting to: %s" - invalidRequestStr = "Invalid request: %s" - clientServeFailStr = "Failed to serve: %s" - clientServedStr = "Served: %s" -)