Compare commits

...

5 Commits

Author SHA1 Message Date
Alan Castro 20d27653ad
Merge 764147c443 into be62f56782 2024-05-03 20:01:43 +02:00
古大羊 be62f56782
feat: Enhance MIME Type Detection for Additional File Extensions (#3183) 2024-05-03 19:59:45 +02:00
Alan Castro 764147c443 fix: update frontend/src/i18n/pl.json
Co-authored-by: Radosław Gryta <radek.gryta@gmail.com>
2024-04-27 02:14:04 +00:00
Alan Castro d294cacbe4 feat: only-office integration pt 2, performed server-side signing, addressed other comments 2024-04-27 02:14:04 +00:00
DrosoCode 08f37b90ce feat: onlyoffice editor 2024-04-27 02:14:04 +00:00
42 changed files with 1044 additions and 34 deletions

View File

@ -48,6 +48,9 @@ func addConfigFlags(flags *pflag.FlagSet) {
flags.String("branding.files", "", "path to directory with images and custom styles")
flags.Bool("branding.disableExternal", false, "disable external links such as GitHub links")
flags.Bool("branding.disableUsedPercentage", false, "disable used disk percentage graph")
flags.String("onlyoffice.url", "", "onlyoffice integration url")
flags.String("onlyoffice.jwtSecret", "", "onlyoffice integration secret")
}
//nolint:gocyclo

View File

@ -42,6 +42,10 @@ override the options.`,
Theme: mustGetString(flags, "branding.theme"),
Files: mustGetString(flags, "branding.files"),
},
OnlyOffice: settings.OnlyOffice{
URL: mustGetString(flags, "onlyoffice.url"),
JWTSecret: mustGetString(flags, "onlyoffice.jwtSecret"),
},
}
ser := &settings.Server{

View File

@ -63,6 +63,10 @@ you want to change. Other options will remain unchanged.`,
set.Branding.DisableUsedPercentage = mustGetBool(flags, flag.Name)
case "branding.files":
set.Branding.Files = mustGetString(flags, flag.Name)
case "onlyoffice.url":
set.OnlyOffice.URL = mustGetString(flags, flag.Name)
case "onlyoffice.jwtSecret":
set.OnlyOffice.JWTSecret = mustGetString(flags, flag.Name)
}
})

View File

@ -205,19 +205,10 @@ func (i *FileInfo) Checksum(algo string) error {
}
func (i *FileInfo) RealPath() string {
if realPathFs, ok := i.Fs.(interface {
RealPath(name string) (fPath string, err error)
}); ok {
realPath, err := realPathFs.RealPath(i.Path)
if err == nil {
return realPath
}
}
return i.Path
return GetRealPath(i.Fs, i.Path)
}
//nolint:goconst
//nolint:goconst,gocyclo
func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
if IsNamedPipe(i.Mode) {
i.Type = "blob"
@ -276,6 +267,10 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error {
i.Content = string(content)
}
return nil
case strings.HasPrefix(mimetype, "application/vnd.openxmlformats-officedocument"),
strings.HasPrefix(mimetype, "application/vnd.oasis.opendocument"):
i.Type = "officedocument"
return nil
default:
i.Type = "blob"
}

608
files/mime.go Normal file
View File

