summaryrefslogtreecommitdiff
path: root/src/misc/ls.cgi.c
blob: d72ae105a39cab787a749e762b4204e07d698f63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* ISC license. */

#include <skalibs/bsdsnowflake.h>

#include <errno.h>
#include <stdlib.h>

#include <skalibs/gccattributes.h>
#include <skalibs/buffer.h>
#include <skalibs/stat.h>
#include <skalibs/direntry.h>
#include <skalibs/strerr.h>
#include <skalibs/tai.h>
#include <skalibs/stralloc.h>
#include <skalibs/djbunix.h>
#include <skalibs/unix-transactional.h>

#include <tipidee/response.h>
#include <tipidee/util.h>

#define USAGE "ls.cgi is meant to be used as a CGI script invoked by tipideed"
#define dieusage() strerr_dieusage(100, USAGE)
#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
#define dieout() strerr_diefu1sys(111, "write to stdout")

#define HEADER1 "Status: 200\nContent-Type: text/html\n\n\
<html>\n\
<head>\n\
  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\
  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n\
  <meta http-equiv=\"Content-Language\" content=\"en\" />\n\
  <title>index of directory at "

#define HEADER2 "</title>\n\
  <meta name=\"Description\" content=\"directory entries list, generated by index.cgi\" />\n\
  <meta name=\"Keywords\" content=\"tipideed index index.cgi CGI directory web server skarnet.org skarnet software\" />\n\
  </head>\n\
<body>\n\
<h1> Directory entries for "

#define HEADER3 " </h1>\n<ul>\n"

#define FOOTER "</ul>\n</body>\n</html>\n"

static void print_one_entry (int dfd, char const *path, char const *name)
{
  struct stat st ;
  if (stat_at(dfd, name, &st) == -1) strerr_diefu4sys(111, "stat ", path, "/", name) ;
  if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) return ;
  if (buffer_puts(buffer_1, " <li> <a href=\"") == -1
   || buffer_puts(buffer_1, name) == -1
   || (S_ISDIR(st.st_mode) && buffer_put(buffer_1, "/", 1) == -1)
   || buffer_puts(buffer_1, "\"> ") == -1
   || buffer_puts(buffer_1, name) == -1
   || (S_ISDIR(st.st_mode) && buffer_put(buffer_1, "/", 1) == -1)
   || buffer_puts(buffer_1, " </a>") == -1) dieout() ;
  if (!S_ISDIR(st.st_mode))
  {
    int iskB = st.st_size >= 1024 ;
    char lmdate[128] ;
    char fmt[UINT64_FMT] ;
    tain t ;
    size_t l ;
    if (!tain_from_timespec(&t, &st.st_mtim))
      strerr_diefu4sys(111, "convert st_mtim for ", path, "/", name) ;
    l = tipidee_response_header_date_fmt(lmdate, 128, &t) ;
    if (!l) strerr_diefu4sys(111, "convert st_mtim for ", path, "/", name) ;
    lmdate[l++] = 0 ;
    fmt[uint64_fmt(fmt, iskB ? st.st_size / 1024 : st.st_size)] = 0 ;
    if (buffer_put(buffer_1, " (", 2) == -1
     || buffer_puts(buffer_1, fmt) == -1
     || buffer_put(buffer_1, " ", 1) == -1
     || buffer_puts(buffer_1, iskB ? "kB" : "bytes") == -1
     || buffer_puts(buffer_1, ", last modified ") == -1
     || buffer_put(buffer_1, lmdate, l) == -1
     || buffer_put(buffer_1, ")", 1) == -1) dieout() ;
  }
  if (buffer_puts(buffer_1, " </li>\n") == -1) dieout() ;
}

static void html_escape (stralloc *sa, char const *s)
{
  size_t len = strlen(s) ;
  char cc[2] = "\0" ;
  for (size_t i = 0 ; i < len ; i++)
  {
    cc[0] = s[i] ;
    if (!stralloc_cats(sa, tipidee_util_htmlescape(cc))) dienomem() ;
  }
  if (!stralloc_0(sa)) dienomem() ;
}

int main (int argc, char const *const *argv, char const *const *envp)
{
  stralloc sa = STRALLOC_ZERO ;
  stralloc tmp = STRALLOC_ZERO ;
  size_t dirlen ;
  char const *x ;
  DIR *dir ;
  int dfd ;
  PROG = "ls.cgi" ;

  x = getenv("SERVER_PROTOCOL") ;
  if (!x) strerr_dienotset(100, "SERVER_PROTOCOL") ;
  if (strncmp(x, "HTTP/1.", 7))
    strerr_diefu1x(100, "SERVER_PROTOCOL isn't HTTP") ;
  x = getenv("REQUEST_METHOD") ;
  if (!x) strerr_dienotset(100, "REQUEST_METHOD") ;
  if (strcmp(x, "GET") && strcmp(x, "HEAD"))
    strerr_diefu1x(100, "ls.cgi can only be used with the GET or HEAD methods") ;

  x = getenv("SERVER_NAME") ;
  if (!x) strerr_dienotset(100, "SERVER_NAME") ;
  if (!stralloc_cats(&sa, x)) dienomem() ;
  if (!stralloc_catb(&sa, ":", 1)) dienomem() ;
  x = getenv("SERVER_PORT") ;
  if (!x) strerr_dienotset(100, "SERVER_PORT") ;
  if (!stralloc_cats(&sa, x)) dienomem() ;
  x = getenv("SCRIPT_NAME") ;
  if (!x) strerr_dienotset(100, "SCRIPT_NAME") ;
  if (*x != '/') strerr_diefu2x(100, "SCRIPT_NAME", " must be absolute") ;
  if (!stralloc_cats(&sa, x)) dienomem() ;
  while (sa.s[sa.len - 1] != '/') sa.len-- ;
  sa.s[--sa.len] = 0 ;
  html_escape(&tmp, sa.s) ;
  dirlen = tmp.len - 1 ;

  dir = opendir(sa.s) ;
  if (!dir) strerr_diefu2sys(111, "opendir ", sa.s) ;
  dfd = dir_fd(dir) ;

  if (buffer_puts(buffer_1, HEADER1) == -1
   || buffer_put(buffer_1, tmp.s, dirlen) == -1
   || buffer_puts(buffer_1, HEADER2) == -1
   || buffer_put(buffer_1, tmp.s, dirlen) == -1
   || buffer_puts(buffer_1, HEADER3) == -1) dieout() ;

  for (;;)
  {
    direntry *d ;
    errno = 0 ;
    d = readdir(dir) ;
    if (!d) break ;
    if (d->d_name[0] == '.') continue ;
    html_escape(&tmp, d->d_name) ;
    print_one_entry(dfd, tmp.s, tmp.s + dirlen + 1) ;
    tmp.len = dirlen + 1 ;
  }
  if (errno) strerr_diefu2sys(111, "readdir ", sa.s) ;
  dir_close(dir) ;

  if (buffer_putsflush(buffer_1, FOOTER) == -1) dieout() ;
  return 0 ;
}