]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | ;;; jabber-httpupload.el --- Emacs Jabber HTTP Upload Implementation -*- lexical-binding: t; -*- |
2 | ||
3 | ;; Copyright 2021 cnngimenez | |
4 | ;; | |
5 | ;; Author: cnngimenez | |
6 | ;; Maintainer: cnngimenez | |
7 | ;; Version: 0.1.0 | |
8 | ;; Keywords: comm | |
9 | ;; URL: https://github.com/cnngimenez/emacs-jabber | |
10 | ;; Package-Requires: ((emacs "26.1") (jabber "0.8.92")) | |
11 | ||
12 | ;; This program is free software: you can redistribute it and/or modify | |
13 | ;; it under the terms of the GNU General Public License as published by | |
14 | ;; the Free Software Foundation, either version 3 of the License, or | |
15 | ;; (at your option) any later version. | |
16 | ||
17 | ;; This program is distributed in the hope that it will be useful, | |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
21 | ||
22 | ;; You should have received a copy of the GNU General Public License | |
23 | ;; along with this program. If not, see <https://www.gnu.org/licenses/>. | |
24 | ||
25 | ||
26 | ;;; Commentary: | |
27 | ||
28 | ;; This file implements XEP-0363: HTTP Upload | |
29 | ;; (https://xmpp.org/extensions/xep-0363.html), providing a way to | |
30 | ;; send files (images, audio, etc) through XMPP clients by using | |
31 | ;; server space, and the HTTP protocol to upload and download from it. | |
32 | ;; The advantage is that the sender user does not need to be connected | |
33 | ;; after sharing the file, and the receiver may be disconnected while | |
34 | ;; the sender is uploading. | |
35 | ||
36 | ;; The procedure to send a file is as follows - | |
37 | ||
38 | ;; 1. Use Disco queries to discover if the server supports the HTTP Upload (~urn:xmpp:http:upload~ namespace). | |
39 | ;; 2. Request a slot to the upload Disco item. The server will answer with a GET and PUT URL. | |
40 | ;; 3. Upload the file to the HTTP server by using the PUT URL. | |
41 | ;; 4. Usually, send the GET URL to the other XMPP clients to allow them to access the uploaded file. | |
42 | ;; | |
43 | ;; TODO - | |
44 | ;; 1. Use wget to send the file | |
45 | ;; 2. Recording audio and sending | |
46 | ||
47 | ;;; Code: | |
48 | ||
49 | (require 'seq) | |
50 | (require 'fsm) | |
51 | (require 'mailcap) | |
52 | (require 'jabber) | |
53 | ||
54 | ;; * Configuration variables * | |
55 | ||
56 | (defgroup jabber-httpupload nil "Jabber HTTP Upload Settings." | |
57 | :group 'jabber) | |
58 | ||
59 | (defcustom jabber-httpupload-upload-function #'jabber-httpupload-put-file-curl | |
60 | "The function used to upload the file. | |
61 | Some functions calls external programs such as Curl and wget, please check their | |
62 | documentation for more information." | |
63 | :group 'jabber-httpupload | |
64 | :type 'function) | |
65 | ||
66 | (defcustom jabber-httpupload-record-command "sox -d -t ogg $(filename).ogg" | |
67 | "What is the command used to record audio? | |
68 | Use $(filename) where the temporal filename should be." | |
69 | :group 'jabber-httpupload | |
70 | :type 'function) | |
71 | ||
72 | ;; Disco is used to discover if HTTP Upload is supported on the server | |
73 | ;; side. Two queries are used: | |
74 | ||
75 | ;; 1. An IQ Disco items request to get all items supported by the | |
76 | ;; server. | |
77 | ;; 2. For each item, an IQ Disco info request to test if the item is | |
78 | ;; the Upload service. | |
79 | ||
80 | ;; The namespace of the HTTP Upload feature is | |
81 | ;; "urn:xmpp:http:upload:0". This will be used on the second query to | |
82 | ;; detect which item is the upload service. | |
83 | ||
84 | ;; For more information, see XML examples at the | |
85 | ;; [[https://xmpp.org/extensions/xep-0363.html#disco][Discovering | |
86 | ;; Support section of XEP-0363]]. | |
87 | ||
88 | ;; This implementation requires an initialization step to fill the | |
89 | ;; `jabber-httpupload-support' variable. This variable registers all | |
90 | ;; connections with their HTTP Upload item. If one of the server | |
91 | ;; associated to a connection does not support HTTP Upload, then it | |
92 | ;; will be registered with a `nil' item. | |
93 | ||
94 | ;; * Discovering support * | |
95 | ||
96 | (defvar jabber-httpupload-support nil | |
97 | "Alist of Jabber connections and the node with HTTP Upload support. | |
98 | This is filled by the `jabber-httpupload-test-all-connections-suport'. | |
99 | Each element are of the form (jabber-connection . string/nil). If the value is | |
100 | a string, it is the upload item IRI, if nil means no support.") | |
101 | ||
102 | (defun jabber-httpupload-test-all-connections-support () | |
103 | "Test all connections in `jabber-connections' for HTTP Upload support. | |
104 | Store the results at `jabber-httpupload-support'. | |
105 | If the connection is already tested, ignore it." | |
106 | (let ((connections (seq-difference jabber-connections | |
107 | (mapcar #'car jabber-httpupload-support)))) | |
108 | (dolist (jc connections) | |
109 | (jabber-httpupload-test-connection-support jc)))) | |
110 | ||
111 | (defun jabber-httpupload-test-connection-support (jc) | |
112 | "Test if HTTP Upload is supported on the JC connection's server. | |
113 | If it is supported, store the item IRI at `jabber-httpupload-support'. | |
114 | ||
115 | This function is asynchronous, thus it won't return any results." | |
116 | (jabber-httpupload-apply-to-items jc | |
117 | (lambda (jc result) | |
118 | (jabber-httpupload-test-item-support jc (elt result 1))))) | |
119 | ||
120 | ;; CALLBACK receives three arguments: the jabber connection, extra | |
121 | ;; data and the query result. The result is a list of features | |
122 | ;; supported by the server. For example, if the client receives the | |
123 | ;; following IQ answer: | |
124 | ;; | |
125 | ;; <iq from="upload.server.org" | |
126 | ;; type="result" | |
127 | ;; to="myjid@server.org/pc1" | |
128 | ;; id="emacs-iq-24678.666.622936"> | |
129 | ;; <query xmlns="http://jabber.org/protocol/disco#info"> | |
130 | ;; <identity name="HTTP File Upload" type="file" category="store"/> | |
131 | ;; <feature var="http://jabber.org/protocol/disco#info"/> | |
132 | ;; <feature var="http://jabber.org/protocol/disco#items"/> | |
133 | ;; <feature var="urn:xmpp:http:upload:0"/> | |
134 | ;; <feature var="urn:xmpp:http:upload"/> | |
135 | ;; <x xmlns="jabber:x:data" type="result"> | |
136 | ;; <field type="hidden" var="FORM_TYPE"> | |
137 | ;; <value>urn:xmpp:http:upload:0</value> | |
138 | ;; </field> | |
139 | ;; <field type="text-single" var="max-file-size"> | |
140 | ;; <value>500000</value> | |
141 | ;; </field> | |
142 | ;; </x> | |
143 | ;; <x xmlns="jabber:x:data" type="result"> | |
144 | ;; <field type="hidden" var="FORM_TYPE"> | |
145 | ;; <value>urn:xmpp:http:upload</value> | |
146 | ;; </field> | |
147 | ;; <field type="text-single" var="max-file-size"> | |
148 | ;; <value>500000</value> | |
149 | ;; </field> | |
150 | ;; </x> | |
151 | ;; </query> | |
152 | ;; </iq> | |
153 | ;; | |
154 | ;; The result would be: | |
155 | ;; | |
156 | ;; ((["HTTP File Upload" "store" "file"]) | |
157 | ;; ("http://jabber.org/protocol/disco#info" | |
158 | ;; "http://jabber.org/protocol/disco#items" | |
159 | ;; "urn:xmpp:http:upload:0" | |
160 | ;; "urn:xmpp:http:upload")) | |
161 | ;; | |
162 | ;; This Disco item supports HTTP Upload because the | |
163 | ;; "urn:xmpp:http:upload" namespace is in the second list. | |
164 | ||
165 | (defun jabber-httpupload-test-item-support (jc iri) | |
166 | "Test if the IRI Disco item supports HTTP Upload. | |
167 | Get the Disco Info from the provided IRI at the current JC jabber connection, | |
168 | if the HTTP Upload namespace feature is in the answer, store the IRI | |
169 | in `jabber-httpupload-support'." | |
170 | (jabber-disco-get-info jc iri nil | |
171 | (lambda (jc _data result) | |
172 | (when (member "urn:xmpp:http:upload" | |
173 | (nth 1 result)) | |
174 | ;; This item supports HTTP Upload... register it! | |
175 | (push (cons jc iri) jabber-httpupload-support))) | |
176 | nil)) | |
177 | ||
178 | ;; CALLBACK receives three arguments: the jabber connection, extra | |
179 | ;; data and the query result. The result is a list of vector with the | |
180 | ;; node name, its IRI and any other properties. | |
181 | ;; | |
182 | ;; For example, if the client receives the following XML: | |
183 | ;; | |
184 | ;; <iq from="server.org" type="result" to="myjid@server.org/pc1" id="emacs-iq-24677.56646.166389"> | |
185 | ;; <query xmlns="http://jabber.org/protocol/disco#items"> | |
186 | ;; <item jid="conference.server.org" name="MUC chats!"/> | |
187 | ;; <item jid="upload.server.org"/> | |
188 | ;; </query> | |
189 | ;; </iq> | |
190 | ;; | |
191 | ;; The result would be: | |
192 | ||
193 | ;; (["MUC chats!" "conference.server.org" nil] [nil "upload.server.org" nil]) | |
194 | ||
195 | (defun jabber-httpupload-apply-to-items (jc callback) | |
196 | "Retrieve al Disco IRIs from the server connected in JC. | |
197 | Return a list of IRI strings. | |
198 | ||
199 | JC is a jabber connection. | |
200 | CALLBACK is a function that receives two arguments: Jabber connection and | |
201 | the item vector." | |
202 | (let ((node (plist-get (fsm-get-state-data jc) :server))) | |
203 | (jabber-disco-get-items jc node nil | |
204 | (lambda (jc _data result) | |
205 | (dolist (item result) | |
206 | (message "item: %S" item) | |
207 | (funcall callback jc item))) | |
208 | nil))) | |
209 | ||
210 | (defun jabber-httpupload-server-has-support (jc) | |
211 | "Check if the server has HTTP Upload support. | |
212 | Return the tuple (jabber-connection . upload-url) when there is support from | |
213 | the server. Return nil when the server does not support HTTP Upload. | |
214 | ||
215 | If the server is not in `jabber-httpupload-support', then it is considered as | |
216 | it is not supported. It SHOULD be tested on-line with | |
217 | `jabber-httpupload-test-connection-support' as soon as the connection and | |
218 | authentication is established. | |
219 | ||
220 | JC is the Jabber Connection to use." | |
221 | ||
222 | (seq-find (lambda (tuple) | |
223 | (and (equal jc (car tuple)) | |
224 | (cdr tuple))) | |
225 | jabber-httpupload-support)) | |
226 | ||
227 | ;; * Requesting a slot * | |
228 | ||
229 | ;; The XEP specifies that the client must ask for a "slot" before | |
230 | ;; uploading the file to the server. The slot is a fresh URL that will | |
231 | ;; be enabled for the client to upload the file. The server may give | |
232 | ;; two URLs in one slot query: the uploading URL and the GET URL to | |
233 | ;; share. | |
234 | ||
235 | ;; The server may limit the file size to upload. | |
236 | ||
237 | ;; <iq from='upload.montague.tld' | |
238 | ;; id='step_03' | |
239 | ;; to='romeo@montague.tld/garden' | |
240 | ;; type='result'> | |
241 | ;; <slot xmlns='urn:xmpp:http:upload:0'> | |
242 | ;; <put url='https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg'> | |
243 | ;; <header name='Authorization'>Basic Base64String==</header> | |
244 | ;; <header name='Cookie'>foo=bar; user=romeo</header> | |
245 | ;; </put> | |
246 | ;; <get url='https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg' /> | |
247 | ;; </slot> | |
248 | ;; </iq> | |
249 | ||
250 | (defun jabber-httpupload-parse-slot-answer (xml-data) | |
251 | "Retrieve the slot data from the XML-DATA information. | |
252 | The XML-DATA is the stanza receive from the Jabber Connection after requesting | |
253 | the slot for a file. | |
254 | The returned list has the PUT URL and the GET URL." | |
255 | (list | |
256 | (jabber-xml-get-attribute (jabber-xml-path xml-data '(slot put)) 'url) | |
257 | (jabber-xml-get-attribute (jabber-xml-path xml-data '(slot get)) 'url))) | |
258 | ||
259 | (defun jabber-httpupload--request-slot-successful (jc xml-data data) | |
260 | "Callback function used when the slot request succeeded. | |
261 | XML-DATA is the received XML from the server. | |
262 | DATA is a triple (filedata success-callback success-args) where: | |
263 | FILEDATA is a triple (filename size content-type) | |
264 | SUCCESS-CALLBACK is a function to call after parsing and requesting the | |
265 | upload. | |
266 | It should accept following arguments: JC XML-DATA FILEDATA PUT-GET-URLS | |
267 | and SUCCESS-ARGS. | |
268 | SUCCESS-ARGS is a list to pass to the SUCCESS-CALLBACK." | |
269 | (let ((urls (jabber-httpupload-parse-slot-answer xml-data)) | |
270 | (filedata (car data)) | |
271 | (success-callback (nth 1 data)) | |
272 | (success-args (nth 2 data))) | |
273 | (funcall success-callback jc xml-data filedata urls success-args))) | |
274 | ||
275 | ;; Maybe this function should be added as lambda inside the jabber-httpupload-request-slot... | |
276 | (defun jabber-httpupload--request-slot-failed (jc xml-data data) | |
277 | "Callback function used when the slot request failed. | |
278 | ||
279 | DATA is a list (filedata error-callback error-args) where: | |
280 | FILEDATA is a triple (filename size content-type) | |
281 | ERROR-CALLBACK is a function to call. If no error-callback is provided, then | |
282 | `error' is used. Its arguments are JC XML-DATA FILEDATA ERROR-ARGS. | |
283 | ERROR-ARGS is list passed to the ERROR-CALLBACK." | |
284 | (let ((filedata (car data)) | |
285 | (error-callback (nth 1 data)) | |
286 | (error-args (nth 2 data))) | |
287 | (if error-callback | |
288 | (funcall error-callback jc xml-data filedata error-args) | |
289 | (error (format "The file %s cannot be uploaded: SLOT rejected. %S" | |
290 | (car data) xml-data))))) | |
291 | ||
292 | ;; The XML used to request a slot is similar to the following - | |
293 | ;; <iq from='romeo@montague.tld/garden' | |
294 | ;; id='step_03' | |
295 | ;; to='upload.montague.tld' | |
296 | ;; type='get'> | |
297 | ;; <request xmlns='urn:xmpp:http:upload:0' | |
298 | ;; filename='trĆØs cool.jpg' | |
299 | ;; size='23456' | |
300 | ;; content-type='image/jpeg' /> | |
301 | ;; </iq> | |
302 | ||
303 | (defun jabber-httpupload-request-slot (jc filedata success-callback success-args | |
304 | &optional error-callback error-args) | |
305 | "Request a slot for HTTP Upload to the server's connection. | |
306 | JC is an active Jabber Connection. | |
307 | FILEDATA is a list with (filename size content-type). | |
308 | SUCCESS-CALLBACK is a function name to call when the slot is received. Its | |
309 | arguments should be: jc xml-data data and put-get-URLs. | |
310 | SUCCESS-ARGS is a list of arguments used by the SUCCESS-CALLBACK | |
311 | ERROR-CALLBACK is a function to call on failure. Its arguments should be: | |
312 | jc xml-data. | |
313 | ERROR-ARGS is a list with arguments for ERROR-CALLBACK." | |
314 | (let ((filename (file-name-nondirectory (car filedata))) | |
315 | (size (nth 1 filedata)) | |
316 | (content-type (nth 2 filedata))) | |
317 | (jabber-send-iq jc (cdr (jabber-httpupload-server-has-support jc)) "get" | |
318 | `(request ((xmlns . "urn:xmpp:http:upload:0") | |
319 | (filename . ,filename) | |
320 | (size . ,size) | |
321 | (content-type . ,content-type))) | |
322 | #'jabber-httpupload--request-slot-successful | |
323 | (list filedata success-callback success-args) | |
324 | #'jabber-httpupload--request-slot-failed | |
325 | (list filedata error-callback error-args)))) | |
326 | ||
327 | ;; * Uploading the file * | |
328 | ||
329 | ;; Use the HTTP protocol to upload the file to the PUT URL provided by | |
330 | ;; the slot. | |
331 | ||
332 | ;; The following functions call the upload programs asynchronously. | |
333 | ;; When the program ends, a callback function is called with one | |
334 | ;; argument provided by the caller function. | |
335 | ||
336 | ;; The uploading process supports multiple calls. For example, the | |
337 | ;; user may call `jabber-httpupload-send-file' again while the upload process of a | |
338 | ;; previous `jabber-httpupload-send-file' call is still running. | |
339 | ||
340 | ;; Also, a callback can be provided in order to send the URL to the | |
341 | ;; receiving Jabber client or to perform any other action after | |
342 | ;; uploading the file. | |
343 | ||
344 | (defun jabber-httpupload-ignore-certificate (jc) | |
345 | "Should the SSL/TLS certificates be ignore from JC connection? | |
346 | Check if JC URL is in the variable `jabber-invalid-certificate-servers', if it | |
347 | is the XMPP and HTTPs connection should be established regarding their | |
348 | certificate validation status." | |
349 | (member (plist-get (fsm-get-state-data jc) :server) | |
350 | jabber-invalid-certificate-servers)) | |
351 | ||
352 | (defun jabber-httpupload-upload-file (filepath content-type put-url | |
353 | callback callback-arg | |
354 | &optional ignore-cert-problems) | |
355 | "Update the given file at FILEPATH to the provided PUT-URL. | |
356 | The CONTENT-TYPE (MIME type) of the file must match the one provided | |
357 | to the Jabber Connection with `jabber-httpupload-request-slot'. | |
358 | IGNORE-CERT-PROBLEMS allows to connect with HTTPS servers with invalid or | |
359 | non-trusted SSL/TLS certificates. | |
360 | When the process ends, a callback function is called using the following | |
361 | code: (funcall CALLBACK CALLBACK-ARG)" | |
362 | (unless (funcall jabber-httpupload-upload-function filepath content-type put-url | |
363 | callback callback-arg | |
364 | ignore-cert-problems) | |
365 | (error (concat "The upload function failed to PUT the file to the server. " | |
366 | "Try other function or install the required program")))) | |
367 | ||
368 | ;; Multiple files can be uploaded in parallel, and thus multiple | |
369 | ;; subprocess could be working at the same time. This happens when the | |
370 | ;; user calls interactively `jabber-httpupload-send-file' twice or while a file is | |
371 | ;; still uploading. | |
372 | ||
373 | ;; This variable keeps track of the subprocesses and their callback | |
374 | ;; along with any data required by these functions. | |
375 | ||
376 | (defvar jabber-httpupload-upload-processes nil | |
377 | "Alist of running processes uploading the file to the server. | |
378 | List of running processes uploading the file to the server | |
379 | associated with their callback and arguments. Each element has | |
380 | the following format: (process . (callback arg))") | |
381 | ||
382 | ;; When the file has been uploaded, the process is still registered | |
383 | ;; with its callback function. This callback should be called and the | |
384 | ;; process deleted from the system. | |
385 | ||
386 | (defun jabber-httpupload-process-ended (process) | |
387 | "What to do when an upload process ends. | |
388 | PROCESS is the process that ended. | |
389 | First remove the process from `jabber-httpupload-upload-processes', | |
390 | then call its callback with the provided argument." | |
391 | (let* ((data (assq process jabber-httpupload-upload-processes)) | |
392 | (callback (cadr data)) | |
393 | (callback-arg (caddr data))) | |
394 | (setq jabber-httpupload-upload-processes | |
395 | (assq-delete-all process jabber-httpupload-upload-processes)) | |
396 | (funcall callback callback-arg))) | |
397 | ||
398 | ;; Using CURL to send the file | |
399 | ||
400 | ;; These functions call curl to send the file to the server. A | |
401 | ;; sentinel is required to check when the subprocess finishes to call | |
402 | ;; the next function (usually, send the URL to the other jabber | |
403 | ;; client). | |
404 | ||
405 | (defun jabber-httpupload-curl-sentinel (process event) | |
406 | "Detect when Curl ends and act accordingly. | |
407 | PROCESS is the asynchronous Curl call. | |
408 | EVENT is a string describing the reason the sentinel were called. | |
409 | ||
410 | When EVENT is \"finished\n\", then the function | |
411 | `jabber-httpupload-process-ended' is called." | |
412 | (with-current-buffer (process-buffer process) | |
413 | (let ((inhibit-read-only t)) | |
414 | (goto-char (point-max)) | |
415 | (insert (format "Sentinel: %S event received." event)))) | |
416 | (when (string= event "finished\n") | |
417 | (jabber-httpupload-process-ended process))) | |
418 | ||
419 | ;; This is the function used to send a file to the server by running a curl subprocess. | |
420 | (defun jabber-httpupload-put-file-curl (filepath content-type put-url | |
421 | callback callback-arg | |
422 | &optional ignore-cert-problems) | |
423 | "Use the curl command to put the file at FILEPATH into the PUT-URL. | |
424 | Send the SIZE and CONTENT-TYPE MIME as headers. | |
425 | IGNORE-CERT-PROBLEMS enable the use of HTTPS connections with invalid or | |
426 | non-trusted SSL/TLS certificates. If nil, curl will validate the certificate | |
427 | provided by the HTTP/S Web server. | |
428 | When the process ends, the function CALLBACK is called like the following | |
429 | call: (funcall CALLBACK CALLBACK-ARG). | |
430 | The process is registered at `jabber-httpupload-upload-processes' AList with | |
431 | the provided CALLBACK and CALLBACK-ARG." | |
432 | (let* ((exec-path (executable-find "curl")) | |
433 | (cmd (format "%s %s --upload-file '%s' -H \"content-type: %s\" '%s'" | |
434 | exec-path | |
435 | (if ignore-cert-problems | |
436 | "--insecure" | |
437 | "") | |
438 | filepath content-type put-url))) | |
439 | (when exec-path | |
440 | (with-current-buffer (get-buffer-create "*jabber-httpupload-put-file-curl*") | |
441 | (let ((inhibit-read-only t)) | |
442 | (goto-char (point-max)) | |
443 | (insert (format "%s Uploading to %s with curl:\n$ %s" | |
444 | (current-time-string) | |
445 | put-url | |
446 | cmd)) | |
447 | (let ((process (start-process-shell-command "jabber-httpupload-put-file-curl" | |
448 | (current-buffer) | |
449 | cmd))) | |
450 | (push (cons process (list callback callback-arg)) | |
451 | jabber-httpupload-upload-processes) | |
452 | (set-process-sentinel process #'jabber-httpupload-curl-sentinel)) | |
453 | (insert "-- done --") | |
454 | t))))) | |
455 | ||
456 | ;; * Send the file URL to the client * | |
457 | ||
458 | ;; The following message is similar to one sent by Conversations - | |
459 | ;; | |
460 | ;; <message from="from_jid@fromserver.org/Resource" | |
461 | ;; id="fc824dcb-c654-4911-a22b-25718dfe4590" | |
462 | ;; type="chat" | |
463 | ;; to="to_jid@toserver.org"> | |
464 | ;; <body>https://fromserver.org:5281/upload/kFTT5ET9JeF_CC6s/_IJNy8ZUSRGiKyVxjf5FkA.jpg</body> | |
465 | ;; <request xmlns="urn:xmpp:receipts"/> | |
466 | ;; <markable xmlns="urn:xmpp:chat-markers:0"/> | |
467 | ;; <origin-id id="fc824dcb-c654-4911-a22b-25718dfe4590" xmlns="urn:xmpp:sid:0"/> | |
468 | ;; <x xmlns="jabber:x:oob"> | |
469 | ;; <url>https://fromserver.org:5281/upload/kFTT5ET9JeF_CC6s/_IJNy8ZUSRGiKyVxjf5FkA.jpg</url> | |
470 | ;; </x> | |
471 | ;; <stanza-id xmlns="urn:xmpp:sid:0" | |
472 | ;; id="7e18d73a-278c-4e5e-bd09-61c12187e5d6" | |
473 | ;; by="to_jid@toserver.org"/> | |
474 | ;; </message> | |
475 | ||
476 | ;; The message should add the "body" and "x" tags. | |
477 | ||
478 | (defun jabber-httpupload-send-file-url (jc jid get-url) | |
479 | "Send the GET URL address to the JID user. | |
480 | The message requiers the GET-URL of the slot file, the receiver's JID | |
481 | and the JC Jabber Connection." | |
482 | ;; This could be a possibliity, but... cannot send the x tag. | |
483 | ;; (jabber-send-message jc jid nil get-url nil) | |
484 | (let ((fromjid (jabber-connection-original-jid jc)) | |
485 | (type (if (assoc jid *jabber-active-groupchats*) | |
486 | "groupchat" | |
487 | "chat"))) | |
488 | (jabber-send-sexp jc | |
489 | `(message ((to . ,jid) | |
490 | (from . ,fromjid) | |
491 | (type . ,type)) | |
492 | (body () ,get-url) | |
493 | (x ((xmlns . "jabber:x:oob")) | |
494 | (url () ,get-url)))))) | |
495 | ||
496 | ;; * Chat buffer * | |
497 | ||
498 | ;; ** Send file (complete process) ** | |
499 | ||
500 | ;; The following functions add interactive commands to the chat buffer | |
501 | ;; to send the GET URL to the current (or selected) client. | |
502 | ||
503 | (defun jabber-httpupload-send-file (jc jid filepath) | |
504 | "Send the file at FILEPATH to the user JID. | |
505 | JC is the Jabber Connection to send the file URL." | |
506 | (interactive (list (jabber-read-account) | |
507 | (jabber-read-jid-completing "Send file to: " nil nil nil 'full t) | |
508 | (read-file-name "File to send:"))) | |
509 | (unless (jabber-httpupload-server-has-support jc) | |
510 | (error "The Jabber Connection provided has no HTTP Upload support")) | |
511 | (let* ((size (file-attribute-size (file-attributes filepath))) | |
512 | (content-type (mailcap-extension-to-mime (file-name-extension filepath))) | |
513 | (filedata (list filepath size content-type))) | |
514 | (jabber-httpupload-request-slot jc filedata | |
515 | #'jabber-httpupload--slot-reserved | |
516 | (list jid)))) | |
517 | ||
518 | ;; The following functions are callbacks used in the following order: | |
519 | ||
520 | ;; 1. `jabber-httpupload-request-slot' calls `jabber-httpupload--slot-reserved'. | |
521 | ;; 2. `jabber-httpupload--slot-reserved' calls `jabber-httpupload--upload-done'. | |
522 | ;; 3. `jabber-httpupload--upload-done' calls `jabber-httpupload-send-file-url'. | |
523 | ||
524 | ;; This form of calling is required because of the asynchronous | |
525 | ;; behaviour of the upload file function. | |
526 | ||
527 | (defun jabber-httpupload--upload-done (data) | |
528 | "Callback function used when the upload is done. | |
529 | When the upload process finished, a callback function is called with an | |
530 | argument. | |
531 | This function is expected to be used as the CALLBACK argument for the function | |
532 | `jabber-httpupload-upload-file', DATA is its CALLBACK-ARG argument. | |
533 | Also, see `jabber-httpupload-process-ended' for more information. | |
534 | DATA is expected to have the following foramt: (jc jid get-url). | |
535 | After the upload is done, send the get-url to the destined Jabber user JID." | |
536 | (let ((jc (car data)) | |
537 | (jid (nth 1 data)) | |
538 | (get-url (nth 2 data))) | |
539 | (condition-case err | |
540 | (jabber-httpupload-send-file-url jc jid get-url) | |
541 | (error "Cannot send message. Error: %S" err)))) | |
542 | ||
543 | ;; When the slot is reserved, the HTTP upload should be started. | |
544 | (defun jabber-httpupload--slot-reserved (jc _xml-data filedata urls extra-data) | |
545 | "Callback function used when the slot request succeeded. | |
546 | JC is the current Jabber Connection. | |
547 | XML-DATA is the received XML from the server. | |
548 | FILEDATA is a triple `(filepath size content-type). | |
549 | URLS is a tuple `(put-url get-url). | |
550 | EXTRA-DATA is a list `(jid)" | |
551 | (let ((filepath (car filedata)) | |
552 | (content-type (nth 2 filedata)) | |
553 | (jid (car extra-data)) | |
554 | (get-url (cadr urls)) | |
555 | (put-url (car urls))) | |
556 | (message "jabber-httpupload: slot PUT and GET URLs: %S" urls) | |
557 | (condition-case err | |
558 | (jabber-httpupload-upload-file (expand-file-name filepath) | |
559 | content-type | |
560 | put-url | |
561 | #'jabber-httpupload--upload-done (list jc jid get-url) | |
562 | (jabber-httpupload-ignore-certificate jc)) | |
563 | (error "Cannot upload the file. Error: %S" err)))) | |
564 | ||
565 | ;; ** TODO Recording and sending audio ** | |
566 | ||
567 | ;; TODO | |
568 | (defun jabber-httpupload--record-audio () | |
569 | "Create a new audio record and save the file into a temporal directory." | |
570 | (let ((process (start-process-shell-command | |
571 | "jabber-httpupload-record-audio" | |
572 | (current-buffer) | |
573 | (replace-string "$(filename" | |
574 | "/tmp/jabber-httpupload-record" | |
575 | jabber-httpupload-record-command)))) | |
576 | (set-process-sentinel process #'jabber-httpupload-record-sentinel))) | |
577 | ||
578 | ;; * Add hooks * | |
579 | ;; Some function should start automatically. | |
580 | ||
581 | ;; ** Test connection support after session is established ** | |
582 | ;; Call `jabber-httpupload-test-connection-support' as soon as | |
583 | ||
584 | ;; * Adding functions to hooks * | |
585 | ;; ** Test HTTP Upload support after connecting ** | |
586 | (add-hook 'jabber-post-connect-hooks #'jabber-httpupload-test-connection-support) | |
587 | ||
588 | (provide 'jabber-httpupload) | |
589 | ||
590 | ;;; jabber-httpupload.el ends here |