@ -0,0 +1,608 @@
package files
// This file contains code primarily sourced from::
// github.com/kataras/iris
import (
"mime"
)
const (
// ContentBinaryHeaderValue header value for binary data.
ContentBinaryHeaderValue = "application/octet-stream"
// ContentWebassemblyHeaderValue header value for web assembly files.
ContentWebassemblyHeaderValue = "application/wasm"
// ContentHTMLHeaderValue is the string of text/html response header's content type value.
ContentHTMLHeaderValue = "text/html"
// ContentJSONHeaderValue header value for JSON data.
ContentJSONHeaderValue = "application/json"
// ContentJSONProblemHeaderValue header value for JSON API problem error.
// Read more at: https://tools.ietf.org/html/rfc7807
ContentJSONProblemHeaderValue = "application/problem+json"
// ContentXMLProblemHeaderValue header value for XML API problem error.
// Read more at: https://tools.ietf.org/html/rfc7807
ContentXMLProblemHeaderValue = "application/problem+xml"
// ContentJavascriptHeaderValue header value for JSONP & Javascript data.
ContentJavascriptHeaderValue = "text/javascript"
// ContentTextHeaderValue header value for Text data.
ContentTextHeaderValue = "text/plain"
// ContentXMLHeaderValue header value for XML data.
ContentXMLHeaderValue = "text/xml"
// ContentXMLUnreadableHeaderValue obselete header value for XML.
ContentXMLUnreadableHeaderValue = "application/xml"
// ContentMarkdownHeaderValue custom key/content type, the real is the text/html.
ContentMarkdownHeaderValue = "text/markdown"
// ContentYAMLHeaderValue header value for YAML data.
ContentYAMLHeaderValue = "application/x-yaml"
// ContentYAMLTextHeaderValue header value for YAML plain text.
ContentYAMLTextHeaderValue = "text/yaml"
// ContentProtobufHeaderValue header value for Protobuf messages data.
ContentProtobufHeaderValue = "application/x-protobuf"
// ContentMsgPackHeaderValue header value for MsgPack data.
ContentMsgPackHeaderValue = "application/msgpack"
// ContentMsgPack2HeaderValue alternative header value for MsgPack data.
ContentMsgPack2HeaderValue = "application/x-msgpack"
// ContentFormHeaderValue header value for post form data.
ContentFormHeaderValue = "application/x-www-form-urlencoded"
// ContentFormMultipartHeaderValue header value for post multipart form data.
ContentFormMultipartHeaderValue = "multipart/form-data"
// ContentMultipartRelatedHeaderValue header value for multipart related data.
ContentMultipartRelatedHeaderValue = "multipart/related"
// ContentGRPCHeaderValue Content-Type header value for gRPC.
ContentGRPCHeaderValue = "application/grpc"
)
var types = map[string]string{
".3dm": "x-world/x-3dmf",
".3dmf": "x-world/x-3dmf",
".7z": "application/x-7z-compressed",
".a": "application/octet-stream",
".aab": "application/x-authorware-bin",
".aam": "application/x-authorware-map",
".aas": "application/x-authorware-seg",
".abc": "text/vndabc",
".ace": "application/x-ace-compressed",
".acgi": "text/html",
".afl": "video/animaflex",
".ai": "application/postscript",
".aif": "audio/aiff",
".aifc": "audio/aiff",
".aiff": "audio/aiff",
".aim": "application/x-aim",
".aip": "text/x-audiosoft-intra",
".alz": "application/x-alz-compressed",
".ani": "application/x-navi-animation",
".aos": "application/x-nokia-9000-communicator-add-on-software",
".aps": "application/mime",
".apk": "application/vnd.android.package-archive",
".arc": "application/x-arc-compressed",
".arj": "application/arj",
".art": "image/x-jg",
".asf": "video/x-ms-asf",
".asm": "text/x-asm",
".asp": "text/asp",
".asx": "application/x-mplayer2",
".au": "audio/basic",
".avi": "video/x-msvideo",
".avs": "video/avs-video",
".bcpio": "application/x-bcpio",
".bin": "application/mac-binary",
".bmp": "image/bmp",
".boo": "application/book",
".book": "application/book",
".boz": "application/x-bzip2",
".bsh": "application/x-bsh",
".bz2": "application/x-bzip2",
".bz": "application/x-bzip",
".c++": ContentTextHeaderValue,
".c": "text/x-c",
".cab": "application/vnd.ms-cab-compressed",
".cat": "application/vndms-pkiseccat",
".cc": "text/x-c",
".ccad": "application/clariscad",
".cco": "application/x-cocoa",
".cdf": "application/cdf",
".cer": "application/pkix-cert",
".cha": "application/x-chat",
".chat": "application/x-chat",
".chrt": "application/vnd.kde.kchart",
".class": "application/java",
".com": ContentTextHeaderValue,
".conf": ContentTextHeaderValue,
".cpio": "application/x-cpio",
".cpp": "text/x-c",
".cpt": "application/mac-compactpro",
".crl": "application/pkcs-crl",
".crt": "application/pkix-cert",
".crx": "application/x-chrome-extension",
".csh": "text/x-scriptcsh",
".css": "text/css",
".csv": "text/csv",
".cxx": ContentTextHeaderValue,
".dar": "application/x-dar",
".dcr": "application/x-director",
".deb": "application/x-debian-package",
".deepv": "application/x-deepv",
".def": ContentTextHeaderValue,
".der": "application/x-x509-ca-cert",
".dif": "video/x-dv",
".dir": "application/x-director",
".divx": "video/divx",
".dl": "video/dl",
".dmg": "application/x-apple-diskimage",
".doc": "application/msword",
".dot": "application/msword",
".dp": "application/commonground",
".drw": "application/drafting",
".dump": "application/octet-stream",
".dv": "video/x-dv",
".dvi": "application/x-dvi",
".dwf": "drawing/x-dwf=(old)",
".dwg": "application/acad",
".dxf": "application/dxf",
".dxr": "application/x-director",
".el": "text/x-scriptelisp",
".elc": "application/x-bytecodeelisp=(compiled=elisp)",
".eml": "message/rfc822",
".env": "application/x-envoy",
".eps": "application/postscript",
".es": "application/x-esrehber",
".etx": "text/x-setext",
".evy": "application/envoy",
".exe": "application/octet-stream",
".f77": "text/x-fortran",
".f90": "text/x-fortran",
".f": "text/x-fortran",
".fdf": "application/vndfdf",
".fif": "application/fractals",
".fli": "video/fli",
".flo": "image/florian",
".flv": "video/x-flv",
".flx": "text/vndfmiflexstor",
".fmf": "video/x-atomic3d-feature",
".for": "text/x-fortran",
".fpx": "image/vndfpx",
".frl": "application/freeloader",
".funk": "audio/make",
".g3": "image/g3fax",
".g": ContentTextHeaderValue,
".gif": "image/gif",
".gl": "video/gl",
".gsd": "audio/x-gsm",
".gsm": "audio/x-gsm",
".gsp": "application/x-gsp",
".gss": "application/x-gss",
".gtar": "application/x-gtar",
".gz": "application/x-compressed",
".gzip": "application/x-gzip",
".h": "text/x-h",
".hdf": "application/x-hdf",
".help": "application/x-helpfile",
".hgl": "application/vndhp-hpgl",
".hh": "text/x-h",
".hlb": "text/x-script",
".hlp": "application/hlp",
".hpg": "application/vndhp-hpgl",
".hpgl": "application/vndhp-hpgl",
".hqx": "application/binhex",
".hta": "application/hta",
".htc": "text/x-component",
".htm": "text/html",
".html": "text/html",
".htmls": "text/html",
".htt": "text/webviewhtml",
".htx": "text/html",
".ice": "x-conference/x-cooltalk",
".ico": "image/x-icon",
".ics": "text/calendar",
".icz": "text/calendar",
".idc": ContentTextHeaderValue,
".ief": "image/ief",
".iefs": "image/ief",
".iges": "application/iges",
".igs": "application/iges",
".ima": "application/x-ima",
".imap": "application/x-httpd-imap",
".inf": "application/inf",
".ins": "application/x-internett-signup",
".ip": "application/x-ip2",
".isu": "video/x-isvideo",
".it": "audio/it",
".iv": "application/x-inventor",
".ivr": "i-world/i-vrml",
".ivy": "application/x-livescreen",
".jam": "audio/x-jam",
".jav": "text/x-java-source",
".java": "text/x-java-source",
".jcm": "application/x-java-commerce",
".jfif-tbnl": "image/jpeg",
".jfif": "image/jpeg",
".jnlp": "application/x-java-jnlp-file",
".jpe": "image/jpeg",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".jps": "image/x-jps",
".js": ContentJavascriptHeaderValue,
".mjs": ContentJavascriptHeaderValue,
".json": ContentJSONHeaderValue,
".vue": ContentJavascriptHeaderValue,
".jut": "image/jutvision",
".kar": "audio/midi",
".karbon": "application/vnd.kde.karbon",
".kfo": "application/vnd.kde.kformula",
".flw": "application/vnd.kde.kivio",
".kml": "application/vnd.google-earth.kml+xml",
".kmz": "application/vnd.google-earth.kmz",
".kon": "application/vnd.kde.kontour",
".kpr": "application/vnd.kde.kpresenter",
".kpt": "application/vnd.kde.kpresenter",
".ksp": "application/vnd.kde.kspread",
".kwd": "application/vnd.kde.kword",
".kwt": "application/vnd.kde.kword",
".ksh": "text/x-scriptksh",
".la": "audio/nspaudio",
".lam": "audio/x-liveaudio",
".latex": "application/x-latex",
".lha": "application/lha",
".lhx": "application/octet-stream",
".list": ContentTextHeaderValue,
".lma": "audio/nspaudio",
".log": ContentTextHeaderValue,
".lsp": "text/x-scriptlisp",
".lst": ContentTextHeaderValue,
".lsx": "text/x-la-asf",
".ltx": "application/x-latex",
".lzh": "application/octet-stream",
".lzx": "application/lzx",
".m1v": "video/mpeg",
".m2a": "audio/mpeg",
".m2v": "video/mpeg",
".m3u": "audio/x-mpegurl",
".m": "text/x-m",
".man": "application/x-troff-man",
".manifest": "text/cache-manifest",
".map": "application/x-navimap",
".mar": ContentTextHeaderValue,
".mbd": "application/mbedlet",
".mc$": "application/x-magic-cap-package-10",
".mcd": "application/mcad",
".mcf": "text/mcf",
".mcp": "application/netmc",
".me": "application/x-troff-me",
".mht": "message/rfc822",
".mhtml": "message/rfc822",
".mid": "application/x-midi",
".midi": "application/x-midi",
".mif": "application/x-frame",
".mime": "message/rfc822",
".mjf": "audio/x-vndaudioexplosionmjuicemediafile",
".mjpg": "video/x-motion-jpeg",
".mm": "application/base64",
".mme": "application/base64",
".mod": "audio/mod",
".moov": "video/quicktime",
".mov": "video/quicktime",
".movie": "video/x-sgi-movie",
".mp2": "audio/mpeg",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".mpa": "audio/mpeg",
".mpc": "application/x-project",
".mpe": "video/mpeg",
".mpeg": "video/mpeg",
".mpg": "video/mpeg",
".mpga": "audio/mpeg",
".mpp": "application/vndms-project",
".mpt": "application/x-project",
".mpv": "application/x-project",
".mpx": "application/x-project",
".mrc": "application/marc",
".ms": "application/x-troff-ms",
".mv": "video/x-sgi-movie",
".my": "audio/make",
".mzz": "application/x-vndaudioexplosionmzz",
".nap": "image/naplps",
".naplps": "image/naplps",
".nc": "application/x-netcdf",
".ncm": "application/vndnokiaconfiguration-message",
".nif": "image/x-niff",
".niff": "image/x-niff",
".nix": "application/x-mix-transfer",
".nsc": "application/x-conference",
".nvd": "application/x-navidoc",
".o": "application/octet-stream",
".oda": "application/oda",
".odb": "application/vnd.oasis.opendocument.database",
".odc": "application/vnd.oasis.opendocument.chart",
".odf": "application/vnd.oasis.opendocument.formula",
".odg": "application/vnd.oasis.opendocument.graphics",
".odi": "application/vnd.oasis.opendocument.image",
".odm": "application/vnd.oasis.opendocument.text-master",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".oga": "audio/ogg",
".ogg": "audio/ogg",
".ogv": "video/ogg",
".omc": "application/x-omc",
".omcd": "application/x-omcdatamaker",
".omcr": "application/x-omcregerator",
".otc": "application/vnd.oasis.opendocument.chart-template",
".otf": "application/vnd.oasis.opendocument.formula-template",
".otg": "application/vnd.oasis.opendocument.graphics-template",
".oth": "application/vnd.oasis.opendocument.text-web",
".oti": "application/vnd.oasis.opendocument.image-template",
".otm": "application/vnd.oasis.opendocument.text-master",
".otp": "application/vnd.oasis.opendocument.presentation-template",
".ots": "application/vnd.oasis.opendocument.spreadsheet-template",
".ott": "application/vnd.oasis.opendocument.text-template",
".p10": "application/pkcs10",
".p12": "application/pkcs-12",
".p7a": "application/x-pkcs7-signature",
".p7c": "application/pkcs7-mime",
".p7m": "application/pkcs7-mime",
".p7r": "application/x-pkcs7-certreqresp",
".p7s": "application/pkcs7-signature",
".p": "text/x-pascal",
".part": "application/pro_eng",
".pas": "text/pascal",
".pbm": "image/x-portable-bitmap",
".pcl": "application/vndhp-pcl",
".pct": "image/x-pict",
".pcx": "image/x-pcx",
".pdb": "chemical/x-pdb",
".pdf": "application/pdf",
".pfunk": "audio/make",
".pgm": "image/x-portable-graymap",
".pic": "image/pict",
".pict": "image/pict",
".pkg": "application/x-newton-compatible-pkg",
".pko": "application/vndms-pkipko",
".pl": "text/x-scriptperl",
".plx": "application/x-pixclscript",
".pm4": "application/x-pagemaker",
".pm5": "application/x-pagemaker",
".pm": "text/x-scriptperl-module",
".png": "image/png",
".pnm": "application/x-portable-anymap",
".pot": "application/mspowerpoint",
".pov": "model/x-pov",
".ppa": "application/vndms-powerpoint",
".ppm": "image/x-portable-pixmap",
".pps": "application/mspowerpoint",
".ppt": "application/mspowerpoint",
".ppz": "application/mspowerpoint",
".pre": "application/x-freelance",
".prt": "application/pro_eng",
".ps": "application/postscript",
".psd": "application/octet-stream",
".pvu": "paleovu/x-pv",
".pwz": "application/vndms-powerpoint",
".py": "text/x-scriptphyton",
".pyc": "application/x-bytecodepython",
".qcp": "audio/vndqcelp",
".qd3": "x-world/x-3dmf",
".qd3d": "x-world/x-3dmf",
".qif": "image/x-quicktime",
".qt": "video/quicktime",
".qtc": "video/x-qtc",
".qti": "image/x-quicktime",
".qtif": "image/x-quicktime",
".ra": "audio/x-pn-realaudio",
".ram": "audio/x-pn-realaudio",
".rar": "application/x-rar-compressed",
".ras": "application/x-cmu-raster",
".rast": "image/cmu-raster",
".rexx": "text/x-scriptrexx",
".rf": "image/vndrn-realflash",
".rgb": "image/x-rgb",
".rm": "application/vndrn-realmedia",
".rmi": "audio/mid",
".rmm": "audio/x-pn-realaudio",
".rmp": "audio/x-pn-realaudio",
".rng": "application/ringing-tones",
".rnx": "application/vndrn-realplayer",
".roff": "application/x-troff",
".rp": "image/vndrn-realpix",
".rpm": "audio/x-pn-realaudio-plugin",
".rt": "text/vndrn-realtext",
".rtf": "text/richtext",
".rtx": "text/richtext",
".rv": "video/vndrn-realvideo",
".s": "text/x-asm",
".s3m": "audio/s3m",
".s7z": "application/x-7z-compressed",
".saveme": "application/octet-stream",
".sbk": "application/x-tbook",
".scm": "text/x-scriptscheme",
".sdml": ContentTextHeaderValue,
".sdp": "application/sdp",
".sdr": "application/sounder",
".sea": "application/sea",
".set": "application/set",
".sgm": "text/x-sgml",
".sgml": "text/x-sgml",
".sh": "text/x-scriptsh",
".shar": "application/x-bsh",
".shtml": "text/x-server-parsed-html",
".sid": "audio/x-psid",
".skd": "application/x-koan",
".skm": "application/x-koan",
".skp": "application/x-koan",
".skt": "application/x-koan",
".sit": "application/x-stuffit",
".sitx": "application/x-stuffitx",
".sl": "application/x-seelogo",
".smi": "application/smil",
".smil": "application/smil",
".snd": "audio/basic",
".sol": "application/solids",
".spc": "text/x-speech",
".spl": "application/futuresplash",
".spr": "application/x-sprite",
".sprite": "application/x-sprite",
".spx": "audio/ogg",
".src": "application/x-wais-source",
".ssi": "text/x-server-parsed-html",
".ssm": "application/streamingmedia",
".sst": "application/vndms-pkicertstore",
".step": "application/step",
".stl": "application/sla",
".stp": "application/step",
".sv4cpio": "application/x-sv4cpio",
".sv4crc": "application/x-sv4crc",
".svf": "image/vnddwg",
".svg": "image/svg+xml",
".svr": "application/x-world",
".swf": "application/x-shockwave-flash",
".t": "application/x-troff",
".talk": "text/x-speech",
".tar": "application/x-tar",
".tbk": "application/toolbook",
".tcl": "text/x-scripttcl",
".tcsh": "text/x-scripttcsh",
".tex": "application/x-tex",
".texi": "application/x-texinfo",
".texinfo": "application/x-texinfo",
".text": ContentTextHeaderValue,
".tgz": "application/gnutar",
".tif": "image/tiff",
".tiff": "image/tiff",
".tr": "application/x-troff",
".tsi": "audio/tsp-audio",
".tsp": "application/dsptype",
".tsv": "text/tab-separated-values",
".turbot": "image/florian",
".txt": ContentTextHeaderValue,
".uil": "text/x-uil",
".uni": "text/uri-list",
".unis": "text/uri-list",
".unv": "application/i-deas",
".uri": "text/uri-list",
".uris": "text/uri-list",
".ustar": "application/x-ustar",
".uu": "text/x-uuencode",
".uue": "text/x-uuencode",
".vcd": "application/x-cdlink",
".vcf": "text/x-vcard",
".vcard": "text/x-vcard",
".vcs": "text/x-vcalendar",
".vda": "application/vda",
".vdo": "video/vdo",
".vew": "application/groupwise",
".viv": "video/vivo",
".vivo": "video/vivo",
".vmd": "application/vocaltec-media-desc",
".vmf": "application/vocaltec-media-file",
".voc": "audio/voc",
".vos": "video/vosaic",
".vox": "audio/voxware",
".vqe": "audio/x-twinvq-plugin",
".vqf": "audio/x-twinvq",
".vql": "audio/x-twinvq-plugin",
".vrml": "application/x-vrml",
".vrt": "x-world/x-vrt",
".vsd": "application/x-visio",
".vst": "application/x-visio",
".vsw": "application/x-visio",
".w60": "application/wordperfect60",
".w61": "application/wordperfect61",
".w6w": "application/msword",
".wav": "audio/wav",
".wb1": "application/x-qpro",
".wbmp": "image/vnd.wap.wbmp",
".web": "application/vndxara",
".wiz": "application/msword",
".wk1": "application/x-123",
".wmf": "windows/metafile",
".wml": "text/vnd.wap.wml",
".wmlc": "application/vnd.wap.wmlc",
".wmls": "text/vnd.wap.wmlscript",
".wmlsc": "application/vnd.wap.wmlscriptc",
".word": "application/msword",
".wp5": "application/wordperfect",
".wp6": "application/wordperfect",
".wp": "application/wordperfect",
".wpd": "application/wordperfect",
".wq1": "application/x-lotus",
".wri": "application/mswrite",
".wrl": "application/x-world",
".wrz": "model/vrml",
".wsc": "text/scriplet",
".wsrc": "application/x-wais-source",
".wtk": "application/x-wintalk",
".x-png": "image/png",
".xbm": "image/x-xbitmap",
".xdr": "video/x-amt-demorun",
".xgz": "xgl/drawing",
".xif": "image/vndxiff",
".xl": "application/excel",
".xla": "application/excel",
".xlb": "application/excel",
".xlc": "application/excel",
".xld": "application/excel",
".xlk": "application/excel",
".xll": "application/excel",
".xlm": "application/excel",
".xls": "application/excel",
".xlt": "application/excel",
".xlv": "application/excel",
".xlw": "application/excel",
".xm": "audio/xm",
".xml": ContentXMLHeaderValue,
".xmz": "xgl/movie",
".xpix": "application/x-vndls-xpix",
".xpm": "image/x-xpixmap",
".xsr": "video/x-amt-showrun",
".xwd": "image/x-xwd",
".xyz": "chemical/x-pdb",
".z": "application/x-compress",
".zip": "application/zip",
".zoo": "application/octet-stream",
".zsh": "text/x-scriptzsh",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".docm": "application/vnd.ms-word.document.macroEnabled.12",
".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
".dotm": "application/vnd.ms-word.template.macroEnabled.12",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
".xltm": "application/vnd.ms-excel.template.macroEnabled.12",
".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12",
".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12",
".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12",
".thmx": "application/vnd.ms-officetheme",
".onetoc": "application/onenote",
".onetoc2": "application/onenote",
".onetmp": "application/onenote",
".onepkg": "application/onenote",
".xpi": "application/x-xpinstall",
".wasm": "application/wasm",
".m4a": "audio/mp4",
".flac": "audio/x-flac",
".amr": "audio/amr",
".aac": "audio/aac",
".opus": "video/ogg",
".m4v": "video/mp4",
".mkv": "video/x-matroska",
".caf": "audio/x-caf",
".m3u8": "application/x-mpegURL",
".mpd": "application/dash+xml",
".webp": "image/webp",
}
//nolint:gochecknoinits
func init() {
for ext, typ := range types {
// skip errors
_ = mime.AddExtensionType(ext, typ)
}
}

