Major changes on API
							parent
							
								
									01c78d2b36
								
							
						
					
					
						commit
						1f7974de38
					
				| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
.DS_Store
 | 
			
		||||
node_modules/
 | 
			
		||||
_assets/dist_dev/*
 | 
			
		||||
_assets/dist/*
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
yarn-debug.log*
 | 
			
		||||
yarn-error.log*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,10 +6,10 @@ module.exports = {
 | 
			
		|||
    env: {
 | 
			
		||||
      NODE_ENV: '"production"'
 | 
			
		||||
    },
 | 
			
		||||
    index: path.resolve(__dirname, '../dist/templates/index.html'),
 | 
			
		||||
    index: path.resolve(__dirname, '../dist/index.html'),
 | 
			
		||||
    assetsRoot: path.resolve(__dirname, '../dist'),
 | 
			
		||||
    assetsSubDirectory: '_',
 | 
			
		||||
    assetsPublicPath: '{{ .BaseURL }}',
 | 
			
		||||
    assetsSubDirectory: 'static',
 | 
			
		||||
    assetsPublicPath: '{{ .BaseURL }}/',
 | 
			
		||||
    productionSourceMap: true,
 | 
			
		||||
    // Gzip off by default as many popular static hosts such as
 | 
			
		||||
    // Surge or Netlify already gzip all static assets for you.
 | 
			
		||||
| 
						 | 
				
			
			@ -27,10 +27,10 @@ module.exports = {
 | 
			
		|||
    env: {
 | 
			
		||||
      NODE_ENV: '"development"'
 | 
			
		||||
    },
 | 
			
		||||
    index: path.resolve(__dirname, '../dist_dev/templates/index.html'),
 | 
			
		||||
    assetsRoot: path.resolve(__dirname, '../dist_dev/'),
 | 
			
		||||
    assetsSubDirectory: '_',
 | 
			
		||||
    assetsPublicPath: '{{ .BaseURL }}',
 | 
			
		||||
    index: path.resolve(__dirname, '../dist/index.html'),
 | 
			
		||||
    assetsRoot: path.resolve(__dirname, '../dist/'),
 | 
			
		||||
    assetsSubDirectory: 'static',
 | 
			
		||||
    assetsPublicPath: '{{ .BaseURL }}/',
 | 
			
		||||
    produceSourceMap: true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@
 | 
			
		|||
  window.addEventListener('load', function() {
 | 
			
		||||
      if ('serviceWorker' in navigator &&
 | 
			
		||||
          (window.location.protocol === 'https:' || isLocalhost)) {
 | 
			
		||||
        navigator.serviceWorker.register('{{ .BaseURL }}/_/service-worker.js')
 | 
			
		||||
        navigator.serviceWorker.register('{{ .BaseURL }}/sw.js')
 | 
			
		||||
        .then(function(registration) {
 | 
			
		||||
          // updatefound is fired if service-worker.js changes.
 | 
			
		||||
          registration.onupdatefound = function() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ exports.assetsPath = function (_path) {
 | 
			
		|||
  var assetsSubDirectory = process.env.NODE_ENV === 'production'
 | 
			
		||||
    ? config.build.assetsSubDirectory
 | 
			
		||||
    : config.dev.assetsSubDirectory
 | 
			
		||||
 | 
			
		||||
  return path.posix.join(assetsSubDirectory, _path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,8 +98,9 @@ var webpackConfig = merge(baseWebpackConfig, {
 | 
			
		|||
    ]),
 | 
			
		||||
    // service worker caching
 | 
			
		||||
    new SWPrecacheWebpackPlugin({
 | 
			
		||||
      cacheId: 'my-vue-app',
 | 
			
		||||
      filename: 'service-worker.js',
 | 
			
		||||
      cacheId: 'File Manager',
 | 
			
		||||
      filename: 'sw.js',
 | 
			
		||||
      replacePrefix: '{{ .BaseURL }}/',
 | 
			
		||||
      staticFileGlobs: ['dist/**/*.{js,html,css}'],
 | 
			
		||||
      minify: true,
 | 
			
		||||
      stripPrefix: 'dist/'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,27 +4,28 @@
 | 
			
		|||
  <meta charset="utf-8">
 | 
			
		||||
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
 | 
			
		||||
  <meta name="base" content="{{ .BaseURL }}">
 | 
			
		||||
  <title>File Manager</title>
 | 
			
		||||
  <link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/_/img/icons/favicon-32x32.png">
 | 
			
		||||
  <link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/_/img/icons/favicon-16x16.png">
 | 
			
		||||
  <link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
 | 
			
		||||
  <link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
 | 
			
		||||
  <!--[if IE]><link rel="shortcut icon" href="/static/img/icons/favicon.ico"><![endif]-->
 | 
			
		||||
  <!-- Add to home screen for Android and modern mobile browsers -->
 | 
			
		||||
  <link rel="manifest" href="{{ .BaseURL }}/_/manifest.json">
 | 
			
		||||
  <meta name="theme-color" content="#4DBA87">
 | 
			
		||||
  <link rel="manifest" href="{{ .BaseURL }}/static/manifest.json">
 | 
			
		||||
  <meta name="theme-color" content="#2979ff">
 | 
			
		||||
 | 
			
		||||
  <!-- Add to home screen for Safari on iOS -->
 | 
			
		||||
  <meta name="apple-mobile-web-app-capable" content="yes">
 | 
			
		||||
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
 | 
			
		||||
  <meta name="apple-mobile-web-app-title" content="assets">
 | 
			
		||||
  <link rel="apple-touch-icon" href="{{ .BaseURL }}/_/img/icons/apple-touch-icon-152x152.png">
 | 
			
		||||
  <link rel="apple-touch-icon" href="{{ .BaseURL }}/static/img/icons/apple-touch-icon-152x152.png">
 | 
			
		||||
  <!-- Add to home screen for Windows -->
 | 
			
		||||
  <meta name="msapplication-TileImage" content="{{ .BaseURL }}/_/img/icons/msapplication-icon-144x144.png">
 | 
			
		||||
  <meta name="msapplication-TileColor" content="#000000">
 | 
			
		||||
  <meta name="msapplication-TileImage" content="{{ .BaseURL }}/static/img/icons/msapplication-icon-144x144.png">
 | 
			
		||||
  <meta name="msapplication-TileColor" content="#2979ff">
 | 
			
		||||
 | 
			
		||||
  <% for (var chunk of webpack.chunks) {
 | 
			
		||||
    for (var file of chunk.files) {
 | 
			
		||||
      if (file.match(/\.(js|css)$/)) { %>
 | 
			
		||||
      <link rel="<%= chunk.initial?'preload':'prefetch' %>" href="{{ .BaseURL }}/<%= file.replace('static', '') %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
 | 
			
		||||
      <link rel="<%= chunk.initial?'preload':'prefetch' %>" href="{{ .BaseURL }}/<%= file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
 | 
			
		||||
 | 
			
		||||
  <style>
 | 
			
		||||
  #loading {
 | 
			
		||||
| 
						 | 
				
			
			@ -88,20 +89,8 @@
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
  </style>
 | 
			
		||||
 | 
			
		||||
  {{- if ne .User.StyleSheet "" -}}
 | 
			
		||||
    <style>{{ CSS .User.StyleSheet }}</style>
 | 
			
		||||
  {{- end -}}
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <script>
 | 
			
		||||
  var info = {
 | 
			
		||||
    user: JSON.parse('{{ Marshal .User }}'),
 | 
			
		||||
    req: JSON.parse('{{ Marshal . }}'),
 | 
			
		||||
    webdavURL: "{{ .WebDavURL }}",
 | 
			
		||||
    baseURL: "{{.BaseURL}}"
 | 
			
		||||
  }
 | 
			
		||||
  </script>
 | 
			
		||||
  <div id="app"></div>
 | 
			
		||||
 | 
			
		||||
  <div id="loading">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,18 +3,18 @@
 | 
			
		|||
  "short_name": "File Manager",
 | 
			
		||||
  "icons": [
 | 
			
		||||
    {
 | 
			
		||||
      "src": "/static/img/icons/android-chrome-192x192.png",
 | 
			
		||||
      "src": "{{ .BaseURL }}/static/img/icons/android-chrome-192x192.png",
 | 
			
		||||
      "sizes": "192x192",
 | 
			
		||||
      "type": "image/png"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "src": "/static/img/icons/android-chrome-512x512.png",
 | 
			
		||||
      "src": "{{ .BaseURL }}/static/img/icons/android-chrome-512x512.png",
 | 
			
		||||
      "sizes": "512x512",
 | 
			
		||||
      "type": "image/png"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "start_url": "/index.html",
 | 
			
		||||
  "start_url": "{{ .BaseURL }}/",
 | 
			
		||||
  "display": "standalone",
 | 
			
		||||
  "background_color": "#000000",
 | 
			
		||||
  "theme_color": "#4DBA87"
 | 
			
		||||
  "background_color": "#ffffff",
 | 
			
		||||
  "theme_color": "#2979ff"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,281 @@
 | 
			
		|||
package filemanager
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func cleanURL(path string) (string, string) {
 | 
			
		||||
	if path == "" {
 | 
			
		||||
		return "", ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path = strings.TrimPrefix(path, "/")
 | 
			
		||||
 | 
			
		||||
	i := strings.Index(path, "/")
 | 
			
		||||
	if i == -1 {
 | 
			
		||||
		return "", path
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return path[0:i], path[i:len(path)]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func serveAPI(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	if r.URL.Path == "/auth" {
 | 
			
		||||
		return getTokenHandler(c, w, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* valid, user := validAuth(c, r)
 | 
			
		||||
	if !valid {
 | 
			
		||||
		return http.StatusForbidden, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println(user)
 | 
			
		||||
	c.us = user */
 | 
			
		||||
 | 
			
		||||
	c.us = c.fm.User
 | 
			
		||||
 | 
			
		||||
	var router string
 | 
			
		||||
	router, r.URL.Path = cleanURL(r.URL.Path)
 | 
			
		||||
 | 
			
		||||
	if !c.us.Allowed(r.URL.Path) {
 | 
			
		||||
		return http.StatusForbidden, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if router == "checksum" || router == "download" {
 | 
			
		||||
		var err error
 | 
			
		||||
		c.fi, err = getInfo(r.URL, c.fm, c.us)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errorToHTTP(err, false), err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch router {
 | 
			
		||||
	case "download":
 | 
			
		||||
		return downloadHandler(c, w, r)
 | 
			
		||||
	case "checksum":
 | 
			
		||||
		return checksumHandler(c, w, r)
 | 
			
		||||
	case "command":
 | 
			
		||||
		return command(c, w, r)
 | 
			
		||||
	case "search":
 | 
			
		||||
		return search(c, w, r)
 | 
			
		||||
	case "resource":
 | 
			
		||||
		return resourceHandler(c, w, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return http.StatusNotFound, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	switch r.Method {
 | 
			
		||||
	case http.MethodGet:
 | 
			
		||||
		return getHandler(c, w, r)
 | 
			
		||||
	case http.MethodDelete:
 | 
			
		||||
		return deleteHandler(c, w, r)
 | 
			
		||||
	case http.MethodPut:
 | 
			
		||||
		return putHandler(c, w, r)
 | 
			
		||||
	case http.MethodPost:
 | 
			
		||||
		// Handle renaming
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* // Execute beforeSave if it is a PUT request.
 | 
			
		||||
	if r.Method == http.MethodPut {
 | 
			
		||||
		if err := c.fm.BeforeSave(r, c.fm, c.us); err != nil {
 | 
			
		||||
			return http.StatusInternalServerError, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Execute afterSave if it is a PUT request.
 | 
			
		||||
	if r.Method == http.MethodPut {
 | 
			
		||||
		if err := c.fm.AfterSave(r, c.fm, c.us); err != nil {
 | 
			
		||||
			return http.StatusInternalServerError, err
 | 
			
		||||
		}
 | 
			
		||||
	} */
 | 
			
		||||
 | 
			
		||||
	return http.StatusNotImplemented, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	// Obtains the information of the directory/file.
 | 
			
		||||
	f, err := getInfo(r.URL, c.fm, c.us)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errorToHTTP(err, false), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If it's a dir and the path doesn't end with a trailing slash,
 | 
			
		||||
	// redirect the user.
 | 
			
		||||
	if f.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
 | 
			
		||||
		r.URL.Path = r.URL.Path + "/"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If it is a dir, go and serve the listing.
 | 
			
		||||
	if f.IsDir {
 | 
			
		||||
		c.fi = f
 | 
			
		||||
		return listingHandler(c, w, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Tries to get the file type.
 | 
			
		||||
	if err = f.RetrieveFileType(); err != nil {
 | 
			
		||||
		return errorToHTTP(err, true), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If it can't be edited or the user isn't allowed to,
 | 
			
		||||
	// serve it as a listing, with a preview of the file.
 | 
			
		||||
	if !f.CanBeEdited() || !c.us.AllowEdit {
 | 
			
		||||
		f.Kind = "preview"
 | 
			
		||||
	} else {
 | 
			
		||||
		// Otherwise, we just bring the editor in!
 | 
			
		||||
		f.Kind = "editor"
 | 
			
		||||
 | 
			
		||||
		err = f.getEditor()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return http.StatusInternalServerError, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return renderJSON(w, f)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func listingHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	f := c.fi
 | 
			
		||||
	f.Kind = "listing"
 | 
			
		||||
 | 
			
		||||
	err := f.getListing(c, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errorToHTTP(err, true), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	listing := f.listing
 | 
			
		||||
 | 
			
		||||
	cookieScope := c.fm.RootURL()
 | 
			
		||||
	if cookieScope == "" {
 | 
			
		||||
		cookieScope = "/"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Copy the query values into the Listing struct
 | 
			
		||||
	listing.Sort, listing.Order, err = handleSortOrder(w, r, cookieScope)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusBadRequest, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	listing.ApplySort()
 | 
			
		||||
	listing.Display = displayMode(w, r, cookieScope)
 | 
			
		||||
 | 
			
		||||
	return renderJSON(w, f)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func deleteHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	// Prevent the removal of the root directory.
 | 
			
		||||
	if r.URL.Path == "/" {
 | 
			
		||||
		return http.StatusForbidden, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Remove the file or folder.
 | 
			
		||||
	err := c.us.fileSystem.RemoveAll(context.TODO(), r.URL.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errorToHTTP(err, true), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return http.StatusOK, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func putHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	if strings.HasSuffix(r.URL.Path, "/") {
 | 
			
		||||
		err := c.us.fileSystem.Mkdir(context.TODO(), r.URL.Path, 0666)
 | 
			
		||||
		return errorToHTTP(err, false), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	f, err := c.us.fileSystem.OpenFile(context.TODO(), r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errorToHTTP(err, false), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = io.Copy(f, r.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errorToHTTP(err, false), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fi, err := f.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errorToHTTP(err, false), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	etag := fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
 | 
			
		||||
	w.Header().Set("ETag", etag)
 | 
			
		||||
	return http.StatusOK, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// displayMode obtaisn the display mode from URL, or from the
 | 
			
		||||
// cookie.
 | 
			
		||||
func displayMode(w http.ResponseWriter, r *http.Request, scope string) string {
 | 
			
		||||
	displayMode := r.URL.Query().Get("display")
 | 
			
		||||
 | 
			
		||||
	if displayMode == "" {
 | 
			
		||||
		if displayCookie, err := r.Cookie("display"); err == nil {
 | 
			
		||||
			displayMode = displayCookie.Value
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if displayMode == "" || (displayMode != "mosaic" && displayMode != "list") {
 | 
			
		||||
		displayMode = "mosaic"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.SetCookie(w, &http.Cookie{
 | 
			
		||||
		Name:   "display",
 | 
			
		||||
		Value:  displayMode,
 | 
			
		||||
		MaxAge: 31536000,
 | 
			
		||||
		Path:   scope,
 | 
			
		||||
		Secure: r.TLS != nil,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return displayMode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
 | 
			
		||||
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
 | 
			
		||||
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {
 | 
			
		||||
	sort = r.URL.Query().Get("sort")
 | 
			
		||||
	order = r.URL.Query().Get("order")
 | 
			
		||||
 | 
			
		||||
	// If the query 'sort' or 'order' is empty, use defaults or any values
 | 
			
		||||
	// previously saved in Cookies.
 | 
			
		||||
	switch sort {
 | 
			
		||||
	case "":
 | 
			
		||||
		sort = "name"
 | 
			
		||||
		if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
 | 
			
		||||
			sort = sortCookie.Value
 | 
			
		||||
		}
 | 
			
		||||
	case "name", "size", "type":
 | 
			
		||||
		http.SetCookie(w, &http.Cookie{
 | 
			
		||||
			Name:   "sort",
 | 
			
		||||
			Value:  sort,
 | 
			
		||||
			MaxAge: 31536000,
 | 
			
		||||
			Path:   scope,
 | 
			
		||||
			Secure: r.TLS != nil,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch order {
 | 
			
		||||
	case "":
 | 
			
		||||
		order = "asc"
 | 
			
		||||
		if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
 | 
			
		||||
			order = orderCookie.Value
 | 
			
		||||
		}
 | 
			
		||||
	case "asc", "desc":
 | 
			
		||||
		http.SetCookie(w, &http.Cookie{
 | 
			
		||||
			Name:   "order",
 | 
			
		||||
			Value:  order,
 | 
			
		||||
			MaxAge: 31536000,
 | 
			
		||||
			Path:   scope,
 | 
			
		||||
			Secure: r.TLS != nil,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
package filemanager
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	jwt "github.com/dgrijalva/jwt-go"
 | 
			
		||||
	"github.com/dgrijalva/jwt-go/request"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/* Set up a global string for our secret */
 | 
			
		||||
var key = []byte("secret")
 | 
			
		||||
 | 
			
		||||
type claims struct {
 | 
			
		||||
	*User
 | 
			
		||||
	jwt.StandardClaims
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getTokenHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	// TODO: get user and password info from the request
 | 
			
		||||
	// check if the password is correct for that user using a DB or JSOn
 | 
			
		||||
	// or something.
 | 
			
		||||
 | 
			
		||||
	claims := claims{
 | 
			
		||||
		c.fm.User,
 | 
			
		||||
		jwt.StandardClaims{
 | 
			
		||||
			ExpiresAt: time.Now().Add(time.Minute * 5).Unix(),
 | 
			
		||||
			Issuer:    "test",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
 | 
			
		||||
	string, err := token.SignedString(key)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.Write([]byte(string))
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validAuth(c *requestContext, r *http.Request) (bool, *User) {
 | 
			
		||||
	token, err := request.ParseFromRequestWithClaims(r, request.AuthorizationHeaderExtractor, &claims{},
 | 
			
		||||
		func(token *jwt.Token) (interface{}, error) {
 | 
			
		||||
			return key, nil
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
	if err == nil && token.Valid {
 | 
			
		||||
		return true, c.fm.User
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +94,6 @@ func parse(c *caddy.Controller) ([]*config, error) {
 | 
			
		|||
 | 
			
		||||
		if len(args) > 0 {
 | 
			
		||||
			m.baseURL = args[0]
 | 
			
		||||
			m.webDavURL = "/webdav"
 | 
			
		||||
			m.SetBaseURL(args[0])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -108,13 +107,6 @@ func parse(c *caddy.Controller) ([]*config, error) {
 | 
			
		|||
				if m.AfterSave, err = makeCommand(c, m); err != nil {
 | 
			
		||||
					return configs, err
 | 
			
		||||
				}
 | 
			
		||||
			case "webdav":
 | 
			
		||||
				if !c.NextArg() {
 | 
			
		||||
					return configs, c.ArgErr()
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				m.webDavURL = "c.Val()"
 | 
			
		||||
				m.SetWebDavURL(c.Val())
 | 
			
		||||
			case "show":
 | 
			
		||||
				if !c.NextArg() {
 | 
			
		||||
					return configs, c.ArgErr()
 | 
			
		||||
| 
						 | 
				
			
			@ -251,7 +243,7 @@ func makeCommand(c *caddy.Controller, m *config) (Command, error) {
 | 
			
		|||
 | 
			
		||||
	fn = func(r *http.Request, c *FileManager, u *User) error {
 | 
			
		||||
		path := strings.Replace(r.URL.Path, m.baseURL+m.webDavURL, "", 1)
 | 
			
		||||
		path = u.Scope() + "/" + path
 | 
			
		||||
		path = u.Scope + "/" + path
 | 
			
		||||
		path = filepath.Clean(path)
 | 
			
		||||
 | 
			
		||||
		for i := range args {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,7 +77,7 @@ func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, er
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// Gets the path and initializes a buffer.
 | 
			
		||||
	path := c.us.scope + "/" + r.URL.Path
 | 
			
		||||
	path := c.us.Scope + "/" + r.URL.Path
 | 
			
		||||
	path = filepath.Clean(path)
 | 
			
		||||
	buff := new(bytes.Buffer)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,10 +12,10 @@ import (
 | 
			
		|||
	"github.com/mholt/archiver"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// serveDownload creates an archive in one of the supported formats (zip, tar,
 | 
			
		||||
// downloadHandler creates an archive in one of the supported formats (zip, tar,
 | 
			
		||||
// tar.gz or tar.bz2) and sends it to be downloaded.
 | 
			
		||||
func serveDownload(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	query := r.URL.Query().Get("download")
 | 
			
		||||
func downloadHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	query := r.URL.Query().Get("format")
 | 
			
		||||
 | 
			
		||||
	if !c.fi.IsDir {
 | 
			
		||||
		w.Header().Set("Content-Disposition", "attachment; filename="+c.fi.Name)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								file.go
								
								
								
								
							
							
						
						
									
										22
									
								
								file.go
								
								
								
								
							| 
						 | 
				
			
			@ -29,6 +29,8 @@ var (
 | 
			
		|||
 | 
			
		||||
// file contains the information about a particular file or directory.
 | 
			
		||||
type file struct {
 | 
			
		||||
	// Indicates the Kind of view on the front-end (listing, editor or preview).
 | 
			
		||||
	Kind string `json:"kind"`
 | 
			
		||||
	// The name of the file.
 | 
			
		||||
	Name string `json:"name"`
 | 
			
		||||
	// The Size of the file.
 | 
			
		||||
| 
						 | 
				
			
			@ -79,15 +81,13 @@ type listing struct {
 | 
			
		|||
func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	i := &file{URL: c.RootURL() + url.Path}
 | 
			
		||||
	i.VirtualPath = url.Path
 | 
			
		||||
	i.VirtualPath = strings.TrimPrefix(i.VirtualPath, "/")
 | 
			
		||||
	i.VirtualPath = "/" + i.VirtualPath
 | 
			
		||||
	i := &file{
 | 
			
		||||
		URL:         c.RootURL() + "/files" + url.Path,
 | 
			
		||||
		VirtualPath: url.Path,
 | 
			
		||||
		Path:        filepath.Join(u.Scope, url.Path),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i.Path = u.scope + i.VirtualPath
 | 
			
		||||
	i.Path = filepath.Clean(i.Path)
 | 
			
		||||
 | 
			
		||||
	info, err := os.Stat(i.Path)
 | 
			
		||||
	info, err := u.fileSystem.Stat(context.TODO(), i.Path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return i, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -103,8 +103,6 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
 | 
			
		|||
 | 
			
		||||
// getListing gets the information about a specific directory and its files.
 | 
			
		||||
func (i *file) getListing(c *requestContext, r *http.Request) error {
 | 
			
		||||
	baseURL := c.fm.RootURL() + r.URL.Path
 | 
			
		||||
 | 
			
		||||
	// Gets the directory information using the Virtual File System of
 | 
			
		||||
	// the user configuration.
 | 
			
		||||
	f, err := c.us.fileSystem.OpenFile(context.TODO(), c.fi.VirtualPath, os.O_RDONLY, 0)
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +138,7 @@ func (i *file) getListing(c *requestContext, r *http.Request) error {
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		// Absolute URL
 | 
			
		||||
		url := url.URL{Path: baseURL + name}
 | 
			
		||||
		url := url.URL{Path: i.URL + name}
 | 
			
		||||
 | 
			
		||||
		i := file{
 | 
			
		||||
			Name:    f.Name(),
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +163,7 @@ func (i *file) getListing(c *requestContext, r *http.Request) error {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// getEditor gets the editor based on a Info struct
 | 
			
		||||
func (i *file) getEditor(r *http.Request) error {
 | 
			
		||||
func (i *file) getEditor() error {
 | 
			
		||||
	i.Language = editorLanguage(i.Extension)
 | 
			
		||||
 | 
			
		||||
	// If the editor will hold only content, leave now.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,11 +29,6 @@ type FileManager struct {
 | 
			
		|||
	// a trailing slash and mustn't contain prefixURL, if set.
 | 
			
		||||
	baseURL string
 | 
			
		||||
 | 
			
		||||
	// webDavURL is the path where the WebDAV will be accessible. It can be set to ""
 | 
			
		||||
	// in order to override the GUI and only use the WebDAV. It musn't end with
 | 
			
		||||
	// a trailing slash.
 | 
			
		||||
	webDavURL string
 | 
			
		||||
 | 
			
		||||
	// Users is a map with the different configurations for each user.
 | 
			
		||||
	Users map[string]*User
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,8 +38,7 @@ type FileManager struct {
 | 
			
		|||
	// AfterSave is a function that is called before saving a file.
 | 
			
		||||
	AfterSave Command
 | 
			
		||||
 | 
			
		||||
	templates *rice.Box
 | 
			
		||||
	static    http.Handler
 | 
			
		||||
	assets *rice.Box
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Command is a command function.
 | 
			
		||||
| 
						 | 
				
			
			@ -53,15 +47,12 @@ type Command func(r *http.Request, m *FileManager, u *User) error
 | 
			
		|||
// User contains the configuration for each user. It should be created
 | 
			
		||||
// using NewUser on a File Manager instance.
 | 
			
		||||
type User struct {
 | 
			
		||||
	// scope is the physical path the user has access to.
 | 
			
		||||
	scope string
 | 
			
		||||
	// Scope is the physical path the user has access to.
 | 
			
		||||
	Scope string
 | 
			
		||||
 | 
			
		||||
	// fileSystem is the virtual file system the user has access.
 | 
			
		||||
	fileSystem webdav.FileSystem
 | 
			
		||||
 | 
			
		||||
	// handler handles incoming requests to the WebDAV backend.
 | 
			
		||||
	handler *webdav.Handler
 | 
			
		||||
 | 
			
		||||
	// Rules is an array of access and deny rules.
 | 
			
		||||
	Rules []*Rule `json:"-"`
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -106,13 +97,11 @@ func New(scope string) *FileManager {
 | 
			
		|||
		Users:      map[string]*User{},
 | 
			
		||||
		BeforeSave: func(r *http.Request, m *FileManager, u *User) error { return nil },
 | 
			
		||||
		AfterSave:  func(r *http.Request, m *FileManager, u *User) error { return nil },
 | 
			
		||||
		static:     http.FileServer(rice.MustFindBox("./_assets/dist_dev/_").HTTPBox()),
 | 
			
		||||
		templates:  rice.MustFindBox("./_assets/dist_dev/templates"),
 | 
			
		||||
		assets:     rice.MustFindBox("./_assets/dist"),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.SetScope(scope, "")
 | 
			
		||||
	m.SetBaseURL("/")
 | 
			
		||||
	m.SetWebDavURL("/webdav")
 | 
			
		||||
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -126,7 +115,7 @@ func (m FileManager) RootURL() string {
 | 
			
		|||
// WebDavURL returns the actual URL
 | 
			
		||||
// where WebDAV can be accessed.
 | 
			
		||||
func (m FileManager) WebDavURL() string {
 | 
			
		||||
	return m.prefixURL + m.baseURL + m.webDavURL
 | 
			
		||||
	return m.prefixURL + m.baseURL + "/api/webdav"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetPrefixURL updates the prefixURL of a File
 | 
			
		||||
| 
						 | 
				
			
			@ -147,32 +136,6 @@ func (m *FileManager) SetBaseURL(url string) {
 | 
			
		|||
	m.baseURL = strings.TrimSuffix(url, "/")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetWebDavURL updates the webDavURL of a File Manager
 | 
			
		||||
// object and updates it's main handler.
 | 
			
		||||
func (m *FileManager) SetWebDavURL(url string) {
 | 
			
		||||
	url = strings.TrimPrefix(url, "/")
 | 
			
		||||
	url = strings.TrimSuffix(url, "/")
 | 
			
		||||
 | 
			
		||||
	m.webDavURL = "/" + url
 | 
			
		||||
 | 
			
		||||
	// update base user webdav handler
 | 
			
		||||
	m.handler = &webdav.Handler{
 | 
			
		||||
		Prefix:     m.webDavURL,
 | 
			
		||||
		FileSystem: m.fileSystem,
 | 
			
		||||
		LockSystem: webdav.NewMemLS(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update other users' handlers to match
 | 
			
		||||
	// the new URL
 | 
			
		||||
	for _, u := range m.Users {
 | 
			
		||||
		u.handler = &webdav.Handler{
 | 
			
		||||
			Prefix:     m.webDavURL,
 | 
			
		||||
			FileSystem: u.fileSystem,
 | 
			
		||||
			LockSystem: webdav.NewMemLS(),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetScope updates a user scope and its virtual file system.
 | 
			
		||||
// If the user string is blank, it will change the base scope.
 | 
			
		||||
func (m *FileManager) SetScope(scope string, username string) error {
 | 
			
		||||
| 
						 | 
				
			
			@ -188,14 +151,8 @@ func (m *FileManager) SetScope(scope string, username string) error {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	u.scope = strings.TrimSuffix(scope, "/")
 | 
			
		||||
	u.fileSystem = webdav.Dir(u.scope)
 | 
			
		||||
 | 
			
		||||
	u.handler = &webdav.Handler{
 | 
			
		||||
		Prefix:     m.webDavURL,
 | 
			
		||||
		FileSystem: u.fileSystem,
 | 
			
		||||
		LockSystem: webdav.NewMemLS(),
 | 
			
		||||
	}
 | 
			
		||||
	u.Scope = strings.TrimSuffix(scope, "/")
 | 
			
		||||
	u.fileSystem = webdav.Dir(u.Scope)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -208,9 +165,8 @@ func (m *FileManager) NewUser(username string) error {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	m.Users[username] = &User{
 | 
			
		||||
		scope:         m.User.scope,
 | 
			
		||||
		fileSystem:    m.User.fileSystem,
 | 
			
		||||
		handler:       m.User.handler,
 | 
			
		||||
		Scope:         m.User.Scope,
 | 
			
		||||
		Rules:         m.User.Rules,
 | 
			
		||||
		AllowNew:      m.User.AllowNew,
 | 
			
		||||
		AllowEdit:     m.User.AllowEdit,
 | 
			
		||||
| 
						 | 
				
			
			@ -252,8 +208,3 @@ func (u User) Allowed(url string) bool {
 | 
			
		|||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scope returns the user scope.
 | 
			
		||||
func (u User) Scope() string {
 | 
			
		||||
	return u.scope
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										266
									
								
								http.go
								
								
								
								
							
							
						
						
									
										266
									
								
								http.go
								
								
								
								
							| 
						 | 
				
			
			@ -1,200 +1,98 @@
 | 
			
		|||
package filemanager
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// assetsURL is the url where static assets are served.
 | 
			
		||||
const assetsURL = "/_"
 | 
			
		||||
 | 
			
		||||
// requestContext contains the needed information to make handlers work.
 | 
			
		||||
type requestContext struct {
 | 
			
		||||
	us *User
 | 
			
		||||
	fm *FileManager
 | 
			
		||||
	fi *file
 | 
			
		||||
	pg *page
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// serveHTTP is the main entry point of this HTML application.
 | 
			
		||||
func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		code int
 | 
			
		||||
		err  error
 | 
			
		||||
	)
 | 
			
		||||
	// Checks if the URL contains the baseURL and strips it. Otherwise, it just
 | 
			
		||||
	// returns a 404 error because we're not supposed to be here!
 | 
			
		||||
	p := strings.TrimPrefix(r.URL.Path, c.fm.baseURL)
 | 
			
		||||
 | 
			
		||||
	// Checks if the URL contains the baseURL. If so, it strips it. Otherwise,
 | 
			
		||||
	// it throws an error.
 | 
			
		||||
	if p := strings.TrimPrefix(r.URL.Path, c.fm.baseURL); len(p) < len(r.URL.Path) || len(c.fm.baseURL) == 0 {
 | 
			
		||||
		r.URL.Path = p
 | 
			
		||||
	} else {
 | 
			
		||||
	if len(p) >= len(r.URL.Path) && c.fm.baseURL != "" {
 | 
			
		||||
		return http.StatusNotFound, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Checks if the URL matches the Assets URL. Returns the asset if the
 | 
			
		||||
	// method is GET and Status Forbidden otherwise.
 | 
			
		||||
	if matchURL(r.URL.Path, assetsURL+"/") {
 | 
			
		||||
		if r.Method == http.MethodGet {
 | 
			
		||||
			r.URL.Path = strings.TrimPrefix(r.URL.Path, assetsURL)
 | 
			
		||||
			c.fm.static.ServeHTTP(w, r)
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
	r.URL.Path = p
 | 
			
		||||
 | 
			
		||||
		return http.StatusForbidden, nil
 | 
			
		||||
	// Check if this request is made to the service worker. If so,
 | 
			
		||||
	// pass it through a template to add the needed variables.
 | 
			
		||||
	if r.URL.Path == "/sw.js" {
 | 
			
		||||
		return renderFile(
 | 
			
		||||
			w,
 | 
			
		||||
			c.fm.assets.MustString(r.URL.Path),
 | 
			
		||||
			"application/javascript",
 | 
			
		||||
			c.fm.RootURL(),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	username, _, _ := r.BasicAuth()
 | 
			
		||||
	if _, ok := c.fm.Users[username]; ok {
 | 
			
		||||
		c.us = c.fm.Users[username]
 | 
			
		||||
	} else {
 | 
			
		||||
		c.us = c.fm.User
 | 
			
		||||
	// Checks if this request is made to the static assets folder. If so, and
 | 
			
		||||
	// if it is a GET request, returns with the asset. Otherwise, returns
 | 
			
		||||
	// a status not implemented.
 | 
			
		||||
	if matchURL(r.URL.Path, "/static") {
 | 
			
		||||
		if r.Method != http.MethodGet {
 | 
			
		||||
			return http.StatusNotImplemented, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return staticHandler(c, w, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Checks if the request URL is for the WebDav server.
 | 
			
		||||
	if matchURL(r.URL.Path, c.fm.webDavURL) {
 | 
			
		||||
		return serveWebDAV(c, w, r)
 | 
			
		||||
	// Checks if this request is made to the API and directs to the
 | 
			
		||||
	// API handler if so.
 | 
			
		||||
	if matchURL(r.URL.Path, "/api") {
 | 
			
		||||
		r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
 | 
			
		||||
		return serveAPI(c, w, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("x-frame-options", "SAMEORIGIN")
 | 
			
		||||
	w.Header().Set("x-content-type", "nosniff")
 | 
			
		||||
	w.Header().Set("x-xss-protection", "1; mode=block")
 | 
			
		||||
	// Checks if this request is made to the base path /files. If so,
 | 
			
		||||
	// shows the index.html page.
 | 
			
		||||
	if matchURL(r.URL.Path, "/files") {
 | 
			
		||||
		w.Header().Set("x-frame-options", "SAMEORIGIN")
 | 
			
		||||
		w.Header().Set("x-content-type", "nosniff")
 | 
			
		||||
		w.Header().Set("x-xss-protection", "1; mode=block")
 | 
			
		||||
 | 
			
		||||
	// Checks if the User is allowed to access this file
 | 
			
		||||
	if !c.us.Allowed(r.URL.Path) {
 | 
			
		||||
		if r.Method == http.MethodGet {
 | 
			
		||||
			return htmlError(
 | 
			
		||||
				w, http.StatusForbidden,
 | 
			
		||||
				errors.New("You don't have permission to access this page"),
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return http.StatusForbidden, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r.URL.Query().Get("search") != "" {
 | 
			
		||||
		return search(c, w, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r.URL.Query().Get("command") != "" {
 | 
			
		||||
		return command(c, w, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r.Method == http.MethodGet {
 | 
			
		||||
		var f *file
 | 
			
		||||
 | 
			
		||||
		// Obtains the information of the directory/file.
 | 
			
		||||
		f, err = getInfo(r.URL, c.fm, c.us)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if r.Method == http.MethodGet {
 | 
			
		||||
				return htmlError(w, code, err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			code = errorToHTTP(err, false)
 | 
			
		||||
			return code, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c.fi = f
 | 
			
		||||
 | 
			
		||||
		// If it's a dir and the path doesn't end with a trailing slash,
 | 
			
		||||
		// redirect the user.
 | 
			
		||||
		if f.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
 | 
			
		||||
			http.Redirect(w, r, c.fm.RootURL()+r.URL.Path+"/", http.StatusTemporaryRedirect)
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch {
 | 
			
		||||
		case r.URL.Query().Get("download") != "":
 | 
			
		||||
			code, err = serveDownload(c, w, r)
 | 
			
		||||
		case !f.IsDir && r.URL.Query().Get("checksum") != "":
 | 
			
		||||
			code, err = serveChecksum(c, w, r)
 | 
			
		||||
		case r.URL.Query().Get("raw") == "true" && !f.IsDir:
 | 
			
		||||
			http.ServeFile(w, r, f.Path)
 | 
			
		||||
			code, err = 0, nil
 | 
			
		||||
		default:
 | 
			
		||||
			code, err = serveDefault(c, w, r)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			code, err = htmlError(w, code, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return code, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return http.StatusNotImplemented, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// serveWebDAV handles the webDAV route of the File Manager.
 | 
			
		||||
func serveWebDAV(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	// Checks for user permissions relatively to this path.
 | 
			
		||||
	if !c.us.Allowed(strings.TrimPrefix(r.URL.Path, c.fm.webDavURL)) {
 | 
			
		||||
		return http.StatusForbidden, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch r.Method {
 | 
			
		||||
	case "GET", "HEAD":
 | 
			
		||||
		// Excerpt from RFC4918, section 9.4:
 | 
			
		||||
		//
 | 
			
		||||
		// 		GET, when applied to a collection, may return the contents of an
 | 
			
		||||
		//		"index.html" resource, a human-readable view of the contents of
 | 
			
		||||
		//		the collection, or something else altogether.
 | 
			
		||||
		//
 | 
			
		||||
		// It was decided on https://github.com/hacdias/caddy-filemanager/issues/85
 | 
			
		||||
		// that GET, for collections, will return the same as PROPFIND method.
 | 
			
		||||
		path := strings.Replace(r.URL.Path, c.fm.webDavURL, "", 1)
 | 
			
		||||
		path = c.us.scope + "/" + path
 | 
			
		||||
		path = filepath.Clean(path)
 | 
			
		||||
 | 
			
		||||
		var i os.FileInfo
 | 
			
		||||
		i, err = os.Stat(path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// Is there any error? WebDav will handle it... no worries.
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if i.IsDir() {
 | 
			
		||||
			r.Method = "PROPFIND"
 | 
			
		||||
 | 
			
		||||
			if r.Method == "HEAD" {
 | 
			
		||||
				w = newResponseWriterNoBody(w)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case "PROPPATCH", "MOVE", "PATCH", "PUT", "DELETE":
 | 
			
		||||
		if !c.us.AllowEdit {
 | 
			
		||||
			return http.StatusForbidden, nil
 | 
			
		||||
		}
 | 
			
		||||
	case "MKCOL", "COPY":
 | 
			
		||||
		if !c.us.AllowNew {
 | 
			
		||||
			return http.StatusForbidden, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Execute beforeSave if it is a PUT request.
 | 
			
		||||
	if r.Method == http.MethodPut {
 | 
			
		||||
		if err = c.fm.BeforeSave(r, c.fm, c.us); err != nil {
 | 
			
		||||
			return http.StatusInternalServerError, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.fm.handler.ServeHTTP(w, r)
 | 
			
		||||
 | 
			
		||||
	// Execute afterSave if it is a PUT request.
 | 
			
		||||
	if r.Method == http.MethodPut {
 | 
			
		||||
		if err = c.fm.AfterSave(r, c.fm, c.us); err != nil {
 | 
			
		||||
			return http.StatusInternalServerError, err
 | 
			
		||||
		}
 | 
			
		||||
		return renderFile(
 | 
			
		||||
			w,
 | 
			
		||||
			c.fm.assets.MustString("index.html"),
 | 
			
		||||
			"text/html",
 | 
			
		||||
			c.fm.RootURL(),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.Redirect(w, r, c.fm.RootURL()+"/files"+r.URL.Path, http.StatusTemporaryRedirect)
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// staticHandler handles the static assets path.
 | 
			
		||||
func staticHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	if r.URL.Path != "/static/manifest.json" {
 | 
			
		||||
		http.FileServer(c.fm.assets.HTTPBox()).ServeHTTP(w, r)
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return renderFile(
 | 
			
		||||
		w,
 | 
			
		||||
		c.fm.assets.MustString(r.URL.Path),
 | 
			
		||||
		"application/json",
 | 
			
		||||
		c.fm.RootURL(),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
 | 
			
		||||
func serveChecksum(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	query := r.URL.Query().Get("checksum")
 | 
			
		||||
func checksumHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	query := r.URL.Query().Get("algo")
 | 
			
		||||
 | 
			
		||||
	val, err := c.fi.Checksum(query)
 | 
			
		||||
	if err == errInvalidOption {
 | 
			
		||||
| 
						 | 
				
			
			@ -207,30 +105,32 @@ func serveChecksum(c *requestContext, w http.ResponseWriter, r *http.Request) (i
 | 
			
		|||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// responseWriterNoBody is a wrapper used to suprress the body of the response
 | 
			
		||||
// to a request. Mainly used for HEAD requests.
 | 
			
		||||
type responseWriterNoBody struct {
 | 
			
		||||
	http.ResponseWriter
 | 
			
		||||
}
 | 
			
		||||
// renderFile renders a file using a template with some needed variables.
 | 
			
		||||
func renderFile(w http.ResponseWriter, file string, contentType string, baseURL string) (int, error) {
 | 
			
		||||
	tpl := template.Must(template.New("file").Parse(file))
 | 
			
		||||
	w.Header().Set("Content-Type", contentType+"; charset=utf-8")
 | 
			
		||||
 | 
			
		||||
// newResponseWriterNoBody creates a new responseWriterNoBody.
 | 
			
		||||
func newResponseWriterNoBody(w http.ResponseWriter) *responseWriterNoBody {
 | 
			
		||||
	return &responseWriterNoBody{w}
 | 
			
		||||
}
 | 
			
		||||
	err := tpl.Execute(w, map[string]string{"BaseURL": baseURL})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
// Header executes the Header method from the http.ResponseWriter.
 | 
			
		||||
func (w responseWriterNoBody) Header() http.Header {
 | 
			
		||||
	return w.ResponseWriter.Header()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write suprresses the body.
 | 
			
		||||
func (w responseWriterNoBody) Write(data []byte) (int, error) {
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WriteHeader writes the header to the http.ResponseWriter.
 | 
			
		||||
func (w responseWriterNoBody) WriteHeader(statusCode int) {
 | 
			
		||||
	w.ResponseWriter.WriteHeader(statusCode)
 | 
			
		||||
// renderJSON prints the JSON version of data to the browser.
 | 
			
		||||
func renderJSON(w http.ResponseWriter, data interface{}) (int, error) {
 | 
			
		||||
	marsh, err := json.Marshal(data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
			
		||||
	if _, err := w.Write(marsh); err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// matchURL checks if the first URL matches the second.
 | 
			
		||||
| 
						 | 
				
			
			@ -244,6 +144,8 @@ func matchURL(first, second string) bool {
 | 
			
		|||
// errorToHTTP converts errors to HTTP Status Code.
 | 
			
		||||
func errorToHTTP(err error, gone bool) int {
 | 
			
		||||
	switch {
 | 
			
		||||
	case err == nil:
 | 
			
		||||
		return http.StatusOK
 | 
			
		||||
	case os.IsPermission(err):
 | 
			
		||||
		return http.StatusForbidden
 | 
			
		||||
	case os.IsNotExist(err):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										195
									
								
								page.go
								
								
								
								
							
							
						
						
									
										195
									
								
								page.go
								
								
								
								
							| 
						 | 
				
			
			@ -1,195 +0,0 @@
 | 
			
		|||
package filemanager
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// functions contains the non-standard functions that are available
 | 
			
		||||
// to use on the HTML templates.
 | 
			
		||||
var functions = template.FuncMap{
 | 
			
		||||
	"CSS": func(s string) template.CSS {
 | 
			
		||||
		return template.CSS(s)
 | 
			
		||||
	},
 | 
			
		||||
	"Marshal": func(v interface{}) template.JS {
 | 
			
		||||
		a, _ := json.Marshal(v)
 | 
			
		||||
		return template.JS(a)
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// page contains the information needed to fill a page template.
 | 
			
		||||
type page struct {
 | 
			
		||||
	User      *User  `json:"-"`
 | 
			
		||||
	BaseURL   string `json:"-"`
 | 
			
		||||
	WebDavURL string `json:"-"`
 | 
			
		||||
	Kind      string `json:"kind"`
 | 
			
		||||
	Data      *file  `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
// breadcrumbItem contains the Name and the URL of a breadcrumb piece.
 | 
			
		||||
type breadcrumbItem struct {
 | 
			
		||||
	Name string
 | 
			
		||||
	URL  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BreadcrumbMap returns p.Path where every element is a map
 | 
			
		||||
// of URLs and path segment names.
 | 
			
		||||
func (p page) BreadcrumbMap() []breadcrumbItem {
 | 
			
		||||
	// TODO: when it is preview alongside with listing!!!!!!!!!!
 | 
			
		||||
	result := []breadcrumbItem{}
 | 
			
		||||
 | 
			
		||||
	if len(p.Path) == 0 {
 | 
			
		||||
		return result
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// skip trailing slash
 | 
			
		||||
	lpath := p.Path
 | 
			
		||||
	if lpath[len(lpath)-1] == '/' {
 | 
			
		||||
		lpath = lpath[:len(lpath)-1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parts := strings.Split(lpath, "/")
 | 
			
		||||
	for i, part := range parts {
 | 
			
		||||
		if i == len(parts)-1 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if i == 0 && part == "" {
 | 
			
		||||
			result = append([]breadcrumbItem{{
 | 
			
		||||
				Name: "/",
 | 
			
		||||
				URL:  "/",
 | 
			
		||||
			}}, result...)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		result = append([]breadcrumbItem{{
 | 
			
		||||
			Name: part,
 | 
			
		||||
			URL:  strings.Join(parts[:i+1], "/") + "/",
 | 
			
		||||
		}}, result...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PreviousLink returns the URL of the previous folder.
 | 
			
		||||
func (p page) PreviousLink() string {
 | 
			
		||||
	path := strings.TrimSuffix(p.Path, "/")
 | 
			
		||||
	path = strings.TrimPrefix(path, "/")
 | 
			
		||||
	path = p.BaseURL + "/" + path
 | 
			
		||||
	path = path[0 : len(path)-len(p.Name)]
 | 
			
		||||
 | 
			
		||||
	if len(path) < len(p.BaseURL+"/") {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return path
 | 
			
		||||
} */
 | 
			
		||||
 | 
			
		||||
func (p page) Render(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	if strings.Contains(r.Header.Get("Accept"), "application/json") {
 | 
			
		||||
		marsh, err := json.MarshalIndent(p, "", "    ")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return http.StatusInternalServerError, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
 | 
			
		||||
		if _, err := w.Write(marsh); err != nil {
 | 
			
		||||
			return http.StatusInternalServerError, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tpl *template.Template
 | 
			
		||||
 | 
			
		||||
	// Get the template from the assets
 | 
			
		||||
	file, err := c.fm.templates.String("index.html")
 | 
			
		||||
 | 
			
		||||
	// Check if there is some error. If so, the template doesn't exist
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tpl, err = template.New("index").Funcs(functions).Parse(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := &bytes.Buffer{}
 | 
			
		||||
	err = tpl.Execute(buf, p)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
 | 
			
		||||
	_, err = buf.WriteTo(w)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// htmlError prints the error page
 | 
			
		||||
func htmlError(w http.ResponseWriter, code int, err error) (int, error) {
 | 
			
		||||
	tpl := errTemplate
 | 
			
		||||
	tpl = strings.Replace(tpl, "TITLE", strconv.Itoa(code)+" "+http.StatusText(code), -1)
 | 
			
		||||
	tpl = strings.Replace(tpl, "CODE", err.Error(), -1)
 | 
			
		||||
 | 
			
		||||
	_, err = w.Write([]byte(tpl))
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusInternalServerError, err
 | 
			
		||||
	}
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const errTemplate = `<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <title>TITLE</title>
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <style>
 | 
			
		||||
    html {
 | 
			
		||||
        background-color: #2196f3;
 | 
			
		||||
        color: #fff;
 | 
			
		||||
        font-family: sans-serif;
 | 
			
		||||
    }
 | 
			
		||||
    code {
 | 
			
		||||
        background-color: rgba(0,0,0,0.1);
 | 
			
		||||
        border-radius: 5px;
 | 
			
		||||
        padding: 1em;
 | 
			
		||||
        display: block;
 | 
			
		||||
        box-sizing: border-box;
 | 
			
		||||
    }
 | 
			
		||||
    .center {
 | 
			
		||||
        max-width: 40em;
 | 
			
		||||
        margin: 2em auto 0;
 | 
			
		||||
    }
 | 
			
		||||
    a {
 | 
			
		||||
        text-decoration: none;
 | 
			
		||||
        color: #eee;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
    }
 | 
			
		||||
	p {
 | 
			
		||||
		line-height: 1.3;
 | 
			
		||||
	}
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div class="center">
 | 
			
		||||
        <h1>TITLE</h1>
 | 
			
		||||
 | 
			
		||||
        <p>Try reloading the page or hitting the back button. If this error persists, it seems that you may have found a bug! Please create an issue at <a href="https://github.com/hacdias/caddy-filemanager/issues">hacdias/caddy-filemanager</a> repository on GitHub with the code below.</p>
 | 
			
		||||
 | 
			
		||||
        <code>CODE</code>
 | 
			
		||||
    </div>
 | 
			
		||||
</html>`
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +73,7 @@ func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, err
 | 
			
		|||
	search = parseSearch(value)
 | 
			
		||||
	scope := strings.TrimPrefix(r.URL.Path, "/")
 | 
			
		||||
	scope = "/" + scope
 | 
			
		||||
	scope = c.us.scope + scope
 | 
			
		||||
	scope = c.us.Scope + scope
 | 
			
		||||
	scope = strings.Replace(scope, "\\", "/", -1)
 | 
			
		||||
	scope = filepath.Clean(scope)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										142
									
								
								serve.go
								
								
								
								
							
							
						
						
									
										142
									
								
								serve.go
								
								
								
								
							| 
						 | 
				
			
			@ -1,142 +0,0 @@
 | 
			
		|||
package filemanager
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func serveDefault(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	// Starts building the page.
 | 
			
		||||
	c.pg = &page{
 | 
			
		||||
		User:      c.us,
 | 
			
		||||
		BaseURL:   c.fm.RootURL(),
 | 
			
		||||
		WebDavURL: c.fm.WebDavURL(),
 | 
			
		||||
		Data:      c.fi,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If it is a dir, go and serve the listing.
 | 
			
		||||
	if c.fi.IsDir {
 | 
			
		||||
		return serveListing(c, w, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Tries to get the file type.
 | 
			
		||||
	if err = c.fi.RetrieveFileType(); err != nil {
 | 
			
		||||
		return errorToHTTP(err, true), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If it can't be edited or the user isn't allowed to,
 | 
			
		||||
	// serve it as a listing, with a preview of the file.
 | 
			
		||||
	if !c.fi.CanBeEdited() || !c.us.AllowEdit {
 | 
			
		||||
		c.pg.Kind = "preview"
 | 
			
		||||
	} else {
 | 
			
		||||
		// Otherwise, we just bring the editor in!
 | 
			
		||||
		c.pg.Kind = "editor"
 | 
			
		||||
 | 
			
		||||
		err = c.fi.getEditor(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return http.StatusInternalServerError, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return c.pg.Render(c, w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// serveListing presents the user with a listage of a directory folder.
 | 
			
		||||
func serveListing(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	c.pg.Kind = "listing"
 | 
			
		||||
 | 
			
		||||
	err = c.fi.getListing(c, r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errorToHTTP(err, true), err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	listing := c.fi.listing
 | 
			
		||||
 | 
			
		||||
	cookieScope := c.fm.RootURL()
 | 
			
		||||
	if cookieScope == "" {
 | 
			
		||||
		cookieScope = "/"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Copy the query values into the Listing struct
 | 
			
		||||
	listing.Sort, listing.Order, err = handleSortOrder(w, r, cookieScope)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return http.StatusBadRequest, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	listing.ApplySort()
 | 
			
		||||
 | 
			
		||||
	listing.Display = displayMode(w, r, cookieScope)
 | 
			
		||||
	return c.pg.Render(c, w, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// displayMode obtaisn the display mode from URL, or from the
 | 
			
		||||
// cookie.
 | 
			
		||||
func displayMode(w http.ResponseWriter, r *http.Request, scope string) string {
 | 
			
		||||
	displayMode := r.URL.Query().Get("display")
 | 
			
		||||
 | 
			
		||||
	if displayMode == "" {
 | 
			
		||||
		if displayCookie, err := r.Cookie("display"); err == nil {
 | 
			
		||||
			displayMode = displayCookie.Value
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if displayMode == "" || (displayMode != "mosaic" && displayMode != "list") {
 | 
			
		||||
		displayMode = "mosaic"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.SetCookie(w, &http.Cookie{
 | 
			
		||||
		Name:   "display",
 | 
			
		||||
		Value:  displayMode,
 | 
			
		||||
		MaxAge: 31536000,
 | 
			
		||||
		Path:   scope,
 | 
			
		||||
		Secure: r.TLS != nil,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return displayMode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
 | 
			
		||||
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
 | 
			
		||||
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {
 | 
			
		||||
	sort = r.URL.Query().Get("sort")
 | 
			
		||||
	order = r.URL.Query().Get("order")
 | 
			
		||||
 | 
			
		||||
	// If the query 'sort' or 'order' is empty, use defaults or any values
 | 
			
		||||
	// previously saved in Cookies.
 | 
			
		||||
	switch sort {
 | 
			
		||||
	case "":
 | 
			
		||||
		sort = "name"
 | 
			
		||||
		if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
 | 
			
		||||
			sort = sortCookie.Value
 | 
			
		||||
		}
 | 
			
		||||
	case "name", "size", "type":
 | 
			
		||||
		http.SetCookie(w, &http.Cookie{
 | 
			
		||||
			Name:   "sort",
 | 
			
		||||
			Value:  sort,
 | 
			
		||||
			MaxAge: 31536000,
 | 
			
		||||
			Path:   scope,
 | 
			
		||||
			Secure: r.TLS != nil,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch order {
 | 
			
		||||
	case "":
 | 
			
		||||
		order = "asc"
 | 
			
		||||
		if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
 | 
			
		||||
			order = orderCookie.Value
 | 
			
		||||
		}
 | 
			
		||||
	case "asc", "desc":
 | 
			
		||||
		http.SetCookie(w, &http.Cookie{
 | 
			
		||||
			Name:   "order",
 | 
			
		||||
			Value:  order,
 | 
			
		||||
			MaxAge: 31536000,
 | 
			
		||||
			Path:   scope,
 | 
			
		||||
			Secure: r.TLS != nil,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue