diff --git a/.gitignore b/.gitignore index 3f87b8fb2..0dd26ce5d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ access.log /*.conf Caddyfile + +og_static/ \ No newline at end of file diff --git a/README.md b/README.md index c17b3d0a6..44fd023b4 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ By default, Caddy serves the current directory at [localhost:2015](http://localh Caddy accepts some flags from the command line. Run `caddy -h` to view the help for flags. You can also pipe a Caddyfile into the caddy command. +**Running as root:** We advise against this; use setcap instead, like so: `setcap cap_net_bind_service=+ep ./caddy` This will allow you to listen on ports below 1024 (like 80 and 443). #### Docker Container @@ -51,6 +52,7 @@ Caddy is available as a Docker container from any of these sources: - [abiosoft/caddy](https://registry.hub.docker.com/u/abiosoft/caddy/) - [darron/caddy](https://registry.hub.docker.com/u/darron/caddy/) +- [joshix/caddy](https://registry.hub.docker.com/u/joshix/caddy/) - [jumanjiman/caddy](https://registry.hub.docker.com/u/jumanjiman/caddy/) - [zenithar/nano-caddy](https://registry.hub.docker.com/u/zenithar/nano-caddy/) diff --git a/app/app.go b/app/app.go index e73f95b4e..b006d2942 100644 --- a/app/app.go +++ b/app/app.go @@ -20,7 +20,7 @@ const ( Name = "Caddy" // Version is the program version - Version = "0.7.4" + Version = "0.7.5" ) var ( diff --git a/config/config.go b/config/config.go index 3037a2678..82a29585f 100644 --- a/config/config.go +++ b/config/config.go @@ -57,6 +57,11 @@ func Load(filename string, input io.Reader) (Group, error) { if config.Port == "" { config.Port = Port } + if config.Port == "http" { + config.TLS.Enabled = false + log.Printf("Warning: TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "+ + "specify port 80 explicitly (https://%s:80).", config.Port, config.Host, config.Host) + } if i == 0 { sharedConfig.Startup = []func() error{} sharedConfig.Shutdown = []func() error{} diff --git a/config/setup/fastcgi.go b/config/setup/fastcgi.go index a2a7e8794..ab21ef1f2 100644 --- a/config/setup/fastcgi.go +++ b/config/setup/fastcgi.go @@ -31,7 +31,7 @@ func FastCGI(c *Controller) (middleware.Middleware, error) { SoftwareName: c.AppName, SoftwareVersion: c.AppVersion, ServerName: c.Host, - ServerPort: c.Port, + ServerPort: c.Port, // BUG: This is not known until the server blocks are split up... } }, nil } diff --git a/config/setup/tls.go b/config/setup/tls.go index 36ecac05e..431409f4a 100644 --- a/config/setup/tls.go +++ b/config/setup/tls.go @@ -2,7 +2,6 @@ package setup import ( "crypto/tls" - "log" "strings" "github.com/mholt/caddy/middleware" @@ -10,11 +9,6 @@ import ( func TLS(c *Controller) (middleware.Middleware, error) { c.TLS.Enabled = true - if c.Port == "http" { - c.TLS.Enabled = false - log.Printf("Warning: TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "+ - "specify port 80 explicitly (https://%s:80).", c.Port, c.Host, c.Host) - } for c.Next() { if !c.NextArg() { diff --git a/dist/CHANGES.txt b/dist/CHANGES.txt index 34faa66a0..dced4bd4e 100644 --- a/dist/CHANGES.txt +++ b/dist/CHANGES.txt @@ -1,9 +1,14 @@ CHANGES - +0.7.5 (August 5, 2015) +- core: All listeners bind to 0.0.0.0 unless 'bind' directive is used - fastcgi: Set HTTPS env variable if connection is secure +- log: Output to system log (except Windows) +- markdown: Added dev command to disable caching during development - markdown: Fixed error reporting during initial site generation - markdown: Fixed crash if path does not exist when server starts +- markdown: Fixed site generation and link indexing when files change +- templates: Added .NowDate for use in date-related functions - Several bug fixes related to startup and shutdown functions diff --git a/dist/README.txt b/dist/README.txt index 4a0713b85..2a3cbfc0f 100644 --- a/dist/README.txt +++ b/dist/README.txt @@ -1,4 +1,4 @@ -CADDY 0.7.4 +CADDY 0.7.5 Website https://caddyserver.com diff --git a/middleware/browse/browse.go b/middleware/browse/browse.go index 6868be881..8036896f0 100644 --- a/middleware/browse/browse.go +++ b/middleware/browse/browse.go @@ -4,12 +4,14 @@ package browse import ( "bytes" + "encoding/json" "errors" "net/http" "net/url" "os" "path" "sort" + "strconv" "strings" "text/template" "time" @@ -185,7 +187,6 @@ func directoryListing(files []os.FileInfo, r *http.Request, canGoUp bool, root s // ServeHTTP implements the middleware.Handler interface. func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { filename := b.Root + r.URL.Path - info, err := os.Stat(filename) if err != nil { return b.Next.ServeHTTP(w, r) @@ -264,12 +265,47 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { listing.applySort() var buf bytes.Buffer - err = bc.Template.Execute(&buf, listing) - if err != nil { - return http.StatusInternalServerError, err + // check if we should provide json + acceptHeader := strings.Join(r.Header["Accept"], ",") + if strings.Contains(strings.ToLower(acceptHeader), "application/json") { + var marsh []byte + // check if we are limited + if limitQuery := r.URL.Query().Get("limit"); limitQuery != "" { + limit, err := strconv.Atoi(limitQuery) + if err != nil { // if the 'limit' query can't be interpreted as a number, return err + return http.StatusBadRequest, err + } + // if `limit` is equal or less than len(listing.Items) and bigger than 0, list them + if limit <= len(listing.Items) && limit > 0 { + marsh, err = json.Marshal(listing.Items[:limit]) + } else { // if the 'limit' query is empty, or has the wrong value, list everything + marsh, err = json.Marshal(listing.Items) + } + if err != nil { + return http.StatusInternalServerError, err + } + } else { // there's no 'limit' query, list them all + marsh, err = json.Marshal(listing.Items) + if err != nil { + return http.StatusInternalServerError, err + } + } + + // write the marshaled json to buf + if _, err = buf.Write(marsh); err != nil { + return http.StatusInternalServerError, err + } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + } else { // there's no 'application/json' in the 'Accept' header, browse normally + err = bc.Template.Execute(&buf, listing) + if err != nil { + return http.StatusInternalServerError, err + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + } - w.Header().Set("Content-Type", "text/html; charset=utf-8") buf.WriteTo(w) return http.StatusOK, nil diff --git a/middleware/browse/browse_test.go b/middleware/browse/browse_test.go index b0ff28db6..9f91f34ef 100644 --- a/middleware/browse/browse_test.go +++ b/middleware/browse/browse_test.go @@ -1,14 +1,16 @@ package browse import ( + "encoding/json" + "github.com/mholt/caddy/middleware" "net/http" "net/http/httptest" + "net/url" + "os" "sort" "testing" "text/template" "time" - - "github.com/mholt/caddy/middleware" ) // "sort" package has "IsSorted" function, but no "IsReversed"; @@ -154,4 +156,88 @@ func TestBrowseTemplate(t *testing.T) { if respBody != expectedBody { t.Fatalf("Expected body: %v got: %v", expectedBody, respBody) } + +} + +func TestBrowseJson(t *testing.T) { + + b := Browse{ + Next: middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { + t.Fatalf("Next shouldn't be called") + return 0, nil + }), + Root: "./testdata", + Configs: []Config{ + Config{ + PathScope: "/photos", + }, + }, + } + + req, err := http.NewRequest("GET", "/photos/", nil) + if err != nil { + t.Fatalf("Test: Could not create HTTP request: %v", err) + } + req.Header.Set("Accept", "application/json") + rec := httptest.NewRecorder() + + b.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Wrong status, expected %d, got %d", http.StatusOK, rec.Code) + } + if rec.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" { + t.Fatalf("Expected Content type to be application/json; charset=utf-8, but got %s ", rec.HeaderMap.Get("Content-Type")) + } + + actualJsonResponseString := rec.Body.String() + + //generating the listing to compare it with the response body + file, err := os.Open(b.Root + req.URL.Path) + if err != nil { + if os.IsPermission(err) { + t.Fatalf("Os Permission Error") + + } + + } + defer file.Close() + + files, err := file.Readdir(-1) + if err != nil { + t.Fatalf("Unable to Read Contents of the directory") + } + var fileinfos []FileInfo + for _, f := range files { + name := f.Name() + + if f.IsDir() { + name += "/" + } + + url := url.URL{Path: name} + + fileinfos = append(fileinfos, FileInfo{ + IsDir: f.IsDir(), + Name: f.Name(), + Size: f.Size(), + URL: url.String(), + ModTime: f.ModTime(), + Mode: f.Mode(), + }) + } + listing := Listing{ + Items: fileinfos, + } + listing.Sort = "name" + listing.Order = "asc" + listing.applySort() + + marsh, err := json.Marshal(listing.Items) + if err != nil { + t.Fatalf("Unable to Marshal the listing ") + } + expectedJsonString := string(marsh) + if actualJsonResponseString != expectedJsonString { + t.Errorf("Json response string doesnt match the expected Json response ") + } } diff --git a/middleware/middleware_test.go b/middleware/middleware_test.go new file mode 100644 index 000000000..e5b238e6b --- /dev/null +++ b/middleware/middleware_test.go @@ -0,0 +1,41 @@ +package middleware + +import ( + "net/http" + "testing" +) + +func TestIndexfile(t *testing.T) { + tests := []struct { + rootDir http.FileSystem + fpath string + indexFiles []string + shouldErr bool + expectedFilePath string //retun value + expectedBoolValue bool //return value + }{ + { + http.Dir("./templates/testdata"), "/images/", []string{"img.htm"}, + false, + "/images/img.htm", true, + }, + } + for i, test := range tests { + actualFilePath, actualBoolValue := IndexFile(test.rootDir, test.fpath, test.indexFiles) + if actualBoolValue == true && test.shouldErr { + t.Errorf("Test %d didn't error, but it should have", i) + } else if actualBoolValue != true && !test.shouldErr { + t.Errorf("Test %d errored, but it shouldn't have; got %s", i, "Please Add a / at the end of fpath or the indexFiles doesnt exist") + } + if actualFilePath != test.expectedFilePath { + t.Fatalf("Test %d expected returned filepath to be %s, but got %s ", + i, test.expectedFilePath, actualFilePath) + + } + if actualBoolValue != test.expectedBoolValue { + t.Fatalf("Test %d expected returned bool value to be %v, but got %v ", + i, test.expectedBoolValue, actualBoolValue) + + } + } +} diff --git a/middleware/recorder_test.go b/middleware/recorder_test.go new file mode 100644 index 000000000..9d4e5b838 --- /dev/null +++ b/middleware/recorder_test.go @@ -0,0 +1,40 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestNewResponseRecorder(t *testing.T) { + w := httptest.NewRecorder() + recordRequest := NewResponseRecorder(w) + if !(recordRequest.ResponseWriter == w) { + t.Fatalf("Expected Response writer in the Recording to be same as the one sent\n") + } + if recordRequest.status != http.StatusOK { + t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", recordRequest.status) + } +} +func TestWriteHeader(t *testing.T) { + w := httptest.NewRecorder() + recordRequest := NewResponseRecorder(w) + recordRequest.WriteHeader(401) + if w.Code != 401 || recordRequest.status != 401 { + t.Fatalf("Expected Response status to be set to 401, but found %d\n", recordRequest.status) + } +} + +func TestWrite(t *testing.T) { + w := httptest.NewRecorder() + responseTestString := "test" + recordRequest := NewResponseRecorder(w) + buf := []byte(responseTestString) + recordRequest.Write(buf) + if recordRequest.size != len(buf) { + t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size) + } + if w.Body.String() != responseTestString { + t.Fatalf("Expected Response Body to be %s , but found %s\n", w.Body.String()) + } +} diff --git a/middleware/replacer_test.go b/middleware/replacer_test.go new file mode 100644 index 000000000..ad8a9489b --- /dev/null +++ b/middleware/replacer_test.go @@ -0,0 +1,38 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestNewReplacer(t *testing.T) { + w := httptest.NewRecorder() + recordRequest := NewResponseRecorder(w) + userJson := `{"username": "dennis"}` + + reader := strings.NewReader(userJson) //Convert string to reader + + request, err := http.NewRequest("POST", "http://caddyserver.com", reader) //Create request with JSON body + if err != nil { + t.Fatalf("Request Formation Failed \n") + } + replaceValues := NewReplacer(request, recordRequest, "") + + switch v := replaceValues.(type) { + case replacer: + if v.replacements["{host}"] != "caddyserver.com" { + t.Errorf("Expected host to be caddyserver.com") + } + if v.replacements["{method}"] != "POST" { + t.Errorf("Expected request method to be POST") + } + if v.replacements["{status}"] != "200" { + t.Errorf("Expected status to be 200") + } + + default: + t.Fatalf("Return Value from New Replacer expected pass type assertion into a replacer type \n") + } +}