View File

@ -3,6 +3,8 @@ package files
import (
"os"
"unicode/utf8"
"github.com/spf13/afero"
)
func isBinary(content []byte) bool {
@ -57,3 +59,16 @@ func IsNamedPipe(mode os.FileMode) bool {
func IsSymlink(mode os.FileMode) bool {
return mode&os.ModeSymlink != 0
}
func GetRealPath(fs afero.Fs, path string) string {
if realPathFs, ok := fs.(interface {
RealPath(name string) (fPath string, err error)
}); ok {
realPath, err := realPathFs.RealPath(path)
if err == nil {
return realPath
}
}
return path
}

View File

@ -9,6 +9,7 @@
"version": "3.0.0",
"dependencies": {
"@chenfengyuan/vue-number-input": "^2.0.1",
"@onlyoffice/document-editor-vue": "^1.4.0",
"@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0",
"ace-builds": "^1.32.9",
@ -2506,6 +2507,14 @@
"node": ">= 8"
}
},
"node_modules/@onlyoffice/document-editor-vue": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@onlyoffice/document-editor-vue/-/document-editor-vue-1.4.0.tgz",
"integrity": "sha512-Fg5gSc1zF6bmpRapUd7rMpm7kEDF7mQIHQKfcsfJcILdFX9bwIhnkXEucETEA9zdt92nWMS6qiAgVeT61TdCyw==",
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/@pkgr/core": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",

View File

@ -19,6 +19,7 @@
},
"dependencies": {
"@chenfengyuan/vue-number-input": "^2.0.1",
"@onlyoffice/document-editor-vue": "^1.4.0",
"@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0",
"ace-builds": "^1.32.9",

View File

@ -195,6 +195,8 @@
"newPassword": "كلمة المرور الجديدة",
"newPasswordConfirm": "تأكيد كلمة المرور",
"newUser": "مستخدم جديد",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "كلمة المرور",
"passwordUpdated": "تم تغيير كلمة المرور!",
"path": "المسار",

View File

@ -182,6 +182,8 @@
"newPassword": "Ihr neues Passwort.",
"newPasswordConfirm": "Bestätigen Sie Ihr neues Passwort",
"newUser": "Neuer Benutzer",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Passwort",
"passwordUpdated": "Passwort aktualisiert!",
"path": "Pfad",
@ -247,4 +249,4 @@
"seconds": "Sekunden",
"unit": "Zeiteinheit"
}
}
}

