source: filezilla/trunk/fuentes/src/putty/windows/winsftp.c @ 130

Last change on this file since 130 was 130, checked in by jrpelegrina, 4 years ago

First release to xenial

File size: 17.6 KB
Line 
1/*
2 * winsftp.c: the Windows-specific parts of PSFTP and PSCP.
3 */
4
5#include <assert.h>
6
7#include "putty.h"
8#include "psftp.h"
9#include "ssh.h"
10#include "int64.h"
11
12char *get_ttymode(void *frontend, const char *mode) { return NULL; }
13
14int get_userpass_input(prompts_t *p, const unsigned char *in, int inlen)
15{
16    int ret;
17    ret = cmdline_get_passwd_input(p, in, inlen);
18    if (ret == -1)
19        ret = console_get_userpass_input(p, in, inlen);
20    return ret;
21}
22
23void platform_get_x11_auth(struct X11Display *display, Conf *conf)
24{
25    /* Do nothing, therefore no auth. */
26}
27const int platform_uses_x11_unix_by_default = TRUE;
28
29/* ----------------------------------------------------------------------
30 * File access abstraction.
31 */
32
33static wchar_t* utf8_to_wide(const char* utf8)
34{
35    wchar_t *w;
36
37    int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, 0, 0);
38    if (len <= 0)
39        return NULL;
40
41    w = snewn(len, wchar_t);
42
43    if (!w)
44        return NULL;
45
46    if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, w, len) <= 0)
47    {
48        sfree(w);
49        return NULL;
50    }
51
52    return w;
53}
54
55static char* wide_to_utf8(const wchar_t* w)
56{
57    char* utf8;
58
59    int len = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0);
60    if (len <= 0)
61        return NULL;
62
63    utf8 = snewn(len, char);
64
65    if (!utf8)
66        return NULL;
67
68    if (WideCharToMultiByte(CP_UTF8, 0, w, -1, utf8, len, 0, 0) <= 0)
69    {
70        sfree(utf8);
71        return NULL;
72    }
73   
74    return utf8;
75}
76
77/*
78 * Set local current directory. Returns NULL on success, or else an
79 * error message which must be freed after printing.
80 */
81char *psftp_lcd(char *dir)
82{
83    char *ret = NULL;
84
85    wchar_t* w = utf8_to_wide(dir);
86    if (!w)
87        return dupstr("Failed to convert to wide character set");
88
89    if (!SetCurrentDirectoryW(w)) {
90        LPVOID message;
91        int i;
92        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
93                      FORMAT_MESSAGE_FROM_SYSTEM |
94                      FORMAT_MESSAGE_IGNORE_INSERTS,
95                      NULL, GetLastError(),
96                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
97                      (LPTSTR)&message, 0, NULL);
98        i = strcspn((char *)message, "\n");
99        ret = dupprintf("%.*s", i, (LPCTSTR)message);
100        LocalFree(message);
101    }
102    sfree(w);
103
104    return ret;
105}
106
107/*
108 * Get local current directory. Returns a string which must be
109 * freed.
110 */
111char *psftp_getcwd(void)
112{
113    char* ret;
114    wchar_t *w = snewn(256, wchar_t);
115    int len = GetCurrentDirectoryW(256, w);
116    if (len > 256)
117        w = sresize(w, len, wchar_t);
118    GetCurrentDirectoryW(len, w);
119
120    ret = wide_to_utf8(w);
121    sfree(w);
122
123    return ret;
124}
125
126#define TIME_POSIX_TO_WIN(t, ft) do { \
127    ULARGE_INTEGER uli; \
128    uli.QuadPart = ((ULONGLONG)(t) + 11644473600ull) * 10000000ull; \
129    (ft).dwLowDateTime  = uli.LowPart; \
130    (ft).dwHighDateTime = uli.HighPart; \
131} while(0)
132#define TIME_WIN_TO_POSIX(ft, t) do { \
133    ULARGE_INTEGER uli; \
134    uli.LowPart  = (ft).dwLowDateTime; \
135    uli.HighPart = (ft).dwHighDateTime; \
136    uli.QuadPart = uli.QuadPart / 10000000ull - 11644473600ull; \
137    (t) = (unsigned long) uli.QuadPart; \
138} while(0)
139
140struct RFile {
141    HANDLE h;
142};
143
144RFile *open_existing_file(const char *name, uint64 *size,
145                          unsigned long *mtime, unsigned long *atime,
146                          long *perms)
147{
148    HANDLE h;
149    RFile *ret;
150
151    wchar_t* wname = utf8_to_wide(name);
152    if (!wname)
153        return NULL;
154
155    h = CreateFileW(wname, GENERIC_READ, FILE_SHARE_READ, NULL,
156                   OPEN_EXISTING, 0, 0);
157    sfree(wname);
158    if (h == INVALID_HANDLE_VALUE)
159        return NULL;
160
161    ret = snew(RFile);
162    ret->h = h;
163
164    if (size)
165        size->lo=GetFileSize(h, &(size->hi));
166
167    if (mtime || atime) {
168        FILETIME actime, wrtime;
169        GetFileTime(h, NULL, &actime, &wrtime);
170        if (atime)
171            TIME_WIN_TO_POSIX(actime, *atime);
172        if (mtime)
173            TIME_WIN_TO_POSIX(wrtime, *mtime);
174    }
175
176    if (perms)
177        *perms = -1;
178
179    return ret;
180}
181
182int read_from_file(RFile *f, void *buffer, int length)
183{
184    int ret;
185    DWORD read;
186    ret = ReadFile(f->h, buffer, length, &read, NULL);
187    if (!ret)
188        return -1;                     /* error */
189    else
190        return (int)read;
191}
192
193void close_rfile(RFile *f)
194{
195    CloseHandle(f->h);
196    sfree(f);
197}
198
199struct WFile {
200    HANDLE h;
201};
202
203WFile *open_new_file(const char *name, long perms)
204{
205    HANDLE h;
206    WFile *ret;
207
208    wchar_t* wname = utf8_to_wide(name);
209    if (!wname)
210        return NULL;
211
212    h = CreateFileW(wname, GENERIC_WRITE, FILE_SHARE_READ, NULL,
213                   CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
214    sfree(wname);
215    if (h == INVALID_HANDLE_VALUE)
216        return NULL;
217
218    ret = snew(WFile);
219    ret->h = h;
220
221    return ret;
222}
223
224WFile *open_existing_wfile(const char *name, uint64 *size)
225{
226    HANDLE h;
227    WFile *ret;
228
229    wchar_t* wname = utf8_to_wide(name);
230    if (!wname)
231        return NULL;
232
233    h = CreateFileW(wname, GENERIC_WRITE, FILE_SHARE_READ, NULL,
234                   OPEN_EXISTING, 0, 0);
235    sfree(wname);
236    if (h == INVALID_HANDLE_VALUE)
237        return NULL;
238
239    ret = snew(WFile);
240    ret->h = h;
241
242    if (size)
243        size->lo=GetFileSize(h, &(size->hi));
244
245    return ret;
246}
247
248int write_to_file(WFile *f, void *buffer, int length)
249{
250    int ret;
251    DWORD written;
252    ret = WriteFile(f->h, buffer, length, &written, NULL);
253    if (!ret)
254        return -1;                     /* error */
255    else
256        return (int)written;
257}
258
259void set_file_times(WFile *f, unsigned long mtime, unsigned long atime)
260{
261    FILETIME actime, wrtime;
262    TIME_POSIX_TO_WIN(atime, actime);
263    TIME_POSIX_TO_WIN(mtime, wrtime);
264    SetFileTime(f->h, NULL, &actime, &wrtime);
265}
266
267void close_wfile(WFile *f)
268{
269    CloseHandle(f->h);
270    sfree(f);
271}
272
273/* Seek offset bytes through file, from whence, where whence is
274   FROM_START, FROM_CURRENT, or FROM_END */
275int seek_file(WFile *f, uint64 offset, int whence)
276{
277    DWORD movemethod;
278
279    switch (whence) {
280    case FROM_START:
281        movemethod = FILE_BEGIN;
282        break;
283    case FROM_CURRENT:
284        movemethod = FILE_CURRENT;
285        break;
286    case FROM_END:
287        movemethod = FILE_END;
288        break;
289    default:
290        return -1;
291    }
292
293    SetFilePointer(f->h, offset.lo, &(offset.hi), movemethod);
294   
295    if (GetLastError() != NO_ERROR)
296        return -1;
297    else 
298        return 0;
299}
300
301uint64 get_file_posn(WFile *f)
302{
303    uint64 ret;
304
305    ret.hi = 0L;
306    ret.lo = SetFilePointer(f->h, 0L, &(ret.hi), FILE_CURRENT);
307
308    return ret;
309}
310
311int file_type(const char *name)
312{
313    DWORD attr;
314
315    wchar_t* wname = utf8_to_wide(name);
316    if (!wname)
317        return FILE_TYPE_NONEXISTENT;
318
319    attr = GetFileAttributesW(wname);
320    sfree(wname);
321    /* We know of no `weird' files under Windows. */
322    if (attr == (DWORD)-1)
323        return FILE_TYPE_NONEXISTENT;
324    else if (attr & FILE_ATTRIBUTE_DIRECTORY)
325        return FILE_TYPE_DIRECTORY;
326    else
327        return FILE_TYPE_FILE;
328}
329
330struct DirHandle {
331    HANDLE h;
332    char *name;
333};
334
335DirHandle *open_directory(const char *name)
336{
337    HANDLE h;
338    WIN32_FIND_DATAW fdat;
339    char *findfile;
340    wchar_t *wfindfile;
341    DirHandle *ret;
342
343    /* Enumerate files in dir `foo'. */
344    findfile = dupcat(name, "/*", NULL);
345
346    wfindfile = utf8_to_wide(findfile);
347    if (!wfindfile)
348        return NULL;
349
350    h = FindFirstFileW(wfindfile, &fdat);
351    if (h == INVALID_HANDLE_VALUE)
352        return NULL;
353    sfree(wfindfile);
354    sfree(findfile);
355
356    ret = snew(DirHandle);
357    ret->h = h;
358    ret->name = wide_to_utf8(fdat.cFileName);
359    return ret;
360}
361
362char *read_filename(DirHandle *dir)
363{
364    do {
365
366        if (!dir->name) {
367            WIN32_FIND_DATAW fdat;
368            int ok = FindNextFileW(dir->h, &fdat);
369            if (!ok)
370                return NULL;
371            else
372                dir->name = wide_to_utf8(fdat.cFileName);
373        }
374
375        assert(dir->name);
376        if (dir->name[0] == '.' &&
377            (dir->name[1] == '\0' ||
378             (dir->name[1] == '.' && dir->name[2] == '\0'))) {
379            sfree(dir->name);
380            dir->name = NULL;
381        }
382
383    } while (!dir->name);
384
385    if (dir->name) {
386        char *ret = dir->name;
387        dir->name = NULL;
388        return ret;
389    } else
390        return NULL;
391}
392
393void close_directory(DirHandle *dir)
394{
395    FindClose(dir->h);
396    if (dir->name)
397        sfree(dir->name);
398    sfree(dir);
399}
400
401int test_wildcard(const char *name, int cmdline)
402{
403    HANDLE fh;
404    WIN32_FIND_DATAW fdat;
405
406    wchar_t* wname = utf8_to_wide(name);
407    if (!wname)
408        return WCTYPE_NONEXISTENT;
409
410    /* First see if the exact name exists. */
411    if (GetFileAttributesW(wname) != (DWORD)-1)
412    {
413        sfree(wname);
414        return WCTYPE_FILENAME;
415    }
416
417    /* Otherwise see if a wildcard match finds anything. */
418    fh = FindFirstFileW(wname, &fdat);
419    if (fh == INVALID_HANDLE_VALUE)
420    {
421        sfree(wname);
422        return WCTYPE_NONEXISTENT;
423    }
424
425    sfree(wname);
426
427    FindClose(fh);
428    return WCTYPE_WILDCARD;
429}
430
431struct WildcardMatcher {
432    HANDLE h;
433    char *name;
434    char *srcpath;
435};
436
437char *stripslashes(const char *str, int local)
438{
439    char *p;
440
441    /*
442     * On Windows, \ / : are all path component separators.
443     */
444
445    if (local) {
446        p = strchr(str, ':');
447        if (p) str = p+1;
448    }
449
450    p = strrchr(str, '/');
451    if (p) str = p+1;
452
453    if (local) {
454        p = strrchr(str, '\\');
455        if (p) str = p+1;
456    }
457
458    return (char *)str;
459}
460
461WildcardMatcher *begin_wildcard_matching(const char *name)
462{
463    HANDLE h;
464    WIN32_FIND_DATA fdat;
465    WildcardMatcher *ret;
466    char *last;
467
468    h = FindFirstFile(name, &fdat);
469    if (h == INVALID_HANDLE_VALUE)
470        return NULL;
471
472    ret = snew(WildcardMatcher);
473    ret->h = h;
474    ret->srcpath = dupstr(name);
475    last = stripslashes(ret->srcpath, 1);
476    *last = '\0';
477    if (fdat.cFileName[0] == '.' &&
478        (fdat.cFileName[1] == '\0' ||
479         (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
480        ret->name = NULL;
481    else
482        ret->name = dupcat(ret->srcpath, fdat.cFileName, NULL);
483
484    return ret;
485}
486
487char *wildcard_get_filename(WildcardMatcher *dir)
488{
489    while (!dir->name) {
490        WIN32_FIND_DATA fdat;
491        int ok = FindNextFile(dir->h, &fdat);
492
493        if (!ok)
494            return NULL;
495
496        if (fdat.cFileName[0] == '.' &&
497            (fdat.cFileName[1] == '\0' ||
498             (fdat.cFileName[1] == '.' && fdat.cFileName[2] == '\0')))
499            dir->name = NULL;
500        else
501            dir->name = dupcat(dir->srcpath, fdat.cFileName, NULL);
502    }
503
504    if (dir->name) {
505        char *ret = dir->name;
506        dir->name = NULL;
507        return ret;
508    } else
509        return NULL;
510}
511
512void finish_wildcard_matching(WildcardMatcher *dir)
513{
514    FindClose(dir->h);
515    if (dir->name)
516        sfree(dir->name);
517    sfree(dir->srcpath);
518    sfree(dir);
519}
520
521int vet_filename(const char *name)
522{
523    if (strchr(name, '/') || strchr(name, '\\') || strchr(name, ':'))
524        return FALSE;
525
526    if (!name[strspn(name, ".")])      /* entirely composed of dots */
527        return FALSE;
528
529    return TRUE;
530}
531
532int create_directory(const char *name)
533{
534    int res;
535
536    wchar_t *wname = utf8_to_wide(name);
537    if (!wname)
538        return 0;
539
540    res = CreateDirectoryW(wname, NULL) != 0;
541    sfree(wname);
542
543    return res;
544}
545
546char *dir_file_cat(const char *dir, const char *file)
547{
548    return dupcat(dir, "\\", file, NULL);
549}
550
551/* ----------------------------------------------------------------------
552 * Platform-specific network handling.
553 */
554
555/*
556 * Be told what socket we're supposed to be using.
557 */
558static SOCKET sftp_ssh_socket = INVALID_SOCKET;
559static HANDLE netevent = INVALID_HANDLE_VALUE;
560char *do_select(SOCKET skt, int startup)
561{
562    int events;
563    if (startup)
564        sftp_ssh_socket = skt;
565    else
566        sftp_ssh_socket = INVALID_SOCKET;
567
568    if (p_WSAEventSelect) {
569        if (startup) {
570            events = (FD_CONNECT | FD_READ | FD_WRITE |
571                      FD_OOB | FD_CLOSE | FD_ACCEPT);
572            netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
573        } else {
574            events = 0;
575        }
576        if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
577            switch (p_WSAGetLastError()) {
578              case WSAENETDOWN:
579                return "Network is down";
580              default:
581                return "WSAEventSelect(): unknown error";
582            }
583        }
584    }
585    return NULL;
586}
587extern int select_result(WPARAM, LPARAM);
588
589int do_eventsel_loop(HANDLE other_event)
590{
591    int n, nhandles, nallhandles, netindex, otherindex;
592    unsigned long next, then;
593    long ticks;
594    HANDLE *handles;
595    SOCKET *sklist;
596    int skcount;
597    unsigned long now = GETTICKCOUNT();
598
599    if (toplevel_callback_pending()) {
600        ticks = 0;
601        next = now;
602    } else if (run_timers(now, &next)) {
603        then = now;
604        now = GETTICKCOUNT();
605        if (now - then > next - then)
606            ticks = 0;
607        else
608            ticks = next - now;
609    } else {
610        ticks = INFINITE;
611        /* no need to initialise next here because we can never get
612         * WAIT_TIMEOUT */
613    }
614
615    handles = handle_get_events(&nhandles);
616    handles = sresize(handles, nhandles+2, HANDLE);
617    nallhandles = nhandles;
618
619    if (netevent != INVALID_HANDLE_VALUE)
620        handles[netindex = nallhandles++] = netevent;
621    else
622        netindex = -1;
623    if (other_event != INVALID_HANDLE_VALUE)
624        handles[otherindex = nallhandles++] = other_event;
625    else
626        otherindex = -1;
627
628    n = WaitForMultipleObjects(nallhandles, handles, FALSE, ticks);
629
630    if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
631        handle_got_event(handles[n - WAIT_OBJECT_0]);
632    } else if (netindex >= 0 && n == WAIT_OBJECT_0 + netindex) {
633        WSANETWORKEVENTS things;
634        SOCKET socket;
635        extern SOCKET first_socket(int *), next_socket(int *);
636        extern int select_result(WPARAM, LPARAM);
637        int i, socketstate;
638
639        /*
640         * We must not call select_result() for any socket
641         * until we have finished enumerating within the
642         * tree. This is because select_result() may close
643         * the socket and modify the tree.
644         */
645        /* Count the active sockets. */
646        i = 0;
647        for (socket = first_socket(&socketstate);
648             socket != INVALID_SOCKET;
649             socket = next_socket(&socketstate)) i++;
650
651        /* Expand the buffer if necessary. */
652        sklist = snewn(i, SOCKET);
653
654        /* Retrieve the sockets into sklist. */
655        skcount = 0;
656        for (socket = first_socket(&socketstate);
657             socket != INVALID_SOCKET;
658             socket = next_socket(&socketstate)) {
659            sklist[skcount++] = socket;
660        }
661
662        /* Now we're done enumerating; go through the list. */
663        for (i = 0; i < skcount; i++) {
664            WPARAM wp;
665            socket = sklist[i];
666            wp = (WPARAM) socket;
667            if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
668                static const struct { int bit, mask; } eventtypes[] = {
669                    {FD_CONNECT_BIT, FD_CONNECT},
670                    {FD_READ_BIT, FD_READ},
671                    {FD_CLOSE_BIT, FD_CLOSE},
672                    {FD_OOB_BIT, FD_OOB},
673                    {FD_WRITE_BIT, FD_WRITE},
674                    {FD_ACCEPT_BIT, FD_ACCEPT},
675                };
676                int e;
677
678                noise_ultralight(socket);
679                noise_ultralight(things.lNetworkEvents);
680
681                for (e = 0; e < lenof(eventtypes); e++)
682                    if (things.lNetworkEvents & eventtypes[e].mask) {
683                        LPARAM lp;
684                        int err = things.iErrorCode[eventtypes[e].bit];
685                        lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
686                        select_result(wp, lp);
687                    }
688            }
689        }
690
691        sfree(sklist);
692    }
693
694    sfree(handles);
695
696    run_toplevel_callbacks();
697
698    if (n == WAIT_TIMEOUT) {
699        now = next;
700    } else {
701        now = GETTICKCOUNT();
702    }
703
704    if (otherindex >= 0 && n == WAIT_OBJECT_0 + otherindex)
705        return 1;
706
707    return 0;
708}
709
710/*
711 * Wait for some network data and process it.
712 *
713 * We have two variants of this function. One uses select() so that
714 * it's compatible with WinSock 1. The other uses WSAEventSelect
715 * and MsgWaitForMultipleObjects, so that we can consistently use
716 * WSAEventSelect throughout; this enables us to also implement
717 * ssh_sftp_get_cmdline() using a parallel mechanism.
718 */
719int ssh_sftp_loop_iteration(void)
720{
721    if (p_WSAEventSelect == NULL) {
722        fd_set readfds;
723        int ret;
724        unsigned long now = GETTICKCOUNT(), then;
725
726        if (sftp_ssh_socket == INVALID_SOCKET)
727            return -1;                 /* doom */
728
729        if (socket_writable(sftp_ssh_socket))
730            select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_WRITE);
731
732        do {
733            unsigned long next;
734            long ticks;
735            struct timeval tv, *ptv;
736
737            if (run_timers(now, &next)) {
738                then = now;
739                now = GETTICKCOUNT();
740                if (now - then > next - then)
741                    ticks = 0;
742                else
743                    ticks = next - now;
744                tv.tv_sec = ticks / 1000;
745                tv.tv_usec = ticks % 1000 * 1000;
746                ptv = &tv;
747            } else {
748                ptv = NULL;
749            }
750
751            FD_ZERO(&readfds);
752            FD_SET(sftp_ssh_socket, &readfds);
753            ret = p_select(1, &readfds, NULL, NULL, ptv);
754
755            if (ret < 0)
756                return -1;                     /* doom */
757            else if (ret == 0)
758                now = next;
759            else
760                now = GETTICKCOUNT();
761
762        } while (ret == 0);
763
764        select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
765
766        return 0;
767    } else {
768        return do_eventsel_loop(INVALID_HANDLE_VALUE);
769    }
770}
771
772/*
773 * Read a command line from standard input.
774 *
775 * In the presence of WinSock 2, we can use WSAEventSelect to
776 * mediate between the socket and stdin, meaning we can send
777 * keepalives and respond to server events even while waiting at
778 * the PSFTP command prompt. Without WS2, we fall back to a simple
779 * fgets.
780 */
781struct command_read_ctx {
782    HANDLE event;
783    char *line;
784};
785
786static DWORD WINAPI command_read_thread(void *param)
787{
788    struct command_read_ctx *ctx = (struct command_read_ctx *) param;
789
790    ctx->line = fgetline(stdin);
791
792    SetEvent(ctx->event);
793
794    return 0;
795}
796
797char *ssh_sftp_get_cmdline(const char *prompt, int no_fds_ok)
798{
799    int ret;
800    struct command_read_ctx actx, *ctx = &actx;
801    DWORD threadid;
802    HANDLE hThread;
803
804    /* Not used in fzsftp
805    fputs(prompt, stdout);
806    fflush(stdout);
807    */
808
809    if ((sftp_ssh_socket == INVALID_SOCKET && no_fds_ok) ||
810        p_WSAEventSelect == NULL) {
811        return fgetline(stdin);        /* very simple */
812    }
813
814    /*
815     * Create a second thread to read from stdin. Process network
816     * and timing events until it terminates.
817     */
818    ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
819    ctx->line = NULL;
820
821    hThread = CreateThread(NULL, 0, command_read_thread, ctx, 0, &threadid);
822    if (!hThread) {
823        CloseHandle(ctx->event);
824        fzprintf(sftpError, "Unable to create command input thread");
825        cleanup_exit(1);
826    }
827
828    do {
829        ret = do_eventsel_loop(ctx->event);
830
831        /* Error return can only occur if netevent==NULL, and it ain't. */
832        assert(ret >= 0);
833    } while (ret == 0);
834
835    CloseHandle(hThread);
836    CloseHandle(ctx->event);
837
838    return ctx->line;
839}
840
841/* ----------------------------------------------------------------------
842 * Main program. Parse arguments etc.
843 */
844int main(int argc, char *argv[])
845{
846    int ret;
847
848    ret = psftp_main(argc, argv);
849
850    return ret;
851}
Note: See TracBrowser for help on using the repository browser.