feature (orgmode): use emacs to export org documents

This commit is contained in:
Mickael KERJEAN
2019-01-08 23:51:11 +11:00
parent f87a58c0ba
commit 43f00e12d6
20 changed files with 328 additions and 58 deletions

View File

@ -45,6 +45,9 @@ export class Dropdown extends React.Component {
}
toggleDropdown(e){
if(this.props.enable === false){
return
}
document.body.removeEventListener("click", this.closeDropdown);
this.setState({button: !this.state.button}, () => {
if(this.state.button === true){

View File

@ -4,7 +4,7 @@ export { debounce, throttle } from './backpressure';
export { encrypt, decrypt, bcrypt_password } from './crypto';
export { event } from './events';
export { cache } from './cache';
export { pathBuilder, basename, dirname, absoluteToRelative, filetype, currentShare, appendShareToUrl } from './path';
export { pathBuilder, basename, dirname, absoluteToRelative, filetype, currentShare, findParams, appendShareToUrl } from './path';
export { memory } from './memory';
export { prepare } from './navigate';
export { invalidate, http_get, http_post, http_delete } from './ajax';

View File

@ -37,7 +37,11 @@ export function absoluteToRelative(from, to){
}
export function currentShare(){
return new window.URL(location.href).searchParams.get("share") || ""
return findParams("share");
}
export function findParams(p){
return new window.URL(location.href).searchParams.get(p) || ""
}
export function appendShareToUrl(link) {

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Card, NgIf, Icon, EventEmitter, EventReceiver, Dropdown, DropdownButton, DropdownList, DropdownItem } from '../../components/';
import { Card, NgIf, Icon, EventEmitter, EventReceiver } from '../../components/';
import { pathBuilder, debounce } from '../../helpers/';
import "./thing.scss";

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Redirect } from 'react-router';
import { Share } from '../model/';
import { notify, basename, filetype } from '../helpers/';
import { notify, basename, filetype, findParams } from '../helpers/';
import { Loader, Input, Button, Container, ErrorPage, Icon, NgIf } from '../components/';
import './error.scss';
import './sharepage.scss';
@ -67,7 +67,20 @@ export class SharePage extends React.Component {
let className = this.state.error ? "error rand-"+Math.random().toString() : "";
if(this.state.path !== null){
if(filetype(this.state.path) === "directory"){
if(!!findParams("next")){
const url = findParams("next");
if(url[0] === "/"){
requestAnimationFrame(() => {
window.location.pathname = url;
});
return (
<div style={marginTop()}>
<Loader />
</div>
);
}
notify.send("You can't do that :)", "error");
}else if(filetype(this.state.path) === "directory"){
return ( <Redirect to={`/files/?share=${this.state.share}`} /> );
}else{
return ( <Redirect to={`/view/${basename(this.state.path)}?nav=false&share=${this.state.share}`} /> );

View File

@ -133,6 +133,7 @@ export class ViewerPage extends React.Component {
onSave={this.save.bind(this)}
content={this.state.content || ""}
url={this.state.url}
path={this.state.path}
filename={this.state.filename}/>
</NgIf>
<NgIf cond={this.state.opener === 'image'}>

View File

@ -4,8 +4,8 @@ import { withRouter } from 'react-router';
import { Prompt } from "react-router-dom";
import { Subject } from 'rxjs/Subject';
import { NgIf, Fab, Icon } from '../../components/';
import { confirm } from '../../helpers/';
import { NgIf, Fab, Icon, Dropdown, DropdownButton, DropdownList, DropdownItem } from '../../components/';
import { confirm, currentShare } from '../../helpers/';
import { Editor } from './editor';
import { MenuBar } from './menubar';
import { OrgTodosViewer, OrgEventsViewer } from './org_viewer';
@ -48,6 +48,7 @@ export class IDE extends React.Component {
}
componentWillUnmount(){
this.unblock();
window.clearInterval(this.state.id);
}
save(){
@ -70,7 +71,6 @@ export class IDE extends React.Component {
});
}
/* Org Viewer specific stuff */
toggleAgenda(force = null){
this.setState({appear_agenda: force === null ? !this.state.appear_agenda : !!force});
@ -85,10 +85,25 @@ export class IDE extends React.Component {
this.state.event.next(["goTo", lineNumber]);
}
download(){
document.cookie = "download=yes; path=/; max-age=120;";
this.setState({random: Math.random()});
this.state.id = window.setInterval(() => {
if(/download=yes/.test(document.cookie) === false){
window.clearInterval(this.state.id);
this.setState({random: Math.random()});
}
}, 100);
}
render(){
const changeExt = function(filename, ext){
return filename.replace(/\.org$/, "."+ext);
};
return (
<div className="component_ide">
<MenuBar title={this.props.filename} download={this.props.url}>
<MenuBar title={this.props.filename} download={this.state.mode === 'orgmode' ? null : this.props.url}>
<NgIf type="inline" cond={this.state.mode === 'orgmode'}>
<span onClick={this.onModeChange.bind(this)}>
<NgIf cond={this.state.folding === "SHOW_ALL"} type="inline">
@ -101,6 +116,22 @@ export class IDE extends React.Component {
<Icon name="arrow_down_double"/>
</NgIf>
</span>
<Dropdown className="view sort" onChange={() => this.download()} enable={/download=yes/.test(document.cookie) ? false : true}>
<DropdownButton>
<Icon name={/download=yes/.test(document.cookie) ? "loading_white" : "download_white"}/>
</DropdownButton>
<DropdownList>
<DropdownItem name="na"><a download={this.props.filename} href={this.props.url}>Save current file</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "me")+"/text/html"+this.props.path}>Export as HTML</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "me")+"/application/pdf"+this.props.path}>Export as PDF</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "me")+"/text/plain"+this.props.path}>Export as Text</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "tex")} href={"/api/export/"+(currentShare() || "me")+"/text/x-latex"+this.props.path}>Export as Latex</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "ics")} href={"/api/export/"+(currentShare() || "me")+"/text/calendar"+this.props.path}>Export as Calendar</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "pdf")} href={"/api/export/"+(currentShare() || "me")+"/application/pdf"+this.props.path+"?mode=beamer"}>Export as Beamer</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} download={changeExt(this.props.filename, "odt")} href={"/api/export/"+(currentShare() || "me")+"/application/vnd.oasis.opendocument.text"+this.props.path}>Export as Open office</a></DropdownItem>
<DropdownItem name="na"><a target={this.props.needSaving ? "_blank" : "_self"} href={"/api/export/"+(currentShare() || "me")+"/text/markdown"+this.props.path}>Export as Markdown</a></DropdownItem>
</DropdownList>
</Dropdown>
<span onClick={this.toggleAgenda.bind(this)}>
<Icon name="calendar_white"/>

