source: trunk/gsdl/src/recpt/cgiwrapper.cpp@ 3006

Last change on this file since 3006 was 3006, checked in by jrm21, 22 years ago

Modified code for redirection of stderr. Now works with gcc3 and debug
versions of VC++6.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 21.5 KB
Line 
1/**********************************************************************
2 *
3 * cgiwrapper.cpp -- output pages using the cgi protocol
4 * Copyright (C) 1999 The New Zealand Digital Library Project
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *********************************************************************/
25
26#include "gsdlconf.h"
27#include "cgiwrapper.h"
28#include "recptconfig.h"
29#include "fileutil.h"
30#include <stdlib.h>
31#include <assert.h>
32
33#if defined(GSDL_USE_OBJECTSPACE)
34# include <ospace/std/iostream>
35# include <ospace/std/fstream>
36#elif defined(GSDL_USE_IOS_H)
37# include <iostream.h>
38# include <fstream.h>
39#else
40# include <iostream>
41# include <fstream>
42#endif
43
44#ifdef USE_FASTCGI
45#include "fcgiapp.h"
46#endif
47
48
49#ifdef USE_FASTCGI
50// used to output the text from receptionist
51class fcgistreambuf : public streambuf {
52public:
53 fcgistreambuf ();
54 int sync ();
55 int overflow (int ch);
56 int underflow () {return EOF;}
57
58 void fcgisbreset() {fcgx_stream = NULL; other_ostream = NULL;};
59 void set_fcgx_stream(FCGX_Stream *newone) {fcgx_stream=newone;};
60 void set_other_ostream(ostream *newone) {other_ostream=newone;};
61
62private:
63 FCGX_Stream *fcgx_stream;
64 ostream *other_ostream;
65};
66
67fcgistreambuf::fcgistreambuf() {
68 fcgisbreset();
69 if (base() == ebuf()) allocate();
70 setp (base(), ebuf());
71};
72
73int fcgistreambuf::sync () {
74 if ((fcgx_stream != NULL) &&
75 (FCGX_PutStr (pbase(), out_waiting(), fcgx_stream) < 0)) {
76 fcgx_stream = NULL;
77 }
78
79 if (other_ostream != NULL) {
80 char *thepbase=pbase();
81 for (int i=0;i<out_waiting();i++) (*other_ostream).put(thepbase[i]);
82 }
83
84 setp (pbase(), epptr());
85
86 return 0;
87}
88
89int fcgistreambuf::overflow (int ch) {
90 if (sync () == EOF) return EOF;
91 if (ch != EOF) sputc (ch);
92 return 0;
93}
94
95#endif
96
97static void format_error_string (text_t &errorpage, const text_t &errortext, bool debug) {
98
99 errorpage.clear();
100
101 if (debug) {
102 errorpage += "\n";
103 errorpage += "ERROR: " + errortext;
104 errorpage += "\n";
105
106 } else {
107
108 errorpage += "Content-type: text/html\n\n";
109
110 errorpage += "<html>\n";
111 errorpage += "<head>\n";
112 errorpage += "<title>Error</title>\n";
113 errorpage += "</head>\n";
114 errorpage += "<body>\n";
115 errorpage += "<h2>Oops!</h2>\n";
116 errorpage += errortext;
117 errorpage += "</body>\n";
118 errorpage += "</html>\n";
119 }
120}
121
122static void page_errorcollect (const text_t &gsdlhome, text_t &errorpage, bool debug) {
123
124 text_t collectdir = filename_cat (gsdlhome, "collect");
125
126 text_t errortext = "No valid collections were found: Check that your collect directory\n";
127 errortext += "(" + collectdir + ") is readable and contains at least one valid collection.\n";
128 errortext += "Note that modelcol is NOT a valid collection.\n";
129 errortext += "If the path to your collect directory is wrong edit the 'gsdlhome' field\n";
130 errortext += "in your gsdlsite.cfg configuration file.\n";
131
132 format_error_string (errorpage, errortext, debug);
133}
134
135static void page_errorsitecfg (text_t &errorpage, bool debug, int mode) {
136
137 text_t errortext;
138
139 if (mode == 0) {
140 errortext += "The gsdlsite.cfg configuration file could not be found. This\n";
141 errortext += "file should contain configuration information relating to this\n";
142 errortext += "site's setup.\n";
143
144 } else if (mode == 1) {
145 errortext += "The gsdlsite.cfg configuration file does not contain a valid\n";
146 errortext += "gsdlhome entry.\n";
147 }
148
149 if (debug) {
150 errortext += "gsdlsite.cfg should reside in the directory from which the\n";
151 errortext += "library executable was run.\n";
152 } else {
153 errortext += "gsdlsite.cfg should reside in the same directory as the library\n";
154 errortext += "executable file.\n";
155 }
156
157 format_error_string (errorpage, errortext, debug);
158}
159
160
161static void page_errormaincfg (const text_t &gsdlhome, const text_t &collection,
162 bool debug, text_t &errorpage) {
163
164 text_t errortext;
165
166 if (collection.empty()) {
167 text_t main_cfg_file = filename_cat (gsdlhome, "etc", "main.cfg");
168 errortext += "The main.cfg configuration file could not be found. This file\n";
169 errortext += "should contain configuration information relating to the\n";
170 errortext += "setup of the interface. As this receptionist is not being run\n";
171 errortext += "in collection specific mode the file should reside at\n";
172 errortext += main_cfg_file + ".\n";
173 } else {
174 text_t collect_cfg_file = filename_cat (gsdlhome, "collect", collection, "etc", "collect.cfg");
175 text_t main_collect_cfg_file = filename_cat (gsdlhome, "etc", "collect.cfg");
176 text_t main_cfg_file = filename_cat (gsdlhome, "etc", "main.cfg");
177 errortext += "Either the collect.cfg or main.cfg configuration file could\n";
178 errortext += "not be found. This file should contain configuration information\n";
179 errortext += "relating to the setup of the interface. As this receptionist is\n";
180 errortext += "being run in collection specific mode the file should reside\n";
181 errortext += "at either " + collect_cfg_file + ",\n";
182 errortext += main_collect_cfg_file + " or " + main_cfg_file + ".\n";
183 }
184
185 format_error_string (errorpage, errortext, debug);
186}
187
188
189static void page_errorinit (const text_t &gsdlhome, bool debug, text_t &errorpage) {
190
191 text_t errortext = "An error occurred during the initialisation of the Greenstone Digital\n";
192 errortext += "Library software. It is likely that the software has not been setup\n";
193 errortext += "correctly.\n";
194
195 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
196 char *efile = error_file.getcstr();
197 ifstream errin (efile);
198 delete efile;
199 if (errin) {
200 errortext += "The error log, " + error_file + ", contains the\n";
201 errortext += "following information:\n\n";
202 if (!debug) errortext += "<pre>\n";
203
204 char c;
205 errin.get(c);
206 while (!errin.eof ()) {
207 errortext.push_back(c);
208 errin.get(c);
209 }
210
211 if (!debug) errortext += "</pre>\n";
212
213 errin.close();
214
215 } else {
216 errortext += "Please consult " + error_file + " for more information.\n";
217 }
218
219 format_error_string (errorpage, errortext, debug);
220}
221
222static void page_errorparseargs (const text_t &gsdlhome, bool debug, text_t &errorpage) {
223
224 text_t errortext = "An error occurred during the parsing of the cgi arguments.\n";
225
226 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
227 char *efile = error_file.getcstr();
228 ifstream errin (efile);
229 delete efile;
230 if (errin) {
231 errortext += "The error log, " + error_file + ", contains the\n";
232 errortext += "following information:\n\n";
233 if (!debug) errortext += "<pre>\n";
234
235 char c;
236 errin.get(c);
237 while (!errin.eof ()) {
238 errortext.push_back(c);
239 errin.get(c);
240 }
241 if (!debug) errortext += "</pre>\n";
242 errin.close();
243
244 } else {
245 errortext += "Please consult " + error_file + " for more information.\n";
246 }
247
248 format_error_string (errorpage, errortext, debug);
249}
250
251static void page_errorcgipage (const text_t &gsdlhome, bool debug, text_t &errorpage) {
252
253 text_t errortext = "An error occurred during the construction of the cgi page.\n";
254
255 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
256 char *efile = error_file.getcstr();
257 ifstream errin (efile);
258 delete efile;
259 if (errin) {
260 errortext += "The error log, " + error_file + ", contains the\n";
261 errortext += "following information:\n\n";
262 if (!debug) errortext += "<pre>\n";
263
264 char c;
265 errin.get(c);
266 while (!errin.eof ()) {
267 errortext.push_back(c);
268 errin.get(c);
269 }
270 if (!debug) errortext += "</pre>\n";
271 errin.close();
272
273 } else {
274 errortext += "Please consult " + error_file + " for more information.\n";
275 }
276
277 format_error_string (errorpage, errortext, debug);
278}
279
280static void print_debug_info (receptionist &recpt) {
281
282 outconvertclass text_t2ascii;
283 const recptconf &configinfo = recpt.get_configinfo ();
284 text_t etc_dir = filename_cat (configinfo.gsdlhome, "etc");
285
286 cout << "\n";
287 cout << text_t2ascii
288 << "------------------------------------------------------------\n"
289 << "Configuration and initialization completed successfully.\n"
290 << " Note that more debug information may be available in the\n"
291 << " initialization and error log error.txt in " << etc_dir << ".\n"
292 << "------------------------------------------------------------\n\n";
293
294 bool colspec = false;
295 if (configinfo.collection.empty()) {
296 cout << "Receptionist is running in \"general\" (i.e. not \"collection\n"
297 << "specific\") mode.\n";
298 } else {
299 cout << text_t2ascii
300 << "Receptionist is running in \"collection specific\" mode.\n"
301 << " collection=" << configinfo.collection << "\n"
302 << " collection directory=" << configinfo.collectdir << "\n";
303 colspec = true;
304 }
305
306 cout << text_t2ascii << "gsdlhome=" << configinfo.gsdlhome << "\n";
307 if (!configinfo.gdbmhome.empty())
308 cout << text_t2ascii << "gdbmhome=" << configinfo.gdbmhome << "\n";
309 cout << text_t2ascii << "httpprefix=" << configinfo.httpprefix << "\n";
310 cout << text_t2ascii << "httpimg=" << configinfo.httpimg << "\n";
311 cout << text_t2ascii << "gwcgi=" << configinfo.gwcgi << "\n"
312 << " Note that unless gwcgi has been set from a configuration\n"
313 << " file it is dependent on environment variables set by your\n"
314 << " webserver. Therefore it may not have the same value when run\n"
315 << " from the command line as it would be when run from your\n"
316 << " web server.\n";
317 if (configinfo.usecookies)
318 cout << "cookies are enabled\n";
319 else
320 cout << "cookies are disabled\n";
321 if (configinfo.logcgiargs)
322 cout << "logging is enabled\n";
323 else
324 cout << "logging is disabled\n";
325 cout << "------------------------------------------------------------\n\n";
326
327 text_tset::const_iterator this_mfile = configinfo.macrofiles.begin();
328 text_tset::const_iterator end_mfile = configinfo.macrofiles.end();
329 cout << "Macro Files:\n"
330 << "------------\n";
331 text_t mfile;
332 bool found;
333 while (this_mfile != end_mfile) {
334 cout << text_t2ascii << *this_mfile;
335 int spaces = (22 - (*this_mfile).size());
336 if (spaces < 2) spaces = 2;
337 text_t outspaces;
338 for (int i = 0; i < spaces; i++) outspaces.push_back (' ');
339 cout << text_t2ascii << outspaces;
340
341 found = false;
342 if (colspec) {
343 // collection specific - try collectdir/macros first
344 mfile = filename_cat (configinfo.collectdir, "macros", *this_mfile);
345 if (file_exists (mfile)) {
346 cout << text_t2ascii << "found (" << mfile << ")\n";
347 found = true;
348 }
349 }
350
351 if (!found) {
352 // try main macro directory
353 mfile = filename_cat (configinfo.gsdlhome, "macros", *this_mfile);
354 if (file_exists (mfile)) {
355 cout << text_t2ascii << "found (" << mfile << ")\n";
356 found = true;
357 }
358 }
359
360 if (!found)
361 cout << text_t2ascii << "NOT FOUND\n";
362
363 this_mfile ++;
364 }
365
366 cout << "------------------------------------------------------------\n\n"
367 << "Collections:\n"
368 << "------------\n"
369 << " Note that collections will only appear as \"running\" if\n"
370 << " their build.cfg files exist, are readable, contain a valid\n"
371 << " builddate field (i.e. > 0), and are in the collection's\n"
372 << " index directory (i.e. NOT the building directory)\n\n";
373
374 recptprotolistclass *protos = recpt.get_recptprotolist_ptr();
375 recptprotolistclass::iterator rprotolist_here = protos->begin();
376 recptprotolistclass::iterator rprotolist_end = protos->end();
377
378 bool is_z3950 = false;
379 bool found_valid_col = false;
380
381
382 while (rprotolist_here != rprotolist_end) {
383 comerror_t err;
384 if ((*rprotolist_here).p == NULL) continue;
385 else if (is_z3950==false &&
386 (*rprotolist_here).p->get_protocol_name(err) == "z3950proto") {
387 cout << "\nZ39.50 Servers: (always public)\n"
388 << "---------------\n";
389 is_z3950=true;
390 }
391
392 text_tarray collist;
393 (*rprotolist_here).p->get_collection_list (collist, err, cerr);
394 if (err == noError) {
395 text_tarray::iterator collist_here = collist.begin();
396 text_tarray::iterator collist_end = collist.end();
397
398 while (collist_here != collist_end) {
399
400 cout << text_t2ascii << *collist_here;
401
402 int spaces = (22 - (*collist_here).size());
403 if (spaces < 2) spaces = 2;
404 text_t outspaces;
405 for (int i = 0; i < spaces; i++) outspaces.push_back (' ');
406 cout << text_t2ascii << outspaces;
407
408 ColInfoResponse_t *cinfo = recpt.get_collectinfo_ptr ((*rprotolist_here).p, *collist_here, cerr);
409 if (cinfo != NULL) {
410 if (cinfo->isPublic) cout << "public ";
411 else cout << "private";
412
413 if (cinfo->buildDate > 0) {
414 cout << " running ";
415 found_valid_col = true;
416 } else {
417 cout << " not running";
418 }
419 }
420
421 cout << "\n";
422
423 collist_here ++;
424 }
425 }
426 is_z3950=false;
427 rprotolist_here ++;
428 } // end of while loop
429
430 if (!found_valid_col) {
431 cout << "WARNING: No \"running\" collections were found. You need to\n";
432 cout << " build one of the above collections\n";
433 }
434
435 cout << "\n------------------------------------------------------------\n";
436 cout << "------------------------------------------------------------\n\n";
437 cout << "receptionist running in command line debug mode\n";
438 cout << "enter cgi arguments as name=value pairs (e.g. 'a=p&p=home'):\n";
439
440}
441
442// cgiwrapper does everything necessary to output a page
443// using the cgi protocol. If this is being run for a particular
444// collection then "collection" should be set, otherwise it
445// should equal "".
446void cgiwrapper (receptionist &recpt, text_t collection) {
447
448 int numrequests = 0;
449 bool debug = false;
450 const recptconf &configinfo = recpt.get_configinfo ();
451
452 // find out whether this is being run as a cgi-script
453 // or a fastcgi script
454#ifdef USE_FASTCGI
455 fcgistreambuf outbuf;
456 int isfastcgi = !FCGX_IsCGI();
457 FCGX_Stream *fcgiin, *fcgiout, *fcgierr;
458 FCGX_ParamArray fcgienvp;
459#else
460 int isfastcgi = 0;
461#endif
462
463 // get the query string if it is not being run as a fastcgi
464 // script
465 text_t argstr = "";
466 cgiargsclass args;
467 char *aURIStr;
468 if (!isfastcgi) {
469 char *request_method_str = getenv("REQUEST_METHOD");
470 char *content_length_str = getenv("CONTENT_LENGTH");
471 if (request_method_str != NULL && strcmp(request_method_str, "POST") == 0 &&
472 content_length_str != NULL) {
473 // POST form data
474 int content_length = text_t(content_length_str).getint();
475 if (content_length > 0) {
476 char c;
477 do {
478 cin.get(c);
479 if (cin.eof()) break;
480 argstr.push_back (c);
481 content_length--;
482 } while (content_length > 0);
483 }
484
485 } else {
486 aURIStr = getenv("QUERY_STRING");
487 if ((request_method_str != NULL && strcmp(request_method_str, "GET") == 0)
488 || aURIStr != NULL) {
489 // GET form data
490 if (aURIStr != NULL) argstr = aURIStr;
491 } else {
492 // debugging from command line
493 debug = true;
494 }
495 }
496 }
497
498 if (debug) {
499 cout << "Configuring Greenstone...\n";
500 cout << flush;
501 }
502
503 // init stuff - we can't output error pages directly with
504 // fastcgi so the pages are stored until we can output them
505 text_t errorpage;
506 outconvertclass text_t2ascii;
507
508 // set defaults
509 int maxrequests = 10000;
510 recpt.configure ("collection", collection);
511 recpt.configure ("httpimg", "/gsdl/images");
512 char *script_name = getenv("SCRIPT_NAME");
513 if (script_name != NULL) recpt.configure("gwcgi", script_name);
514 else recpt.configure("gwcgi", "/gsdl");
515
516 // read in the configuration files.
517 text_t gsdlhome;
518 if (!site_cfg_read (recpt, gsdlhome, maxrequests)) {
519 // couldn't find the site configuration file
520 page_errorsitecfg (errorpage, debug, 0);
521 } else if (gsdlhome.empty()) {
522 // no gsdlhome in gsdlsite.cfg
523 page_errorsitecfg (errorpage, debug, 1);
524 } else if (!directory_exists(gsdlhome)) {
525 // gsdlhome not a valid directory
526 page_errorsitecfg (errorpage, debug, 1);
527 } else if (!main_cfg_read (recpt, gsdlhome, collection)) {
528 // couldn't find the main configuration file
529 page_errormaincfg (gsdlhome, collection, debug, errorpage);
530 } else if (configinfo.collectinfo.empty() && false) { // commented out for corba
531 // don't have any collections
532 page_errorcollect (gsdlhome, errorpage, debug);
533 }
534
535 if (errorpage.empty()) {
536
537 // initialise the library software
538 if (debug) {
539 cout << "Initializing...\n";
540 cout << flush;
541 }
542
543 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
544 char *eout = error_file.getcstr();
545 ofstream errout (eout, ios::app);
546 delete eout;
547 if (!recpt.init(errout)) {
548 // an error occurred during the initialisation
549 errout.close();
550 page_errorinit(gsdlhome, debug, errorpage);
551 }
552 errout.close();
553 }
554
555 if (debug && errorpage.empty()) {
556 // get query string from command line
557 print_debug_info (recpt);
558 char cinURIStr[1024];
559 cin.get(cinURIStr, 1024);
560 argstr = cinURIStr;
561 }
562
563 // cgi scripts only deal with one request
564 if (!isfastcgi) maxrequests = 1;
565
566 // Page-request loop. If this is not being run as a fastcgi
567 // process then only one request will be processed and then
568 // the process will exit.
569 while (numrequests < maxrequests) {
570#ifdef USE_FASTCGI
571 if (isfastcgi) {
572 if (FCGX_Accept(&fcgiin, &fcgiout, &fcgierr, &fcgienvp) < 0) break;
573
574 char *request_method_str = FCGX_GetParam ("REQUEST_METHOD", fcgienvp);
575 char *content_length_str = FCGX_GetParam ("CONTENT_LENGTH", fcgienvp);
576
577 if (request_method_str != NULL && strcmp(request_method_str, "POST") == 0 &&
578 content_length_str != NULL) {
579 // POST form data
580 int content_length = text_t(content_length_str).getint();
581 if (content_length > 0) {
582 argstr.clear();
583 int c;
584 do {
585 c = FCGX_GetChar (fcgiin);
586 if (c < 0) break;
587 argstr.push_back (c);
588 content_length--;
589 } while (content_length > 0);
590 }
591
592 } else {
593 // GET form data
594 aURIStr = FCGX_GetParam("QUERY_STRING", fcgienvp);
595 if (aURIStr != NULL) argstr = aURIStr;
596 else argstr = "";
597 }
598 }
599#endif
600
601 // get output streams ready
602#ifdef USE_FASTCGI
603 outbuf.fcgisbreset ();
604 if (isfastcgi) outbuf.set_fcgx_stream (fcgiout);
605 else outbuf.set_other_ostream (&cout);
606 ostream pageout (&outbuf);
607#else
608#define pageout cout
609#endif
610
611 // if using fastcgi we'll load environment into a map,
612 // otherwise simply pass empty map (can't get environment
613 // variables using getenv() while using FCGX versions
614 // of fastcgi - at least I can't ;-) - Stefan)
615 text_tmap fastcgienv;
616#ifdef USE_FASTCGI
617 if (isfastcgi) {
618 for(; *fcgienvp != NULL; fcgienvp++) {
619 text_t fvalue = *fcgienvp;
620 text_t::const_iterator begin = fvalue.begin();
621 text_t::const_iterator end = fvalue.end();
622 text_t::const_iterator equals_sign = findchar (begin, end, '=');
623 if (equals_sign != end)
624 fastcgienv[substr(begin, equals_sign)] = substr(equals_sign+1, end);
625 }
626 }
627#endif
628
629 // temporarily need to configure gwcgi here when using fastcgi as I can't
630 // get it to pass the SCRIPT_NAME environment variable to the initial
631 // environment (if anyone can work out how to do this using the apache
632 // server, let me know). Note that this overrides the gwcgi field in
633 // site.cfg (which it shouldn't do) but I can't at present set gwcgi
634 // from site.cfg as I have old receptionists laying around that wouldn't
635 // appreciate it. The following 5 lines of code should be deleted once
636 // I either a: get the server to pass SCRIPT_NAME at initialization
637 // time or b: convert all the collections using old receptionists over
638 // to this version and uncomment gwcgi in the site.cfg file -- Stefan.
639#ifdef USE_FASTCGI
640 if (isfastcgi) {
641 recpt.configure("gwcgi", fastcgienv["SCRIPT_NAME"]);
642 }
643#endif
644
645
646 // if there has been no error so far, perform the production of the
647 // output page
648 if (errorpage.empty()) {
649 text_t error_file = filename_cat (gsdlhome, "etc", "error.txt");
650 char *eout = error_file.getcstr();
651 ofstream errout (eout, ios::app);
652 delete eout;
653
654 // can't do this anymore according to c++ standard...
655 //cerr = errout;
656 // ... but can do this instead
657 cerr.rdbuf(errout.rdbuf());
658
659 // parse the cgi arguments and produce the resulting page if there
660 // has been no errors so far
661 if (!recpt.parse_cgi_args (argstr, args, errout, fastcgienv)) {
662 errout.close ();
663 page_errorparseargs(gsdlhome, debug, errorpage);
664 } else {
665 // produce the output page
666
667 if (!recpt.produce_cgi_page (args, pageout, errout, fastcgienv)) {
668 errout.close ();
669 page_errorcgipage(gsdlhome, debug, errorpage);
670 }
671 recpt.log_cgi_args (args, errout, fastcgienv);
672 errout.close ();
673 }
674 }
675 // there was an error, output the error page
676 if (!errorpage.empty()) {
677 pageout << text_t2ascii << errorpage;
678 errorpage.clear();
679 numrequests = maxrequests; // make this the last page
680 }
681 pageout << flush;
682
683 // finish with the output streams
684#ifdef USE_FASTCGI
685 if (isfastcgi) FCGX_Finish();
686#endif
687
688 numrequests++;
689 }
690
691 return;
692}
Note: See TracBrowser for help on using the repository browser.