2020-03-16 18:04:09 +01:00
|
|
|
import { field, logger } from "@coder/logger"
|
2020-03-02 19:43:02 +01:00
|
|
|
import * as http from "http"
|
2020-03-16 18:04:09 +01:00
|
|
|
import * as path from "path"
|
|
|
|
import { Readable } from "stream"
|
|
|
|
import * as tarFs from "tar-fs"
|
|
|
|
import * as zlib from "zlib"
|
2020-03-02 19:43:02 +01:00
|
|
|
import { HttpProvider, HttpResponse, Route } from "../http"
|
2020-05-18 19:57:50 +02:00
|
|
|
import { pathToFsPath } from "../util"
|
2020-03-02 19:43:02 +01:00
|
|
|
|
|
|
|
/**
|
2020-03-16 18:04:09 +01:00
|
|
|
* Static file HTTP provider. Regular static requests (the path is the request
|
|
|
|
* itself) do not require authentication and they only allow access to resources
|
|
|
|
* within the application. Requests for tars (the path is in a query parameter)
|
|
|
|
* do require permissions and can access any directory.
|
2020-03-02 19:43:02 +01:00
|
|
|
*/
|
|
|
|
export class StaticHttpProvider extends HttpProvider {
|
|
|
|
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
|
|
|
this.ensureMethod(request)
|
2020-03-16 18:04:09 +01:00
|
|
|
|
|
|
|
if (typeof route.query.tar === "string") {
|
|
|
|
this.ensureAuthenticated(request)
|
2020-05-18 19:57:50 +02:00
|
|
|
return this.getTarredResource(request, pathToFsPath(route.query.tar))
|
2020-03-16 18:04:09 +01:00
|
|
|
}
|
|
|
|
|
2020-03-02 19:43:02 +01:00
|
|
|
const response = await this.getReplacedResource(route)
|
|
|
|
if (!this.isDev) {
|
|
|
|
response.cache = true
|
|
|
|
}
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a resource with variables replaced where necessary.
|
|
|
|
*/
|
|
|
|
protected async getReplacedResource(route: Route): Promise<HttpResponse> {
|
|
|
|
// The first part is always the commit (for caching purposes).
|
|
|
|
const split = route.requestPath.split("/").slice(1)
|
|
|
|
|
|
|
|
switch (split[split.length - 1]) {
|
2020-03-03 00:44:16 +01:00
|
|
|
case "manifest.json": {
|
2020-03-02 19:43:02 +01:00
|
|
|
const response = await this.getUtf8Resource(this.rootPath, ...split)
|
|
|
|
return this.replaceTemplates(route, response)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.getResource(this.rootPath, ...split)
|
|
|
|
}
|
2020-03-16 18:04:09 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Tar up and stream a directory.
|
|
|
|
*/
|
|
|
|
private async getTarredResource(request: http.IncomingMessage, ...parts: string[]): Promise<HttpResponse> {
|
|
|
|
const filePath = path.join(...parts)
|
|
|
|
let stream: Readable = tarFs.pack(filePath)
|
|
|
|
const headers: http.OutgoingHttpHeaders = {}
|
|
|
|
if (request.headers["accept-encoding"] && request.headers["accept-encoding"].includes("gzip")) {
|
|
|
|
logger.debug("gzipping tar", field("filePath", filePath))
|
|
|
|
const compress = zlib.createGzip()
|
|
|
|
stream.pipe(compress)
|
|
|
|
stream.on("error", (error) => compress.destroy(error))
|
|
|
|
stream.on("close", () => compress.end())
|
|
|
|
stream = compress
|
|
|
|
headers["content-encoding"] = "gzip"
|
|
|
|
}
|
|
|
|
return { stream, filePath, mime: "application/x-tar", cache: true, headers }
|
|
|
|
}
|
2020-03-02 19:43:02 +01:00
|
|
|
}
|