xref: /plugin/localopen/README.md (revision b7faabe8fb9b407df41cf2ae269d6b066370c383)
1# DokuWiki Plugin: localopen
2
3Open local files and folders directly from DokuWiki pages — **without browser popups or protocol handlers**.
4
5This plugin replaces `localexplorer://`-style links with a modern, secure approach using a local HTTP service and background requests (`fetch()`), resulting in a seamless, one-click experience.
6
7---
8
9## ✨ Features
10
11- Open local files (`.docx`, `.pdf`, etc.) and folders from wiki links
12- No browser confirmation dialogs
13- No blank tabs or navigation away from the wiki
14- Works with:
15  - Local drives (`C:\`, `Z:\`, etc.)
16  - Network shares (`\\server\share`)
17- Uses standard HTTP instead of custom protocol handlers
18- Lightweight and fast
19
20---
21
22## �� How It Works
23
241. The plugin renders links like:  \[\[localopen>c:\windows|Windows\]\]
25
262. When clicked:
27- A background `fetch()` request is sent to:
28
29  ```
30  http://127.0.0.1:2222/open?path=...&token=...
31  ```
32- A small Python service running locally receives the request
33- The service opens the file using Windows (`os.startfile`)
34
35�� The browser never navigates away from the page.
36
37---
38
39## �� Requirements
40
41- DokuWiki
42- Python 3 (Windows)
43- A local Python service (included below)
44
45---
46
47## �� Installation
48
49### 1. Install the Plugin
50
51Copy the plugin into:  lib/plugins/localopen/
52
53---
54
55### 2. Set Your Token
56
57Choose a secret token (any random string)
58
59
60Update it in:
61
62- the plugin PHP file
63- the Python service
64
65---
66
67### 3. Run the Local Python Service
68
69Save this as `localopen_service.py`:
70
71```python
72from http.server import BaseHTTPRequestHandler, HTTPServer
73from urllib.parse import urlparse, parse_qs, unquote
74import os
75
76HOST = "127.0.0.1"
77PORT = 2222
78TOKEN = "ChangeThisToYourToken"
79
80ALLOWED_PREFIXES = [
81    r"C:\\",
82    r"D:\\",
83    r"Z:\\",
84    r"\\10.2.1.74\\",
85]
86
87def normalize_path(path):
88    path = unquote(path)
89    return path.strip().strip('"').strip("'")
90
91def is_allowed(path):
92    p = path.lower()
93    return any(p.startswith(prefix.lower()) for prefix in ALLOWED_PREFIXES)
94
95class Handler(BaseHTTPRequestHandler):
96    def do_GET(self):
97        parsed = urlparse(self.path)
98
99        if parsed.path != "/open":
100            self.send_error(404)
101            return
102
103        qs = parse_qs(parsed.query)
104        token = qs.get("token", [""])[0]
105        path = normalize_path(qs.get("path", [""])[0])
106
107        if token != TOKEN:
108            self.send_error(403)
109            return
110
111        if not is_allowed(path):
112            self.send_error(403)
113            return
114
115        if not os.path.exists(path):
116            self.send_error(404)
117            return
118
119        os.startfile(path)
120
121        self.send_response(200)
122        self.end_headers()
123
124    def log_message(self, *args):
125        pass
126
127HTTPServer((HOST, PORT), Handler).serve_forever()
128
129