2016-01-01 22:06:47 -08:00
|
|
|
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
|
|
|
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// In addition, as a special exception, the copyright holders give
|
|
|
|
// permission to link the code of portions of this program with the
|
|
|
|
// OpenSSL library under certain conditions as described in each
|
|
|
|
// individual source file, and distribute linked combinations including
|
|
|
|
// the two.
|
|
|
|
//
|
|
|
|
// You must obey the GNU General Public License in all respects for all
|
|
|
|
// of the code used other than OpenSSL. If you modify file(s) with this
|
|
|
|
// exception, you may extend this exception to your version of the
|
|
|
|
// file(s), but you are not obligated to do so. If you do not wish to do
|
|
|
|
// so, delete this exception statement from your version. If you delete
|
|
|
|
// this exception statement from all source files in the program, then
|
|
|
|
// also delete it here.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//
|
|
|
|
// http.h: classes for HTTP serving. In particular, there are helpers for
|
|
|
|
// serving HTTP byte range requests with libevent.
|
|
|
|
|
|
|
|
#ifndef MOONFIRE_NVR_HTTP_H
|
|
|
|
#define MOONFIRE_NVR_HTTP_H
|
|
|
|
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
#include <functional>
|
|
|
|
#include <iostream>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include <event2/buffer.h>
|
|
|
|
#include <event2/http.h>
|
|
|
|
#include <glog/logging.h>
|
|
|
|
#include <re2/stringpiece.h>
|
|
|
|
|
|
|
|
namespace moonfire_nvr {
|
|
|
|
|
|
|
|
// Wrapped version of libevent's "struct evbuffer" which uses RAII and simply
|
|
|
|
// aborts the process if allocations fail. (Moonfire NVR is intended to run on
|
|
|
|
// Linux systems with the default vm.overcommit_memory=0, so there's probably
|
|
|
|
// no point in trying to gracefully recover from a condition that's unlikely
|
|
|
|
// to ever happen.)
|
|
|
|
class EvBuffer {
|
|
|
|
public:
|
|
|
|
EvBuffer() { buf_ = CHECK_NOTNULL(evbuffer_new()); }
|
|
|
|
EvBuffer(const EvBuffer &) = delete;
|
|
|
|
EvBuffer &operator=(const EvBuffer &) = delete;
|
|
|
|
~EvBuffer() { evbuffer_free(buf_); }
|
|
|
|
|
|
|
|
struct evbuffer *get() {
|
|
|
|
return buf_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Add(const re2::StringPiece &s) {
|
|
|
|
CHECK_EQ(0, evbuffer_add(buf_, s.data(), s.size()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void AddPrintf(const char *fmt, ...) __attribute__((format(printf, 2, 3))) {
|
|
|
|
va_list argp;
|
|
|
|
va_start(argp, fmt);
|
|
|
|
CHECK_LE(0, evbuffer_add_vprintf(buf_, fmt, argp));
|
|
|
|
va_end(argp);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delegates to evbuffer_add_file.
|
|
|
|
// On success, |fd| will be closed by libevent. On failure, it remains open.
|
|
|
|
bool AddFile(int fd, ev_off_t offset, ev_off_t length,
|
|
|
|
std::string *error_message);
|
|
|
|
|
2016-01-09 12:02:36 -08:00
|
|
|
void AddReference(const void *data, size_t datlen,
|
|
|
|
evbuffer_ref_cleanup_cb cleanupfn, void *cleanupfn_arg) {
|
|
|
|
CHECK_EQ(
|
|
|
|
0, evbuffer_add_reference(buf_, data, datlen, cleanupfn, cleanupfn_arg))
|
|
|
|
<< strerror(errno);
|
|
|
|
}
|
|
|
|
|
2016-01-01 22:06:47 -08:00
|
|
|
private:
|
|
|
|
struct evbuffer *buf_;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ByteRange {
|
|
|
|
ByteRange() {}
|
|
|
|
ByteRange(int64_t begin, int64_t end) : begin(begin), end(end) {}
|
|
|
|
int64_t begin = 0;
|
|
|
|
int64_t end = 0; // exclusive.
|
2016-01-09 12:02:36 -08:00
|
|
|
int64_t size() const { return end - begin; }
|
2016-01-01 22:06:47 -08:00
|
|
|
bool operator==(const ByteRange &o) const {
|
|
|
|
return begin == o.begin && end == o.end;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
inline std::ostream &operator<<(std::ostream &out, const ByteRange &range) {
|
|
|
|
out << "[" << range.begin << ", " << range.end << ")";
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper for sending HTTP errors based on POSIX error returns.
|
|
|
|
void HttpSendError(evhttp_request *req, int http_err, const std::string &prefix,
|
|
|
|
int posix_errno);
|
|
|
|
|
2016-01-09 12:02:36 -08:00
|
|
|
class FileSlice {
|
|
|
|
public:
|
|
|
|
virtual ~FileSlice() {}
|
|
|
|
virtual int64_t size() const = 0;
|
|
|
|
virtual bool AddRange(ByteRange range, EvBuffer *buf,
|
|
|
|
std::string *error_message) const = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
class VirtualFile : public FileSlice {
|
2016-01-01 22:06:47 -08:00
|
|
|
public:
|
|
|
|
virtual ~VirtualFile() {}
|
|
|
|
|
|
|
|
// Return the given property of the file.
|
|
|
|
virtual time_t last_modified() const = 0;
|
|
|
|
virtual std::string etag() const = 0;
|
|
|
|
virtual std::string mime_type() const = 0;
|
|
|
|
virtual std::string filename() const = 0; // for logging.
|
2016-01-09 12:02:36 -08:00
|
|
|
};
|
2016-01-01 22:06:47 -08:00
|
|
|
|
2016-01-09 12:02:36 -08:00
|
|
|
// A FileSlice of a pre-defined length which calls a function which fills the
|
|
|
|
// slice on demand. The FillerFileSlice is responsible for subsetting.
|
|
|
|
class FillerFileSlice : public FileSlice {
|
|
|
|
public:
|
|
|
|
using FillFunction =
|
|
|
|
std::function<bool(std::string *slice, std::string *error_message)>;
|
|
|
|
|
|
|
|
void Init(size_t size, FillFunction fn) {
|
|
|
|
fn_ = fn;
|
|
|
|
size_ = size;
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t size() const final { return size_; }
|
|
|
|
|
|
|
|
bool AddRange(ByteRange range, EvBuffer *buf,
|
|
|
|
std::string *error_message) const final;
|
|
|
|
|
|
|
|
private:
|
|
|
|
FillFunction fn_;
|
|
|
|
size_t size_;
|
2016-01-01 22:06:47 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
// Serve an HTTP request |req| from |file|, handling byte range and
|
|
|
|
// conditional serving. (Similar to golang's http.ServeContent.)
|
|
|
|
//
|
|
|
|
// |file| only needs to live through the call to HttpServe itself.
|
|
|
|
// This contract may change in the future; currently all the ranges are added
|
|
|
|
// at the beginning of the request, so if large memory-backed buffers (as
|
|
|
|
// opposed to file-backed buffers) are used, the program's memory usage will
|
|
|
|
// spike, even if the HTTP client aborts early in the request. If this becomes
|
|
|
|
// problematic, this interface may change to take advantage of
|
|
|
|
// evbuffer_add_cb, adding buffers incrementally, and some mechanism will be
|
|
|
|
// added to guarantee VirtualFile objects outlive the HTTP requests they serve.
|
|
|
|
void HttpServe(const VirtualFile &file, evhttp_request *req);
|
|
|
|
|
|
|
|
// Serve a file over HTTP. Expects the caller to supply a sanitized |filename|
|
|
|
|
// (rather than taking it straight from the path specified in |req|).
|
|
|
|
void HttpServeFile(evhttp_request *req, const std::string &mime_type,
|
|
|
|
const std::string &filename, const struct stat &statbuf);
|
|
|
|
|
|
|
|
namespace internal {
|
|
|
|
|
|
|
|
// Value to represent result of parsing HTTP 1.1 "Range:" header.
|
|
|
|
enum class RangeHeaderType {
|
|
|
|
// Ignore the header, serving all bytes in the file.
|
|
|
|
kAbsentOrInvalid,
|
|
|
|
|
|
|
|
// The server SHOULD return a response with status 416 (Requested range not
|
|
|
|
// satisfiable).
|
|
|
|
kNotSatisfiable,
|
|
|
|
|
|
|
|
// The server SHOULD return a response with status 406 (Partial Content).
|
|
|
|
kSatisfiable
|
|
|
|
};
|
|
|
|
|
|
|
|
// Parse an HTTP 1.1 "Range:" header value, following RFC 2616 section 14.35.
|
|
|
|
// This function is for use by HttpServe; it is exposed for testing only.
|
|
|
|
//
|
|
|
|
// |value| on entry should be the header value (after the ": "), or nullptr.
|
|
|
|
// |size| on entry should be the number of bytes available to serve.
|
|
|
|
// On kSatisfiable return, |ranges| will be filled with the satisfiable ranges.
|
|
|
|
// Otherwise, its contents are undefined.
|
|
|
|
RangeHeaderType ParseRangeHeader(const char *value, int64_t size,
|
|
|
|
std::vector<ByteRange> *ranges);
|
|
|
|
|
|
|
|
} // namespace internal
|
|
|
|
|
|
|
|
} // namespace moonfire_nvr
|
|
|
|
|
|
|
|
#endif // MOONFIRE_NVR_HTTP_H
|