Fossil SCM

fossil-scm / src / ajax.c
Source Blame History 424 lines
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 }

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button