Documentation
¶
Overview ¶
Package onlyoffice integrates a self-hosted OnlyOffice Document Server with filex storage backends.
Three pieces cooperate:
- Config: builds a JWT-signed editor descriptor for the embed iframe. Includes a signed fetch URL the document server uses to pull source bytes from filex.
- Fetch: serves source bytes back to the document server (HMAC-signed URL). Public — no session required, but the URL is unguessable and short-lived.
- Callback: receives save events from the document server, validates the JWT it sends, downloads the saved revision, and writes back to storage.
All signing uses HS256 (HMAC-SHA256) with the JWTSecret loaded from config (FILEX_ONLYOFFICE_JWT). That same secret is what the document server has in its `local.json` -> `services.CoAuthoring.token.enable` stanza.
Index ¶
- Constants
- Variables
- func ConvertKey(parts ...string) string
- func DocumentType(ext string) string
- func FramePage(dsURL string) (page []byte, csp string, ok bool)
- func FrameSource(dsURL string) (string, bool)
- func RewriteCSV(saved []byte, d CSVDialect) []byte
- func ValidPurpose(p string) bool
- type CSVDialect
- type CSVKept
- type CallbackPayload
- type ConfigOption
- type ConvertError
- type ConvertRequest
- type ConvertResult
- type Converter
- type Diagnosis
- type EditorConfig
- type ErrorClass
- type FetchOutcome
- type LayoutMargin
- type LayoutSize
- type PurposeOutcome
- type ReverseResult
- type SaveVerdict
- type Service
- func (s *Service) AttachBody(b *filebody.Resolver)
- func (s *Service) AttachSync(sy *protocolsync.Syncer)
- func (s *Service) BuildConfigForNode(ctx context.Context, node *model.Node, user *model.User, lang, mode string, ...) (*EditorConfig, error)
- func (s *Service) CheckReversePath(ctx context.Context, t *Target) ReverseResult
- func (s *Service) Converter(ctx context.Context) (*Converter, error)
- func (s *Service) Diagnose(nodeID int64) Diagnosis
- func (s *Service) DrawThumbnail(ctx context.Context, r ThumbRequest) ([]byte, error)
- func (s *Service) DropSession(ctx context.Context, node *model.Node, key string) error
- func (s *Service) Enabled() bool
- func (s *Service) EnabledCtx(ctx context.Context) bool
- func (s *Service) HandleCallback(r *http.Request, nodeID int64) (map[string]any, error)
- func (s *Service) LastPurposeFetch(nodeID int64, purpose string) (PurposeOutcome, bool)
- func (s *Service) LiveTarget(ctx context.Context) Target
- func (s *Service) NoteFetch(nodeID int64, status int, code, reason string, signed bool)
- func (s *Service) NotePurposeFetch(nodeID int64, purpose string, status int, code string)
- func (s *Service) OfferFile(ctx context.Context, path, name string) (string, func(), error)
- func (s *Service) OpenOffered(ctx context.Context, token string, exp int64, purpose, sig string) (*os.File, string, error)
- func (s *Service) PruneSessions(ctx context.Context)
- func (s *Service) PurposeFetchURL(ctx context.Context, nodeID int64, purpose string) (string, error)
- func (s *Service) RebaseSession(ctx context.Context, node *model.Node, key string) error
- func (s *Service) ServeFetchProbe(ctx context.Context, token string, exp int64, sig string) (int, string)
- func (s *Service) SessionState(ctx context.Context, node *model.Node, key string) (stale, known bool)
- func (s *Service) SignedFetchURL(nodeID, exp int64) string
- func (s *Service) ThumbKey(ctx context.Context, r ThumbRequest) string
- func (s *Service) VerifyFetchSignature(nodeID, exp int64, sig string) error
- func (s *Service) VerifyFetchSignatureCtx(ctx context.Context, nodeID, exp int64, sig string) error
- func (s *Service) VerifyPurposeFetchCtx(ctx context.Context, nodeID, exp int64, purpose, sig string) error
- func (s *Service) VerifyReversePath(ctx context.Context) ReverseResult
- type SpreadsheetLayout
- type Target
- type ThumbRequest
- type Thumbnail
Constants ¶
const ( // AuditActionSavedBeside: an edit the document server saved in another // format was written beside the file, which was left as it was. AuditActionSavedBeside = "file.office_saved_beside" // AuditActionSaveRefused: the document server's save was not written. AuditActionSaveRefused = "file.office_save_refused" )
Audit actions (web/tests/lib/auditLabel.test.ts holds their labels).
const ( DSUnknown = -1 // unknown error, a storage or queue failure DSTimeout = -2 // the conversion took too long, or the task was dead-lettered DSConvert = -3 // the converter could not read or convert the file DSDownload = -4 // the document server could not download the source DSPassword = -5 // the file is protected by a password (or DRM) DSResultDB = -6 // the conversion result database could not be reached DSParams = -7 // the request's input is wrong for this file DSToken = -8 // the token is missing or wrong DSDetect = -9 // the output format could not be determined DSLimit = -10 // a size limit: the download, or the file's content )
Conversion error codes the document server answers (`error`). The names are filex's; the numbers are the document server's (Common/sources/utils.js, mapAscServerErrorToOldError).
const ( FetchBadLink = "bad_link" FetchSignatureExpired = "signature_expired" FetchSignatureBad = "signature_bad" FetchNotFound = "not_found" FetchObjectMissing = "object_missing" FetchReadFailed = "read_failed" )
Reason codes for a refused fetch (FetchOutcome.Code). The editor words them in the reader's language; Reason is the English the log line carries.
const ( // VerdictServed: the document server downloaded the document and filex // served it. A "Download failed" after that is the browser's leg: the // converted copy could not be loaded from the document server. VerdictServed = "served" // VerdictNotRequested: the document server has not asked filex for the // document since filex last opened it (or ever). If the document was // opened before, the document server may be using its own converted copy. VerdictNotRequested = "not_requested" // VerdictRefused: the document server asked and filex refused; the // outcome's Code and Reason say why. VerdictRefused = "refused" )
Verdicts (Diagnosis.Verdict).
const ( PurposeThumb = "thumb" PurposeConvert = "convert" )
The purposes a fetch address may name.
const ( // AdviceCheckJWT: the document server answered -4 and filex cannot tell // whether it enforces JWT. Check that first: by default the document // server fetches the address in a signed request without its // private-address filter (see downloadAdvice for where that is set), so // with JWT on a container address is fine and the filter is not the // cause. The filter matters with JWT off, or when that default was turned // off. AdviceCheckJWT = "check_jwt" // AdviceJWTOff: -4, and the document server took an unsigned request. With // JWT off it applies its private-address filter to every download (a // container network is private), and its save callbacks arrive unsigned. // Turning JWT on with filex's secret fixes both. AdviceJWTOff = "jwt_off" // AdviceRoute: -4 from a document server that enforces JWT. By default // the filter does not apply to this signed request, so it could not reach // filex at that address (name, network, proxy). AdviceRoute = "route" // AdviceFilexRefused: the request reached filex and filex refused it. // Detail names the reason. AdviceFilexRefused = "filex_refused" )
Advice codes for a failed download (ReverseResult.Advice).
const ( StatusBeingEdited = 1 StatusReadyForSaving = 2 StatusSavingError = 3 StatusClosedNoChange = 4 StatusForceSave = 6 StatusForceSaveError = 7 )
Status codes per OnlyOffice callback spec.
const AuditActionSavedConflict = "file.office_saved_conflict"
AuditActionSavedConflict: the document changed while an editing session had it open, and the session's save was written beside it, not over it.
const CSVSniffBytes = 64 << 10
CSVSniffBytes is how much of a CSV is read to learn how it is written.
const FetchEncrypted = "encrypted"
FetchEncrypted is the reason code of a purpose fetch refused because the file is end-to-end encrypted.
const FetchPath = "/api/files/onlyoffice/fetch"
FetchPath is the endpoint the document server downloads a document from. The reverse-path probe is sent to it too.
const FrameHostPath = FrameHostPrefix + "editor"
FrameHostPath is the frame on a frame origin.
const FrameHostPrefix = "/filex-frame/"
FrameHostPrefix is the one path prefix a frame origin's reverse proxy sends to filex (FILEX_ONLYOFFICE_FRAME_ORIGIN); everything else on that host is the document server's. At the host's root, whatever filex's own base path: it is the document server's host, not filex's. No document server path starts with it (theirs are /web-apps, /sdkjs, /fonts, /cache, /coauthoring, /<version>/…).
const FramePath = "/_appui/_onlyoffice/editor"
FramePath is where the frame is served on the interface origin (FILEX_APP_UI_ORIGIN), below the base path; api.appUIHostSplit refuses the interface route on filex's own host. It is under the interface route on purpose: that host answers nothing else. `_onlyoffice` cannot be an app's name (an app's name starts with a letter or a digit), so it never shadows an app's interface.
const ProbeNodeID int64 = 0
ProbeNodeID is the node id a probe URL carries. No document has it: node ids start at 1, so the fetch handler can tell a probe from a document by it.
const PurposeFetchTTL = 10 * time.Minute
PurposeFetchTTL is how long a purpose's fetch address is good for.
const ScopeThisProcess = "this_process"
ScopeThisProcess is the only Diagnosis.Scope there is: what this filex process saw.
const ThumbSize = 320
ThumbSize is the box a thumbnail's page is drawn into, in pixels: the pipeline's own JPEG is at most this large on either side, and asking for more would only be scaled away.
Variables ¶
var ( ErrSignatureExpired = errors.New("onlyoffice: signature expired") ErrBadSignature = errors.New("onlyoffice: bad signature") )
The two ways a fetch signature is refused. Callers tell them apart with errors.Is: an expired link and a wrong secret want different advice.
var DefaultCSVDialect = CSVDialect{Comma: ',', UTF8: true}
DefaultCSVDialect is a CSV with nothing to say about itself: an empty file, or one filex could not read. Comma-separated UTF-8, no byte order mark, "\n" line ends.
var ErrForeignOrigin = errors.New("the result is not on the document server's address")
ErrForeignOrigin is a result address outside the document server's origin.
var ErrNotConfigured = errors.New("onlyoffice: not configured")
ErrNotConfigured: OnlyOffice is not configured in filex right now (no document server address, or no secret).
var ErrOfferGone = errors.New("onlyoffice: no such offered file")
ErrOfferGone: the address names no file on offer (never offered, withdrawn, or expired).
var ErrResultTooLarge = errors.New("the result is larger than allowed")
ErrResultTooLarge is a result over FetchResult's maxBytes.
var ErrUnknownPurpose = errors.New("onlyoffice: unknown fetch purpose")
ErrUnknownPurpose: a fetch address names a purpose filex does not hand out.
Functions ¶
func ConvertKey ¶ added in v0.50.0
ConvertKey is a conversion key made of parts: the same parts, the same key. Callers name everything the result depends on - which filex and which file (the fetch address's base, the storage, the node), the content (its fingerprint), the parameters (a version string the caller bumps when it changes what it asks for) and the attempt - because the document server answers a key it has seen with what it made then (see the package comment).
The key is "fx" and 40 hex digits: the document server takes 128 of [0-9A-Za-z.=_-].
func DocumentType ¶
DocumentType returns "word", "cell", "slide", or "" for an extension.
Matches the official OnlyOffice mapping. If the extension is unknown we return "" so callers can 415.
func FramePage ¶ added in v0.53.0
FramePage is the frame's page and the Content-Security-Policy it is served with, for the document server at dsURL (the configuration in force). ok is false when there is no document server, or its address cannot be named in a policy (FrameSource).
The policy, directive by directive:
- script-src: frame.js by its hash and the document server, nothing else. api.js finds its own <script> element to learn where the editor lives, so it is loaded as a script element, from that server.
- frame-src, child-src: the document server, where api.js puts the editor.
- style-src 'unsafe-inline': the page's own few lines and the style attributes api.js writes on the editor's frame.
- connect-src, img-src, font-src, media-src, form-action: the document server only.
- no `sandbox` directive: the page keeps its own origin on purpose (the document server's editor wants storage, and the origin is not filex's). The explorer's frame element carries the sandbox flags.
- frame-ancestors *: the explorer is embedded in other sites by design (<filex-explorer>, tenants on their own domains), and the page holds nothing: what it shows comes from the framing page's config, signed for a document that page's own session could open, which it could as well hand to the document server directly. The checks that matter are the framing page's (core lib/officeFrame.ts: this frame's window, this origin, this session, once).
func FrameSource ¶ added in v0.53.0
FrameSource is the document server's address as a policy source: scheme, host and path, ending in "/" (https://docs.example.com/, or https://example.com/ds/ for one under a path), so that everything the server serves below it matches and nothing else does. Refused: no address, a scheme other than http(s), a user, a query or fragment, and anything that could end a directive or name another source (a quote, a semicolon, a comma, a space, a wildcard).
func RewriteCSV ¶ added in v0.51.0
func RewriteCSV(saved []byte, d CSVDialect) []byte
RewriteCSV turns the CSV the document server saved (comma-separated, a UTF-8 byte order mark, "\n" line ends) back into the file's own way of writing: its delimiter, its byte order mark or none, its line ends. The values are not touched: they stay ONLYOFFICE's, `7` for a cell that held `007`. KeepCSV is what keeps the file's own text, and this is what is written when it cannot. A field is quoted when the delimiter, a quote or a line end is in it.
func ValidPurpose ¶ added in v0.50.0
ValidPurpose reports whether p is a purpose a fetch address may name.
Types ¶
type CSVDialect ¶ added in v0.51.0
type CSVDialect struct {
// Comma is the delimiter: ',', ';', '\t' or '|'.
Comma byte
// BOM: the file starts with a UTF-8 byte order mark.
BOM bool
// CRLF: its lines end in "\r\n" (else "\n").
CRLF bool
// UTF8: its bytes are UTF-8 (ASCII included). False for UTF-16 and for a
// legacy code page.
UTF8 bool
}
CSVDialect is how a CSV file is written.
func SniffCSV ¶ added in v0.51.0
func SniffCSV(head []byte) CSVDialect
SniffCSV reads how a CSV is written from its first bytes (CSVSniffBytes of them is plenty; fewer is fine). The delimiter is the candidate that splits the first lines (outside quotes) into the same number of fields, the most of them; with none consistent, the most frequent; with none at all, a comma.
func (CSVDialect) KeepsBOM ¶ added in v0.51.0
func (d CSVDialect) KeepsBOM() bool
KeepsBOM reports whether the saved file starts with a byte order mark: when the file had one, and when it was not UTF-8 (the save is UTF-8 now, and the mark is what tells a reader so).
func (CSVDialect) OpenOptions ¶ added in v0.51.0
func (d CSVDialect) OpenOptions() map[string]any
OpenOptions is the editor config's `document.options` that opens the CSV without ONLYOFFICE's "Choose CSV options" dialog: UTF-8 and its delimiter. Nil for a file that is not UTF-8: the dialog asks which encoding it is.
type CSVKept ¶ added in v0.52.0
type CSVKept struct {
// Applied: the cells were kept. False: the bytes are RewriteCSV's.
Applied bool
// Why they were not: "empty" (the file on storage has no record),
// "not_utf8", "too_large", "too_many_records", "too_many_fields" (in one
// record), "check_failed" (the result did not read back as the save),
// "panic".
Why string
// Panic, when Why is "panic": what panicked and where, for the log. A
// runtime panic names indexes, never a cell's text.
Panic string
// Records written.
Records int
// Unchanged records, written from the file's own bytes.
Unchanged int
// Restored cells: in a record somebody changed, the cells whose own text
// was put back over ONLYOFFICE's way of writing it.
Restored int
// New records: saved ones with no record of the file behind them.
New int
// Deleted records: the file's, with no saved record.
Deleted int
}
CSVKept says what KeepCSV did, for the log and the tests.
func KeepCSV ¶ added in v0.52.0
func KeepCSV(original, saved []byte, d CSVDialect) ([]byte, CSVKept)
KeepCSV turns the CSV the document server saved into the bytes to write, with the text of every cell nobody changed as it is in original, the file on storage: a record in which nothing changed byte for byte, the unchanged cells of a changed one, new records in the file's dialect. d is how the original is written (SniffCSV). When the cells cannot be kept the bytes are RewriteCSV(saved, d) and CSVKept says why.
type CallbackPayload ¶
type CallbackPayload struct {
Key string `json:"key"`
Status int `json:"status"`
URL string `json:"url"`
Token string `json:"token,omitempty"`
// FileType is the type of the document at URL ("csv", "xlsx", ...). The
// server saves in the document's own type by default
// (`assemblyFormatAsOrigin`), in OOXML when that is off (filex 0.51,
// callback_csv.go).
FileType string `json:"filetype,omitempty"`
// Users are the editors of the session: the user ids the editor config
// named, filex account ids.
Users []string `json:"users,omitempty"`
}
CallbackPayload is the body the document server posts on save / status transitions. See https://api.onlyoffice.com/editors/callback for the full schema; we use the fields filex actually needs.
type ConfigOption ¶ added in v0.51.0
type ConfigOption func(*configExtras)
ConfigOption adds to an editor config what only the caller knows.
func WithHead ¶ added in v0.51.0
func WithHead(head []byte) ConfigOption
WithHead hands over the first bytes of the document (CSVSniffBytes is plenty): for a CSV they say its delimiter and encoding, which the config passes on so ONLYOFFICE opens it without asking (csv.go).
type ConvertError ¶ added in v0.50.0
type ConvertError struct {
// Code is the document server's error number (-1 to -10), 0 when the
// failure was not its answer (the network, an HTTP error, a timeout).
Code int
// Class says what it means (ClassOf, or transient for everything that is
// not the document server's answer).
Class ErrorClass
// HTTPStatus is the status of an answer that was not a conversion answer.
HTTPStatus int
// What is the short name a record keeps: "ds-3", "http-502", "net",
// "timeout", "answer", "origin", "result". Never a URL, never a secret.
What string
Err error
}
ConvertError is why a conversion did not give a result.
func AsConvertError ¶ added in v0.50.0
func AsConvertError(err error) (*ConvertError, bool)
AsConvertError returns err as a *ConvertError, and false for any other error.
func (*ConvertError) Error ¶ added in v0.50.0
func (e *ConvertError) Error() string
func (*ConvertError) Unwrap ¶ added in v0.50.0
func (e *ConvertError) Unwrap() error
type ConvertRequest ¶ added in v0.50.0
type ConvertRequest struct {
// FetchURL is where the document server downloads the source: filex's
// signed fetch address for a purpose (PurposeFetchURL).
FetchURL string
// FileType is the source's type: its extension, lower case, no dot.
FileType string
// OutputType is what to make: "png" for a thumbnail, "pdf", "docx", ...
OutputType string
// Title is the file's name, for the document server's log and the
// result's name.
Title string
// Key is the conversion's key (ConvertKey). Required.
Key string
// Thumbnail and SpreadsheetLayout are optional.
Thumbnail *Thumbnail
SpreadsheetLayout *SpreadsheetLayout
// CodePage and Delimiter are the conversion API's CSV and text knobs
// (`codePage`, `delimiter`); zero leaves them out.
CodePage int
Delimiter int
}
ConvertRequest is one file to convert.
type ConvertResult ¶ added in v0.50.0
ConvertResult is a finished conversion: where its result is, and its type.
type Converter ¶ added in v0.50.0
type Converter struct {
// DocumentServerURL is the document server's base address, as filex is
// configured with it. Results are only fetched from its origin.
DocumentServerURL string
// Secret signs every request; empty sends them unsigned (only the
// reverse-path probe's JWT question does).
Secret string
// HTTP is the client requests go through (nil: a default one without a
// timeout of its own; the caller's context bounds every request).
HTTP *http.Client
// PollFirst and PollMax are the wait before the first poll and the
// longest wait between two (zero: 200 ms and 1 s).
PollFirst, PollMax time.Duration
}
Converter talks to one document server.
func (*Converter) Convert ¶ added in v0.50.0
func (c *Converter) Convert(ctx context.Context, r ConvertRequest) (*ConvertResult, error)
Convert asks for one conversion and polls until it ends: the result, or a *ConvertError. The caller's context bounds it (a per-file deadline belongs there): when it ends first the error is a transient "timeout".
func (*Converter) FetchResult ¶ added in v0.50.0
func (c *Converter) FetchResult(ctx context.Context, res *ConvertResult, maxBytes int64) ([]byte, error)
FetchResult downloads a finished conversion's result, at most maxBytes of it, and only from the document server's own origin (see the package comment). A result over maxBytes is an error, not a truncated file.
type Diagnosis ¶ added in v0.50.0
type Diagnosis struct {
Verdict string `json:"verdict"`
// OpenedAt is when filex last handed out an editor configuration for the
// document.
OpenedAt *time.Time `json:"opened_at,omitempty"`
// Fetch is the last answer filex gave the document server for it.
Fetch *FetchOutcome `json:"fetch,omitempty"`
Scope string `json:"scope"`
}
Diagnosis is the answer to "the editor said Download failed: what did filex see?".
type EditorConfig ¶
type EditorConfig struct {
DocumentServerURL string `json:"documentServerUrl"`
Config map[string]any `json:"config"`
// Frame is the address of the editor's frame on another origin (task #92):
// FrameHostPath on FILEX_ONLYOFFICE_FRAME_ORIGIN (normally the document
// server's own), else FramePath under FILEX_APP_UI_ORIGIN. The explorer
// frames it and hands it Config, and api.js runs there instead of in
// filex's page. Absent: neither is set, and api.js runs in the page.
Frame string `json:"frame,omitempty"`
}
EditorConfig is the descriptor we send to the embed iframe.
type ErrorClass ¶ added in v0.50.0
type ErrorClass string
ErrorClass is what a conversion failure means for whoever asked: whether asking again can help, and what to tell the person looking at the file.
const ( // ClassTransient: asking again later may work - the network, the // document server's own trouble (-1, -2, -6), a token it refused (-8, // a secret being changed), a download that did not reach filex (-4), a // timeout, an answer filex could not read, a result it could not fetch. ClassTransient ErrorClass = "transient" // ClassCorrupt: the file cannot be converted (-3, -7, -9). The same // bytes will fail again. ClassCorrupt ErrorClass = "corrupt" // ClassPassword: the file is protected by a password (-5). ClassPassword ErrorClass = "password" // ClassTooLarge: over the document server's own size limits (-10). ClassTooLarge ErrorClass = "too_large" )
const ClassEncrypted ErrorClass = "encrypted"
ClassEncrypted: filex refused to send the document (it is end-to-end encrypted), so the document server answered -4.
func ClassOf ¶ added in v0.50.0
func ClassOf(code int) ErrorClass
ClassOf classifies a document server error code.
type FetchOutcome ¶ added in v0.50.0
type FetchOutcome struct {
At time.Time `json:"at"`
Status int `json:"status"`
Code string `json:"reason_code,omitempty"`
Reason string `json:"reason,omitempty"`
}
FetchOutcome is what filex answered one request of the document server's.
type LayoutMargin ¶ added in v0.50.0
type LayoutMargin struct {
Left string `json:"left"`
Right string `json:"right"`
Top string `json:"top"`
Bottom string `json:"bottom"`
}
LayoutMargin is a page's margins, with their unit ("2mm").
type LayoutSize ¶ added in v0.50.0
LayoutSize is a page size, with its unit ("120mm").
type PurposeOutcome ¶ added in v0.50.0
PurposeOutcome is what filex answered one purpose fetch.
type ReverseResult ¶
type ReverseResult struct {
// Checked is false when the question could not be put at all — nothing
// configured, or the document server did not answer. A false here must
// never be rendered as a failure of the route.
Checked bool `json:"checked"`
// OK is true only when the probe request actually ARRIVED at filex and
// filex served it, the way it serves a document.
OK bool `json:"ok"`
// URL is the address the document server was asked to fetch, so an
// operator reading a red result can try it themselves.
URL string `json:"url,omitempty"`
// Code is the document server's own error number when it returned one.
// -4 is "error while downloading the document file" — the shape of a
// broken reverse path. 0 means it did not report an error.
Code int `json:"code,omitempty"`
// Detail is the sentence to show the operator.
Detail string `json:"detail,omitempty"`
// Advice says what to look at first when the document server could not
// download the probe (one of the Advice* codes). Empty when there is
// nothing to advise. A client that wants its own wording keys off this,
// never off Detail.
Advice string `json:"advice,omitempty"`
// JWTEnforced answers the second question: did the document server refuse
// a request that carried no token? true: it enforces JWT. false: it took
// an unsigned request, so it refuses filex's editor token (it checks it
// against its own secret) and sends its save callbacks unsigned, which
// filex refuses. Absent: it could not be told.
JWTEnforced *bool `json:"jwt_enforced,omitempty"`
}
ReverseResult is one answer to "can the document server reach filex?".
type SaveVerdict ¶ added in v0.53.0
type SaveVerdict int
SaveVerdict is what becomes of a session's save.
const ( // SaveCurrent: the session is on the file's current version (or filex // knows nothing about it): written over the file. SaveCurrent SaveVerdict = iota // SaveStale: the file changed since the session opened it: written // beside it. SaveStale // SaveDropped: the person chose the outside version: not written. SaveDropped )
type Service ¶
type Service struct {
Store db.Store
StorageResolver func(int64) (storage.Driver, error)
// DocumentServerURL / JWTSecret are the BOOT-TIME values (env/YAML). They
// are the fallback only: when Live is wired the running configuration is
// read from the `external_services` row on every call, so an operator who
// configures OnlyOffice in the admin UI does not have to restart filex.
// See internal/external and issue #17.
DocumentServerURL string // e.g. https://docs.example.com
JWTSecret string // shared secret with the doc server
// Live, when non-nil, returns the current url + secret. Nil means "use the
// fields", which is what the unit tests and any no-DB caller want.
Live func(ctx context.Context) (url, secret string)
PublicURL string // filex public base URL — used to build callbacks
// LiveCallbackURL, when non-nil, returns the address the DOCUMENT SERVER
// should use to reach filex. Empty result means "the public URL".
//
// ⚠ These are two different addresses for two different readers. PublicURL
// is what a person's browser uses and what every share link is built from;
// this one is only ever read by the document server's container. They are
// usually the same string, and on podman or a private network they cannot
// be — which is why a document opened and its save never came back with
// nothing in filex's log to show for it (issue #17).
LiveCallbackURL func(ctx context.Context) string
FetchTTL time.Duration
// Body resolves where a document's bytes are: the driver, or filex's
// staging area while a staged upload is still transferring. It is the
// resolver the fetch endpoint serves the document server from, so the
// callback compares a saved CSV with the bytes the editor was given
// (callback_csv.go csvOriginal). Nil-safe: it then answers the driver.
Body *filebody.Resolver
// Sync is the shared post-write gate every other write surface in filex
// goes through: it upserts the node row, re-indexes the document,
// dispatches a thumbnail, tells open explorers, emits the canonical
// `file.updated` webhook event and schedules the antivirus scan.
//
// ⚠ Optional, and its absence degrades gracefully rather than silently:
// an unwired Service falls back to a Syncer with no search index (see
// syncer), so a save still announces itself and is still scanned — only
// the re-index is skipped. Wire it with AttachSync at boot.
Sync *protocolsync.Syncer
// contains filtered or unexported fields
}
Service builds editor configs and validates document-server callbacks.
func New ¶
func New(store db.Store, resolver func(int64) (storage.Driver, error), docURL, jwtSecret, publicURL string, fetchTTL time.Duration) *Service
New constructs a Service. fetchTTL defaults to 1 hour when zero.
func (*Service) AttachBody ¶ added in v0.52.0
AttachBody wires the byte-source resolver, the one the fetch endpoint reads a document through.
func (*Service) AttachSync ¶
func (s *Service) AttachSync(sy *protocolsync.Syncer)
AttachSync wires the post-write gate. ⚠ Without it an edited document keeps its PRE-EDIT text in content search, because nothing else ever revisits a file whose path did not change.
func (*Service) BuildConfigForNode ¶
func (s *Service) BuildConfigForNode(ctx context.Context, node *model.Node, user *model.User, lang, mode string, opts ...ConfigOption) (*EditorConfig, error)
BuildConfigForNode resolves the node, presigns the fetch URL, and signs the JSON descriptor with HS256. `mode` selects "edit" or "view"; any value other than "edit" is treated as read-only and toggles the permissions block so OnlyOffice renders the document with toolbars disabled.
func (*Service) CheckReversePath ¶ added in v0.50.0
func (s *Service) CheckReversePath(ctx context.Context, t *Target) ReverseResult
CheckReversePath asks the document server to download a one-shot URL from filex and reports whether that request arrived and was served. t nil means the configuration in force.
⚠ The verdict is the ARRIVAL. The conversion result is a hint for the message, never the answer: a document server that downloaded our probe and then failed to convert it has still proved the only thing being asked.
func (*Service) Converter ¶ added in v0.50.0
Converter is a conversion client for the document server in force, or ErrNotConfigured.
func (*Service) Diagnose ¶ added in v0.50.0
Diagnose says what this process saw of the document server's requests for the document.
func (*Service) DrawThumbnail ¶ added in v0.50.0
DrawThumbnail asks the document server for a PNG of the document's first page, ThumbSize on its longer side, and downloads it. A failure is a *ConvertError (ErrNotConfigured when OnlyOffice is not configured): a -4 whose download filex itself refused because the file is end-to-end encrypted comes back as ClassEncrypted.
func (*Service) DropSession ¶ added in v0.53.0
DropSession: the person chose the outside version; the session's save, when it comes, is not written.
func (*Service) Enabled ¶
Enabled reports whether the service has the minimum config to run, as of this instant — not as of the last restart.
func (*Service) EnabledCtx ¶
EnabledCtx is Enabled with a caller-supplied context, so the DB read that backs it inherits the request's deadline and cancellation.
func (*Service) HandleCallback ¶
HandleCallback validates the JWT, downloads the saved version, and writes it back to storage.
Returns the OnlyOffice expected JSON envelope.
func (*Service) LastPurposeFetch ¶ added in v0.50.0
func (s *Service) LastPurposeFetch(nodeID int64, purpose string) (PurposeOutcome, bool)
LastPurposeFetch is what filex last answered the document server's fetch of node for purpose (false: it never asked, as far as this process knows).
func (*Service) LiveTarget ¶ added in v0.50.0
LiveTarget is the configuration in force right now.
func (*Service) NoteFetch ¶ added in v0.50.0
NoteFetch records what the fetch endpoint answered for the document. signed says the request's signature checked out: only such a request may add a document to the log; any other only updates a document already in it.
func (*Service) NotePurposeFetch ¶ added in v0.50.0
NotePurposeFetch records what the fetch endpoint answered a SIGNED purpose fetch (an unsigned one is refused before it is anybody's). The oldest entry goes when the record is full.
func (*Service) OfferFile ¶ added in v0.50.0
OfferFile puts the file at path on offer to the document server and answers the address it downloads it from, and the function that withdraws it (call it when the conversion ends, whatever the outcome). name is what the download is called. ErrNotConfigured when OnlyOffice is not configured.
func (*Service) OpenOffered ¶ added in v0.50.0
func (s *Service) OpenOffered(ctx context.Context, token string, exp int64, purpose, sig string) (*os.File, string, error)
OpenOffered checks an offered file's address and opens the file: the file, the name it is offered under, or ErrUnknownPurpose, ErrSignatureExpired, ErrBadSignature or ErrOfferGone.
func (*Service) PruneSessions ¶ added in v0.53.0
PruneSessions removes the records whose session never said it ended, at most once per pruneEvery however often it is called (the server calls it from a maintenance tick).
func (*Service) PurposeFetchURL ¶ added in v0.50.0
func (s *Service) PurposeFetchURL(ctx context.Context, nodeID int64, purpose string) (string, error)
PurposeFetchURL is the address the document server downloads node from for a purpose: the fetch endpoint on the address the document server reaches filex at (callbackBase), signed with the secret in force and the purpose.
func (*Service) RebaseSession ¶ added in v0.53.0
RebaseSession: the person chose to write their version over the outside one. The session's base becomes the version now, so its save is written over it - over THAT version only: a change after this answer makes the session stale again.
func (*Service) ServeFetchProbe ¶ added in v0.50.0
func (s *Service) ServeFetchProbe(ctx context.Context, token string, exp int64, sig string) (int, string)
ServeFetchProbe answers a probe that arrived at the fetch endpoint (a request for node ProbeNodeID carrying a probe token `t`). It returns the HTTP status to answer with and, on 200, the body.
It asks what a document's fetch asks, in the same order: is OnlyOffice configured in filex right now, and is the signature good (the same HMAC, checked by VerifyFetchSignatureCtx). A probe made from the admin page's unsaved values is verified against those values instead, because they are what is being tested. An unknown or expired token is a plain 404 and leaves no trace: a stranger who guesses at it learns nothing.
func (*Service) SessionState ¶ added in v0.53.0
func (s *Service) SessionState(ctx context.Context, node *model.Node, key string) (stale, known bool)
SessionState answers the editor page: is the session with this key still on the document's current version? known is false when filex has no record of it (one that expired, or was opened by a filex older than its table) and the answer comes from the key the document would get now.
func (*Service) SignedFetchURL ¶
SignedFetchURL returns an HMAC-signed URL the document server can use to pull document bytes from filex without authenticating.
The URL embeds node id + expiry + sig=base64-url(HMAC-SHA256(secret, "n=<id>&exp=<exp>")).
func (*Service) ThumbKey ¶ added in v0.50.0
func (s *Service) ThumbKey(ctx context.Context, r ThumbRequest) string
ThumbKey is the conversion key of a thumbnail (ConvertKey): which filex (the address the document server reaches it at), which file, which content, which parameters and which attempt.
func (*Service) VerifyFetchSignature ¶
VerifyFetchSignature validates a query against the fetch HMAC.
func (*Service) VerifyFetchSignatureCtx ¶
VerifyFetchSignatureCtx is VerifyFetchSignature with the request context, so the secret is the one in force now.
func (*Service) VerifyPurposeFetchCtx ¶ added in v0.50.0
func (s *Service) VerifyPurposeFetchCtx(ctx context.Context, nodeID, exp int64, purpose, sig string) error
VerifyPurposeFetchCtx checks a purpose's fetch address against the secret in force: ErrUnknownPurpose, ErrSignatureExpired or ErrBadSignature.
func (*Service) VerifyReversePath ¶
func (s *Service) VerifyReversePath(ctx context.Context) ReverseResult
VerifyReversePath runs the reverse-path check against the configuration in force.
type SpreadsheetLayout ¶ added in v0.50.0
type SpreadsheetLayout struct {
IgnorePrintArea bool `json:"ignorePrintArea"`
Orientation string `json:"orientation,omitempty"`
FitToWidth int `json:"fitToWidth"`
FitToHeight int `json:"fitToHeight"`
GridLines bool `json:"gridLines"`
Headings bool `json:"headings"`
PageSize *LayoutSize `json:"pageSize,omitempty"`
Margins *LayoutMargin `json:"margins,omitempty"`
}
SpreadsheetLayout lays a spreadsheet out on a page before it is converted to a page format (a PDF, a picture). Without it a sheet is cut where the default A4 page ends and a thumbnail shows a corner of the first columns.
type Target ¶ added in v0.50.0
type Target struct {
DocumentServerURL string
Secret string
// CallbackURL is the address the document server is sent to. Empty means
// filex's public URL, as for a document.
CallbackURL string
// Draft marks values that are not the ones in force (the admin page's
// unsaved form). The probe is verified against them rather than against
// the saved configuration, which would refuse a candidate secret.
Draft bool
}
Target is the configuration a reverse-path check runs against.
type ThumbRequest ¶ added in v0.50.0
type ThumbRequest struct {
NodeID int64
StorageID int64
// Name is the file's name; its extension is the type the document server
// is told.
Name string
// ContentSig is the content's fingerprint (what the thumbnail row
// records): a new version of the file is a new conversion key.
ContentSig string
// Attempt counts the tries at this content, from 1: a retry is a new key,
// so a failure the document server cached is not answered again.
Attempt int
// MaxBytes is the largest result accepted (0: 32 MB).
MaxBytes int64
}
ThumbRequest is one document to draw a thumbnail of.
type Thumbnail ¶ added in v0.50.0
type Thumbnail struct {
Aspect int `json:"aspect"`
First bool `json:"first"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
}
Thumbnail asks for a picture of the first page instead of a document (outputtype png, jpg, bmp or gif). Aspect 1 keeps the page's proportions inside Width x Height; First is the first page only.