summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHorus32015-05-11 23:47:47 +0200
committerHorus32015-05-11 23:47:47 +0200
commit5986015358e5b7cf523122bbdb6831dccf7ca306 (patch)
tree6b0f5b5b8b80bef7f6a9f286298e8d06080cad84
parent9c757388f1974b32a2f88cf36b709a160491baf8 (diff)
downloaduhttpd-5986015358e5b7cf523122bbdb6831dccf7ca306.tar.gz
Some fixes, shows 404 page on directory not found.
-rw-r--r--main.go29
-rw-r--r--recorder.go72
-rw-r--r--template.go166
3 files changed, 168 insertions, 99 deletions
diff --git a/main.go b/main.go
index 9ec7157..452ed35 100644
--- a/main.go
+++ b/main.go
@@ -10,24 +10,20 @@ import (
"strings"
)
-func accessLog(h http.Handler) http.Handler {
+func accessLog(h http.Handler, quiet bool) http.Handler {
t := &TemplateHandler{handler: h}
- //t := &TemplateHandler{}
fn := func(w http.ResponseWriter, r *http.Request) {
- log.Println(r.Method, r.URL.Path, r.RemoteAddr)
+ if !quiet {
+ log.Println(r.Method, r.URL.Path, r.RemoteAddr)
+ }
t.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
-/*
-func TemplateHandler(h http.Handler) http.Handler {
- h.handler
-}
-*/
-
-func uploadHandler(dir string) http.Handler {
+func uploadHandler(dir string, quiet bool) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Server", "uhttpd")
file, header, err := r.FormFile("file")
if err != nil {
@@ -60,7 +56,9 @@ func uploadHandler(dir string) http.Handler {
return
}
- log.Println(r.Method, r.URL.Path, header.Filename, r.RemoteAddr)
+ if !quiet {
+ log.Println(r.Method, r.URL.Path, header.Filename, r.RemoteAddr)
+ }
w.Write([]byte("Uploaded " + header.Filename))
}
return http.HandlerFunc(fn)
@@ -72,6 +70,7 @@ func main() {
dir_f := flag.String("dir", ".", "Directory to serve.")
disallow_upl_f := flag.Bool("disallow-upload", false, "Disallow uploads to /upload.")
upl_dir_f := flag.String("upload-dir", ".", "Directory to store uploaded files.")
+ quiet_f := flag.Bool("quiet", false, "Be quiet, suppress output.")
flag.Parse()
@@ -80,13 +79,15 @@ func main() {
port = *port_f
}
- fmt.Println("Starting uhttpd serving \"" + *dir_f + "\" on " + *ip_f + ":" + port + ".")
+ if !*quiet_f {
+ fmt.Println("Starting uhttpd serving \"" + *dir_f + "\" on " + *ip_f + ":" + port + ".")
+ }
mux := http.NewServeMux()
if !*disallow_upl_f {
os.MkdirAll(*upl_dir_f, 0755)
- mux.Handle("/upload", uploadHandler(*upl_dir_f))
+ mux.Handle("/upload", uploadHandler(*upl_dir_f, *quiet_f))
}
- mux.Handle("/", accessLog(http.FileServer(http.Dir(*dir_f))))
+ mux.Handle("/", accessLog(http.FileServer(http.Dir(*dir_f)), *quiet_f))
log.Fatal(http.ListenAndServe(*ip_f+":"+port, mux))
}
diff --git a/recorder.go b/recorder.go
new file mode 100644
index 0000000..90ef1a7
--- /dev/null
+++ b/recorder.go
@@ -0,0 +1,72 @@
+package main
+
+/*
+ Copy from net/http/httptest
+ Please read there for the license.
+*/
+
+import (
+ "bytes"
+ "net/http"
+)
+
+// ResponseRecorder is an implementation of http.ResponseWriter that
+// records its mutations for later inspection in tests.
+type ResponseRecorder struct {
+ Code int // the HTTP response code from WriteHeader
+ HeaderMap http.Header // the HTTP response headers
+ Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
+ Flushed bool
+
+ wroteHeader bool
+}
+
+// NewRecorder returns an initialized ResponseRecorder.
+func NewRecorder() *ResponseRecorder {
+ return &ResponseRecorder{
+ HeaderMap: make(http.Header),
+ Body: new(bytes.Buffer),
+ Code: 200,
+ }
+}
+
+// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
+// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
+const DefaultRemoteAddr = "1.2.3.4"
+
+// Header returns the response headers.
+func (rw *ResponseRecorder) Header() http.Header {
+ m := rw.HeaderMap
+ if m == nil {
+ m = make(http.Header)
+ rw.HeaderMap = m
+ }
+ return m
+}
+
+// Write always succeeds and writes to rw.Body, if not nil.
+func (rw *ResponseRecorder) Write(buf []byte) (int, error) {
+ if !rw.wroteHeader {
+ rw.WriteHeader(200)
+ }
+ if rw.Body != nil {
+ rw.Body.Write(buf)
+ }
+ return len(buf), nil
+}
+
+// WriteHeader sets rw.Code.
+func (rw *ResponseRecorder) WriteHeader(code int) {
+ if !rw.wroteHeader {
+ rw.Code = code
+ }
+ rw.wroteHeader = true
+}
+
+// Flush sets rw.Flushed to true.
+func (rw *ResponseRecorder) Flush() {
+ if !rw.wroteHeader {
+ rw.WriteHeader(200)
+ }
+ rw.Flushed = true
+}
diff --git a/template.go b/template.go
index c90b13c..017690d 100644
--- a/template.go
+++ b/template.go
@@ -4,7 +4,6 @@ import (
"html/template"
"log"
"net/http"
- "net/http/httptest"
"strings"
)
@@ -14,44 +13,69 @@ type TemplateHandler struct {
func (t *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Server", "uhttpd")
+
// We use our custom handler to prettify directories. Files are served via default.
- if strings.HasSuffix(r.URL.Path, "/") {
- r.Header.Del("If-Modified-Since")
- rec := httptest.NewRecorder()
+ if !strings.HasSuffix(r.URL.Path, "/") {
+ t.handler.ServeHTTP(w, r)
+ return
+ }
- defer rec.Body.Reset()
+ rec := NewRecorder()
- // passing the recorder instead of the real ResponseWriter
- t.handler.ServeHTTP(rec, r)
+ defer rec.Body.Reset()
- // we copy the original headers first
- for k, v := range rec.Header() {
- w.Header()[k] = v
- }
+ // passing the recorder instead of the real ResponseWriter
+ t.handler.ServeHTTP(rec, r)
- // BUG: Sometimes we get a 0 byte response
- if rec.Body.Len() == 0 {
- log.Println("Error: Empty body. Requested:", r.URL.Path)
- }
+ // let the standard lib handle all the caching
+ if rec.Code > 300 && rec.Code < 400 {
+ log.Println("Code: ", rec.Code)
+ t.handler.ServeHTTP(w, r)
+ return
+ }
- // we serve a file instead of a html page
- if !strings.Contains(w.Header().Get("Content-Type"), "text/html") {
- w.Write(rec.Body.Bytes())
+ // we copy the original headers first
+ for k, v := range rec.Header() {
+ w.Header()[k] = v
+ }
+
+ // not found
+ if rec.Code == 404 {
+ w.Header().Set("Content-Type", "text/html")
+ w.WriteHeader(404)
+ tmpl := template.New("404")
+ tmpl, err := tmpl.Parse(get404())
+ if err != nil {
+ log.Println(err.Error())
+ w.WriteHeader(500)
+ w.Write([]byte(err.Error()))
return
}
- data := rec.Body.String()
-
- // we serve the directoy page
- if strings.HasPrefix(data, "<pre>") {
- execTemplate(w, r, data)
- } else {
- w.Write(rec.Body.Bytes())
+ err = tmpl.Execute(w, struct{ URL string }{URL: r.URL.Path})
+ if err != nil {
+ log.Println(err.Error())
+ w.WriteHeader(500)
+ w.Write([]byte(err.Error()))
+ return
}
+ return
+ }
+ // we serve a file instead of a html page
+ if !strings.Contains(w.Header().Get("Content-Type"), "text/html") {
+ w.Write(rec.Body.Bytes())
return
+ }
+
+ data := rec.Body.String()
+
+ // we serve the directoy page
+ if strings.HasPrefix(data, "<pre>") {
+ execTemplate(w, r, data)
} else {
- t.handler.ServeHTTP(w, r)
+ w.Write(rec.Body.Bytes())
}
}
@@ -59,10 +83,9 @@ func execTemplate(w http.ResponseWriter, r *http.Request, data string) {
data = strings.Replace(data, "<pre>", "", 1)
data = strings.Replace(data, "</pre>", "", 1)
data = strings.Replace(data, "</a>", "</a><br>", -1)
- data = "<a class=\"dir\" href=\"..\">.. (up a dir)</a><br><br>" + data
tmpl := template.New("page")
- tmpl, err := tmpl.Parse(renderTemplate())
+ tmpl, err := tmpl.Parse(getTemplate())
if err != nil {
log.Println(err.Error())
w.WriteHeader(500)
@@ -84,73 +107,25 @@ func NewTemplateHandler(handler http.Handler, allowedHost string) *TemplateHandl
return &TemplateHandler{handler: handler}
}
-func renderTemplate() string {
- html := `<!doctype html>
+func getTemplate() string {
+ return `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{{.URL}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
- <style>
- /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-moz-box-sizing:content-box;-webkit-box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}.container{position:relative;width:100%;max-width:960px;margin:0 auto;padding:0 20px;box-sizing:border-box}.column,.columns{width:100%;float:left;box-sizing:border-box}@media (min-width:400px){.container{width:85%;padding:0}}@media (min-width:550px){.container{width:80%}.column,.columns{margin-left:4%}.column:first-child,.columns:first-child{margin-left:0}.one.column,.one.columns{width:4.66666666667%}.two.columns{width:13.3333333333%}.three.columns{width:22%}.four.columns{width:30.6666666667%}.five.columns{width:39.3333333333%}.six.columns{width:48%}.seven.columns{width:56.6666666667%}.eight.columns{width:65.3333333333%}.nine.columns{width:74%}.ten.columns{width:82.6666666667%}.eleven.columns{width:91.3333333333%}.twelve.columns{width:100%;margin-left:0}.one-third.column{width:30.6666666667%}.two-thirds.column{width:65.3333333333%}.one-half.column{width:48%}.offset-by-one.column,.offset-by-one.columns{margin-left:8.66666666667%}.offset-by-two.column,.offset-by-two.columns{margin-left:17.3333333333%}.offset-by-three.column,.offset-by-three.columns{margin-left:26%}.offset-by-four.column,.offset-by-four.columns{margin-left:34.6666666667%}.offset-by-five.column,.offset-by-five.columns{margin-left:43.3333333333%}.offset-by-six.column,.offset-by-six.columns{margin-left:52%}.offset-by-seven.column,.offset-by-seven.columns{margin-left:60.6666666667%}.offset-by-eight.column,.offset-by-eight.columns{margin-left:69.3333333333%}.offset-by-nine.column,.offset-by-nine.columns{margin-left:78%}.offset-by-ten.column,.offset-by-ten.columns{margin-left:86.6666666667%}.offset-by-eleven.column,.offset-by-eleven.columns{margin-left:95.3333333333%}.offset-by-one-third.column,.offset-by-one-third.columns{margin-left:34.6666666667%}.offset-by-two-thirds.column,.offset-by-two-thirds.columns{margin-left:69.3333333333%}.offset-by-one-half.column,.offset-by-one-half.columns{margin-left:52%}}html{font-size:62.5%}body{font-size:1.5em;line-height:1.6;font-weight:400;font-family:Raleway,HelveticaNeue,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#222}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:2rem;font-weight:300}h1{font-size:4rem;line-height:1.2;letter-spacing:-.1rem}h2{font-size:3.6rem;line-height:1.25;letter-spacing:-.1rem}h3{font-size:3rem;line-height:1.3;letter-spacing:-.1rem}h4{font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}h5{font-size:1.8rem;line-height:1.5;letter-spacing:-.05rem}h6{font-size:1.5rem;line-height:1.6;letter-spacing:0}@media (min-width:550px){h1{font-size:5rem}h2{font-size:4.2rem}h3{font-size:3.6rem}h4{font-size:3rem}h5{font-size:2.4rem}h6{font-size:1.5rem}}p{margin-top:0}a{color:#1EAEDB}a:hover{color:#0FA0CE}.button,button,input[type=button],input[type=reset],input[type=submit]{display:inline-block;height:38px;padding:0 30px;color:#555;text-align:center;font-size:11px;font-weight:600;line-height:38px;letter-spacing:.1rem;text-transform:uppercase;text-decoration:none;white-space:nowrap;background-color:transparent;border-radius:4px;border:1px solid #bbb;cursor:pointer;box-sizing:border-box}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{color:#333;border-color:#888;outline:0}.button.button-primary,button.button-primary,input[type=button].button-primary,input[type=reset].button-primary,input[type=submit].button-primary{color:#FFF;background-color:#33C3F0;border-color:#33C3F0}.button.button-primary:focus,.button.button-primary:hover,button.button-primary:focus,button.button-primary:hover,input[type=button].button-primary:focus,input[type=button].button-primary:hover,input[type=reset].button-primary:focus,input[type=reset].button-primary:hover,input[type=submit].button-primary:focus,input[type=submit].button-primary:hover{color:#FFF;background-color:#1EAEDB;border-color:#1EAEDB}input[type=email],input[type=text],input[type=tel],input[type=url],input[type=password],input[type=number],input[type=search],select,textarea{height:38px;padding:6px 10px;background-color:#fff;border:1px solid #D1D1D1;border-radius:4px;box-shadow:none;box-sizing:border-box}input[type=email],input[type=text],input[type=tel],input[type=url],input[type=password],input[type=number],input[type=search],textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none}textarea{min-height:65px;padding-top:6px;padding-bottom:6px}input[type=email]:focus,input[type=text]:focus,input[type=tel]:focus,input[type=url]:focus,input[type=password]:focus,input[type=number]:focus,input[type=search]:focus,select:focus,textarea:focus{border:1px solid #33C3F0;outline:0}label,legend{display:block;margin-bottom:.5rem;font-weight:600}fieldset{padding:0;border-width:0}input[type=checkbox],input[type=radio]{display:inline}label>.label-body{display:inline-block;margin-left:.5rem;font-weight:400}ul{list-style:none}ol{list-style:decimal inside}ol,ul{padding-left:0;margin-top:0}ol ol,ol ul,ul ol,ul ul{margin:1.5rem 0 1.5rem 3rem;font-size:90%}li{margin-bottom:1rem}code{padding:.2rem .5rem;margin:0 .2rem;font-size:90%;white-space:nowrap;background:#F1F1F1;border:1px solid #E1E1E1;border-radius:4px}pre>code{display:block;padding:1rem 1.5rem;white-space:pre}td,th{padding:12px 15px;text-align:left;border-bottom:1px solid #E1E1E1}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}.button,button{margin-bottom:1rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}.u-full-width{width:100%;box-sizing:border-box}.u-max-full-width{max-width:100%;box-sizing:border-box}.u-pull-right{float:right}.u-pull-left{float:left}hr{margin-top:3rem;margin-bottom:3.5rem;border-width:0;border-top:1px solid #E1E1E1}.container:after,.row:after,.u-cf{content:"";display:table;clear:both}.margin-top{margin-top:20px}.dir,.dir:hover{color: #000}.dir:hover{text-decoration:none}.dir:before{content: "> "}
-div.icon {
- height: 32px;
- width: 32px;
- position: relative;
- margin: 15px;
- overflow: hidden;
- display: inline-block;
-}
-div.icon div.home {
- height: 0px;
- width: 0px;
- border-width: 16px;
- border-style: solid;
- border-color: transparent transparent #333 transparent;
- position: absolute;
- bottom: 16px;
- left: 0;
-}
-
-div.icon div.home:after {
- content: '';
- width: 5px;
- height: 16px;
- background-color: transparent;
- position: absolute;
- top: 16px;
- right: -11px;
- border-left: 8px solid #333;
- border-right: 8px solid #333;
-}
-
-div.icon div.home:before {
- content: '';
- width: 9px;
- height: 6px;
- background-color: #333;
- position: absolute;
- top: 16px;
- right: -5px;
-}
-
-div.icon div.chimney {
- width: 4px;
- height: 10px;
- background: #333;
- position: absolute;
- right: 6px;
- top: 3px;
-}
- </style>
+ <style>/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}a:active,a:hover{outline:0}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}.container{position:relative;width:100%;max-width:960px;margin:0 auto;padding:0 20px;box-sizing:border-box}.column{width:100%;float:left;box-sizing:border-box}@media (min-width:400px){.container{width:85%;padding:0}}@media (min-width:550px){.container{width:80%}.column{margin-left:4%}.column:first-child{margin-left:0}}html{font-size:62.5%}body{font-size:1.5em;line-height:1.6;font-weight:400;font-family:Raleway,HelveticaNeue,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#222}h4{margin-top:0;margin-bottom:2rem;font-weight:300;font-size:2.4rem;line-height:1.35;letter-spacing:-.08rem}@media (min-width:550px){h4{font-size:3rem}}a{color:#1EAEDB}a:hover{color:#0FA0CE}blockquote{margin-bottom:2.5rem}.container:after,.row:after{content:"";display:table;clear:both}.margin-top{margin-top:20px}.dir,.dir:hover{color:#000}.dir:hover{text-decoration:none}.dir:before{content:"> "}div.icon{height:32px;width:32px;position:relative;margin:15px;overflow:hidden;display:inline-block}div.icon div.home{height:0;width:0;border-width:16px;border-style:solid;border-color:transparent transparent #333;position:absolute;bottom:16px;left:0}div.icon div.home:after{content:'';width:5px;height:16px;background-color:transparent;position:absolute;top:16px;right:-11px;border-left:8px solid #333;border-right:8px solid #333}div.icon div.home:before{content:'';width:9px;height:6px;background-color:#333;position:absolute;top:16px;right:-5px}div.icon div.chimney{width:4px;height:10px;background:#333;position:absolute;right:6px;top:3px}</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="column margin-top">
- <a href="/.."><div class="icon"><div class="home"></div><div class="chimney"></div></div></a>
- <h4>{{.URL}}</h4>
+ <h4><a href="/.." title="Go back to root"><div class="icon"><div class="home"></div><div class="chimney"></div></div></a><br>
+ {{.URL}}</h4>
<blockquote>
+ {{if ne .URL "/"}}
+ <a class="dir" href="..">.. (up a dir)</a><br><br>
+ {{end}}
{{.Data}}
</blockquote>
</div>
@@ -171,6 +146,27 @@ div.icon div.chimney {
</script>
</body>
</html>`
+}
- return html
+func get404() string {
+ return `<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Not Found - {{.URL}}</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <style>/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background-color:transparent}a:active,a:hover{outline:0}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}.container{position:relative;width:100%;max-width:960px;margin:0 auto;padding:0 20px;box-sizing:border-box}@media (min-width:400px){.container{width:85%;padding:0}}@media (min-width:550px){.container{width:80%}}html{font-size:62.5%}body{font-size:1.5em;line-height:1.6;font-weight:400;font-family:Raleway,HelveticaNeue,"Helvetica Neue",Helvetica,Arial,sans-serif;color:#222}h3{margin-top:0;margin-bottom:2rem;font-weight:300;font-size:3rem;line-height:1.3;letter-spacing:-.1rem}@media (min-width:550px){h3{font-size:3.6rem}}p{margin-top:0}a{color:#1EAEDB}a:hover{color:#0FA0CE}.button{display:inline-block;height:38px;padding:0 30px;color:#555;text-align:center;font-size:11px;font-weight:600;line-height:38px;letter-spacing:.1rem;text-transform:uppercase;text-decoration:none;white-space:nowrap;background-color:transparent;border-radius:4px;border:1px solid #bbb;cursor:pointer;box-sizing:border-box}.button:focus,.button:hover{color:#333;border-color:#888;outline:0}.button.button-primary{color:#FFF;background-color:#33C3F0;border-color:#33C3F0}.button.button-primary:focus,.button.button-primary:hover{color:#FFF;background-color:#1EAEDB;border-color:#1EAEDB}.button{margin-bottom:1rem}p{margin-bottom:2.5rem}.container:after{content:"";display:table;clear:both}.section{padding:8rem 0 7rem;text-align:center}.section-description,.section-heading{margin-bottom:1.2rem}</style>
+</head>
+<body>
+<div class="section">
+ <div class="container">
+ <h3 class="section-heading">Not Found - {{.URL}}</h3>
+ <p class="section-description">
+ Please check for typos in your url. </p>
+ <p class="section-description">
+ <a href="/" class="button button-primary">Go back to root</a></p>
+ </div>
+</div>
+</body>
+</html>`
}