swarm: give correct error on 0x hash prefix (#16195)

- added a case error struct that contains information about certain error cases
in which we would like to output more information to the client
- added a validation method that iterates and adds the information that is
stored in the error cases
This commit is contained in:
Elad Nachmias 2018-02-27 14:32:38 +01:00 committed by Péter Szilágyi
parent 18bb3da55e
commit b574b57766
5 changed files with 98 additions and 21 deletions

3
.gitignore vendored
View File

@ -34,6 +34,9 @@ profile.cov
# IdeaIDE # IdeaIDE
.idea .idea
# VS Code
.vscode
# dashboard # dashboard
/dashboard/assets/flow-typed /dashboard/assets/flow-typed
/dashboard/assets/node_modules /dashboard/assets/node_modules

View File

@ -35,6 +35,7 @@ import (
//templateMap holds a mapping of an HTTP error code to a template //templateMap holds a mapping of an HTTP error code to a template
var templateMap map[int]*template.Template var templateMap map[int]*template.Template
var caseErrors []CaseError
//metrics variables //metrics variables
var ( var (
@ -51,6 +52,13 @@ type ErrorParams struct {
Details template.HTML Details template.HTML
} }
//a custom error case struct that would be used to store validators and
//additional error info to display with client responses.
type CaseError struct {
Validator func(*Request) bool
Msg func(*Request) string
}
//we init the error handling right on boot time, so lookup and http response is fast //we init the error handling right on boot time, so lookup and http response is fast
func init() { func init() {
initErrHandling() initErrHandling()
@ -74,6 +82,29 @@ func initErrHandling() {
//assign formatted HTML to the code //assign formatted HTML to the code
templateMap[code] = template.Must(template.New(fmt.Sprintf("%d", code)).Parse(tname)) templateMap[code] = template.Must(template.New(fmt.Sprintf("%d", code)).Parse(tname))
} }
caseErrors = []CaseError{
{
Validator: func(r *Request) bool { return r.uri != nil && r.uri.Addr != "" && strings.HasPrefix(r.uri.Addr, "0x") },
Msg: func(r *Request) string {
uriCopy := r.uri
uriCopy.Addr = strings.TrimPrefix(uriCopy.Addr, "0x")
return fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.<br/>
Please click <a href='%[1]s'>here</a> if your browser does not redirect you.<script>setTimeout("location.href='%[1]s';",5000);</script>`, "/"+uriCopy.String())
},
}}
}
//ValidateCaseErrors is a method that process the request object through certain validators
//that assert if certain conditions are met for further information to log as an error
func ValidateCaseErrors(r *Request) string {
for _, err := range caseErrors {
if err.Validator(r) {
return err.Msg(r)
}
}
return ""
} }
//ShowMultipeChoices is used when a user requests a resource in a manifest which results //ShowMultipeChoices is used when a user requests a resource in a manifest which results
@ -82,10 +113,10 @@ func initErrHandling() {
//For example, if the user requests bzz:/<hash>/read and that manifest contains entries //For example, if the user requests bzz:/<hash>/read and that manifest contains entries
//"readme.md" and "readinglist.txt", a HTML page is returned with this two links. //"readme.md" and "readinglist.txt", a HTML page is returned with this two links.
//This only applies if the manifest has no default entry //This only applies if the manifest has no default entry
func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.ManifestList) { func ShowMultipleChoices(w http.ResponseWriter, r *Request, list api.ManifestList) {
msg := "" msg := ""
if list.Entries == nil { if list.Entries == nil {
ShowError(w, r, "Internal Server Error", http.StatusInternalServerError) ShowError(w, r, "Could not resolve", http.StatusInternalServerError)
return return
} }
//make links relative //make links relative
@ -102,7 +133,7 @@ func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.Manife
//create clickable link for each entry //create clickable link for each entry
msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>" msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>"
} }
respond(w, r, &ErrorParams{ respond(w, &r.Request, &ErrorParams{
Code: http.StatusMultipleChoices, Code: http.StatusMultipleChoices,
Details: template.HTML(msg), Details: template.HTML(msg),
Timestamp: time.Now().Format(time.RFC1123), Timestamp: time.Now().Format(time.RFC1123),
@ -115,13 +146,15 @@ func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.Manife
//The function just takes a string message which will be displayed in the error page. //The function just takes a string message which will be displayed in the error page.
//The code is used to evaluate which template will be displayed //The code is used to evaluate which template will be displayed
//(and return the correct HTTP status code) //(and return the correct HTTP status code)
func ShowError(w http.ResponseWriter, r *http.Request, msg string, code int) { func ShowError(w http.ResponseWriter, r *Request, msg string, code int) {
additionalMessage := ValidateCaseErrors(r)
if code == http.StatusInternalServerError { if code == http.StatusInternalServerError {
log.Error(msg) log.Error(msg)
} }
respond(w, r, &ErrorParams{ respond(w, &r.Request, &ErrorParams{
Code: code, Code: code,
Msg: msg, Msg: msg,
Details: template.HTML(additionalMessage),
Timestamp: time.Now().Format(time.RFC1123), Timestamp: time.Now().Format(time.RFC1123),
template: getTemplate(code), template: getTemplate(code),
}) })

View File

@ -168,6 +168,11 @@ func GetGenericErrorPage() string {
{{.Msg}} {{.Msg}}
</td> </td>
</tr> </tr>
<tr>
<td class="value">
{{.Details}}
</td>
</tr>
<tr> <tr>
<td class="key"> <td class="key">
@ -342,6 +347,12 @@ func GetNotFoundErrorPage() string {
{{.Msg}} {{.Msg}}
</td> </td>
</tr> </tr>
<tr>
<td class="value">
{{.Details}}
</td>
</tr>
<tr> <tr>
<td class="key"> <td class="key">

View File

@ -18,12 +18,13 @@ package http_test
import ( import (
"encoding/json" "encoding/json"
"golang.org/x/net/html"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
"golang.org/x/net/html"
"github.com/ethereum/go-ethereum/swarm/testutil" "github.com/ethereum/go-ethereum/swarm/testutil"
) )
@ -96,8 +97,37 @@ func Test500Page(t *testing.T) {
defer resp.Body.Close() defer resp.Body.Close()
respbody, err = ioutil.ReadAll(resp.Body) respbody, err = ioutil.ReadAll(resp.Body)
if resp.StatusCode != 500 || !strings.Contains(string(respbody), "500") { if resp.StatusCode != 404 {
t.Fatalf("Invalid Status Code received, expected 500, got %d", resp.StatusCode) t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode)
}
_, err = html.Parse(strings.NewReader(string(respbody)))
if err != nil {
t.Fatalf("HTML validation failed for error page returned!")
}
}
func Test500PageWith0xHashPrefix(t *testing.T) {
srv := testutil.NewTestSwarmServer(t)
defer srv.Close()
var resp *http.Response
var respbody []byte
url := srv.URL + "/bzz:/0xthisShouldFailWith500CodeAndAHelpfulMessage"
resp, err := http.Get(url)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
respbody, err = ioutil.ReadAll(resp.Body)
if resp.StatusCode != 404 {
t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode)
}
if !strings.Contains(string(respbody), "The requested hash seems to be prefixed with") {
t.Fatalf("Did not receive the expected error message")
} }
_, err = html.Parse(strings.NewReader(string(respbody))) _, err = html.Parse(strings.NewReader(string(respbody)))
@ -127,8 +157,8 @@ func TestJsonResponse(t *testing.T) {
defer resp.Body.Close() defer resp.Body.Close()
respbody, err = ioutil.ReadAll(resp.Body) respbody, err = ioutil.ReadAll(resp.Body)
if resp.StatusCode != 500 { if resp.StatusCode != 404 {
t.Fatalf("Invalid Status Code received, expected 500, got %d", resp.StatusCode) t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode)
} }
if !isJSON(string(respbody)) { if !isJSON(string(respbody)) {

View File

@ -336,7 +336,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) {
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
getFail.Inc(1) getFail.Inc(1)
s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) s.NotFound(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
return return
} }
@ -421,7 +421,7 @@ func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) {
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
getFilesFail.Inc(1) getFilesFail.Inc(1)
s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) s.NotFound(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
return return
} }
@ -494,7 +494,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
getListFail.Inc(1) getListFail.Inc(1)
s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) s.NotFound(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
return return
} }
@ -598,7 +598,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
key, err := s.api.Resolve(r.uri) key, err := s.api.Resolve(r.uri)
if err != nil { if err != nil {
getFileFail.Inc(1) getFileFail.Inc(1)
s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) s.NotFound(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err))
return return
} }
@ -628,7 +628,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
s.logDebug(fmt.Sprintf("Multiple choices! --> %v", list)) s.logDebug(fmt.Sprintf("Multiple choices! --> %v", list))
//show a nice page links to available entries //show a nice page links to available entries
ShowMultipleChoices(w, &r.Request, list) ShowMultipleChoices(w, r, list)
return return
} }
@ -693,7 +693,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// strictly a traditional PUT request which replaces content // strictly a traditional PUT request which replaces content
// at a URI, and POST is more ubiquitous) // at a URI, and POST is more ubiquitous)
if uri.Raw() || uri.DeprecatedRaw() { if uri.Raw() || uri.DeprecatedRaw() {
ShowError(w, r, fmt.Sprintf("No PUT to %s allowed.", uri), http.StatusBadRequest) ShowError(w, req, fmt.Sprintf("No PUT to %s allowed.", uri), http.StatusBadRequest)
return return
} else { } else {
s.HandlePostFiles(w, req) s.HandlePostFiles(w, req)
@ -701,7 +701,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case "DELETE": case "DELETE":
if uri.Raw() || uri.DeprecatedRaw() { if uri.Raw() || uri.DeprecatedRaw() {
ShowError(w, r, fmt.Sprintf("No DELETE to %s allowed.", uri), http.StatusBadRequest) ShowError(w, req, fmt.Sprintf("No DELETE to %s allowed.", uri), http.StatusBadRequest)
return return
} }
s.HandleDelete(w, req) s.HandleDelete(w, req)
@ -725,7 +725,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.HandleGetFile(w, req) s.HandleGetFile(w, req)
default: default:
ShowError(w, r, fmt.Sprintf("Method "+r.Method+" is not supported.", uri), http.StatusMethodNotAllowed) ShowError(w, req, fmt.Sprintf("Method "+r.Method+" is not supported.", uri), http.StatusMethodNotAllowed)
} }
} }
@ -757,13 +757,13 @@ func (s *Server) logError(format string, v ...interface{}) {
} }
func (s *Server) BadRequest(w http.ResponseWriter, r *Request, reason string) { func (s *Server) BadRequest(w http.ResponseWriter, r *Request, reason string) {
ShowError(w, &r.Request, fmt.Sprintf("Bad request %s %s: %s", r.Method, r.uri, reason), http.StatusBadRequest) ShowError(w, r, fmt.Sprintf("Bad request %s %s: %s", r.Request.Method, r.uri, reason), http.StatusBadRequest)
} }
func (s *Server) Error(w http.ResponseWriter, r *Request, err error) { func (s *Server) Error(w http.ResponseWriter, r *Request, err error) {
ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, err), http.StatusInternalServerError) ShowError(w, r, fmt.Sprintf("Error serving %s %s: %s", r.Request.Method, r.uri, err), http.StatusInternalServerError)
} }
func (s *Server) NotFound(w http.ResponseWriter, r *Request, err error) { func (s *Server) NotFound(w http.ResponseWriter, r *Request, err error) {
ShowError(w, &r.Request, fmt.Sprintf("NOT FOUND error serving %s %s: %s", r.Method, r.uri, err), http.StatusNotFound) ShowError(w, r, fmt.Sprintf("NOT FOUND error serving %s %s: %s", r.Request.Method, r.uri, err), http.StatusNotFound)
} }