File size: 5,008 Bytes
90f0300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
155
import fs from 'node:fs/promises';
import path from 'node:path';
import { gzipSync } from 'node:zlib';
import { GENERATED_ROOT } from './image-generator.js';
import { CLIENT_DIST, HTTPS_ROOT_CA_PATH, compressibleExtensions, mimeTypes } from './app-config.js';

export function acceptsGzip(req) {
  return String(req.headers['accept-encoding'] || '')
    .split(',')
    .some((value) => value.trim().toLowerCase().startsWith('gzip'));
}

export function staticCacheControl(ext, filePath = '') {
  if (ext === '.html') {
    return 'no-store';
  }
  const normalized = filePath.split(path.sep).join('/');
  if (normalized.includes('/assets/')) {
    return 'public, max-age=31536000, immutable';
  }
  return 'public, max-age=3600';
}

export function sendStaticContent(req, res, status, content, headers, ext) {
  let body = content;
  const nextHeaders = { ...headers };
  if (content.length >= 1024 && compressibleExtensions.has(ext) && acceptsGzip(req)) {
    body = gzipSync(content);
    nextHeaders['content-encoding'] = 'gzip';
    nextHeaders.vary = nextHeaders.vary ? `${nextHeaders.vary}, Accept-Encoding` : 'Accept-Encoding';
  }
  nextHeaders['content-length'] = body.length;
  res.writeHead(status, nextHeaders);
  res.end(body);
}

export async function serveFileFromRoot(req, res, rootDir, requestedPath, cacheControl) {
  const relativePath = requestedPath.replace(/^\/+/, '');
  const resolvedRoot = path.resolve(rootDir);
  const candidate = path.normalize(path.join(resolvedRoot, relativePath));
  const rootWithSep = resolvedRoot.endsWith(path.sep) ? resolvedRoot : `${resolvedRoot}${path.sep}`;
  if (candidate !== resolvedRoot && !candidate.startsWith(rootWithSep)) {
    res.writeHead(403);
    res.end('Forbidden');
    return true;
  }

  try {
    const [realRoot, realCandidate] = await Promise.all([
      fs.realpath(resolvedRoot),
      fs.realpath(candidate)
    ]);
    const realRootWithSep = realRoot.endsWith(path.sep) ? realRoot : `${realRoot}${path.sep}`;
    if (realCandidate !== realRoot && !realCandidate.startsWith(realRootWithSep)) {
      res.writeHead(403);
      res.end('Forbidden');
      return true;
    }
    const stat = await fs.stat(realCandidate);
    if (!stat.isFile()) {
      res.writeHead(404);
      res.end('Not found');
      return true;
    }
    const ext = path.extname(realCandidate);
    const content = await fs.readFile(realCandidate);
    sendStaticContent(req, res, 200, content, {
      'content-type': mimeTypes.get(ext) || 'application/octet-stream',
      'cache-control': cacheControl,
      'x-content-type-options': 'nosniff'
    }, ext);
    return true;
  } catch {
    res.writeHead(404);
    res.end('Not found');
    return true;
  }
}

export async function serveStatic(req, res, url) {
  let requestedPath = '';
  try {
    requestedPath = decodeURIComponent(url.pathname);
  } catch {
    res.writeHead(400);
    res.end('Bad request');
    return true;
  }
  if (requestedPath === '/codexmobile-root-ca.cer') {
    try {
      const stat = await fs.stat(HTTPS_ROOT_CA_PATH);
      const content = await fs.readFile(HTTPS_ROOT_CA_PATH);
      res.writeHead(200, {
        'content-type': 'application/x-x509-ca-cert',
        'content-length': stat.size,
        'cache-control': 'no-store',
        'content-disposition': 'attachment; filename="codexmobile-root-ca.cer"',
        'x-content-type-options': 'nosniff'
      });
      res.end(content);
    } catch {
      res.writeHead(404);
      res.end('Certificate not found');
    }
    return;
  }

  if (requestedPath.startsWith('/generated/')) {
    await serveFileFromRoot(
      req,
      res,
      GENERATED_ROOT,
      requestedPath.slice('/generated/'.length),
      'private, max-age=86400'
    );
    return;
  }

  if (requestedPath === '/') {
    requestedPath = '/index.html';
  }

  const candidate = path.normalize(path.join(CLIENT_DIST, requestedPath));
  if (!candidate.startsWith(CLIENT_DIST)) {
    res.writeHead(403);
    res.end('Forbidden');
    return;
  }

  try {
    const stat = await fs.stat(candidate);
    const filePath = stat.isDirectory() ? path.join(candidate, 'index.html') : candidate;
    const ext = path.extname(filePath);
    const content = await fs.readFile(filePath);
    sendStaticContent(req, res, 200, content, {
      'content-type': mimeTypes.get(ext) || 'application/octet-stream',
      'cache-control': staticCacheControl(ext, filePath),
      'x-content-type-options': 'nosniff'
    }, ext);
  } catch {
    const indexPath = path.join(CLIENT_DIST, 'index.html');
    try {
      const content = await fs.readFile(indexPath);
      sendStaticContent(req, res, 200, content, {
        'content-type': 'text/html; charset=utf-8',
        'cache-control': 'no-store',
        'x-content-type-options': 'nosniff'
      }, '.html');
    } catch {
      res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8' });
      res.end('CodexMobile server is running. Build the PWA with: npm run build');
    }
  }
}