View File

@ -195,6 +195,9 @@
"newPassword": "Your new password",
"newPasswordConfirm": "Confirm your new password",
"newUser": "New User",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"onlyOfficeJwtSecret": "Only Office JWT Secret (works only with https, leave blank to disable)",
"password": "Password",
"passwordUpdated": "Password updated!",
"path": "Path",
@ -261,4 +264,4 @@
"seconds": "Seconds",
"unit": "Time Unit"
}
}
}

View File

@ -182,6 +182,8 @@
"newPassword": "Tu nueva contraseña",
"newPasswordConfirm": "Confirma tu contraseña",
"newUser": "Nuevo usuario",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Contraseña",
"passwordUpdated": "¡Contraseña actualizada!",
"path": "Ruta",
@ -247,4 +249,4 @@
"seconds": "Segundos",
"unit": "Unidad"
}
}
}

View File

@ -181,6 +181,8 @@
"newPassword": "Votre nouveau mot de passe",
"newPasswordConfirm": "Confirmation du nouveau mot de passe",
"newUser": "Nouvel Utilisateur",
"onlyOffice": "Intégration Only Office",
"onlyOfficeUrl": "URL vers OnlyOffice (laisser vide pour désactiver)",
"password": "Mot de passe",
"passwordUpdated": "Mot de passe mis à jour !",
"path": "",
@ -246,4 +248,4 @@
"seconds": "Secondes",
"unit": "Unité de temps"
}
}
}

