| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | // Copyright 2017 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of the go-ethereum library. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Lesser General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU Lesser General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Lesser General Public License | 
					
						
							|  |  |  | // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  | Show nicely (but simple) formatted HTML error pages (or respond with JSON | 
					
						
							|  |  |  | if the appropriate `Accept` header is set)) for the http package. | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | package http | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"html/template" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/log" | 
					
						
							| 
									
										
										
										
											2018-02-23 14:19:59 +01:00
										 |  |  | 	"github.com/ethereum/go-ethereum/metrics" | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | 	"github.com/ethereum/go-ethereum/swarm/api" | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //templateMap holds a mapping of an HTTP error code to a template | 
					
						
							|  |  |  | var templateMap map[int]*template.Template | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | var caseErrors []CaseError | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-23 14:19:59 +01:00
										 |  |  | //metrics variables | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil) | 
					
						
							|  |  |  | 	jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil) | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | //parameters needed for formatting the correct HTML page | 
					
						
							|  |  |  | type ErrorParams struct { | 
					
						
							|  |  |  | 	Msg       string | 
					
						
							|  |  |  | 	Code      int | 
					
						
							|  |  |  | 	Timestamp string | 
					
						
							|  |  |  | 	template  *template.Template | 
					
						
							|  |  |  | 	Details   template.HTML | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | //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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | //we init the error handling right on boot time, so lookup and http response is fast | 
					
						
							|  |  |  | func init() { | 
					
						
							|  |  |  | 	initErrHandling() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func initErrHandling() { | 
					
						
							|  |  |  | 	//pages are saved as strings - get these strings | 
					
						
							|  |  |  | 	genErrPage := GetGenericErrorPage() | 
					
						
							|  |  |  | 	notFoundPage := GetNotFoundErrorPage() | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | 	multipleChoicesPage := GetMultipleChoicesErrorPage() | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 	//map the codes to the available pages | 
					
						
							|  |  |  | 	tnames := map[int]string{ | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | 		0: genErrPage, //default | 
					
						
							|  |  |  | 		http.StatusBadRequest:          genErrPage, | 
					
						
							|  |  |  | 		http.StatusNotFound:            notFoundPage, | 
					
						
							|  |  |  | 		http.StatusMultipleChoices:     multipleChoicesPage, | 
					
						
							|  |  |  | 		http.StatusInternalServerError: genErrPage, | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	templateMap = make(map[int]*template.Template) | 
					
						
							|  |  |  | 	for code, tname := range tnames { | 
					
						
							|  |  |  | 		//assign formatted HTML to the code | 
					
						
							|  |  |  | 		templateMap[code] = template.Must(template.New(fmt.Sprintf("%d", code)).Parse(tname)) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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 "" | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | //ShowMultipeChoices is used when a user requests a resource in a manifest which results | 
					
						
							|  |  |  | //in ambiguous results. It returns a HTML page with clickable links of each of the entry | 
					
						
							|  |  |  | //in the manifest which fits the request URI ambiguity. | 
					
						
							| 
									
										
										
										
											2017-10-30 01:23:23 +01:00
										 |  |  | //For example, if the user requests bzz:/<hash>/read and that manifest contains entries | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | //"readme.md" and "readinglist.txt", a HTML page is returned with this two links. | 
					
						
							|  |  |  | //This only applies if the manifest has no default entry | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | func ShowMultipleChoices(w http.ResponseWriter, r *Request, list api.ManifestList) { | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | 	msg := "" | 
					
						
							|  |  |  | 	if list.Entries == nil { | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | 		ShowError(w, r, "Could not resolve", http.StatusInternalServerError) | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	//make links relative | 
					
						
							|  |  |  | 	//requestURI comes with the prefix of the ambiguous path, e.g. "read" for "readme.md" and "readinglist.txt" | 
					
						
							|  |  |  | 	//to get clickable links, need to remove the ambiguous path, i.e. "read" | 
					
						
							|  |  |  | 	idx := strings.LastIndex(r.RequestURI, "/") | 
					
						
							|  |  |  | 	if idx == -1 { | 
					
						
							|  |  |  | 		ShowError(w, r, "Internal Server Error", http.StatusInternalServerError) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	//remove ambiguous part | 
					
						
							|  |  |  | 	base := r.RequestURI[:idx+1] | 
					
						
							|  |  |  | 	for _, e := range list.Entries { | 
					
						
							|  |  |  | 		//create clickable link for each entry | 
					
						
							|  |  |  | 		msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>" | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | 	respond(w, &r.Request, &ErrorParams{ | 
					
						
							| 
									
										
										
										
											2017-10-06 08:45:54 -05:00
										 |  |  | 		Code:      http.StatusMultipleChoices, | 
					
						
							|  |  |  | 		Details:   template.HTML(msg), | 
					
						
							|  |  |  | 		Timestamp: time.Now().Format(time.RFC1123), | 
					
						
							|  |  |  | 		template:  getTemplate(http.StatusMultipleChoices), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | //ShowError is used to show an HTML error page to a client. | 
					
						
							|  |  |  | //If there is an `Accept` header of `application/json`, JSON will be returned instead | 
					
						
							|  |  |  | //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 | 
					
						
							|  |  |  | //(and return the correct HTTP status code) | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | func ShowError(w http.ResponseWriter, r *Request, msg string, code int) { | 
					
						
							|  |  |  | 	additionalMessage := ValidateCaseErrors(r) | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 	if code == http.StatusInternalServerError { | 
					
						
							|  |  |  | 		log.Error(msg) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | 	respond(w, &r.Request, &ErrorParams{ | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 		Code:      code, | 
					
						
							|  |  |  | 		Msg:       msg, | 
					
						
							| 
									
										
										
										
											2018-02-27 14:32:38 +01:00
										 |  |  | 		Details:   template.HTML(additionalMessage), | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 		Timestamp: time.Now().Format(time.RFC1123), | 
					
						
							|  |  |  | 		template:  getTemplate(code), | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //evaluate if client accepts html or json response | 
					
						
							|  |  |  | func respond(w http.ResponseWriter, r *http.Request, params *ErrorParams) { | 
					
						
							|  |  |  | 	w.WriteHeader(params.Code) | 
					
						
							|  |  |  | 	if r.Header.Get("Accept") == "application/json" { | 
					
						
							|  |  |  | 		respondJson(w, params) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		respondHtml(w, params) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //return a HTML page | 
					
						
							|  |  |  | func respondHtml(w http.ResponseWriter, params *ErrorParams) { | 
					
						
							| 
									
										
										
										
											2018-02-23 14:19:59 +01:00
										 |  |  | 	htmlCounter.Inc(1) | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 	err := params.template.Execute(w, params) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Error(err.Error()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //return JSON | 
					
						
							|  |  |  | func respondJson(w http.ResponseWriter, params *ErrorParams) { | 
					
						
							| 
									
										
										
										
											2018-02-23 14:19:59 +01:00
										 |  |  | 	jsonCounter.Inc(1) | 
					
						
							| 
									
										
										
										
											2017-09-08 13:29:09 -05:00
										 |  |  | 	w.Header().Set("Content-Type", "application/json") | 
					
						
							|  |  |  | 	json.NewEncoder(w).Encode(params) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //get the HTML template for a given code | 
					
						
							|  |  |  | func getTemplate(code int) *template.Template { | 
					
						
							|  |  |  | 	if val, tmpl := templateMap[code]; tmpl { | 
					
						
							|  |  |  | 		return val | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		return templateMap[0] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |