PageSourceSearch

https://www.developers.thinkfree.com/docs/assets/js/bba0965d.1b3b813c.js

js thinkfree.com collected 2026-10-02 03:29:19 UTC 150,716 bytes, 16 lines download raw bytes

1"use strict";(self.webpackChunkthinkfree_developers_docs=self.webpackChunkthinkfree_developers_docs||[]).push([[38],{2979(e,t,n){n.r(t),n.d(t,{assets:()=>K,contentTitle:()=>Y,default:()=>Z,frontMatter:()=>G,metadata:()=>r,toc:()=>X});const r=JSON.parse('{"id":"api-reference/tfo-http-storage-protocol","title":"HTTP Storage Protocol","description":"HTTP Storage Protocol v1 requests and responses, including URLs, signatures, fields, size limits, operation examples, and errors.","source":"@site/docs/api-reference/tfo-http-storage-protocol.md","sourceDirName":"api-reference","slug":"/api-reference/tfo-http-storage-protocol","permalink":"/docs/api-reference/tfo-http-storage-protocol","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"title":"HTTP Storage Protocol","description":"HTTP Storage Protocol v1 requests and responses, including URLs, signatures, fields, size limits, operation examples, and errors.","keywords":["HTTP Storage","storage provider","JWT","API reference","Self-hosted Office"]},"sidebar":"docsSidebar","previous":{"title":"HTTP Storage","permalink":"/docs/office/http-storage"},"next":{"title":"Create document URLs","permalink":"/docs/office/document-urls"}}');var s=n(4848),i=n(8453),d=n(9696),o=n(6540),c=n(4586),a=n(4164),l=n(7559),h=n(6347),x=n(205),j=n(7485);var u=n(679);function p(e){var t=e.values,n=e.children;return(0,o.useMemo)(function(){var e=null!=t?t:function(e){return o.Children.toArray(e).flatMap(function(e){if(!e)return[];if((0,o.isValidElement)(e)&&(t=e.props)&&"object"==typeof t&&"value"in t)return[e];var t,n="string"==typeof e.type?e.type:e.type.name;throw new Error("Docusaurus error: Bad <Tabs> child <"+n+'>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.\nIf you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.')}).map(function(e){var t=e.props;return{value:t.value,label:t.label,attributes:t.attributes,default:t.default}})}(n);return function(e){var t,n,r=(void 0===(n=function(e,t){return e.value===t.value})&&(n=function(e,t){return e===t}),(t=e).filter(function(e,r){return t.findIndex(function(t){return n(t,e)})!==r}));if(r.length>0)throw new Error('Docusaurus error: Duplicate values "'+r.map(function(e){return"'"+e.value+"'"}).join(", ")+'" found in <Tabs>. Every value needs to be unique.')}(e),e},[t,n])}function m(e){var t=e.value;return e.tabValues.some(function(e){return e.value===t})}function g(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId,s=(0,h.W6)(),i=function(e){var t=e.queryString,n=void 0!==t&&t,r=e.groupId;if("string"==typeof n)return n;if(!1===n)return null;if(!0===n&&!r)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:n,groupId:r});return[(0,j.aZ)(i),(0,o.useCallback)(function(e){if(i){var t=new URLSearchParams(s.location.search);t.set(i,e),s.replace(Object.assign({},s.location,{search:t.toString()}))}},[i,s])]}function f(e){var t,n,r,s,i,d,c=e.defaultValue,a=e.queryString,l=void 0!==a&&a,h=e.groupId,j=p(e),f=(0,o.useState)(function(){return function(e){var t,n=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(n){if(!m({value:n,tabValues:r}))throw new Error('Docusaurus error: The <Tabs> has a defaultValue "'+n+'" but none of its children has the corresponding value. Available values are: '+r.map(function(e){return e.value}).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return n}var s=null!=(t=r.find(function(e){return e.default}))?t:r[0];if(!s)throw new Error("Unexpected error: 0 tabValues");return s.value}({defaultValue:c,tabValues:j})}),y=f[0],v=f[1],T=g({queryString:l,groupId:h}),b=T[0],S=T[1],q=(r=function(e){return e?"docusaurus.tab."+e:null}({groupId:h}.groupId),s=(0,u.Dv)(r),i=s[0],d=s[1],[i,(0,o.useCallback)(function(e){r&&d.set(e)}
1,[r,d])]),w=q[0],k=q[1],P=function(){var e=null!=b?b:w;return m({value:e,tabValues:j})?e:null}();return(0,x.A)(function(){P&&v(P)},[P]),{selectedValue:y,selectValue:(0,o.useCallback)(function(e){if(!m({value:e,tabValues:j}))throw new Error("Can't select invalid tab value="+e);v(e),S(e),k(e)},[S,k,j]),tabValues:j,lazy:null!=(t=e.lazy)&&t,block:null!=(n=e.block)&&n}}var y=(0,o.createContext)(null);function v(){var e=o.useContext(y);if(!e)throw new Error("useTabsContext() must be used within a Tabs component");return e}function T(e){return(0,s.jsx)(y.Provider,{value:e.value,children:e.children})}var b=n(3104),S=n(2303);const q="tabList__CuJ",w="tabItem_LNqP";function k(e){var t=e.className,n=v(),r=n.selectedValue,i=n.selectValue,d=n.tabValues,o=n.block,c=[],l=(0,b.a_)().blockElementScrollPositionUntilNextRender,h=function(e){var t=e.currentTarget,n=c.indexOf(t),s=d[n].value;s!==r&&(l(t),i(s))},x=function(e){var t,n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":var r,s=c.indexOf(e.currentTarget)+1;n=null!=(r=c[s])?r:c[0];break;case"ArrowLeft":var i,d=c.indexOf(e.currentTarget)-1;n=null!=(i=c[d])?i:c[c.length-1]}null==(t=n)||t.focus()};return(0,s.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.A)("tabs",{"tabs--block":o},t),children:d.map(function(e){var t=e.value,n=e.label,i=e.attributes;return(0,s.jsx)("li",Object.assign({role:"tab",tabIndex:r===t?0:-1,"aria-selected":r===t,ref:function(e){c.push(e)},onKeyDown:x,onClick:h},i,{className:(0,a.A)("tabs__item",w,null==i?void 0:i.className,{"tabs__item--active":r===t}),children:null!=n?n:t}),t)})})}function P(e){var t=e.children;return(0,s.jsx)("div",{className:"margin-top--md",children:t})}function R(e){var t=e.className,n=e.children;return(0,s.jsxs)("div",{className:(0,a.A)(l.G.tabs.container,"tabs-container",q),children:[(0,s.jsx)(k,{className:t}),(0,s.jsx)(P,{children:n})]})}function O(e){var t,n=(0,S.A)(),r=f(e);return(0,s.jsx)(T,{value:r,children:(0,s.jsx)(R,{className:e.className,children:(t=e.children,o.Children.toArray(t).filter(function(e){return"\n"!==e}))})},String(n))}const C="tabItem_Ymn6";function N(e){var t=e.children,n=e.className,r=e.hidden;return(0,s.jsx)("div",{role:"tabpanel",className:(0,a.A)(C,n),hidden:r,children:t})}function _(e){var t=e.children,n=e.className,r=e.value,i=v(),d=i.selectedValue,o=i.lazy,c=r===d;return!c&&o?null:(0,s.jsx)(N,{className:n,hidden:!c,children:t})}var E=n(3457);const J=["info","list","get","put","lock","unlock","mkdir","rename","delete"],I=[{value:"node",label:"Node.js",grammar:"javascript",path:"src/routes/storage-router.mjs"},{value:"java",label:"Java",grammar:"java",path:"examples/java/src/main/java/com/thinkfree/storage/web/StorageController.java"},{value:"python",label:"Python",grammar:"python",path:"examples/python/app/api/routes.py"}],U={info:"GET",list:"GET",get:"GET",put:"PUT",lock:"POST",unlock:"POST",mkdir:"POST",rename:"POST",delete:"DELETE"},D=(e,t)=>e.split("\n").map(e=>`${" ".repeat(t)}${e}`).join("\n"),A={info:"sendJson(response, await storageService.info(segments));",list:"sendJson(response, await storageService.list(segments));",get:'const file = await storageService.download(segments);\nresponse.status(200).set({\n  "Content-Type": "application/octet-stream",\n  "Content-Length": file.contentLength,\n  "Cache-Control": "no-store",\n});\nawait pipeline(file.stream, response);',put:"const docId = await storageService.save(segments, body.stagingFile);\nsendJson(response, { docId });",lock:'const owner = parseSingleField(body.bytes, "owner");\nawait storageService.lock(segments, owner);\nresponse.status(204).set("Cache-Control", "no-store").end();',unlock:'const owner = parseSingleField(body.bytes, "owner");\nawait storageService.unlock(segments, owner);\nresponse.status(204).set("Cache-Control", "no-store").end();',mkdir:'const name = requireChildName(parseSingleField(body.bytes, "name"));\nawait storageService.createDirectory(segments, name);\nresponse.status(204).set("Cache-Control", "no-store").end();',rename:'const name = requireChildName(parseSingleField(body.bytes, "name"));\nawait storageService.rename(segments, name);\nresponse.status(204).set("Cache-Control", "no-store").end();',delete:'await storageService.delete(segments);\nresponse.status(204).set("Cache-Control", "no-store").end();'},W={info:"return jsonResponse(HttpStatus.OK, storageService.info(path));",list:"return jsonResponse(HttpStatus.OK, storageService.list(path));",get:"LocalDirectoryStorageService.Download file = storageService.download(path);\nreturn ResponseEntity.ok().cacheControl(CacheControl.noStore())\n        .contentType(MediaType.APPLICATION_OCTET_STREAM)\n        .contentLength(file.contentLength()).body(file.resource());
1",put:'String docId = storageService.save(path, body.stagedFile());\nreturn jsonResponse(HttpStatus.OK, java.util.Map.of("docId", docId));',lock:"String owner = lockRequest(body.bytes()).owner();\nstorageService.lock(path, owner);\nreturn ResponseEntity.noContent().cacheControl(CacheControl.noStore()).build();",unlock:"String owner = lockRequest(body.bytes()).owner();\nstorageService.unlock(path, owner);\nreturn ResponseEntity.noContent().cacheControl(CacheControl.noStore()).build();",mkdir:"String name = nameRequest(body.bytes()).name();\nstorageService.createDirectory(path, name);\nreturn ResponseEntity.noContent().cacheControl(CacheControl.noStore()).build();",rename:"String name = nameRequest(body.bytes()).name();\nstorageService.rename(path, name);\nreturn ResponseEntity.noContent().cacheControl(CacheControl.noStore()).build();",delete:"storageService.delete(path);\nreturn ResponseEntity.noContent().cacheControl(CacheControl.noStore()).build();"},H={info:"return model_response(storage.info(segments))",list:"return model_response(storage.list(segments))",get:'return FileResponse(\n    storage.download(segments),\n    media_type="application/octet-stream", headers=NO_STORE,\n)',put:'doc_id = storage.save(segments, body.staging_file)\nreturn fixed_json_response({"docId": doc_id})',lock:'owner = parse_model(LockRequest, body.content, "owner").owner\nstorage.lock(segments, owner)\nreturn Response(status_code=204, headers=NO_STORE)',unlock:'owner = parse_model(LockRequest, body.content, "owner").owner\nstorage.unlock(segments, owner)\nreturn Response(status_code=204, headers=NO_STORE)',mkdir:'name = parse_model(NameRequest, body.content, "name").name\nstorage.create_directory(segments, name)\nreturn Response(status_code=204, headers=NO_STORE)',rename:'name = parse_model(NameRequest, body.content, "name").name\nstorage.rename(segments, name)\nreturn Response(status_code=204, headers=NO_STORE)',delete:"storage.delete(segments)\nreturn Response(status_code=204, headers=NO_STORE)"};function F(e,t=!1){const n=t?"\uae30\uc874 \uc608\uc81c\uc5d0 \ud544\uc694\ud55c import (\uc774\ubbf8 \uc788\uc73c\uba74 \uc720\uc9c0)":"Required imports in the existing example (keep them if already present)";return"node"===e?[{title:n,grammar:"javascript",code:'import { rm } from "node:fs/promises";\nimport { requirePathSegment } from "../domain/storage-route.mjs";'}]:"python"===e?[{title:n,grammar:"python",code:"from urllib.parse import unquote_to_bytes"}]:[{title:`${n} (StorageController.java)`,grammar:"java",code:"import com.thinkfree.storage.service.StoragePathPolicy;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;"},{title:t?"\uacbd\ub85c \ub9e4\uce6d \uc124\uc815 (\ubcc4\ub3c4 StorageWebConfig.java)":"Path matching configuration (separate StorageWebConfig.java)",grammar:"java",code:"package com.thinkfree.storage.web;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.PathMatchConfigurer;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\nimport org.springframework.web.util.UrlPathHelper;\n\n@Configuration\npublic class StorageWebConfig implements WebMvcConfigurer {\n    @Override\n    public void configurePathMatch(PathMatchConfigurer configurer) {\n        UrlPathHelper paths = new UrlPathHelper();\n        paths.setUrlDecode(false);\n        paths.setRemoveSemicolonContent(false);\n        configurer.setPatternParser(null).setUrlPathHelper(paths);\n    }\n}"}]}function L(e,t,n=!1){if(!J.includes(t))throw new Error(`Unknown HTTP Storage operation: ${t}`);const r=U[t],s=!["info","get"].includes(t),i="put"===t,d=i?"document":["lock","unlock","mkdir","rename"].includes(t)?"json":"none",o=n?{path:"URL \uacbd\ub85c\uc5d0\uc11c \uc811\ub450\uc0ac\uc640 \uc791\uc5c5 \uc774\ub984\uc744 \uc81c\uc678\ud558\uace0, \ud3f4\ub354\uba85\xb7\ud30c\uc77c\uba85\uc758 URL \uc778\ucf54\ub529\uc744 \ud489\ub2c8\ub2e4.",verify:"JWT \uc11c\uba85\xb7\uc720\ud6a8 \uae30\uac04\uc744 \uac80\uc99d\ud558\uace0, \uc694\uccad \uc815\ubcf4\uac00 \uc77c\uce58\ud558\ub294\uc9c0\uc640 JWT\uac00 \uc7ac\uc0ac\uc6a9\ub410\ub294\uc9c0 \ud655\uc778\ud569\ub2c8\ub2e4.",nodeHeader:"X-TFO-Storage-Request-JWT\ub294 request.headers\uc5d0\uc11c \uc77d\uc2b5\ub2c8\ub2e4.",bodyMatch:"body\ub294 \uc218\uc2e0\ud55c \ubcf8\ubb38\uc758 \uae38\uc774\xb7SHA-256\uc744 JWT\uc5d0 \uae30\ub85d\ub41c \uac12\uacfc \ube44\uad50\ud558\ub294 \ub370 \ud544\uc694\ud569\ub2c8\ub2e4.",emptyMatch:"\ubcf8\ubb38\uc774 \uc5c6\ub294 \uc694\uccad\ub3c4 \uae38\uc774 0\uacfc \ube48 \ubcf8\ubb38\uc758 SHA-256\uc774 JWT\uc640 \uc77c\uce58\ud574\uc57c \ud569\ub2c8\ub2e4.",auth:"\uace0\uac1d \uc138\uc158\uc744 \uc0ac\uc6a9\ud55c\ub2e4\uba74 \uc774 \uc704\uce58\uc5d0 client_metadata\uc758 \ud1a0\ud070 \uac80\uc99d\uacfc \ubb38\uc11c\xb7\uc791\uc5c5 \uad8c\ud55c \uac80\uc0ac\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.",empty:"\ubcf8\ubb38\uc774 \uc5c6\uc5b4\uc57c \ud569\ub2c8\ub2e4. \uae38\uc774 0\uacfc \ube48 \ubcf8\ubb38\uc758 SHA-256\uc744 \uc0ac\uc6a9\ud569\ub2c8\ub2e4.",json:"\uc218\uc2e0\ud55c JSON \ubcf8\ubb38\uc758 \ubc14\uc774\ud2b8 \uc218\uc640 SHA-256\uc744 \uad6c\ud569\ub2c8\ub2e4.",upload:"\uc218\uc2e0 \ud06c\uae30\ub97c \uc81c\ud55c\ud558\uba74\uc11c \ud30c\uc77c\uc744 \uc784\uc2dc \uc800\uc7a5\ud558\uace0, \ubc14\uc774\ud2b8 \uc218\uc640 SHA-256\uc744 \uad6c\ud569\ub2c8\ub2e4.",cleanup:"\uac80\uc99d \uc2e4\ud328\xb7\ubbf8\uc9c0\uc6d0 \uc751\ub2f5\xb7\uc800\uc7a5 \uc2e4\ud328\ub85c \ub0a8\uc740 \uc784\uc2dc \ud30c\uc77c\uc744 \uc0ad\uc81c\ud569\ub2c8\ub2e4. \uc774\ubbf8 \uc774\ub3d9\ub41c \ud30c\uc77c\uc740 \uac74\ub108\ub701\ub2c8\ub2e4."}:{path:"Strip the prefix and operation from the raw path; decode each directory/file segment.",verify:"Verify the JWT signature and validity period, match the request details, and check for replay.",nodeHeader:"Read X-TFO-Storage-Request-JWT from request.headers.",bodyMatch:"Compare the received length/SHA-256 in body with the signed JWT values to detect body tampering.",emptyMatch:"Even without a body, length 0 and the empty-body SHA-256 must match the JWT.",auth:"For customer sessions, validate the token in client_metadata and check document/operation permissions here.",empty:"Require no body. Use length 0 and the SHA-256 of the empty body.",json:"Read the original JSON bytes and calculate their length and SHA-256.",upload:"Enforce the size limit while staging the upload and calculating length and SHA-256.",cleanup:"Delete temporary files left after verification failure, an unsupported response, or a failed save; skip files already moved."},c=o[i?"upload":"json"===d?"json":"empty"];if("node"===e){const e=`// ${o.verify}\n// ${o.nodeHeader}\n// ${"none"===d?o.emptyMatch:o.bodyMatch}\nconst signedRequest = await requestVerifier.verify(request, { rawPath }, body);\n// ${o.auth}\n${s?`if (config.unsupportedOperations?.has("${t}")) {\n  return sendOperationNotSupported(response, "${t}");\n}\n`:""}${A[t]}`;return String.raw`// ${r} /tfo-http-storage/v1/{path}/${t}
2router.${r.toLowerCase()}(/^\/tfo-http-storage\/v1\/(?:.+\/)?${t}$/, async function ${t}Document(request, response) {
3  const rawPath = request.originalUrl;
4  if (rawPath.includes("?")) throw new StorageError(400, "Query not allowed");
5  // ${o.path}
6  const encodedPath = rawPath.slice("/tfo-http-storage/v1".length, -"/${t}".length);
7  let segments;
8  try {
9    segments = encodedPath ? encodedPath.slice(1).split("/").map(
10      part => requirePathSegment(decodeURIComponent(part)),
11    ) : [];
12  } catch { throw new StorageError(400, "Invalid document path"); }
13  // ${c}
14  const body = await readRequestBody(request, "${d}", config, stateStore);
15${i?`  try {\n${D(e,4)}\n  } finally {\n    // ${o.cleanup}\n    await rm(body.stagingFile, { force: true });\n  }`:D(e,2)}
16});`}if("java"===e){const e=`// ${o.verify}\nString jwt = request.getHeader("X-TFO-Storage-Request-JWT");\nMap<String, Object> signedRequest = requestVerifier.verify(\n        jwt, request.getMethod(), rawPath,\n        request.getHeader(HttpHeaders.CONTENT_TYPE), body.length(), body.sha256());\n// ${o.auth}\n${s?`if (properties.unsupportedOperations().contains("${t}")) {\n    return operationNotSupported(StorageOperation.${t.toUpperCase()});\n}\n`:""}${W[t]}`;return`@RequestMapping(value = {"/tfo-http-storage/v1/${t}", "/tfo-http-storage/v1/**/${t}"},\n        method = RequestMethod.${r})\npublic ResponseEntity<?> ${t}(HttpServletRequest request) throws Exception {\n    String rawPath = request.getRequestURI();\n    if (request.getQueryString() != null) {\n        throw new StorageException(HttpStatus.BAD_REQUEST, "Query not allowed");\n    }\n    // ${o.path}\n    String encodedPath = rawPath.substring("/tfo-http-storage/v1".length(), rawPath.length() - "/${t}".length());\n    List<String> path = new ArrayList<>();\n    if (!encodedPath.isEmpty()) {\n        for (String part : encodedPath.substring(1).split("/", -1)) {\n            try {\n                String decoded = 
16URLDecoder.decode(part.replace("+", "%2B"), StandardCharsets.UTF_8);\n                path.add(StoragePathPolicy.requireSegment(decoded));\n            } catch (IllegalArgumentException exception) {\n                throw new StorageException(HttpStatus.BAD_REQUEST, "Invalid document path");\n            }\n        }\n    }\n    // ${c}\n    RequestBody body = ${i?"stageDocument":"json"===d?"readJsonBody":"readEmptyBody"}(request);\n${i?`    try {\n${D(e,8)}\n    } finally {\n        // ${o.cleanup}\n        Files.deleteIfExists(body.stagedFile());\n    }`:D(e,4)}\n}`}if("python"===e){const e=`# ${o.verify}\njwt = request.headers.get("X-TFO-Storage-Request-JWT")\nsigned_request = verifier.verify(\n    jwt, request.method, raw_path,\n    request.headers.get("Content-Type"), body.length, body.sha256,\n)\n# ${o.auth}\n${s?`if storage.settings.is_operation_unsupported("${t}"):\n    return fixed_json_response({"code": "${t.toUpperCase()}_NOT_SUPPORTED"}, status_code=501)\n`:""}${H[t]}`;return`@router.${r.toLowerCase()}("/tfo-http-storage/v1/${t}")\n@router.${r.toLowerCase()}("/tfo-http-storage/v1/{document_path:path}/${t}")\nasync def ${t}_document(\n    request: Request, storage: StorageServiceDependency,\n    state_store: StateStoreDependency, verifier: RequestVerifierDependency,\n) -> Response:\n    raw_path = request.scope["raw_path"].decode("ascii")\n    if request.url.query:\n        raise StorageError(400, "Query not allowed")\n    # ${o.path}\n    encoded_path = raw_path[len(PROTOCOL_PREFIX):-len("/${t}")]\n    if re.search(r"%(?![0-9A-Fa-f]{2})", encoded_path):\n        raise StorageError(400, "Invalid percent-encoding")\n    try:\n        segments = tuple(storage.require_segment(unquote_to_bytes(part).decode("utf-8"))\n                         for part in encoded_path[1:].split("/")) if encoded_path else ()\n    except UnicodeDecodeError:\n        raise StorageError(400, "Invalid UTF-8 path") from None\n    # ${c}\n    body = await read_request_body(request, Operation.${t.toUpperCase()}, storage.settings, state_store)\n${i?`    try:\n${D(e,8)}\n    finally:\n        # ${o.cleanup}\n        body.staging_file.unlink(missing_ok=True)`:D(e,4)}`}throw new Error(`Unknown HTTP Storage example language: ${e}`)}const z="endpointBody_dllC";function B(e){var t=e.children;return(0,s.jsx)("div",{className:z,children:t})}function $(e){var t=e.language;return"node"===t?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("p",{children:["\uacf5\uac1c \uc608\uc81c\uc758 ",(0,s.jsx)("code",{children:"storage-router.mjs"}),"\ub97c \uae30\uc900\uc73c\ub85c \ud569\ub2c8\ub2e4. \uae30\uc874 import\uc640 \uacf5\ud1b5 \ud568\uc218\ub97c \uc720\uc9c0\ud558\uace0,"," ",(0,s.jsx)("code",{children:"createStorageRouter"})," \uc548\uc758 \ud504\ub85c\ud1a0\ucf5c \ucc98\ub9ac\uc6a9 ",(0,s.jsx)("code",{children:"router.use"}),"\ub97c \uc544\ub798 \uc791\uc5c5\ubcc4 handler\ub85c \uad50\uccb4\ud569\ub2c8\ub2e4. \uae30\uc874 \uc624\ub958 \ucc98\ub9ac middleware\ub3c4 \uc0ac\uc6a9\ud569\ub2c8\ub2e4."]}),(0,s.jsxs)("p",{children:[(0,s.jsxs)("code",{children:["requestVerifier.verify(request, ","{ rawPath }",", body)"]}),"\ub294 JWT \uc11c\uba85\uacfc \uc720\ud6a8 \uae30\uac04\uc744 \uac80\uc99d\ud558\uace0, JWT\uc5d0 \uae30\ub85d\ub41c \uc694\uccad \uc815\ubcf4\ub97c \uc2e4\uc81c \uc694\uccad\uacfc \ube44\uad50\ud569\ub2c8\ub2e4. ",(0,s.jsx)("code",{children:"body"}),"\ub294 \uc218\uc2e0\ud55c \ubcf8\ubb38\uc758 \uae38\uc774\uc640 SHA-256\uc744 \ub300\uc870\ud558\ub294 \ub370 \ud544\uc694\ud569\ub2c8\ub2e4. \uac80\uc99d \uc608\uc678\uac00 \ubc1c\uc0dd\ud558\uba74 \uc624\ub958 \ucc98\ub9ac middleware\uac00 ",(0,s.jsx)("code",{children:"401 Unauthorized"}),"\ub97c \ubc18\ud658\ud569\ub2c8\ub2e4."]})]}):"java"===t?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("p",{children:["\uacf5\uac1c \uc608\uc81c\uc758 ",(0,s.jsx)("code",{children:"StorageController.java"}),"\uc5d0\uc11c \uae30\uc874 import\xb7\uc0dd\uc131\uc790\xb7\uacf5\ud1b5 \uba54\uc11c\ub4dc\ub294 \uc720\uc9c0\ud558\uace0,"," ",(0,s.jsx)("code",{children:"handle"})," \uba54\uc11c\ub4dc\ub97c \uc544\ub798 \uc791\uc5c5\ubcc4 \uba54\uc11c\ub4dc\ub85c \uad50\uccb4\ud569\ub2c8\ub2e4. \uae30\uc874 \uc608\uc678 \ucc98\ub9ac \ud074\ub798\uc2a4\ub3c4 \uc0ac\uc6a9\ud569\ub2c8\ub2e4."]}),(0,s.jsxs)("p",{children:[(0,s.jsx)("code",{children:"/**/info"}),"\ucc98\ub7fc \uc791\uc5c5 \uc774\ub984\uc73c\ub85c \ub05d\ub098\ub294 \uacbd\ub85c\ub97c \uc0ac\uc6a9\ud558\ub824\uba74 \uc544\ub798 Ant \uacbd\ub85c \ub9e4\uce6d \uc124\uc815\uc774 \ud544\uc694\ud569\ub2c8\ub2e4. \uc124\uc815 \ud074\ub798\uc2a4\ub294 \ubcc4\ub3c4 ",(0,s.jsx)("code",{children:"StorageWebConfig.java"})," \ud30c\uc77c\ub85c \ucd94\uac00\ud569\ub2c8\ub2e4."]}),(0,s.jsxs)("p",{children:[(0,s.jsx)("code",{children:"requestVerifier.verify"}),"\uc5d0 \ud5e4\ub354\uc5d0\uc11c \uc77d\uc740 JWT\uc640 \uba54\uc11c\ub4dc\xb7\uacbd\ub85c\xb7Content-Type\xb7\ubcf8\ubb38 \uae38\uc774\xb7SHA-256\uc744 \uc804\ub2ec\ud569\ub2c8\ub2e4. \uac80\uc99d \uc608\uc678\uac00 \ubc1c\uc0dd\ud558\uba74 \uc608\uc678 \ucc98\ub9ac \ud074\ub798\uc2a4\uac00 ",(0,s.jsx)("code",{children:"401 Unauthorized"}),"\ub97c \ubc18\ud658\ud569\ub2c8\ub2e4."]})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("p",{children:["\uacf5\uac1c \uc608\uc81c\uc758 ",(0,s.jsx)("code",{children:"app/api/routes.py"}),"\uc5d0\uc11c \uae30\uc874 import\xb7\uacf5\ud1b5 \ud568\uc218\xb7\uc758\uc874\uc131 \uc124\uc815\uc740 \uc720\uc9c0\ud558\uace0,"," ",(0,s.jsx)("code",{children:"storage_request"}),"\uc640 \uadf8 route \ub4f1\ub85d\uc744 \uc544\ub798 \uc791\uc5c5\ubcc4 \ud568\uc218\ub85c \uad50\uccb4\ud569\ub2c8\ub2e4. \uae30\uc874 \uc608\uc678 \ucc98\ub9ac \ud568\uc218\ub3c4 \uc0ac\uc6a9\ud569\ub2c8\ub2e4."]}),(0,s.jsxs)("p",{children:[(0,s.jsx)("code",{children:"verifier.verify"}),"\uc5d0 \ud5e4\ub354\uc5d0\uc11c \uc77d\uc740 JWT\uc640 \uba54\uc11c\ub4dc\xb7\uacbd\ub85c\xb7Content-Type\xb7\ubcf8\ubb38 \uae38\uc774\xb7SHA-256\uc744 \uc804\ub2ec\ud569\ub2c8\ub2e4. \uac80\uc99d \uc608\uc678\uac00 \ubc1c\uc0dd\ud558\uba74 \uc608\uc678 \ucc98\ub9ac \ud568\uc218\uac00 ",(0,s.jsx)("code",{children:"401 Unauthorized"}),"\ub97c \ubc18\ud658\ud569\ub2c8\ub2e4."]})]})}function M(e){var t=e.language;return"node"===t?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("p",{children:["Start from the public example's ",(0,s.jsx)("code",{children:"storage-router.mjs"}),". Keep its imports and shared helpers. Inside ",(0,s.jsx)("code",{children:"createStorageRouter"}),", replace the protocol-handling ",(0,s.jsx)("code",{children:"router.use"})," with the operation handlers below. Keep the existing error-handling middleware."]}),(0,s.jsxs)("p",{children:[(0,s.jsxs)("code",{children:["requestVerifier.verify(request, ","{ rawPath }",", body)"]})," verifies the JWT signature and validity period, then compares the signed request details with the actual request. It needs ",(0,s.jsx)("code",{children:"body"})," to check the received body's length and SHA-256. Verification errors are handled by the middleware, which returns ",(0,s.jsx)("code",{children:"401 Unauthorized"}),"."]})]}):"java"===t?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("p",{children:["Start from the public example's ",(0,s.jsx)("code",{children:"StorageController.java"}),". Keep its imports, constructor, and shared methods. Replace ",(0,s.jsx)("code",{children:"handle"})," with the operation methods below, and keep the existing exception handler."]}),(0,s.jsxs)("p",{children:["Routes ending in an operation name, such as ",(0,s.jsx)("code",{children:"/**/info"}),", require the Ant path-matching configuration below. Add the configuration class in a separate ",(0,s.jsx)("code",{children:"StorageWebConfig.java"})," file."]}),(0,s.jsxs)("p",{children:["Pass the JWT header, HTTP method, path, Content-Type, body length, and SHA
16-256 to ",(0,s.jsx)("code",{children:"requestVerifier.verify"}),". Verification errors are handled by the exception handler, which returns ",(0,s.jsx)("code",{children:"401 Unauthorized"}),"."]})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("p",{children:["Start from the public example's ",(0,s.jsx)("code",{children:"app/api/routes.py"}),". Keep its imports, shared helpers, and dependency setup. Replace ",(0,s.jsx)("code",{children:"storage_request"})," and its route registration with the operation functions below. Keep the existing exception handler."]}),(0,s.jsxs)("p",{children:["Pass the JWT header, HTTP method, path, Content-Type, body length, and SHA-256 to ",(0,s.jsx)("code",{children:"verifier.verify"}),". Verification errors are handled by the exception handler, which returns ",(0,s.jsx)("code",{children:"401 Unauthorized"}),"."]})]})}function V(e){var t=e.operation,n="ko"===(0,c.A)().i18n.currentLocale;return(0,s.jsx)("div",{"data-http-storage-example":t,children:(0,s.jsx)(O,{groupId:"http-storage-provider-language",defaultValue:"node",children:I.map(function(e){var r=e.value,i=e.label,d=e.grammar,c=e.path;return(0,s.jsxs)(_,{value:r,label:i,children:[(0,s.jsxs)("p",{children:[n?"\uc774 \uc2a4\ub2c8\ud3ab\uc5d0\uc11c \uc0ac\uc6a9\ud558\ub294 \uacf5\ud1b5 \ud568\uc218\uc640 \uc800\uc7a5\uc18c \uc11c\ube44\uc2a4: ":"Shared helpers and storage service used by this snippet: ",(0,s.jsx)("a",{href:"https://github.com/thinkfree/http-storage-provider/blob/main/"+c,children:n?i+" Provider \uad6c\ud604":i+" Provider source"})]}),"setup"===t&&(n?(0,s.jsx)($,{language:r}):(0,s.jsx)(M,{language:r})),"setup"===t?F(r,n).map(function(e){return(0,s.jsxs)(o.Fragment,{children:[(0,s.jsx)("p",{children:(0,s.jsx)("strong",{children:e.title})}),(0,s.jsx)(E.A,{language:e.grammar,children:e.code})]},e.title)}):(0,s.jsx)(E.A,{language:d,children:L(r,t,n)})]},r)})})})}const G={title:"HTTP Storage Protocol",description:"HTTP Storage Protocol v1 requests and responses, including URLs, signatures, fields, size limits, operation examples, and errors.",keywords:["HTTP Storage","storage provider","JWT","API reference","Self-hosted Office"]},Y="HTTP Storage Protocol",K={},X=[{value:"From document listing to closing",id:"editing-scenario",level:2},{value:"URLs and paths",id:"paths",level:2},{value:"Common request headers",id:"request-headers",level:2},{value:"JWT signing and verification",id:"authentication",level:2},{value:"Verification order",id:"verification-order",level:3},{value:"Client metadata",id:"client-metadata",level:3},{value:"Response framing and size limits",id:"framing",level:2},{value:"Success statuses and bodies",id:"success-responses",level:3},{value:"Metadata fields",id:"metadata",level:2},{value:"Requests and responses by operation",id:"operations",level:2},{value:"Reading the implementation snippets",id:"implementation-snippets",level:3},{value:"info - Retrieve document information",id:"info",level:3},{value:"Request",id:"info-request",level:4},{value:"Implementation example",id:"info-implementation",level:4},{value:"Response",id:"info-response",level:4},{value:"list - List child entries",id:"list",level:3},{value:"Request",id:"list-request",level:4},{value:"Implementation example",id:"list-implementation",level:4},{value:"Response",id:"list-response",level:4},{value:"get - Download a document",id:"get",level:3},{value:"Request",id:"get-request",level:4},{value:"Implementation example",id:"get-implementation",level:4},{value:"Response",id:"get-response",level:4},{value:"put - Save a document",id:"put",level:3},{value:"Request",id:"put-request",level:4},{value:"Save types",id:"save-type",level:4},{value:"Implementation example",id:"put-implementation",level:4},{value:"Response",id:"put-response",level:4},{value:"lock - Lock a document",id:"lock",level:3},{value:"Request",id:"lock-request",level:4},{value:"Implementation example",id:"lock-implementation",level:4},{value:"Response",id:"lock-response",level:4},{value:"unlock - Unlock a document",id:"unlock",level:3},{value:"Request",id:"unlock-request",level:4},{value:"Implementation example",id:"unlock-implementation",level:4},{value:"Response",id:"unlock-response",level:4},{value:"mkdir - Create a directory",id:"mkdir",level:3},{value:"Request",id:"mkdir-request",level:4},{value:"Implementation example",id:"mkdir-implementation",level:4},{value:"Response",id:"mkdir-response",level:4},{value:"rename - Rename an entry",id:"rename",level:3},{value:"Request",id:"rename-request",level:4},{value:"Implementation example",id:"rename-implementation",level:4},{value:"Response",id:"rename-response",level:4},{value:"delete - Delete an entry",id:"delete",level:3},{value:"Request",id:"delete-request",level:4},{value:"Implementation example",id:"delete-implementation",level:4},{value:"Response",id:"delete-response",level:4},{value:"Unsupported operations",id:"declare-an-operation-not-implemented",level:2},{value:"Error responses",id:"errors",level:2}];function Q(e){const t={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"http-storage-protocol",children:"HTTP Storage Protocol"})}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"HTTP Storage Protocol v1"})," defines requests and responses between the Office ",(0,s.jsx)(t.strong,{children:"HTTP Storage"})," adapter and a customer-operated ",(0,s.jsx)(t.strong,{children:"HTTP Storage Provider"}),". You implement the Provider endpoints below; Office sends requests to them. See ",(0,s.jsx)(t.a,{href:"/office/http-storage/",children:"HTTP Storage"})," for connection instructions and examples."]}),"\n",(0,s.jsx)(t.p,{children:"This document defines the methods and paths, request headers and bodies, response fields, and status codes for all nine endpoints. Common authentication, path, and size rules apply to every endpoint. Additional conditions appear in each operation's section."}),"\n",(0,s.jsx)(t.h2,{id:"editing-scenario",children:"From document listing to closing"}),"\n",(0,s.jsxs)(t.p,{children:["First, prepare ",(0,s.jsx)(t.strong,{children:"code that builds a document-open URL"})," in your application. This example assumes that the ",(0,s.jsx)(t.code,{children:"http-remote-directory"})," adapter is connected to a Provider that implements ",(0,s.jsx)(t.code,{children:"list"}),", ",(0,s.jsx)(t.code,{children:"put"}),", ",(0,s.jsx)(t.code,{children:"lock"}),", and ",(0,s.jsx)(t.code,{children:"unlock"}),"."]}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsxs)(t.li,{children:["Choose the Office address and registered adapter name. Send ",(0,s.jsx)(t.code,{children:"/open"})," to the ",(0,s.jsx)(t.strong,{children:"Office address"}),", not the Provider."]}),"\n",(0,s.jsxs)(t.li,{children:["Put the selected document's relative path in the URL path and set ",(0,s.jsx)(t.code,{children:"app"}),", ",(0,s.jsx)(t.code,{children:"docId"}),", and ",(0,s.jsx)(t.code,{children:"user_id"}),"."]}),"\n",(0,s.jsxs)(t.li,{children:["Add a ",(0,s.jsx)(t.code,{children:"userSessionToken"})," issued by your service after checking the user and document access. Serialize the metadata as a JSON string and set ",(0,s.jsx)(t.code,{children:"__tfo_adapter_http_storage_client_metadata"}),"; ",(0,s.jsx)(t.code,{children:"URLSearchParams"})," applies URL encoding once."]}),"\n"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-javascript",children:'function createOpenUrl(documentPath, docId, userSessionToken) {\n  const encodedPath = documentPath.split("/").map(encodeURIComponent).join("/");\n  const url = new URL(\n    `/cloud-office/api/http-remote-directory/${encodedPath}/open`,\n    "https://office.example.com",\n  );\n  url.searchParams.set("app", "WORD_EDITOR");\n  url.searchParams.set("docId", docId);\n  url.searchParams.set("user_id", "example-user");\n  url.searchParams.set("__tfo_adapter_http_storage_client_metadata", JSON.stringify({\n    project: "example-project",\n    userSessionToken,\n  }));\n  return url.toString();\n}\n\nconst openUrl = createOpenUrl(\n  "contracts/sample.docx",\n  "s
16ample01",\n  "example-short-lived-document-session-token",\n);\n'})}),"\n",(0,s.jsxs)(t.p,{children:["The token above is illustrative. Use the value issued by your service when the document is selected. Open the completed ",(0,s.jsx)(t.code,{children:"openUrl"})," in the browser or set it as the Office iframe's ",(0,s.jsx)(t.code,{children:"src"}),". The ",(0,s.jsx)(t.a,{href:"#client-metadata",children:"customer metadata example"})," shows the JSON string, encoded parameter, and complete URL."]}),"\n",(0,s.jsxs)(t.p,{children:["After preparing this URL builder, the sequence below follows ",(0,s.jsx)(t.strong,{children:"listing \u2192 document selection and URL creation/opening \u2192 editing \u2192 saving \u2192 closing"}),". ",(0,s.jsx)(t.code,{children:"list"})," serves the document selection screen; ",(0,s.jsx)(t.code,{children:"info"})," and later operations handle the selected document's ",(0,s.jsx)(t.code,{children:"/open"})," and subsequent work. Listing also requires the customer session and listing permission expected by the Provider."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"TFO and HTTP Storage Adapter are built into the Office framework; you do not need to implement them."})," For the storage integration, the component you must implement and operate is ",(0,s.jsx)(t.strong,{children:"HTTP Storage Provider"}),". Your application builds the document-open URL and supplies the customer session as shown above."]}),"\n",(0,s.jsxs)(t.p,{children:["The Office Adapter signs a JWT containing the document path, request details, and ",(0,s.jsx)(t.code,{children:"request.client_metadata"}),", then places it in the ",(0,s.jsxs)(t.strong,{children:[(0,s.jsx)(t.code,{children:"X-TFO-Storage-Request-JWT"})," header"]})," of each HTTP request to the Provider. Document file bytes travel in the HTTP body rather than the JWT."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"On every HTTP request, the Provider must verify the JWT signature and compare the signed request details with the actual request. You must also validate the customer session and the current permissions for the document and operation."})," Reject failed verification or authorization before accessing storage. Follow ",(0,s.jsx)(t.a,{href:"#authentication",children:"JWT signing and verification"})," for the complete checks."]}),"\n",(0,s.jsx)(d.A,{kind:"editingScenario"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.strong,{children:"Implement each endpoint statelessly: complete the request without depending on intermediate processing state from an earlier request."})," For example, ",(0,s.jsx)(t.code,{children:"get"})," must not rely on temporary state created by an earlier ",(0,s.jsx)(t.code,{children:"info"})," call. It must be able to verify the incoming request and read the document independently."]}),"\n",(0,s.jsxs)(t.p,{children:["The ",(0,s.jsx)(t.code,{children:"lock"})," and ",(0,s.jsx)(t.code,{children:"unlock"})," operations must share the document's lock state and ",(0,s.jsx)(t.code,{children:"owner"}),". You must also retain JWT replay-protection records. These are separate from carrying intermediate request state from ",(0,s.jsx)(t.code,{children:"info"})," to ",(0,s.jsx)(t.code,{children:"get"}),"."]}),"\n",(0,s.jsxs)(t.p,{children:["Closing a document sends no separate ",(0,s.jsx)(t.code,{children:"close"})," or ",(0,s.jsx)(t.code,{children:"stop"})," request to the Provider. An automatic save during closing uses ",(0,s.jsx)(t.code,{children:"put"}),". See ",(0,s.jsx)(t.a,{href:"#save-type",children:"Save types"})," for the meaning of ",(0,s.jsx)(t.code,{children:"save_type: close"}),"."]}),"\n",(0,s.jsxs)(t.p,{children:["Request order and frequency can vary with editor state and collaboration. A document may be saved several times while editing. The diagram shows the last participant finishing a save and closing the document. See ",(0,s.jsx)(t.a,{href:"#declare-an-operation-not-implemented",children:"Unsupported operations"})," for Providers that do not implement ",(0,s.jsx)(t.code,{children:"lock"})," and ",(0,s.jsx)(t.code,{children:"unlock"}),"."]}),"\n",(0,s.jsx)(t.h2,{id:"paths",children:"URLs and paths"}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-text",children:"{PROVIDER_BASE_URL}/tfo-http-storage/v1/{ENCODED_DOCUMENT_PATH}/{OPERATION}\n"})}),"\n",(0,s.jsxs)(t.p,{children:["The Provider base URL is an HTTP or HTTPS address and may include a path prefix. User information, query strings, fragments, and ",(0,s.jsx)(t.code,{children:"."})," or ",(0,s.jsx)(t.code,{children:".."})," path segments are not allowed. Remove any trailing ",(0,s.jsx)(t.code,{children:"/"})," before appending the protocol path."]}),"\n",(0,s.jsxs)(t.p,{children:["Document paths are relative to the Provider root. Percent-encode each UTF-8 path segment separately and join them with ",(0,s.jsx)(t.code,{children:"/"}),". The root has an empty document path, as in ",(0,s.jsx)(t.code,{children:"/tfo-http-storage/v1/list"}),". Do not insert empty intermediate segments or extra ",(0,s.jsx)(t.code,{children:"/"})," characters."]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Example"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Provider base URL"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"https://storage.example.com/office"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Document path"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"contracts/Proposal 2026.docx"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Document information request"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"GET /office/tfo-http-storage/v1/contracts/Proposal%202026.docx/info"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Root listing request"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"GET /office/tfo-http-storage/v1/list"})})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["The Provider must resolve decoded paths only within its storage root. Reject ",(0,s.jsx)(t.code,{children:"."})," and ",(0,s.jsx)(t.code,{children:".."}),", ",(0,s.jsx)(t.code,{children:"/"})," or ",(0,s.jsx)(t.code,{children:"\\"})," inside path segments, control characters, invalid UTF-8, and symbolic links that escape the root. The protocol does not use query parameters, cookies, redirects, or forwarding of arbitrary authentication headers. Proxies must preserve the raw encoded path used for JWT verification."]}),"\n",(0,s.jsx)(t.h2,{id:"request-headers",children:"Common request headers"}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Header"}),(0,s.jsx)(t.th,{children:"Scope"}),(0,s.jsx)(t.th,{children:"Value"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"X-TFO-Storage-Request-JWT"})}),(0,s.jsx)(t.td,{children:"Required on every request"}),(0,s.jsxs)(t.td,{children:["The raw JWT signed according to the rules below, without a ",(0,s.jsx)(t.code,{children:"Bearer "})," prefix"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"put"}),", ",(0,s.jsx)(t.code,{children:"lock"}),", ",(0,s.jsx)(t.code,{children:"unlock"}),", ",(0,s.jsx)(t.code,{children:"mkdir"}),", ",(0,s.jsx)(t.code,{children:"rename"})]}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"application/octet-stream"})," for ",(0,s.jsx)(t.code,{children:"put"}),"; ",(0,s.jsx)(t.code,{children:"application/json"})," for JSON operations"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required on requests with a body"}),(0,s.jsx)(t.td,{children:"A single decimal integer giving the exact body length in bytes"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["Requests for ",(0,s.jsx)(t.code,{children:"info"}),", ",(0,s.jsx)(t.code,{children:"list"}),", ",(0,s.jsx)(t.code,{children:"get"}),", and ",(0,s.jsx)(t.code,{children:"delete"})," have no body or ",(0,s.jsx)(t.code,{children:"Content-Type"}),". Sign them with body length ",(0,s.jsx)(t.code,{children:"0"})," and the SHA-256 digest of an empty body. Do not wrap bodies in multipart or base64, or use chunked uploads or compression. Send JSON as UTF-8 bytes and include their length and SHA-256 digest in the signed JWT."]}),"\n",(0,s.jsx)(t.h2,{id:"authentication",children:"JWT signing and verification"}
16),"\n",(0,s.jsx)(t.p,{children:"Office signs a JWT containing request details with the shared secret. Before reading or modifying a document, the Provider must verify the signature with that secret and compare the signed method, path, and body details with the received request. This detects changes in transit. It must also check the JWT's validity period and unique request ID to reject expired or reused JWTs."}),"\n",(0,s.jsx)(t.p,{children:"Signing does not encrypt document contents. Use HTTPS to protect data in transit, and separately check user authentication and document permissions through your service's session and permission records."}),"\n",(0,s.jsx)(t.p,{children:"Office and the Provider share a random secret of at least 32 UTF-8 bytes per adapter. The HMAC key is the UTF-8 byte representation of this string. Never send the secret in request URLs or bodies."}),"\n",(0,s.jsxs)(t.p,{children:["The JWT uses compact JWS format, signed with ",(0,s.jsx)(t.code,{children:"HS256"}),", and the following header."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-json",children:'{\n  "alg": "HS256",\n  "typ": "tfo-storage-request+jwt"\n}\n'})}),"\n",(0,s.jsxs)(t.p,{children:["The following payload represents a request to save ",(0,s.jsx)(t.code,{children:"contracts/sample.docx"}),". The file size and SHA-256 digest are illustrative; calculate them from the file you send. Generate fresh ",(0,s.jsx)(t.code,{children:"iat"}),", ",(0,s.jsx)(t.code,{children:"exp"}),", and ",(0,s.jsx)(t.code,{children:"jti"})," values for every request."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-json",children:'{\n  "iss": "thinkfree-office",\n  "aud": "tfo-http-storage-provider",\n  "iat": 1789344000,\n  "exp": 1789344060,\n  "jti": "26bc9b5e-9dab-4a78-95d8-b1ae34a5d9eb",\n  "request": {\n    "adapter": "http-remote-directory",\n    "method": "PUT",\n    "path": "/tfo-http-storage/v1/contracts/sample.docx/put",\n    "content_type": "application/octet-stream",\n    "content_length": 231958,\n    "content_sha256": "754464a613ba4d87d1535f69a9b1e3a0a8f84b8277d2bf4d0c74aacceceb23d7",\n    "office_connection_id": "example-connection",\n    "arguments": {\n      "save_type": "save"\n    },\n    "client_metadata": {\n      "project": "example-project",\n      "userSessionToken": "example-short-lived-document-session-token"\n    }\n  }\n}\n'})}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Field"}),(0,s.jsx)(t.th,{children:"Type"}),(0,s.jsx)(t.th,{children:"Required"}),(0,s.jsx)(t.th,{children:"Validation rule"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:["Header ",(0,s.jsx)(t.code,{children:"alg"})]}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"HS256"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:["Header ",(0,s.jsx)(t.code,{children:"typ"})]}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"tfo-storage-request+jwt"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"iss"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"thinkfree-office"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"aud"})}),(0,s.jsx)(t.td,{children:"string or single-element array"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"tfo-http-storage-provider"}),", or an array containing only that value"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"iat"})}),(0,s.jsx)(t.td,{children:"integer"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Issued-at Unix time in seconds, not later than the current time"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"exp"})}),(0,s.jsx)(t.td,{children:"integer"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:["Expiration Unix time in seconds, later than the current time, with ",(0,s.jsx)(t.code,{children:"0 < exp - iat <= 60"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"jti"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"A unique ID for each request"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request"})}),(0,s.jsx)(t.td,{children:"object"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Contains the signed request fields below"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.adapter"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:["Registered adapter name: 1\u2013128 ASCII alphanumeric, ",(0,s.jsx)(t.code,{children:"."}),", ",(0,s.jsx)(t.code,{children:"_"}),", or ",(0,s.jsx)(t.code,{children:"-"})," characters, beginning with an alphanumeric character. Must match the adapter name associated with the secret used to verify the signature"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.method"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Matches the actual uppercase HTTP method"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.path"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Matches the actual raw encoded path, including the Provider base path, without scheme, host, or query"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.content_type"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"When the header is present"}),(0,s.jsxs)(t.td,{children:["Exactly matches the actual ",(0,s.jsx)(t.code,{children:"Content-Type"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.content_length"})}),(0,s.jsx)(t.td,{children:"integer"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:["The actual number of body bytes received, or ",(0,s.jsx)(t.code,{children:"0"})," when there is no body"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.content_sha256"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"SHA-256 of the actual body bytes, as 64 lowercase hexadecimal characters"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.office_connection_id"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"No"}),(0,s.jsx)(t.td,{children:"Identifies an Office connection; do not interpret its internal structure or use it as proof of user authentication"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.arguments"})}),(0,s.jsx)(t.td,{children:"object"}),(0,s.jsx)(t.td,{children:"No"}),(0,s.jsxs)(t.td,{children:["Operation context such as ",(0,s.jsx)(t.code,{children:"save_type"}),"; does not grant access"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.client_metadata"})}),(0,s.jsx)(t.td,{children:"object"}),(0,s.jsx)(t.td,{children:"No"}),(0,s.jsx)(t.td,{children:"Customer-defined JSON object that the Provider can use for user authentication and authorization after signature verification"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["The complete JWT must not exceed 8,192 bytes. See ",(0,s.jsx)(t.a,{href:"#client-metadata",children:"Client metadata"})," for the ",(0,s.jsx)(t.code,{children:"client_metadata"})," input and limits."]}),"\n",(0,s.jsx)(t.h3,{id:"verification-order",children:"Verification order"}),"\n",(0,s.jsxs)(t.ol,{children:["\n",(0,s.jsx)(t.li,{children:"Check that the request method, path, headers, and body size meet the requirements."}),"\n",(0,s.jsxs)(t.li,{children:["Use ",(0,s.jsx)(t.code,{children:"request.adapter"})," to select a configured secret, then verify the original JWT's signature, issuer, audience, and validity period. Use the unverified adapter name only to select the secret."]}),"\n",(0,s.jsx)(t.li,{children:"Compare the signed request fields with the actual request. Calculate the body length and SHA-256 digest from the received bytes."}),"\n",(0,s.jsxs)(t.li,{children:["Check and record ",(0,s.jsx)(t.code,{children:"jti"})," in one atomic operation, and reject reuse of that JWT until ",(0,s.jsx)(t.code,{children:"exp"}),". Multiple Provider instances must share these records."]}),"\n",(0,s.jsxs)(t.li,{children:["Check document access permissions, then perform the operation or return an ",(0,s.jsx)(t.a,{href:"#declare-an-operation-not-implemented",children:"unsupported operation response"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(t.p,{children:["Reject requests that fail verification before accessing storage. Keep the Office and Provider server clocks synchronized. Office must generate fresh ",(0,s.jsx)(t.code,{children:"iat"}),", ",(0,s.jsx)(t.code,{children:"exp"}),", and ",(0,s.jsx)(t.code,{children:"jti"})," values for retries. ",(0,s.jsx)(t.code,{children:"jti"})," is not an idempotency key that prevents duplicate operations, so check storage state if a response is lost."]}),"\n",(0,s.jsx)(d.A,{kind:"verification"}),"\n",(0,s.jsx)(t.h3,{id:"client-metadata",children:"Client metadata"}),"\n",(0,s.jsxs)(t.p,{children:["To pass customer-defined context when opening a document, supply ",(0,s.jsx)(t.code,{children:"__tfo_adapter_http_storage_client_metadata"})," as a JSON ",(0,s.jsx)(t.strong,{children:"string"})," in the Office ",(0,s.jsx)(t.code,{children:"/open"})," parameters. URL-encode it once when using a query string. For a signed document-open ticket, put the same JSON string in ",(0,s.jsx)(t.code,{children:"command.params"})," under that name; unsigned query parameters cannot override the signed value. This is not a Provider endpoint query parameter."]}),"\n",(0,s.jsxs)(t.p,{children:["The request JWT received by the Provider contains the parsed JSON ",(0,s.jsx)(t.strong,{children:"object"})," in ",(0,s.jsx)(t.code,{children:"request.client_metadata"}),"."]}),"\n",(0,s.jsxs)(t.p,{children:["For example, build the following URL to open ",(0,s.jsx)(t.code,{children:"contracts/sample.docx"})," through the ",(0,s.jsx)(t.code,{children:"http-remote-directory"})," adapter. Replace the Office address and document path for your environment, and use a real token issued by your service. The token below is illustrative."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-javascript",children:'const metadata = {\n  project: "example-project",\n  userSessionToken: "example-sh
16ort-lived-document-session-token",\n};\nconst openUrl = new URL(\n  "/cloud-office/api/http-remote-directory/contracts/sample.docx/open",\n  "https://office.example.com",\n);\nopenUrl.searchParams.set("app", "WORD_EDITOR");\nopenUrl.searchParams.set("docId", "sample01");\nopenUrl.searchParams.set("user_id", "example-user");\nopenUrl.searchParams.set(\n  "__tfo_adapter_http_storage_client_metadata",\n  JSON.stringify(metadata),\n);\nconsole.log(openUrl.toString());\n'})}),"\n",(0,s.jsxs)(t.p,{children:["The customer application produces this JSON string with ",(0,s.jsx)(t.code,{children:"JSON.stringify(metadata)"}),":"]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-text",children:'{"project":"example-project","userSessionToken":"example-short-lived-document-session-token"}\n'})}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"URLSearchParams"})," encodes it once and adds the following parameter to ",(0,s.jsx)(t.code,{children:"/open"}),". Do not encode this value again."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-text",children:"__tfo_adapter_http_storage_client_metadata=%7B%22project%22%3A%22example-project%22%2C%22userSessionToken%22%3A%22example-short-lived-document-session-token%22%7D\n"})}),"\n",(0,s.jsxs)(t.p,{children:["The complete document-open URL is shown below. The Provider receives the ",(0,s.jsx)(t.code,{children:"request.client_metadata"})," object inside the JWT, rather than this URL's query string."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-text",children:"https://office.example.com/cloud-office/api/http-remote-directory/contracts/sample.docx/open?app=WORD_EDITOR&docId=sample01&user_id=example-user&__tfo_adapter_http_storage_client_metadata=%7B%22project%22%3A%22example-project%22%2C%22userSessionToken%22%3A%22example-short-lived-document-session-token%22%7D\n"})}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Required"}),(0,s.jsx)(t.th,{children:"Limit"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Metadata input"}),(0,s.jsx)(t.td,{children:"Optional; a Provider's access policy may require it"}),(0,s.jsx)(t.td,{children:"JSON object encoded as a string; the 4,096 UTF-8 byte limit applies only to the original input"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Object key"}),(0,s.jsx)(t.td,{children:"For each field"}),(0,s.jsxs)(t.td,{children:["1\u201364 UTF-16 code units; not blank according to Java ",(0,s.jsx)(t.code,{children:"String.isBlank()"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"String value"}),(0,s.jsx)(t.td,{children:"When present"}),(0,s.jsx)(t.td,{children:"At most 512 UTF-16 code units"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Nesting"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Root object depth 0; each object field value or array element adds 1; maximum 8"})]})]})]}),"\n",(0,s.jsx)(t.p,{children:"The Provider must also check received metadata against the structural limits above."}),"\n",(0,s.jsxs)(t.p,{children:["You can pass a token issued by your service for document access, such as ",(0,s.jsx)(t.code,{children:"userSessionToken"})," above, so the Provider can authenticate the user. After verifying the JWT, the Provider checks this token against your service's session records to verify the user, expiration, target document, and allowed operations. ",(0,s.jsx)(t.code,{children:"userSessionToken"})," is an example field name; your service implements token issuance and verification."]}),"\n",(0,s.jsxs)(t.p,{children:["Check permissions on every request: ",(0,s.jsx)(t.code,{children:"info"})," and ",(0,s.jsx)(t.code,{children:"get"})," require read permission, and ",(0,s.jsx)(t.code,{children:"put"})," requires write permission. Reject requests that require this token if it is missing, expired, revoked, or lacks permission for the operation."]}),"\n",(0,s.jsx)(t.p,{children:"Metadata is not encrypted: it can appear in the open URL, connection state, and request JWT. Do not include login cookies, refresh tokens, shared signing secrets, long-lived credentials, or document contents. If metadata carries an access capability, issue a separate short-lived value scoped to the document and allowed operations, and redact it from logs."}),"\n",(0,s.jsx)(t.h2,{id:"framing",children:"Response framing and size limits"}),"\n",(0,s.jsxs)(t.p,{children:["Successful ",(0,s.jsx)(t.code,{children:"info"}),", ",(0,s.jsx)(t.code,{children:"list"}),", ",(0,s.jsx)(t.code,{children:"get"}),", and ",(0,s.jsx)(t.code,{children:"put"})," responses must have ",(0,s.jsx)(t.strong,{children:"exactly one"})," decimal ",(0,s.jsx)(t.code,{children:"Content-Length"})," header matching the number of bytes actually sent. ",(0,s.jsx)(t.code,{children:"Transfer-Encoding"})," and ",(0,s.jsx)(t.code,{children:"Content-Encoding"})," are not allowed. A charset parameter in the media type is allowed, but JSON bodies must be UTF-8."]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Target"}),(0,s.jsx)(t.th,{children:"Content-Type"}),(0,s.jsx)(t.th,{children:"Limit"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:["Successful ",(0,s.jsx)(t.code,{children:"info"}),", ",(0,s.jsx)(t.code,{children:"list"}),", and ",(0,s.jsx)(t.code,{children:"put"})," responses"]}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"application/json"})}),(0,s.jsx)(t.td,{children:"5 MiB each (5,242,880 bytes)"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:["Successful ",(0,s.jsx)(t.code,{children:"get"})," response"]}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"application/octet-stream"})}),(0,s.jsx)(t.td,{children:"300 MiB (314,572,800 bytes)"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"put"})," request"]}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"application/octet-stream"})}),(0,s.jsx)(t.td,{children:"300 MiB (314,572,800 bytes)"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:["File ",(0,s.jsx)(t.code,{children:"size"})," in metadata"]}),(0,s.jsx)(t.td,{children:"JSON integer"}),(0,s.jsx)(t.td,{children:"0\u2013314,572,800"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"entries"})," in ",(0,s.jsx)(t.code,{children:"list"})]}),(0,s.jsx)(t.td,{children:"JSON array"}),(0,s.jsx)(t.td,{children:"At most 10,000 entries, with the complete JSON also limited to 5 MiB"})]})]})]}),"\n",(0,s.jsx)(t.p,{children:"The limits in the table are inclusive. A Provider may impose a lower document size limit."}),"\n",(0,s.jsx)(t.p,{children:"Office fails the operation if headers, size, or actual body length do not meet these requirements. Unsupported operation responses and ordinary errors follow their respective sections below."}),"\n",(0,s.jsx)(t.h3,{id:"success-responses",children:"Success statuses and bodies"}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Operation"}),(0,s.jsx)(t.th,{children:"Normal response"}),(0,s.jsx)(t.th,{children:"Body format"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"info"}),", ",(0,s.jsx)(t.code,{children:"list"})]}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"200 OK"})}),(0,s.jsxs)(t.td,{children:["A JSON object is required; do not substitute an empty body or ",(0,s.jsx)(t.code,{children:"204"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"get"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"200 OK"})}),(0,s.jsxs)(t.td,{children:["The complete file bytes; return an empty file with ",(0,s.jsx)(t.code,{children:"Content-Length: 0"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"put"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"200 OK"})}),(0,s.jsxs)(t.td,{children:["A JSON object with ",(0,s.jsx)(t.code,{children:"docId"})," is required. See ",(0,s.jsx)(t.a,{href:"#put-response",children:"PUT response"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"lock"}),", ",(0,s.jsx)(t.code,{children:"unlock"}),", ",(0,s.jsx)(t.code,{children:"mkdir"}),", ",(0,s.jsx)(t.code,{children:"rename"}),", ",(0,s.jsx)(t.code,{children:"delete"})]}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"204 No Content"})}),(0,s.jsx)(t.td,{children:"No body"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["Office also accepts other ",(0,s.jsx)(t.code,{children:"2xx"})," statuses, but ",(0,s.jsx)(t.code,{children:"info"}),", ",(0,s.jsx)(t.code,{children:"list"}),", ",(0,s.jsx)(t.code,{children:"get"}),", and ",(0,s.jsx)(t.code,{children:"put"})," must pass response body validation. The remaining operations require no result fields, so use ",(0,s.jsx)(t.code,{children:"204"})," without a body. If result text is needed, return ",(0,s.jsx)(t.code,{children:"200"})," with ",(0,s.jsx)(t.code,{children:"text/plain; charset=utf-8"}),", up to 10 MiB."]}),"\n",(0,s.jsx)(t.h2,{id:"metadata",children:"Metadata fields"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"info"})," returns one entry object; ",(0,s.jsx)(t.code,{children:"list"}
16)," returns one object containing an ",(0,s.jsx)(t.code,{children:"entries"})," array. Do not add fields other than those below to entry objects."]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Field"}),(0,s.jsx)(t.th,{children:"Type"}),(0,s.jsx)(t.th,{children:"Required"}),(0,s.jsx)(t.th,{children:"Meaning and limits"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Decoded path relative to the root, at most 4,096 characters. The root uses an empty string"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"name"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"A 1\u2013255 character name matching the final path segment. The root uses a fixed display name"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"type"})}),(0,s.jsx)(t.td,{children:"string"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"file"})," or ",(0,s.jsx)(t.code,{children:"directory"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"size"})}),(0,s.jsx)(t.td,{children:"integer"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsxs)(t.td,{children:["File length in bytes, 0\u2013314,572,800. Must be ",(0,s.jsx)(t.code,{children:"0"})," for directories"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"readable"})}),(0,s.jsx)(t.td,{children:"boolean"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Whether the document can be read"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"writable"})}),(0,s.jsx)(t.td,{children:"boolean"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Whether the document can be written"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"locked"})}),(0,s.jsx)(t.td,{children:"boolean"}),(0,s.jsx)(t.td,{children:"Yes"}),(0,s.jsx)(t.td,{children:"Current lock state"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"locker"})}),(0,s.jsx)(t.td,{children:"string or null"}),(0,s.jsx)(t.td,{children:"No"}),(0,s.jsx)(t.td,{children:"Lock owner, at most 255 characters"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"createdAt"})}),(0,s.jsx)(t.td,{children:"string or null"}),(0,s.jsx)(t.td,{children:"No"}),(0,s.jsx)(t.td,{children:"RFC 3339 creation time, at most 64 characters"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"modifiedAt"})}),(0,s.jsx)(t.td,{children:"string or null"}),(0,s.jsx)(t.td,{children:"No"}),(0,s.jsx)(t.td,{children:"RFC 3339 modification time, at most 64 characters"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"revision"})}),(0,s.jsx)(t.td,{children:"string or null"}),(0,s.jsx)(t.td,{children:"No"}),(0,s.jsx)(t.td,{children:"Storage revision or ETag-equivalent value, at most 1,024 characters"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"info.path"})," must match the requested document path. ",(0,s.jsx)(t.code,{children:"list"})," contains only the requested directory's immediate children, with no duplicate paths. Pagination is not supported."]}),"\n",(0,s.jsxs)(t.p,{children:["Required fields must not be ",(0,s.jsx)(t.code,{children:"null"}),". Optional fields may be omitted or returned as ",(0,s.jsx)(t.code,{children:"null"}),". ",(0,s.jsx)(t.code,{children:"path"})," is decoded, not URL-encoded. ",(0,s.jsx)(t.code,{children:"createdAt"})," and ",(0,s.jsx)(t.code,{children:"modifiedAt"})," include a time zone, as in ",(0,s.jsx)(t.code,{children:"2026-09-14T00:00:00Z"}),". ",(0,s.jsx)(t.code,{children:"readable"})," and ",(0,s.jsx)(t.code,{children:"writable"})," describe an entry's accessibility; the Provider must check permissions again on each request. ",(0,s.jsx)(t.code,{children:"revision"})," describes the storage version and is not automatically used as a conditional request header or lock token in this protocol."]}),"\n",(0,s.jsxs)(t.p,{children:["To validate the JSON response format, use the ",(0,s.jsx)(t.a,{href:"https://github.com/thinkfree/http-storage-provider/blob/main/schemas/v1/entry.schema.json",children:"entry schema"}),", ",(0,s.jsx)(t.a,{href:"https://github.com/thinkfree/http-storage-provider/blob/main/schemas/v1/info-response.schema.json",children:"info response schema"}),", and ",(0,s.jsx)(t.a,{href:"https://github.com/thinkfree/http-storage-provider/blob/main/schemas/v1/list-response.schema.json",children:"list response schema"}),". Schema validation alone cannot verify every relationship between the request and response paths. Also check the path conditions and size limits above."]}),"\n",(0,s.jsx)(t.h2,{id:"operations",children:"Requests and responses by operation"}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"path"})," identifies the target entry, and ",(0,s.jsx)(t.code,{children:"parent"})," identifies the parent for a new directory. Both are ",(0,s.jsx)(t.strong,{children:"relative paths under the Provider root"}),". Encode each segment according to the ",(0,s.jsx)(t.a,{href:"#paths",children:"URL rules"}),", rather than encoding the entire path. Every request requires the ",(0,s.jsx)(t.code,{children:"X-TFO-Storage-Request-JWT"}
16)," header described in ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common request headers"}),". Query parameters are not supported."]}),"\n",(0,s.jsxs)(t.p,{children:["The request bodies for ",(0,s.jsx)(t.code,{children:"lock"}),", ",(0,s.jsx)(t.code,{children:"unlock"}),", ",(0,s.jsx)(t.code,{children:"mkdir"}),", and ",(0,s.jsx)(t.code,{children:"rename"})," are JSON objects containing only the field specified for that operation. Required fields cannot be omitted, ",(0,s.jsx)(t.code,{children:"null"}),", or another type, and extra fields are not allowed. The request ",(0,s.jsx)(t.code,{children:"Content-Type"})," must be exactly ",(0,s.jsx)(t.code,{children:"application/json"}),", and ",(0,s.jsx)(t.code,{children:"Content-Length"})," is the length of the UTF-8 serialized bytes."]}),"\n",(0,s.jsxs)(t.p,{children:["Each operation's error table describes its specific conditions. The ",(0,s.jsx)(t.a,{href:"#errors",children:"common errors"})," for paths, authentication, permissions, body formats, and storage failures also apply to every endpoint."]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Operation"}),(0,s.jsx)(t.th,{children:"Method"}),(0,s.jsx)(t.th,{children:"Path suffix"}),(0,s.jsx)(t.th,{children:"Implementation"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#info",children:"info"})}),(0,s.jsx)(t.td,{children:"GET"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{path}/info"})}),(0,s.jsx)(t.td,{children:"Required"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#list",children:"list"})}),(0,s.jsx)(t.td,{children:"GET"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{path}/list"})}),(0,s.jsx)(t.td,{children:"Optional"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#get",children:"get"})}),(0,s.jsx)(t.td,{children:"GET"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{path}/get"})}),(0,s.jsx)(t.td,{children:"Required"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#put",children:"put"})}),(0,s.jsx)(t.td,{children:"PUT"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{path}/put"})}),(0,s.jsx)(t.td,{children:"Optional"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#lock",children:"lock"})}),(0,s.jsx)(t.td,{children:"POST"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{path}/lock"})}),(0,s.jsxs)(t.td,{children:["Implement with ",(0,s.jsx)(t.code,{children:"unlock"}),", or declare both unsupported"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#unlock",children:"unlock"})}),(0,s.jsx)(t.td,{children:"POST"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{path}/unlock"})}),(0,s.jsxs)(t.td,{children:["Implement with ",(0,s.jsx)(t.code,{children:"lock"}),", or declare both unsupported"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#mkdir",children:"mkdir"})}),(0,s.jsx)(t.td,{children:"POST"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{parent}/mkdir"})}),(0,s.jsx)(t.td,{children:"Optional"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#rename",children:"rename"})}),(0,s.jsx)(t.td,{children:"POST"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{path}/rename"})}),(0,s.jsx)(t.td,{children:"Optional"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.a,{href:"#delete",children:"delete"})}),(0,s.jsx)(t.td,{children:"DELETE"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"/{path}/delete"})}),(0,s.jsx)(t.td,{children:"Optional"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["The examples below use a Provider at ",(0,s.jsx)(t.code,{children:"https://storage.example.com"})," to read and save ",(0,s.jsx)(t.code,{children:"contracts/sample.docx"}),", rename it to ",(0,s.jsx)(t.code,{children:"reviewed.docx"}),", and then delete it. Replace ",(0,s.jsx)(t.code,{children:"<SIGNED_REQUEST_JWT>"})," in every request with a ",(0,s.jsx)(t.strong,{children:"fresh JWT signing that request's method, path, and body"}),". Example bodies exclude the final newline in the code block; the displayed ",(0,s.jsx)(t.code,{children:"Content-Length"})," values reflect this. In the ",(0,s.jsx)(t.code,{children:"get"})," response and ",(0,s.jsx)(t.code,{children:"put"})," request, ",(0,s.jsx)(t.code,{children:"<DOCX binary>"})," represents the omitted DOCX file bytes. Send the raw file bytes in place of this text and replace ",(0,s.jsx)(t.code,{children:"<DOCX_BYTE_LENGTH>"})," with their actual byte count."]}),"\n",(0,s.jsx)(t.h3,{id:"implementation-snippets",children:"Reading the implementation snippets"}),"\n",(0,s.jsx)(t.p,{children:"The Node.js (Express), Java (Spring MVC), and Python (FastAPI) snippets show request parsing, JWT verification, storage c
16alls, and responses for each operation. Selecting a language changes all snippet tabs together."}),"\n",(0,s.jsxs)(t.p,{children:["These snippets use the helpers and configuration from the ",(0,s.jsx)(t.a,{href:"https://github.com/thinkfree/http-storage-provider",children:"public Provider examples"}),"; they are not standalone programs. Each language tab explains where to place the code and which imports and configuration it needs."]}),"\n",(0,s.jsx)(t.p,{children:"Implement storage access and customer-session and document-permission checks for your service. Use the original URL-encoded request path for JWT verification."}),"\n",(0,s.jsx)(V,{operation:"setup"}),"\n",(0,s.jsx)(t.p,{children:"If JWT verification fails, return an error before accessing storage. If you use customer sessions, add user authentication and document/operation permission checks where indicated by the comments."}),"\n",(0,s.jsx)(t.h3,{id:"info",children:"info - Retrieve document information"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsx)(t.p,{children:"This required endpoint retrieves metadata for a document or directory. It does not return document contents."}),(0,s.jsx)(t.h4,{id:"info-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"GET /tfo-http-storage/v1/{path}/info"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"GET /tfo-http-storage/v1/contracts/sample.docx/info HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsxs)(t.td,{children:["Relative file or directory path. Use ",(0,s.jsx)(t.code,{children:"/tfo-http-storage/v1/info"})," for the root"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this GET request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Request body"}),(0,s.jsx)(t.td,{children:"None"}),(0,s.jsxs)(t.td,{children:["Omit ",(0,s.jsx)(t.code,{children:"Content-Type"}),". Follow the ",(0,s.jsx)(t.a,{href:"#request-headers",children:"common request rules"})]})]})]})]}),(0,s.jsx)(t.h4,{id:"info-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"info"}),(0,s.jsx)(t.h4,{id:"info-response",children:"Response"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:'HTTP/1.1 200 OK\nContent-Type: application/json\nContent-Length: 283\n\n{\n  "path": "contracts/sample.docx",\n  "name": "sample.docx",\n  "type": "file",\n  "size": 231958,\n  "readable": true,\n  "writable": true,\n  "locked": false,\n  "locker": null,\n  "createdAt": "2026-09-14T00:00:00Z",\n  "modifiedAt": "2026-09-14T00:10:00Z",\n  "revision": "revision-17"\n}\n'})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"200 OK"})}),(0,s.jsx)(t.td,{children:"Target entry metadata retrieved successfully"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"application/json"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["Response JSON length in bytes, subject to the ",(0,s.jsx)(t.a,{href:"#framing",children:"size limit"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"object"}
16),(0,s.jsxs)(t.td,{children:["One entry object containing the ",(0,s.jsx)(t.a,{href:"#metadata",children:"metadata fields"})," directly, without an ",(0,s.jsx)(t.code,{children:"entry"})," or ",(0,s.jsx)(t.code,{children:"data"})," wrapper"]})]})]})]}),(0,s.jsxs)(t.p,{children:["Follow the ",(0,s.jsx)(t.a,{href:"#metadata",children:"metadata field requirements"}),". For the root, use ",(0,s.jsx)(t.code,{children:'path: ""'}),", ",(0,s.jsx)(t.code,{children:'type: "directory"'}),", and ",(0,s.jsx)(t.code,{children:"size: 0"}),"."]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Conditions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"404 Not Found"})}),(0,s.jsx)(t.td,{children:"The target does not exist. Office treats the entry as absent and does not require fabricated metadata"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"413 Payload Too Large"})}),(0,s.jsx)(t.td,{children:"File size or response JSON exceeds its limit"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsx)(t.td,{children:"Not accepted as an unsupported capability because this endpoint is required"})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"list",children:"list - List child entries"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsx)(t.p,{children:"This optional endpoint lists the immediate files and directories within a directory. It provides neither recursive listing nor pagination."}),(0,s.jsx)(t.h4,{id:"list-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"GET /tfo-http-storage/v1/{path}/list"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"GET /tfo-http-storage/v1/contracts/list HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsxs)(t.td,{children:["Directory to list. Use ",(0,s.jsx)(t.code,{children:"/tfo-http-storage/v1/list"})," for the root"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this GET request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Request body"}),(0,s.jsx)(t.td,{children:"None"}),(0,s.jsxs)(t.td,{children:["Omit ",(0,s.jsx)(t.code,{children:"Content-Type"}),". Follow the ",(0,s.jsx)(t.a,{href:"#request-headers",children:"common request rules"})]})]})]})]}),(0,s.jsx)(t.h4,{id:"list-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"list"}),(0,s.jsx)(t.h4,{id:"list-response",children:"Response"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:'HTTP/1.1 200 OK\nContent-Type: application/json\nContent-Length: 358\n\n{\n  "entries": [\n    {\n      "path": "contracts/sample.docx",\n      "name": "sample.docx",\n      "type": "file",\n      "size": 231958,\n      "readable": true,\n      "writable": true,\n      "locked": false,\n      "locker": null,\n      "createdAt": "2026-09-14T00:00:00Z",\n      "modifiedAt": "2026-09-14T00:10:00Z",\n      "revision": "revision-17"\n    }\n  ]\n}\n'})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"200 OK"})}),(0,s.jsx)(t.td,{children:"Directory listed successfully, including an empty directory"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"application/json"})})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["Complete JSON length in bytes, subject to the ",(0,s.jsx)(t.a,{href:"#framing",children:"size limit"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"entries"})}),(0,s.jsx)(t.td,{children:"Body \xb7 array \xb7 required"}),(0,s.jsxs)(t.td,{children:["0\u201310,000 ",(0,s.jsx)(t.a,{href:"#metadata",children:"entry objects"}),"; the only field in the top-level object"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"entries[]"})}),(0,s.jsx)(t.td,{children:"object"}),(0,s.jsxs)(t.td,{children:["Same required and optional fields as ",(0,s.jsx)(t.code,{children:"info"}),". Each decoded ",(0,s.jsx)(t.code,{children:"path"})," must be an immediate child of the listed directory, without duplicates"]})]})]})]}),(0,s.jsxs)(t.p,{children:["Entry paths are always relative to the root: return ",(0,s.jsx)(t.code,{children:"contracts"})," in a root listing and ",(0,s.jsx)(t.code,{children:"contracts/sample.docx"})," in a listing of ",(0,s.jsx)(t.code,{children:"contracts"}),". Do not include the directory itself, its parent, or entries inside subdirectories. Ordering is unspecified. Do not send additional fields such as ",(0,s.jsx)(t.code,{children:"total"}),", ",(0,s.jsx)(t.code,{children:"cursor"}),", or ",(0,s.jsx)(t.code,{children:"hasMore"}),"."]}),(0,s.jsxs)(t.p,{children:["For an empty directory, return ",(0,s.jsx)(t.code,{children:"200"})," with an empty ",(0,s.jsx)(t.code,{children:"entries"})," array. Return ",(0,s.jsx)(t.code,{children:"404"})," if the directory does not exist, or ",(0,s.jsx)(t.code,{children:"501"})," with ",(0,s.jsx)(t.code,{children:"LIST_NOT_SUPPORTED"})," after authentication if listing is unsupported."]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Conditions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"404 Not Found"})}),(0,s.jsx)(t.td,{children:"The directory to list does not exist"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"413 Payload Too Large"})}),(0,s.jsx)(t.td,{children:"Entry count, metadata size, or an included file size exceeds its limit. Do not return a partial listing as successful"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsxs)(t.td,{children:["After authentication, ",(0,s.jsx)(t.code,{children:"LIST_NOT_SUPPORTED"}),". File listing is unavailable in Office"]})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"get",children:"get - Download a document"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsx)(t.p,{children:"This required endpoint returns the complete original file bytes. Office reads this content to open the document."}),(0,s.jsx)(t.h4,{id:"get-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"GET /tfo-http-storage/v1/{path}/get"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"GET /tfo-http-storage/v1/contracts/sample.docx/get HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsxs)(t.td,{children:["File to download. Use ",(0,s.jsx)(t.code,{children:"list"})," to list a directory"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this GET request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Request body"}),(0,s.jsx)(t.td,{children:"None"}),(0,s.jsxs)(t.td,{children:["Omit ",(0,s.jsx)(t.code,{children:"Content-Type"}),". Follow the ",(0,s.jsx)(t.a,{href:"#request-headers",children:"common request rules"})]})]})]})]}),(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"Range"})," requests for partial downloads are not supported. Return the file itself, rather than a download URL, JSON, or Base64."]}),(0,s.jsx)(t.h4,{id:"get-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"get"}),(0,s.jsx)(t.h4,{id:"get-response",children:"Response"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"HTTP/1.1 200 OK\nContent-Type: application/octet-stream\nContent-Length: <DOCX_BYTE_LENGTH>\n\n<DOCX binary>\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"200 OK"})}),(0,s.jsx)(t.td,{children:"Complete file downloaded successfully"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"application/octet-stream"}),", regardless of file extension"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsx)(t.td,{children:"Actual file length in bytes, 0\u2013314,572,800"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"binary"}),(0,s.jsxs)(t.td,{children:["Original file bytes of exactly ",(0,s.jsx)(t.code,{children:"Content-Length"}),"; no JSON fields"]})]})]})]}),(0,s.jsxs)(t.p,{children:["Return an empty file with ",(0,s.jsx)(t.code,{children:"200"})," and ",(0,s.jsx)(t.code,{children:"Content-Length: 0"}),". Return the same version of the file throughout the download."]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Conditions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"404 Not Found"})}),(0,s.jsx)(t.td,{children:"The file does not exist"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"413 Payload Too Large"})}),(0,s.jsx)(t.td,{children:"The file exceeds the size limit. Reject it before sending success headers or file bytes"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsx)(t.td,{children:"Not accepted as an unsupported capability because this endpoint is required"})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"put",children:"put - Save a document"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsx)(t.p,{children:"Receives the complete edited file in the HTTP body and saves it. Partial updates and multipart uploads are not supported. Verify the request before saving, and preserve the existing file if verification or saving fails."}),(0,s.jsx)(t.h4,{id:"put-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"PUT /tfo-http-storage/v1/{path}/put"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"PUT /tfo-http-storage/v1/contracts/sample.docx/put HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\nContent-Type: application/octet-stream\nContent-Length: <DOCX_BYTE_LENGTH>\n\n<DOCX binary>\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsx)(t.td,{children:"Relative path of the file to save. An empty path cannot over
16write the Provider root"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this PUT request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"application/octet-stream"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsx)(t.td,{children:"Actual complete file length in bytes, 0\u2013314,572,800"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Request body"}),(0,s.jsx)(t.td,{children:"binary"}),(0,s.jsx)(t.td,{children:"Complete file bytes, not a JSON object, Base64, or multipart data"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"request.arguments.save_type"})}),(0,s.jsx)(t.td,{children:"JWT \xb7 string \xb7 optional"}),(0,s.jsxs)(t.td,{children:["Why Office is saving the file. See ",(0,s.jsx)(t.a,{href:"#save-type",children:"Save types"}),". Included in the JWT, not in the body or query parameters"]})]})]})]}),(0,s.jsx)(t.h4,{id:"save-type",children:"Save types"}),(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"request.arguments.save_type"})," indicates what triggered the save. Office sends the following values; spelling and capitalization matter."]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Value"}),(0,s.jsx)(t.th,{children:"Meaning"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"save"})}),(0,s.jsx)(t.td,{children:"A regular save, such as when the user selects Save"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"saveAs"})}),(0,s.jsxs)(t.td,{children:["Save As, writing the document to the destination in this request's ",(0,s.jsx)(t.code,{children:"path"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"autoSave"})}),(0,s.jsx)(t.td,{children:"Automatic saving during editing"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"autoput"})}),(0,s.jsx)(t.td,{children:"Server-triggered saving after an editing connection expires, for example after the browser disconnects"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"close"})}),(0,s.jsx)(t.td,{children:"An automatic save triggered while closing the document"})]})]})]}),(0,s.jsx)(t.p,{children:"This field is optional. Process the save even when it is absent; absence does not imply a particular save type."}),(0,s.jsxs)(t.p,{children:["When closing a document triggers an automatic save, Office calls ",(0,s.jsx)(t.code,{children:"put"})," with ",(0,s.jsx)(t.code,{children:"request.arguments.save_type"})," set to ",(0,s.jsx)(t.code,{children:"close"}),". ",(0,s.jsx)(t.strong,{children:"This value describes why the save occurred; it is not a document-close event."})," The Provider must save the file. It must not call ",(0,s.jsx)(t.code,{children:"dispose"}),", release connection or session resources, or assume that no further requests will arrive just because the save type is ",(0,s.jsx)(t.code,{children:"close"}),"."]}),(0,s.jsx)(t.h4,{id:"put-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"put"}),(0,s.jsx)(t.h4,{id:"put-response",children:"Response"}),(0,s.jsx)(t.p,{children:"After committing the file to storage, return the saved document's identifier as a JSON object."}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:'HTTP/1.1 200 OK\nContent-Type: application/json\nContent-Length: 34\n\n{\n  "docId": "saved-document-id"\n}\n'})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"200 OK"})}),(0,s.jsxs)(t.td,{children:["Other ",(0,s.jsx)(t.code,{children:"2xx"})," statuses must pass body validation. ",(0,s.jsx)(t.code,{children:"204 No Content"})," is not allowed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["UTF-8 ",(0,s.jsx)(t.code,{children:"application/json"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["Exact JSON body byte count. The ",(0,s.jsx)(t.a,{href:"#framing",children:"fixed-length rules"})," apply"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"JSON object \xb7 required"}),(0,s.jsxs)(t.td,{children:["Contains only ",(0,s.jsx)(t.code,{children:"docId"}),". Extra or duplicate fields, trailing JSON values, and trailing text are not allowed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"docId"})}),(0,s.jsx)(t.td,{children:"string \xb7 required"}),(0,s.jsxs)(t.td,{children:["1\u20131,024 ASCII letters, digits, ",(0,s.jsx)(t.code,{children:"."}),", ",(0,s.jsx)(t.code,{children:"_"}),", ",(0,s.jsx)(t.code,{children:":"}),", or ",(0,s.jsx)(t.code,{children:"-"}),", starting with a letter or digit. ",(0,s.jsx)(t.code,{children:"true"})," and ",(0,s.jsx)(t.code,{children:"false"})," are reserved regardless of case"]})]})]})]}),(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"docId"})," identifies the saved destination. Within an adapter, the same destination keeps the same ID when its contents change, and different destinations have different IDs. This applies to both regular saves and Save As. Do not substitute a revision or the original session's document ID."]}),(0,s.jsxs)(t.p,{children:["Providers can use their own document IDs that satisfy the format above. See the ",(0,s.jsx)(t.a,{href:"https://github.com/thinkfree/http-storage-provider/blob/main/schemas/v1/put-response.schema.json",children:"PUT response schema"})," for response validation."]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Conditions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"400 Bad Request"})}),(0,s.jsx)(t.td,{children:"Empty document path, declared length differing from the actual body, or similar invalid input"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"409 Conflict"})}),(0,s.jsx)(t.td,{children:"The save conflicts with the destination's current state"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"413 Payload Too Large"})}),(0,s.jsx)(t.td,{children:"The document exceeds the protocol limit or a lower Provider limit"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsxs)(t.td,{children:["After authentication, ",(0,s.jsx)(t.code,{children:"PUT_NOT_SUPPORTED"}),". Nothing is written to storage, and Office reports a save failure"]})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"lock",children:"lock - Lock a document"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"lock"})," sets a document lock and records its ",(0,s.jsx)(t.code,{children:"owner"}),". A repeated request with the same ",(0,s.jsx)(t.code,{children:"owner"})," succeeds; a lock held by another ",(0,s.jsx)(t.code,{children:"owner"})," returns ",(0,s.jsx)(t.code,{children:"409 Conflict"}),". Implement both ",(0,s.jsx)(t.code,{children:"lock"})," and ",(0,s.jsx)(t.code,{children:"unlock"}),", or declare both unsupported."]}),(0,s.jsx)(t.h4,{id:"lock-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"POST /tfo-http-storage/v1/{path}/lock"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:'POST /tfo-http-storage/v1/contracts/sample.docx/lock HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\nContent-Type: application/json\nContent-Length: 25\n\n{\n  "owner": "editor-1"\n}\n'})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsx)(t.td,{children:"Relative path of the document to lock"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this POST request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"application/json"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsx)(t.td,{children:"UTF-8 byte length of the JSON body"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"owner"})}),(0,s.jsx)(t.td,{children:"Body \xb7 string \xb7 required"}),(0,s.jsx)(t.td,{children:"Nonempty lock owner identifier; the only JSON field"})]})]})]}),(0,s.jsxs)(t.p,{children:["Store ",(0,s.jsx)(t.code,{children:"owner"})," unchanged and compare it for equality when processing unlock requests. Do not use it as a user authentication token. A value returned as ",(0,s.jsx)(t.code,{children:"locker"})," in ",(0,s.jsx)(t.code,{children:"info"})," or ",(0,s.jsx)(t.code,{children:"list"})," must fit that field's 255-character limit. This request defines no lock expiration, lease duration, or renewal interval fields."]}),(0,s.jsx)(t.h4,{id:"lock-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"lock"}),(0,s.jsx)(t.h4,{id:"lock-response",children:"Response"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"HTTP/1.1 204 No Content\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"204 No Content"})}),(0,s.jsxs)(t.td,{children:["A new lock was acquired or an existing lock for the same ",(0,s.jsx)(t.code,{children:"owner"})," was confirmed. Other ",(0,s.jsx)(t.code,{children:"2xx"})," statuses also succeed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"None"}),(0,s.jsxs)(t.td,{children:["No lock token or result JSON is required. Bodies of other ",(0,s.jsx)(t.code,{children:"2xx"})," responses contain no fields required to determine success"]})]})]})]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Conditions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"400 Bad Request"})}),(0,s.jsxs)(t.td,{children:["Missing ",(0,s.jsx)(t.code,{children:"owner"}),", empty string, incorrect type, or extra JSON fields"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"409 Conflict"})}),(0,s.jsxs)(t.td,{children:["Another ",(0,s.jsx)(t.code,{children:"owner"})," holds the lock. Do not change the existing lock"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsxs)(t.td,{children:["After authentication, ",(0,s.jsx)(t.code,{children:"LOCK_NOT_SUPPORTED"}),". Office treats this as successful without an actual lock"]})]})]})]}),(0,s.jsxs)(t.p,{children:["If ",(0,s.jsx)(t.code,{children:"lock"})," and ",(0,s.jsx)(t.code,{children:"unlock"})," are unsupported, return the ",(0,s.jsx)(t.a,{href:"#declare-an-operation-not-implemented",children:"unsupported operation response"}),"."]})]}),"\n",(0,s.jsx)(t.h3,{id:"unlock",children:"unlock - Unlock a document"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"unlock"})," releases the lock held by the requested ",(0,s.jsx)(t.code,{children:"owner"}),". It succeeds if the lock is already absent. If a different ",(0,s.jsx)(t.code,{children:"owner"})," holds the lock, return ",(0,s.jsx)(t.code,{children:"409 Conflict"}),". Implement it together with ",(0,s.jsx)(t.code,{children:"lock"}),"."]}),(0,s.jsx)(t.h4,{id:"unlock-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"POST /tfo-http-storage/v1/{path}/unlock"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:'POST /tfo-http-storage/v1/contracts/sample.docx/unlock HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\nContent-Type: application/json\nContent-Length: 25\n\n{\n  "owner": "editor-1"\n}\n'})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsx)(t.td,{children:"Relative path of the document to unlock"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this POST request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"application/json"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsx)(t.td,{children:"UTF-8 byte length of the JSON body"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"owner"})}),(0,s.jsx)(t.td,{children:"Body \xb7 string \xb7 required"}),(0,s.jsx)(t.td,{children:"The same owner identifier used to acquire the lock; the only JSON field"})]})]})]}),(0,s.jsx)(t.h4,{id:"unlock-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"unlock"}),(0,s.jsx)(t.h4,{id:"unlock-response",children:"Response"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"HTTP/1.1 204 No Content\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"204 No Content"})}),(0,s.jsxs)(t.td,{children:["The same owner's lock was released, or no lock remains. Other ",(0,s.jsx)(t.code,{children:"2xx"})," statuses also succeed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"None"}),(0,s.jsx)(t.td,{children:"No unlock result JSON is required"})]})]})]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Condi
16tions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"400 Bad Request"})}),(0,s.jsxs)(t.td,{children:["Missing ",(0,s.jsx)(t.code,{children:"owner"}),", empty string, incorrect type, or extra JSON fields"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"409 Conflict"})}),(0,s.jsx)(t.td,{children:"The current lock owner differs. Preserve the existing lock"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsxs)(t.td,{children:["After authentication, ",(0,s.jsx)(t.code,{children:"UNLOCK_NOT_SUPPORTED"}),". Office treats this as successful without an actual unlock operation"]})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"mkdir",children:"mkdir - Create a directory"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsx)(t.p,{children:"This optional endpoint creates one directory directly under the specified parent. It does not create a multilevel path with intermediate directories."}),(0,s.jsx)(t.h4,{id:"mkdir-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"POST /tfo-http-storage/v1/{parent}/mkdir"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:'POST /tfo-http-storage/v1/contracts/mkdir HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\nContent-Type: application/json\nContent-Length: 23\n\n{\n  "name": "archive"\n}\n'})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"parent"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsxs)(t.td,{children:["Relative path of an existing parent directory. Use ",(0,s.jsx)(t.code,{children:"/tfo-http-storage/v1/mkdir"})," to create a directory under the root"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this POST request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"application/json"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsx)(t.td,{children:"UTF-8 byte length of the JSON body"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"name"})}),(0,s.jsx)(t.td,{children:"Body \xb7 string \xb7 required"}),(0,s.jsx)(t.td,{children:"One new directory name, 1\u2013255 characters; the only JSON field"})]})]})]}),(0,s.jsxs)(t.p,{children:[(0,s.jsx)(t.code,{children:"name"})," is the actual name, not a URL-encoded path. ",(0,s.jsx)(t.code,{children:"/"}),", ",(0,s.jsx)(t.code,{children:"\\"}),", control characters, and names equal to ",(0,s.jsx)(t.code,{children:"."})," or ",(0,s.jsx)(t.code,{children:".."})," are not allowed. For example, ",(0,s.jsx)(t.code,{children:"2026/reports"})," cannot create a nested path in one request."]}),(0,s.jsx)(t.h4,{id:"mkdir-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"mkdir"}),(0,s.jsx)(t.h4,{id:"mkdir-response",children:"Response"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"HTTP/1.1 204 No Content\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"204 No Content"})}),(0,s.jsxs)(t.td,{children:["The ",(0,s.jsx)(t.code,{children:"parent/name"})," directory was created. Other ",(0,s.jsx)(t.code,{children:"2xx"})," statuses also succeed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"None"}),(0,s.jsxs)(t.td,{children:["No created entry object is required. Retrieve it later with ",(0,s.jsx)(t.code,{children:"info"})," or ",(0,s.jsx)(t.code,{children:"list"})]})]})]})]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Conditions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"400 Bad Request"})}),(0,s.jsxs)(t.td,{children:["Missing ",(0,s.jsx)(t.code,{children:"name"}
16),", incorrect type, extra fields, or an invalid name"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsxs)(t.td,{children:["After authentication, ",(0,s.jsx)(t.code,{children:"MKDIR_NOT_SUPPORTED"}),". No directory is created"]})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"rename",children:"rename - Rename an entry"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsx)(t.p,{children:"This optional endpoint renames a file or directory within the same parent directory."}),(0,s.jsx)(t.h4,{id:"rename-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"POST /tfo-http-storage/v1/{path}/rename"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:'POST /tfo-http-storage/v1/contracts/sample.docx/rename HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\nContent-Type: application/json\nContent-Length: 29\n\n{\n  "name": "reviewed.docx"\n}\n'})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsx)(t.td,{children:"Relative path of the existing entry to rename. The Provider root cannot be renamed"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this POST request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"application/json"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Length"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsx)(t.td,{children:"UTF-8 byte length of the JSON body"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"name"})}),(0,s.jsx)(t.td,{children:"Body \xb7 string \xb7 required"}),(0,s.jsx)(t.td,{children:"One new name, 1\u2013255 characters; the only JSON field"})]})]})]}),(0,s.jsxs)(t.p,{children:["Use the actual decoded name for ",(0,s.jsx)(t.code,{children:"name"}),", following the ",(0,s.jsx)(t.a,{href:"#mkdir-request",children:"mkdir name rules"}),". Sending ",(0,s.jsx)(t.code,{children:"reviewed.docx"})," for ",(0,s.jsx)(t.code,{children:"contracts/sample.docx"})," results in ",(0,s.jsx)(t.code,{children:"contracts/reviewed.docx"}),"."]}),(0,s.jsx)(t.h4,{id:"rename-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"rename"}),(0,s.jsx)(t.h4,{id:"rename-response",children:"Response"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"HTTP/1.1 204 No Content\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"204 No Content"})}),(0,s.jsxs)(t.td,{children:["The entry was renamed within the same parent. Other ",(0,s.jsx)(t.code,{children:"2xx"})," statuses also succeed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"None"}),(0,s.jsxs)(t.td,{children:["No new path or entry object is required. Use ",(0,s.jsx)(t.code,{children:"info"})," on the new path or ",(0,s.jsx)(t.code,{children:"list"})," on its parent to retrieve it"]})]})]})]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Conditions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"400 Bad Request"})}),(0,s.jsxs)(t.td,{children:["Invalid ",(0,s.jsx)(t.code,{children:"name"}),", extra JSON fields, or an attempt to rename the Provider root"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"404 Not Found"})}),(0,s.jsx)(t.td,{children:"No entry exists at the original path"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"409 Conflict"})}),(0,s.jsx)(t.td,{children:"The new name is already in use"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsxs)(t.td,{children:["After authentication, ",(0,s.jsx)(t.code,{children:"RENAME_NOT_SUPPORTED"}),". The original name is preserved"]})]})]})]})]}),"\n",(0,s.jsx)(t.h3,{id:"delete",children:"delete - Delete an entry"}),"\n",(0,s.jsxs)(B,{children:[(0,s.jsx)(t.p,{children:"This optional endpoint deletes a file or directory. Reject attempts to delete the Provider root."}),(0,s.jsx)(t.h4,{id:"delete-request",children:"Request"}),(0,s.jsx)(t.p,{children:(0,s.jsx)(t.code,{children:"DELETE /tfo-http-storage/v1/{path}/delete"})}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"DELETE /tfo-http-storage/v1/contracts/reviewed.docx/delete HTTP/1.1\nHost: storage.example.com\nX-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"path"})}),(0,s.jsx)(t.td,{children:"Path \xb7 string"}),(0,s.jsx)(t.td,{children:"Relative path of the entry to delete. The Provider root cannot be deleted"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Authentication headers"}),(0,s.jsx)(t.td,{children:"Required \xb7 string"}),(0,s.jsxs)(t.td,{children:["JWT for this DELETE request. See ",(0,s.jsx)(t.a,{href:"#request-headers",children:"Common headers"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Request body"}),(0,s.jsx)(t.td,{children:"None"}),(0,s.jsxs)(t.td,{children:["Omit ",(0,s.jsx)(t.code,{children:"Content-Type"}),". Follow the ",(0,s.jsx)(t.a,{href:"#request-headers",children:"common request rules"})]})]})]})]}),(0,s.jsx)(t.h4,{id:"delete-implementation",children:"Implementation example"}),(0,s.jsx)(V,{operation:"delete"}),(0,s.jsx)(t.h4,{id:"delete-response",children:"Response"}),(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"HTTP/1.1 204 No Content\n"})}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"204 No Content"})}),(0,s.jsxs)(t.td,{children:["The entry was deleted. Other ",(0,s.jsx)(t.code,{children:"2xx"})," statuses also succeed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"None"}
16),(0,s.jsx)(t.td,{children:"No deleted entry object or deletion count is required"})]})]})]}),(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"Conditions and handling"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"400 Bad Request"})}),(0,s.jsx)(t.td,{children:"Attempt to delete the Provider root"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsxs)(t.td,{children:["After authentication, ",(0,s.jsx)(t.code,{children:"DELETE_NOT_SUPPORTED"}),". The entry is not deleted"]})]})]})]})]}),"\n",(0,s.jsx)(t.h2,{id:"declare-an-operation-not-implemented",children:"Unsupported operations"}),"\n",(0,s.jsxs)(t.p,{children:["Use the following response only for an optional operation that is not implemented. Return it ",(0,s.jsx)(t.strong,{children:"after authentication, body verification, and replay checks for that request"}),". The status must be ",(0,s.jsx)(t.code,{children:"501"}),", the media type must be ",(0,s.jsx)(t.code,{children:"application/json"}),", and the JSON body must contain ",(0,s.jsx)(t.strong,{children:"only one"})," ",(0,s.jsx)(t.code,{children:"code"})," field matching the current operation. A charset parameter is allowed."]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:"Required"}),(0,s.jsxs)(t.td,{children:["Exactly ",(0,s.jsx)(t.code,{children:"501 Not Implemented"})]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Required header"}),(0,s.jsxs)(t.td,{children:[(0,s.jsx)(t.code,{children:"application/json"}),"; a charset parameter is allowed"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"code"})}),(0,s.jsx)(t.td,{children:"Body \xb7 string \xb7 required"}),(0,s.jsx)(t.td,{children:"The exact value for the current operation from the table below. No additional fields"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body length"}),(0,s.jsx)(t.td,{children:"Brief UTF-8 JSON"}),(0,s.jsx)(t.td,{children:"The complete object must fit within Office's 8 KiB error-body read limit"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["The fixed-length checks for successful read responses do not apply here, but you may include an exact ",(0,s.jsx)(t.code,{children:"Content-Length"})," as in the example below."]}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:'HTTP/1.1 501 Not Implemented\nContent-Type: application/json\nContent-Length: 34\n\n{\n  "code": "LIST_NOT_SUPPORTED"\n}\n'})}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Operation"}),(0,s.jsx)(t.th,{children:"Exact code"}),(0,s.jsx)(t.th,{children:"Office behavior"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"list"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"LIST_NOT_SUPPORTED"})}),(0,s.jsx)(t.td,{children:"File listing is unavailable. The connection can be saved after acknowledging the notice during registration"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"put"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"PUT_NOT_SUPPORTED"})}),(0,s.jsx)(t.td,{children:"Saving fails"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"lock"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"LOCK_NOT_SUPPORTED"})}),(0,s.jsx)(t.td,{children:"Treated as successful without an actual lock"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"unlock"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"UNLOCK_NOT_SUPPORTED"})}),(0,s.jsx)(t.td,{children:"Treated as successful without an actual unlock"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"mkdir"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"MKDIR_NOT_SUPPORTED"})}),(0,s.jsx)(t.td,{children:"Directory creation is unavailable"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"rename"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"RENAME_NOT_SUPPORTED"})}),(0,s.jsx)(t.td,{children:"Renaming is unavailable"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"delete"})}),(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"DELETE_NOT_SUPPORTED"})}),(0,s.jsx)(t.td,{children:"Deletion is unavailable"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["Because ",(0,s.jsx)(t.code,{children:"info"})," and ",(0,s.jsx)(t.code,{children:"get"})," are required, a ",(0,s.jsx)(t.code,{children:"501"})," fails those operations. For optional operations, Office also reports failure if the status, media type, or body does not match the format above."]}),"\n",(0,s.jsxs)(t.p,{children:[(0,s.jsxs)(t.strong,{children:["Among unsupported-operation responses, only those for ",(0,s.jsx)(t.code,{children:"lock"})," and ",(0,s.jsx)(t.code,{children:"unlock"})," are treated as successful by Office."]})," Declare both operations unsupported and return ",(0,s.jsx)(t.code,{children:"LOCK_NOT_SUPPORTED"})," or ",(0,s.jsx)(t.code,{children:"UNLOCK_NOT_SUPPORTED"})," for the corresponding request. Do not implement only one of the pair. No document lock is established in this case, so the Provider must define how 
16it handles overwrites or conflicts from concurrent saves."]}),"\n",(0,s.jsxs)(t.p,{children:["For implemented ",(0,s.jsx)(t.code,{children:"lock"})," and ",(0,s.jsx)(t.code,{children:"unlock"})," operations, the normal response is ",(0,s.jsx)(t.code,{children:"204"}),". Office treats ",(0,s.jsx)(t.code,{children:"409 Conflict"}),", authentication failures, network errors, and storage failures as operation failures."]}),"\n",(0,s.jsxs)(t.p,{children:["The admin connection test calls only ",(0,s.jsx)(t.code,{children:"list"}),". Verify other capabilities through actual document operations; registration does not arbitrarily execute writes or deletes. Also see the ",(0,s.jsx)(t.a,{href:"https://github.com/thinkfree/http-storage-provider/blob/main/schemas/v1/operation-not-supported-response.schema.json",children:"unsupported operation JSON schema"}),"."]}),"\n",(0,s.jsx)(t.h2,{id:"errors",children:"Error responses"}),"\n",(0,s.jsxs)(t.p,{children:["Office treats ",(0,s.jsx)(t.code,{children:"404"})," from ",(0,s.jsx)(t.code,{children:"info"})," as an absent target. Except for that case and the exact unsupported responses above, non-",(0,s.jsx)(t.code,{children:"2xx"})," responses fail the Office operation. Ordinary errors have no fixed JSON body schema. Return a brief error description without secrets, JWTs, internal paths, document contents, or stack traces. Office may display up to ",(0,s.jsx)(t.strong,{children:"8 KiB"})," of the error body."]}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Item"}),(0,s.jsx)(t.th,{children:"Format"}),(0,s.jsx)(t.th,{children:"Description"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Status"}),(0,s.jsx)(t.td,{children:"HTTP status code"}),(0,s.jsx)(t.td,{children:"Choose according to the error cause below. The status determines success or failure"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"Content-Type"})}),(0,s.jsx)(t.td,{children:"Match the body"}),(0,s.jsx)(t.td,{children:"No fixed media type for ordinary errors"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:"Body"}),(0,s.jsx)(t.td,{children:"text or Provider-defined JSON"}),(0,s.jsx)(t.td,{children:"Optional description of the error"})]})]})]}),"\n",(0,s.jsx)(t.p,{children:"The following is an authentication error response. Do not distinguish unknown adapters, signature mismatches, expiration, and replay through detailed response messages."}),"\n",(0,s.jsx)(t.pre,{children:(0,s.jsx)(t.code,{className:"language-http",children:"HTTP/1.1 401 Unauthorized\nContent-Type: text/plain; charset=utf-8\nContent-Length: 12\n\nUnauthorized\n"})}),"\n",(0,s.jsxs)(t.table,{children:[(0,s.jsx)(t.thead,{children:(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.th,{children:"Status"}),(0,s.jsx)(t.th,{children:"When to use"})]})}),(0,s.jsxs)(t.tbody,{children:[(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"400 Bad Request"})}),(0,s.jsx)(t.td,{children:"Invalid path, JSON, body length, or a disallowed query"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"401 Unauthorized"})}),(0,s.jsx)(t.td,{children:"Unknown adapter, invalid signature or claims, or an expired or reused JWT"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"403 Forbidden"})}),(0,s.jsx)(t.td,{children:"No permission to access the storage or document"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"404 Not Found"})}),(0,s.jsx)(t.td,{children:"Target entry or request path does not exist"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"405 Method Not Allowed"})}),(0,s.jsx)(t.td,{children:"Incorrect method for a known operation path"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"409 Conflict"})}),(0,s.jsx)(t.td,{children:"Name, type, or lock-owner conflict"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"411 Length Required"})}),(0,s.jsxs)(t.td,{children:["Required body ",(0,s.jsx)(t.code,{children:"Content-Length"})," is missing"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"413 Payload Too Large"})}),(0,s.jsx)(t.td,{children:"Request body, document, or listing exceeds its limit, or a Provider-defined limit is exceeded"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"415 Unsupported Media Type"})}),(0,s.jsxs)(t.td,{children:["Request ",(0,s.jsx)(t.code,{children:"Content-Type"})," does not match the operation's required media type"]})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"500 Internal Server Error"})}),(0,s.jsx)(t.td,{children:"Internal Provider error. Do not expose exception details in the response"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"501 Not Implemented"})}),(0,s.jsx)(t.td,{children:"Exact unsupported declaration for an optional operation as specified above"})]}),(0,s.jsxs)(t.tr,{children:[(0,s.jsx)(t.td,{children:(0,s.jsx)(t.code,{children:"503 Service Unavailable"})}),(0,s.jsx)(t.td,{children:"Temporary storage failure"})]})]})]}),"\n",(0,s.jsxs)(t.p,{children:["Do not turn authentication errors into unsupported responses or report storage failures as successful. See ",(0,s.jsx)(t.a,{href:"/office/http-storage/#implementations",children:"Run a Provider example"})," for example-specific configuration."]})]})}function Z(e={}){const{wrapper:t}={...(0,i.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(Q,{...e})}):Q(e)}}
16,9696(e,t,n){n.d(t,{A:()=>y});var r=n(6540),s=n(4586),i=n(4848),d="#121314",o="#262626",c="#1A1B1E",a="#2A2C2F",l="#E2E4E8",h="#B9BCC1",x="#87AFD9",j="#C797C8";function u(e){var t=e.x,n=e.y,r=e.lines,s=e.size,d=void 0===s?15:s,o=e.color,c=void 0===o?l:o,a=e.anchor,h=void 0===a?"middle":a;return(0,i.jsx)("text",{x:t,y:n,textAnchor:h,fill:c,fontSize:d,children:r.map(function(e,n){return(0,i.jsx)("tspan",{x:t,dy:n?22:0,children:e},n)})})}function p(e){var t=e.x,n=e.y,r=e.width,s=e.height,d=e.lines,o=e.accent,l=void 0===o?x:o;return(0,i.jsxs)("g",{children:[(0,i.jsx)("rect",{x:t,y:n,width:r,height:s,rx:"12",fill:c,stroke:a}),(0,i.jsx)(u,{x:t+r/2,y:n+s/2-11*(d.length-1)+5,lines:d,color:l})]})}function m(e){var t=e.x1,n=e.y1,r=e.x2,s=e.y2,d=e.marker,o=e.dashed,c=void 0!==o&&o;return(0,i.jsx)("line",{x1:t,y1:n,x2:r,y2:s,stroke:x,strokeWidth:"1.5",strokeDasharray:c?"5 5":void 0,markerEnd:"url(#"+d+")"})}function g(e){var t=e.x,n=e.y1,r=e.y2;return(0,i.jsx)("line",{x1:t,y1:n,x2:t,y2:r,stroke:h,strokeOpacity:"0.7",strokeWidth:"1.5",strokeDasharray:"5 5"})}var f={architecture:{height:408,component:function(e){var t=e.ko,n=e.marker;return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(u,{x:32,y:38,anchor:"start",lines:[t?"\uad6c\uc131 \uc694\uc18c\uc640 \ub370\uc774\ud130 \uc774\ub3d9":"Components and data flow"],size:18}),(0,i.jsx)("rect",{x:"24",y:"64",width:"306",height:"282",rx:"14",stroke:a}),(0,i.jsx)("rect",{x:"480",y:"64",width:"376",height:"282",rx:"14",stroke:a}),(0,i.jsx)(u,{x:177,y:93,lines:["Thinkfree Office (TFO)"],color:h}),(0,i.jsx)(u,{x:668,y:93,lines:[t?"\uace0\uac1d \uc11c\ube44\uc2a4":"Customer service"],color:h}),(0,i.jsx)(p,{x:46,y:114,width:262,height:64,lines:[t?"\ubb38\uc11c \ud3b8\uc9d1\uae30 \xb7 Office \ub7f0\ud0c0\uc784":"Editor \xb7 Office runtime"]}),(0,i.jsx)(p,{x:46,y:250,width:262,height:70,lines:["HTTP Storage Adapter",t?"Office\uc5d0 \ub0b4\uc7a5":"Built into Office"]}),(0,i.jsx)(m,{x1:177,y1:181,x2:177,y2:245,marker:n}),(0,i.jsx)(u,{x:188,y:211,anchor:"start",size:13,lines:["info / get / put"]}),(0,i.jsx)(p,{x:498,y:114,width:162,height:64,lines:[t?"\uc138\uc158 \xb7 \ubb38\uc11c \uad8c\ud55c":"Sessions \xb7 permissions"]}),(0,i.jsx)(p,{x:676,y:114,width:162,height:64,lines:[t?"\ubb38\uc11c \uc800\uc7a5\uc18c":"Document storage"]}),(0,i.jsx)(p,{x:498,y:250,width:340,height:70,lines:["HTTP Storage Provider",t?"\uace0\uac1d\uc774 \uad6c\ud604\xb7\uc6b4\uc601":"Implemented by the customer"]}),(0,i.jsx)(m,{x1:580,y1:246,x2:580,y2:184,marker:n}),(0,i.jsx)(m,{x1:756,y1:246,x2:756,y2:184,marker:n}),(0,i.jsx)(u,{x:568,y:215,anchor:"end",lines:[t?"\uad8c\ud55c \ud655\uc778":"Authorize"],size:12,color:h}),(0,i.jsx)(u,{x:744,y:211,anchor:"end",lines:[t?"\ud5c8\uc6a9 \ud6c4":"Read / write",t?"\uc77d\uae30\xb7\uc4f0\uae30":"only if allowed"],size:12,color:h}),(0,i.jsx)(m,{x1:313,y1:270,x2:493,y2:270,marker:n}),(0,i.jsx)(u,{x:403,y:235,size:12,lines:[t?"\uc11c\uba85\ub41c HTTP \uc694\uccad":"Signed HTTP request","JWT + body"]}),(0,i.jsx)(m,{x1:493,y1:308,x2:313,y2:308,marker:n,dashed:!0}),(0,i.jsx)(u,{x:403,y:333,size:12,lines:[t?"JSON \ub610\ub294 \ubb38\uc11c \ubc14\uc774\ud2b8":"JSON or document bytes"]}),(0,i.jsx)(u,{x:440,y:379,lines:[t?"\uac19\uc740 \uacf5\uc720 \ud0a4\ub85c \uc694\uccad\uc744 \uc11c\uba85\xb7\uac80\uc99d\ud558\uace0, HTTPS\ub85c \uc804\uc1a1 \ub0b4\uc6a9\uc744 \ubcf4\ud638\ud569\ub2c8\ub2e4.":"A shared key signs and verifies requests. HTTPS protects data in transit."],size:14,color:h})]})},en:"HTTP Storage components and data flow",ko:"HTTP Storage \uad6c\uc131 \uc694\uc18c\uc640 \ub370\uc774\ud130 \ud750\ub984"},lifecycle:{height:693,component:function(e){var t=e.ko,n=e.marker,r=[130,425,740],s=function(e,t,s,d,o){return void 0===o&&(o=!1),(0,i.jsxs)("g",{children:[(0,i.jsx)(u,{x:(r[e]+r[t])/2,y:s-14,lines:d,size:13}),(0,i.jsx)(m,{x1:r[e],y1:s,x2:r[t],y2:s,marker:n,dashed:o})]},s)};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(u,{x:32,y:38,anchor:"start",lines:[t?"\ubb38\uc11c \uc5f4\uae30\uc640 \ud6c4\uc18d \uc800\uc7a5\uc18c \uc694\uccad":"Document open and subsequent storage requests"],size:18}),["TFO","HTTP Storage Adapter","HTTP Storage Provider"].map(function(e,t){return(0,i.jsxs)("g",{children:[(0,i.jsx)(p,{x:r[t]-112,y:60,width:224,height:52,lines:[e]}),(0,i.jsx)(g,{x:r[t],y1:116,y2:629})]},e)}),s(0,1,160,["info()"]),s(1,2,211,["GET \u2026/info + request JWT"]),(0,i.jsx)(p,{x:620,y:229,width:240,height:64,lines:[t?"JWT\xb7\uc2e4\uc81c \uc694\uccad \uac80\uc99d":"Verify JWT and HTTP request",t?"\uc138\uc158\xb7\ubb38\uc11c \uc77d\uae30 \uad8c\ud55c \ud655\uc778":"Check session and read access"],accent:j}),s(2,1,329,[t?"\ud30c\uc77c \uc815\ubcf4 \ub610\ub294 \uc811\uadfc \uac70\ubd80":"File info or access denied"],!0),s(1,0,373,[t?"\ud655\uc778 \uacb0\uacfc":"Inspection result"],!0),(0,i.jsx)("rect",{x:"30",y:"397",width:"820",height:"48",rx:"10",fill:c,stroke:a}),(0,i.jsx)(u,{x:440,y:427,lines:[t?"info \uc131\uacf5 + readable \ud655\uc778 \ud6c4\uc5d0\ub9cc \ud3b8\uc9d1\uae30\ub85c \uc774\ub3d9 \xb7 \uac70\ubd80\ub418\uba74 \uc5f4\uae30 \uc911\ub2e8":"Enter the editor only after successful, readable info. Denial stops the open."],size:14,color:j}),s(0,1,497,[t?"\ub0b4\uc6a9 \uc77d\uae30 get() / \ubcc0\uacbd \uc800\uc7a5 put()":"Read get() / save changes put()"]),s(1,2,545,["GET \u2026/get / PUT \u2026/put + fresh JWT"]),s(2,1,595,[t?"\ubb38\uc11c \ubc14\uc774\ud2b8 / \uc800\uc7a5\ub41c docId":"Document bytes / saved docId"],!0),(0,i.jsx)(u,{x:440,y:663,lines:[t?"Provider\ub294 \ubaa8\ub4e0 \uc694\uccad\uc5d0\uc11c JWT\uc640 \ubb38\uc11c \uc811\uadfc \uad8c\ud55c\uc744 \ud655\uc778\ud569\ub2c8\ub2e4.":"The Provider verifies the JWT and document permissions on every request."],size:14,color:h})]})},en:"Document open sequence and later reads and saves",ko:"\ubb38\uc11c \uc5f4\uae30\uc640 \uc774\ud6c4 \uc870\ud68c\xb7\uc800\uc7a5 \uc2dc\ud000\uc2a4"},editingScenario:{height:1844,component:function(e){var t=e.ko,n=e.marker,r=[130,425,740],s=function(e,t,s,d,o){return void 0===o&&(o=!1),(0,i.jsxs)("g",{children:[(0,i.jsx)(u,{x:(r[e]+r[t])/2,y:s-12,lines:[d],size:13}),(0,i.jsx)(m,{x1:r[e],y1:s,x2:r[t],y2:s,marker:n,dashed:o})]},s)},d=function(e,t,n,r,d){return(0,i.jsxs)("g",{children:[s(0,1,e,t),s(1,2,e+38,n),s(2,1,e+76,r,!0),s(1,0,e+114,d,!0)]},t)},o=function(e,t){return(0,i.jsx)(p,{x:24,y:e,width:832,height:38,lines:[t],accent:j})};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(u,{x:32,y:38,anchor:"start",size:18,lines:[t?"\ubaa9\ub85d \uc870\ud68c\ubd80\ud130 \ud3b8\uc9d1\xb7\uc800\uc7a5\xb7\ub2eb\uae30\uae4c\uc9c0":"List, open, edit, save, and close"]}),(0,i.jsx)("rect",{x:"18",y:"60",width:"536",height:"110",rx:"14",stroke:x}),(0,i.jsx)(u,{x:286,y:85,size:14,color:x,lines:[t?"Office \ud504\ub808\uc784\uc6cc\ud06c \xb7 \uae30\ubcf8 \uc81c\uacf5 (\uace0\uac1d \uad6c\ud604 \ubd88\ud544\uc694)":"Office framework \xb7 built in (no customer implementation)"]}),(0,i.jsx)("rect",{x:"594",y:"60",width:"268",height:"110",rx:"14",stroke:j}),(0,i.jsx)(u,{x:728,y:85,size:14,color:j,lines:[t?"\uace0\uac1d\uc774 \uad6c\ud604\xb7\uc6b4\uc601\ud558\ub294 \ubd80\ubd84":"Customer implements and operates"]}),["TFO","HTTP Storage Adapter","HTTP Storage Provider"].map(function(e,t){return(0,i.jsxs)("g",{children:[(0,i.jsx)(p,{x:r[t]-(0===t?96:112),y:103,width:0===t?192:224,height:52,lines:[e],accent:2===t?j:x}),(0,i.jsx)(g,{x:r[t],y1:174,y2:1660})]},e)}),(0,i.jsx)("rect",{x:"24",y:"190",width:"832",height:"168",rx:"12",fill:c,stroke:a}
16),(0,i.jsx)(u,{x:44,y:216,anchor:"start",size:14,color:x,lines:[t?"Adapter \u2192 Provider: info HTTP \uc694\uccad \uc608\uc2dc \xb7 \uc694\uccad\ub9c8\ub2e4 \uc0c8 JWT \uc0ac\uc6a9":"Adapter \u2192 Provider: example info HTTP request \xb7 a fresh JWT per request"]}),(0,i.jsx)("g",{fontFamily:"var(--ifm-font-family-monospace)",children:(0,i.jsx)(u,{x:44,y:246,anchor:"start",size:13,lines:["GET /tfo-http-storage/v1/contracts/sample.docx/info HTTP/1.1","Host: storage.example.com","X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>"]})}),(0,i.jsx)(u,{x:44,y:334,anchor:"start",size:14,color:h,lines:[t?"JWT \ub0b4\ubd80: request.path \xb7 request.client_metadata { project, userSessionToken }":"Inside the JWT: request.path \xb7 request.client_metadata { project, userSessionToken }"]}),o(390,t?"1. \ubb38\uc11c \uc120\ud0dd \ud654\uba74\uc5d0\uc11c \ubaa9\ub85d \uc870\ud68c":"1. Browse the document selection list"),d(460,"list()","GET \u2026/list + JWT","200 \xb7 entries",t?"\ubb38\uc11c \ubaa9\ub85d \ud45c\uc2dc":"Display document list"),(0,i.jsx)(p,{x:24,y:610,width:832,height:58,accent:j,lines:t?["2. \uace0\uac1d \uc560\ud50c\ub9ac\ucf00\uc774\uc158\uc5d0\uc11c \ubb38\uc11c\ub97c \uc120\ud0dd\ud558\uace0 /open URL\uc744 \ub9cc\ub4e4\uc5b4 \uc5f4\uae30","/open\uc5d0 \ub123\uc740 \uace0\uac1d \uba54\ud0c0\ub370\uc774\ud130 \u2192 JWT\uc758 request.client_metadata"]:["2. Select a document \xb7 the customer app builds and opens its /open URL","Customer metadata in /open \u2192 JWT request.client_metadata"]}),d(706,"info()","GET \u2026/info + JWT","200 \xb7 file metadata",t?"\ubb38\uc11c \uc815\ubcf4 \ud655\uc778":"Document info verified"),(0,i.jsx)(p,{x:24,y:844,width:832,height:56,accent:j,lines:t?["\ubb38\uc11c\uac00 \uc5c6\uac70\ub098 \uc778\uc99d\xb7\uad8c\ud55c \ud655\uc778\uc5d0 \uc2e4\ud328\ud558\uba74 \uc5f4\uae30 \uc911\ub2e8","\uc544\ub798\ub294 \uac80\uc99d\uc5d0 \uc131\uacf5\ud574 \ud3b8\uc9d1\uc744 \uacc4\uc18d\ud558\ub294 \ud750\ub984\uc785\ub2c8\ub2e4."]:["Missing document, invalid authentication, or denied access \u2192 stop opening","The flow below continues after successful verification."]}),d(940,"lock()","POST \u2026/lock + JWT \xb7 owner","204 \xb7 locked",t?"lock \uacb0\uacfc \ud655\uc778":"Document lock confirmed"),d(1096,"get()","GET \u2026/get + JWT","200 \xb7 document bytes",t?"\ud3b8\uc9d1\uae30\uc5d0 \ubb38\uc11c \ud45c\uc2dc":"Display document in editor"),o(1238,t?"3. \ubb38\uc11c \ud3b8\uc9d1 \xb7 \uc800\uc7a5 \uc120\ud0dd":"3. Edit the document \xb7 select Save"),d(1310,"put()","PUT \u2026/put + JWT \xb7 file bytes","200 \xb7 { docId }",t?"\uc800\uc7a5 \uc644\ub8cc \ud655\uc778":"Save confirmed"),o(1458,t?"4. \uacf5\ub3d9 \ud3b8\uc9d1\uc758 \ub9c8\uc9c0\ub9c9 \uc0ac\uc6a9\uc790\uac00 \uc800\uc7a5\uc744 \ub9c8\uce58\uace0 \ubb38\uc11c \ub2eb\uae30":"4. Last editor closes after saving"),d(1530,"unlock()","POST \u2026/unlock + JWT \xb7 owner","204 \xb7 unlocked",t?"unlock \uacb0\uacfc \ud655\uc778":"Unlock confirmed"),(0,i.jsx)(p,{x:24,y:1682,width:832,height:76,accent:j,lines:t?["Provider\ub294 \ub9e4 \uc694\uccad\uc758 JWT \uc11c\uba85\xb7\uc720\ud6a8 \uae30\uac04\xb7\uc7ac\uc0ac\uc6a9 \uc5ec\ubd80\ub97c \uac80\uc0ac\ud569\ub2c8\ub2e4.","JWT\uc758 \uc694\uccad \uc815\ubcf4\uc640 \uc218\uc2e0\ud55c \uc694\uccad\uc744 \ube44\uad50\ud558\uace0, \uace0\uac1d \uc138\uc158\uacfc \ubb38\uc11c\xb7\uc791\uc5c5 \uad8c\ud55c\uc744 \ud655\uc778\ud569\ub2c8\ub2e4.","\uac80\uc99d\uc5d0 \uc2e4\ud328\ud558\uac70\ub098 \uad8c\ud55c\uc774 \uc5c6\uc73c\uba74 \uc800\uc7a5\uc18c\uc5d0 \uc811\uadfc\ud558\uc9c0 \uc54a\uace0 \uc694\uccad\uc744 \uac70\ubd80\ud569\ub2c8\ub2e4."]:["The Provider checks JWT signatures, validity periods, and replay on every request.","Compare signed and received request details; check the session and document/operation access.","Reject failed verification or authorization before accessing storage."]}),(0,i.jsx)(u,{x:440,y:1792,size:14,color:h,lines:t?["\ubb38\uc11c\uac00 \ub2eb\ud78c \ub4a4 Provider\uc5d0 \ubcc4\ub3c4\uc758 close \ub610\ub294 stop \uc694\uccad\uc744 \ubcf4\ub0b4\uc9c0\ub294 \uc54a\uc2b5\ub2c8\ub2e4.","\uac01 endpoint\ub294 \uc774\uc804 \uc694\uccad\uc758 \ucc98\ub9ac state\uc5d0 \uc758\uc874\ud558\uc9c0 \uc54a\uace0 \ub3c5\ub9bd\uc801\uc73c\ub85c \uc644\ub8cc\ud569\ub2c8\ub2e4(stateless)."]:["Closing sends no separate close or stop request to the Provider.","Complete each request independently of earlier request-processing state (stateless)."]})]})},en:"Document workflow with built-in Office components, JWT headers, and customer Provider responsibilities",ko:"Office \uae30\ubcf8 \uc81c\uacf5 \uc601\uc5ed\xb7JWT \ud5e4\ub354\xb7\uace0\uac1d Provider\uc758 \uad6c\ud604 \ucc45\uc784\uc744 \uad6c\ubd84\ud55c \ubb38\uc11c \ucc98\ub9ac \uc2dc\ud000\uc2a4"},metadataSequence:{height:684,component:function(e){var t=e.ko,n=e.marker,r=[140,435,740],s=function(e,t,s,d,o){return void 0===o&&(o=!1),(0,i.jsxs)("g",{children:[(0,i.jsx)(u,{x:(r[e]+r[t])/2,y:s-14,lines:[d],size:13}),(0,i.jsx)(m,{x1:r[e],y1:s,x2:r[t],y2:s,marker:n,dashed:o})]})};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(u,{x:32,y:38,anchor:"start",size:18,lines:[t?"\uace0\uac1d \uc138\uc158 \uc804\ub2ec\uacfc \uc694\uccad\ubcc4 \uad8c\ud55c \ud655\uc778":"Customer session delivery and permission checks"]}),[t?"\uace0\uac1d \uc560\ud50c\ub9ac\ucf00\uc774\uc158":"Customer application","TFO \xb7 HTTP Storage Adapter","HTTP Storage Provider"].map(function(e,t){return(0,i.jsxs)("g",{children:[(0,i.jsx)(p,{x:r[t]-116,y:60,width:232,height:56,lines:[e]}),(0,i.jsx)(g,{x:r[t],y1:120,y2:622})]},e)}),(0,i.jsx)(p,{x:24,y:136,width:232,height:64,accent:j,lines:[t?"\uc0ac\uc6a9\uc790\xb7\ubb38\uc11c \uad8c\ud55c \ud655\uc778":"Check user access",t?"\uc9e7\uc740 \uc720\ud6a8 \uae30\uac04\uc758 \uc138\uc158 \ubc1c\uae09":"Issue document session"]}),s(0,1,244,"1. /open + metadata JSON"),s(1,2,306,"2. info + JWT (client_metadata)"),(0,i.jsx)(p,{x:620,y:326,width:240,height:78,accent:j,lines:[t?"JWT\xb7\uc2e4\uc81c \uc694\uccad \uac80\uc99d":"Verify JWT + HTTP request",t?"\uc138\uc158 \uc800\uc7a5\uc18c\uc5d0\uc11c \uc77d\uae30 \uad8c\ud55c \ud655\uc778":"Check session + read access"]}),s(2,1,448,t?"3. \ud30c\uc77c \uc815\ubcf4 \ub610\ub294 \uc811\uadfc \uac70\ubd80":"3. File info or access denied",!0),s(1,2,514,"4. get / put + fresh JWT"),(0,i.jsx)(p,{x:620,y:538,width:240,height:78,accent:j,lines:[t?"\uc694\uccad\ub9c8\ub2e4 \ub2e4\uc2dc \ud655\uc778":"Recheck every request",t?"\uc138\uc158\xb7\ub300\uc0c1 \ubb38\uc11c\xb7\uc77d\uae30/\uc4f0\uae30 \uad8c\ud55c":"Session, document, operation"]}),(0,i.jsx)(u,{x:440,y:654,size:14,color:h,lines:[t?"\uac19\uc740 \uba54\ud0c0\ub370\uc774\ud130\ub97c \uc804\ub2ec\ud574\ub3c4 \ub9cc\ub8cc\xb7\ud3d0\uae30\ub41c \uc138\uc158\uc774\ub098 \ud5c8\uc6a9\ub418\uc9c0 \uc54a\uc740 \uc791\uc5c5\uc740 \uac70\ubd80\ud569\ub2c8\ub2e4.":"Reusing metadata does not authorize an expired session or a disallowed operation."]})]})},en:"Customer session metadata and per-operation authorization sequence",ko:"\uace0\uac1d \uc138\uc158 \uba54\ud0c0\ub370\uc774\ud130 \uc804\ub2ec\uacfc \uc791\uc5c5\ubcc4 \uad8c\ud55c \ud655\uc778 \uc2dc\ud000\uc2a4"},verification:{height:526,component:function(e){var t=e.ko,n=e.marker,r=t?[["1. request.adapter\ub85c \ubbf8\ub9ac \ub4f1\ub85d\ud55c \uc11c\uba85 \ud0a4 \uc120\ud0dd","\uac80\uc99d \uc804\uc774\ubbc0\ub85c \ud0a4 \uc120\ud0dd\uc5d0\ub9cc \uc0ac\uc6a9"],["2. JWT \uac80\uc99d \ud6c4 \uc218\uc2e0\ud55c \uc694\uccad\uacfc \ube44\uad50","\uc11c\uba85\xb7\ubc1c\uae09\uc790\xb7\ub300\uc0c1\xb7\uc720\ud6a8 \uae30\uac04\xb7\uc694\uccad \uc815\ubcf4\xb7\uc7ac\uc0ac\uc6a9 \uc5ec\ubd80 \ud655\uc778"],["3. \uace0\uac1d \uc138\uc158\uacfc \ubb38\uc11c \uc811\uadfc \uad8c\ud55c \ud655\uc778","client_metadata\uc758 \ud1a0\ud070\uc73c\ub85c \uc0ac\uc6a9\uc790\uc640 \ud574\ub2f9 \uc791\uc5c5\uc758 \uad8c\ud55c \ud655\uc778"],["4. \ud5c8\uc6a9\ub41c \uc800\uc7a5\uc18c \uc791\uc5c5 \uc218\ud589","info / get / put \ub4f1 \uc694\uccad \uacb0\uacfc \ubc18\ud658"]]:[["1. Use unverified request.adapter to select a registered key","This step grants no document access"],["2. Verify the JWT and compare it with the received request","Signature, issuer, audience, validity period, request details and replay"],["3. Check the customer session and document access","Use the client_metadata token to verify the user and operation permissions"],["4. Perform the authorized storage operation","Return the result of info / get / put or another operation"]];return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(u,{x:32,y:38,anchor:"start",lines:[t?"Provider\uc758 \uc694\uccad \ucc98\ub9ac \uc21c\uc11c":"Provider request processing"],size:18}),r.map(function(e,t){return(0,i.jsxs)("g",{children:[(0,i.jsx)(p,{x:24,y:68+104*t,width:650,height:76,lines:e,accent:3===t?x:l}),t<3&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(m,{x1:349,y1:147+104*t,x2:349,y2:168+104*t,marker:n}),(0,i.jsx)("line",{x1:"679",y1:106+104*t,x2:"732",y2:106+104*t,stroke:j})]})]},t)}),(0,i.jsx)("path",{d:"M732 106V346H764V365",stroke:j,fill:"none"}),(0,i.jsx)(u,{x:748,y:184,anchor:"start",lines:[t?"\uc2e4\ud328\xb7\uac70\ubd80":"Failure / denial"],color:j,size:14}),(0,i.jsx)(p,{x:700,y:369,width:156,height:90,lines:[t?"\uc2e4\ud328 \uc751\ub2f5":"Error response",t?"\ubb38\uc11c \uc811\uadfc \uc5c6\uc74c":"No document access"],accent:j}),(0,i.jsx)(u,{x:440,y:494,size:14,color:h,lines:[t?"\uc694\uccad\uc758 \uc9c4\u
16c704 \ud655\uc778\uacfc \uace0\uac1d \uad8c\ud55c \ud655\uc778\uc740 \ubcc4\uac1c\uc774\uba70, \ub458 \ub2e4 \ud1b5\uacfc\ud574\uc57c \ud569\ub2c8\ub2e4.":"Request authentication and customer authorization are separate. Both must pass."]})]})},en:"Request authentication followed by customer document authorization",ko:"\uc694\uccad \uc778\uc99d\uacfc \uace0\uac1d \ubb38\uc11c \uad8c\ud55c \ud655\uc778 \uc21c\uc11c"}};function y(e){var t=e.kind,n="ko"===(0,s.A)().i18n.currentLocale,c="http-storage-"+(0,r.useId)().replace(/:/g,""),a=f[t],l=a.height,h=a.component,j=a.en,u=a.ko;return(0,i.jsx)("figure",{style:{margin:"1.5rem 0"},children:(0,i.jsxs)("svg",{role:"img","aria-labelledby":c+"-title "+c+"-description",viewBox:"0 0 880 "+l,xmlns:"http://www.w3.org/2000/svg",fill:"none",fontFamily:"var(--ifm-font-family-base)",style:{width:"100%",maxWidth:"880px",height:"auto",display:"block"},children:[(0,i.jsx)("title",{id:c+"-title",children:n?u:j}),(0,i.jsx)("desc",{id:c+"-description",children:n?"Office \ub0b4\uc7a5 HTTP Storage Adapter\uac00 \uc11c\uba85\ub41c \uc694\uccad\uc744 \uace0\uac1d Provider\ub85c \ubcf4\ub0c5\ub2c8\ub2e4. Provider\ub294 \uc694\uccad\uacfc \uace0\uac1d\uc758 \ubb38\uc11c \uad8c\ud55c\uc744 \ud655\uc778\ud55c \ub4a4 \ubb38\uc11c\ub97c \uc77d\uac70\ub098 \uc800\uc7a5\ud569\ub2c8\ub2e4.":"The built-in Office HTTP Storage Adapter sends signed requests to the customer Provider. The Provider checks the request and customer document permission before reading or saving a document."}),(0,i.jsxs)("defs",{children:[(0,i.jsx)("pattern",{id:c+"-dots",width:"18.5",height:"21.5",patternUnits:"userSpaceOnUse",children:(0,i.jsx)("circle",{cx:"11.25",cy:"11.25",r:"1",fill:o})}),(0,i.jsx)("marker",{id:c+"-arrow",viewBox:"0 0 8 8",refX:"7",refY:"4",markerWidth:"6",markerHeight:"6",orient:"auto-start-reverse",children:(0,i.jsx)("path",{d:"M0 0L8 4L0 8",fill:x})})]}),(0,i.jsx)("rect",{width:"880",height:l,rx:"16",fill:d}),(0,i.jsx)("rect",{width:"880",height:l,rx:"16",fill:"url(#"+c+"-dots)"}),(0,i.jsx)(h,{ko:n,marker:c+"-arrow"})]})})}},8453(e,t,n){n.d(t,{R:()=>d,x:()=>o});var r=n(6540);const s={},i=r.createContext(s);function d(e){const t=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:d(e.components),r.createElement(i.Provider,{value:t},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.