View File

@ -169,6 +169,8 @@
"newPassword": "Nýja lykilorðið þitt",
"newPasswordConfirm": "Staðfestu nýja lykilorðið",
"newUser": "Nýr notandi",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Lykilorð",
"passwordUpdated": "Lykilorð vistað!",
"path": "",
@ -232,4 +234,4 @@
"seconds": "Sekúndur",
"unit": "Tímastilling"
}
}
}

View File

@ -170,6 +170,8 @@
"newPassword": "La tua nuova password",
"newPasswordConfirm": "Conferma la password",
"newUser": "Nuovo utente",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Password",
"passwordUpdated": "Password aggiornata!",
"path": "Percorso",
@ -233,4 +235,4 @@
"seconds": "Secondi",
"unit": "Unità di tempo"
}
}
}

View File

@ -191,6 +191,8 @@
"newPassword": "新しいパスワード",
"newPasswordConfirm": "新しいパスワード(再入力)",
"newUser": "新規ユーザー作成",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "パスワード",
"passwordUpdated": "パスワードを更新しました!",
"path": "パス",
@ -256,4 +258,4 @@
"seconds": "秒",
"unit": "時間の単位"
}
}
}

View File

@ -169,6 +169,8 @@
"newPassword": "새로운 비밀번호",
"newPasswordConfirm": "새로운 비밀번호 확인",
"newUser": "새로운 사용자",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "비밀번호",
"passwordUpdated": "비밀번호 수정 완료!",
"path": "",
@ -232,4 +234,4 @@
"seconds": "초",
"unit": "Time Unit"
}
}
}

