|
c313cac…
|
stephan
|
1 |
/* |
|
c313cac…
|
stephan
|
2 |
** Copyright (c) 2020 D. Richard Hipp |
|
c313cac…
|
stephan
|
3 |
** |
|
c313cac…
|
stephan
|
4 |
** This program is free software; you can redistribute it and/or |
|
c313cac…
|
stephan
|
5 |
** modify it under the terms of the Simplified BSD License (also |
|
c313cac…
|
stephan
|
6 |
** known as the "2-Clause License" or "FreeBSD License".) |
|
c313cac…
|
stephan
|
7 |
** |
|
c313cac…
|
stephan
|
8 |
** This program is distributed in the hope that it will be useful, |
|
c313cac…
|
stephan
|
9 |
** but without any warranty; without even the implied warranty of |
|
c313cac…
|
stephan
|
10 |
** merchantability or fitness for a particular purpose. |
|
c313cac…
|
stephan
|
11 |
** |
|
c313cac…
|
stephan
|
12 |
** Author contact information: |
|
c313cac…
|
stephan
|
13 |
** [email protected] |
|
c313cac…
|
stephan
|
14 |
** http://www.hwaci.com/drh/ |
|
c313cac…
|
stephan
|
15 |
** |
|
c313cac…
|
stephan
|
16 |
******************************************************************************* |
|
c313cac…
|
stephan
|
17 |
** |
|
c313cac…
|
stephan
|
18 |
** This file contains shared Ajax-related code for /fileedit, the wiki/forum |
|
c313cac…
|
stephan
|
19 |
** editors, and friends. |
|
c313cac…
|
stephan
|
20 |
*/ |
|
c313cac…
|
stephan
|
21 |
#include "config.h" |
|
c313cac…
|
stephan
|
22 |
#include "ajax.h" |
|
c313cac…
|
stephan
|
23 |
#include <assert.h> |
|
c313cac…
|
stephan
|
24 |
#include <stdarg.h> |
|
c313cac…
|
stephan
|
25 |
|
|
c313cac…
|
stephan
|
26 |
#if INTERFACE |
|
c313cac…
|
stephan
|
27 |
/* enum ajax_render_preview_flags: */ |
|
c313cac…
|
stephan
|
28 |
#define AJAX_PREVIEW_LINE_NUMBERS 1 |
|
c313cac…
|
stephan
|
29 |
/* enum ajax_render_modes: */ |
|
c313cac…
|
stephan
|
30 |
#define AJAX_RENDER_GUESS 0 /* Guess rendering mode based on mimetype. */ |
|
c313cac…
|
stephan
|
31 |
/* GUESS must be 0. All others have unspecified values. */ |
|
c313cac…
|
stephan
|
32 |
#define AJAX_RENDER_PLAIN_TEXT 1 /* Render as plain text. */ |
|
c313cac…
|
stephan
|
33 |
#define AJAX_RENDER_HTML_IFRAME 2 /* Render as HTML inside an IFRAME. */ |
|
c313cac…
|
stephan
|
34 |
#define AJAX_RENDER_HTML_INLINE 3 /* Render as HTML without an IFRAME. */ |
|
c313cac…
|
stephan
|
35 |
#define AJAX_RENDER_WIKI 4 /* Render as wiki/markdown. */ |
|
c313cac…
|
stephan
|
36 |
#endif |
|
c313cac…
|
stephan
|
37 |
|
|
c313cac…
|
stephan
|
38 |
/* |
|
c313cac…
|
stephan
|
39 |
** Emits JS code which initializes the |
|
c313cac…
|
stephan
|
40 |
** fossil.page.previewModes object to a map of AJAX_RENDER_xxx values |
|
c313cac…
|
stephan
|
41 |
** and symbolic names for use by client-side scripts. |
|
c313cac…
|
stephan
|
42 |
** |
|
c313cac…
|
stephan
|
43 |
** If addScriptTag is true then the output is wrapped in a SCRIPT tag |
|
c313cac…
|
stephan
|
44 |
** with the current nonce, else no SCRIPT tag is emitted. |
|
c313cac…
|
stephan
|
45 |
** |
|
34f7fd7…
|
stephan
|
46 |
** Requires that builtin_emit_script_fossil_bootstrap() has already been |
|
c313cac…
|
stephan
|
47 |
** called in order to initialize the window.fossil.page object. |
|
c313cac…
|
stephan
|
48 |
*/ |
|
c313cac…
|
stephan
|
49 |
void ajax_emit_js_preview_modes(int addScriptTag){ |
|
c313cac…
|
stephan
|
50 |
if(addScriptTag){ |
|
6854244…
|
drh
|
51 |
style_script_begin(__FILE__,__LINE__); |
|
c313cac…
|
stephan
|
52 |
} |
|
c313cac…
|
stephan
|
53 |
CX("fossil.page.previewModes={" |
|
c313cac…
|
stephan
|
54 |
"guess: %d, %d: 'guess', wiki: %d, %d: 'wiki'," |
|
c313cac…
|
stephan
|
55 |
"htmlIframe: %d, %d: 'htmlIframe', " |
|
c313cac…
|
stephan
|
56 |
"htmlInline: %d, %d: 'htmlInline', " |
|
c313cac…
|
stephan
|
57 |
"text: %d, %d: 'text'" |
|
c313cac…
|
stephan
|
58 |
"};\n", |
|
c313cac…
|
stephan
|
59 |
AJAX_RENDER_GUESS, AJAX_RENDER_GUESS, |
|
c313cac…
|
stephan
|
60 |
AJAX_RENDER_WIKI, AJAX_RENDER_WIKI, |
|
c313cac…
|
stephan
|
61 |
AJAX_RENDER_HTML_IFRAME, AJAX_RENDER_HTML_IFRAME, |
|
c313cac…
|
stephan
|
62 |
AJAX_RENDER_HTML_INLINE, AJAX_RENDER_HTML_INLINE, |
|
c313cac…
|
stephan
|
63 |
AJAX_RENDER_PLAIN_TEXT, AJAX_RENDER_PLAIN_TEXT); |
|
c313cac…
|
stephan
|
64 |
if(addScriptTag){ |
|
6854244…
|
drh
|
65 |
style_script_end(); |
|
c313cac…
|
stephan
|
66 |
} |
|
c313cac…
|
stephan
|
67 |
} |
|
c313cac…
|
stephan
|
68 |
|
|
c313cac…
|
stephan
|
69 |
/* |
|
c313cac…
|
stephan
|
70 |
** Returns a value from the ajax_render_modes enum, based on the |
|
4cb50c4…
|
stephan
|
71 |
** given mimetype string (which may be NULL), defaulting to |
|
c313cac…
|
stephan
|
72 |
** AJAX_RENDER_PLAIN_TEXT. |
|
c313cac…
|
stephan
|
73 |
*/ |
|
c313cac…
|
stephan
|
74 |
int ajax_render_mode_for_mimetype(const char * zMimetype){ |
|
c313cac…
|
stephan
|
75 |
int rc = AJAX_RENDER_PLAIN_TEXT; |
|
c313cac…
|
stephan
|
76 |
if( zMimetype ){ |
|
c313cac…
|
stephan
|
77 |
if( fossil_strcmp(zMimetype, "text/html")==0 ){ |
|
c313cac…
|
stephan
|
78 |
rc = AJAX_RENDER_HTML_IFRAME; |
|
c313cac…
|
stephan
|
79 |
}else if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 |
|
c313cac…
|
stephan
|
80 |
|| fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ |
|
c313cac…
|
stephan
|
81 |
rc = AJAX_RENDER_WIKI; |
|
c313cac…
|
stephan
|
82 |
} |
|
c313cac…
|
stephan
|
83 |
} |
|
c313cac…
|
stephan
|
84 |
return rc; |
|
c313cac…
|
stephan
|
85 |
} |
|
c313cac…
|
stephan
|
86 |
|
|
c313cac…
|
stephan
|
87 |
/* |
|
c313cac…
|
stephan
|
88 |
** Renders text/wiki content preview for various /ajax routes. |
|
c313cac…
|
stephan
|
89 |
** |
|
c313cac…
|
stephan
|
90 |
** pContent is text/wiki content to preview. zName is the name of the |
|
c313cac…
|
stephan
|
91 |
** content, for purposes of determining the mimetype based on the |
|
c313cac…
|
stephan
|
92 |
** extension (if NULL, mimetype text/plain is assumed). flags may be a |
|
c313cac…
|
stephan
|
93 |
** bitmask of values from the ajax_render_preview_flags |
|
c313cac…
|
stephan
|
94 |
** enum. *renderMode must specify the render mode to use. If |
|
c313cac…
|
stephan
|
95 |
** *renderMode==AJAX_RENDER_GUESS then *renderMode gets set to the |
|
c313cac…
|
stephan
|
96 |
** mode which is guessed at for the rendering (based on the mimetype). |
|
c313cac…
|
stephan
|
97 |
** |
|
c313cac…
|
stephan
|
98 |
** nIframeHeightEm is only used for the AJAX_RENDER_HTML_IFRAME |
|
c313cac…
|
stephan
|
99 |
** renderMode, and specifies the height, in EM's, of the resulting |
|
c313cac…
|
stephan
|
100 |
** iframe. If passed 0, it defaults to "some sane value." |
|
c313cac…
|
stephan
|
101 |
*/ |
|
c313cac…
|
stephan
|
102 |
void ajax_render_preview(Blob * pContent, const char *zName, |
|
c313cac…
|
stephan
|
103 |
int flags, int * renderMode, |
|
c313cac…
|
stephan
|
104 |
int nIframeHeightEm){ |
|
c313cac…
|
stephan
|
105 |
const char * zMime; |
|
c313cac…
|
stephan
|
106 |
|
|
c313cac…
|
stephan
|
107 |
zMime = zName ? mimetype_from_name(zName) : "text/plain"; |
|
c313cac…
|
stephan
|
108 |
if(AJAX_RENDER_GUESS==*renderMode){ |
|
c313cac…
|
stephan
|
109 |
*renderMode = ajax_render_mode_for_mimetype(zMime); |
|
c313cac…
|
stephan
|
110 |
} |
|
c313cac…
|
stephan
|
111 |
switch(*renderMode){ |
|
c313cac…
|
stephan
|
112 |
case AJAX_RENDER_HTML_IFRAME:{ |
|
c313cac…
|
stephan
|
113 |
char * z64 = encode64(blob_str(pContent), blob_size(pContent)); |
|
c313cac…
|
stephan
|
114 |
CX("<iframe width='100%%' frameborder='0' " |
|
c313cac…
|
stephan
|
115 |
"marginwidth='0' style='height:%dem' " |
|
c313cac…
|
stephan
|
116 |
"marginheight='0' sandbox='allow-same-origin' " |
|
c313cac…
|
stephan
|
117 |
"src='data:text/html;base64,%z'" |
|
c313cac…
|
stephan
|
118 |
"></iframe>", |
|
c313cac…
|
stephan
|
119 |
nIframeHeightEm ? nIframeHeightEm : 40, |
|
c313cac…
|
stephan
|
120 |
z64); |
|
c313cac…
|
stephan
|
121 |
break; |
|
c313cac…
|
stephan
|
122 |
} |
|
c313cac…
|
stephan
|
123 |
case AJAX_RENDER_HTML_INLINE:{ |
|
c313cac…
|
stephan
|
124 |
CX("%b",pContent); |
|
c313cac…
|
stephan
|
125 |
break; |
|
c313cac…
|
stephan
|
126 |
} |
|
c313cac…
|
stephan
|
127 |
case AJAX_RENDER_WIKI: |
|
c313cac…
|
stephan
|
128 |
safe_html_context(DOCSRC_FILE); |
|
c313cac…
|
stephan
|
129 |
wiki_render_by_mimetype(pContent, zMime); |
|
c313cac…
|
stephan
|
130 |
break; |
|
c313cac…
|
stephan
|
131 |
default:{ |
|
c313cac…
|
stephan
|
132 |
const char *zContent = blob_str(pContent); |
|
c313cac…
|
stephan
|
133 |
if(AJAX_PREVIEW_LINE_NUMBERS & flags){ |
|
b699040…
|
drh
|
134 |
output_text_with_line_numbers(zContent, blob_size(pContent), |
|
34f7fd7…
|
stephan
|
135 |
zName, "on", 0); |
|
c313cac…
|
stephan
|
136 |
}else{ |
|
c313cac…
|
stephan
|
137 |
const char *zExt = strrchr(zName,'.'); |
|
c313cac…
|
stephan
|
138 |
if(zExt && zExt[1]){ |
|
c313cac…
|
stephan
|
139 |
CX("<pre><code class='language-%s'>%h</code></pre>", |
|
c313cac…
|
stephan
|
140 |
zExt+1, zContent); |
|
c313cac…
|
stephan
|
141 |
}else{ |
|
c313cac…
|
stephan
|
142 |
CX("<pre>%h</pre>", zContent); |
|
c313cac…
|
stephan
|
143 |
} |
|
c313cac…
|
stephan
|
144 |
} |
|
c313cac…
|
stephan
|
145 |
break; |
|
c313cac…
|
stephan
|
146 |
} |
|
c313cac…
|
stephan
|
147 |
} |
|
c313cac…
|
stephan
|
148 |
} |
|
c313cac…
|
stephan
|
149 |
|
|
c313cac…
|
stephan
|
150 |
/* |
|
c313cac…
|
stephan
|
151 |
** Renders diffs for ajax routes. pOrig is the "original" (v1) content |
|
c313cac…
|
stephan
|
152 |
** and pContent is the locally-edited (v2) content. diffFlags is any |
|
c313cac…
|
stephan
|
153 |
** set of flags suitable for passing to text_diff(). |
|
ef69044…
|
stephan
|
154 |
** |
|
ef69044…
|
stephan
|
155 |
** zOrigHash, if not NULL, must be the SCM-side hash of pOrig's |
|
ef69044…
|
stephan
|
156 |
** contents. If set, additional information may be built into |
|
ef69044…
|
stephan
|
157 |
** the diff output to enable dynamic loading of additional |
|
ef69044…
|
stephan
|
158 |
** diff context. |
|
c313cac…
|
stephan
|
159 |
*/ |
|
ef69044…
|
stephan
|
160 |
void ajax_render_diff(Blob * pOrig, const char * zOrigHash, |
|
ef69044…
|
stephan
|
161 |
Blob *pContent, u64 diffFlags){ |
|
c313cac…
|
stephan
|
162 |
Blob out = empty_blob; |
|
1347a1d…
|
drh
|
163 |
DiffConfig DCfg; |
|
c313cac…
|
stephan
|
164 |
|
|
1347a1d…
|
drh
|
165 |
diff_config_init(&DCfg, diffFlags); |
|
ef69044…
|
stephan
|
166 |
DCfg.zLeftHash = zOrigHash; |
|
1347a1d…
|
drh
|
167 |
text_diff(pOrig, pContent, &out, &DCfg); |
|
c313cac…
|
stephan
|
168 |
if(blob_size(&out)==0){ |
|
c313cac…
|
stephan
|
169 |
/* nothing to do */ |
|
c313cac…
|
stephan
|
170 |
}else{ |
|
0cbfc02…
|
stephan
|
171 |
CX("%b",&out); |
|
c313cac…
|
stephan
|
172 |
} |
|
c313cac…
|
stephan
|
173 |
blob_reset(&out); |
|
19f2753…
|
stephan
|
174 |
} |
|
19f2753…
|
stephan
|
175 |
|
|
19f2753…
|
stephan
|
176 |
/* |
|
19f2753…
|
stephan
|
177 |
** Uses P(zKey) to fetch a CGI environment variable. If that var is |
|
19f2753…
|
stephan
|
178 |
** NULL or starts with '0' or 'f' then this function returns false, |
|
19f2753…
|
stephan
|
179 |
** else it returns true. |
|
19f2753…
|
stephan
|
180 |
*/ |
|
19f2753…
|
stephan
|
181 |
int ajax_p_bool(char const *zKey){ |
|
19f2753…
|
stephan
|
182 |
const char * zVal = P(zKey); |
|
19f2753…
|
stephan
|
183 |
return (!zVal || '0'==*zVal || 'f'==*zVal) ? 0 : 1; |
|
c313cac…
|
stephan
|
184 |
} |
|
c313cac…
|
stephan
|
185 |
|
|
c313cac…
|
stephan
|
186 |
/* |
|
c313cac…
|
stephan
|
187 |
** Helper for /ajax routes. Clears the CGI content buffer, sets an |
|
c313cac…
|
stephan
|
188 |
** HTTP error status code, and queues up a JSON response in the form |
|
c313cac…
|
stephan
|
189 |
** of an object: |
|
c313cac…
|
stephan
|
190 |
** |
|
c313cac…
|
stephan
|
191 |
** {error: formatted message} |
|
c313cac…
|
stephan
|
192 |
** |
|
c313cac…
|
stephan
|
193 |
** If httpCode<=0 then it defaults to 500. |
|
c313cac…
|
stephan
|
194 |
** |
|
c313cac…
|
stephan
|
195 |
** After calling this, the caller should immediately return. |
|
c313cac…
|
stephan
|
196 |
*/ |
|
c313cac…
|
stephan
|
197 |
void ajax_route_error(int httpCode, const char * zFmt, ...){ |
|
c313cac…
|
stephan
|
198 |
Blob msg = empty_blob; |
|
c313cac…
|
stephan
|
199 |
Blob content = empty_blob; |
|
c313cac…
|
stephan
|
200 |
va_list vargs; |
|
c313cac…
|
stephan
|
201 |
va_start(vargs,zFmt); |
|
c313cac…
|
stephan
|
202 |
blob_vappendf(&msg, zFmt, vargs); |
|
c313cac…
|
stephan
|
203 |
va_end(vargs); |
|
c313cac…
|
stephan
|
204 |
blob_appendf(&content,"{\"error\":%!j}", blob_str(&msg)); |
|
c313cac…
|
stephan
|
205 |
blob_reset(&msg); |
|
c313cac…
|
stephan
|
206 |
cgi_set_content(&content); |
|
c313cac…
|
stephan
|
207 |
cgi_set_status(httpCode>0 ? httpCode : 500, "Error"); |
|
c313cac…
|
stephan
|
208 |
cgi_set_content_type("application/json"); |
|
c313cac…
|
stephan
|
209 |
} |
|
c313cac…
|
stephan
|
210 |
|
|
c313cac…
|
stephan
|
211 |
/* |
|
c313cac…
|
stephan
|
212 |
** Performs bootstrapping common to the /ajax/xyz AJAX routes, such as |
|
c313cac…
|
stephan
|
213 |
** logging in the user. |
|
c313cac…
|
stephan
|
214 |
** |
|
c313cac…
|
stephan
|
215 |
** Returns false (0) if bootstrapping fails, in which case it has |
|
c313cac…
|
stephan
|
216 |
** reported the error and the route should immediately return. Returns |
|
c313cac…
|
stephan
|
217 |
** true on success. |
|
c313cac…
|
stephan
|
218 |
** |
|
c313cac…
|
stephan
|
219 |
** If requireWrite is true then write permissions are required. |
|
c313cac…
|
stephan
|
220 |
** If requirePost is true then the request is assumed to be using |
|
c313cac…
|
stephan
|
221 |
** POST'ed data and CSRF validation is performed. |
|
c313cac…
|
stephan
|
222 |
** |
|
c313cac…
|
stephan
|
223 |
*/ |
|
c313cac…
|
stephan
|
224 |
int ajax_route_bootstrap(int requireWrite, int requirePost){ |
|
c313cac…
|
stephan
|
225 |
login_check_credentials(); |
|
c313cac…
|
stephan
|
226 |
if( requireWrite!=0 && g.perm.Write==0 ){ |
|
c313cac…
|
stephan
|
227 |
ajax_route_error(403,"Write permissions required."); |
|
c313cac…
|
stephan
|
228 |
return 0; |
|
c313cac…
|
stephan
|
229 |
}else if(0==cgi_csrf_safe(requirePost)){ |
|
c313cac…
|
stephan
|
230 |
ajax_route_error(403, |
|
c313cac…
|
stephan
|
231 |
"CSRF violation (make sure sending of HTTP " |
|
c313cac…
|
stephan
|
232 |
"Referer headers is enabled for XHR " |
|
c313cac…
|
stephan
|
233 |
"connections)."); |
|
c313cac…
|
stephan
|
234 |
return 0; |
|
c313cac…
|
stephan
|
235 |
} |
|
c313cac…
|
stephan
|
236 |
return 1; |
|
c313cac…
|
stephan
|
237 |
} |
|
c313cac…
|
stephan
|
238 |
|
|
c313cac…
|
stephan
|
239 |
/* |
|
bc36fdc…
|
danield
|
240 |
** Helper for collecting filename/check-in request parameters. |
|
c313cac…
|
stephan
|
241 |
** |
|
c313cac…
|
stephan
|
242 |
** If zFn is not NULL, it is assigned the value of the first one of |
|
c313cac…
|
stephan
|
243 |
** the "filename" or "fn" CGI parameters which is set. |
|
c313cac…
|
stephan
|
244 |
** |
|
c313cac…
|
stephan
|
245 |
** If zCi is not NULL, it is assigned the value of the first one of |
|
c313cac…
|
stephan
|
246 |
** the "checkin" or "ci" CGI parameters which is set. |
|
c313cac…
|
stephan
|
247 |
** |
|
c313cac…
|
stephan
|
248 |
** If a parameter is not NULL, it will be assigned NULL if the |
|
c313cac…
|
stephan
|
249 |
** corresponding parameter is not set. |
|
c313cac…
|
stephan
|
250 |
** |
|
c313cac…
|
stephan
|
251 |
** Returns the number of non-NULL values it assigns to arguments. Thus |
|
c313cac…
|
stephan
|
252 |
** if passed (&x, NULL), it returns 1 if it assigns non-NULL to *x and |
|
c313cac…
|
stephan
|
253 |
** 0 if it assigns NULL to *x. |
|
c313cac…
|
stephan
|
254 |
*/ |
|
c313cac…
|
stephan
|
255 |
int ajax_get_fnci_args( const char **zFn, const char **zCi ){ |
|
c313cac…
|
stephan
|
256 |
int rc = 0; |
|
c313cac…
|
stephan
|
257 |
if(zCi!=0){ |
|
c313cac…
|
stephan
|
258 |
*zCi = PD("checkin",P("ci")); |
|
c313cac…
|
stephan
|
259 |
if( *zCi ) ++rc; |
|
c313cac…
|
stephan
|
260 |
} |
|
c313cac…
|
stephan
|
261 |
if(zFn!=0){ |
|
c313cac…
|
stephan
|
262 |
*zFn = PD("filename",P("fn")); |
|
c313cac…
|
stephan
|
263 |
if (*zFn) ++rc; |
|
c313cac…
|
stephan
|
264 |
} |
|
c313cac…
|
stephan
|
265 |
return rc; |
|
c313cac…
|
stephan
|
266 |
} |
|
c313cac…
|
stephan
|
267 |
|
|
c313cac…
|
stephan
|
268 |
/* |
|
6c1ac83…
|
stephan
|
269 |
** AJAX route /ajax/preview-text |
|
c313cac…
|
stephan
|
270 |
** |
|
c313cac…
|
stephan
|
271 |
** Required query parameters: |
|
c313cac…
|
stephan
|
272 |
** |
|
c313cac…
|
stephan
|
273 |
** filename=name of content, for use in determining the |
|
6c1ac83…
|
stephan
|
274 |
** mimetype/render mode. |
|
6c1ac83…
|
stephan
|
275 |
** |
|
6c1ac83…
|
stephan
|
276 |
** content=text |
|
c313cac…
|
stephan
|
277 |
** |
|
c313cac…
|
stephan
|
278 |
** Optional query parameters: |
|
c313cac…
|
stephan
|
279 |
** |
|
c313cac…
|
stephan
|
280 |
** render_mode=integer (AJAX_RENDER_xxx) (default=AJAX_RENDER_GUESS) |
|
c313cac…
|
stephan
|
281 |
** |
|
c313cac…
|
stephan
|
282 |
** ln=0 or 1 to disable/enable line number mode in |
|
c313cac…
|
stephan
|
283 |
** AJAX_RENDER_PLAIN_TEXT mode. |
|
c313cac…
|
stephan
|
284 |
** |
|
c313cac…
|
stephan
|
285 |
** iframe_height=integer (default=40) Height, in EMs of HTML preview |
|
c313cac…
|
stephan
|
286 |
** iframe. |
|
c313cac…
|
stephan
|
287 |
** |
|
c313cac…
|
stephan
|
288 |
** User must have Write access to use this page. |
|
c313cac…
|
stephan
|
289 |
** |
|
c313cac…
|
stephan
|
290 |
** Responds with the HTML content of the preview. On error it produces |
|
c313cac…
|
stephan
|
291 |
** a JSON response as documented for ajax_route_error(). |
|
c313cac…
|
stephan
|
292 |
** |
|
c313cac…
|
stephan
|
293 |
** Extra response headers: |
|
c313cac…
|
stephan
|
294 |
** |
|
c313cac…
|
stephan
|
295 |
** x-ajax-render-mode: string representing the rendering mode |
|
c313cac…
|
stephan
|
296 |
** which was really used (which will differ from the requested mode |
|
c313cac…
|
stephan
|
297 |
** only if mode 0 (guess) was requested). The names are documented |
|
c313cac…
|
stephan
|
298 |
** below in code and match those in the emitted JS object |
|
c313cac…
|
stephan
|
299 |
** fossil.page.previewModes. |
|
c313cac…
|
stephan
|
300 |
*/ |
|
c313cac…
|
stephan
|
301 |
void ajax_route_preview_text(void){ |
|
c313cac…
|
stephan
|
302 |
const char * zFilename = 0; |
|
c313cac…
|
stephan
|
303 |
const char * zContent = P("content"); |
|
c313cac…
|
stephan
|
304 |
int renderMode = atoi(PD("render_mode","0")); |
|
c313cac…
|
stephan
|
305 |
int ln = atoi(PD("ln","0")); |
|
c313cac…
|
stephan
|
306 |
int iframeHeight = atoi(PD("iframe_height","40")); |
|
c313cac…
|
stephan
|
307 |
Blob content = empty_blob; |
|
c313cac…
|
stephan
|
308 |
const char * zRenderMode = 0; |
|
c313cac…
|
stephan
|
309 |
|
|
c313cac…
|
stephan
|
310 |
ajax_get_fnci_args( &zFilename, 0 ); |
|
c313cac…
|
stephan
|
311 |
|
|
3ecef40…
|
drh
|
312 |
if(!ajax_route_bootstrap(0,1)){ |
|
c313cac…
|
stephan
|
313 |
return; |
|
c313cac…
|
stephan
|
314 |
} |
|
c313cac…
|
stephan
|
315 |
if(zFilename==0){ |
|
c313cac…
|
stephan
|
316 |
/* The filename is only used for mimetype determination, |
|
c313cac…
|
stephan
|
317 |
** so we can default it... */ |
|
c313cac…
|
stephan
|
318 |
zFilename = "foo.txt"; |
|
c313cac…
|
stephan
|
319 |
} |
|
c313cac…
|
stephan
|
320 |
cgi_set_content_type("text/html"); |
|
c313cac…
|
stephan
|
321 |
blob_init(&content, zContent, -1); |
|
c313cac…
|
stephan
|
322 |
ajax_render_preview(&content, zFilename, |
|
c313cac…
|
stephan
|
323 |
ln ? AJAX_PREVIEW_LINE_NUMBERS : 0, |
|
c313cac…
|
stephan
|
324 |
&renderMode, iframeHeight); |
|
c313cac…
|
stephan
|
325 |
/* |
|
c313cac…
|
stephan
|
326 |
** Now tell the caller if we did indeed use AJAX_RENDER_WIKI, so that |
|
c313cac…
|
stephan
|
327 |
** they can re-set the <base href> to an appropriate value (which |
|
bc36fdc…
|
danield
|
328 |
** requires knowing the content's current check-in version, which we |
|
c313cac…
|
stephan
|
329 |
** don't have here). |
|
c313cac…
|
stephan
|
330 |
*/ |
|
c313cac…
|
stephan
|
331 |
switch(renderMode){ |
|
c313cac…
|
stephan
|
332 |
/* The strings used here MUST correspond to those used in the JS-side |
|
c313cac…
|
stephan
|
333 |
** fossil.page.previewModes map. |
|
c313cac…
|
stephan
|
334 |
*/ |
|
c313cac…
|
stephan
|
335 |
case AJAX_RENDER_WIKI: zRenderMode = "wiki"; break; |
|
c313cac…
|
stephan
|
336 |
case AJAX_RENDER_HTML_INLINE: zRenderMode = "htmlInline"; break; |
|
c313cac…
|
stephan
|
337 |
case AJAX_RENDER_HTML_IFRAME: zRenderMode = "htmlIframe"; break; |
|
c313cac…
|
stephan
|
338 |
case AJAX_RENDER_PLAIN_TEXT: zRenderMode = "text"; break; |
|
c313cac…
|
stephan
|
339 |
case AJAX_RENDER_GUESS: |
|
c313cac…
|
stephan
|
340 |
assert(!"cannot happen"); |
|
c313cac…
|
stephan
|
341 |
} |
|
c313cac…
|
stephan
|
342 |
if(zRenderMode!=0){ |
|
c313cac…
|
stephan
|
343 |
cgi_printf_header("x-ajax-render-mode: %s\r\n", zRenderMode); |
|
c313cac…
|
stephan
|
344 |
} |
|
c313cac…
|
stephan
|
345 |
} |
|
c313cac…
|
stephan
|
346 |
|
|
19f2753…
|
stephan
|
347 |
#if INTERFACE |
|
c313cac…
|
stephan
|
348 |
/* |
|
c313cac…
|
stephan
|
349 |
** Internal mapping of ajax sub-route names to various metadata. |
|
c313cac…
|
stephan
|
350 |
*/ |
|
c313cac…
|
stephan
|
351 |
struct AjaxRoute { |
|
c313cac…
|
stephan
|
352 |
const char *zName; /* Name part of the route after "ajax/" */ |
|
c313cac…
|
stephan
|
353 |
void (*xCallback)(); /* Impl function for the route. */ |
|
c313cac…
|
stephan
|
354 |
int bWriteMode; /* True if requires write mode */ |
|
c313cac…
|
stephan
|
355 |
int bPost; /* True if requires POST (i.e. CSRF |
|
c313cac…
|
stephan
|
356 |
** verification) */ |
|
c313cac…
|
stephan
|
357 |
}; |
|
c313cac…
|
stephan
|
358 |
typedef struct AjaxRoute AjaxRoute; |
|
19f2753…
|
stephan
|
359 |
#endif /*INTERFACE*/ |
|
c313cac…
|
stephan
|
360 |
|
|
c313cac…
|
stephan
|
361 |
/* |
|
c313cac…
|
stephan
|
362 |
** Comparison function for bsearch() for searching an AjaxRoute |
|
c313cac…
|
stephan
|
363 |
** list for a matching name. |
|
c313cac…
|
stephan
|
364 |
*/ |
|
19f2753…
|
stephan
|
365 |
int cmp_ajax_route_name(const void *a, const void *b){ |
|
c313cac…
|
stephan
|
366 |
const AjaxRoute * rA = (const AjaxRoute*)a; |
|
c313cac…
|
stephan
|
367 |
const AjaxRoute * rB = (const AjaxRoute*)b; |
|
c313cac…
|
stephan
|
368 |
return fossil_strcmp(rA->zName, rB->zName); |
|
c313cac…
|
stephan
|
369 |
} |
|
c313cac…
|
stephan
|
370 |
|
|
c313cac…
|
stephan
|
371 |
/* |
|
6c1ac83…
|
stephan
|
372 |
** WEBPAGE: ajax hidden |
|
c313cac…
|
stephan
|
373 |
** |
|
c313cac…
|
stephan
|
374 |
** The main dispatcher for shared ajax-served routes. Requires the |
|
c313cac…
|
stephan
|
375 |
** 'name' parameter be the main route's name (as defined in a list in |
|
c313cac…
|
stephan
|
376 |
** this function), noting that fossil automatically assigns all path |
|
c313cac…
|
stephan
|
377 |
** parts after "ajax" to "name", e.g. /ajax/foo/bar assigns |
|
c313cac…
|
stephan
|
378 |
** name=foo/bar. |
|
c313cac…
|
stephan
|
379 |
** |
|
c313cac…
|
stephan
|
380 |
** This "page" is only intended to be used by higher-level pages which |
|
c313cac…
|
stephan
|
381 |
** have certain Ajax-driven features in common. It is not intended to |
|
c313cac…
|
stephan
|
382 |
** be used by clients and NONE of its HTTP interfaces are considered |
|
c313cac…
|
stephan
|
383 |
** documented/stable/supported - they may change on any given build of |
|
c313cac…
|
stephan
|
384 |
** fossil. |
|
c313cac…
|
stephan
|
385 |
** |
|
c313cac…
|
stephan
|
386 |
** The exact response type depends on the route which gets called. In |
|
c313cac…
|
stephan
|
387 |
** the case of an initialization error it emits a JSON-format response |
|
c313cac…
|
stephan
|
388 |
** as documented for ajax_route_error(). Individual routes may emit |
|
c313cac…
|
stephan
|
389 |
** errors in different formats, e.g. HTML. |
|
c313cac…
|
stephan
|
390 |
*/ |
|
c313cac…
|
stephan
|
391 |
void ajax_route_dispatcher(void){ |
|
c313cac…
|
stephan
|
392 |
const char * zName = P("name"); |
|
c313cac…
|
stephan
|
393 |
AjaxRoute routeName = {0,0,0,0}; |
|
c313cac…
|
stephan
|
394 |
const AjaxRoute * pRoute = 0; |
|
c313cac…
|
stephan
|
395 |
const AjaxRoute routes[] = { |
|
c313cac…
|
stephan
|
396 |
/* Keep these sorted by zName (for bsearch()) */ |
|
0c6e669…
|
stephan
|
397 |
{"preview-text", ajax_route_preview_text, 0, 1 |
|
0c6e669…
|
stephan
|
398 |
/* Note that this does not require write permissions in the repo. |
|
0c6e669…
|
stephan
|
399 |
** It should arguably require write permissions but doing means |
|
e2bdc10…
|
danield
|
400 |
** that /chat does not work without check-in permissions: |
|
0c6e669…
|
stephan
|
401 |
** |
|
0c6e669…
|
stephan
|
402 |
** https://fossil-scm.org/forum/forumpost/ed4a762b3a557898 |
|
0c6e669…
|
stephan
|
403 |
** |
|
0c6e669…
|
stephan
|
404 |
** This particular route is used by /fileedit and /chat, whereas |
|
0c6e669…
|
stephan
|
405 |
** /wikiedit uses a simpler wiki-specific route. |
|
0c6e669…
|
stephan
|
406 |
*/ } |
|
c313cac…
|
stephan
|
407 |
}; |
|
c313cac…
|
stephan
|
408 |
|
|
c313cac…
|
stephan
|
409 |
if(zName==0 || zName[0]==0){ |
|
c313cac…
|
stephan
|
410 |
ajax_route_error(400,"Missing required [route] 'name' parameter."); |
|
c313cac…
|
stephan
|
411 |
return; |
|
c313cac…
|
stephan
|
412 |
} |
|
c313cac…
|
stephan
|
413 |
routeName.zName = zName; |
|
c313cac…
|
stephan
|
414 |
pRoute = (const AjaxRoute *)bsearch(&routeName, routes, |
|
c313cac…
|
stephan
|
415 |
count(routes), sizeof routes[0], |
|
c313cac…
|
stephan
|
416 |
cmp_ajax_route_name); |
|
c313cac…
|
stephan
|
417 |
if(pRoute==0){ |
|
c313cac…
|
stephan
|
418 |
ajax_route_error(404,"Ajax route not found."); |
|
c313cac…
|
stephan
|
419 |
return; |
|
c313cac…
|
stephan
|
420 |
}else if(0==ajax_route_bootstrap(pRoute->bWriteMode, pRoute->bPost)){ |
|
c313cac…
|
stephan
|
421 |
return; |
|
c313cac…
|
stephan
|
422 |
} |
|
0c6e669…
|
stephan
|
423 |
pRoute->xCallback(); |
|
c313cac…
|
stephan
|
424 |
} |