1//===-- llvm/Debuginfod/HTTPServer.cpp - HTTP server library -----*- C++-*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10///
11/// This file defines the methods of the HTTPServer class and the streamFile
12/// function.
13///
14//===----------------------------------------------------------------------===//
15
16#include "llvm/Debuginfod/HTTPServer.h"
17#include "llvm/ADT/StringExtras.h"
18#include "llvm/ADT/StringRef.h"
19#include "llvm/Support/Errc.h"
20#include "llvm/Support/Error.h"
21#include "llvm/Support/FileSystem.h"
22#include "llvm/Support/MemoryBuffer.h"
23#include "llvm/Support/Regex.h"
24
25#ifdef LLVM_ENABLE_HTTPLIB
26#include "httplib.h"
27#endif
28
29using namespace llvm;
30
31char HTTPServerError::ID = 0;
32
33HTTPServerError::HTTPServerError(const Twine &Msg) : Msg(Msg.str()) {}
34
35void HTTPServerError::log(raw_ostream &OS) const { OS << Msg; }
36
37bool llvm::streamFile(HTTPServerRequest &Request, StringRef FilePath) {
38  Expected<sys::fs::file_t> FDOrErr = sys::fs::openNativeFileForRead(FilePath);
39  if (Error Err = FDOrErr.takeError()) {
40    consumeError(std::move(Err));
41    Request.setResponse({404u, "text/plain", "Could not open file to read.\n"});
42    return false;
43  }
44  ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
45      MemoryBuffer::getOpenFile(*FDOrErr, FilePath,
46                                /*FileSize=*/-1,
47                                /*RequiresNullTerminator=*/false);
48  sys::fs::closeFile(*FDOrErr);
49  if (Error Err = errorCodeToError(MBOrErr.getError())) {
50    consumeError(std::move(Err));
51    Request.setResponse({404u, "text/plain", "Could not memory-map file.\n"});
52    return false;
53  }
54  // Lambdas are copied on conversion to std::function, preventing use of
55  // smart pointers.
56  MemoryBuffer *MB = MBOrErr->release();
57  Request.setResponse({200u, "application/octet-stream", MB->getBufferSize(),
58                       [=](size_t Offset, size_t Length) -> StringRef {
59                         return MB->getBuffer().substr(Offset, Length);
60                       },
61                       [=](bool Success) { delete MB; }});
62  return true;
63}
64
65#ifdef LLVM_ENABLE_HTTPLIB
66
67bool HTTPServer::isAvailable() { return true; }
68
69HTTPServer::HTTPServer() { Server = std::make_unique<httplib::Server>(); }
70
71HTTPServer::~HTTPServer() { stop(); }
72
73static void expandUrlPathMatches(const std::smatch &Matches,
74                                 HTTPServerRequest &Request) {
75  bool UrlPathSet = false;
76  for (const auto &it : Matches) {
77    if (UrlPathSet)
78      Request.UrlPathMatches.push_back(it);
79    else {
80      Request.UrlPath = it;
81      UrlPathSet = true;
82    }
83  }
84}
85
86HTTPServerRequest::HTTPServerRequest(const httplib::Request &HTTPLibRequest,
87                                     httplib::Response &HTTPLibResponse)
88    : HTTPLibResponse(HTTPLibResponse) {
89  expandUrlPathMatches(HTTPLibRequest.matches, *this);
90}
91
92void HTTPServerRequest::setResponse(HTTPResponse Response) {
93  HTTPLibResponse.set_content(Response.Body.begin(), Response.Body.size(),
94                              Response.ContentType);
95  HTTPLibResponse.status = Response.Code;
96}
97
98void HTTPServerRequest::setResponse(StreamingHTTPResponse Response) {
99  HTTPLibResponse.set_content_provider(
100      Response.ContentLength, Response.ContentType,
101      [=](size_t Offset, size_t Length, httplib::DataSink &Sink) {
102        if (Offset < Response.ContentLength) {
103          StringRef Chunk = Response.Provider(Offset, Length);
104          Sink.write(Chunk.begin(), Chunk.size());
105        }
106        return true;
107      },
108      [=](bool Success) { Response.CompletionHandler(Success); });
109
110  HTTPLibResponse.status = Response.Code;
111}
112
113Error HTTPServer::get(StringRef UrlPathPattern, HTTPRequestHandler Handler) {
114  std::string ErrorMessage;
115  if (!Regex(UrlPathPattern).isValid(ErrorMessage))
116    return createStringError(errc::argument_out_of_domain, ErrorMessage);
117  Server->Get(std::string(UrlPathPattern),
118              [Handler](const httplib::Request &HTTPLibRequest,
119                        httplib::Response &HTTPLibResponse) {
120                HTTPServerRequest Request(HTTPLibRequest, HTTPLibResponse);
121                Handler(Request);
122              });
123  return Error::success();
124}
125
126Error HTTPServer::bind(unsigned ListenPort, const char *HostInterface) {
127  if (!Server->bind_to_port(HostInterface, ListenPort))
128    return createStringError(errc::io_error,
129                             "Could not assign requested address.");
130  Port = ListenPort;
131  return Error::success();
132}
133
134Expected<unsigned> HTTPServer::bind(const char *HostInterface) {
135  int ListenPort = Server->bind_to_any_port(HostInterface);
136  if (ListenPort < 0)
137    return createStringError(errc::io_error,
138                             "Could not assign any port on requested address.");
139  return Port = ListenPort;
140}
141
142Error HTTPServer::listen() {
143  if (!Port)
144    return createStringError(errc::io_error,
145                             "Cannot listen without first binding to a port.");
146  if (!Server->listen_after_bind())
147    return createStringError(
148        errc::io_error,
149        "An unknown error occurred when cpp-httplib attempted to listen.");
150  return Error::success();
151}
152
153void HTTPServer::stop() {
154  Server->stop();
155  Port = 0;
156}
157
158#else
159
160// TODO: Implement barebones standalone HTTP server implementation.
161bool HTTPServer::isAvailable() { return false; }
162
163HTTPServer::HTTPServer() = default;
164
165HTTPServer::~HTTPServer() = default;
166
167void HTTPServerRequest::setResponse(HTTPResponse Response) {
168  llvm_unreachable("no httplib");
169}
170
171void HTTPServerRequest::setResponse(StreamingHTTPResponse Response) {
172  llvm_unreachable("no httplib");
173}
174
175Error HTTPServer::get(StringRef UrlPathPattern, HTTPRequestHandler Handler) {
176  // TODO(https://github.com/llvm/llvm-project/issues/63873) We would ideally
177  // return an error as well but that's going to require refactoring of error
178  // handling in DebuginfodServer.
179  return Error::success();
180}
181
182Error HTTPServer::bind(unsigned ListenPort, const char *HostInterface) {
183  return make_error<HTTPServerError>("no httplib");
184}
185
186Expected<unsigned> HTTPServer::bind(const char *HostInterface) {
187  return make_error<HTTPServerError>("no httplib");
188}
189
190Error HTTPServer::listen() {
191  return make_error<HTTPServerError>("no httplib");
192}
193
194void HTTPServer::stop() {
195  llvm_unreachable("no httplib");
196}
197
198#endif // LLVM_ENABLE_HTTPLIB
199