View File

@ -9,6 +9,28 @@
// https://stackoverflow.com/questions/44948158/flexbox-overflow-issue-in-firefox
min-height: 0;
}
.component_menubar{
.component_dropdown{
float: right;
.dropdown_button{
border: none;
padding: 0; margin: 0;
}
&.active .dropdown_button{
box-shadow: none;
}
.dropdown_container ul li > div{
padding: 0;
a {
padding: 7px 5px 7px 10px;
display: inline-block;
width: 100%;
box-sizing: border-box;
}
}
}
}
}

View File

@ -16,7 +16,7 @@ export const MenuBar = (props) => {
<span className="specific">
{props.children}
</span>
<DownloadButton link={props.download} name={props.title} />
{ props.download === null ? null : <DownloadButton link={props.download} name={props.title} /> }
</div>
</ReactCSSTransitionGroup>
</Container>

12
config/emacs.el Normal file
View File

@ -0,0 +1,12 @@
;; this is the config that's loaded by emacs when using the org mode export
;; org mode keywords
(setq org-todo-keywords (quote ((sequence "TODO(t)" "DOING(d)" "WAITING(w)" "|" "CANCEL(C)" "DEFERRED(F)" "DONE(D)"))))
;; html export
(setq org-html-head "<meta http-equiv='X-UA-Compatible' content='IE=edge'><meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'><style>html{touch-action:manipulation;-webkit-text-size-adjust:100%}body{padding:0;margin:0;background:#f2f6fa;color:#3c495a;font-weight:normal;font-size:16px;font-family:'avenir next','avenir','San Francisco','Roboto','Arial',sans-serif}h2,h3,h4,h5,h6{font-family:'Trebuchet MS',Verdana,sans-serif;color:#586b82;padding:0;margin:20px 0 10px 0;font-size:1.2em}h2{margin:30px 0 20px 0;font-size:1.5em}h2:after{display:block;content:' ';width:60px;border-bottom:3px solid #586b82;margin-top:5px;}li{text-align:left;}a{color:#3fa7ba;text-decoration:none}p{margin:10px 0;text-align:justify}ul,ol{margin:0;text-align:justify}#content ul,#content ol{margin-top:-5px;}#content ul>li>ul, #content ol>li>ol{margin-top:0;} ul>li>code{color:#586b82}pre{white-space:pre-wrap}pre.src{padding:10px}#content{width:96%;max-width:950px;margin:2% auto 5% auto;background:white;border-radius:2px;border-right:1px solid #e2e9f0;border-bottom:2px solid #e2e9f0;padding:0 115px 150px 115px;box-sizing:border-box}.org-src-container{margin-top:50px}#postamble{opacity:0.5;padding-bottom:10px;}#postamble .author{display:none}#postamble p{text-align:center;}h1.title{background-color:#343C44;color:#fff;margin:0 -115px 50px -115px;padding:60px 0;font-weight:normal;font-size:2em;border-top-left-radius:2px;border-top-right-radius:2px}@media (max-width: 1050px){#content{padding:0 70px 100px 70px}h1.title{margin:0 -70px 50px -70px}}@media (max-width: 800px){#content{width:100%;margin-top:0;padding:0 4% 60px 4%}h1.title{margin:0 -5% 50px -5%;padding:40px 5%}}pre,.verse{box-shadow:none;background-color:#f9fbfd;border:1px solid #e2e9f0;color:#586b82;padding:10px;font-family:monospace;overflow:auto;margin:6px 0}#table-of-contents{margin-bottom:50px;margin-top:50px}#table-of-contents h2{margin-bottom:15px}#text-table-of-contents ul{padding-left:15px}#text-table-of-contents>ul{padding-left:0}#text-table-of-contents li{list-style-type:none}#text-table-of-contents a{color:#7c8ca1;font-size:0.95em;text-decoration:none}table{border-color:#586b82;font-size:0.95em}table thead{color:#586b82}table tbody tr:nth-child(even){background:#f9f9f9}table tbody tr:hover{background:#586b82!important;color:white}table .left{text-align:left}table .right{text-align:right}.todo{font-family:inherit;color:inherit}.done{color:inherit}.tag{background:initial}.tag>span{background-color:#eee;font-family:monospace;padding-left:7px;padding-right:7px;border-radius:2px;float:right;margin-left:5px;font-size:14px;padding-bottom:2px;}#text-table-of-contents .tag>span{float:none;margin-left:0}.timestamp{color:#7c8ca1}@media print{@page{margin-bottom:3cm;margin-top:3cm;margin-left:2cm;margin-right:2cm;font-size:10px}#content{border:none}}</style>")
(setq org-html-validation-link nil)
(setq org-html-creator-string "Using <a href=\"http://filestash.app\">Filestash</a>")
(setq org-export-use-babel nil)
(setq org-confirm-babel-evaluate nil)

View File

@ -90,6 +90,7 @@
"ogg": "audio/ogg",
"ogv": "application/ogg",
"orf": "image/x-olympus-orf",
"org": "text/org",
"pdb": "application/x-pilot",
"pdf": "application/pdf",
"pef": "image/x-pentax-pef",

View File

@ -5,6 +5,7 @@ const (
CONFIG_PATH = "data/config/"
PLUGIN_PATH = "data/plugin/"
LOG_PATH = "data/log/"
TMP_PATH = "data/tmp/"
COOKIE_NAME_AUTH = "auth"
COOKIE_NAME_PROOF = "proof"
COOKIE_NAME_ADMIN = "admin"

View File

@ -15,6 +15,9 @@ var (
ErrNotValid error = NewError("Not Valid", 405)
ErrNotReachable error = NewError("Cannot Reach Destination", 502)
ErrInvalidPassword = NewError("Invalid Password", 403)
ErrNotImplemented = NewError("Not Implemented", 501)
ErrFilesystemError = NewError("Can't use filesystem", 503)
ErrMissingDependency = NewError("Missing dependency", 424)
)
type AppError struct {

149
server/ctrl/export.go Normal file
View File

@ -0,0 +1,149 @@
package ctrl
import (
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/mickael-kerjean/filestash/server/model"
"github.com/gorilla/mux"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
)
var EXPORT_PATH string
func init() {
EXPORT_PATH = GetAbsolutePath(TMP_PATH)
os.RemoveAll(EXPORT_PATH)
os.MkdirAll(EXPORT_PATH, os.ModePerm)
}
func FileExport(ctx App, res http.ResponseWriter, req *http.Request) {
query := req.URL.Query()
p := mux.Vars(req)
mimeType := fmt.Sprintf("%s/%s", p["mtype0"], p["mtype1"])
path, err := pathBuilder(ctx, strings.Replace(req.URL.Path, fmt.Sprintf("/api/export/%s/%s/%s", p["share"], p["mtype0"], p["mtype1"]), "", 1))
if err != nil {
SendErrorResult(res, err)
return
} else if model.CanRead(&ctx) == false {
SendErrorResult(res, ErrPermissionDenied)
return
}
http.SetCookie(res, &http.Cookie{
Name: "download",
Value: "",
MaxAge: -1,
Path: "/",
})
var tmpPath string = EXPORT_PATH + "/export_" + QuickString(10)
var cmd *exec.Cmd
var emacsPath string
var outPath string
if GetMimeType(path) == "text/org" {
if emacsPath, err = exec.LookPath("emacs"); err != nil {
SendErrorResult(res, ErrMissingDependency)
return
}
if runtime.GOOS == "darwin" {
// on OSX, the default emacs isn't usable so we default to the one provided by `brew`
if f, err := os.OpenFile("/usr/local/Cellar/emacs/", os.O_RDONLY, os.ModePerm); err == nil {
if dirs, err := f.Readdirnames(0); err == nil {
if len(dirs) > 0 {
emacsPath = "/usr/local/Cellar/emacs/" + dirs[0] + "/bin/emacs"
}
}
}
}
if mimeType == "text/html" {
cmd = exec.Command(
emacsPath, "--no-init-file", "--batch",
"--load", GetAbsolutePath(CONFIG_PATH + "emacs.el"),
"--eval", "(setq org-html-extension \"org\")",
tmpPath + "/index.org", "-f", "org-html-export-to-html",
)
outPath = "index.org.org"
} else if mimeType == "application/pdf" {
cmd = exec.Command(
emacsPath, "--no-init-file", "--batch",
tmpPath + "/index.org", "-f", "org-latex-export-to-pdf",
)
if query.Get("mode") == "beamer" {
cmd = exec.Command(
emacsPath, "--no-init-file", "--batch",
tmpPath + "/index.org", "-f", "org-beamer-export-to-pdf",
)
}
outPath = "index.pdf"
} else if mimeType == "text/calendar" {
cmd = exec.Command(
emacsPath, "--no-init-file", "--batch",
tmpPath + "/index.org", "-f", "org-icalendar-export-to-ics",
)
outPath = "index.ics"
} else if mimeType == "text/plain" {
cmd = exec.Command(
emacsPath, "--no-init-file", "--batch",
tmpPath + "/index.org", "-f", "org-ascii-export-to-ascii",
)
outPath = "index.txt"
} else if mimeType == "text/x-latex" {
cmd = exec.Command(
emacsPath, "--no-init-file", "--batch",
tmpPath + "/index.org", "-f", "org-latex-export-to-latex",
)
outPath = "index.tex"
} else if mimeType == "text/markdown" {
cmd = exec.Command(
emacsPath, "--no-init-file", "--batch",
tmpPath + "/index.org", "-f", "org-md-export-to-markdown",
)
outPath = "index.md"
} else if mimeType == "application/vnd.oasis.opendocument.text" {
cmd = exec.Command(
emacsPath, "--no-init-file", "--batch",
tmpPath + "/index.org", "-f", "org-odt-export-to-odt",
)
outPath = "index.odt"
}else {
SendErrorResult(res, ErrNotImplemented)
return
}
os.MkdirAll(tmpPath, os.ModePerm)
defer os.RemoveAll(tmpPath)
f, err := os.OpenFile(tmpPath + "/index.org", os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
SendErrorResult(res, ErrFilesystemError)
return
}
file, err := ctx.Backend.Cat(path)
if err != nil {
SendErrorResult(res, err)
return
}
io.Copy(f, file)
// TODO: insert related resources: eg: images
if err = cmd.Run(); err != nil {
SendErrorResult(res, NewError("emacs has quit with error: '%s'" + err.Error(), 400))
return
}
f, err = os.OpenFile(tmpPath + "/"+outPath, os.O_RDONLY, os.ModePerm)
if err != nil {
SendErrorResult(res, ErrFilesystemError)
return
}
res.Header().Set("Content-Type", mimeType)
io.Copy(res, f)
return
}
SendErrorResult(res, ErrNotImplemented)
return
}

View File

@ -106,7 +106,7 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) {
Path: "/",
})
if model.CanRead(&ctx) == false {
SendErrorResult(res, NewError("Permission denied", 403))
SendErrorResult(res, ErrPermissionDenied)
return
}

View File

@ -151,7 +151,7 @@ func ShareVerifyProof(ctx App, res http.ResponseWriter, req *http.Request) {
Key: fmt.Sprint(ctx.Body["type"]),
Value: fmt.Sprint(ctx.Body["value"]),
}
verifiedProof = model.ShareProofGetAlreadyVerified(req, &ctx)
verifiedProof = model.ShareProofGetAlreadyVerified(req)
requiredProof = model.ShareProofGetRequired(s)
// 2) validate the current context

View File

@ -66,21 +66,27 @@ func Init(a *App) {
// API for File management
files := r.PathPrefix("/api/files").Subrouter()
middlewares = []Middleware{ ApiHeaders, SecureHeaders, SessionStart, LoggedInOnly }
files.HandleFunc("/ls", NewMiddlewareChain(FileLs, middlewares, *a)).Methods("GET")
files.HandleFunc("/cat", NewMiddlewareChain(FileCat, middlewares, *a)).Methods("GET")
files.HandleFunc("/cat", NewMiddlewareChain(FileSave, middlewares, *a)).Methods("POST")
files.HandleFunc("/mv", NewMiddlewareChain(FileMv, middlewares, *a)).Methods("GET")
files.HandleFunc("/rm", NewMiddlewareChain(FileRm, middlewares, *a)).Methods("GET")
files.HandleFunc("/mkdir", NewMiddlewareChain(FileMkdir, middlewares, *a)).Methods("GET")
files.HandleFunc("/touch", NewMiddlewareChain(FileTouch, middlewares, *a)).Methods("GET")
files.HandleFunc("/ls", NewMiddlewareChain(FileLs, middlewares, *a)).Methods("GET")
files.HandleFunc("/cat", NewMiddlewareChain(FileCat, middlewares, *a)).Methods("GET")
files.HandleFunc("/cat", NewMiddlewareChain(FileSave, middlewares, *a)).Methods("POST")
files.HandleFunc("/mv", NewMiddlewareChain(FileMv, middlewares, *a)).Methods("GET")
files.HandleFunc("/rm", NewMiddlewareChain(FileRm, middlewares, *a)).Methods("GET")
files.HandleFunc("/mkdir", NewMiddlewareChain(FileMkdir, middlewares, *a)).Methods("GET")
files.HandleFunc("/touch", NewMiddlewareChain(FileTouch, middlewares, *a)).Methods("GET")
// API for exporter
middlewares = []Middleware{ ApiHeaders, SecureHeaders, RedirectSharedLoginIfNeeded, SessionStart, LoggedInOnly }
r.PathPrefix("/api/export/{share}/{mtype0}/{mtype1}").Handler(NewMiddlewareChain(FileExport, middlewares, *a))
// API for Shared link
share := r.PathPrefix("/api/share").Subrouter()
middlewares = []Middleware{ ApiHeaders, SecureHeaders, SessionStart, LoggedInOnly }
share.HandleFunc("", NewMiddlewareChain(ShareList, middlewares, *a)).Methods("GET")
share.HandleFunc("/{share}", NewMiddlewareChain(ShareDelete, middlewares, *a)).Methods("DELETE")
middlewares = []Middleware{ ApiHeaders, SecureHeaders, SessionStart, BodyParser, LoggedInOnly }
middlewares = []Middleware{ ApiHeaders, SecureHeaders, SessionStart, LoggedInOnly, BodyParser }
share.HandleFunc("/{share}", NewMiddlewareChain(ShareUpsert, middlewares, *a)).Methods("POST")
middlewares = []Middleware{ ApiHeaders, SecureHeaders, BodyParser }
share.HandleFunc("/{share}/proof", NewMiddlewareChain(ShareVerifyProof, middlewares, *a)).Methods("POST")
// Webdav server / Shared Link

View File

@ -2,6 +2,7 @@ package middleware
import (
"encoding/json"
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/mickael-kerjean/filestash/server/model"
"github.com/gorilla/mux"
@ -46,45 +47,18 @@ func AdminOnly(fn func(App, http.ResponseWriter, *http.Request)) func(ctx App, r
}
func SessionStart (fn func(App, http.ResponseWriter, *http.Request)) func(ctx App, res http.ResponseWriter, req *http.Request) {
extractShare := func(req *http.Request, ctx *App, share_id string) (Share, error) {
if share_id == "" {
return Share{}, nil
}
if Config.Get("features.share.enable").Bool() == false {
Log.Debug("Share feature isn't enable, contact your administrator")
return Share{}, NewError("Feature isn't enable, contact your administrator", 405)
}
s, err := model.ShareGet(share_id)
if err != nil {
return Share{}, nil
}
if err = s.IsValid(); err != nil {
return Share{}, err
}
return s, nil
}
extractSession := func(req *http.Request, ctx *App) (map[string]string, error) {
var str string
var err error
var res map[string]string = make(map[string]string)
if ctx.Share.Id != "" {
var verifiedProof []model.Proof = model.ShareProofGetAlreadyVerified(req, ctx)
var requiredProof []model.Proof = model.ShareProofGetRequired(ctx.Share)
var remainingProof []model.Proof = model.ShareProofCalculateRemainings(requiredProof, verifiedProof)
if len(remainingProof) != 0 {
return res, NewError("Unauthorized Shared space", 400)
}
str = ctx.Share.Auth
str, err = DecryptString(SECRET_KEY, str)
str, err = DecryptString(SECRET_KEY, ctx.Share.Auth)
if err != nil {
// This typically happen when changing the secret key
return res, nil
}
err = json.Unmarshal([]byte(str), &res)
if ctx.Share.Path[len(ctx.Share.Path)-1:] == "/" {
res["path"] = ctx.Share.Path
} else {
@ -116,14 +90,7 @@ func SessionStart (fn func(App, http.ResponseWriter, *http.Request)) func(ctx Ap
return func(ctx App, res http.ResponseWriter, req *http.Request) {
var err error
share_id := func() string {
if len(req.URL.Path) > 3 && req.URL.Path[:3] == "/s/" {
// this runs while using a link as a webdav server
return mux.Vars(req)["share"]
}
return req.URL.Query().Get("share")
}()
if ctx.Share, err = extractShare(req, &ctx, share_id); err != nil {
if ctx.Share, err = _findShare(req, _extractShareId(req)); err != nil {
SendErrorResult(res, err)
return
}
@ -138,3 +105,60 @@ func SessionStart (fn func(App, http.ResponseWriter, *http.Request)) func(ctx Ap
fn(ctx, res, req)
}
}
func RedirectSharedLoginIfNeeded(fn func(App, http.ResponseWriter, *http.Request)) func(ctx App, res http.ResponseWriter, req *http.Request) {
return func(ctx App, res http.ResponseWriter, req *http.Request) {
share_id := _extractShareId(req)
if share_id == "" {
SendErrorResult(res, ErrNotValid)
return
}
share, err := _findShare(req, share_id);
if err != nil || share_id != share.Id {
http.Redirect(res, req, fmt.Sprintf("/s/%s?next=%s", share_id, req.URL.Path), http.StatusTemporaryRedirect)
return
}
fn(ctx, res, req)
}
}
func _extractShareId(req *http.Request) string {
share := req.URL.Query().Get("share")
if share != "" {
return share
}
m := mux.Vars(req)["share"]
if m == "me" {
return ""
}
return m
}
func _findShare(req *http.Request, share_id string) (Share, error) {
var err error
if share_id == "" {
return Share{}, nil
}
if Config.Get("features.share.enable").Bool() == false {
Log.Debug("Share feature isn't enable, contact your administrator")
return Share{}, NewError("Feature isn't enable, contact your administrator", 405)
}
s, err := model.ShareGet(share_id)
if err != nil {
return Share{}, nil
}
if err = s.IsValid(); err != nil {
return Share{}, err
}
var verifiedProof []model.Proof = model.ShareProofGetAlreadyVerified(req)
var requiredProof []model.Proof = model.ShareProofGetRequired(s)
var remainingProof []model.Proof = model.ShareProofCalculateRemainings(requiredProof, verifiedProof)
if len(remainingProof) != 0 {
return Share{}, NewError("Unauthorized Shared space", 400)
}
return s, nil
}

View File

@ -249,7 +249,7 @@ func ShareProofVerifier(ctx *App, s Share, proof Proof) (Proof, error) {
return p, nil
}
func ShareProofGetAlreadyVerified(req *http.Request, ctx *App) []Proof {
func ShareProofGetAlreadyVerified(req *http.Request) []Proof {
var p []Proof
var cookieValue string

View File

@ -165,7 +165,7 @@ func Init(conf *Configuration) {
// => lower RAM usage while processing
file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm)
if err != nil {
return reader, NewError("Can't use filesystem", 500)
return reader, ErrFilesystemError
}
io.Copy(file, reader)
file.Close()