root/src/noit_rest.c

Revision f3bf693ba571a6c22958a8842af4f05c6e4c93d2, 19.8 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 3 years ago)

fix for issue GH-#58, segfault in handoff journals

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34 #include "noit_listener.h"
35 #include "noit_http.h"
36 #include "noit_rest.h"
37 #include "noit_conf.h"
38
39 #include <pcre.h>
40 #include <unistd.h>
41 #include <fcntl.h>
42 #include <sys/mman.h>
43 #include <errno.h>
44
45 struct rest_xml_payload {
46   char *buffer;
47   xmlDocPtr indoc;
48   int len;
49   int allocd;
50   int complete;
51 };
52
53 struct rest_url_dispatcher {
54   char *method;
55   pcre *expression;
56   pcre_extra *extra;
57   rest_request_handler handler;
58   rest_authorize_func_t auth;
59   /* Chain to the next one */
60   struct rest_url_dispatcher *next;
61 };
62
63 struct rule_container {
64   char *base;
65   struct rest_url_dispatcher *rules;
66   struct rest_url_dispatcher *rules_endptr;
67 };
68 noit_hash_table dispatch_points = NOIT_HASH_EMPTY;
69
70 struct noit_rest_acl_rule {
71   noit_boolean allow;
72   pcre *url;
73   pcre *cn;
74   struct noit_rest_acl_rule *next;
75 };
76 struct noit_rest_acl {
77   noit_boolean allow;
78   pcre *url;
79   pcre *cn;
80   struct noit_rest_acl_rule *rules;
81   struct noit_rest_acl *next;
82 };
83
84 static struct noit_rest_acl *global_rest_acls = NULL;
85
86 static int
87 noit_http_rest_permission_denied(noit_http_rest_closure_t *restc,
88                                  int npats, char **pats) {
89   noit_http_session_ctx *ctx = restc->http_ctx;
90   noit_http_response_standard(ctx, 403, "DENIED", "text/xml");
91   noit_http_response_end(ctx);
92   return 0;
93 }
94 static rest_request_handler
95 noit_http_get_handler(noit_http_rest_closure_t *restc) {
96   struct rule_container *cont = NULL;
97   struct rest_url_dispatcher *rule;
98   noit_http_request *req = noit_http_session_request(restc->http_ctx);
99   const char *uri_str;
100   const char *eoq, *eob;
101   uri_str = noit_http_request_uri_str(req);
102   eoq = strchr(uri_str, '?');
103   if(!eoq)
104     eoq = uri_str + strlen(uri_str);
105   eob = eoq - 1;
106
107   /* find the right base */
108   while(1) {
109     void *vcont;
110     while(eob >= uri_str && *eob != '/') eob--;
111     if(eob < uri_str) break; /* off the front */
112     if(noit_hash_retrieve(&dispatch_points, uri_str,
113                           eob - uri_str + 1, &vcont)) {
114       cont = vcont;
115       eob++; /* move past the determined base */
116       break;
117     }
118     eob--;
119   }
120   if(!cont) return NULL;
121   for(rule = cont->rules; rule; rule = rule->next) {
122     int ovector[30];
123     int cnt;
124     if(strcmp(rule->method, noit_http_request_method_str(req))) continue;
125     if((cnt = pcre_exec(rule->expression, rule->extra, eob, eoq - eob, 0, 0,
126                         ovector, sizeof(ovector)/sizeof(*ovector))) > 0) {
127       /* We match, set 'er up */
128       restc->fastpath = rule->handler;
129       restc->nparams = cnt - 1;
130       if(restc->nparams) {
131         restc->params = calloc(restc->nparams, sizeof(*restc->params));
132         for(cnt = 0; cnt < restc->nparams; cnt++) {
133           int start = ovector[(cnt+1)*2];
134           int end = ovector[(cnt+1)*2+1];
135           restc->params[cnt] = malloc(end - start + 1);
136           memcpy(restc->params[cnt], eob + start, end - start);
137           restc->params[cnt][end - start] = '\0';
138         }
139       }
140       if(rule->auth && !rule->auth(restc, restc->nparams, restc->params))
141         return noit_http_rest_permission_denied;
142       return restc->fastpath;
143     }
144   }
145   return NULL;
146 }
147 noit_boolean
148 noit_http_rest_access(noit_http_rest_closure_t *restc,
149                       int npats, char **pats) {
150   struct noit_rest_acl *acl;
151   struct noit_rest_acl_rule *rule;
152   noit_http_request *req = noit_http_session_request(restc->http_ctx);
153   const char *uri_str;
154   int ovector[30];
155
156   uri_str = noit_http_request_uri_str(req);
157   for(acl = global_rest_acls; acl; acl = acl->next) {
158     if(acl->cn && pcre_exec(acl->cn, NULL, "", 0, 0, 0,
159                             ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
160       continue;
161     if(acl->url && pcre_exec(acl->url, NULL, uri_str, strlen(uri_str), 0, 0,
162                              ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
163       continue;
164     for(rule = acl->rules; rule; rule = rule->next) {
165       if(rule->cn && pcre_exec(rule->cn, NULL, "", 0, 0, 0,
166                                ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
167         continue;
168       if(rule->url && pcre_exec(rule->url, NULL, uri_str, strlen(uri_str), 0, 0,
169                                 ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
170         continue;
171       return rule->allow;
172     }
173     return acl->allow;
174   }
175   return noit_false;
176 }
177 noit_boolean
178 noit_http_rest_client_cert_auth(noit_http_rest_closure_t *restc,
179                                 int npats, char **pats) {
180   struct noit_rest_acl *acl;
181   struct noit_rest_acl_rule *rule;
182   noit_http_request *req = noit_http_session_request(restc->http_ctx);
183   const char *uri_str;
184   int ovector[30];
185
186   uri_str = noit_http_request_uri_str(req);
187   if(!restc->remote_cn || !strlen(restc->remote_cn)) return noit_false;
188   for(acl = global_rest_acls; acl; acl = acl->next) {
189     if(acl->cn && pcre_exec(acl->cn, NULL, restc->remote_cn,
190                             strlen(restc->remote_cn), 0, 0,
191                             ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
192       continue;
193     if(acl->url && pcre_exec(acl->url, NULL, uri_str, strlen(uri_str), 0, 0,
194                              ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
195       continue;
196     for(rule = acl->rules; rule; rule = rule->next) {
197       if(rule->cn && pcre_exec(rule->cn, NULL, restc->remote_cn,
198                                strlen(restc->remote_cn), 0, 0,
199                                ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
200         continue;
201       if(rule->url && pcre_exec(rule->url, NULL, uri_str, strlen(uri_str), 0, 0,
202                                 ovector, sizeof(ovector)/sizeof(*ovector)) <= 0)
203         continue;
204       return rule->allow;
205     }
206     return acl->allow;
207   }
208   return noit_false;
209 }
210 int
211 noit_http_rest_register(const char *method, const char *base,
212                         const char *expr, rest_request_handler f) {
213   return noit_http_rest_register_auth(method, base, expr, f, NULL);
214 }
215 int
216 noit_http_rest_register_auth(const char *method, const char *base,
217                              const char *expr, rest_request_handler f,
218                              rest_authorize_func_t auth) {
219   void *vcont;
220   struct rule_container *cont;
221   struct rest_url_dispatcher *rule;
222   const char *error;
223   int erroffset;
224   pcre *pcre_expr;
225   int blen = strlen(base);
226   /* base must end in a /, 'cause I said so */
227   if(blen == 0 || base[blen-1] != '/') return -1;
228   pcre_expr = pcre_compile(expr, 0, &error, &erroffset, NULL);
229   if(!pcre_expr) {
230     noitL(noit_error, "Error in rest expr(%s) '%s'@%d: %s\n",
231           base, expr, erroffset, error);
232     return -1;
233   }
234   rule = calloc(1, sizeof(*rule));
235   rule->method = strdup(method);
236   rule->expression = pcre_expr;
237   rule->extra = pcre_study(rule->expression, 0, &error);
238   rule->handler = f;
239   rule->auth = auth;
240
241   /* Make sure we have a container */
242   if(!noit_hash_retrieve(&dispatch_points, base, strlen(base), &vcont)) {
243     cont = calloc(1, sizeof(*cont));
244     cont->base = strdup(base);
245     noit_hash_store(&dispatch_points, cont->base, strlen(cont->base), cont);
246   }
247   else cont = vcont;
248
249   /* Append the rule */
250   if(cont->rules_endptr) {
251     cont->rules_endptr->next = rule;
252     cont->rules_endptr = cont->rules_endptr->next;
253   }
254   else
255     cont->rules = cont->rules_endptr = rule;
256   return 0;
257 }
258
259 static noit_http_rest_closure_t *
260 noit_http_rest_closure_alloc() {
261   noit_http_rest_closure_t *restc;
262   restc = calloc(1, sizeof(*restc));
263   return restc;
264 }
265 static void
266 noit_http_rest_clean_request(noit_http_rest_closure_t *restc) {
267   int i;
268   if(restc->nparams) {
269     for(i=0;i<restc->nparams;i++) free(restc->params[i]);
270     free(restc->params);
271   }
272   if(restc->call_closure_free) restc->call_closure_free(restc->call_closure);
273   restc->call_closure_free = NULL;
274   restc->call_closure = NULL;
275   restc->nparams = 0;
276   restc->params = NULL;
277   restc->fastpath = NULL;
278 }
279 void
280 noit_http_rest_closure_free(void *v) {
281   noit_http_rest_closure_t *restc = v;
282   free(restc->remote_cn);
283   noit_http_rest_clean_request(restc);
284   free(restc);
285 }
286
287 int
288 noit_rest_request_dispatcher(noit_http_session_ctx *ctx) {
289   noit_http_rest_closure_t *restc = noit_http_session_dispatcher_closure(ctx);
290   rest_request_handler handler = restc->fastpath;
291   if(!handler) handler = noit_http_get_handler(restc);
292   if(handler) {
293     void *old_closure = restc, *new_closure;
294     noit_http_response *res = noit_http_session_response(ctx);
295     int rv;
296     rv = handler(restc, restc->nparams, restc->params);
297     /* If the request is closed, we need to cleanup.  However
298      * if the dispatch closure has changed, the callee has done
299      * something (presumably freeing the restc in the process)
300      * and it would be unsafe for us to free it as well.
301      */
302     new_closure = noit_http_session_dispatcher_closure(ctx);
303     if(old_closure == new_closure &&
304        noit_http_response_closed(res)) noit_http_rest_clean_request(restc);
305     return rv;
306   }
307   noit_http_response_status_set(ctx, 404, "NOT FOUND");
308   noit_http_response_option_set(ctx, NOIT_HTTP_CHUNKED);
309   noit_http_rest_clean_request(restc);
310   noit_http_response_end(ctx);
311   return 0;
312 }
313
314 int
315 noit_http_rest_handler(eventer_t e, int mask, void *closure,
316                        struct timeval *now) {
317   int newmask = EVENTER_READ | EVENTER_EXCEPTION, rv, done = 0;
318   acceptor_closure_t *ac = closure;
319   noit_http_rest_closure_t *restc = ac->service_ctx;
320
321   if(mask & EVENTER_EXCEPTION || (restc && restc->wants_shutdown)) {
322 socket_error:
323     /* Exceptions cause us to simply snip the connection */
324     eventer_remove_fd(e->fd);
325     e->opset->close(e->fd, &newmask, e);
326     if(ac) acceptor_closure_free(ac);
327     return 0;
328   }
329
330   if(!ac->service_ctx) {
331     const char *primer = "";
332     ac->service_ctx = restc = noit_http_rest_closure_alloc();
333     ac->service_ctx_free = noit_http_rest_closure_free;
334     restc->ac = ac;
335     restc->remote_cn = strdup(ac->remote_cn ? ac->remote_cn : "");
336     restc->http_ctx =
337         noit_http_session_ctx_new(noit_rest_request_dispatcher,
338                                   restc, e, ac);
339    
340     switch(ac->cmd) {
341       case NOIT_CONTROL_DELETE:
342         primer = "DELE";
343         break;
344       case NOIT_CONTROL_GET:
345         primer = "GET ";
346         break;
347       case NOIT_CONTROL_HEAD:
348         primer = "HEAD";
349         break;
350       case NOIT_CONTROL_POST:
351         primer = "POST";
352         break;
353       case NOIT_CONTROL_PUT:
354         primer = "PUT ";
355         break;
356       case NOIT_CONTROL_MERGE:
357         primer = "MERG";
358         break;
359       default:
360         goto socket_error;
361     }
362     noit_http_session_prime_input(restc->http_ctx, primer, 4);
363   }
364   rv = noit_http_session_drive(e, mask, restc->http_ctx, now, &done);
365   if(done) {
366     if(ac) acceptor_closure_free(ac);
367   }
368   return rv;
369 }
370
371 int
372 noit_http_rest_raw_handler(eventer_t e, int mask, void *closure,
373                            struct timeval *now) {
374   int newmask = EVENTER_READ | EVENTER_EXCEPTION, rv, done = 0;
375   acceptor_closure_t *ac = closure;
376   noit_http_rest_closure_t *restc = ac->service_ctx;
377
378   if(mask & EVENTER_EXCEPTION || (restc && restc->wants_shutdown)) {
379     /* Exceptions cause us to simply snip the connection */
380     eventer_remove_fd(e->fd);
381     e->opset->close(e->fd, &newmask, e);
382     if(ac) acceptor_closure_free(ac);
383     return 0;
384   }
385   if(!ac->service_ctx) {
386     ac->service_ctx = restc = noit_http_rest_closure_alloc();
387     ac->service_ctx_free = noit_http_rest_closure_free;
388     restc->ac = ac;
389     restc->http_ctx =
390         noit_http_session_ctx_new(noit_rest_request_dispatcher,
391                                   restc, e, ac);
392   }
393   rv = noit_http_session_drive(e, mask, restc->http_ctx, now, &done);
394   if(done) {
395     if(ac) acceptor_closure_free(ac);
396   }
397   return rv;
398 }
399
400 static void
401 rest_xml_payload_free(void *f) {
402   struct rest_xml_payload *xmlin = f;
403   if(xmlin->buffer) free(xmlin->buffer);
404   if(xmlin->indoc) xmlFreeDoc(xmlin->indoc);
405 }
406
407 xmlDocPtr
408 rest_get_xml_upload(noit_http_rest_closure_t *restc,
409                     int *mask, int *complete) {
410   struct rest_xml_payload *rxc;
411   noit_http_request *req = noit_http_session_request(restc->http_ctx);
412
413   if(restc->call_closure == NULL) {
414     restc->call_closure = calloc(1, sizeof(*rxc));
415     restc->call_closure_free = rest_xml_payload_free;
416   }
417   rxc = restc->call_closure;
418   while(!rxc->complete) {
419     int len;
420     if(rxc->len == rxc->allocd) {
421       char *b;
422       rxc->allocd += 32768;
423       b = rxc->buffer ? realloc(rxc->buffer, rxc->allocd) :
424                         malloc(rxc->allocd);
425       if(!b) {
426         *complete = 1;
427         return NULL;
428       }
429       rxc->buffer = b;
430     }
431     len = noit_http_session_req_consume(restc->http_ctx,
432                                         rxc->buffer + rxc->len,
433                                         rxc->allocd - rxc->len,
434                                         mask);
435     if(len > 0) rxc->len += len;
436     if(len < 0 && errno == EAGAIN) return NULL;
437     else if(len < 0) {
438       *complete = 1;
439       return NULL;
440     }
441     if(rxc->len == noit_http_request_content_length(req)) {
442       rxc->indoc = xmlParseMemory(rxc->buffer, rxc->len);
443       rxc->complete = 1;
444     }
445   }
446
447   *complete = 1;
448   return rxc->indoc;
449 }
450 int
451 noit_rest_simple_file_handler(noit_http_rest_closure_t *restc,
452                               int npats, char **pats) {
453   int drlen = 0;
454   const char *document_root = NULL;
455   const char *index_file = NULL;
456   noit_http_session_ctx *ctx = restc->http_ctx;
457   char file[PATH_MAX], rfile[PATH_MAX];
458   struct stat st;
459   int fd;
460   void *contents = MAP_FAILED;
461
462   if(npats != 1 ||
463      !noit_hash_retr_str(restc->ac->config,
464                          "document_root", strlen("document_root"),
465                          &document_root)) {
466     goto not_found;
467   }
468   if(!noit_hash_retr_str(restc->ac->config,
469                          "index_file", strlen("index_file"),
470                          &index_file)) {
471     index_file = "index.html";
472   }
473   drlen = strlen(document_root);
474   snprintf(file, sizeof(file), "%s/%s", document_root, pats[0]);
475   if(file[strlen(file) - 1] == '/') {
476     snprintf(file + strlen(file), sizeof(file) - strlen(file),
477              "%s", index_file);
478   }
479   /* resolve */
480   if(realpath(file, rfile) == NULL) goto not_found;
481   /* restrict */
482   if(strncmp(rfile, document_root, drlen)) goto denied;
483   if(rfile[drlen] != '/' && rfile[drlen + 1] != '/') goto denied;
484   /* stat */
485   if(stat(rfile, &st) != 0) {
486     switch (errno) {
487       case EACCES: goto denied;
488       default: goto not_found;
489     }
490   }
491   /* open */
492   if(st.st_size > 0) {
493     fd = open(rfile, O_RDONLY);
494     if(fd < 0) goto not_found;
495     contents = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
496     close(fd);
497     if(contents == MAP_FAILED) goto not_found;
498   }
499   noit_http_response_ok(ctx, "text/html");
500   if(st.st_size > 0) {
501     noit_http_response_append(ctx, contents, st.st_size);
502     munmap(contents, st.st_size);
503   }
504   noit_http_response_end(ctx);
505   return 0;
506
507  denied:
508   noit_http_response_denied(ctx, "text/html");
509   noit_http_response_end(ctx);
510   return 0;
511  not_found:
512   noit_http_response_not_found(ctx, "text/html");
513   noit_http_response_end(ctx);
514   return 0;
515 }
516
517 void noit_http_rest_load_rules() {
518   int ai, cnt = 0;
519   noit_conf_section_t *acls;
520   char path[256];
521   struct noit_rest_acl *newhead = NULL, *oldacls, *remove_acl;
522   struct noit_rest_acl_rule *remove_rule;
523
524   snprintf(path, sizeof(path), "//rest//acl");
525   acls = noit_conf_get_sections(NULL, path, &cnt);
526   noitL(noit_stderr, "Found %d acl stanzas\n", cnt);
527   for(ai = cnt-1; ai>=0; ai--) {
528     char tbuff[32];
529     struct noit_rest_acl *newacl;
530     int ri, rcnt = 0;
531     noit_boolean default_allow = noit_false;
532     noit_conf_section_t *rules;
533
534     newacl = calloc(1, sizeof(*newacl));
535     newacl->next = newhead;
536     newhead = newacl;
537     if(noit_conf_get_stringbuf(acls[ai], "@type", tbuff, sizeof(tbuff)) &&
538        !strcmp(tbuff, "allow"))
539       newacl->allow = noit_true;
540
541 #define compile_re(node, cont, name) do { \
542   char buff[256]; \
543   if(noit_conf_get_stringbuf(node, "@" #name, buff, sizeof(buff))) { \
544     const char *error; \
545     int erroffset; \
546     cont->name = pcre_compile(buff, 0, &error, &erroffset, NULL); \
547   } \
548 } while(0)
549
550     newacl->allow = default_allow;
551     compile_re(acls[ai], newacl, cn);
552     compile_re(acls[ai], newacl, url);
553     rules = noit_conf_get_sections(acls[ai], "rule", &rcnt);
554     for(ri = rcnt - 1; ri >= 0; ri--) {
555       struct noit_rest_acl_rule *newacl_rule;
556       newacl_rule = calloc(1, sizeof(*newacl_rule));
557       newacl_rule->next = newacl->rules;
558       newacl->rules = newacl_rule;
559       if(noit_conf_get_stringbuf(rules[ri], "@type", tbuff, sizeof(tbuff)) &&
560          !strcmp(tbuff, "allow"))
561         newacl_rule->allow = noit_true;
562       compile_re(rules[ri], newacl_rule, cn);
563       compile_re(rules[ri], newacl_rule, url);
564     }
565     free(rules);
566   }
567   free(acls);
568
569   oldacls = global_rest_acls;
570   global_rest_acls = newhead;
571
572   while(oldacls) {
573     remove_acl = oldacls->next;
574     while(oldacls->rules) {
575       remove_rule = oldacls->rules->next;
576       if(oldacls->rules->cn) pcre_free(oldacls->rules->cn);
577       if(oldacls->rules->url) pcre_free(oldacls->rules->url);
578       free(oldacls->rules);
579       oldacls->rules = remove_rule;
580     }
581     if(oldacls->cn) pcre_free(oldacls->cn);
582     if(oldacls->url) pcre_free(oldacls->url);
583     free(oldacls);
584     oldacls = remove_acl;
585   }
586 }
587 void noit_http_rest_init() {
588   noit_http_init();
589   eventer_name_callback("noit_wire_rest_api/1.0", noit_http_rest_handler);
590   eventer_name_callback("http_rest_api", noit_http_rest_raw_handler);
591
592   noit_http_rest_load_rules();
593
594   noit_control_dispatch_delegate(noit_control_dispatch,
595                                  NOIT_CONTROL_DELETE,
596                                  noit_http_rest_handler);
597   noit_control_dispatch_delegate(noit_control_dispatch,
598                                  NOIT_CONTROL_MERGE,
599                                  noit_http_rest_handler);
600   noit_control_dispatch_delegate(noit_control_dispatch,
601                                  NOIT_CONTROL_GET,
602                                  noit_http_rest_handler);
603   noit_control_dispatch_delegate(noit_control_dispatch,
604                                  NOIT_CONTROL_HEAD,
605                                  noit_http_rest_handler);
606   noit_control_dispatch_delegate(noit_control_dispatch,
607                                  NOIT_CONTROL_POST,
608                                  noit_http_rest_handler);
609   noit_control_dispatch_delegate(noit_control_dispatch,
610                                  NOIT_CONTROL_PUT,
611                                  noit_http_rest_handler);
612 }
613
Note: See TracBrowser for help on using the browser.