View File

@ -169,6 +169,8 @@
"newPassword": "Uw nieuw wachtwoord",
"newPasswordConfirm": "Bevestig uw nieuw wachtwoord",
"newUser": "Nieuwe gebruiker",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Wachtwoord",
"passwordUpdated": "Wachtwoord bijgewerkt!",
"path": "",
@ -232,4 +234,4 @@
"seconds": "Seconden",
"unit": "Tijdseenheid"
}
}
}

View File

@ -169,6 +169,8 @@
"newPassword": "Twoje nowe hasło",
"newPasswordConfirm": "Potwierdź swoje hasło",
"newUser": "Nowy Użytkownik",
"onlyOffice": "Integracja z Only Office",
"onlyOfficeUrl": "Link do Only Office (w celu wyłączenia usługi, pozostaw puste)",
"password": "Hasło",
"passwordUpdated": "Hasło zostało zapisane!",
"path": "Ścieżka",
@ -232,4 +234,4 @@
"seconds": "Sekundy",
"unit": "Jednostka czasu"
}
}
}

View File

@ -182,6 +182,8 @@
"newPassword": "Nova senha",
"newPasswordConfirm": "Confirme a nova senha",
"newUser": "Novo usuário",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Senha",
"passwordUpdated": "Senha atualizada!",
"path": "",
@ -247,4 +249,4 @@
"seconds": "Segundos",
"unit": "Unidades de Tempo"
}
}
}

View File

@ -170,6 +170,8 @@
"newPassword": "Nova palavra-passe",
"newPasswordConfirm": "Confirme a nova palavra-passe",
"newUser": "Novo utilizador",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Palavra-passe",
"passwordUpdated": "Palavra-passe atualizada!",
"path": "",
@ -233,4 +235,4 @@
"seconds": "Segundos",
"unit": "Unidades de tempo"
}
}
}

View File

@ -169,6 +169,8 @@
"newPassword": "Noua ta parolă",
"newPasswordConfirm": "Confirmă noua parolă",
"newUser": "Utilizator nou",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Parola",
"passwordUpdated": "Parola actualizată!",
"path": "",
@ -232,4 +234,4 @@
"seconds": "Secunde",
"unit": "Unitate de timp"
}
}
}

View File

@ -177,6 +177,8 @@
"newPassword": "Новый пароль",
"newPasswordConfirm": "Повтор нового пароля",
"newUser": "Новый пользователь",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Пароль",
"passwordUpdated": "Пароль обновлен!",
"path": "Путь",
@ -242,4 +244,4 @@
"seconds": "Секунды",
"unit": "Единица времени"
}
}
}

View File

@ -169,6 +169,8 @@
"newPassword": "Ditt nya lösenord",
"newPasswordConfirm": "Bekräfta ditt nya lösenord",
"newUser": "Ny användare",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "Lösenord",
"passwordUpdated": "Lösenord uppdaterat",
"path": "",
@ -232,4 +234,4 @@
"seconds": "Sekunder",
"unit": "Tidsenhet"
}
}
}

View File

@ -195,6 +195,8 @@
"newPassword": "你的新密码",
"newPasswordConfirm": "再次输入以确认你的新密码",
"newUser": "新建用户",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "密码",
"passwordUpdated": "密码已更新!",
"path": "路径",
@ -261,4 +263,4 @@
"seconds": "秒",
"unit": "时间单位"
}
}
}

View File

@ -195,6 +195,8 @@
"newPassword": "您的新密碼",
"newPasswordConfirm": "重輸一遍新密碼",
"newUser": "建立使用者",
"onlyOffice": "Only Office Integration",
"onlyOfficeUrl": "Only Office URL (leave blank to disable)",
"password": "密碼",
"passwordUpdated": "密碼已更新!",
"path": "路徑",
@ -261,4 +263,4 @@
"seconds": "秒",
"unit": "時間單位"
}
}
}

View File

@ -35,7 +35,8 @@ type ResourceType =
| "pdf"
| "text"
| "blob"
| "textImmutable";
| "textImmutable"
| "officedocument";
type DownloadFormat =
| "zip"

View File

