Switch to acorn-based JS templates with classes, ids, scripts, template strings
[jst_server.git] / site.js
1 let assert = require('assert')
2 let config = require('./config')
3 let cookie = require('cookie')
4 let fs = require('fs')
5 let js_template = require('js_template')
6 let resources = require('./resources')
7 let server = require('./server')
8 let util = require('util')
9 let url = require('url')
10
11 let fs_readFile = util.promisify(fs.readFile)
12 let fs_stat = util.promisify(fs.stat)
13
14 let app = async (site, site_root, req, res, protocol) => {
15   // parse the pathname portion of url
16   // this is actually cheating since it's not a complete url
17   let parsed_url = url.parse(req.url, true)
18   let path = parsed_url.pathname.split('/')
19
20   // path must begin with /
21   if (path.length === 0 || path[0].length) {
22     server.die(res)
23     return
24   }
25
26   // path elements must be findable in the file system (thus can't be empty)
27   let dir_name = ''
28   let dir_name_is_pub = false
29   for (let i = 1; i < path.length - 1; ++i) {
30     dir_name += '/' + path[i]
31     if (path[i].length === 0 || path[i].charAt(0) === '.') {
32       console.log(site, 'bad path component', dir_name)
33       server.die(res)
34       return
35     }
36     let stats
37     try {
38       stats = await fs_stat(site_root + dir_name)
39     }
40     catch (err) {
41       if (err.code !== 'ENOENT')
42         throw err
43       if (!dir_name_is_pub) {
44         let temp = dir_name + '.pub'
45         try {
46           stats = await fs_stat(site_root + temp)
47           dir_name = temp
48           dir_name_is_pub = true
49         }
50         catch (err2) {
51           if (err2.code !== 'ENOENT')
52             throw err2
53           console.log(site, 'directory not found', dir_name)
54           server.die(res)
55           return
56         }
57       }
58       if (!stats.isDirectory()) {
59         console.log(site, 'not directory', dir_name)
60         server.die(res)
61         return
62       }
63     }
64   }
65
66   file_name = path[path.length - 1]
67   if (file_name === '') {
68     path[path.length - 1] = 'index.html'
69     path = path.join('/')
70     console.log(site, 'server.redirecting', parsed_url.pathname, 'to', path)
71     server.redirect(res, path + (parsed_url.search || ''))
72     return
73   }
74   let page = path.join('/')
75
76   let temp = file_name.lastIndexOf('.')
77   let file_type = temp === -1 ? '' : file_name.substring(temp + 1)
78   let mime_type =
79     Object.prototype.hasOwnProperty.call(config.mime_types, file_type) ?
80     config.mime_types[file_type] :
81     config.mime_type_default
82
83   if (dir_name_is_pub) {
84     temp = site_root + dir_name + '/' + file_name
85     try {
86       let data = await fs_readFile(temp)
87       console.log(
88         site,
89         'serving',
90         temp,
91         'length',
92         data.length,
93         'from pub'
94       )
95       server.serve(res, 200, mime_type, data)
96       return
97     }
98     catch (err) {
99       if (err.code !== 'ENOENT')
100         throw err
101     }
102   }
103   else {
104     // at this point dir_name is guaranteed to be a prefix of page
105     // (has no .pub component), though constructed in a roundabout way
106     temp = page + '.pub'
107     try {
108       let data = await fs_readFile(site_root + temp)
109       console.log(
110         site,
111         'serving',
112         temp,
113         'length',
114         data.length,
115         'from pub'
116       )
117       server.serve(res, 200, mime_type, data)
118       return
119     }
120     catch (err) {
121       if (err.code !== 'ENOENT')
122         throw err
123     }
124
125     switch (file_type) {
126     case 'html':
127       temp = page + '.jst'
128       try {
129         let _out = []
130         await (await js_template(site_root, site_root, temp))(
131           {
132             cookies: cookie.parse(req.headers.cookie || ''),
133             lang: 'en',
134             method: req.method,
135             page: page,
136             query: parsed_url.query,
137             read_stream: req,
138             resources: resources,
139             set_cookie: (key, value, expires, path) => {
140               res.setHeader(
141                 'Set-Cookie',
142                 key +
143                 '=' +
144                 value +
145                 '; expires=' +
146                 expires +
147                 '; path=' +
148                 path +
149                 ';'
150               )
151             },
152             site: site,
153             site_root: site_root
154           },
155           _out
156         )
157         let data = Buffer.from(_out.join(''))
158         console.log(
159           site,
160           'serving',
161           temp,
162           'length',
163           data.length,
164           'from js'
165         )
166         server.serve(res, 200, mime_type, data)
167         return
168       }
169       catch (err) {
170         if (err.code !== 'ENOENT') // should check error type
171           throw err
172       }
173       break
174
175     case 'css':
176       temp = page + '.less'
177       try {
178         let data = await resources.get_less(
179           site_root + temp,
180           site_root,
181           dir_name
182         )
183         console.log(
184           site,
185           'serving',
186           temp,
187           'length',
188           data.length,
189           'from less'
190         )
191         server.serve(res, 200, mime_type, data)
192         return
193       }
194       catch (err) {
195         if (err.code !== 'ENOENT') // note: err.code might be undefined
196           throw err
197       }
198       break
199     }
200   }
201
202   let favicons = await resources.get_zip(site_root + '/favicons.zip')
203   if (Object.prototype.hasOwnProperty.call(favicons, page)) {
204     let data = favicons[page]
205     console.log(
206       site,
207       'serving',
208       page,
209       'length',
210       data.length,
211       'from favicons'
212     )
213     server.serve(res, 200, mime_type, data)
214     return
215   }
216
217   console.log(site, 'file not found', page)
218   server.die(res)
219 }
220
221 exports.app = app