@ -8,6 +8,7 @@ interface ISettings {
tus: SettingsTus;
shell: string[];
commands: SettingsCommand;
onlyoffice: SettingsOnlyOffice;
}
interface SettingsDefaults {
@ -55,3 +56,8 @@ interface SettingsUnit {
GB: number;
TB: number;
}
interface SettingsOnlyOffice {
url: string;
jwtSecret: string;
}

View File

@ -18,6 +18,7 @@ const enableExec: boolean = window.FileBrowser.EnableExec;
const tusSettings = window.FileBrowser.TusSettings;
const origin = window.location.origin;
const tusEndpoint = `/api/tus`;
const onlyOfficeUrl = window.FileBrowser.OnlyOfficeUrl;
export {
name,
@ -39,4 +40,5 @@ export {
tusSettings,
origin,
tusEndpoint,
onlyOfficeUrl,
};

View File

@ -37,6 +37,7 @@ import { storeToRefs } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { useUploadStore } from "@/stores/upload";
import { onlyOfficeUrl } from "@/utils/constants";
import HeaderBar from "@/components/header/HeaderBar.vue";
import Breadcrumbs from "@/components/Breadcrumbs.vue";
@ -47,6 +48,9 @@ import FileListing from "@/views/files/FileListing.vue";
import { StatusError } from "@/api/utils";
const Editor = defineAsyncComponent(() => import("@/views/files/Editor.vue"));
const Preview = defineAsyncComponent(() => import("@/views/files/Preview.vue"));
const OnlyOfficeEditor = defineAsyncComponent(
() => import("@/views/files/OnlyOfficeEditor.vue")
);
const layoutStore = useLayoutStore();
const fileStore = useFileStore();
@ -77,6 +81,8 @@ const currentView = computed(() => {
fileStore.req.type === "textImmutable"
) {
return Editor;
} else if (fileStore.req.type === "officedocument" && onlyOfficeUrl) {
return OnlyOfficeEditor;
} else {
return Preview;
}

View File

@ -0,0 +1,58 @@
<template>
<div id="editor-container">
<header-bar>
<action icon="close" :label="$t('buttons.close')" @action="close()" />
<title>{{ fileStore.req?.name ?? "" }}</title>
</header-bar>
<breadcrumbs base="/files" noLink />
<errors v-if="error" :errorCode="error.status" />
<div id="editor" v-if="clientConfig">
<DocumentEditor
v-if="clientConfig"
id="onlyoffice-editor"
:documentServerUrl="onlyOfficeUrl"
:config="clientConfig"
/>
</div>
</div>
</template>
<script setup lang="ts">
import url from "@/utils/url";
import { onlyOfficeUrl } from "@/utils/constants";
import HeaderBar from "@/components/header/HeaderBar.vue";
import Action from "@/components/header/Action.vue";
import Breadcrumbs from "@/components/Breadcrumbs.vue";
import Errors from "@/views/Errors.vue";
import { fetchJSON, StatusError } from "@/api/utils";
import { useFileStore } from "@/stores/file";
import { useRoute } from "vue-router";
import { useRouter } from "vue-router";
import { onMounted, ref } from "vue";
import { DocumentEditor } from "@onlyoffice/document-editor-vue";
const fileStore = useFileStore();
const route = useRoute();
const router = useRouter();
const error = ref<StatusError | null>(null);
const clientConfig = ref<any>(null);
onMounted(async () => {
try {
const isMobile = window.innerWidth <= 736;
clientConfig.value = await fetchJSON(
`/api/onlyoffice/client-config${fileStore.req!.path}?isMobile=${isMobile}`
);
} catch (err) {
if (err instanceof Error) {
error.value = err;
}
}
});
const close = () => {
fileStore.updateRequest(null);
let uri = url.removeLastDir(route.path) + "/";
router.push({ path: uri });
};
</script>

View File

@ -136,6 +136,31 @@
/>
</p>
</div>
<h3>{{ $t("settings.onlyOffice") }}</h3>
<p>
<label for="onlyoffice-url">{{
$t("settings.onlyOfficeUrl")
}}</label>
<input
class="input input--block"
type="text"
v-model="settings.onlyoffice.url"
id="onlyoffice-url"
/>
</p>
<p>
<label for="onlyoffice-jwt">{{
$t("settings.onlyOfficeJwtSecret")
}}</label>
<input
class="input input--block"
type="text"
v-model="settings.onlyoffice.jwtSecret"
id="onlyoffice-jwt"
/>
</p>
</div>
<div class="card-action">

1
go.mod
View File

@ -32,6 +32,7 @@ require (
)
require (
github.com/allegro/bigcache v1.2.1 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/asticode/go-astikit v0.42.0 // indirect
github.com/asticode/go-astits v1.13.0 // indirect

2
go.sum
View File

@ -2,6 +2,8 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=

View File

@ -84,6 +84,7 @@ func withUser(fn handleFunc) handleFunc {
w.Header().Add("X-Renew-Token", "true")
}
d.authToken = token.Raw
d.user, err = d.store.Users.Get(d.server.Root, tk.User.ID)
if err != nil {
return http.StatusInternalServerError, err

View File

@ -18,11 +18,12 @@ type handleFunc func(w http.ResponseWriter, r *http.Request, d *data) (int, erro
type data struct {
*runner.Runner
settings *settings.Settings
server *settings.Server
store *storage.Storage
user *users.User
raw interface{}
authToken string
settings *settings.Settings
server *settings.Server
store *storage.Storage
user *users.User
raw interface{}
}
// Check implements rules.Checker.

View File

@ -60,6 +60,9 @@ func NewHandler(
users.Handle("/{id:[0-9]+}", monkey(userGetHandler, "")).Methods("GET")
users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE")
api.PathPrefix("/onlyoffice").Handler(monkey(onlyofficeClientConfigGetHandler, "/api/onlyoffice/client-config")).Methods("GET")
api.PathPrefix("/onlyoffice").Handler(monkey(onlyofficeCallbackHandler, "/api/onlyoffice/callback")).Methods("POST")
api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET")
api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE")
api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST")

216
http/onlyoffice.go Normal file
View File

@ -0,0 +1,216 @@
package http
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strconv"
"time"
"github.com/allegro/bigcache"
"github.com/golang-jwt/jwt/v4"
"github.com/filebrowser/filebrowser/v2/files"
)
const (
onlyOfficeStatusDocumentClosedWithChanges = 2
onlyOfficeStatusDocumentClosedWithNoChanges = 4
onlyOfficeStatusForceSaveWhileDocumentStillOpen = 6
trueString = "true" // linter-enforced constant
twoDays = 48 * time.Hour // linter enforced constant
)
var (
// Refer to only-office documentation on co-editing
// https://api.onlyoffice.com/editors/coedit
//
// a 48 hour TTL here is not required, because the document server will notify
// us when keys should be evicted. However, it is added defensively in order to
// prevent potential memory leaks.
coeditingDocumentKeys, _ = bigcache.NewBigCache(bigcache.DefaultConfig(twoDays))
)
type OnlyOfficeCallback struct {
ChangesURL string `json:"changesurl,omitempty"`
Key string `json:"key,omitempty"`
Status int `json:"status,omitempty"`
URL string `json:"url,omitempty"`
Users []string `json:"users,omitempty"`
UserData string `json:"userdata,omitempty"`
}
var onlyofficeClientConfigGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if d.settings.OnlyOffice.JWTSecret == "" {
return http.StatusInternalServerError, errors.New("only-office integration must be configured in settings")
}
if !d.user.Perm.Modify || !d.Check(r.URL.Path) {
return http.StatusForbidden, nil
}
referrer, err := getReferer(r)
if err != nil {
return http.StatusInternalServerError, errors.Join(errors.New("could not determine request referrer"), err)
}
file, err := files.NewFileInfo(&files.FileOptions{
Fs: d.user.Fs,
Path: r.URL.Path,
Modify: d.user.Perm.Modify,
Expand: false,
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
})
if err != nil {
return errToStatus(err), err
}
clientConfig := map[string]interface{}{
"document": map[string]interface{}{
"fileType": file.Extension[1:],
"key": getDocumentKey(file.RealPath()),
"title": file.Name,
"url": (&url.URL{
Scheme: referrer.Scheme,
Host: referrer.Host,
RawQuery: "auth=" + url.QueryEscape(d.authToken),
}).JoinPath(d.server.BaseURL, "/api/raw", file.Path).String(),
"permissions": map[string]interface{}{
"edit": d.user.Perm.Modify,
"download": d.user.Perm.Download,
"print": d.user.Perm.Download,
},
},
"editorConfig": map[string]interface{}{
"callbackUrl": (&url.URL{
Scheme: referrer.Scheme,
Host: referrer.Host,
RawQuery: "auth=" + url.QueryEscape(d.authToken) + "&save=" + url.QueryEscape(file.Path),
}).JoinPath(d.server.BaseURL, "/api/onlyoffice/callback").String(),
"user": map[string]interface{}{
"id": strconv.FormatUint(uint64(d.user.ID), 10),
"name": d.user.Username,
},
"customization": map[string]interface{}{
"autosave": true,
"forcesave": true,
"uiTheme": ternary(d.Settings.Branding.Theme == "dark", "default-dark", "default-light"),
},
"lang": d.user.Locale,
"mode": ternary(d.user.Perm.Modify, "edit", "view"),
},
"type": ternary(r.URL.Query().Get("isMobile") == trueString, "mobile", "desktop"),
}
signature, err := jwt.
NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(clientConfig)).
SignedString([]byte(d.Settings.OnlyOffice.JWTSecret))
if err != nil {
return http.StatusInternalServerError, errors.Join(errors.New("could not sign only-office client-config"), err)
}
clientConfig["token"] = signature
return renderJSON(w, r, clientConfig)
})
var onlyofficeCallbackHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
body, err := io.ReadAll(r.Body)
if err != nil {
return http.StatusInternalServerError, err
}
var data OnlyOfficeCallback
err = json.Unmarshal(body, &data)
if err != nil {
return http.StatusInternalServerError, err
}
docPath := r.URL.Query().Get("save")
if docPath == "" {
return http.StatusInternalServerError, errors.New("unable to get file save path")
}
if data.Status == onlyOfficeStatusDocumentClosedWithChanges ||
data.Status == onlyOfficeStatusDocumentClosedWithNoChanges {
// Refer to only-office documentation
// - https://api.onlyoffice.com/editors/coedit
// - https://api.onlyoffice.com/editors/callback
//
// When the document is fully closed by all editors,
// then the document key should no longer be re-used.
realPath := files.GetRealPath(d.user.Fs, docPath)
_ = coeditingDocumentKeys.Delete(realPath)
}
if data.Status == onlyOfficeStatusDocumentClosedWithChanges ||
data.Status == onlyOfficeStatusForceSaveWhileDocumentStillOpen {
if !d.user.Perm.Modify || !d.Check(docPath) {
return http.StatusForbidden, nil
}
doc, err := http.Get(data.URL)
if err != nil {
return http.StatusInternalServerError, err
}
defer doc.Body.Close()
err = d.Runner.RunHook(func() error {
_, writeErr := writeFile(d.user.Fs, docPath, doc.Body)
if writeErr != nil {
return writeErr
}
return nil
}, "save", docPath, "", d.user)
if err != nil {
return http.StatusInternalServerError, err
}
}
resp := map[string]int{
"error": 0,
}
return renderJSON(w, r, resp)
})
func getReferer(r *http.Request) (*url.URL, error) {
if len(r.Header["Referer"]) != 1 {
return nil, errors.New("expected exactly one Referer header")
}
return url.ParseRequestURI(r.Header["Referer"][0])
}
func getDocumentKey(realPath string) string {
// error is intentionally ignored in order treat errors
// the same as a cache-miss
cachedDocumentKey, _ := coeditingDocumentKeys.Get(realPath)
if cachedDocumentKey != nil {
return string(cachedDocumentKey)
}
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
documentKey := hashSHA256(realPath + timestamp)
_ = coeditingDocumentKeys.Set(realPath, []byte(documentKey))
return documentKey
}
func hashSHA256(data string) string {
bytes := sha256.Sum256([]byte(data))
return hex.EncodeToString(bytes[:])
}
func ternary(condition bool, trueValue, falseValue string) string {
if condition {
return trueValue
}
return falseValue
}

View File

@ -18,6 +18,7 @@ type settingsData struct {
Tus settings.Tus `json:"tus"`
Shell []string `json:"shell"`
Commands map[string][]string `json:"commands"`
OnlyOffice settings.OnlyOffice `json:"onlyoffice"`
}
var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
@ -31,6 +32,7 @@ var settingsGetHandler = withAdmin(func(w http.ResponseWriter, r *http.Request,
Tus: d.settings.Tus,
Shell: d.settings.Shell,
Commands: d.settings.Commands,
OnlyOffice: d.settings.OnlyOffice,
}
return renderJSON(w, r, data)
@ -52,6 +54,7 @@ var settingsPutHandler = withAdmin(func(_ http.ResponseWriter, r *http.Request,
d.settings.Tus = req.Tus
d.settings.Shell = req.Shell
d.settings.Commands = req.Commands
d.settings.OnlyOffice = req.OnlyOffice
err = d.store.Settings.Save(d.settings)
return errToStatus(err), err

View File

@ -46,6 +46,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
"ResizePreview": d.server.ResizePreview,
"EnableExec": d.server.EnableExec,
"TusSettings": d.settings.Tus,
"OnlyOfficeUrl": d.settings.OnlyOffice.URL,
}
if d.settings.Branding.Files != "" {

7
settings/onlyoffice.go Normal file
View File

@ -0,0 +1,7 @@
package settings
// OnlyOffice contains the onlyoffice server connection settings of the app.
type OnlyOffice struct {
URL string `json:"url"`
JWTSecret string `json:"jwtSecret"`
}

View File

@ -27,6 +27,7 @@ type Settings struct {
Commands map[string][]string `json:"commands"`
Shell []string `json:"shell"`
Rules []rules.Rule `json:"rules"`
OnlyOffice OnlyOffice `json:"onlyoffice"`
}
// GetRules implements rules.Provider.